Reverse engineering di 9253000117a.prt.35: contenitore (offset dall'ultimo campo dell'intestazione, indice a catena), grammatica completa delle sezioni (id progressivi dei campi, tabella "dopo X viene Y", marcatori di chiusura), tabella dei double verificata sulla norma delle matrici, sezioni compresse e catalogo del contenuto. Parser Python in scripts/psb/. docs/history.md raccoglie stato, metodo e punti aperti per riprendere il lavoro. Co-Authored-By: Claude <noreply@anthropic.com>
372 lines
21 KiB
Markdown
372 lines
21 KiB
Markdown
# Il formato PSB: leggere tutte le sezioni di un `.prt`/`.asm` senza Creo
|
||
|
||
Come è scritto **per intero** il contenuto di un file Creo, e come leggerlo campo per campo con
|
||
nome e valore, senza àncore a pattern. Ricavato per reverse engineering su
|
||
`9253000117a.prt.35` (Creo 2.0 M200, generazione `1632`, 2 MB) e poi provato sugli 89 file di
|
||
`raw/`. Il nome "PSB" è quello che il formato dà a se stesso (`PSB_Tokens`, §7).
|
||
|
||
Questo doc **sostituisce** le parti di [`formato-prt-feature.md`](formato-prt-feature.md) e
|
||
[`formato-prt-geometria.md`](formato-prt-geometria.md) che leggevano i record cercando sequenze
|
||
di byte: quelle àncore restano valide come esempi, ma il modo giusto di leggere è quello qui
|
||
sotto. Le correzioni puntuali a quei due doc sono in §9.
|
||
|
||
Implementazione di riferimento: [`scripts/psb/psb.py`](../scripts/psb/psb.py) (parser),
|
||
[`scripts/psb/prt.py`](../scripts/psb/prt.py) (contenitore),
|
||
[`scripts/psb/unz.py`](../scripts/psb/unz.py) (sezioni compresse). È Python (solo libreria
|
||
standard), non C#: è lo strumento con cui è stato fatto il reverse engineering, non codice
|
||
dell'app. Nessun codice di `PrepStep/` lo usa.
|
||
|
||
**Livelli di prova** (come negli altri doc):
|
||
|
||
1. **Verifica interna forte**: la sezione si legge fino all'ultimo byte, con i conteggi degli
|
||
array rispettati; i numeri reali sono controllati su vincoli geometrici (righe delle matrici
|
||
di rotazione a norma 1: 5397 su 5397 in `9253000117a.prt.35`).
|
||
2. **Su scala**: 2152 sezioni su 2226 lette fino in fondo negli 89 file di `raw/` (tutte le
|
||
generazioni, da `1322` a `2063`). Le 74 che falliscono sono in §8.
|
||
3. **Contro Creo**: nessuna. Nessun valore di questo doc è stato confrontato con il modello
|
||
aperto in Creo. I nomi dei campi sono quelli scritti nel file; il loro *significato* è
|
||
dedotto dal nome.
|
||
|
||
## 1. Il contenitore
|
||
|
||
```
|
||
#UGC:2 PART 1632 1060 805 1 1 15 3300 2015440 000002cc \
|
||
...
|
||
#-END_OF_UGC_HEADER
|
||
#Creo TM 2.0 (c) 2020 by PTC Inc. All Rights Reserved. M200
|
||
#UGC_TOC 2 32 81 17####...
|
||
DEPDB_DATA ab1 19 d 1632 9 -1 05e1 260f####
|
||
...
|
||
NEXT_TOC_ENTRY 1f8679 3cc 0 1632 1 -1 0000 0000####
|
||
```
|
||
|
||
- **L'ultimo campo della prima riga** (`000002cc`) è la lunghezza dell'intestazione: è la
|
||
costante da sommare a tutti gli offset dell'indice. `inizio = 0x2cc + offset`.
|
||
- Ogni sezione comincia con una riga `#<Nome>\n`, **compresa** nell'offset e nella lunghezza.
|
||
Il corpo è quindi `[0x2cc + offset + len(nome) + 2, 0x2cc + offset + lunghezza)`.
|
||
- **L'indice è a catena**: la voce `NEXT_TOC_ENTRY` punta (stessa regola) a un secondo
|
||
`#UGC_TOC`, in fondo al file, con altre sezioni. Vanno seguiti tutti fino a `#END_OF_UGC`.
|
||
- Campi di una riga: `nome offset lunghezza dimensione generazione tipo metodo crc crc`.
|
||
`dimensione` è la lunghezza del corpo **decompresso** (per le sezioni in chiaro è
|
||
`lunghezza − len(nome) − 2`).
|
||
|
||
Il campo `tipo` in questo file:
|
||
|
||
| Tipo | Contenuto | Sezioni |
|
||
|---|---|---|
|
||
| `9` | PSB in chiaro | quasi tutte |
|
||
| `a` | PSB compresso Unix `compress` (`1f 9d 10`, LZW a 16 bit): si decomprime e si legge come le altre; la dimensione ottenuta coincide con quella dichiarata | `ND:0:*`, `SolidPersistTable`, `SolidPrimdata` |
|
||
| `c` | comincia con `1f 9d 10` ma **non** è un LZW standard | `DispDataTable` (non letto) |
|
||
| `11` | JPEG (`ff d8 ff e0 ... JFIF`): la miniatura del modello | `THMB_IMG_MAIN` |
|
||
| `b` | non decodificato: primi 4 byte in chiaro (`e0 00 Fi`, `e0 00 Fe`), poi entropia massima, stessa lunghezza del testo in chiaro | `IndentTag`, `FeatRefData` |
|
||
|
||
Il tipo `b` ha lo stesso aspetto delle sezioni "tipo `11`" dei file Wildfire descritte in
|
||
`formato-prt-feature.md` §1.3. Resta il blocco principale.
|
||
|
||
## 2. La grammatica
|
||
|
||
Una sezione è un albero di campi. I byte `e0`-`ff` sono marcatori, gli altri dati.
|
||
|
||
| Byte | Significato |
|
||
|---|---|
|
||
| `e0 <tipo> <nome>\0` | **dichiara** un campo e ne dà subito il valore |
|
||
| `e1` | puntatore nullo |
|
||
| `e2`, `e3` | apre una struttura (non distinte dal lettore) |
|
||
| `e4`, `e5`, `e6` | dentro un array: ripeti il valore precedente 1, 2, 3 volte |
|
||
| `e7 <n>` | dentro un array: ripeti il valore precedente `n` volte |
|
||
| `e8`-`ed` | forme "grezze" dei numeri (§3) |
|
||
| `f0`-`f3` | chiudi 0, 1, 2, 3 contenitori (§2.3) |
|
||
| `f4 <n>` | chiudi `n` contenitori |
|
||
| `f5` | prefisso di un intero: bit 31 acceso |
|
||
| `f6` | intero −1 |
|
||
| `f7 <id>` | il prossimo valore è del campo numero `id` (§2.2) |
|
||
| `f8 <n>` | array a una dimensione di `n` elementi |
|
||
| `f9 <a> <b>` | array a due dimensioni (`a`×`b` numeri; per gli array di strutture `a` è il totale) |
|
||
| `fa <k> <d1>..<dk>` | array a `k` dimensioni |
|
||
| `fb` | segue l'intestazione di un array di strutture, prima del primo elemento |
|
||
| `fc <n>` | array a due dimensioni: cambia solo il numero di righe, colonne come l'ultima volta |
|
||
|
||
Tipi di campo (il byte dopo `e0`):
|
||
|
||
| Tipo | Valore |
|
||
|---|---|
|
||
| `00` | puntatore: `e1`, struttura (`e2`/`e3`), o array di strutture (`f8 n [f7 id] [fb]`) |
|
||
| `01`, `05`, `08`, `09` | intero (§3.1). `09` è senza segno (flag); `05`/`08` non distinti |
|
||
| `02` | `double` (§3.2) |
|
||
| `06` | `float` a 32 bit (§3.3) |
|
||
| `04`, `07` | array di byte: ogni byte vale se stesso; un byte ≥ `e0` è preceduto da `e8` |
|
||
| `03`, `0a` | stringa Latin1 terminata da `\0`, o `e1` |
|
||
|
||
Un campo numerico o stringa può essere un array: `f8`/`f9`/`fa` prima dei valori.
|
||
|
||
### 2.1 Gli id dei campi
|
||
|
||
Ogni `e0` riceve un **numero progressivo** (dal primo `e0` della sezione = 1). Lo stesso nome in
|
||
due strutture diverse ha due id diversi. È la chiave di tutto: `f7 <id>` si riferisce a quel
|
||
numero. Verifica: l'intestazione di ogni array di strutture ripete l'id del proprio campo
|
||
(`unit_arr` è il 20° campo di `BasicText` e l'array è `f8 03 f7 14 fb`).
|
||
|
||
### 2.2 Modalità posizionale: la tabella "dopo X viene Y"
|
||
|
||
Un campo è scritto col nome **una sola volta**. Dopo, i valori arrivano nudi. Per sapere di
|
||
quale campo è un valore nudo, chi scrive e chi legge tengono la stessa tabella:
|
||
|
||
```
|
||
next[id] = l'id del campo scritto subito dopo, l'ultima volta che id è stato scritto
|
||
```
|
||
|
||
- Se il prossimo campo è `next[ultimo]`, il valore è scritto e basta.
|
||
- Altrimenti è preceduto da `f7 <id>`.
|
||
- La tabella è **piatta** (non tiene conto dell'annidamento) e si aggiorna a ogni campo, anche
|
||
dopo un `f7` e dopo un campo dichiarato per nome.
|
||
- Ogni elemento di un array di strutture è una nuova occorrenza del campo-array: per questo
|
||
dopo il primo elemento si trova `f1 f7 <id array>` (insegna che "dopo l'ultimo campo
|
||
dell'elemento viene un altro elemento") e dopo gli altri non c'è nulla.
|
||
- Un campo mai dichiarato e nullo **non viene scritto**: compare col nome la prima volta che ha
|
||
un valore, anche in mezzo a un record posizionale.
|
||
|
||
**Lunghezza degli array in modalità posizionale**: `f8 <n>` compare solo se `n` è diverso
|
||
dall'ultima volta che quel campo è stato scritto. Eccezione osservata: un array di stringhe più
|
||
corto del precedente arriva senza `f8` (`edit_header` in `BasicData`: 6 stringhe, poi 5); il
|
||
lettore si ferma al primo byte < `20`. È un'euristica, non una regola capita.
|
||
|
||
### 2.3 Dove va un campo: la pila dei contenitori
|
||
|
||
Strutture, array e loro elementi sono contenitori su una pila. Tre regole:
|
||
|
||
1. **Valore nudo senza `f7`**: va nel contenitore più vicino alla cima che possiede quel campo;
|
||
quelli sopra si chiudono in silenzio.
|
||
2. **Campo con nome o `f7`, con marcatore `f0`-`f4`**: si chiudono esattamente quel numero di
|
||
contenitori (`f0` = nessuno). Il marcatore sta **dopo** il nome del campo.
|
||
3. **Campo con nome o `f7`, senza marcatore**: se il campo atteso (`next[ultimo]`) appartiene al
|
||
contenitore **padre** di quello in cui è stato scritto l'ultimo campo, quest'ultimo si chiude;
|
||
altrimenti non si chiude nulla.
|
||
|
||
Casi particolari, tutti osservati:
|
||
|
||
- **Alias di tipo**: `e3 f7 <id>` con `id` di un'altra struttura vuol dire "questa struttura ha
|
||
lo stesso schema di quella" (`next_feat_ptr 00 f2 e3 f7 03`: il record successivo usa i campi
|
||
del primo).
|
||
- **Puntatori "al successivo"** (`next_feat_ptr`, `next_cntr_ptr`, `relcode_tree_next`): sono
|
||
scritti **dopo aver chiuso** la struttura a cui appartengono, quindi al livello del padre. Le
|
||
liste concatenate risultano così sequenze piatte, non annidamenti.
|
||
- **Campi a livello di array**: `next` in `ref_part_table` è dichiarato dopo aver chiuso il
|
||
primo elemento e ricompare (`e1`) dopo ogni elemento.
|
||
- **Varianti di unione**: il nome porta la variante tra parentesi (`data(sld_trace)`,
|
||
`srf_prim_ptr(cylinder)`, `typed_data(MTTyped_CreateData)`). Lo stesso id può comparire in
|
||
contenitori diversi.
|
||
|
||
**Cosa è solido e cosa no.** La sequenza *campo → valore* dipende solo da `next[]` e dai tipi:
|
||
è la parte provata (tutte le sezioni lette fino all'ultimo byte). L'annidamento dipende dalle
|
||
regole 1-3, che sono ricavate per tentativi: in `AllFeatur`, `MdlStatus` e `FeatDefs` di
|
||
questo file la pila alla fine non torna a zero (un contenitore in più o in meno). I valori sono
|
||
giusti, il livello di qualche campo può non esserlo.
|
||
|
||
## 3. I numeri
|
||
|
||
### 3.1 Interi
|
||
|
||
| Primo byte | Valore |
|
||
|---|---|
|
||
| `00`-`7f` | il byte |
|
||
| `80`-`bf` | 2 byte, 14 bit |
|
||
| `c0`-`df` | 3 byte, 21 bit |
|
||
| `e8` / `e9` / `ea` / `eb` | 1 / 2 / 3 / 4 byte grezzi (`e9` ipotizzato) |
|
||
| `f5 <intero>` | intero con il bit 31 acceso (`hostid` `f5 eb 2c 14 fd cf` = `ac14fdcf` = 172.20.253.207) |
|
||
| `f6` | −1 |
|
||
|
||
### 3.2 `double`
|
||
|
||
Il primo byte è un tag. `T` è il byte alto del `double` IEEE big-endian.
|
||
|
||
| Tag | Byte dopo | Valore |
|
||
|---|---|---|
|
||
| `00`-`03` | 1 costante + (tag+1) byte | la costante, con gli ultimi (tag+1) byte sostituiti: `00 0c 9a` = 0,1 esatto; `02 11 66 89 30` = π con rumore |
|
||
| `04`-`18` | 0 | costante (tabella sotto) |
|
||
| `19`-`2f` | vedi modi | positivi, 5 tag per ogni `T` da `3c` a `40`: `T = 0x3c + (tag − 0x17) / 5` |
|
||
| `30`-`48` | vedi modi | negativi, `T` da `bc` a `c0`: `T = 0xbc + (tag − 0x30) / 5` |
|
||
| `49`-`a4` | 6 | positivi: i primi 2 byte sono `0x3f75 + tag` (da `3fbe` a `4019`) |
|
||
| `a5`-`df` | 6 | negativi: i primi 2 byte sono, in ordine, `bfd0`...`c00d` **saltando** `bfd2`, `bfdf` e `bffe` |
|
||
| `ea` | 3 | 3 byte alti, il resto zero |
|
||
| `ec` | 7 | 7 byte alti, l'ultimo zero |
|
||
| `ed` | 8 | il `double` intero |
|
||
|
||
Modi dei tag a famiglie di 5 (resto di `(tag − base) / 5`):
|
||
|
||
| Resto | Byte dopo | Composizione |
|
||
|---|---|---|
|
||
| 4 | 2 | `T b1 b2` e 5 zeri (`2f 59 00` = 100) |
|
||
| 3 | 2 | `T b1 b2` e `b2` ripetuto (`2e 1b 33` = 6,8) |
|
||
| 2 | 7 | `T` e 7 byte |
|
||
| 1 | 6 | `T`, 6 byte, uno zero |
|
||
| 0 | 3 | `T`, 3 byte, 4 zeri |
|
||
|
||
La famiglia di `T = 3c` ha solo i tag `19`, `1a`, `1b`: `17` e `18` sono costanti.
|
||
|
||
Costanti a un byte:
|
||
|
||
| Tag | Valore | Prova |
|
||
|---|---|---|
|
||
| `07` / `0a` / `0c` | 0,001 / 0,01 / 0,1 | tolleranze di default per numero di decimali (`BasicData`), ISO 2768 sulle quote |
|
||
| `0d` | −0,5 | **ipotesi** (un solo caso) |
|
||
| `0e` / `0f` / `10` | 0,5 / 1 / −1 | matrici ortonormali |
|
||
| `11` / `12` / `13` / `14` | π / π/2 / 2π / 3π/2 | estremi angolari dei cilindri; stesso rumore (`+0x112e0c` sulla mantissa) nelle forme `02 xx ...` |
|
||
| `15` / `16` / `17` | −π / −π/2 / −2π | idem |
|
||
| `18` | 0 | ovunque |
|
||
| `04` | **ignoto** | compare come componente di tangente e come estremo di un inviluppo |
|
||
| `05`, `06`, `08`, `09`, `0b` | mai visti | |
|
||
|
||
Prove: le righe e colonne di tutte le matrici `local_sys` hanno norma 1 con questa tabella
|
||
(5397 su 5397 in questo file); i prefissi `3fd0`-`3fef` e `bfd0`-`bfef` sono verificati uno a
|
||
uno sugli 89 file risolvendo il modulo dalla norma. Fuori da quell'intervallo valgono solo
|
||
alcuni punti fermi: `86` = `3ffb` (√3), `8c` = `4001` (2,2046 lb/kg), `8f` = `4004` (5π/6),
|
||
`ce` = `bffb`, `d9` = `c007` (−3 + ε). Il buco `bffe` (e non `bffd`) è una **scelta non
|
||
provata**: uno dei due manca, non so quale.
|
||
|
||
### 3.3 `float` a 32 bit (tipo `06`)
|
||
|
||
Usati nei dati di visualizzazione (`SolidPrimdata`, `SolidPersistTable`).
|
||
|
||
| Tag | Byte dopo | Valore |
|
||
|---|---|---|
|
||
| `00` / `0b` / `28` | 0 | 0 / +1 / −1 (segno ricavato dalle normali delle facce estreme) |
|
||
| `30`-`3f` | 3 | byte alto `0x37 + (tag − 0x30)`, positivi |
|
||
| `40`-`4f` | 3 | byte alto `0xb7 + (tag − 0x40)`, negativi |
|
||
| `50`-`a3` | 2 | positivi: i primi 2 byte da una **tabella non lineare** |
|
||
| `a4`-`df` | 2 | negativi: idem |
|
||
| `e8` / `e9` / `ea` / `eb` | 1 / 2 / 3 / 4 | byte alti grezzi, il resto zero |
|
||
|
||
La tabella dei tag `50`-`df` **non è ricostruita**: sono prefissi scelti per frequenza, non una
|
||
formula. Voci ricavate dalle normali unitarie: `63`=`3e1d`, `67`=`3e46`, `68`=`3e80`,
|
||
`69`=`3e84`, `6a`=`3ea6`, `6d`=`3f08`, `6e`=`3f0a`, `6f`=`3f0b`, `70`=`3f0c`, `71`=`3f0d`,
|
||
`72`=`3f0e`, `73`=`3f0f`, `74`=`3f10`, `75`=`3f17`, `76`=`3f21`, `77`=`3f57`; `bc`=`be80`,
|
||
`bd`=`be84`, `bf`=`becf`, `c0`=`bee4`, `c1`=`beea`, `c4`=`bf0a`, `c5`=`bf1c`, `c6`=`bf20`,
|
||
`c7`=`bf27`, `c8`=`bf32`, `ca`=`bf60`, `cb`=`bf66`, `cc`=`bf7a`. Il parser di riferimento usa
|
||
una formula lineare **sbagliata** per questi tag: la lunghezza è giusta (la lettura prosegue),
|
||
il valore no. Finché la tabella non è completa la mesh non si può esportare.
|
||
|
||
## 4. Algoritmo di lettura
|
||
|
||
```
|
||
pila = [], ultimo = nessuno, next = {}, campi = {}
|
||
finché ci sono byte:
|
||
m = marcatore f0-f4 se presente
|
||
se il byte è e0: # campo con nome
|
||
leggi tipo e nome; m = marcatore dopo il nome, se presente
|
||
applica la regola 2 (m presente) o 3 (m assente) di §2.3
|
||
id = nuovo numero; il campo appartiene al contenitore in cima
|
||
altrimenti:
|
||
se m presente: chiudi m contenitori
|
||
se il byte è f7: id = intero che segue (poi regola 3 se m assente)
|
||
altrimenti: id = next[ultimo]
|
||
porta in cima il contenitore di id (regola 1; alias se la cima è una struttura vuota)
|
||
next[ultimo] = id; ultimo = id
|
||
leggi il valore secondo il tipo di id; strutture e array vanno sulla pila
|
||
```
|
||
|
||
Uso:
|
||
|
||
```powershell
|
||
python scripts/psb/prt.py 9253000117a.prt.35 # indice delle sezioni
|
||
python scripts/psb/psb.py 9253000117a.prt.35 '*' out # ogni sezione in out/<Nome>.txt
|
||
python scripts/psb/psb.py 9253000117a.prt.35 FullMData out
|
||
python scripts/psb/unz.py 9253000117a.prt.35 out # decomprime le sezioni .Z in out/*.bin
|
||
```
|
||
|
||
## 5. Cosa c'è in ogni sezione
|
||
|
||
Nomi di radice e campi come nel file. "Letto" = arriva all'ultimo byte.
|
||
|
||
| Sezione | Radice | Contenuto |
|
||
|---|---|---|
|
||
| `BasicText` | `Sld_BasicText` | proprietà di massa (`density`, `volume`, `cg`, `inertia`, `surfarea`), unità (`unit_arr`: `MM`, `KILOGRAM`, `SECS`; `principal_sys_units` = `millimeter Kilogram Sec (mmKs)`) |
|
||
| `BasicData` | `Sld_BasicData` | tolleranze di default, tabelle ISO (`iso_tol_tbls_ptr`, classi `Fine`...`Very Coarse`), tabelle dei fori (`holes_arr`) |
|
||
| `FullMData` | `Sld_FullData` | **quote** (`dim_array`, 788 voci: `dim_type`, `feat_id`, `value`, `tol_ptr.value`), modelli referenziati (`ref_part_table`) |
|
||
| `AllFeatur` | `Sld_Features` | lista delle feature (`first_feat_ptr`/`next_feat_ptr`): `id`, `feat_type_ptr.type`, `sect_name`, tabelle delle geometrie create, date |
|
||
| `FeatDefs` | `Sld_FeatDefs` | definizione di ogni feature (`feat_defs_<tipo>`), schizzi, istruzioni di posizionamento |
|
||
| `FeatDefsIndex` | `Sld_FeatDefsIndex` | i tipi di feature presenti (`feat_defs_index`: 923, 979, 1084, 917, 916, 914, 926, 1104, 1232, 911, 949, 1070, 913, 1052) |
|
||
| `MdlStatus` | `Sld_MdlStatus` | storico: un record per azione, con utente, data e nome (`Extrude 9`, `Specchiare 4`) |
|
||
| `VisibGeom` / `NovisGeom` | `Sld_VisGeom` / `Sld_NonVisGeom` | **B-rep**: `srf_array` (superfici: `geom_id`, `geom_type`, `feat_id`, `srf_prim_ptr(plane/cylinder/...)` con `local_sys` e raggio), contorni (`next_cntr_ptr`), `crv_array` (spigoli con i punti) |
|
||
| `ActEntity` | `Sld_Entities` | entità di riferimento: punti (`PNT0`...), linee, sistemi di coordinate |
|
||
| `GeomDepen` | `Sld_GeomDepend` | ultima feature rigenerata, data dell'ultima rigenerazione, proprietà delle geometrie |
|
||
| `LargeText` | `Sld_LargeText` | **nomi** (`names_table`), **parametri** (`symtbl`: `DENOMINAZIONE`, `PRO_MP_*`...), **relazioni** come albero (`relset_code_tree`) con il testo sorgente e i commenti |
|
||
| `DispCntrl` | `Sld_DispCntrl` | layer (`01___PRT_ALL_DTM_PLN`, ...) con gli id contenuti e le regole |
|
||
| `FamilyInf` | `Sld_FamilyInfo` | family table (`gen_name`, `info_items`) |
|
||
| `Xsections` | `Sld_Xsections` | sezioni trasversali, ognuna con la propria geometria (`xsec_geom`) |
|
||
| `ModelView#n` | `View` | viste salvate: matrici `orient`/`local_sys` 4×4, nome |
|
||
| `NeuPrtSld` | `Sld_NeutPart` | dati "neutral" della parte |
|
||
| `ColorSchemeInfo*`, `DispDataColor`, `AssMrgPrt`, `PipeInfos`, `NeuAsmSld`, `DEPDB_DATA`, `THMB_IMG_INDX` | | piccole o vuote |
|
||
| `SolidPrimdata` (compressa) | `pgl_primdata_group_table` | **geometria di visualizzazione**: archi (`prim_arc`: raggio, angoli, centro, piano), linee e la **mesh** (`mv_p_NxNyNzxyz`: normale + vertice, `float`) |
|
||
| `SolidPersistTable` (compressa) | `pgl_persist_table` | ingombro (`xyz_min_max`: x ±45,57, y −74,07..90, z −807..0), `bounding_radius` 414,27 |
|
||
| `ND:0:Model_*`, `ND:0:UnitSystemDef_*`, `ND:0:PDMTrail_*` (compresse) | `PSB_Tokens` | §7 |
|
||
|
||
Valori letti da questo file, come esempio (nessuno confrontato con Creo): densità
|
||
7,85·10⁻⁶ kg/mm³, volume 3,11·10⁶ mm³, dipendenze `9053004005` e `9253018001`.
|
||
|
||
## 6. Cosa cambia rispetto alla lettura a pattern
|
||
|
||
- **Il primo record non è più un caso speciale**: è solo quello che dichiara i campi. "La prima
|
||
quota dell'array può mancare" (`formato-prt-geometria.md` §2) non vale più.
|
||
- **I record posizionali si leggono con i nomi**: ogni valore ha il nome del suo campo.
|
||
- **Le quote**: `dim_array` → `dim_dat_ptr.value`, `tol_ptr.value` (i due scostamenti: 0,1 per
|
||
la quota 2; 0,2 per 20; 0,3 per 59, cioè ISO 2768-m), `dim_cosm_ptr`.
|
||
- **Le unità** si leggono (`BasicText`), non vanno più assunte.
|
||
|
||
## 7. Le sezioni `ND:0:*`: `PSB_Tokens`
|
||
|
||
Decompresse sono un PSB con una sola struttura:
|
||
|
||
```
|
||
PSB_Tokens:
|
||
format = 0
|
||
type_har = "MOMOIOIOIOIOFCOFOM..." un carattere per token
|
||
int_har = [0, 1, 2, 3, 4, 5, -1, ...]
|
||
double_xar = [-1.0, 0.001, 1e-09, ...]
|
||
string_xar = ['ndmodel_l05::Model_L05', 'Descriptor', 'Name', '9253000117a', 'Type', 'prt', ...]
|
||
```
|
||
|
||
È un secondo livello di serializzazione: `type_har` è la sequenza dei token, gli altri tre array
|
||
sono i valori a cui i token attingono in ordine. **Il significato delle lettere non è
|
||
decodificato** (`M`, `O`, `I`, `F`, `C`, `P`, `Q`, `N`, `H`, `R`, `S`, `U`). Le stringhe però si
|
||
leggono già: nome e tipo del modello, dipendenze, nomi dei parametri.
|
||
|
||
## 8. Dove non regge
|
||
|
||
| Problema | Dove | Stato |
|
||
|---|---|---|
|
||
| Sezioni tipo `b` (`IndentTag`, `FeatRefData`) e tipo `11` dei file Wildfire | tutti i file | **illeggibili** |
|
||
| `DispDataTable` (tipo `c`) | file recenti | non letto |
|
||
| Tabella dei prefissi `float` `50`-`df` | `SolidPrimdata` | parziale: la mesh non è esportabile |
|
||
| Costante `double` `04` | geometria | valore ignoto |
|
||
| Annidamento | `AllFeatur`, `MdlStatus`, `FeatDefs` | la pila non torna a zero a fine sezione |
|
||
| Alberi bilanciati (`lesser_child`/`greater_child`/`balance_factor`) | `LargeText` di 24 file di `raw/` | il lettore si ferma: la regola dei puntatori "al successivo" non basta |
|
||
| `srf_flip_dat`, `end2`, `backup_size` "contenitore non in pila" | 26 sezioni di `raw/` | stesso tipo di errore: è la pila, non i valori |
|
||
| Array di stringhe senza `f8` | `BasicData` | euristica (§2.2) |
|
||
| `PSB_Tokens` | `ND:0:*` | solo gli array, non la struttura |
|
||
| Significato dei campi | ovunque | dedotto dal nome, mai verificato in Creo |
|
||
|
||
Sugli 89 file di `raw/`: 2152 sezioni lette per intero, 74 no. Tutti i fallimenti sono del tipo
|
||
"contenitore non in pila" o "pila vuota": la sequenza dei valori fino a quel punto è corretta.
|
||
|
||
## 9. Correzioni agli altri doc
|
||
|
||
- `formato-prt-feature.md` §1.1: la costante `K` non va ricavata dall'ultima sezione: è
|
||
l'ultimo campo della prima riga del file, e la sezione comincia con la riga `#Nome`. L'indice
|
||
può essere a catena (`NEXT_TOC_ENTRY`). La formula data lì funziona solo per caso.
|
||
- `formato-prt-feature.md` §1.2: le sezioni `ND:0:*` decompresse non sono XML ma `PSB_Tokens`
|
||
(§7); i nomi tipo SOAP sono le stringhe di `string_xar`.
|
||
- `formato-prt-geometria.md` §1: la tabella dei tag è sostituita da §3.2. In particolare
|
||
`e4`-`e7` non sono "zeri" ma **ripetizioni del valore precedente**, e i tag `49`-`df`
|
||
("non decodificato") sono i prefissi a 2 byte.
|
||
- `formato-prt-geometria.md` §1: `UnitSystemDef` non serve per le unità, stanno in `BasicText`.
|
||
|
||
## 10. Come proseguire
|
||
|
||
1. **Confrontare con Creo** un file piccolo: è l'unica prova che manca a tutto il doc.
|
||
2. Completare la tabella dei `float` raccogliendo le normali su più file (stesso metodo di
|
||
§3.3): sblocca l'esportazione della mesh senza Creo.
|
||
3. Sistemare la pila sugli alberi di `LargeText` (§8), poi portare il parser in C# accanto ad
|
||
`AsmRecordReader`, con la stessa regola: se la sezione non si legge fino in fondo,
|
||
`InvalidDataException`.
|
||
4. Sezioni tipo `b`: servono due salvataggi dello stesso modello che differiscono di poco.
|