Terzo caso di "[-4] Dati di input non completi" in Creo Distributed Batch, dopo i soppressi e le copie ambigue del commit precedente: falliva 9255728001, e solo lui fra i sei assiemi del batch. Il file in raw/ era identico bit per bit a quello sulla share, quindi non era il modello: mancava il contorno. Il suo sottoassieme _200 e' legato ad altri due (9255708017, 9255708018) da assembly feature e datum copiati, e monta una vite V6-12--_-_U5933_C il cui generico uni5933.prt non era in raw/. Nessuno dei sei file compariva in "members" ne' fra i piazzamenti: i due lettori esistenti non potevano vederli. Quei modelli stanno in una tabella a parte del .asm - array \xf2\xf7\x05 … \xf3 con elementi \xe3\xe2<tipo><nome>\0 - che ora si legge con EstraiCodiciRiferimentiEsterni, riusando la normalizzazione di #AF<n> e istanza<generico> gia' fatta per i membri. A differenza dei soppressi si ricorre dentro: Creo carica il modello citato con tutto il suo albero, e infatti 9255700182 arriva solo per quella via. La tabella contiene anche nomi di feature (MIRROR3, CHAMFER1) che dal record non si distinguono dai modelli. Non e' un problema: come per i soppressi si cerca ogni nome nell'indice e si ignora quello che non c'e'. Per lo stesso motivo questi codici restano fuori dai "non trovati", e la coda di scansione porta ora un flag che tiene fuori anche i rami raggiunti per riferimento esterno - la lista dei componenti mancanti continua a parlare solo della distinta. Documentato in docs/formato-asm-record.md §7.5. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
584 lines
33 KiB
Markdown
584 lines
33 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.0.2 Istanza risolta direttamente in `members`, senza passare da `ConfigName`
|
|
|
|
Non tutti i membri che sono istanze di family table hanno un record `ConfigName` associato.
|
|
Per alcune parti commerciali/normalizzate non-UNI, il campo `name` (e allo stesso modo
|
|
`family_name`) del record `members` (§3) porta già per intero la notazione
|
|
`<nome istanza><generico>` invece del solo nome generico:
|
|
|
|
```
|
|
\xe3 <id> <type> "<istanza><generico>"\0 "<istanza><generico>"\0
|
|
```
|
|
|
|
È la stessa notazione istanza→generico della §4.1 sotto, ma qui compare **dentro** il record
|
|
`members` stesso, come valore diretto del componente - non nell'elenco di riferimenti
|
|
geometrici di cui parla la §4.1 (quello sì da ignorare per la BOM). Il generico fra `<` e `>`
|
|
è il file realmente indicizzato sul disco; il nome prima di `<` è quello che la BOM
|
|
mostrerebbe.
|
|
|
|
`AsmRecordReader.LeggiMembri` lo riconosce per i membri **senza** un `ConfigName`
|
|
corrispondente il cui campo `name` rispetta per intero il pattern `<testo><testo>`: il testo
|
|
dopo l'ultima `<` (fino alla `>` finale) diventa il generico da cercare nell'indice, quello
|
|
prima diventa il nome mostrato. Senza questo split, il codice cercato nell'indice sarebbe la
|
|
stringa intera con le parentesi comprese, mai presente nell'indice - il componente
|
|
risulterebbe erroneamente "non trovato".
|
|
|
|
### 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: serve la conferma del nome proprio (`objname`), il solo `model_name` dava falsi positivi | BOM reali + albero `raw/` completo (§7.1) |
|
|
| Family table di **assiemi** (contenuto di ogni istanza) | Non risolto, nessun impatto sulla BOM (fallback già corretto) | Scan sull'intera share (§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.
|
|
|
|
> **Sparisce da `members`, non dal file.** La feature che lo posiziona resta, e con essa la
|
|
> stringa `Componente ID <n> (<codice>.PRT)` (localizzata come in
|
|
> `AssemblyComponentScanner.HaPlacement`). Per la BOM va ignorata - Creo non elenca i
|
|
> soppressi - ma **il file di quel componente serve comunque per aprire l'assieme**: se manca,
|
|
> Creo fallisce il caricamento con "dati di input non completi" (`[-4]`, cioè
|
|
> `PRO_TK_E_NOT_FOUND`). Verificato su un caso reale: un assieme che non si apriva dalla
|
|
> cartella `raw/`, e che si è aperto senza errori copiandoci dentro quel solo file.
|
|
> `AsmRecordReader.EstraiCodiciSoppressi` li restituisce a parte, apposta per questo - vedi
|
|
> §7.3.
|
|
- **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
|
|
```
|
|
|
|
Quando questo succede il file generico è già di per sé un'istanza (`model_name` porta il
|
|
suffisso `_C0n`, non è neutro) e `stamp` elenca le sorelle.
|
|
|
|
> **Il record `model_name` da solo non è un'identità.** Su file reali quel record nomina
|
|
> quasi sempre **un altro modello** - tipicamente uno dei membri del file stesso, a volte un
|
|
> codice imparentato - e in quei casi `members` è già corretto e va usato. Serve una
|
|
> conferma indipendente: il **nome proprio del modello** (`objname`, §5). `LeggiMembri`
|
|
> segnala la family table solo se `objname` esiste **e** coincide con `model_name` privato
|
|
> del suffisso `_C0n`; negli altri casi legge `members` normalmente.
|
|
>
|
|
> Il controllo più largo (senza la conferma di `objname`) era la causa di
|
|
> **falsi "componenti mancanti"**: il parser esatto rifiutava file perfettamente leggibili,
|
|
> si ripiegava sull'euristica testuale e quella estraeva codici che non esistono, riportati
|
|
> poi come componenti non trovati nell'indice. Verificato contro BOM Creo di riferimento:
|
|
> sui file dove il segnale scattava senza conferma di `objname`, `members` elenca
|
|
> **esattamente** i componenti della BOM reale (anche nello stesso ordine); uno di questi
|
|
> file è stato inoltre aperto in Creo, e l'albero del modello mostra gli stessi componenti,
|
|
> senza alcuna family table. Con la conferma di `objname` richiesta, tutti i `.asm` di una
|
|
> cartella `raw/` reale (351 file, alberi completi di più macchine) si leggono col parser
|
|
> esatto, zero ricadute sull'euristica.
|
|
|
|
Da tenere presente: il sintomo dipende da **come il file è stato salvato**, non solo dal
|
|
modello. Rileggendo una revisione più recente dello stesso assieme generico, il record di
|
|
identità può non esserci più e `members` elencare direttamente tutte le istanze, col tipo
|
|
giusto per ciascuna - cioè esattamente quello che mostra la BOM. Il blocco quindi non è una
|
|
proprietà stabile del codice: va valutato file per file, revisione per revisione.
|
|
|
|
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:
|
|
`stamp` porta **solo i nomi** delle sorelle, non il loro contenuto, quindi non è la
|
|
struttura da cui partire per risolvere questo punto.
|
|
|
|
Resta aperto solo per completezza: sui file dove il segnale scatta davvero (con conferma
|
|
del nome proprio, §7.1) il parser si ferma invece di restituire un contenuto sbagliato, e
|
|
il chiamante ripiega sull'euristica testuale, che i nomi delle istanze li trova comunque
|
|
come testo puro. Nessun comportamento dell'app dipende da questa decodifica.
|
|
- **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`).
|
|
|
|
### 7.3 Due domande diverse: cosa va in distinta, cosa serve per aprire
|
|
|
|
È la lezione che costa di più riscoprire, quindi sta qui in chiaro: **la lista dei componenti
|
|
della BOM e la lista dei file che servono a Creo per aprire l'assieme non coincidono.** La
|
|
seconda è più lunga.
|
|
|
|
| Lista | Serve per | Si ottiene con |
|
|
| --- | --- | --- |
|
|
| Componenti della distinta | BOM, verifica completezza | `LeggiMembri` / `EstraiCodiciReferenziati` (sezione `members`) |
|
|
| File necessari al caricamento | popolare `raw/` per l'export STEP | i precedenti **più** `EstraiCodiciSoppressi` e `EstraiCodiciRiferimentiEsterni` (§7.5) |
|
|
|
|
La differenza sono i **componenti soppressi** (§7.1) e i **riferimenti esterni** (§7.5): fuori
|
|
dalla BOM, ma senza il loro file Creo non apre l'assieme. Chi popola `raw/` deve quindi usare
|
|
l'unione; chi genera la BOM o verifica la completezza deve usare **solo** i membri, altrimenti
|
|
quelle voci diventano falsi "componenti mancanti".
|
|
|
|
Il sintomo, se si sbaglia, non assomiglia per niente alla causa: Creo Distributed Batch
|
|
fallisce con `Carica modello in Creo Pro | Non riuscito | [-4] Errore: Dati di input non
|
|
completi` - un messaggio che non nomina il file mancante. Il modo più rapido per trovarlo è
|
|
aprire l'assieme in Creo **dalla cartella `raw/`** (non dalla share, dove i file ci sono tutti
|
|
e quindi si apre senza errori) e cercare nell'albero la voce con l'icona rossa.
|
|
|
|
### 7.4 Lo stesso codice esiste in più cartelle della share
|
|
|
|
Sulla share **13% dei codici** (5.193 su 39.274 fra prt e asm) ha la versione più alta in più
|
|
cartelle, con contenuto diverso. Non sono revisioni: sono copie parallele, lasciate da studi
|
|
preliminari, allestimenti fiera, cartelle di lavoro datate, raccolte temporanee.
|
|
|
|
Il `.asm` **non dice quale copia usa**: verificato che non registra né il percorso, né
|
|
l'`UOBJ_ID` del figlio (che pure distingue le copie), né la revisione attesa. Cita solo il
|
|
nome del modello. Quale copia venga caricata lo decide Creo al momento dell'apertura, e la
|
|
regola pratica è: **prima la cartella del modello che la referenzia**.
|
|
|
|
Non esiste quindi un criterio globale corretto - e i numeri lo confermano: nel 79% dei
|
|
conflitti nessuna delle copie sta in una cartella "ufficiale" (`…\M_B`, `…\A`), perché il
|
|
conflitto è fra due istantanee parallele, ognuna autoconsistente con i propri componenti. Per
|
|
data, per versione e per posizione sono equivalenti: solo il contesto di chi referenzia
|
|
discrimina.
|
|
|
|
Conseguenze per il codice:
|
|
|
|
- `Form1.ScegliPerCartella` risolve ogni componente preferendo la copia nella **stessa cartella
|
|
dell'assieme che lo referenzia**, e solo in mancanza ripiega su versione più alta e data più
|
|
recente. `Form1.CartellaDiOrigine` ricostruisce quella cartella per gli assiemi di partenza,
|
|
che arrivano dalla fase precedente senza portarsela dietro.
|
|
- La vista `files_ultima_versione` (vedi [`db-cad-index.md`](db-cad-index.md)) rompe il
|
|
pareggio fra copie di pari versione in modo deterministico invece che arbitrario.
|
|
- Resta un rischio noto: quando il componente **non** sta nella cartella del suo assieme, la
|
|
scelta torna a essere una scommessa. Se un assieme non si apre da `raw/`, è la prima cosa da
|
|
verificare - `9244206098` è il caso reale che ha portato a questa sezione: due copie di pari
|
|
versione, una del 2011 in una cartella di file sciolti e una del 2026 accanto all'assieme,
|
|
e in `raw/` finiva quella del 2011.
|
|
|
|
### 7.5 Riferimenti esterni: la tabella dei modelli citati
|
|
|
|
Terzo modo in cui un `.asm` dipende da un file che non è fra i suoi membri, dopo i soppressi
|
|
(§7.1) e le copie ambigue (§7.4). Un assieme che porta **assembly feature** o **datum copiati**
|
|
da un altro modello lo cita in una tabella a parte, che non è `members` e non lascia una stringa
|
|
`Componente ID …`: i due lettori esistenti non la vedevano.
|
|
|
|
Struttura: un array aperto da `\xf2\xf7\x05` e chiuso da `\xf3`, con elementi
|
|
`\xe3\xe2<tipo><nome>\x00`. Il nome può portare il suffisso `#AF<n>` di §4.0.1 e/o la notazione
|
|
`istanza<generico>` di §4.0.2 - il file su disco è il generico:
|
|
|
|
```
|
|
f2 f7 05 e3 e2 01 "9255708017#AF2<9255708017>" 00 ... -> 9255708017
|
|
e3 e2 02 "9255700181" 00 ... -> 9255700181
|
|
f3
|
|
```
|
|
|
|
Va letta con `EstraiCodiciRiferimentiEsterni`, e - a differenza dei soppressi - **si ricorre
|
|
dentro**: Creo carica il modello citato con tutti i suoi componenti, che quindi servono anch'essi
|
|
in `raw/`. La tabella contiene anche nomi di *feature* (`MIRROR3`, `CHAMFER1`,
|
|
`M10_TAPPED_HOLE1`), indistinguibili dai modelli guardando il record: non è un problema perché
|
|
chi la usa cerca ogni nome nell'indice e ignora quelli che non ci sono - per lo stesso motivo
|
|
questi codici non devono finire fra i "non trovati".
|
|
|
|
Il caso reale: `9255728001` falliva in Distributed Batch con lo stesso `[-4] Dati di input non
|
|
completi` di §7.3 perché il suo sottoassieme `9255728001_200` referenzia per assembly feature
|
|
`9255708017` e `9255708018` (e con loro `9255700181/182/183`), e monta una vite
|
|
`V6-12--_-_U5933_C<UNI5933>` il cui generico `uni5933.prt` non era in `raw/`. Nessuno dei sei
|
|
file compariva in `members` né fra i piazzamenti.
|
|
|
|
## 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).
|