AsmRecordReader.LeggiMembri ora lancia InvalidDataException quando un assieme e' esso stesso un'istanza di family table (members descriverebbe solo una rappresentazione, non le istanze sorelle) invece di restituire un risultato sbagliato in silenzio - il chiamante ripiega sull'euristica testuale, che risolve gia' correttamente il caso. Aggiunti due controlli di coerenza analoghi (conteggio grezzo vs interpretato dei record ConfigName; occorrenze multiple di "members"), verificati senza falsi positivi su tutti i 350 .asm reali della share. Documentazione riorganizzata per distinguere cio' che e' verificato contro una BOM Creo di riferimento da cio' che e' verificato solo su scala (conteggi), e cio' che resta genuinamente non risolto. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
445 lines
24 KiB
Markdown
445 lines
24 KiB
Markdown
# Decodifica strutturale del .asm Creo: record, membri, family table
|
|
|
|
Questo documento descrive il formato **record per record** del `.asm` Creo, ricavato per
|
|
reverse engineering su un campione di assiemi reali e verificato confrontando l'output con
|
|
le BOM esportate da Creo per gli stessi file. Sostituisce, per l'estrazione dei componenti,
|
|
l'euristica testuale ("codice che compare almeno due volte") descritta in
|
|
[`formato-asm.md`](formato-asm.md): quella era una statistica sui nomi, questa legge la
|
|
struttura effettiva dei record.
|
|
|
|
Implementazione: lettura in [`PrepStep/AsmRecordReader.cs`](../PrepStep/AsmRecordReader.cs)
|
|
(condivisa con la GUI, `Form1.EstraiCodiciReferenziati` in fase "Trova componenti
|
|
referenziati"/"Verifica completezza" - con fallback su `AssemblyComponentScanner` per i file
|
|
che non rispettano il formato validato), resa in formato BOM e ricorsione sui sottoassiemi
|
|
in [`scripts/CreoBom.cs`](../scripts/CreoBom.cs) (via `#:project`).
|
|
|
|
**Stato della validazione.** Due livelli di prova, da non confondere:
|
|
|
|
1. **Byte-per-byte contro BOM Creo di riferimento** (la prova forte): 3 assiemi del
|
|
reverse engineering iniziale, più 5 casi reali aggiunti in seguito, ciascuno con la sua BOM
|
|
esportata da Creo messa a confronto riga per riga con l'output di `CreoBom.cs` —
|
|
`9284218024b.asm` (albero profondo, multi-livello), `2546220101.asm`/`2546220101_c04.asm.1`
|
|
(istanza di family table isolata), `2546220101.asm` risalvato con un componente soppresso,
|
|
`9244218080_c01.asm` (sottoassieme montato due volte), `9284208092_D110.asm` (revisione del
|
|
figlio). Ognuno di questi è citato per nome nella sezione del documento a cui si riferisce,
|
|
così resta verificabile quale affermazione poggia su quale file.
|
|
2. **Su tutti i 350 `.asm` reali di `raw/`**, solo conteggi interni e coerenza con l'euristica
|
|
testuale (`AssemblyComponentScanner`), **non** contro BOM di riferimento: usato per
|
|
calibrare le soglie dei controlli di sicurezza (§7) su scala, non per dimostrare la
|
|
correttezza dei valori estratti file per file.
|
|
|
|
Un'affermazione verificata solo al livello 2 (conteggi su scala) è più debole di una
|
|
verificata al livello 1 (BOM reale) - il testo lo distingue sempre esplicitamente.
|
|
|
|
---
|
|
|
|
## 1. Intestazione
|
|
|
|
Il file si apre con un header ASCII a righe terminate da `\` + newline:
|
|
|
|
```
|
|
#UGC:2 ASSEMBLY 2038 1760 805 1 1 15 4000 2022414 000002d0 \
|
|
#- VERS 0 0 \
|
|
#- HOST \
|
|
#- LINK \
|
|
#- DBID \
|
|
#- REVS 0, \
|
|
```
|
|
|
|
`ASSEMBLY` distingue il tipo di modello. Gli altri campi numerici non sono
|
|
stati decodificati e non servono per la BOM. Dopo l'header inizia il corpo
|
|
binario. **Il corpo non è compresso**: i nomi dei modelli sono in chiaro,
|
|
1 byte per carattere (Latin1 — `Encoding.Latin1.GetString`, stesso metodo già
|
|
usato in `AssemblyComponentScanner.cs` per leggere il file come testo senza
|
|
alterarne i byte).
|
|
|
|
## 2. Il formato dei record
|
|
|
|
Il corpo è una serializzazione a marcatori. I byte `\xe0`-`\xff` sono
|
|
marcatori di struttura, tutti gli altri sono dati. I marcatori osservati:
|
|
|
|
| byte | significato dedotto |
|
|
|------|---------------------|
|
|
| `\xe0` | inizio campo: `\xe0 <tipo> <nome campo>\0 <valore>` |
|
|
| `\xe1` | valore nullo / vuoto |
|
|
| `\xe2` | inizio struttura o unione (per i parametri: il byte dopo è il discriminante) |
|
|
| `\xe3` | inizio struttura, usato per gli elementi di un array |
|
|
| `\xf1` | apre un contenitore (visto prima di un array) |
|
|
| `\xf7` | id di schema: `\xf7 <n>` identifica il layout dei record che seguono |
|
|
| `\xf8` | array: `\xf8 <conteggio> ... <elementi>` |
|
|
| `\xfb` | lo schema è definito in linea nel primo elemento dell'array |
|
|
| `\xf4` | chiude una sezione (seguito da `#\n#<NomeSezione>\n`) |
|
|
|
|
Il byte `<tipo>` dopo `\xe0` codifica il tipo del valore. Osservati:
|
|
`\x00` struttura/nullable, `\x01` intero, `\x02` array numerico,
|
|
`\x0a` stringa terminata da `\0`.
|
|
|
|
### 2.1 Interi
|
|
|
|
Gli interi sono big-endian con la **lunghezza codificata nei bit alti del primo
|
|
byte**, per non collidere con i marcatori `\xe0`+:
|
|
|
|
| primo byte | byte totali | valore |
|
|
|------------|-------------|--------|
|
|
| `0x00`-`0x7f` | 1 | il byte stesso |
|
|
| `0x80`-`0xbf` | 2 | `(b0 & 0x3f) << 8 \| b1` |
|
|
| `0xc0`-`0xdf` | 3 | `(b0 & 0x1f) << 16 \| b1 << 8 \| b2` |
|
|
|
|
Esempi verificati: `\x2e` = 46; `\x80\x81` = 129; `\xc0\x4d\x51` = 19793.
|
|
Questa regola è stata **ricavata** dalla necessità di far quadrare il
|
|
conteggio dei membri: con la lettura a 1 byte fisso i campioni grandi si
|
|
disallineavano. Non è confermata da una specifica; regge sui campioni
|
|
disponibili al momento del reverse engineering.
|
|
|
|
In `CreoBom.cs` è `AsmRecordReader.AsInt`, applicata al testo decodificato Latin1 (ogni `char`
|
|
della stringa corrisponde 1:1 a un byte, quindi indicizzare la stringa o l'array di byte
|
|
originale dà lo stesso offset).
|
|
|
|
### 2.2 Compressione per schema
|
|
|
|
Un array `\xf8 <n> \xf7 <idSchema> \xfb` scrive il **primo** elemento con tutti
|
|
i nomi di campo per esteso; gli elementi successivi sono **posizionali**, senza
|
|
nomi. È il motivo per cui nel file si vedono i nomi dei componenti "nudi", uno
|
|
dopo l'altro, senza etichetta. Per leggere l'elemento N bisogna conoscere lo
|
|
schema definito dall'elemento 1.
|
|
|
|
---
|
|
|
|
## 3. L'elenco dei componenti: la chiave `members`
|
|
|
|
La chiave `members` è l'elenco dei **componenti diretti** dell'assieme
|
|
(livello 1), nell'ordine di montaggio.
|
|
|
|
```
|
|
\xe0 \x00 "members" \0
|
|
\xf1 \xf8 <N> \xf7 <idSchema> \xfb
|
|
\xe3 \xe0\x01 "id"\0 <int>
|
|
\xe0\x01 "type"\0 <int>
|
|
\xe0\x0a "name"\0 <stringa>\0
|
|
\xe0\x0a "family_name"\0 <stringa>\0
|
|
\xe0\x00 "tha_ref"\0 \xe1
|
|
\xe0\x02 "e1"\0 ... "e2" ... "e3" ... "origin" ...
|
|
\xe3 <id> <type> <name>\0 <family_name>\0 <resto posizionale>
|
|
\xe3 <id> <type> <name>\0 <family_name>\0 <resto posizionale>
|
|
...
|
|
```
|
|
|
|
Campi utili:
|
|
|
|
- **`id`** — identificatore del componente **dentro questo assieme**. È lo
|
|
stesso numero che Creo mostra come "Componente ID N". Serve da chiave per
|
|
collegare il membro ad altre sezioni (vedi family table, §4).
|
|
- **`type`** — `1` = sottoassieme (`.asm`), `2` = parte (`.prt`). Sui campioni
|
|
osservati compaiono solo questi due valori.
|
|
- **`name`** — il **file su disco**, senza estensione. Per le family table è
|
|
il **generico** (`UNI6592`), non l'istanza.
|
|
- **`family_name`** — sui campioni disponibili è **sempre uguale a `name`**.
|
|
Non porta quindi l'informazione sull'istanza; il nome è fuorviante.
|
|
- `e1`, `e2`, `e3`, `origin` — la matrice di posizionamento (assi + origine).
|
|
Non decodificati: non servono per la BOM, ma occupano il resto del record e
|
|
hanno lunghezza variabile.
|
|
|
|
`N` (il conteggio dichiarato) è il numero totale di **occorrenze**, non di
|
|
codici distinti: una parte montata 4 volte conta 4 e compare in 4 record con id
|
|
diversi. Il conteggio va usato come controllo di correttezza: se i record
|
|
trovati non sono esattamente `N`, la lettura si è disallineata e va segnalata
|
|
un'eccezione invece di restituire un risultato parziale silenzioso — vedi
|
|
`AsmRecordReader.LeggiMembri`, che lancia `InvalidDataException` in questo caso.
|
|
|
|
La sezione termina a `\xf4\x04#\n#MdlStatus\n`.
|
|
|
|
### 3.1 Come lo legge `AsmRecordReader.LeggiMembri`
|
|
|
|
Il parser non ricostruisce lo schema completo (servirebbe decodificare le
|
|
matrici). Si ancora invece al pattern `\xe3 <int> <int> <stringa>\0 <stringa>\0`
|
|
(`AsmRecordReader.MemberRegex`, una regex .NET sul testo Latin1 del file) e salta il
|
|
resto del record fino al `\xe3` successivo che soddisfa il pattern.
|
|
La doppia stringa terminata da `\0` rende i falsi positivi improbabili, e il
|
|
conteggio dichiarato `N` fa da rete di sicurezza.
|
|
|
|
> `ponytail:` scansione a pattern invece del parser di schema — il punto debole
|
|
> è che si appoggia al conteggio per accorgersi di un errore. Da sostituire con
|
|
> la lettura dello schema se dovessero comparire campioni che non quadrano.
|
|
|
|
---
|
|
|
|
## 4. Family table: dove sta il nome dell'istanza
|
|
|
|
Le parti normalizzate (viti, rondelle, spine, boccole) sono **istanze di family
|
|
table**. In Creo esiste un solo file, il **generico** (`UNI6592.prt`), che
|
|
contiene una tabella di varianti; ogni variante è un'**istanza**
|
|
(`R8--_-_U6592_AISI304`). La BOM elenca le istanze, il disco contiene il
|
|
generico. La sezione `members` cita **solo il generico**: senza risolvere
|
|
`ConfigName` (sotto), il livello 1 produrrebbe `UNI6592` dove la BOM dice
|
|
`R8--_-_U6592_AISI304`.
|
|
|
|
Il collegamento sta in un record di **parametro** applicato al membro:
|
|
|
|
```
|
|
\xe3 "ConfigName"\0 \xe2 \x33 [\xf7 <idSchema>] <nome istanza>\0 <id membro> \x82\x7b \xe1\xe1
|
|
```
|
|
|
|
- `\xe2\x33` — parametro di tipo stringa (`\x33` = `'3'`). Confronto: `\xe2\x32`
|
|
è un parametro numerico, es. `DENSITA`.
|
|
- `<nome istanza>` — il nome esatto che la BOM stampa.
|
|
- `<id membro>` — l'`id` del record `members` a cui il parametro si applica,
|
|
con la codifica intera di §2.1. **È questo il collegamento.**
|
|
- `\xf7 <idSchema>` compare solo sulla prima occorrenza (schema definito in
|
|
linea), poi è omesso.
|
|
- `\x82\x7b` — costante (= 635) identica su tutte le occorrenze di tutti i
|
|
campioni. Significato ignoto, presumibilmente attributi/flag del parametro.
|
|
`AsmRecordReader.ConfigNameRegex` la usa come ancora di fine record; se un campione
|
|
futuro avesse un valore diverso, il match salterebbe silenziosamente (nessun
|
|
contatore di sicurezza analogo a quello di `members` copre questo caso).
|
|
|
|
`ConfigName` compare **esattamente** per i membri che sono istanze, e per
|
|
nessun altro. L'algoritmo è quindi: leggere `members`, costruire la mappa
|
|
`id membro → nome istanza` dai record `ConfigName`
|
|
(`AsmRecordReader.LeggiMembri`, dizionario `istanze`), e sostituire il nome dove la
|
|
mappa ha una voce. Il generico resta disponibile come `Membro.Generico`.
|
|
|
|
> **Avvertenza.** `ConfigName` sta in mezzo a parametri palesemente aziendali
|
|
> (`TIPO_PARTE`, `CLASSE_RICAR`, `GR_PRODOTTO`, `TECN_RIORDINO`), quindi
|
|
> potrebbe non essere un meccanismo standard di Creo ma una convenzione
|
|
> interna. Regge sui campioni disponibili, ma su file provenienti da
|
|
> un'altra installazione va riverificato prima di fidarsene.
|
|
|
|
### 4.0.1 Eccezione: componenti da pattern/array feature (`#AF<n>`)
|
|
|
|
Un componente creato da un **pattern/array feature** (stessa geometria ripetuta N volte da
|
|
una feature di pattern, non N placement manuali distinti) ha un `ConfigName` con un nome
|
|
nella forma `<codice>#AF<n>` (`n` = indice dell'istanza nel pattern), es.
|
|
`9244250136_C01#AF0`. **Non è un'istanza di family table**: è un tag interno di
|
|
disambiguazione fra le ripetizioni del pattern. La BOM reale di Creo lo **ignora** e mostra
|
|
il codice pulito, unendo le occorrenze duplicate in una sola riga con la quantità totale.
|
|
|
|
Osservato su `9244250136.asm`: 4 dei suoi 8 membri hanno `ConfigName` con suffisso `#AF`,
|
|
due dei quali (`_C03#AF0` e `_C03#AF1`) sono lo **stesso codice** con `n` diverso — la BOM li
|
|
mostra come un'unica riga `9244250136_C03` con quantità 2. Senza normalizzare il suffisso,
|
|
ogni istanza del pattern risulterebbe un componente distinto (e con nome sbagliato).
|
|
`AsmRecordReader` rimuove `#AF\d+$` da ogni valore di `ConfigName` prima di usarlo
|
|
(`SuffissoArrayFeatureRegex`). Scoperto durante un test end-to-end su un albero reale più
|
|
ampio degli 8 campioni originali (`9284218024b.asm`, confrontato con la sua BOM Creo di
|
|
riferimento) — non era presente in nessuno degli 8 campioni usati per il reverse engineering
|
|
iniziale.
|
|
|
|
### 4.1 Una seconda pista, che NON va usata per la BOM
|
|
|
|
Altrove nel file compaiono stringhe nella forma `istanza<generico>`:
|
|
|
|
```
|
|
\xe2\x02 "R8--_-_U6592_AISI304<UNI6592>"\0
|
|
```
|
|
|
|
Documenta la stessa relazione istanza→generico in modo autonomo ed è utile come
|
|
riscontro incrociato (è lo stesso pattern `<codice><GENERICO>` già sfruttato
|
|
dall'euristica testuale in `formato-asm.md` per le family table non-UNI).
|
|
**Non è però una lista dei componenti**: su un campione produceva 26 voci
|
|
contro 17 membri reali, includeva alcuni nipoti (figli dei sottoassiemi) ma
|
|
non tutti, e ripeteva le stesse voci più volte. È la lista dei riferimenti
|
|
geometrici dei vincoli di accoppiamento, non una distinta. Usarla per la BOM
|
|
dà risultati sbagliati sia in eccesso sia in difetto.
|
|
|
|
---
|
|
|
|
## 5. Il nome proprio del modello
|
|
|
|
La BOM intesta i blocchi col nome **del modello**, non del file: il file
|
|
`9244248550b.asm.11` produce `Assieme 9244248550B contiene:`. Il nome sta nel
|
|
primo record `objname` del file:
|
|
|
|
```
|
|
\x00 "objname"\0 \xe2 \xe0\x01 "object_type"\0 <int> \xe0\x0a "name"\0 <nome>\0
|
|
```
|
|
|
|
La **prima** occorrenza è il modello stesso; quelle successive sono altri
|
|
oggetti referenziati (disegni, modelli citati). `AsmRecordReader.NomeProprio` cerca
|
|
solo la prima occorrenza (`Regex.Match`, non `Matches`), verificato su tutti i
|
|
campioni disponibili durante il reverse engineering.
|
|
|
|
---
|
|
|
|
## 6. Generare la BOM: ricorsione e formato di output
|
|
|
|
`members` dà un solo livello. La BOM di Creo è ricorsiva: apre anche i `.asm`
|
|
dei sottoassiemi. Implementato in `CreoAsm.GeneraBom`/`Render` in
|
|
[`scripts/CreoBom.cs`](../scripts/CreoBom.cs).
|
|
|
|
### 6.1 Struttura del file BOM
|
|
|
|
- Codifica **UTF-8 con BOM** (`EF BB BF`), fine riga **CRLF**.
|
|
- Due righe vuote in testa, poi il primo blocco.
|
|
- **Tre** righe vuote fra un blocco e il successivo, **due** prima del
|
|
riepilogo. Il file termina con un solo CRLF.
|
|
- Riga voce: `{qta:6} {tipo:>12} {nome}` — cioè quantità allineata a destra su
|
|
6 caratteri, spazio, tipo allineato a destra su 12, due spazi, nome.
|
|
- Intestazioni: `Assieme <nome> contiene:` per la radice,
|
|
`Sottoassieme <nome> contiene:` per gli altri.
|
|
|
|
### 6.2 Ordine dei blocchi
|
|
|
|
Visita **depth-first pre-order**: si emette il blocco di un sottoassieme e
|
|
subito dopo, ricorsivamente, quelli dei suoi sottoassiemi, prima di passare al
|
|
fratello successivo. Ogni modello compare **una volta sola** anche se montato
|
|
più volte (deduplica per nome, `CreoAsm.Blocchi`).
|
|
|
|
> **Verificato.** `9244218080_C01.asm` monta `2605410010_C01` due volte
|
|
> (`scripts/9244218080_c01.asm.1` + `scripts/9244218080_c01.bom.2` di riferimento): la BOM
|
|
> Creo mostra un solo blocco `Sottoassieme 2605410010_C01 contiene:` con quantità 2 in testa,
|
|
> non due blocchi separati, e `CreoBom.cs` riproduce l'output esatto senza modifiche. Confermata
|
|
> anche la moltiplicazione delle quantità nel riepilogo (§6.3): le parti di `2605410010_C01`
|
|
> compaiono raddoppiate (1→2, 1→2, 16→32).
|
|
|
|
### 6.3 Riepilogo finale
|
|
|
|
`Summary of items for <radice>:` elenca **solo le parti**, con la quantità
|
|
totale moltiplicata lungo l'albero (una parte in 4 esemplari dentro un
|
|
sottoassieme montato 2 volte conta 8), nell'ordine di **prima comparsa** nella
|
|
visita depth-first (`CreoAsm.Sommario`). Una parte che compare a più livelli
|
|
occupa la posizione della sua prima comparsa e somma le quantità.
|
|
|
|
### 6.4 Risoluzione dei file dei sottoassiemi
|
|
|
|
È il punto che il formato **non** risolve: il `.asm` registra il nome del
|
|
modello, non il percorso. Servono quindi cartelle di ricerca fornite
|
|
dall'esterno (`-p`/`--path`). `CreoAsm.CreaFinder` indicizza `<nome>.asm.<rev>`
|
|
senza distinzione di maiuscole e, a parità di nome, prende la **revisione più
|
|
alta** presente.
|
|
|
|
> **Verificato: è la scelta giusta.** Il `.asm` padre non registra la revisione attesa del
|
|
> figlio (cercata e non trovata, vedi §7), ma non serve: confermato che **Creo stesso apre
|
|
> sempre la revisione più alta disponibile** di un componente, indipendentemente da quale
|
|
> fosse presente quando l'assieme padre fu salvato l'ultima volta. Caso reale controllato:
|
|
> `9284208092_D110.asm` (salvato 2018-08-01) referenzia `9284202481.prt`, che sulla share ha
|
|
> revisioni sia del 2014 (v1-v3, presenti all'epoca) sia del 2024 (v4-v6, arrivate anni dopo) -
|
|
> Creo carica v6, non v3. `CreaFinder` che prende sempre la revisione più alta in cartella
|
|
> replica esattamente questo comportamento, non è un'assunzione azzardata. Vedi `formato-
|
|
> asm.md` §"Risoluzione ricorsiva dell'albero" per le trappole storiche su questo punto (con
|
|
> l'euristica testuale - restano valide, sono su un problema diverso: trovare la cartella
|
|
> giusta, non quale revisione preferire).
|
|
|
|
Se un sottoassieme non si trova, il blocco viene emesso con
|
|
`*** NON RISOLTO: file .asm non trovato ***` e le sue parti restano fuori dal
|
|
riepilogo. Il risultato è dichiaratamente incompleto invece che silenziosamente
|
|
sbagliato.
|
|
|
|
---
|
|
|
|
## 7. Stato di ogni punto aperto
|
|
|
|
Tabella di riferimento: cosa è provato, come, e cosa resta genuinamente ignoto. Il dettaglio
|
|
di ciascun punto segue sotto, in due gruppi.
|
|
|
|
| Punto | Stato | Prova |
|
|
|---|---|---|
|
|
| Deduplica blocchi + moltiplicazione quantità (§6.2/§6.3) | Risolto | BOM reale (§7.1) |
|
|
| Revisione del figlio che il padre si aspetta (§6.4) | Risolto | BOM reale (§7.1) |
|
|
| Stato soppresso/escluso in `members` | Risolto | BOM reale (§7.1) |
|
|
| Family table di **assiemi** (rilevamento) | Risolto | BOM reale + scala 350 file (§7.1) |
|
|
| Family table di **assiemi** (contenuto di ogni istanza) | Non risolto | — (§7.2) |
|
|
| Costante `\x82\x7b` in `ConfigName` | Significato ignoto, rischio coperto | Scala 350 file (§7.2) |
|
|
| Occorrenze multiple di `members` (simplified rep) | Mai osservato, rischio coperto | Scala 350 file (§7.2) |
|
|
| Matrice di posizionamento (`e1`/`e2`/`e3`/`origin`) | Non letto, nessun impatto sulla BOM | — (§7.2) |
|
|
| Campi numerici dell'header dopo `ASSEMBLY` | Non letto, nessun impatto sulla BOM | — (§7.2) |
|
|
| Divergenza `family_name` ≠ `name` | Mai osservata, non letta | — (§7.2) |
|
|
|
|
"Rischio coperto" vuol dire: il codice non sa cosa significhi il byte, ma verifica un
|
|
invariante che lo renderebbe visibile (eccezione) se mai smettesse di valere - non è una prova
|
|
che il byte non conti mai, è la garanzia che se contasse il programma lo segnalerebbe invece
|
|
di sbagliare in silenzio.
|
|
|
|
### 7.1 Risolti con una BOM Creo di riferimento
|
|
|
|
- **Deduplica blocchi e moltiplicazione quantità.** `9244218080_C01.asm` monta
|
|
`2605410010_C01` due volte (`scripts/9244218080_c01.asm.1` +
|
|
`scripts/9244218080_c01.bom.2`): la BOM Creo mostra un solo blocco
|
|
`Sottoassieme 2605410010_C01 contiene:` con quantità 2 in testa (non due blocchi separati),
|
|
e le sue parti compaiono raddoppiate nel riepilogo (1→2, 1→2, 16→32). `CreoBom.cs` riproduce
|
|
l'output esatto senza modifiche: la deduplica per nome (`CreoAsm.Blocchi`) e la
|
|
moltiplicazione lungo l'albero (`CreoAsm.Sommario`) sono il comportamento reale di Creo, non
|
|
solo un'assunzione.
|
|
- **Revisione del figlio.** `9284208092_D110.asm` (salvato 2018-08-01) referenzia
|
|
`9284202481.prt`, che sulla share ha revisioni sia del 2014 (v1-v3, presenti quando il padre
|
|
fu salvato) sia del 2024 (v4-v6, arrivate anni dopo). Confermato direttamente in Creo: apre
|
|
v6, non v3 - **sempre la revisione più alta disponibile**, indipendentemente da quale fosse
|
|
presente all'ultimo salvataggio del padre. `CreoAsm.CreaFinder`, che prende sempre la
|
|
revisione più alta trovata in cartella, replica esattamente questo comportamento.
|
|
- **Stato soppresso/escluso.** Stesso assieme `2546220101.asm` risalvato da Creo con
|
|
`2546220101_C02` soppresso (`scripts/2546220101.asm.1` + `scripts/2546220101.bom.1`, con le
|
|
parti/sottoassiemi correlati). Un componente soppresso **non produce alcun record** in
|
|
`members`: sparisce e basta, il conteggio dichiarato scende di conseguenza (`\xf8 03` invece
|
|
di `\xf8 04`). Nessun marcatore di stato da gestire: `CreoBom.cs` sul file risalvato
|
|
riproduce esattamente la BOM di riferimento senza modifiche al parser.
|
|
- **Family table di assiemi — rilevamento.** Non solo le parti normalizzate (§4) possono
|
|
essere family table: un intero **assieme** può esserlo. Osservato su `2546220101.asm`
|
|
(dentro l'albero di `9284218024b.asm`): il file generico ha una propria sezione `members`
|
|
che elenca il contenuto di UNA rappresentazione (3 cuscinetti + 1 sotto-assieme, dati
|
|
arbitrari), ma la BOM Creo per lo stesso file mostra 4 **istanze diverse**
|
|
(`2546220101_C01`.."_C04", tre "Parte" e una "Sottoassieme" - tipi diversi fra loro),
|
|
ciascuna con un contenuto diverso. Il nome istanza qui **non** passa da `ConfigName`
|
|
(verificato: nessuna occorrenza letterale nel file), ma da una coppia di record distinta
|
|
subito dopo l'header del modello:
|
|
|
|
```
|
|
\xe0\x01model_t_type\0<int>\xe0\x0amodel_name\0<codice>_C0n\0\xe0\x0ageneric_name\0\0
|
|
...stamp\0\xe2\xf2\xf7<idSchema>
|
|
(\xe3<type>\x02<codice>_C0n\0\0\xe2)* -- un'occorrenza per ogni istanza sorella
|
|
\xf4
|
|
```
|
|
|
|
Il file generico è già di per sé un'istanza (`model_name` porta il suffisso `_C0n`, non è
|
|
neutro), e `stamp` elenca le sorelle. **Segnale affidabile**: la combinazione
|
|
`model_t_type`+`model_name` (con suffisso `_C0n`)+`generic_name` vuoto
|
|
(`AsmRecordReader.OwnModelIdentityRegex` + `SuffissoIstanzaFamilyTableRegex`) - **non** la
|
|
sola presenza di `generic_name`, che su altri 119 file del corpus da 350 compare in un
|
|
contesto diverso e non correlato (`model_name` lì è un codice slegato dal nome del file, e
|
|
`members` in quei casi è già corretto). Verificato: solo 25/350 file rispettano il segnale
|
|
affidabile, e su quei 25 `AsmRecordReader.LeggiMembri` lancia `InvalidDataException` invece
|
|
di restituire il contenuto sbagliato della rappresentazione di default.
|
|
|
|
Verificato anche che il fallback su `AssemblyComponentScanner` **risolve già correttamente**
|
|
il caso vero: i nomi delle istanze compaiono come testo puro nel campo `stamp`, e
|
|
l'euristica ("codice che compare almeno due volte") li trova senza bisogno di decodificare
|
|
`stamp`. E che una singola istanza, se esportata da Creo come file a sé (Family Table →
|
|
istanza → "Salva una copia"), è un `.asm`/`.prt` normale senza traccia di family table al suo
|
|
interno - letto correttamente dal parser esatto (`2546220101_c04.asm.1`, verificato contro
|
|
`2546220101.bom.1`).
|
|
|
|
### 7.2 Non risolti
|
|
|
|
- **Family table di assiemi — contenuto di ogni istanza.** Come recuperare il contenuto
|
|
specifico di un'istanza **senza** che esista già un file separato per essa sul disco (il
|
|
caso comune, se nessuno l'ha mai esportata a parte con "Salva una copia"): il meccanismo
|
|
dietro `stamp` non è stato decodificato oltre a nome+tipo di ciascuna istanza sorella.
|
|
Aperto anche cosa sia davvero il record `generic_name` sui 119 file dove non è questo
|
|
sintomo.
|
|
- **Costante `\x82\x7b`** nei record `ConfigName`: significato ignoto. Rischio coperto -
|
|
`LeggiMembri` conta le occorrenze grezze di `ConfigName` e le confronta col numero di match
|
|
del regex; se un campione avesse un valore diverso, il regex non farebbe più match (prima:
|
|
in silenzio) e ora lancia eccezione invece di restituire il nome generico al posto
|
|
dell'istanza senza dirlo. Verificato su tutti i 350 `.asm` di `raw/`: 215 usano davvero
|
|
`ConfigName`, 0 mismatch.
|
|
- **Occorrenze multiple di `members`** (rappresentazioni semplificate, mai osservate). Rischio
|
|
coperto - `LeggiMembri` lancia eccezione se ne trova più di una invece di prendere
|
|
silenziosamente la prima senza sapere se sia quella giusta. Verificato su tutti i 350 `.asm`
|
|
di `raw/`: sempre esattamente una occorrenza.
|
|
- **La matrice di posizionamento** (`e1`, `e2`, `e3`, `origin`): formato numerico non
|
|
decodificato, nessun impatto sulla BOM (non letta da nessuna parte del codice).
|
|
- **I campi numerici dell'header** dopo `ASSEMBLY`: non letti, nessun impatto sulla BOM.
|
|
- **`family_name`**: sempre uguale a `name` su tutti i campioni disponibili; non si sa in
|
|
quale situazione divergerebbe, né se il codice dovrebbe fare qualcosa se succedesse (oggi
|
|
non la usa in nessun modo, legge solo `name`).
|
|
|
|
## 8. Uso
|
|
|
|
```powershell
|
|
dotnet run scripts/CreoBom.cs -- raw/9244228558.asm.20 -p M:\modelli -o out\9244228558.bom.1
|
|
dotnet run scripts/CreoBom.cs -- raw/9244228558.asm.20 -p M:\modelli # stampa a video
|
|
dotnet run scripts/Analyze.cs -- raw/9244228558.asm.20 "ConfigName" # dump dei byte per investigare a mano
|
|
```
|
|
|
|
Senza `-p`/`--path`, la ricerca dei sottoassiemi usa la cartella del file di partenza.
|
|
`-p` è ripetibile per più cartelle. `-o` scrive il file col BOM UTF-8 e CRLF descritti
|
|
al §6.1; senza `-o` il testo va su stdout (con `\n` al posto di `\r\n`, per leggibilità
|
|
in console).
|