La prima versione era poco leggibile: l'ombreggiatura usava solo l'inclinazione frontale, quindi le facce complanari di un pezzo piatto uscivano tutte dello stesso grigio e la forma non si distingueva. Due correzioni: - luce direzionale obliqua invece che frontale, con una rampa di luminosita' piu' ampia, cosi' le facce orientate diversamente si separano davvero; - soprattutto, gli spigoli. Sopra alle superfici si disegnano ora gli spigoli vivi (due facce che formano un angolo netto) e la silhouette (faccia visibile contro faccia nascosta). Sono le linee che rendono riconoscibile un pezzo meccanico, le stesse che traccerebbe un disegnatore. Per distinguere uno spigolo vivo da una suddivisione interna della tassellatura servono le facce adiacenti, che un STL non fornisce perche' ripete i vertici a ogni triangolo. Mesh.indexed() li fonde e ricostruisce la topologia: sul pezzo di prova 912 ripetizioni diventano 144 vertici reali, 304 facce e 456 spigoli, senza bordi liberi. La mesh risulta chiusa e la caratteristica di Eulero indica cinque fori passanti, come il pezzo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
258 lines
11 KiB
Markdown
258 lines
11 KiB
Markdown
# creoparams
|
||
|
||
Estrae parametri e proprietà dai file nativi Creo Parametric (`.prt`, `.asm`,
|
||
`.drw`) **senza avviare Creo** e senza SDK commerciali. Solo Python 3, nessuna
|
||
dipendenza esterna.
|
||
|
||
## Uso
|
||
|
||
Un file alla volta — è il caso d'uso principale:
|
||
|
||
```bash
|
||
python3 -m creoparams 9258400201.prt.12 # tabella a video
|
||
python3 -m creoparams 9258400201.prt.12 --quiet -o - # JSON su stdout
|
||
python3 -m creoparams 9258400201.prt.12 -o scheda.json
|
||
```
|
||
|
||
Con un solo file l'output JSON è un **oggetto**; con più file una **lista**.
|
||
`-o -` scrive su stdout, quindi il comando si può mettere in pipe.
|
||
|
||
Un file indicato esplicitamente viene sempre elaborato così com'è: la scelta
|
||
automatica dell'ultima versione riguarda solo l'esplorazione di una cartella.
|
||
|
||
In lotto, quando serve:
|
||
|
||
```bash
|
||
python3 -m creoparams . --csv metadati.csv # cartella corrente
|
||
python3 -m creoparams /rete/archivio -r --csv indice.csv
|
||
```
|
||
|
||
Opzioni principali:
|
||
|
||
| Opzione | Effetto |
|
||
|---|---|
|
||
| `-o`, `--json FILE` | scrive il JSON su file, oppure su stdout con `-` |
|
||
| `--csv FILE` | tabella riepilogativa CSV (Excel, separatore `;`) |
|
||
| `-r`, `--recursive` | esplora le sottocartelle |
|
||
| `--all-versions` | elabora tutte le versioni (`.prt.1`, `.prt.2`, …) invece della sola più recente |
|
||
| `--features` | include i parametri delle feature (fori, lavorazioni) oltre a quelli di modello |
|
||
| `--system` | include i parametri generati da Creo (`PTC_*`, `SMT_*`) |
|
||
| `--preview [DEST]` | salva l'anteprima incorporata come JPEG |
|
||
| `--3d [DEST]` | genera la pagina HTML per ruotare il modello nel browser |
|
||
| `--quiet` | non stampa la tabella |
|
||
|
||
Codici di uscita: `0` estrazione riuscita, `1` nessun file trovato, `2` nessun
|
||
file elaborabile (file mancante o non nativo Creo). I messaggi diagnostici
|
||
vanno su stderr, così stdout resta pulito per le pipe.
|
||
|
||
Nella tabella a video, il marcatore a inizio riga indica l'affidabilità:
|
||
spazio = confermato in due rappresentazioni indipendenti, `?` = trovato una
|
||
volta sola, `~` = valore binario non decodificato, `!` = valori discordanti,
|
||
`-` = parametro atteso ma assente.
|
||
|
||
## Anteprima incorporata
|
||
|
||
Ogni file contiene una miniatura JPEG dell'ultimo salvataggio: il render
|
||
ombreggiato del pezzo nei modelli, l'immagine della tavola nei disegni. È la
|
||
stessa che Creo mostra nella finestra "Apri".
|
||
|
||
```bash
|
||
python3 -m creoparams 9258400201.prt.12 --preview # accanto all'originale
|
||
python3 -m creoparams 9258400201.prt.12 --preview foto.jpg # nome esplicito
|
||
python3 -m creoparams . --preview anteprime/ # tutte in una cartella
|
||
```
|
||
|
||
Le dimensioni compaiono nell'intestazione della tabella e in `preview` nel
|
||
JSON; i byte dell'immagine restano fuori dal record, si ottengono con
|
||
`read_preview()`.
|
||
|
||
La miniatura non è referenziata dall'indice delle sezioni, quindi va cercata
|
||
per firma. Per non scambiare dati binari qualsiasi per un'immagine, ogni
|
||
candidato viene validato percorrendone i segmenti JPEG: sui file campione
|
||
questo produce esattamente un risultato per file, e il test
|
||
`test_una_sola_anteprima_per_file` lo verifica.
|
||
|
||
Attenzione: l'anteprima fotografa **l'ultimo salvataggio**, non lo stato
|
||
corrente del modello. Se un pezzo è stato modificato e salvato da un CAD che
|
||
non rigenera la miniatura, l'immagine può essere più vecchia dei parametri.
|
||
|
||
## Contenuto dei disegni (`.drw`)
|
||
|
||
Un disegno non contiene la tabella parametri del modello, ma contiene la
|
||
**rappresentazione vettoriale della tavola**, in blocchi compressi con
|
||
`compress(1)` (il vecchio `.Z` Unix). Il programma li decomprime ed estrae
|
||
tutti i testi:
|
||
|
||
```bash
|
||
python3 -m creoparams 9258400205.drw.10
|
||
```
|
||
|
||
```
|
||
118 testi nella tavola (68 unici). I parametri del modello stanno nel .prt.
|
||
CODICE ... STAMPIGLIATURA ... PESO FINITO ... DISEGNATO ... SCALA
|
||
9258400205 ... 0.197 ... 9/02/2026 ... 1:2 ... Grilli
|
||
```
|
||
|
||
Questo rende le tavole **ricercabili per contenuto**: voci del cartiglio,
|
||
note, tolleranze, quote. Da notare che nel disegno i valori compaiono
|
||
**già risolti come testo** — compreso il peso — mentre nel `.prt` sono
|
||
numeri in codifica binaria.
|
||
|
||
Attenzione: i testi sono estratti in ordine di comparsa, **non associati
|
||
alla rispettiva etichetta**. Accoppiare `PESO FINITO` al suo valore richiede
|
||
le coordinate di ogni testo, che non sono ancora decodificate (vedi sotto).
|
||
Un disegno riporta inoltre lo stato all'ultima rigenerazione, non
|
||
necessariamente quello attuale del modello.
|
||
|
||
Nel JSON tutto questo sta sotto `drawing`, insieme all'elenco delle primitive
|
||
grafiche presenti (`prim_text`, `prim_arc`, `prim_multiline`, …).
|
||
|
||
## Vista 3D del modello
|
||
|
||
```bash
|
||
python3 -m creoparams 9258400207.prt.11 --3d viste/
|
||
```
|
||
|
||
Produce una **pagina HTML autonoma**: la apri con un doppio clic e ruoti il
|
||
pezzo col mouse, ingrandisci con la rotella, sposti col tasto destro. Nessuna
|
||
libreria esterna, nessuna connessione, funziona anche da una cartella di rete.
|
||
|
||
Un pezzo meccanico solo ombreggiato risulta illeggibile, perché le facce
|
||
complanari hanno tutte lo stesso colore. Sopra alle superfici vengono quindi
|
||
disegnati **gli spigoli vivi** (dove due facce formano un angolo netto) e la
|
||
**silhouette** (dove una faccia visibile incontra una nascosta) — le stesse
|
||
linee che traccerebbe un disegnatore. Per distinguerli servono le facce
|
||
adiacenti a ogni spigolo, che `Mesh.indexed()` ricava fondendo i vertici
|
||
ripetuti dell'STL (sul pezzo di prova: 912 ripetizioni → 144 vertici reali).
|
||
|
||
La forma **non** viene dal `.prt`: viene da una **mesh STL esportata da Creo**,
|
||
cercata automaticamente accanto al modello (`9258400207.prt.11` →
|
||
`9258400207.stl`). Se manca, il programma lo dice invece di fallire.
|
||
|
||
Quando la mesh c'è, il record JSON guadagna un blocco `geometry` con ingombro,
|
||
volume e area — le uniche grandezze geometriche ottenibili senza Creo:
|
||
|
||
```json
|
||
"geometry": {"triangles": 304, "size": [150.0, 4.0, 150.0],
|
||
"volume": 87928.502, "area": 49050.5}
|
||
```
|
||
|
||
Il **peso non viene calcolato**: servirebbe la densità, che dipende dal
|
||
materiale, e il materiale qui è testo libero (`Anticordal 100` è alluminio,
|
||
`ACC.INOX LAMIERA SP.4` è acciaio). Moltiplicare per la densità sbagliata
|
||
darebbe un errore del 300 %.
|
||
|
||
### Perché serve la mesh esportata
|
||
|
||
La geometria dentro il `.prt` è scritta nel formato del kernel **Granite** di
|
||
PTC ed è compressa. Verificato con un attacco a testo noto: presi da un export
|
||
STEP i 22 valori esatti del modello (raggi 1,6 / 2,1 / 2,5 / 2,75 / 5,25 e
|
||
coordinate fino a 75,0) e cercati nel file in cinque codifiche su tutti i
|
||
blocchi — **nessun riscontro**.
|
||
|
||
Per leggere i `.prt` nativi senza Creo esiste la via ufficiale: il
|
||
[Granite Interoperability Kernel](https://support.ptc.com/products/granite/gplugs)
|
||
di PTC, che legge geometria, attributi e parametri senza Creo installato.
|
||
Quando fosse disponibile, si innesta al posto di `mesh.py` senza toccare il
|
||
resto.
|
||
|
||
## Come funziona
|
||
|
||
Un file nativo Creo è un container con header ASCII in chiaro, un indice delle
|
||
sezioni (`UGC_TOC`) e un corpo binario. La maggior parte delle sezioni **non è
|
||
compressa**, e la tabella dei parametri è leggibile direttamente.
|
||
|
||
Ogni parametro è presente in due rappresentazioni indipendenti:
|
||
|
||
- la **tabella estesa** (sezione `LargeText`), che contiene i parametri di modello;
|
||
- la **copia neutra** (sezione `NeuPrtSld`), che contiene anche quelli delle feature.
|
||
|
||
Il lettore percorre entrambe e confronta i risultati. Da qui derivano due
|
||
informazioni che finiscono nell'output:
|
||
|
||
- `owner`: `model` se il parametro compare nella tabella estesa, `feature` se
|
||
vive solo nella copia neutra (è un parametro di un foro, non del pezzo);
|
||
- `confidence`: `0.99` se le due rappresentazioni concordano, valori più bassi
|
||
se il dato compare una volta sola o se le copie divergono (`status: conflict`).
|
||
|
||
## Formato di output
|
||
|
||
```json
|
||
{
|
||
"schema_version": 1,
|
||
"file": { "name": "9258400201.prt.12", "sha256": "994dd3a8…", "size": 450797 },
|
||
"model": { "name": "9258400201", "kind": "PART", "creo_version": "9.0.3.0" },
|
||
"extraction": { "method": "ugc_native_reader", "holds_model_parameters": true },
|
||
"parameters": {
|
||
"DENOMINAZIONE": {
|
||
"type": "string", "owner": "model", "scope": "user",
|
||
"value": "Carter lato destro", "status": "found", "confidence": 0.99,
|
||
"sections": ["LargeText", "NeuPrtSld"]
|
||
},
|
||
"PESO": {
|
||
"type": "real", "status": "encoded_not_decoded",
|
||
"raw": "90f8382356ca2d", "driven_by": "value(d_val)"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
L'`sha256` permette di verificare in seguito se un record estratto corrisponde
|
||
ancora al file da cui proviene.
|
||
|
||
## Limiti noti
|
||
|
||
**Parametri numerici non decodificati.** `PESO` e `DENSITA` restano
|
||
`encoded_not_decoded`, con i byte grezzi in `raw`. La codifica è però ora in
|
||
gran parte capita: sono **double IEEE big-endian con i byte bassi omessi**,
|
||
preceduti da un byte-tag. La prova viene dai disegni, dove `size_x` = `2f 7a 40`
|
||
e `size_y` = `2f 72 90` corrispondono a 420,0 × 297,0 — un A3 esatto — e dove
|
||
le coordinate di una polilinea danno differenze regolari (−9,72 / −6,48: un
|
||
tratteggio). Applicando la stessa regola, `DENSITA` risulta **7,8e-06**, la
|
||
densità dell'acciaio, identica in tutti i file.
|
||
|
||
Quello che manca è la mappatura del byte-tag iniziale: sui pesi produce due
|
||
candidati, uno plausibile e uno assurdo, e su un file nessuno dei due convince.
|
||
Finché la regola non è certa il programma **non pubblica alcun valore**: un
|
||
peso plausibile ma sbagliato è peggio di un peso assente. Per chiudere servono
|
||
due o tre valori di `PESO` letti da Creo.
|
||
|
||
Nel frattempo i disegni offrono una via alternativa: nel `.drw` il peso compare
|
||
**già risolto come testo** nel cartiglio.
|
||
|
||
**Copertura verificata.** Il lettore è stato validato su file `PART`,
|
||
`PART/SHEETMETAL` e `DRAWING` scritti da **Creo 9.0.3.0**. Gli **assiemi
|
||
(`.asm`) non sono mai stati provati**: nessun file campione era disponibile.
|
||
Il codice li gestisce come i part, ma è una previsione, non una verifica.
|
||
Idem per family table e versioni Creo precedenti: prima di usare il lettore
|
||
sull'archivio storico va provato su un campione di file più vecchi.
|
||
|
||
**Family table.** Le istanze non hanno un file proprio: vivono nel generic. Su
|
||
un generic con family table il lettore restituirebbe i valori del generic, non
|
||
quelli dell'istanza. Nei file campione la sezione `FamilyInf` è vuota, quindi
|
||
il caso non è mai stato esercitato.
|
||
|
||
## Test
|
||
|
||
```bash
|
||
python3 -m unittest test_regression -v
|
||
```
|
||
|
||
I test girano sui `.prt` presenti nella cartella e verificano che i parametri
|
||
aziendali obbligatori siano letti con confidenza alta. `KNOWN` in
|
||
`test_regression.py` contiene i valori verificati a mano: **è la rete di
|
||
sicurezza vera del progetto** e va estesa ogni volta che si confronta un nuovo
|
||
file con Creo.
|
||
|
||
## Struttura
|
||
|
||
| File | Contenuto |
|
||
|---|---|
|
||
| [creoparams/ugc.py](creoparams/ugc.py) | header, indice delle sezioni e blocchi del container |
|
||
| [creoparams/params.py](creoparams/params.py) | riconoscimento dei record parametro nel binario |
|
||
| [creoparams/lzw.py](creoparams/lzw.py) | decompressione compress(1) dei blocchi interni |
|
||
| [creoparams/drawing.py](creoparams/drawing.py) | testi e primitive delle tavole |
|
||
| [creoparams/preview.py](creoparams/preview.py) | anteprima JPEG incorporata |
|
||
| [creoparams/extract.py](creoparams/extract.py) | aggregazione, confidenza, record di output |
|
||
| [creoparams/cli.py](creoparams/cli.py) | riga di comando ed esportazioni |
|