Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d7ace07e2 | ||
|
|
1eac3ccaa7 | ||
|
|
5b11b3e8ca |
No files matched your search
@@ -43,3 +43,4 @@ publish/
|
|||||||
|
|
||||||
# Setup generato da Inno Setup (installer/PrepStep.iss)
|
# Setup generato da Inno Setup (installer/PrepStep.iss)
|
||||||
installer/Output/
|
installer/Output/
|
||||||
|
__pycache__/
|
||||||
@@ -7,6 +7,12 @@ aderisce a [Semantic Versioning](https://semver.org/lang/it/).
|
|||||||
|
|
||||||
## [Non rilasciato]
|
## [Non rilasciato]
|
||||||
|
|
||||||
|
### Aggiunto
|
||||||
|
|
||||||
|
- `docs/formato-prt-feature.md`: decodifica (da reverse engineering) dell'albero delle feature di un `.prt`.
|
||||||
|
- `docs/formato-prt-geometria.md`: numeri, quote e geometria degli schizzi di un `.prt`, con la tabella di cosa resta da decodificare.
|
||||||
|
- `docs/formato-psb.md` e `scripts/psb/`: formato di serializzazione completo delle sezioni dei file Creo (reverse engineering su `9253000117a.prt.35`) e parser Python di riferimento.
|
||||||
|
|
||||||
## [1.1.0] - 2026-10-07
|
## [1.1.0] - 2026-10-07
|
||||||
|
|
||||||
### Aggiunto
|
### Aggiunto
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ e cambia solo quando cambia il programma.
|
|||||||
- [`db-cad-index.md`](db-cad-index.md) — schema, vista e query di `cad_index.db`.
|
- [`db-cad-index.md`](db-cad-index.md) — schema, vista e query di `cad_index.db`.
|
||||||
- [`formato-asm-record.md`](formato-asm-record.md) — parser esatto dei record `.asm`, casi limite (§7).
|
- [`formato-asm-record.md`](formato-asm-record.md) — parser esatto dei record `.asm`, casi limite (§7).
|
||||||
- [`formato-asm.md`](formato-asm.md) — euristica testuale di fallback.
|
- [`formato-asm.md`](formato-asm.md) — euristica testuale di fallback.
|
||||||
|
- [`formato-prt-feature.md`](formato-prt-feature.md) — come leggere l'albero delle feature di un `.prt` (reverse engineering, non ancora nel codice).
|
||||||
|
- [`formato-prt-geometria.md`](formato-prt-geometria.md) — numeri, quote e geometria degli schizzi di un `.prt`, con la tabella di cosa manca per determinarlo per intero.
|
||||||
|
- [`formato-psb.md`](formato-psb.md) — il formato di serializzazione di tutte le sezioni (`.prt` e `.asm`): contenitore, grammatica, numeri, cosa c'è in ogni sezione, con un parser di riferimento in `scripts/psb/`.
|
||||||
|
- [`history.md`](history.md) — stato del reverse engineering dei file Creo: cosa è provato, cosa manca, da dove ripartire.
|
||||||
- [`glossario.md`](glossario.md) — i termini usati nei doc e nell'app.
|
- [`glossario.md`](glossario.md) — i termini usati nei doc e nell'app.
|
||||||
- [`../CHANGELOG.md`](../CHANGELOG.md) — storico delle versioni.
|
- [`../CHANGELOG.md`](../CHANGELOG.md) — storico delle versioni.
|
||||||
|
|
||||||
@@ -37,6 +41,9 @@ Ogni informazione vive in **un** posto; altrove si mette un link, non una copia.
|
|||||||
| Versione e nome prodotto | `PrepStep/PrepStep.csproj` (`<Version>`, `<Product>`) |
|
| Versione e nome prodotto | `PrepStep/PrepStep.csproj` (`<Version>`, `<Product>`) |
|
||||||
| Schema e vista del DB | [`db-cad-index.md`](db-cad-index.md) |
|
| Schema e vista del DB | [`db-cad-index.md`](db-cad-index.md) |
|
||||||
| Formato dei record `.asm` e casi limite | [`formato-asm-record.md`](formato-asm-record.md) |
|
| Formato dei record `.asm` e casi limite | [`formato-asm-record.md`](formato-asm-record.md) |
|
||||||
|
| Albero delle feature di un `.prt` | [`formato-prt-feature.md`](formato-prt-feature.md) |
|
||||||
|
| Quote, coordinate e geometria degli schizzi di un `.prt` | [`formato-prt-geometria.md`](formato-prt-geometria.md) |
|
||||||
|
| Come è scritta una sezione qualsiasi di un `.prt`/`.asm` | [`formato-psb.md`](formato-psb.md) |
|
||||||
| Processo delle 5 fasi, mappa dei file | [`architettura.md`](architettura.md) |
|
| Processo delle 5 fasi, mappa dei file | [`architettura.md`](architettura.md) |
|
||||||
| Passi dell'utente | [`guida-utente.md`](guida-utente.md) |
|
| Passi dell'utente | [`guida-utente.md`](guida-utente.md) |
|
||||||
| Build, test, regole sui doc | [`sviluppo.md`](sviluppo.md) |
|
| Build, test, regole sui doc | [`sviluppo.md`](sviluppo.md) |
|
||||||
|
|||||||
@@ -286,6 +286,10 @@ Comandi di build del setup in [`rilascio.md`](rilascio.md).
|
|||||||
|
|
||||||
## Script standalone (`scripts/`)
|
## Script standalone (`scripts/`)
|
||||||
|
|
||||||
|
`scripts/psb/` fa eccezione: sono tre file Python (`prt.py`, `psb.py`, `unz.py`), lo strumento
|
||||||
|
del reverse engineering descritto in [`formato-psb.md`](formato-psb.md). Non referenziano il
|
||||||
|
progetto e l'app non li usa.
|
||||||
|
|
||||||
C# file-based apps (.NET 10, `dotnet run scripts/Nome.cs -- arg1 arg2`, nessun `.csproj`,
|
C# file-based apps (.NET 10, `dotnet run scripts/Nome.cs -- arg1 arg2`, nessun `.csproj`,
|
||||||
dipendenze extra con `#:package Nome@versione` in testa al file):
|
dipendenze extra con `#:package Nome@versione` in testa al file):
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,538 @@
|
|||||||
|
# Decodifica del `.prt` Creo: numeri, quote e geometria degli schizzi
|
||||||
|
|
||||||
|
Seguito di [`formato-prt-feature.md`](formato-prt-feature.md), che spiega come trovare le sezioni
|
||||||
|
del file e leggere l'**albero** delle feature (ordine, tipo, nome). Qui: come leggere i **numeri**
|
||||||
|
(quote, coordinate) e come ricostruire da quelli la forma di una feature.
|
||||||
|
|
||||||
|
> **Aggiornamento.** La lettura campo per campo di tutte le sezioni, e la tabella completa dei
|
||||||
|
> numeri, sono in [`formato-psb.md`](formato-psb.md), che corregge la §1 di questo doc (vedi lì §9).
|
||||||
|
|
||||||
|
**Stato, da leggere prima di fidarsi.** Questo doc contiene *tutto quello che è stato decodificato
|
||||||
|
e verificato*, e dice per ogni cosa quanto è provata. **Non basta ancora a determinare in modo
|
||||||
|
univoco com'è fatto un `.prt`**: manca il posizionamento delle feature nello spazio e il
|
||||||
|
significato certo di alcuni codici (§7 elenca cosa manca, dove sta nel file, e come finire il
|
||||||
|
lavoro). Nessun codice del progetto lo usa: è reverse engineering fatto con script di prova.
|
||||||
|
|
||||||
|
Livelli di prova, come negli altri doc:
|
||||||
|
|
||||||
|
1. **Contro Creo** (schermate del modello aperto): `9252000507.prt.11`, feature *Rivoluzione 1*
|
||||||
|
(§5). Quote e coordinate dello schizzo coincidono con quelle mostrate da Creo.
|
||||||
|
2. **Coerenza e plausibilità**: `test.prt.1` e le altre feature di `9252000507.prt.11`. I valori
|
||||||
|
sono numeri "tondi" e coerenti fra sezioni diverse, ma nessuno li ha confrontati con Creo.
|
||||||
|
|
||||||
|
## 1. Come sono scritti i numeri reali
|
||||||
|
|
||||||
|
Nelle sezioni a marcatori ([`formato-asm-record.md`](formato-asm-record.md) §2) i `double` sono
|
||||||
|
scritti in forma compatta. Si distinguono per il primo byte, `tag`:
|
||||||
|
|
||||||
|
| Tag (hex) | Byte dopo il tag | Valore | Prova |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `18` | 0 | `0.0` | in ogni sezione |
|
||||||
|
| `0f` | 0 | `1.0` | diagonale delle matrici identità |
|
||||||
|
| `0e` | 0 | `0.5` | **livello 1**: la quota di *Smusso 1* (D = 0.500) è il solo byte `0e` |
|
||||||
|
| `10` | 0 | `-1.0` | ipotesi dal contesto |
|
||||||
|
| `2f` | 2 | `40 b1 b2 00 00 00 00 00` | **livello 1** (12,5; 69,5; 100; 139) e 2 (8, 20, 25, 75) |
|
||||||
|
| `2e` | 2 | `40 b1 b2 b2 b2 b2 b2 b2` (l'ultimo byte si ripete) | **livello 1**: `2e 1b 33` = 6,8, il diametro della punta mostrato da Creo |
|
||||||
|
| `2d` | 7 | `40 b1 .. b7` (precisione piena) | livello 2: `2d 39 7f ff ff ff ff ff` = 25,5 |
|
||||||
|
| `2a` | 2 | `3f b1 b2 00...` | livello 2: `2a f4 00` = 1,25, il passo di M8x1.25 |
|
||||||
|
| `48` | 2 | `c0 b1 b2 00...` (negativo) | **livello 1** (−12,5; −69,5) |
|
||||||
|
| `47` / `46` | 2 / 7 | stessa famiglia di `48`, con ripetizione / precisione piena | non isolati |
|
||||||
|
| `e4`-`e7` | 0 | `K − 3` zeri di seguito, `K` = byte − `e0` (solo dentro le matrici, §6.1) | **livello 1** (matrici di `9239902104`) |
|
||||||
|
| `ed`, `32`... | 8-9 | altri byte alti | non decodificato |
|
||||||
|
|
||||||
|
**La regola, ricavata dai dati** (non da una specifica): a ogni *byte alto* `T` del `double`
|
||||||
|
corrispondono **tre tag consecutivi**, e la base si sposta di 5 per ogni passo di `T`:
|
||||||
|
|
||||||
|
```
|
||||||
|
tag_zero(T) = 0x2f + 5 * (T - 0x40) per i positivi (negativi: 0x48 + 5 * (T - 0xc0))
|
||||||
|
tag_zero -> T, b1, b2 e 5 byte a zero (valori "tondi")
|
||||||
|
tag_zero - 1 -> T, b1, b2 e b2 ripetuto 5 volte (es. 6,8 = 40 1b 33 33 33 33 33 33)
|
||||||
|
tag_zero - 2 -> T e 7 byte pieni (precisione piena)
|
||||||
|
```
|
||||||
|
|
||||||
|
Verifica: `T = 0x40` dà `2f`, `2e`, `2d`; `T = 0x3f` dà `2a` (1,25 con `f4 00`); `T = 0xc0` dà
|
||||||
|
`48`. Funziona solo finché i tag non si sovrappongono: i byte alti `0x41`-`0x43` e `0xb8`-`0xbf`
|
||||||
|
non li ho incontrati e non so se la regola regge (i tag di `0x45` collidono con quelli dei
|
||||||
|
negativi). Per i valori "non tondi" con ripetizione (come 6,8 o 3,4) serve il tag `2e`: senza
|
||||||
|
quello si leggerebbe `6.7998`.
|
||||||
|
|
||||||
|
Come si ricostruisce un valore a 3 byte: si prende il `double` big-endian `40 b1 b2 00...` (per
|
||||||
|
`2f`) oppure `c0 b1 b2 00...` (per `48`). Esempi verificati (`struct.unpack('>d', ...)`):
|
||||||
|
|
||||||
|
| Byte | Valore | Dove |
|
||||||
|
|---|---|---|
|
||||||
|
| `2f 59 00` | `100.0` | quota |
|
||||||
|
| `2f 52 c0` | `75.0` | quota |
|
||||||
|
| `2f 51 60` | `69.5` | coordinata y dello schizzo di Rivoluzione 1 (= 139 / 2) |
|
||||||
|
| `2f 29 00` | `12.5` | coordinata x (= 25 / 2) |
|
||||||
|
| `48 29 00` | `-12.5` | idem, lato opposto all'asse |
|
||||||
|
| `48 51 60` | `-69.5` | idem |
|
||||||
|
| `2f 61 60` | `139.0` | quota (lunghezza) |
|
||||||
|
| `48 59 00` | `-100.0` | estremo della linea d'asse |
|
||||||
|
|
||||||
|
Le unità non sono scritte nel record. Nei campioni sono millimetri, ma il file ha una sezione
|
||||||
|
`UnitSystemDef` (nel contenitore compresso, §1.2 di `formato-prt-feature.md`) che non ho letto:
|
||||||
|
l'unità va presa da lì prima di dare un numero come "mm".
|
||||||
|
|
||||||
|
## 2. Le quote: `dim_array` in `BasFullData`
|
||||||
|
|
||||||
|
Ogni quota (dimensione guida) di ogni feature e di ogni schizzo è un record della sezione
|
||||||
|
`BasFullData`, array `dim_array`. Campi, nell'ordine in cui compaiono:
|
||||||
|
|
||||||
|
```
|
||||||
|
\xe1 nome della quota (qui sempre nullo: il nome "d12" si calcola altrove)
|
||||||
|
\x08 tipo del record
|
||||||
|
<dim_type> 1 byte: classe della quota (tabella sotto)
|
||||||
|
<feat_id> intero (formato asm-record §2.1): la feature a cui appartiene
|
||||||
|
<attributi> \xf8 \x02 ... flag
|
||||||
|
\xe3 <valore> <valore_di_backup> \xe3 ... due numeri uguali: value e bck_value
|
||||||
|
\xe3 \x09 \x01 ... <tolleranza> ... blocco della tolleranza (nominal_value, bound...)
|
||||||
|
```
|
||||||
|
|
||||||
|
Per leggere solo valore e feature basta ancorarsi a `\xe1 \x08 <dim_type> <feat_id>` e leggere il
|
||||||
|
primo numero che segue (§1). Il primo record dell'array è scritto con i nomi di campo per esteso
|
||||||
|
(layout diverso: non l'ho agganciato con lo stesso pattern, quindi **la prima quota dell'array può
|
||||||
|
mancare**).
|
||||||
|
|
||||||
|
`feat_id` è lo stesso id di `AllFeatur` e dello storico (`formato-prt-feature.md` §2 e §5): è il
|
||||||
|
modo per attribuire la quota a "Foro 1" o a "Estrusione 2".
|
||||||
|
|
||||||
|
### 2.1 Le quote lette sui due campioni
|
||||||
|
|
||||||
|
`test.prt.1` (id → feature come in `formato-prt-feature.md` §6):
|
||||||
|
|
||||||
|
| Feature | `dim_type` | Valore | Interpretazione |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Estrusione 1 (79) | 62 | 75 | profondità (ipotesi) |
|
||||||
|
| Estrusione 1 (79) | 4 | 100 | diametro del cerchio dello schizzo: nello schizzo il raggio è 50 (`entity(circle)`, `radius`) |
|
||||||
|
| Foro 1 (100) | 4 | 19 | diametro (ipotesi) |
|
||||||
|
| Foro 1 (100) | 62 | 40 | profondità (ipotesi) |
|
||||||
|
| Arrotondamento 1 (125) | 3 | 3,5 | raggio (ipotesi) |
|
||||||
|
| Estrusione 2 (149) | 62 / 1 / 1 | 11 / 25 / 53 | profondità e due quote dello schizzo (ipotesi) |
|
||||||
|
| Smusso 1 (188) | 9 | 6 | distanza dello smusso (ipotesi) |
|
||||||
|
|
||||||
|
`9252000507.prt.11`:
|
||||||
|
|
||||||
|
| Feature | `dim_type` | Valore | Interpretazione |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Rivoluzione 1 (73) | 2 | **25** | **diametro, confermato da Creo** (Ø25 h7) |
|
||||||
|
| Rivoluzione 1 (73) | 1 | **139** | **lunghezza, confermata da Creo** |
|
||||||
|
| Estrusione 1 / taglio (118) | 62 / 14 / 5 / 20 | 4 / 45 / 8 / 3 | non verificate |
|
||||||
|
| Foro 1 (157) | 4 / 9 / 2 / 10 / 2 | 8 / 20 / 6,8 / 118 / 25 | **confermate da Creo** (§6.4): 8 = diametro nominale, 20 = profondità filetto, 118 = angolo punta, 25 = profondità foro |
|
||||||
|
|
||||||
|
`9239902104.prt.1` (piastra con tre fori, §6.4.1):
|
||||||
|
|
||||||
|
| Feature | `dim_type` | Valore | Interpretazione |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Sketch 1 / Estrusione 1 (84) | 1 / 5 / 1 | 21 / 54 / 125 (+ 46) | quote del profilo a L (§3.2) |
|
||||||
|
| Estrusione 1 (84) | 62 | 6 | profondità, coerente con y = 0 ... 6 delle facce |
|
||||||
|
| Sketch 2 / Foro 1 (124 / 131) | 8 / 2 / 2 / 7 | 38 / 8 / 8 / 30 | quote dei 4 punti (§6.4.1) |
|
||||||
|
| Foro 1 (131) | 4 | 5,5 | diametro |
|
||||||
|
| Sketch 3 / Foro 2 (1095 / 1099) | 2 / 2 | 4 / 5 | distanze del punto dai bordi: l'origine `(67, 6, -5)` = 71 − 4 e 5 |
|
||||||
|
| Foro 2 (1099) | 4 | 5 | diametro |
|
||||||
|
| Foro 3 (1132) | 4 / 9 / 2 / 2 | 3 / 6 / 2,5 / 137,5 | diametro nominale M3, profondità filetto, punta, lunghezza (distanza fino all'estremo, §6.1) |
|
||||||
|
| Sketch 4 / Estrusione 2 (1903 / 1911) | 1 / 62 | 3,5 / 3 | quota dello schizzo e profondità |
|
||||||
|
| Arrotondamento 1 (2777) | 3 | 3 | raggio, confermato dallo storico |
|
||||||
|
|
||||||
|
**Cosa è provato e cosa no.** I valori `25` e `139` di *Rivoluzione 1* sono confermati da Creo
|
||||||
|
(§5). Per tutte le altre righe il *numero* è decodificato in modo plausibile ma il *significato*
|
||||||
|
(profondità? diametro?) è dedotto dal codice `dim_type`, da cui non ho una tabella certa. I codici
|
||||||
|
visti: `1` e `2` quote lineari/diametrali di schizzo, `3` raggio, `4` diametro, `9` distanza
|
||||||
|
(smusso), `62` profondità/estensione, altri (`5`, `10`, `14`, `37`) non interpretati. Per
|
||||||
|
distinguere con certezza serve un confronto riga per riga con un modello noto, o leggere il
|
||||||
|
nome della quota (`d0`, `d1`... del Creo).
|
||||||
|
|
||||||
|
La *tolleranza* (es. `h7` di Ø25) sta nello stesso record, ma non l'ho decodificata: nel campo
|
||||||
|
`tol_class`/`tol_table_index` compaiono indici, non la stringa `h7`.
|
||||||
|
|
||||||
|
## 3. La geometria di uno schizzo: `p_saved_result`
|
||||||
|
|
||||||
|
Ogni schizzo (sezione) ha, dentro `BasFullData`, un blocco:
|
||||||
|
|
||||||
|
```
|
||||||
|
gsec_ptr ... name "Sezione 1" <- nome mostrato nel tree
|
||||||
|
gsec2d_ptr name "S2D0001"
|
||||||
|
segtab_ptr / dimtab_ptr / dim_ref <- segmenti, quote e a cosa si riferiscono
|
||||||
|
...
|
||||||
|
p_saved_result
|
||||||
|
entity(line) ... <- le linee dello schizzo con le coordinate
|
||||||
|
entity(circle) ... <- i cerchi (centro, raggio)
|
||||||
|
local_sys 4x3 <- sistema di coordinate dello schizzo
|
||||||
|
```
|
||||||
|
|
||||||
|
Le coordinate delle entità sono **nel sistema dello schizzo**, non assolute (§6).
|
||||||
|
|
||||||
|
Ogni `entity(line)` ha due estremi con tre coordinate ciascuno `(x, y, z)`; con z sempre `0` nello
|
||||||
|
schizzo. Le coordinate sono numeri come in §1.
|
||||||
|
|
||||||
|
### 3.1 Esempio verificato: *Sezione 1* di Rivoluzione 1
|
||||||
|
|
||||||
|
Decodificando i numeri del blocco `entity(line)` di `9252000507.prt.11` si ottengono quattro
|
||||||
|
linee chiuse e una linea d'asse:
|
||||||
|
|
||||||
|
| Linea | Da `(x, y)` | A `(x, y)` |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | (0, 69,5) | (−12,5, 69,5) |
|
||||||
|
| 2 | (−12,5, 69,5) | (−12,5, −69,5) |
|
||||||
|
| 3 | (−12,5, −69,5) | (0, −69,5) |
|
||||||
|
| 4 | (0, −69,5) | (0, 69,5) |
|
||||||
|
| asse | (0, −100) | (0, 100) |
|
||||||
|
|
||||||
|
È il rettangolo di larghezza 12,5 (= raggio) e altezza 139, centrato in y sul piano di
|
||||||
|
riferimento, con il lato destro sull'asse x = 0, e una linea d'asse più lunga della figura.
|
||||||
|
**Coincide con quello che Creo mostra** nella vista dello schizzo (rettangolo di 139 di altezza,
|
||||||
|
quotato 25 come diametro rispetto all'asse tratteggiato, simmetrico rispetto a `TOP`).
|
||||||
|
|
||||||
|
Nota: nel file la quota `25` è un **diametro** (Ø25), anche se in geometria il lato è 12,5. Per
|
||||||
|
questo non si può dedurre la forma dalle sole quote: servono anche le linee.
|
||||||
|
|
||||||
|
### 3.2 Secondo esempio: il profilo a L di *Estrusione 1* (`9239902104.prt.1`)
|
||||||
|
|
||||||
|
Le linee del profilo (`entity(line)`, estremi `(x, y)` nel sistema dello schizzo, `z = 0`):
|
||||||
|
|
||||||
|
| Linea | Da | A |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | (71, 0) | (−54, 0) |
|
||||||
|
| 2 | (−54, 0) | (−54, 46) |
|
||||||
|
| 3 | (−54, 46) | (0, 46) |
|
||||||
|
| 4 | (0, 46) | (0, 21) |
|
||||||
|
| 5 | (0, 21) | (71, 21) |
|
||||||
|
| 6 | (71, 21) | (71, 0) |
|
||||||
|
|
||||||
|
più una linea d'asse. Le quote 125 (= 54 + 71), 54, 46 e 21 sono tutte spiegate dal profilo. Qui le
|
||||||
|
linee stanno in `FeatDefs` (record della feature, sezione `S2D0006`), non in `BasFullData` come in
|
||||||
|
§3: la posizione del blocco `p_saved_result` dipende dal tipo di feature.
|
||||||
|
|
||||||
|
## 4. Da schizzo + quote alla forma: la rivoluzione
|
||||||
|
|
||||||
|
Con le due cose sopra, *Rivoluzione 1* è determinata così:
|
||||||
|
|
||||||
|
```
|
||||||
|
profilo (nel piano dello schizzo): x in [-12,5 ; 0], y in [-69,5 ; 69,5]
|
||||||
|
asse di rotazione: la linea x = 0 (la linea d'asse dello schizzo)
|
||||||
|
angolo: 360 (non decodificato, vedi §7)
|
||||||
|
risultato: cilindro Ø25 x 139, centrato sull'origine lungo y dello schizzo
|
||||||
|
```
|
||||||
|
|
||||||
|
Nell'albero di Creo il cilindro risulta con il fondo sul piano `DTM1` e l'asse come `A_1`: la
|
||||||
|
posizione assoluta (quale piano contiene lo schizzo, come è orientato `y`) **non è nello schizzo**
|
||||||
|
ma nella definizione della feature (§7).
|
||||||
|
|
||||||
|
## 5. Verifica di *Rivoluzione 1* contro Creo
|
||||||
|
|
||||||
|
`9252000507.prt` aperto in Creo (schermate del model tree e dello schizzo):
|
||||||
|
|
||||||
|
| Cosa | Creo | Letto dal file |
|
||||||
|
|---|---|---|
|
||||||
|
| Lunghezza dello schizzo | 139.000 | quota `dim_type 1` = 139 |
|
||||||
|
| Diametro | Ø25 (h7) | quota `dim_type 2` = 25 |
|
||||||
|
| Rettangolo, simmetrico rispetto a `TOP` | sì | y da −69,5 a 69,5 |
|
||||||
|
| Rettangolo a sinistra dell'asse | sì | x da −12,5 a 0 |
|
||||||
|
| Asse (linea di costruzione) | più lungo della figura | da y = −100 a 100 |
|
||||||
|
| Assi A_3, A_4, A_5, punto PNT0 | presenti nel tree | `AllFeatur` + storico (`formato-prt-feature.md`) |
|
||||||
|
|
||||||
|
## 6. Posizione nello spazio
|
||||||
|
|
||||||
|
### 6.1 Matrici 4×3
|
||||||
|
|
||||||
|
Piani di riferimento, assi, sistemi di coordinate, schizzi e feature hanno un `local_sys`: array
|
||||||
|
`f9 04 03` = 4 righe per 3 colonne = 12 numeri: **tre versori e, per ultima, l'origine**. Si leggono
|
||||||
|
con i numeri di §1 più un codice di ripetizione: un byte `e4`-`e7` vale **"altri `K − 3` zeri"**,
|
||||||
|
con `K` = byte − `e0` (`e4` = 1 zero, `e5` = 2, `e6` = 3, `e7` = 4). L'identità è quindi
|
||||||
|
`0f 18 e5 0f 18 e5 0f 18 e5` = `1,0,0,0, 1,0,0,0, 1,0,0,0`.
|
||||||
|
|
||||||
|
**Verificato su `9239902104.prt.1`** (livello 1: i valori coincidono con quelli mostrati da Creo,
|
||||||
|
sotto), con le 4 righe `(e1, e2, e3, origine)`:
|
||||||
|
|
||||||
|
| Feature | Matrice letta | Significato |
|
||||||
|
|---|---|---|
|
||||||
|
| Foro 1 | `(1,0,0) (0,0,-1) (0,1,0)`, origine `(-46, 6, -38)` | uno dei 4 punti dello schizzo `(-46, 38)`: x = x dello schizzo, z = **−**y dello schizzo, y = 6 = faccia superiore (la piastra è spessa 6) |
|
||||||
|
| Foro 2 | `(1,0,0) (0,0,-1) (0,1,0)`, origine `(67, 6, -5)` | 71 − 4 e 5 dal bordo: le due quote 4 e 5 della feature |
|
||||||
|
| Foro 3 | `(0,1,0) (-1,0,0) (0,0,1)`, origine `(-66.5, 3, -5)` | y = 3: l'**offset 3,00** dalla faccia superiore (y = 6) che Creo mostra; z = −5: allineato all'asse di Foro 2 (**"Allinea" con `A_7`**); x = −66,5 = 71 − 137,5, cioè il foro parte 137,5 prima dell'estremo destro (71) |
|
||||||
|
|
||||||
|
Quindi **la posizione di un foro si legge dall'origine della sua matrice**: le quote di
|
||||||
|
posizionamento (4, 5, 3,00) non servono, sono già il risultato. Nei tre fori l'`y` e la `z` tornano
|
||||||
|
esattamente con le quote e con i riferimenti mostrati da Creo.
|
||||||
|
|
||||||
|
Dalla stessa matrice: nel modello di questa piastra lo schizzo di Estrusione 1 sta nel piano `x–z`
|
||||||
|
(x dello schizzo = x, y dello schizzo = −z) e l'estrusione cresce lungo +y fino a 6.
|
||||||
|
|
||||||
|
**Resta incerta l'interpretazione dell'asse del foro.** Per Foro 1 e 2 il terzo versore è `(0,1,0)`
|
||||||
|
(perpendicolare alla piastra, come deve essere). Per Foro 3 è `(0,0,1)`, ma il foro in Creo entra
|
||||||
|
dalla faccia laterale: o la convenzione del versore dell'asse è diversa, o la direzione di foratura
|
||||||
|
sta in un altro campo. Non risolto.
|
||||||
|
|
||||||
|
### 6.2 Dove stanno
|
||||||
|
|
||||||
|
| Cosa | Dove nel file |
|
||||||
|
|---|---|
|
||||||
|
| Piani di riferimento (RIGHT, TOP, FRONT, DTM1) | `ActDatums` (`srf_prim_ptr(plane)` con `local_sys`), `feat_id` = id della feature; vincoli di posizionamento in `FeatDefsDtm` (§6.3) |
|
||||||
|
| Sistema di coordinate (`PRT_CSYS_DEF`) | `MdlRefInfo` (`ent_list(coord_sys)`) e `GeomDepen` |
|
||||||
|
| Assi, punti (A_3, PNT0...) | `ent_list(arc_z)`, `ent_list(point)` in `MdlRefInfo` |
|
||||||
|
| Piano che contiene lo schizzo | `gsec3d_ptr` -> `plane_id`, `ref_planes`; il piano è identificato da un id di geometria |
|
||||||
|
| Orientamento dello schizzo | `sec_orient_data`, `place_instruction_ptrs` (nella definizione di feature `feat_defs_<tipo>`) |
|
||||||
|
| Offset di un piano da un altro (DTM1) | quota di tipo "offset" nel `dim_array` della feature, se esiste |
|
||||||
|
|
||||||
|
### 6.3 Come è posizionato un piano: i vincoli di `FeatDefsDtm`
|
||||||
|
|
||||||
|
La finestra "Piano di riferimento → Posizionamento" di Creo (riferimenti con il loro vincolo,
|
||||||
|
es. *Tangente* a una superficie, *Parallelo* a un piano) **si legge dal file**. Sta nella sezione
|
||||||
|
`FeatDefsDtm`, record `feat_defs_923` (un record per piano; `feat_defs_926` assi, `feat_defs_931`
|
||||||
|
punti, `feat_defs_979` sistemi di assi). Ogni record ha:
|
||||||
|
|
||||||
|
```
|
||||||
|
id id della feature (come in AllFeatur)
|
||||||
|
local_sys matrice 4x3 del piano risultante (§6.1)
|
||||||
|
place_instruction_ptrs una istruzione per ogni riferimento della finestra:
|
||||||
|
type codice del vincolo (intero a 2 byte)
|
||||||
|
offset double, offset del vincolo
|
||||||
|
dim_id, ref_id quota dell'offset e riferimento, se esistono
|
||||||
|
geometry1_id id della geometria a cui ci si riferisce
|
||||||
|
geometry2_id id della geometria che si sta creando (il piano stesso)
|
||||||
|
```
|
||||||
|
|
||||||
|
Gli `id` di geometria (`geom_id`) si risolvono nelle sezioni `ActDatums` (piani e assi di
|
||||||
|
riferimento), `VisibGeom` e `NovisGeom` (superfici del solido, visibili e no). Ogni voce di
|
||||||
|
`srf_array` ha `geom_id`, `geom_type` (`"`, 0x22 = piano; `$`, 0x24 = cilindro) e **`feat_id`**: è il
|
||||||
|
legame con la feature che l'ha generata.
|
||||||
|
|
||||||
|
**Verifica sul caso che hai mostrato**: *DTM1* di `9252000507.prt` (feature 116), finestra di Creo
|
||||||
|
"Supfce:F5(RIVOLUZIONE_1) Tangente / FRONT:F3 Parallelo". Dal file:
|
||||||
|
|
||||||
|
| Istruzione | `type` | `geometry1_id` | Si risolve in | Corrisponde a |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 1 | `81 fb` (507) | 88 | `VisibGeom`: cilindro (`$`), `feat_id` 73 = Rivoluzione 1 | "Supfce:F5(RIVOLUZIONE_1)", **Tangente** |
|
||||||
|
| 2 | `81 f9` (505) | 6 | `ActDatums`: piano (`"`), `feat_id` 5 = FRONT | "FRONT:F3", **Parallelo** |
|
||||||
|
|
||||||
|
`geometry2_id` è 117 in entrambe: `ActDatums` lo dà come piano con `feat_id` 116 = DTM1 (il piano
|
||||||
|
risultante). Nella sua matrice compare il valore `12.5` (`2f 29 00`): è il raggio del cilindro,
|
||||||
|
come deve essere per un piano tangente ad esso. Il campo `Traslazione` della finestra era vuoto.
|
||||||
|
|
||||||
|
Quindi **quali sono i riferimenti** (quale feature/superficie e quale piano) si legge con certezza.
|
||||||
|
Resta da fissare:
|
||||||
|
|
||||||
|
- **Il significato di `type`.** 507 ↔ Tangente e 505 ↔ Parallelo è dedotto da un solo esempio e
|
||||||
|
dall'ordine delle righe della finestra: non ho una tabella dei codici. Gli altri visti
|
||||||
|
(`82 01`, `82 02`, `82 03`, `81 fd`, `81 ff`) sono quelli dei piani di default e degli altri
|
||||||
|
piani del campione, non interpretati.
|
||||||
|
- **`Feature number` di Creo** (`F3`, `F5`): non è l'`id` ma la **posizione** nel tree
|
||||||
|
(RIGHT = F1, TOP = F2, FRONT = F3, PRT_CSYS_DEF = F4, Rivoluzione 1 = F5). I piani interni non
|
||||||
|
contano. Per tradurre un `id` in `F<n>` si numerano le feature visibili in ordine
|
||||||
|
(`formato-prt-feature.md` §3).
|
||||||
|
- **L'offset.** Se un vincolo ha un offset (quota di tipo offset) il valore sta nel `dim_array`
|
||||||
|
della feature (§2) con il `dim_id` dell'istruzione; non ho un esempio con offset non nullo.
|
||||||
|
|
||||||
|
### 6.4 Il foro: `feat_defs_911`
|
||||||
|
|
||||||
|
Riferimento: *Foro 1* di `9252000507.prt.11` (feature 157), confrontato con le schermate di Creo.
|
||||||
|
Creo mostra: tipo **Standard**, profilo **Diritto** con **Maschiato**, filetto ISO **M8x1.25**,
|
||||||
|
**profondità 25**, **Ø6,8**, **profondità del filetto 20**, **angolo della punta 118°**,
|
||||||
|
"Includi la superficie del filetto" attivo, posizionamento **Coassiale** con riferimenti
|
||||||
|
`Supfce:F5(RIVOLUZIONE_1)` e `A_1(ASSE):F5(RIVOLUZIONE_1)`.
|
||||||
|
|
||||||
|
La definizione del foro sta nella sezione `FeatDefs`, record `feat_defs_911`. Ha due parti.
|
||||||
|
|
||||||
|
**a) I parametri del filetto, in chiaro** (`data(ddt_plinfo_hole_fstn_param)`):
|
||||||
|
|
||||||
|
| Campo | Valore letto | In Creo |
|
||||||
|
|---|---|---|
|
||||||
|
| `name`, `series` | `ISO`, `ISO` | Tipo filetto: ISO |
|
||||||
|
| `scrsize` | `M8x1.25` | Dimensione vite: M8x1.25 |
|
||||||
|
| `scrsize_val` | `0x1d` (29) | indice di M8x1.25 nella tabella delle viti di Creo (nel segnaposto `M1x.25` vale 0) |
|
||||||
|
| `hole_class` | `H` | (classe del foro) |
|
||||||
|
| `drillnum` | `6.8` | Ø6,8 |
|
||||||
|
|
||||||
|
**b) Le istruzioni** (`place_instruction_ptrs`): una per ogni parametro numerico, nella forma
|
||||||
|
`type`, `offset` (un `double`, §1), `dim_id`, `ref_id`, `geometry1_id`, `geometry2_id`. Quelle
|
||||||
|
del foro filettato, con il valore atteso da Creo:
|
||||||
|
|
||||||
|
| `type` | `offset` letto | In Creo | Prova |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 5104 (`0x13f0`) | 6,8 | Ø6,8 (anche nel foro semplice, vedi sotto) | **livello 1** |
|
||||||
|
| 5528 (`0x1598`) | 118 | angolo della punta | **livello 1** |
|
||||||
|
| 5538 (`0x15a2`) | 25 | profondità del foro | **livello 1** |
|
||||||
|
| 5536 (`0x15a0`) | 1,25 | passo del filetto (M8**x1.25**) | livello 2 |
|
||||||
|
| 6617 (`0x19d9`) | 11 | non corrisponde a nulla di mostrato | non interpretato |
|
||||||
|
| 5542, 10103, 5533, 5534 | 0 | non mostrati | non interpretati |
|
||||||
|
|
||||||
|
Il codice `type` identifica il parametro; il valore è `offset`. La profondità del filetto (20) e il
|
||||||
|
diametro nominale (8) **non** sono istruzioni ma quote del `dim_array` (§2), con `dim_type` 9 e 4
|
||||||
|
e `feat_id` 157. In tutto il `dim_array` ha per il foro cinque quote: 8 (`dim_type` 4), 20 (9),
|
||||||
|
Ø6,8 (2, scritto con il tag `2e`), 118 (10) e 25 (2). Tre di queste (6,8, 118, 25) compaiono
|
||||||
|
quindi due volte, come quota e come parametro di un'istruzione.
|
||||||
|
|
||||||
|
**Foro semplice e foro standard.** `test.prt.1` ha un foro **semplice**: il suo `feat_defs_911`
|
||||||
|
è lungo 862 byte e ha solo due istruzioni (`5104` con `dim_id` 7, e `1853`); i parametri del
|
||||||
|
filetto ci sono comunque, ma con i valori segnaposto di Creo (`M1x.25`, `drillnum 0.75`,
|
||||||
|
`scrsize_val 0`): **quindi le stringhe `scrsize`/`drillnum` da sole non dicono se il foro è
|
||||||
|
filettato**. Quello di Fata ha 3212 byte e 12 istruzioni, valori diversi dal segnaposto. Il segno
|
||||||
|
affidabile oggi è: **foro standard = presenza delle istruzioni 5536 (passo) e 5528 (angolo punta)**,
|
||||||
|
e il foro è filettato quando anche i valori del filetto non sono quelli segnaposto.
|
||||||
|
|
||||||
|
**Non individuato nel file** (per ognuno servono due salvataggi che differiscono solo per
|
||||||
|
quell'opzione):
|
||||||
|
|
||||||
|
- *Maschiato / Trapanato / Gioco*, *Diritto / Rastremato*: non so quale istruzione o flag le
|
||||||
|
codifichi. `plin_info_hole_flag.flags` vale `2, 2` in entrambi i fori.
|
||||||
|
- *Includi la superficie del filetto*: idem.
|
||||||
|
- Il tipo *Standard*/*Semplice* come campo esplicito: oggi si deduce solo dal numero di istruzioni.
|
||||||
|
|
||||||
|
**Posizionamento Coassiale.** Gli ingredienti ci sono: un blocco `data(plin_info_ref)` per ogni
|
||||||
|
riferimento, con gli id di geometria. Il piano del riferimento `Supfce:F5` dovrebbe essere la
|
||||||
|
faccia piana di *Rivoluzione 1* (feature 73, in `VisibGeom` con `feat_id` 73) e il secondo
|
||||||
|
riferimento l'asse `A_1`: non ho agganciato quegli id perché le coppie `id_type1`/`id_type2` non le
|
||||||
|
ho decifrate. La geometria generata dal foro (cilindro, cono, piani con `feat_id` 157) è invece
|
||||||
|
in `VisibGeom` e `NovisGeom`, e `geometry2_id` 175 delle istruzioni è un suo piano.
|
||||||
|
|
||||||
|
### 6.4.1 Foro a punti di schizzo e facce piane: `9239902104.prt.1`
|
||||||
|
|
||||||
|
*Foro 1* è definito da *Sketch 2* con **4 punti**. Cosa si legge, tutto da `local_sys` (§6.1):
|
||||||
|
|
||||||
|
**I punti.** Nello schizzo (`entity(point)`, campo `pnt`, un `[x, y, z]` per punto) sono
|
||||||
|
`(-46, 8)`, `(-8, 8)`, `(-8, 38)`, `(-46, 38)`, con quote 8, 8, 30 e 38. Nel modello ogni punto
|
||||||
|
diventa un'**istanza** del foro, e ogni istanza ha un piano proprio in `NovisGeom` (superficie
|
||||||
|
piana con `feat_id` 131, §6.3) con la matrice del punto: l'**origine** è la posizione assoluta.
|
||||||
|
|
||||||
|
| Piano (`geom_id`) | Origine nel modello |
|
||||||
|
|---|---|
|
||||||
|
| 135 | `(-46, 6, -8)` |
|
||||||
|
| 147 | `(-8, 6, -8)` |
|
||||||
|
| 159 | `(-8, 6, -38)` |
|
||||||
|
| 171 | `(-46, 6, -38)` |
|
||||||
|
|
||||||
|
Quindi `x` = `x` dello schizzo, `z` = **−**`y` dello schizzo (confermato dai quattro punti e da Foro
|
||||||
|
2, §6.1) e `y` = 6.
|
||||||
|
|
||||||
|
**La faccia.** La superficie su cui stanno è la faccia piana superiore di *Estrusione 1*: ha
|
||||||
|
`y = 6` e la stessa matrice del sistema di coordinate dello schizzo (origine `(0, 6, 0)`,
|
||||||
|
versori `(1,0,0)`, `(0,0,1)`, `(0,-1,0)`). Le facce piane di *Estrusione 1* (`feat_id` 84, in
|
||||||
|
`VisibGeom`) sono, con origine e normale (il terzo versore):
|
||||||
|
|
||||||
|
| `geom_id` | Origine | Normale | Faccia |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 95 | `(0, 6, 0)` | `(0,-1,0)` | **superiore** (`y = 6`), dove sono i fori |
|
||||||
|
| 107 | `(71, 0, 0)` | `(-1,0,0)` | estremo destro (`x = 71`) |
|
||||||
|
| 113 | `(-54, 0, -46)` | `(1,0,0)` | estremo sinistro (`x = -54`) |
|
||||||
|
| 115 | `(-54, 0, 0)` | `(0,0,1)` | lato `z = 0` |
|
||||||
|
| 2999 | `(71, 0, -21)` | `(0,0,-1)` | lato del gradino (`z = -21`) |
|
||||||
|
| 3001 | `(0, 0, -21)` | `(-1,0,0)` | alzata del gradino (`x = 0`) |
|
||||||
|
| 3003 | `(0, 0, -46)` | `(0,0,-1)` | lato `z = -46` |
|
||||||
|
|
||||||
|
La piastra a L di Estrusione 1 (§3.2, profilo `x` da −54 a 71, `y` dello schizzo da 0 a 46, gradino
|
||||||
|
a 21) torna in coordinate modello con `z = −y` dello schizzo, e spessore 6 lungo `y`. Gli altri
|
||||||
|
tre fori cadono dentro il materiale (`x` −46 e −8, `z` −8 e −38).
|
||||||
|
|
||||||
|
**Non risolto:** quale `geom_id` il foro usi *formalmente* come faccia di riferimento (95, per
|
||||||
|
coincidenza di piano e di origine) non è scritto in un campo che io abbia identificato: è
|
||||||
|
un'identificazione per posizione, non una lettura.
|
||||||
|
|
||||||
|
### 6.5 Lo smusso: `feat_defs_914`
|
||||||
|
|
||||||
|
Riferimento: *Smusso 1* di `9252000507.prt.11` (feature 252), in modifica in Creo: modalità
|
||||||
|
**Insiemi**, schema quote **D x D** con **D = 0.500**, un insieme ("Insieme 1") con **due
|
||||||
|
riferimenti**, entrambi `Spigolo:F5(RIVOLUZIONE_1)`, e l'opzione "Superfici con offset".
|
||||||
|
|
||||||
|
| Cosa | Dove | Letto | Prova |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Valore D | `dim_array`, `feat_id` 252, `dim_type` 9 | `0e` = **0,5** | **livello 1** |
|
||||||
|
| Valore in `test.prt.1` | `dim_array`, feat 188, `dim_type` 9 | `2f 18 00` = 6,0 | livello 2 |
|
||||||
|
| Superfici del bordo | `feat_defs_914`, istruzioni di tipo 4530 | `geometry1_id` = 88, 86, 106, 90 | vedi sotto |
|
||||||
|
| Superfici generate dallo smusso | `VisibGeom`, `feat_id` 252 | superfici coniche (`geom_id` 253, 258, 261, 266) | presenti |
|
||||||
|
|
||||||
|
Il `dim_type` 9 è lo stesso del foro (profondità del filetto): dice la *classe* della quota, non
|
||||||
|
cosa misura. Per lo smusso vale D, ma la stessa classe in un foro vale 20.
|
||||||
|
|
||||||
|
**I riferimenti.** Le istruzioni di tipo `4530` hanno nel campo `geometry1_id` gli id 88, 86, 106 e
|
||||||
|
90, ripetuti a coppie. Risolti con `VisibGeom`: **88** e **106** sono superfici cilindriche con
|
||||||
|
`feat_id` 73 (cioè *Rivoluzione 1*, F5), **86** è il piano di estremità della stessa rivoluzione
|
||||||
|
(visto in §3.1 e §6.3). Quindi i riferimenti `Spigolo:F5(RIVOLUZIONE_1)` sono spigoli ricavati dalle
|
||||||
|
superfici di Rivoluzione 1, come mostra Creo. Non so ancora quale coppia di id formi quale
|
||||||
|
spigolo, né a quale dei due "Spigolo" della lista corrisponda (le istruzioni non sono in un ordine
|
||||||
|
che io abbia decifrato), e **90** non l'ho risolto.
|
||||||
|
|
||||||
|
**Non decodificato:**
|
||||||
|
|
||||||
|
- lo schema quote (D x D, D1 x D2, angolo x D...): non ho trovato dove sta; compaiono vari tipi di
|
||||||
|
istruzione (`7350`, `7360`, `4549`, `4552`, `4557`, `4558`, `1125`, `7265`, `14741`) senza
|
||||||
|
corrispondenza con le voci mostrate;
|
||||||
|
- "Insieme 1" e la sua composizione: compare la struttura (`data(plin_info_chut_info)`, array di
|
||||||
|
riferimenti) ma non la ho letta;
|
||||||
|
- "Superfici con offset" e il tipo di transizione;
|
||||||
|
- l'istruzione `14741`, con un valore `11,476` in precisione piena: non corrisponde a niente di
|
||||||
|
mostrato.
|
||||||
|
|
||||||
|
**Foro filettato con posizionamento lineare** (*Foro 3* di `9239902104.prt.1`). Creo mostra: tipo
|
||||||
|
Standard, Diritto, Maschiato, ISO **M3x.5**, Ø2,50 (punta), campo profondità vuoto, posizionamento
|
||||||
|
**Lineare** con riferimento primario `Supfce:F6(ESTRUSIONE_1)` e due riferimenti di offset
|
||||||
|
(`Supfce:F6(ESTRUSIONE_1)` con offset 3,00 e `A_7(ASSE):F10(FORO 2)` allineato). Dal file:
|
||||||
|
|
||||||
|
| Informazione | Letto | Prova |
|
||||||
|
|---|---|---|
|
||||||
|
| Filetto | stringhe `ISO`, `M3x.5`, punta `2.5`; `scrsize_val` = 12 | concorda con Creo |
|
||||||
|
| Ø della punta | istruzione 5104, offset 2,5 | concorda con Creo (Ø2,50) |
|
||||||
|
| Quote del foro | 3 (`dim_type` 4), 6 (9), 2,5 (2), 137,5 (2) | numeri letti; 137,5 con campo "Profondità" vuoto in Creo: probabilmente un foro passante, valore non verificato |
|
||||||
|
| Posizione risultante (offset 3,00 e allineamento a `A_7`) | **letta dalla matrice** `local_sys` (§6.1): origine `(-66.5, 3, -5)`, con y = 3 e z = −5 uguali a quelli di Foro 2 | concorda con Creo; la quota 3,00 non è una quota a sé nel `dim_array` |
|
||||||
|
| Riferimenti `F6` e `A_7` | non risolti | gli id stanno in blocchi `plin_info_ref` come per DTM1 (§6.3) |
|
||||||
|
|
||||||
|
Correzione: il primo codice delle istruzioni è 5542 anche qui, come nel foro coassiale di
|
||||||
|
`9252000507`; il `12266` che avevo letto nella prima versione di questo doc veniva da un'istruzione
|
||||||
|
di un altro record. Il tipo di posizionamento (coassiale / lineare) **non** è quel codice. Quale sia
|
||||||
|
non lo so: la posizione finale si legge comunque dalla matrice (§6.1).
|
||||||
|
|
||||||
|
## 7. Cosa manca per determinare un `.prt` in modo univoco
|
||||||
|
|
||||||
|
Tabella di verità: ogni informazione necessaria, se l'ho decodificata e quanto è provata.
|
||||||
|
|
||||||
|
| Informazione | Stato | Dove si trova |
|
||||||
|
|---|---|---|
|
||||||
|
| Elenco e ordine delle feature | **fatto**, provato (`test.prt.1`, `9252000507.prt.11` contro Creo) | `AllFeatur` |
|
||||||
|
| Tipo di ogni feature | **fatto**; estrusione vs rivoluzione solo con lo storico | `AllFeatur`, storico |
|
||||||
|
| Nome di ogni feature | **fatto**, ma solo se esiste lo storico (10 file su 31 leggibili) | `MdlStatus` |
|
||||||
|
| Quote (valori numerici) | **fatto per i valori interi/semplici**, tag `2f`/`48` | `dim_array` |
|
||||||
|
| Significato di ogni quota | **parziale**: provato solo per Rivoluzione 1 | `dim_type` (§2.1) |
|
||||||
|
| Geometria dello schizzo (linee) | **fatto** per le linee, provato su Rivoluzione 1 | `p_saved_result` |
|
||||||
|
| Cerchi, archi, spline dello schizzo | non verificato (cerchio visto in `test.prt.1`, raggio 50) | `entity(circle)`, `entity(arc_z)`, `entity(spline)` |
|
||||||
|
| Valori non "tondi" | **non decodificato** (tag diversi da `2f`/`48`) | ovunque |
|
||||||
|
| Piano e orientamento dello schizzo | **non decodificato** | `gsec3d_ptr`, `sec_orient_data` |
|
||||||
|
| Posizione/rotazione di fori e feature | **fatto**: matrice 4×3 (§6.1) | `local_sys` |
|
||||||
|
| Posizione/rotazione di piani, assi, punti | stessa matrice, da verificare su un piano ruotato | `ActDatums`, `MdlRefInfo` |
|
||||||
|
| Vincoli e riferimenti di un piano (come DTM1) | **fatto per DTM1 di `9252000507`** (riferimenti sì, nome del vincolo da un esempio) | `FeatDefsDtm`, §6.3 |
|
||||||
|
| Offset di un piano da un altro | **non decodificato**: mai visto un offset non nullo | quota in `dim_array` (`dim_id`) |
|
||||||
|
| Direzione/lato e angolo di una rivoluzione, profondità e direzione di un'estrusione | **non decodificato** | `feat_defs_917`/`916` (`place_instruction_ptrs`, `plin_info_arr`) |
|
||||||
|
| Parametri del foro (misura, punta, passo, profondità, angolo) | **fatto**, provato contro Creo su `9252000507` | `feat_defs_911`, §6.4 |
|
||||||
|
| Opzioni del foro (maschiato, diritto, superficie del filetto) | **non individuato** | §6.4 |
|
||||||
|
| Posizione assoluta del foro (origine e versori) | **fatto**, provato contro i riferimenti di Creo su `9239902104` (§6.1) | `local_sys` del record |
|
||||||
|
| Riferimenti del posizionamento (quale faccia/asse) | **non decodificato** | `plin_info_ref` in `feat_defs_911` |
|
||||||
|
| Valore di raccordi e smussi | **fatto**, provato contro Creo su `9252000507` (D = 0,5) | `dim_array` |
|
||||||
|
| Spigoli a cui si applicano raccordi/smussi | **parziale**: si risolvono le superfici (feature di origine), non lo spigolo né l'insieme | `feat_defs_914`, §6.5 |
|
||||||
|
| Schema quote e insiemi dello smusso | **non decodificato** | `feat_defs_914` |
|
||||||
|
| Tolleranze (h7...) | **non decodificato** | blocco `tol_ptr` del `dim_array` |
|
||||||
|
| Unità di misura | **non letto** | `UnitSystemDef` (sezione compressa) |
|
||||||
|
| Feature nascoste / soppresse | **non decodificato** | non individuato nel file |
|
||||||
|
| Parametri e materiale | parziale, via `ND:0:Model_*` (XML) | sezione compressa `.Z` |
|
||||||
|
| File Wildfire (generazioni 1322/1375/1376) | **illeggibile** (sezioni di tipo `11`) | `formato-prt-feature.md` §1.3 |
|
||||||
|
|
||||||
|
**Conclusione onesta:** oggi si ricava con affidabilità *quali* feature ci sono, *che tipo* sono,
|
||||||
|
*come si chiamano* (se c'è lo storico), *quanto misurano* le quote semplici e *che forma ha lo
|
||||||
|
schizzo* se fatto di linee con numeri semplici. Non si ricava ancora *dove* stanno nello spazio né
|
||||||
|
*come* sono orientate, né si può ricostruire il solido completo.
|
||||||
|
|
||||||
|
## 8. Come finire il lavoro
|
||||||
|
|
||||||
|
Per ogni riga "non decodificato" della §7 il metodo è lo stesso, ed è quello che ha dato Rivoluzione
|
||||||
|
1:
|
||||||
|
|
||||||
|
1. Scegliere un `.prt` piccolo con **una sola feature** del tipo da decodificare, e annotare da
|
||||||
|
Creo i valori veri (quote, posizione dei piani, angolo, direzione).
|
||||||
|
2. Estrarre da `BasFullData`/`ActDatums` i record di quella feature e cercarvi quei numeri con il
|
||||||
|
decodificatore di §1: i valori noti ancorano i byte.
|
||||||
|
3. Variare **una cosa alla volta** nel modello (spostare il piano, cambiare l'angolo), risalvare e
|
||||||
|
confrontare i due file: i byte che cambiano sono il campo cercato.
|
||||||
|
4. Registrare in questo doc il campo, con il file di prova e il valore atteso, come in §5.
|
||||||
|
|
||||||
|
Tre modelli di prova che chiuderebbero la maggior parte della §7: un piano `DTM1` a offset non nullo
|
||||||
|
dal piano `TOP` (e con un vincolo *Attraverso*, *Normale*, *Offset*...); una estrusione di profondità nota su un piano ruotato; una rivoluzione di 90°.
|
||||||
|
|
||||||
|
Una volta chiusa la §7 il passo successivo è un `PrtModelReader` in C# accanto ad `AsmRecordReader`,
|
||||||
|
con le stesse regole: se un valore non si decodifica si solleva `InvalidDataException`, non si
|
||||||
|
restituisce un modello parziale in silenzio.
|
||||||
@@ -0,0 +1,371 @@
|
|||||||
|
# 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.
|
||||||
+160
@@ -0,0 +1,160 @@
|
|||||||
|
# Reverse engineering dei file Creo: stato e ripresa
|
||||||
|
|
||||||
|
Diario di passaggio di consegne per il lavoro sul formato dei `.prt`/`.asm`. Serve a chi riprende
|
||||||
|
(anche in un'altra sessione) per sapere **dove siamo, cosa è provato, da dove ripartire**, senza
|
||||||
|
rifare la strada. Il *risultato* del lavoro sta in [`formato-psb.md`](formato-psb.md): qui c'è
|
||||||
|
solo lo stato, le decisioni e i vicoli ciechi. Quando un punto si chiude, il contenuto va nel doc
|
||||||
|
di formato e qui resta una riga.
|
||||||
|
|
||||||
|
Ultimo aggiornamento: 2026-11-10, ramo `reverse-engineering-creo`.
|
||||||
|
|
||||||
|
## Stato in una riga
|
||||||
|
|
||||||
|
Il formato di serializzazione (PSB) è decodificato e tutte le sezioni in chiaro di
|
||||||
|
`9253000117a.prt.35` si leggono fino all'ultimo byte con nome e valore di ogni campo; **nessun
|
||||||
|
valore è stato ancora confrontato con Creo**, e tre cose bloccano "tutti i dati senza Creo"
|
||||||
|
(vedi "Cosa manca").
|
||||||
|
|
||||||
|
## Da non perdere
|
||||||
|
|
||||||
|
- **Niente è committato.** In sospeso: `docs/formato-psb.md`, `docs/history.md`, `scripts/psb/`,
|
||||||
|
e le modifiche a `CHANGELOG.md`, `docs/README.md`, `docs/architettura.md`,
|
||||||
|
`docs/formato-prt-geometria.md`.
|
||||||
|
- `9253000117a.prt.35` è nella root, non tracciato: è il campione di tutto il lavoro. Decidere
|
||||||
|
se tenerlo nel repo (2 MB) o in `raw/`.
|
||||||
|
- `scripts/psb/__pycache__/` va ignorato, non committato.
|
||||||
|
- `docs/formato-prt-feature.md` contiene byte NUL: gli strumenti di modifica lo rifiutano. Le
|
||||||
|
correzioni che lo riguardano sono in `formato-psb.md` §9, non applicate al file. Va
|
||||||
|
ripulito dai NUL (conservando la codifica) prima di poterlo correggere.
|
||||||
|
|
||||||
|
## Come ripartire in cinque minuti
|
||||||
|
|
||||||
|
Dalla root del repo, con Python 3.12 (solo libreria standard):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python scripts/psb/prt.py 9253000117a.prt.35 # indice delle sezioni
|
||||||
|
python scripts/psb/psb.py 9253000117a.prt.35 '*' out # dump di ogni sezione in out/
|
||||||
|
python scripts/psb/psb.py 9253000117a.prt.35 LargeText - t # traccia campo per campo (debug)
|
||||||
|
python scripts/psb/unz.py 9253000117a.prt.35 out # decomprime le sezioni .Z
|
||||||
|
python scripts/psb/pbin.py out/SolidPrimdata.bin # legge una sezione decompressa
|
||||||
|
python scripts/psb/batch.py "raw/*" # regressione sugli 89 file
|
||||||
|
python scripts/psb/norms.py 9253000117a.prt.35 # controllo norma 1 delle matrici
|
||||||
|
python scripts/psb/tagmap.py 9253000117a.prt.35 "raw/*" # tag double -> prefisso, dalle norme
|
||||||
|
python scripts/psb/ctx.py raw/x.prt.1,VisibGeom,cf8 # hexdump attorno a un offset
|
||||||
|
```
|
||||||
|
|
||||||
|
Valori attesi oggi, da usare come regressione prima e dopo ogni modifica al parser:
|
||||||
|
|
||||||
|
| Controllo | Atteso |
|
||||||
|
|---|---|
|
||||||
|
| `psb.py 9253000117a.prt.35` | 29 sezioni `OK`, 5 no: `AllFeatur` (`pop underflow` sull'ultimo byte), `MdlStatus` e `FeatDefs` (pila non a zero), più l'intestazione delle due ultime |
|
||||||
|
| `norms.py` | `good rows 5397 bad 0` |
|
||||||
|
| `batch.py "raw/*"` | 2152 sezioni lette, 74 no (misurato **prima** delle ultime correzioni ai tag: va rilanciato) |
|
||||||
|
|
||||||
|
Se `norms.py` dà righe "bad" dopo una modifica, si è rotta la tabella dei `double`: è il
|
||||||
|
controllo più sensibile che abbiamo.
|
||||||
|
|
||||||
|
## Cosa è provato, e come
|
||||||
|
|
||||||
|
| Cosa | Prova |
|
||||||
|
|---|---|
|
||||||
|
| Contenitore (offset = ultimo campo della prima riga, riga `#Nome` inclusa, indice a catena) | tutte le sezioni cominciano con `e0 00 <radice>` |
|
||||||
|
| Grammatica e tabella "dopo X viene Y" | lettura fino all'ultimo byte di tutte le sezioni; l'id nell'intestazione degli array coincide con quello calcolato |
|
||||||
|
| Tag dei `double` | norma 1 su 5397 righe; prefissi `3fd0`-`3fef` e `bfd0`-`bfef` verificati uno a uno sugli 89 file |
|
||||||
|
| Costanti π, π/2, 2π, 3π/2 e negative | stesso rumore di mantissa nelle forme modificate (`02 xx ...`) |
|
||||||
|
| Sezioni `.Z` | la dimensione decompressa coincide con quella dichiarata nell'indice |
|
||||||
|
| Significato dei campi | **nessuna**: dedotto dai nomi |
|
||||||
|
|
||||||
|
## Cosa manca, in ordine di valore
|
||||||
|
|
||||||
|
1. **Confronto con Creo.** Aprire un file piccolo, annotare quote, parametri, massa, posizione
|
||||||
|
di una faccia, e confrontare con il dump. Finché non c'è, tutto il doc è "livello 1 e 2".
|
||||||
|
2. **Tabella dei `float` (tag `50`-`df`).** È una tabella di prefissi scelti per frequenza, non
|
||||||
|
una formula. Il parser usa `FBASE` lineare, **sbagliato** per quei tag (lunghezza giusta,
|
||||||
|
valore no). Senza, la mesh di `SolidPrimdata` (`mv_p_NxNyNzxyz`) non si esporta. Metodo che
|
||||||
|
ha funzionato per le 29 voci note: normali unitarie, risolvere il modulo dalla norma. Va
|
||||||
|
ripetuto su tutti i file di `raw/` (ognuno ha `SolidPrimdata`), poi sostituire `FBASE` con
|
||||||
|
un dizionario e far fallire il parser sui tag ignoti invece di inventare.
|
||||||
|
3. **La pila dei contenitori.** I valori sono giusti, l'annidamento no in alcuni punti:
|
||||||
|
- `LargeText` di 24 file di `raw/`: alberi bilanciati (`lesser_child`, `greater_child`,
|
||||||
|
`balance_factor`, `backup_size`). La regola `post` (puntatore "al successivo" scritto al
|
||||||
|
livello del padre) copre un solo campo per struttura; qui i figli sono due.
|
||||||
|
- `srf_flip_dat` (geometria), `end2` (`FeatDefs`): "contenitore non in pila".
|
||||||
|
- fine di `AllFeatur`/`MdlStatus`/`FeatDefs`: pila non a zero.
|
||||||
|
Punto di partenza: `default_close` e `locate` in `psb.py`, con la traccia (`- t`).
|
||||||
|
4. **Sezioni tipo `b`** (`IndentTag`, `FeatRefData`) e **tipo `11` Wildfire**: illeggibili.
|
||||||
|
Stessa lunghezza del chiaro, primi 4 byte in chiaro. Già escluso (vedi
|
||||||
|
`formato-prt-feature.md` §1.3): XOR a chiave fissa, zlib, LZW. Non riprovare senza una
|
||||||
|
coppia chiaro/cifrato dello stesso modello.
|
||||||
|
5. **`DispDataTable`** (tipo `c`): comincia con `1f 9d 10` ma l'LZW standard fallisce al
|
||||||
|
secondo codice. Non indagato.
|
||||||
|
6. **`PSB_Tokens`** (`ND:0:*`): letti i quattro array, non il significato delle lettere di
|
||||||
|
`type_har`. Ipotesi da cui partire: `I` consuma un intero, `F` un double, una lettera per le
|
||||||
|
stringhe; `M`/`O` aprono oggetto e campo. Contare le occorrenze contro le lunghezze degli
|
||||||
|
array la conferma o la smentisce in pochi minuti.
|
||||||
|
7. **Costanti `double` ignote**: `04` (vista come componente di tangente e come estremo di
|
||||||
|
inviluppo), `0d` (−0,5 da un solo caso), buco `bffe` contro `bffd` non deciso.
|
||||||
|
8. **Portare il parser in C#** accanto ad `AsmRecordReader`: solo dopo i punti 1 e 3.
|
||||||
|
|
||||||
|
## Il ragionamento da adottare
|
||||||
|
|
||||||
|
È il metodo che ha portato fin qui. Va seguito anche per i punti aperti: ogni volta che è stato
|
||||||
|
saltato, si è perso tempo.
|
||||||
|
|
||||||
|
1. **Partire dal modello di chi scrive, non dai byte.** Il file è l'uscita di un serializzatore
|
||||||
|
generico che percorre strutture C. La domanda utile non è "cosa significano questi byte" ma
|
||||||
|
"che regola semplice userebbe un programma per scriverli". Ogni marcatore ridondante (`f7`,
|
||||||
|
`f0`-`f4`) esiste perché il lettore, da solo, avrebbe previsto un'altra cosa: capire la
|
||||||
|
previsione spiega il marcatore.
|
||||||
|
2. **Un'ipotesi alla volta, e un controllo che la possa smentire.** Prima di scrivere codice
|
||||||
|
dire cosa ci si aspetta di vedere se l'ipotesi è vera. Esempio: "se gli id sono progressivi,
|
||||||
|
l'id nell'intestazione di `unit_arr` deve essere 0x14" (contato a mano: lo era).
|
||||||
|
3. **Il parser è lo strumento di misura.** Una regola è buona se fa arrivare la lettura
|
||||||
|
all'ultimo byte di *tutte* le sezioni; l'errore dice dove guardare. Si corregge la regola
|
||||||
|
generale, mai il caso singolo: un'eccezione scritta per una sezione ha sempre rotto un'altra.
|
||||||
|
4. **Cercare i controesempi, non le conferme.** Quando una regola spiega cinque casi, elencare
|
||||||
|
per iscritto i casi noti (con i byte) e verificare che li spieghi tutti, compreso quello
|
||||||
|
scomodo. La regola sulle chiusure implicite è nata mettendo in tabella dieci casi e cercando
|
||||||
|
l'unica formulazione che non ne contraddiceva nessuno.
|
||||||
|
5. **Ancorare i numeri a vincoli fisici, non all'aspetto.** Un valore "plausibile" non è una
|
||||||
|
prova. Lo sono: la norma 1 delle righe di una matrice di rotazione, valori noti (π, √2/2,
|
||||||
|
0,1, 25,4), la coerenza tra sezioni (lo stesso punto scritto due volte). Per i tag dei
|
||||||
|
numeri: far risolvere il prefisso al vincolo (`tagmap.py`), non estrapolare una formula.
|
||||||
|
6. **Separare ciò che è provato da ciò che è dedotto.** Tokenizzazione (sequenza campo-valore),
|
||||||
|
annidamento e significato dei campi sono tre cose con tre livelli di prova diversi. Nel doc
|
||||||
|
ognuna va dichiarata col suo livello; "regge sui campioni" non è "è così".
|
||||||
|
7. **Allargare il campione appena una regola regge.** Un file solo nasconde le tabelle: i buchi
|
||||||
|
nei prefissi negativi e la tabella dei `float` sono emersi solo confrontando più file.
|
||||||
|
Dopo ogni modifica: `norms.py` sul campione, poi `batch.py` su `raw/`.
|
||||||
|
8. **Quando due spiegazioni reggono entrambe, cercare il dato che le distingue** invece di
|
||||||
|
scegliere la più elegante (esempio: i tag `91`-`a0` sembravano negativi; li ha decisi una
|
||||||
|
curva i cui parametri dovevano crescere da 3π/2).
|
||||||
|
9. **Scrivere subito nel doc, con il sintomo e i byte.** Una scoperta non scritta si rifà.
|
||||||
|
Scrivere anche ciò che è stato escluso e perché.
|
||||||
|
10. **Dire quello che non si sa.** Una costante ignota resta "ignota" (`<c04>` nel dump), non
|
||||||
|
riceve un valore inventato; un parser che non capisce si ferma, non restituisce un risultato
|
||||||
|
parziale in silenzio.
|
||||||
|
|
||||||
|
## Vicoli ciechi e trappole, per non rifarli
|
||||||
|
|
||||||
|
- **Modello "pop = chiudi N livelli" puro**: non basta. I marcatori `f0`-`f4` compaiono solo
|
||||||
|
quando la chiusura reale è diversa da quella che il lettore farebbe da solo; `f0` vuol dire
|
||||||
|
"resta qui". Ci sono volute quattro riscritture per arrivarci.
|
||||||
|
- **Ordine dei campi per struttura** (primo/successivo per schema): sbagliato. La tabella è
|
||||||
|
piatta, per id, e vale l'ultima transizione vista.
|
||||||
|
- **`e4`-`e7` come "zeri"**: sono ripetizioni del valore precedente.
|
||||||
|
- **Lo stesso id in contenitori diversi**: succede con le varianti di unione
|
||||||
|
(`typed_data(...)`); il parser le accetta e le conta in `lenient`.
|
||||||
|
- **Tag `double` negativi lineari**: no, ci sono tre buchi. Non fidarsi di un'estrapolazione
|
||||||
|
fuori dagli intervalli verificati.
|
||||||
|
- **Heredoc in Bash con `\\0`**: lo strumento collassa le barre e scrive un NUL nel sorgente.
|
||||||
|
Per creare file Python usare lo strumento di scrittura, non un heredoc.
|
||||||
|
- **Git Bash e `cd`**: gli script vanno lanciati dalla root del repo; `dotnet run` dentro
|
||||||
|
`PrepStep/` avvia la GUI.
|
||||||
|
|
||||||
|
## Cronologia
|
||||||
|
|
||||||
|
| Data | Fatto |
|
||||||
|
|---|---|
|
||||||
|
| 2026-10-21 | Albero delle feature, quote e geometria degli schizzi letti a pattern (`formato-prt-feature.md`, `formato-prt-geometria.md`) |
|
||||||
|
| 2026-11-10 | Contenitore corretto; grammatica PSB completa; tabella dei `double`; sezioni `.Z`; catalogo delle sezioni; parser di riferimento in `scripts/psb/`; `formato-psb.md` |
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import sys,os,glob,collections
|
||||||
|
sys.path.insert(0,os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
import prt,psb
|
||||||
|
files=sorted(glob.glob(sys.argv[1]))
|
||||||
|
tot=collections.Counter(); errs=collections.Counter(); bygen=collections.defaultdict(collections.Counter)
|
||||||
|
samples={}
|
||||||
|
for f in files:
|
||||||
|
try: d,secs,order=prt.load(f)
|
||||||
|
except Exception as e:
|
||||||
|
print('LOADERR',f,e); continue
|
||||||
|
gen=d[:60].split()[2].decode()
|
||||||
|
for n in order:
|
||||||
|
o,l,fl=secs[n]
|
||||||
|
typ=fl[2]
|
||||||
|
if typ!='9': bygen[gen]['type_'+typ]+=1; continue
|
||||||
|
b=d[o:o+l]
|
||||||
|
p,root,e=psb.parse(b)
|
||||||
|
full = p.p==len(b)
|
||||||
|
if e is None and full: bygen[gen]['ok']+=1; bygen[gen]['okbytes']+=l
|
||||||
|
else:
|
||||||
|
bygen[gen]['err']+=1; bygen[gen]['errbytes']+=l
|
||||||
|
key=(n.split('#')[0], str(e).split('@')[0][:50])
|
||||||
|
errs[key]+=1; samples.setdefault(key,(os.path.basename(f),str(e)))
|
||||||
|
for g,c in sorted(bygen.items()): print(g,dict(c))
|
||||||
|
for k,v in errs.most_common(40): print(v,k,samples[k])
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import sys,os
|
||||||
|
sys.path.insert(0,os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
import prt
|
||||||
|
for a in sys.argv[1:]:
|
||||||
|
f,sec,off=a.split(',')
|
||||||
|
off=int(off,16)
|
||||||
|
d,secs,order=prt.load(f); o,l,_=secs[sec]; b=d[o:o+l]
|
||||||
|
print('==',a)
|
||||||
|
prt.hexdump(b,max(0,off-96),192)
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import sys,os,math,collections
|
||||||
|
sys.path.insert(0,os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
import prt,psb
|
||||||
|
d,secs,order=prt.load(sys.argv[1])
|
||||||
|
bad=collections.Counter(); good=0; unk=collections.Counter()
|
||||||
|
def walk(v,name=''):
|
||||||
|
global good
|
||||||
|
if isinstance(v,psb.Node):
|
||||||
|
for n,x in v: walk(x,n)
|
||||||
|
elif isinstance(v,psb.Arr):
|
||||||
|
for x in v: walk(x,name)
|
||||||
|
elif isinstance(v,list) and v and isinstance(v[0],list) and len(v) in (3,4) and len(v[0])==3 and name in ('local_sys','orient','matrix'):
|
||||||
|
for r in v[:3]:
|
||||||
|
if any(isinstance(c,psb.Unk) for c in r):
|
||||||
|
u=[c for c in r if isinstance(c,psb.Unk)]
|
||||||
|
if len(u)==1:
|
||||||
|
rest=sum(c*c for c in r if not isinstance(c,psb.Unk))
|
||||||
|
unk[(u[0].code, round(math.sqrt(max(0,1-rest)),6))]+=1
|
||||||
|
continue
|
||||||
|
n=math.sqrt(sum(c*c for c in r))
|
||||||
|
if abs(n-1)<1e-6: good+=1
|
||||||
|
else: bad[(name,tuple(round(c,4) for c in r))]+=1
|
||||||
|
for nme in order:
|
||||||
|
o,l,f=secs[nme]
|
||||||
|
if f[2]!='9': continue
|
||||||
|
p,root,e=psb.parse(d[o:o+l])
|
||||||
|
if root: walk(root[1])
|
||||||
|
print('good rows',good,'bad',sum(bad.values()))
|
||||||
|
for k,v in bad.most_common(15): print(v,k)
|
||||||
|
print('unknown consts solved by norm:',unk.most_common(20))
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import sys,os
|
||||||
|
sys.path.insert(0,os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
import prt,psb
|
||||||
|
for f in sys.argv[1:]:
|
||||||
|
b=open(f,'rb').read()
|
||||||
|
p,root,e=psb.parse(b)
|
||||||
|
print(os.path.basename(f),len(b),p.p,'fields',p.nid,'lenient',p.lenient,'OK' if e is None else 'ERR '+repr(e))
|
||||||
|
if e is not None: prt.hexdump(b,max(0,p.p-64),128)
|
||||||
|
if root:
|
||||||
|
out=[root[0]+':']; psb.fmt(root[1],1,out)
|
||||||
|
open(f[:-4]+'.txt','w',encoding='utf-8').write('\n'.join(out))
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import sys,re
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8',errors='replace')
|
||||||
|
def load(p):
|
||||||
|
d=open(p,'rb').read()
|
||||||
|
base=int(d[:d.find(b'\n')].split()[-2],16)
|
||||||
|
secs={}
|
||||||
|
order=[]
|
||||||
|
i=d.find(b'#UGC_TOC')
|
||||||
|
while True:
|
||||||
|
e=d.find(b'\n',i); pos=e+1; nxt=None
|
||||||
|
while True:
|
||||||
|
e2=d.find(b'\n',pos); line=d[pos:e2]
|
||||||
|
if line.startswith(b'#'): break
|
||||||
|
f=line.rstrip(b'#').split()
|
||||||
|
name=f[0].decode('latin1'); off=int(f[1],16); ln=int(f[2],16)
|
||||||
|
if name=='NEXT_TOC_ENTRY': nxt=base+off
|
||||||
|
else:
|
||||||
|
mk=len(name)+2
|
||||||
|
secs[name]=(base+off+mk,ln-mk,[x.decode() for x in f[3:]]); order.append(name)
|
||||||
|
pos=e2+1
|
||||||
|
if nxt is None: break
|
||||||
|
i=nxt
|
||||||
|
return d,secs,order
|
||||||
|
def sec(p,name):
|
||||||
|
d,secs,_=load(p); o,l,_=secs[name]; return d[o:o+l]
|
||||||
|
def hexdump(b,start=0,n=None,w=32):
|
||||||
|
b=b[start:start+n] if n else b[start:]
|
||||||
|
for i in range(0,len(b),w):
|
||||||
|
c=b[i:i+w]
|
||||||
|
print(f"{start+i:6x} "+' '.join(f"{x:02x}" for x in c).ljust(w*3)+' '+''.join(chr(x) if 32<=x<127 else '.' for x in c))
|
||||||
|
if __name__=='__main__':
|
||||||
|
d,secs,order=load(sys.argv[1])
|
||||||
|
if len(sys.argv)>2:
|
||||||
|
o,l,_=secs[sys.argv[2]]; b=d[o:o+l]
|
||||||
|
st=int(sys.argv[3],0) if len(sys.argv)>3 else 0
|
||||||
|
n=int(sys.argv[4],0) if len(sys.argv)>4 else None
|
||||||
|
hexdump(b,st,n)
|
||||||
|
else:
|
||||||
|
for nme in order:
|
||||||
|
o,l,f=secs[nme]; print(f"{nme:26} {o:8} {l:8} {' '.join(f):34} {d[o:o+20]!r}")
|
||||||
@@ -0,0 +1,529 @@
|
|||||||
|
"""Parser generico delle sezioni a marcatori dei file Creo (.prt/.asm). Reverse engineering.
|
||||||
|
Modello: ogni campo dichiarato per nome (e0 <tipo> nome\\0) riceve un id progressivo.
|
||||||
|
In modalita' posizionale il campo atteso e' next[ultimo id letto]; f7 <id> lo forza.
|
||||||
|
"""
|
||||||
|
import struct, sys, os
|
||||||
|
|
||||||
|
|
||||||
|
class Err(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Node(list):
|
||||||
|
"""struct: lista di (nome, valore)"""
|
||||||
|
|
||||||
|
def get(s, k, d=None):
|
||||||
|
for n, v in s:
|
||||||
|
if n == k:
|
||||||
|
return v
|
||||||
|
return d
|
||||||
|
|
||||||
|
def all(s, k):
|
||||||
|
return [v for n, v in s if n == k]
|
||||||
|
|
||||||
|
|
||||||
|
class Arr(list):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Unk(float):
|
||||||
|
"""costante a un byte non ancora identificata"""
|
||||||
|
def __new__(cls, code):
|
||||||
|
o = float.__new__(cls, 'nan')
|
||||||
|
o.code = code
|
||||||
|
return o
|
||||||
|
|
||||||
|
def __repr__(s):
|
||||||
|
return f"<c{s.code:02x}>"
|
||||||
|
|
||||||
|
|
||||||
|
class P:
|
||||||
|
def __init__(s, b, trace=False):
|
||||||
|
s.b = b
|
||||||
|
s.p = 0
|
||||||
|
s.fields = {}
|
||||||
|
s.nid = 0
|
||||||
|
s.next = {}
|
||||||
|
s.alias = {}
|
||||||
|
s.stack = []
|
||||||
|
s.trace = trace
|
||||||
|
s.root = None
|
||||||
|
s.last = None
|
||||||
|
s.lenient = 0
|
||||||
|
s.lastc = None
|
||||||
|
s.popped = None
|
||||||
|
|
||||||
|
def u8(s):
|
||||||
|
v = s.b[s.p]
|
||||||
|
s.p += 1
|
||||||
|
return v
|
||||||
|
|
||||||
|
def peek(s):
|
||||||
|
return s.b[s.p] if s.p < len(s.b) else None
|
||||||
|
|
||||||
|
def rint(s):
|
||||||
|
b = s.u8()
|
||||||
|
if b < 0x80:
|
||||||
|
return b
|
||||||
|
if b < 0xc0:
|
||||||
|
return (b & 0x3f) << 8 | s.u8()
|
||||||
|
if b < 0xe0:
|
||||||
|
v = (b & 0x1f) << 16 | s.b[s.p] << 8 | s.b[s.p + 1]
|
||||||
|
s.p += 2
|
||||||
|
return v
|
||||||
|
if b == 0xe8:
|
||||||
|
return s.u8()
|
||||||
|
if b == 0xe9:
|
||||||
|
v = int.from_bytes(s.b[s.p:s.p + 2], 'big')
|
||||||
|
s.p += 2
|
||||||
|
return v
|
||||||
|
if b == 0xea:
|
||||||
|
v = int.from_bytes(s.b[s.p:s.p + 3], 'big')
|
||||||
|
s.p += 3
|
||||||
|
return v
|
||||||
|
if b == 0xeb:
|
||||||
|
v = int.from_bytes(s.b[s.p:s.p + 4], 'big')
|
||||||
|
s.p += 4
|
||||||
|
return v
|
||||||
|
if b == 0xf5:
|
||||||
|
return s.rint() | 0x80000000
|
||||||
|
if b == 0xf6:
|
||||||
|
return -1
|
||||||
|
raise Err(f"int tag {b:02x} @{s.p - 1:x}")
|
||||||
|
|
||||||
|
CONST = {0x17: -6.283185307179586, 0x14: 4.71238898038469, 0x0d: -0.5, 0x11: 3.141592653589793, 0x15: -3.141592653589793, 0x12: 1.5707963267948966, 0x16: -1.5707963267948966, 0x13: 6.283185307179586, 0x18: 0.0, 0x0f: 1.0, 0x0e: 0.5, 0x10: -1.0, 0x0c: 0.1, 0x0a: 0.01, 0x07: 0.001}
|
||||||
|
|
||||||
|
FBASE = 0x3f9d
|
||||||
|
FCONST = {0x0b: 1.0, 0x28: -1.0}
|
||||||
|
|
||||||
|
def rflt(s):
|
||||||
|
t = s.u8()
|
||||||
|
if 0x30 <= t <= 0x4f:
|
||||||
|
T = (0x37 + t - 0x30) if t < 0x40 else (0xb7 + t - 0x40)
|
||||||
|
raw = bytes([T]) + s.b[s.p:s.p + 3]
|
||||||
|
s.p += 3
|
||||||
|
elif t == 0xea:
|
||||||
|
raw = s.b[s.p:s.p + 3] + bytes(1)
|
||||||
|
s.p += 3
|
||||||
|
elif t == 0xeb:
|
||||||
|
raw = s.b[s.p:s.p + 4]
|
||||||
|
s.p += 4
|
||||||
|
elif t == 0xe9:
|
||||||
|
raw = s.b[s.p:s.p + 2] + bytes(2)
|
||||||
|
s.p += 2
|
||||||
|
elif t == 0xe8:
|
||||||
|
raw = s.b[s.p:s.p + 1] + bytes(3)
|
||||||
|
s.p += 1
|
||||||
|
elif t == 0x00:
|
||||||
|
return 0.0
|
||||||
|
elif t in s.FCONST:
|
||||||
|
return s.FCONST[t]
|
||||||
|
elif t < 0x30:
|
||||||
|
return Unk(t)
|
||||||
|
elif 0x50 <= t <= 0xdf:
|
||||||
|
raw = (s.FBASE + t).to_bytes(2, 'big') + s.b[s.p:s.p + 2]
|
||||||
|
s.p += 2
|
||||||
|
else:
|
||||||
|
raise Err(f"flt tag {t:02x} @{s.p - 1:x}")
|
||||||
|
return struct.unpack('>f', raw)[0]
|
||||||
|
|
||||||
|
def rbyte(s):
|
||||||
|
b = s.u8()
|
||||||
|
if b == 0xe8:
|
||||||
|
return s.u8()
|
||||||
|
return b
|
||||||
|
|
||||||
|
# prefissi negativi a 16 bit dei tag a5-df: bfd0..c00d meno tre buchi (bfd2, bfdf, bffe)
|
||||||
|
NEG = [x for x in range(0xbfd0, 0xc00e) if x not in (0xbfd2, 0xbfdf, 0xbffe)]
|
||||||
|
|
||||||
|
def rdbl(s):
|
||||||
|
t = s.u8()
|
||||||
|
if t in s.CONST:
|
||||||
|
return s.CONST[t]
|
||||||
|
if t <= 0x16 and t > 0x03:
|
||||||
|
return Unk(t)
|
||||||
|
if t <= 0x03:
|
||||||
|
c = s.u8()
|
||||||
|
k = t + 1
|
||||||
|
pl = s.b[s.p:s.p + k]
|
||||||
|
s.p += k
|
||||||
|
if c not in s.CONST:
|
||||||
|
if 0x04 <= c <= 0x16:
|
||||||
|
return Unk(c)
|
||||||
|
raise Err(f"dbl const {c:02x} after modifier @{s.p - 1:x}")
|
||||||
|
raw = bytearray(struct.pack('>d', s.CONST[c]))
|
||||||
|
raw[8 - k:] = pl
|
||||||
|
return struct.unpack('>d', bytes(raw))[0]
|
||||||
|
if t == 0xed:
|
||||||
|
v = struct.unpack('>d', s.b[s.p:s.p + 8])[0]
|
||||||
|
s.p += 8
|
||||||
|
return v
|
||||||
|
if t == 0xea:
|
||||||
|
v = struct.unpack('>d', s.b[s.p:s.p + 3] + bytes(5))[0]
|
||||||
|
s.p += 3
|
||||||
|
return v
|
||||||
|
if t == 0xec:
|
||||||
|
v = struct.unpack('>d', s.b[s.p:s.p + 7] + bytes(1))[0]
|
||||||
|
s.p += 7
|
||||||
|
return v
|
||||||
|
if 0x19 <= t <= 0x48:
|
||||||
|
if t <= 0x2f:
|
||||||
|
T = 0x3c + (t - 0x17) // 5
|
||||||
|
m = (t - 0x17) % 5
|
||||||
|
else:
|
||||||
|
T = 0xbc + (t - 0x30) // 5
|
||||||
|
m = (t - 0x30) % 5
|
||||||
|
if m == 4:
|
||||||
|
pl = s.b[s.p:s.p + 2]
|
||||||
|
s.p += 2
|
||||||
|
raw = bytes([T]) + pl + b'\x00' * 5
|
||||||
|
elif m == 3:
|
||||||
|
pl = s.b[s.p:s.p + 2]
|
||||||
|
s.p += 2
|
||||||
|
raw = bytes([T]) + pl + pl[1:] * 5
|
||||||
|
elif m == 2:
|
||||||
|
pl = s.b[s.p:s.p + 7]
|
||||||
|
s.p += 7
|
||||||
|
raw = bytes([T]) + pl
|
||||||
|
elif m == 1:
|
||||||
|
pl = s.b[s.p:s.p + 6]
|
||||||
|
s.p += 6
|
||||||
|
raw = bytes([T]) + pl + b'\x00'
|
||||||
|
else:
|
||||||
|
pl = s.b[s.p:s.p + 3]
|
||||||
|
s.p += 3
|
||||||
|
raw = bytes([T]) + pl + bytes(4)
|
||||||
|
return struct.unpack('>d', raw)[0]
|
||||||
|
if 0x49 <= t <= 0xdf:
|
||||||
|
hi = (0x3f75 + t) if t <= 0xa4 else s.NEG[t - 0xa5]
|
||||||
|
pl = s.b[s.p:s.p + 6]
|
||||||
|
s.p += 6
|
||||||
|
return struct.unpack('>d', hi.to_bytes(2, 'big') + pl)[0]
|
||||||
|
raise Err(f"dbl tag {t:02x} @{s.p - 1:x}")
|
||||||
|
|
||||||
|
def rstr(s):
|
||||||
|
if s.peek() == 0xe1:
|
||||||
|
s.p += 1
|
||||||
|
return None
|
||||||
|
e = s.b.index(0, s.p)
|
||||||
|
v = s.b[s.p:e].decode('latin1')
|
||||||
|
s.p = e + 1
|
||||||
|
return v
|
||||||
|
|
||||||
|
def rarr(s, n, rd):
|
||||||
|
out = []
|
||||||
|
while len(out) < n:
|
||||||
|
b = s.peek()
|
||||||
|
if 0xe4 <= b <= 0xe6 and out:
|
||||||
|
s.p += 1
|
||||||
|
out += [out[-1]] * (b - 0xe3)
|
||||||
|
elif b == 0xe7 and out:
|
||||||
|
s.p += 1
|
||||||
|
k = s.rint()
|
||||||
|
out += [out[-1]] * k
|
||||||
|
else:
|
||||||
|
out.append(rd())
|
||||||
|
if len(out) != n:
|
||||||
|
raise Err(f"array overrun {len(out)}>{n} @{s.p:x}")
|
||||||
|
return out
|
||||||
|
|
||||||
|
def scalar(s, f):
|
||||||
|
t = f['t']
|
||||||
|
if t in (0x0a, 0x03):
|
||||||
|
if s.peek() == 0xf8:
|
||||||
|
s.p += 1
|
||||||
|
f['dim'] = (s.rint(),)
|
||||||
|
return [s.rstr() for _ in range(f['dim'][0])]
|
||||||
|
if f.get('dim'):
|
||||||
|
out = []
|
||||||
|
while len(out) < f['dim'][0] and 0x20 <= s.peek() < 0xe0:
|
||||||
|
out.append(s.rstr())
|
||||||
|
return out
|
||||||
|
return s.rstr()
|
||||||
|
rd = s.rdbl if t == 0x02 else s.rbyte if t in (0x07, 0x04) else s.rflt if t == 0x06 else s.rint
|
||||||
|
b = s.peek()
|
||||||
|
if b == 0xe1:
|
||||||
|
s.p += 1
|
||||||
|
return None
|
||||||
|
if b == 0xf8:
|
||||||
|
s.p += 1
|
||||||
|
f['dim'] = (s.rint(),)
|
||||||
|
elif b == 0xf9:
|
||||||
|
s.p += 1
|
||||||
|
f['dim'] = (s.rint(), s.rint())
|
||||||
|
elif b == 0xfc:
|
||||||
|
s.p += 1
|
||||||
|
f['dim'] = (s.rint(),) + tuple(f['dim'][1:])
|
||||||
|
elif b == 0xfa:
|
||||||
|
s.p += 1
|
||||||
|
f['dim'] = tuple(s.rint() for _ in range(s.rint()))
|
||||||
|
d = f.get('dim')
|
||||||
|
if d is None:
|
||||||
|
return rd()
|
||||||
|
n = 1
|
||||||
|
for k in d:
|
||||||
|
n *= k
|
||||||
|
v = s.rarr(n, rd)
|
||||||
|
if len(d) == 2:
|
||||||
|
v = [v[i * d[1]:(i + 1) * d[1]] for i in range(d[0])]
|
||||||
|
return v
|
||||||
|
|
||||||
|
def sch(s, fid):
|
||||||
|
return s.alias.get(fid, fid)
|
||||||
|
|
||||||
|
# --- contenitori -------------------------------------------------
|
||||||
|
def owner_index(s, E, post=False):
|
||||||
|
own = s.fields[E]['own']
|
||||||
|
for i in range(len(s.stack) - 1, -1, -1):
|
||||||
|
c = s.stack[i]
|
||||||
|
if c['kind'] == 'struct':
|
||||||
|
if c['schema'] in own:
|
||||||
|
if post and s.fields[E].get('post') and i > 0:
|
||||||
|
return i - 1
|
||||||
|
return i
|
||||||
|
elif c['occ'] == E or ('A', c['occ']) in own:
|
||||||
|
return i
|
||||||
|
return None
|
||||||
|
|
||||||
|
def default_close(s):
|
||||||
|
"""chiusura implicita prima di un campo con nome o di un salto senza marcatore f0-f4:
|
||||||
|
se il campo atteso appartiene al contenitore padre di quello in cui e' stato scritto
|
||||||
|
l'ultimo campo, quest'ultimo contenitore si considera chiuso."""
|
||||||
|
if s.last is None or not s.stack or s.lastc is None:
|
||||||
|
return
|
||||||
|
E = s.next.get(s.last)
|
||||||
|
if E is None:
|
||||||
|
return
|
||||||
|
p = None
|
||||||
|
for k in range(len(s.stack) - 1, -1, -1):
|
||||||
|
if s.stack[k] is s.lastc:
|
||||||
|
p = k
|
||||||
|
break
|
||||||
|
if not p:
|
||||||
|
return
|
||||||
|
i = s.owner_index(E, True)
|
||||||
|
if i == p - 1:
|
||||||
|
del s.stack[p:]
|
||||||
|
|
||||||
|
def marker(s):
|
||||||
|
b = s.peek()
|
||||||
|
if b is None or not (0xf0 <= b <= 0xf4):
|
||||||
|
return None
|
||||||
|
s.p += 1
|
||||||
|
if b == 0xf4:
|
||||||
|
return s.rint()
|
||||||
|
return b - 0xf0
|
||||||
|
|
||||||
|
def explicit(s, n):
|
||||||
|
for _ in range(n):
|
||||||
|
if not s.stack:
|
||||||
|
raise Err(f"pop underflow @{s.p:x}")
|
||||||
|
s.popped = s.stack.pop()
|
||||||
|
|
||||||
|
def trans(s, E):
|
||||||
|
if s.last is not None:
|
||||||
|
s.next[s.last] = E
|
||||||
|
s.last = E
|
||||||
|
|
||||||
|
def locate(s, E, jump, stay):
|
||||||
|
"""porta in cima allo stack il contenitore che riceve il campo E. Ritorna 'elem' o 'field'."""
|
||||||
|
f = s.fields[E]
|
||||||
|
hdr = s.peek() in (0xf8, 0xf9)
|
||||||
|
top = s.stack[-1]
|
||||||
|
if not hdr and f['t'] == 0:
|
||||||
|
for i in range(len(s.stack) - 1, -1, -1):
|
||||||
|
c = s.stack[i]
|
||||||
|
if c['kind'] == 'array' and c['occ'] == E:
|
||||||
|
del s.stack[i + 1:]
|
||||||
|
return 'elem'
|
||||||
|
par = f['parent']
|
||||||
|
i = s.owner_index(E, not jump)
|
||||||
|
if i is None and jump and top['kind'] == 'struct' and top['node']:
|
||||||
|
# variante di unione riusata in un altro contenitore: resta dove siamo
|
||||||
|
f['own'].add(top['schema'])
|
||||||
|
s.lenient += 1
|
||||||
|
return 'field'
|
||||||
|
fresh = top['kind'] == 'struct' and not top['node']
|
||||||
|
if jump and fresh and i != len(s.stack) - 1:
|
||||||
|
s.alias[top['field']] = par
|
||||||
|
top['schema'] = par
|
||||||
|
return 'field'
|
||||||
|
if i is not None:
|
||||||
|
if jump and i != len(s.stack) - 1:
|
||||||
|
s.lenient += 1
|
||||||
|
del s.stack[i + 1:]
|
||||||
|
return 'field'
|
||||||
|
if jump and top['kind'] == 'array' and not top['node'] and f['t'] == 0 and not hdr:
|
||||||
|
top['occ'] = E
|
||||||
|
return 'elem'
|
||||||
|
raise Err(f"field {E} {f['name']}: container {par} not on stack @{s.p:x}")
|
||||||
|
|
||||||
|
def element(s, E):
|
||||||
|
c = s.stack[-1]
|
||||||
|
s.trans(E)
|
||||||
|
s.lastc = c
|
||||||
|
if s.peek() == 0xfb:
|
||||||
|
s.p += 1
|
||||||
|
b = s.u8()
|
||||||
|
if len(c['node']) >= c['n']:
|
||||||
|
raise Err(f"too many elements in {s.fields[c['field']]['name']} @{s.p:x}")
|
||||||
|
if b == 0xe1:
|
||||||
|
c['node'].append(None)
|
||||||
|
elif b in (0xe2, 0xe3):
|
||||||
|
v = Node()
|
||||||
|
c['node'].append(v)
|
||||||
|
s.stack.append(dict(kind='struct', field=c['field'], schema=s.sch(c['occ']), node=v, elem=True, occ=None, occ0=c['occ']))
|
||||||
|
else:
|
||||||
|
raise Err(f"array elem tag {b:02x} @{s.p - 1:x} in {s.fields[c['field']]['name']}")
|
||||||
|
|
||||||
|
def value(s, fid):
|
||||||
|
c = s.stack[-1] if s.stack else None
|
||||||
|
f = s.fields[fid]
|
||||||
|
s.trans(fid)
|
||||||
|
s.lastc = c
|
||||||
|
if f['t'] != 0:
|
||||||
|
v = s.scalar(f)
|
||||||
|
else:
|
||||||
|
b = s.u8()
|
||||||
|
if b == 0xe1:
|
||||||
|
v = None
|
||||||
|
elif b in (0xe2, 0xe3):
|
||||||
|
v = Node()
|
||||||
|
elif b == 0xf8:
|
||||||
|
v = Arr()
|
||||||
|
n = s.rint()
|
||||||
|
elif b == 0xf9:
|
||||||
|
v = Arr()
|
||||||
|
n = s.rint()
|
||||||
|
s.rint()
|
||||||
|
else:
|
||||||
|
raise Err(f"ptr tag {b:02x} @{s.p - 1:x} field {f['name']}")
|
||||||
|
if c is None:
|
||||||
|
s.root = (f['name'], v)
|
||||||
|
elif c['kind'] == 'array':
|
||||||
|
c.setdefault('tail', []).append((f['name'], v))
|
||||||
|
else:
|
||||||
|
c['node'].append((f['name'], v))
|
||||||
|
if isinstance(v, Arr):
|
||||||
|
s.stack.append(dict(kind='array', field=fid, occ=fid, n=n, node=v))
|
||||||
|
elif isinstance(v, Node):
|
||||||
|
s.stack.append(dict(kind='struct', field=fid, schema=s.sch(fid), node=v, occ=None))
|
||||||
|
|
||||||
|
def run(s):
|
||||||
|
b = s.b
|
||||||
|
while s.p < len(b):
|
||||||
|
m = s.marker()
|
||||||
|
x = s.peek()
|
||||||
|
if x is None:
|
||||||
|
s.explicit(m or 0)
|
||||||
|
break
|
||||||
|
if x == 0xe0:
|
||||||
|
s.p += 1
|
||||||
|
t = s.u8()
|
||||||
|
e = b.index(0, s.p)
|
||||||
|
name = b[s.p:e].decode('latin1')
|
||||||
|
s.p = e + 1
|
||||||
|
m2 = s.marker()
|
||||||
|
if m2 is not None:
|
||||||
|
m = (m or 0) + m2
|
||||||
|
s.popped = None
|
||||||
|
if m is None:
|
||||||
|
s.default_close()
|
||||||
|
else:
|
||||||
|
s.explicit(m)
|
||||||
|
c = s.stack[-1] if s.stack else None
|
||||||
|
s.nid += 1
|
||||||
|
fid = s.nid
|
||||||
|
if c is None:
|
||||||
|
par = 0
|
||||||
|
elif c['kind'] == 'array':
|
||||||
|
par = ('A', c['occ'])
|
||||||
|
else:
|
||||||
|
par = c['schema']
|
||||||
|
s.fields[fid] = dict(name=name, t=t, parent=par, own={par})
|
||||||
|
pp = s.popped
|
||||||
|
if pp is not None and c is not None and pp['kind'] == 'struct' and c['kind'] == 'struct' and pp['schema'] == c['schema'] and t == 0:
|
||||||
|
s.fields[fid]['post'] = True
|
||||||
|
if s.trace:
|
||||||
|
print(f"{s.p:6x} {' ' * len(s.stack)}#{fid} {name} t={t}")
|
||||||
|
s.value(fid)
|
||||||
|
continue
|
||||||
|
if m:
|
||||||
|
s.explicit(m)
|
||||||
|
jump = False
|
||||||
|
if x == 0xf7:
|
||||||
|
s.p += 1
|
||||||
|
E = s.rint()
|
||||||
|
jump = True
|
||||||
|
if m is None:
|
||||||
|
s.default_close()
|
||||||
|
if E not in s.fields:
|
||||||
|
raise Err(f"jump to unknown field {E} @{s.p:x}")
|
||||||
|
else:
|
||||||
|
E = s.next.get(s.last)
|
||||||
|
if E is None:
|
||||||
|
raise Err(f"no expected field @{s.p:x} after {s.last} "
|
||||||
|
f"{s.fields[s.last]['name'] if s.last else ''}")
|
||||||
|
kind = s.locate(E, jump or m is not None, m == 0)
|
||||||
|
if s.trace:
|
||||||
|
print(f"{s.p:6x} {' ' * len(s.stack)}{'>' if jump else '='}{E} {s.fields[E]['name']} {kind}")
|
||||||
|
if kind == 'elem':
|
||||||
|
s.element(E)
|
||||||
|
else:
|
||||||
|
s.value(E)
|
||||||
|
return s.root
|
||||||
|
|
||||||
|
|
||||||
|
def short(x, maxarr=40):
|
||||||
|
if isinstance(x, Unk):
|
||||||
|
return repr(x)
|
||||||
|
if isinstance(x, float):
|
||||||
|
return repr(round(x, 9))
|
||||||
|
if isinstance(x, list):
|
||||||
|
r = ', '.join(short(y) for y in x[:maxarr])
|
||||||
|
return f"[{r}{', ...' if len(x) > maxarr else ''}] (n={len(x)})"
|
||||||
|
return repr(x)
|
||||||
|
|
||||||
|
|
||||||
|
def fmt(v, ind=0, out=None, maxarr=40):
|
||||||
|
pad = ' ' * ind
|
||||||
|
if isinstance(v, Node):
|
||||||
|
for n, x in v:
|
||||||
|
if isinstance(x, (Node, Arr)):
|
||||||
|
out.append(f"{pad}{n}:")
|
||||||
|
fmt(x, ind + 1, out, maxarr)
|
||||||
|
else:
|
||||||
|
out.append(f"{pad}{n} = {short(x, maxarr)}")
|
||||||
|
elif isinstance(v, Arr):
|
||||||
|
for i, x in enumerate(v):
|
||||||
|
out.append(f"{pad}[{i}]")
|
||||||
|
fmt(x, ind + 1, out, maxarr)
|
||||||
|
|
||||||
|
|
||||||
|
def parse(b, trace=False):
|
||||||
|
p = P(b, trace)
|
||||||
|
try:
|
||||||
|
root = p.run()
|
||||||
|
return p, root, None
|
||||||
|
except (Err, IndexError, ValueError, KeyError) as e:
|
||||||
|
return p, p.root, e
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
import prt
|
||||||
|
d, secs, order = prt.load(sys.argv[1])
|
||||||
|
names = sys.argv[2].split(',') if len(sys.argv) > 2 and sys.argv[2] != '*' else [n for n in order if secs[n][2][2] == '9']
|
||||||
|
outdir = sys.argv[3] if len(sys.argv) > 3 and sys.argv[3] != '-' else None
|
||||||
|
for nme in names:
|
||||||
|
o, l, f = secs[nme]
|
||||||
|
b = d[o:o + l]
|
||||||
|
p, root, e = parse(b, trace=len(sys.argv) > 4)
|
||||||
|
ok = e is None and not p.stack
|
||||||
|
print(f"{nme:22} len={l:7} parsed={p.p:7} fields={p.nid:5} lenient={p.lenient} {'OK' if ok else 'ERR ' + repr(e) + ' stack=' + str(len(p.stack))}")
|
||||||
|
if e is not None:
|
||||||
|
prt.hexdump(b, max(0, p.p - 64), 128)
|
||||||
|
if outdir and root:
|
||||||
|
out = [root[0] + ':']
|
||||||
|
fmt(root[1], 1, out)
|
||||||
|
open(f"{outdir}/{nme.replace(':', '_').replace('#', '_')}.txt", 'w', encoding='utf-8').write('\n'.join(out))
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import sys,os,math,collections,struct,glob,itertools
|
||||||
|
sys.path.insert(0,os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
import prt,psb
|
||||||
|
class TF(float):
|
||||||
|
pass
|
||||||
|
orig=psb.P.rdbl
|
||||||
|
def rdbl(s):
|
||||||
|
p0=s.p; t=s.b[p0]
|
||||||
|
v=orig(s)
|
||||||
|
if 0x49<=t<=0xdf and not isinstance(v,psb.Unk):
|
||||||
|
o=TF(v); o.tag=t; o.pl=bytes(s.b[p0+1:p0+7]); return o
|
||||||
|
return v
|
||||||
|
psb.P.rdbl=rdbl
|
||||||
|
OFFS=[0]+list(range(0x44,0x4d))
|
||||||
|
def mag(tf,off):
|
||||||
|
return struct.unpack('>d',(0x3f75+tf.tag-off).to_bytes(2,'big')+tf.pl)[0]
|
||||||
|
votes=collections.defaultdict(collections.Counter)
|
||||||
|
def walk(v,name=''):
|
||||||
|
if isinstance(v,psb.Node):
|
||||||
|
for n,x in v: walk(x,n)
|
||||||
|
elif isinstance(v,psb.Arr):
|
||||||
|
for x in v: walk(x,name)
|
||||||
|
elif isinstance(v,list) and v and isinstance(v[0],list) and len(v) in (3,4) and len(v[0])==3 and name in ('local_sys','orient','matrix','transf_to_local'):
|
||||||
|
rows=v[:3]
|
||||||
|
if any(isinstance(c,psb.Unk) for r in rows for c in r): return
|
||||||
|
vecs=rows+[[rows[i][j] for i in range(3)] for j in range(3)]
|
||||||
|
for r in vecs:
|
||||||
|
tf=[c for c in r if isinstance(c,TF)]
|
||||||
|
if not tf or len(tf)>2: continue
|
||||||
|
rest=sum(c*c for c in r if not isinstance(c,TF))
|
||||||
|
sols=[]
|
||||||
|
for offs in itertools.product(OFFS,repeat=len(tf)):
|
||||||
|
n=rest+sum(mag(c,o)**2 for c,o in zip(tf,offs))
|
||||||
|
if abs(n-1)<1e-9: sols.append(offs)
|
||||||
|
if len(sols)==1:
|
||||||
|
for c,o in zip(tf,sols[0]): votes[c.tag][o]+=1
|
||||||
|
for f in sys.argv[1:]:
|
||||||
|
for g in glob.glob(f):
|
||||||
|
try: d,secs,order=prt.load(g)
|
||||||
|
except Exception: continue
|
||||||
|
for nme in order:
|
||||||
|
o,l,fl=secs[nme]
|
||||||
|
if fl[2]!='9' or l<200: continue
|
||||||
|
p,root,e=psb.parse(d[o:o+l])
|
||||||
|
if root: walk(root[1])
|
||||||
|
for t in sorted(votes):
|
||||||
|
print(hex(t), {hex(o):n for o,n in votes[t].items()}, ' -> prefix', [hex((0x3f75+t-o) | (0x8000 if o else 0)) for o in votes[t]])
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
"""Decompressore Unix compress (.Z, LZW) minimale, come ncompress."""
|
||||||
|
import sys, os
|
||||||
|
|
||||||
|
|
||||||
|
def unz(data):
|
||||||
|
assert data[0] == 0x1f and data[1] == 0x9d, "non e' un flusso .Z"
|
||||||
|
flags = data[2]
|
||||||
|
maxbits = flags & 0x1f
|
||||||
|
block = bool(flags & 0x80)
|
||||||
|
out = bytearray()
|
||||||
|
n_bits = 9
|
||||||
|
maxcode = (1 << n_bits) - 1
|
||||||
|
table = {i: bytes([i]) for i in range(256)}
|
||||||
|
free = 257 if block else 256
|
||||||
|
pos = 3 * 8 # posizione in bit
|
||||||
|
total = len(data) * 8
|
||||||
|
start = pos # inizio del gruppo corrente di codici (per il riallineamento)
|
||||||
|
old = None
|
||||||
|
while pos + n_bits <= total:
|
||||||
|
byte = pos >> 3
|
||||||
|
chunk = int.from_bytes(data[byte:byte + 4], 'little')
|
||||||
|
code = (chunk >> (pos & 7)) & ((1 << n_bits) - 1)
|
||||||
|
pos += n_bits
|
||||||
|
if old is None:
|
||||||
|
old = table[code]
|
||||||
|
out += old
|
||||||
|
continue
|
||||||
|
if code == 256 and block:
|
||||||
|
table = {i: bytes([i]) for i in range(256)}
|
||||||
|
free = 256
|
||||||
|
# riallinea a multipli di 8 codici
|
||||||
|
grp = n_bits * 8
|
||||||
|
pos = start + ((pos - start + grp - 1) // grp) * grp
|
||||||
|
n_bits = 9
|
||||||
|
maxcode = (1 << n_bits) - 1
|
||||||
|
start = pos
|
||||||
|
old = None
|
||||||
|
free = 257
|
||||||
|
continue
|
||||||
|
if code in table:
|
||||||
|
entry = table[code]
|
||||||
|
elif code == free:
|
||||||
|
entry = old + old[:1]
|
||||||
|
else:
|
||||||
|
raise ValueError(f"codice LZW non valido {code} (free={free}) a bit {pos}")
|
||||||
|
out += entry
|
||||||
|
if free <= (1 << maxbits) - 1:
|
||||||
|
table[free] = old + entry[:1]
|
||||||
|
free += 1
|
||||||
|
old = entry
|
||||||
|
if free > maxcode and n_bits < maxbits:
|
||||||
|
grp = n_bits * 8
|
||||||
|
pos = start + ((pos - start + grp - 1) // grp) * grp
|
||||||
|
n_bits += 1
|
||||||
|
maxcode = (1 << n_bits) - 1
|
||||||
|
start = pos
|
||||||
|
return bytes(out)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
import prt
|
||||||
|
d, secs, order = prt.load(sys.argv[1])
|
||||||
|
outdir = sys.argv[2]
|
||||||
|
for n in order:
|
||||||
|
o, l, f = secs[n]
|
||||||
|
b = d[o:o + l]
|
||||||
|
if b[:2] == b'\x1f\x9d':
|
||||||
|
try:
|
||||||
|
u = unz(b)
|
||||||
|
except Exception as e:
|
||||||
|
print(n, 'ERR', e)
|
||||||
|
continue
|
||||||
|
print(n, 'compressa', l, '->', len(u), 'dichiarata', int(f[0], 16), u[:60])
|
||||||
|
open(os.path.join(outdir, n.replace(':', '_') + '.bin'), 'wb').write(u)
|
||||||
Reference in new issue
Block a user