Files
prep-step/docs/formato-asm-record.md
T
davideandClaude Sonnet 5 35009b5cd3 Aggiungi CreoBom.cs: parser esatto del formato .asm via record binari
Porting C# del reverse engineering della struttura a record del .asm Creo
(sezione "members" + parametro "ConfigName" per le family table): legge
esattamente i componenti diretti e genera la stessa BOM esportata da Creo,
senza le euristiche testuali (conteggio occorrenze) di AssemblyComponentScanner.
Resta uno script di analisi indipendente, non integrato nella GUI.

Aggiorna anche formato-asm.md (non e' piu' vero che "non esiste un parser
strutturale") e architettura.md (script mancante nell'elenco).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 15:41:14 +02:00

15 KiB

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: quella era una statistica sui nomi, questa legge la struttura effettiva dei record.

Implementazione: scripts/CreoBom.cs.

Stato della validazione. Durante il reverse engineering, il livello 1 estratto da 3 assiemi con BOM Creo di riferimento coincideva esattamente: stesse voci, stesso nome istanza, stesse quantità, stesso ordine. Altri 5 .asm, mai usati per ricavare le regole, parsavano con il conteggio dichiarato uguale a quello trovato. Tutto ciò che segue oltre questo perimetro (§7) è segnalato esplicitamente come ipotesi non dimostrata.


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 è CreoAsm.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 CreoAsm.LeggiMembri, che lancia InvalidDataException in questo caso.

La sezione termina a \xf4\x04#\n#MdlStatus\n.

3.1 Come lo legge CreoAsm.LeggiMembri

Il parser non ricostruisce lo schema completo (servirebbe decodificare le matrici). Si ancora invece al pattern \xe3 <int> <int> <stringa>\0 <stringa>\0 (CreoAsm.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. CreoAsm.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 (CreoAsm.LeggiMembri, dizionario istanze), e sostituire il nome dove la mappa ha una voce. Il generico resta disponibile come Membro.File.

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.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). CreoAsm.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.

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).

La deduplica è un'assunzione: nessuno dei campioni disponibili durante il reverse engineering montava lo stesso sottoassieme due volte, quindi non è dimostrata su un caso reale.

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.

Scelta discutibile: la revisione più alta in cartella non è necessariamente quella con cui l'assieme padre è stato salvato. Il .asm padre non sembra registrare la revisione attesa del figlio, o non è stata trovata. Vedi formato-asm.md §"Risoluzione ricorsiva dell'albero" per le trappole già incontrate storicamente su questo punto (con l'euristica testuale, ma il problema della revisione attesa è indipendente dal metodo di estrazione).

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. Cosa resta sconosciuto

Elencato esplicitamente per non far passare per dimostrato ciò che non lo è:

  • Stato soppresso / escluso. Nessuna evidenza cercata o trovata. Non si sa se un componente soppresso compaia in members, né con quale marcatore. Sui campioni disponibili il totale coincideva con la BOM, il che suggerisce che non ce ne fossero, non che siano gestiti.
  • La matrice di posizionamento (e1, e2, e3, origin): formato numerico non decodificato.
  • family_name: sempre uguale a name sui campioni; non si sa in quale situazione divergerebbe.
  • La costante \x82\x7b nei record ConfigName.
  • I campi numerici dell'header dopo ASSEMBLY.
  • Occorrenze multiple di members. CreoAsm.LeggiMembri prende la prima. Sui campioni disponibili funziona, ma non è stato verificato se le rappresentazioni semplificate (simplified rep) generino sezioni members aggiuntive.
  • La deduplica dei blocchi e la revisione attesa dei figli (§6.2, §6.4).
  • Quale revisione del figlio il padre si aspetta: non trovata nel .asm.

8. Uso

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).