# Come leggere un .asm Creo per dedurne i componenti Il file `.asm` di Pro/ENGINEER-Creo è un formato binario proprietario, non documentato pubblicamente. Questo documento descrive l'euristica testuale di `AssemblyComponentScanner.cs`: conta le occorrenze di stringhe candidate nel testo binario, validata a mano confrontando l'output con l'albero reale mostrato da Creo. Riassume cosa si è osservato e i suoi limiti, in particolare i falsi positivi che un conteggio testuale non può escludere con certezza (riferimenti fantasma, componenti soppressi con placement residuo — vedi sotto). Un reverse engineering successivo ha decodificato la **struttura a record** del file (non solo pattern testuali): legge esattamente la sezione `members` di un assieme, senza contare occorrenze né rischiare i falsi positivi descritti in questo documento. Implementato in `PrepStep/AsmRecordReader.cs`, documentato in [`formato-asm-record.md`](formato-asm-record.md). È il metodo **preferito** in GUI (fase 4 "Trova componenti referenziati", `Form1.EstraiCodiciReferenziati`) e nello script `CreoBom.cs`: `AssemblyComponentScanner` (l'euristica di questo documento) resta come **fallback**, usato solo quando `AsmRecordReader` lancia `InvalidDataException` (file che non rispetta il formato validato dal reverse engineering — copertura limitata agli 8 campioni disponibili al momento), e come fonte di verità per gli script `ExtractCreoStrings.cs`/`BomViaDb.cs`, che risolvono l'albero contro `cad_index.db` invece che tramite un percorso di ricerca locale. ## Struttura osservata Il file contiene, frammentate tra dati binari, molte coppie testuali del tipo ``` \x00\x00 ``` in encoding a 1 byte per carattere (Latin1/ASCII), es.: ``` 6d 6f 64 65 6c 5f 6e 61 6d 65 00 39 30 34 34 32 30 37 30 36 31 5f 43 30 31 23 41 46 30 00 m o d e l _ n a m e \0 9 0 4 4 2 0 7 0 6 1 _ C 0 1 # A F 0 \0 ``` I codici dei componenti (propri e sotto-componenti) compaiono come stringhe ASCII leggibili in diversi campi ricorrenti: - `model_name`, `generic_name`, `inst_name`, `gen_name`, `feat_name`, `family_name`, `name` — metadati associati a un modello referenziato. - `Componente ID N (.PRT|.ASM)` — riga di placement: `N` è un ID progressivo assegnato da Creo al componente posizionato nell'assieme. - `#AF0` — riferimento con suffisso di versione/configurazione. Un codice reale compare **più volte** nel file (una per feature/placement diverso); rumore binario che assomiglia casualmente a un codice compare quasi sempre una sola volta. Questa è la base del filtro in [`AssemblyComponentScanner.cs`](../PrepStep/AssemblyComponentScanner.cs) e in [`scripts/ExtractCreoStrings.cs`](../scripts/ExtractCreoStrings.cs): ``` regex: \b\d{8,12}(?:_[A-Za-z]\d{2,4}|[A-Za-z]{0,2})\b occorrenze: >= 2 ``` La regex effettiva in `AssemblyComponentScanner.cs` (usata anche da `scripts/ExtractCreoStrings.cs` via `#:project`) è una versione più raffinata, con in più il filtro sui falsi positivi descritto sotto: ``` regex: \b\d{8,12}[A-Za-z]{0,2}(?:_[A-Za-z]?\d{2,4})?\b ``` (lettera di revisione e suffisso "_..." possono comparire anche insieme, es. `9284200366A_C01`; il suffisso dopo "_" può essere puramente numerico, es. `2702220182_8188`, non solo lettera+cifre come `_C01`/`_D100`.) ## Falsi positivi noti **ID di viste salvate.** Il pattern `ModelView#` (es. `ModelView#1073741828`) genera candidati a 10 cifre indistinguibili da un codice reale per forma, ma sono ID interni di viste orientate salvate, non componenti. Si escludono con un lookbehind negativo sulla regex: `(?...)`/`Component id N (...)` (la stringa cambia con la localizzazione Creo con cui il file è stato salvato - osservate sia italiana sia inglese); - il codice ripetuto due volte di seguito separato da un singolo byte NUL, `\0\0` (osservato per componenti di family table non-UNI, es. cuscinetti `CUSCI_...` - vedi sotto; il placement testuale "Componente ID" non li copre, ma questa coppia doppia sì); - il campo `family_name\0\0` (con `name` e `family_name` uguali, entrambi pari al codice). Osservato una sola volta per file, su un sotto-assieme referenziato come family table generica invece che con un placement testuale esplicito - caso `9284202452` dentro `9284208188_D400.asm`: il sotto-assieme (che contiene a sua volta `9284202452_C01/_C02/_C03`) non ha nessun `Componente ID (...)` né coppia NUL, solo questo campo `family_name`. Verificato su 5 file diversi (`9284208188_D400/D600/D610/D620/D630.asm`): l'unica occorrenza di `family_name\0\0` in ciascuno corrisponde sempre a un componente reale confermato dalla BOM Creo, mai al riferimento fantasma verso l'alto; - il codice seguito immediatamente da un bracket, `` (senza NUL né spazio in mezzo) - l'istanza di una family table non-UNI (es. `2510060303`, un cuscinetto, confermato aprendo l'assieme in Creo: descrizione "CUSC.OBL.SFERE 2COR 17X40"). Il codice dell'istanza non è mai indicizzato a sé sulla share (solo il generico tra `<` e `>` lo è - stesso meccanismo di UNI, vedi sotto), quindi anche riconosciuto come placement reale finisce comunque negli "scartati" alla ricerca nel DB - ma in modo tracciabile, invece di sparire in silenzio prima ancora di essere cercato. Un riferimento incidentale al nome di un altro assieme, anche se ripetuto più volte, non compare mai in nessuna delle quattro forme. `scripts/ExtractCreoStrings.cs` e `AssemblyComponentScanner.cs` applicano questa regola ai codici "nudi" (senza suffisso `_...` e senza bracket family table - vedi sotto perché limitata a questi): un codice ripetuto ma mai trovato dentro uno dei quattro segnali viene scartato. Questo NON copre i casi fantasma/soppressi sotto, che hanno un placement sintatticamente identico a uno vero e restano quindi indistinguibili a livello testuale. **Il codice radice del proprio assieme (assieme generale, senza suffisso `_D.../_C...`).** Caso particolare e sistematico del precedente, non solo incidentale: un assieme suddiviso in sotto-assiemi con suffisso (`9284208188_D600.asm`, `_D610.asm`, ...) referenzia **sempre** al suo interno anche il codice radice dell'assieme generale (`9284208188`, senza suffisso) - il "padre" a cui tutti i `_D...` appartengono nella struttura Creo - con un vero placement sintatticamente identico a un componente reale. Non è un componente di quel sotto-assieme, è la risalita verso l'assieme che lo contiene. Osservato su `9284208188_D600.asm`: `Componente ID N (9284208188...)`. Se seguito in una risoluzione ricorsiva, riporta dentro l'intero albero del prodotto invece del solo sotto-assieme richiesto: 598 componenti raccolti invece dei ~65 reali. A differenza del caso generico sopra, qui il codice "padre" _è_ prevedibile a priori (è la parte del proprio codice prima del primo `_`), quindi va escluso esplicitamente seminando l'insieme dei codici già visitati anche con la radice del codice di partenza, non solo con il codice stesso — non basta il controllo generico sul placement, perché il placement qui è reale. Vedi `AssemblyComponentScanner.CodiceRadice`. **Riferimenti fantasma a codici superati/rinominati.** Caso osservato: `9244207061.asm` contiene un riferimento a `9044207061` (stesso numero, prima di una rinumerazione) con la stessa identica struttura sintattica di un componente vero — `Componente ID 46 (9044207061.ASM)`, più i campi `model_name`/`generic_name` corrispondenti. Il codice `9044207061` esiste davvero nell'indice CAD (il file vecchio non è stato eliminato dalla share), quindi supera anche una validazione incrociata contro il DB. Non è però un componente realmente presente nell'albero mostrato da Creo per quell'assieme (confermato aprendo il file in Creo). Questo caso **non è distinguibile a livello testuale**: la sintassi è identica a quella di un placement reale. Nessuna euristica sulla stringa riesce a discriminarlo in modo affidabile senza rischiare di scartare componenti veri con pattern simili altrove. Se ricompare, l'unica verifica sicura resta l'apertura manuale in Creo del sotto-assieme sospetto. **Componenti soppressi/rimossi con placement residuo.** Stessa natura del caso precedente: capita che un codice compaia con un vero `Componente ID N (.PRT)`/`Component id N (.PRT)` - con un ID numerico nello stesso intervallo di quelli dei componenti realmente visibili nell'albero Creo, quindi non distinguibile per posizione o formato - ma il codice non compare nell'albero/BOM che Creo mostra per quell'assieme. Osservato su più assiemi in questa sessione (`9044207061` dentro `9244207061.asm`, e diversi codici dentro `9284208188_D200.asm`). Ipotesi più probabile: feature soppresse, cancellate o superate in una revisione successiva, il cui placement resta comunque nella storia binaria del file (Creo non riscrive da zero il file a ogni salvataggio, accumula stato). Nella pratica, quando compaiono, sono un numero ridotto di codici extra rispetto al reale (falsi positivi, non falsi negativi): la BOM generata resta un sovrainsieme sicuro dei componenti veri, mai un sottoinsieme. Nessuna euristica testuale può escluderli con certezza; se serve un elenco esatto l'unica verifica è aprire l'assieme in Creo e controllare a mano quali ID sono effettivamente presenti/attivi. **Placement senza il dettaglio del suffisso/bracket.** Il testo dentro `Componente ID N (...)` a volte riporta solo il nome generico/istanza, senza il suffisso di variante dimensionale o il bracket family-table, che compaiono invece solo altrove nei metadati dello stesso file (non vicino al placement). Osservato in due forme sullo stesso assieme (`9284208188_D200.asm`): `Component id 82 (R8--_-_U6592.PRT)` (nessun `` nel placement, pure presente nel file) e `Component id 150 (2702220182.PRT)` (nessun `_8188` nel placement, mentre la variante dimensionale reale `2702220182_8188` compare solo come stringa ripetuta altrove). Per questo il requisito del placement esplicito (caso sopra) si applica solo ai codici "nudi" (senza suffisso `_...` e senza bracket UNI): per le varianti con suffisso o family table il placement può mancare del dettaglio, quindi resta necessaria la sola euristica delle occorrenze ripetute - meno precisa, ma l'unica applicabile senza rischiare falsi negativi su casi come questi. ## Parti commerciali con nome alfabetico (validazione via DB) Oltre il 90% dei codici indicizzati sono numerici Pro-E (regex sopra), ma una minoranza sono parti commerciali scaricate come STEP da cataloghi online (anelli, boccole, guarnizioni, manopole...) che in Creo hanno un nome **alfabetico**, assegnato dal file STEP di origine, non un codice del gestionale — es. `NOME_PARTE-1-SOLID1`, senza nessuna cifra riconoscibile. Non esiste un pattern comune da ipotizzare a priori per questi nomi: variano da fornitore a fornitore. Per questa famiglia l'estrazione si inverte: invece di restringere la regex a una forma nota (come per i codici numerici), si tokenizza il binario in modo generico — ``` regex: [A-Za-z0-9_-]{3,} ``` — e si lascia decidere al DB (`cad_index.db`, già caricato in memoria una sola volta dal chiamante, non per-file) se il token è un codice reale. Senza questo filtro il rumore binario sarebbe ingestibile: su un assieme con poche migliaia di stringhe, la tokenizzazione generica produce tipicamente qualche migliaio di candidati, di cui solo una manciata sopravvive al controllo DB. L'essere nel DB da solo non basta, per lo stesso motivo dei codici numerici "nudi": codici corti o generici (es. una keyword interna di Creo come `start_part`, o un codice indicizzato per errore/caso limite) possono comparire per puro rumore. Si applica quindi la stessa regola di validazione della famiglia numerica: - un token con un suffisso di revisione/variante (`_[A-Za-z]?\d{2,4}` in coda, es. `_C01`, `_D100`) o un prefisso di family table nota (`UNI`, `CUSCI_`) è considerato "forma nota": basta che occorra almeno 2 volte nel file; - un token "nudo" (nessuno dei due segnali) richiede invece un vero placement testuale, come per i codici numerici nudi (stesse quattro forme viste sopra). Una parte commerciale referenziata ma **non ancora indicizzata** non è distinguibile dal rumore con questo metodo: resta invisibile, come lo era prima che questa famiglia venisse gestita. La famiglia numerica invece resta rilevabile anche quando manca dall'indice (rinominata/spostata), perché si riconosce dalla forma senza bisogno del DB — è per questo che le due famiglie restano due passaggi distinti invece di un unico tokenizer generico sempre validato contro il DB: unificarle farebbe perdere la segnalazione dei codici numerici mancanti (vedi "Riferimenti fantasma" sopra). `AssemblyComponentScanner.EstraiCodiciReferenziati` accetta quindi un parametro opzionale `codiciNoti` (l'insieme di tutti i codici prt/asm noti all'indice) usato solo per questa seconda famiglia; se omesso, si estraggono solo i codici numerici. `scripts/BomViaDb.cs` è il prototipo standalone usato per validare l'approccio prima di portarlo nella GUI. ## Componenti normalizzati (family table UNI, CUSCI, ...) I componenti di minuteria standard (viti, dadi, rosette UNI) e i cuscinetti (`CUSCI_...`) sono definiti in Creo come **family table**: un file generico (es. `uni6592.prt`, `cusci_acbb7.prt`) con tante righe/istanze quante sono le varianti dimensionali. Nel binario dell'assieme compaiono così: ``` Componente ID 28755 (R6--_-_U6592.PRT) 2510060303 ``` `R6--_-_U6592` e `2510060303` sono il nome/codice della singola istanza (riga della family table), **non un file separato su disco** — sulla share esiste solo il generico (`uni6592.prt.`, `cusci_acbb7.prt.`, verificato in `Unificati/`). Cercare l'istanza intera nell'indice fallisce sempre; va estratto solo il contenuto tra `<` e `>` (`UNI6592`, `CUSCI_ACBB7`), che coincide col nome del file generico realmente indicizzato: ``` regex aggiuntiva: (?<=<)(?:UNI\d+|CUSCI_[A-Za-z0-9]+)(?=>) ``` Implementata sia in [`scripts/ExtractCreoStrings.cs`](../scripts/ExtractCreoStrings.cs) sia in [`AssemblyComponentScanner.cs`](../PrepStep/AssemblyComponentScanner.cs) (fase "Trova componenti referenziati" dell'app). Il prefisso family table non è garantito essere solo `UNI`/`CUSCI`: se ricompaiono famiglie diverse (altri componenti normalizzati), vanno aggiunte allo stesso modo, verificando prima con `scripts/Analyze.cs` che il contenuto tra `<` e `>` corrisponda a un codice realmente indicizzato in `Unificati/`. ## Risoluzione ricorsiva dell'albero Un `.asm` referenzia solo i propri sotto-componenti diretti, non l'intero albero annidato. Per ricostruire la distinta base completa (non solo il primo livello) serve una BFS: 1. Estrarre i codici candidati dal file corrente (euristica sopra). 2. Per ogni codice, cercarlo nell'indice `cad_index.db` (`files_ultima_versione`) per sapere se è un `.prt` (foglia) o un `.asm` (da accodare alla coda per essere scansionato a sua volta). 3. Un codice non trovato nell'indice va scartato/segnalato: o è rumore binario, o è un componente non ancora indicizzato. Stessa logica di estrazione candidati ed euristica del codice radice (sopra), duplicata in due punti del codice per scopi diversi: - [`AssemblyComponentScanner.EstraiCodiciReferenziati`](../PrepStep/AssemblyComponentScanner.cs) + `CodiceRadice` — estrazione dei candidati da un file, usata come fallback dalla BFS in [`Form1.RisolviAlberoComponenti`](../PrepStep/Form1.cs) (fase 4 "Trova componenti referenziati"). La BFS semina l'insieme dei codici visitati con codice + radice di ogni file di partenza e carica una volta sola i codici noti all'indice (`Form1.CaricaCodiciNoti`, per validare i token generici delle parti commerciali) e tutte le copie di ogni codice (`Form1.CaricaCandidati`, per scegliere il percorso da copiare), senza una query SQL per candidato. - [`scripts/ExtractCreoStrings.cs`](../scripts/ExtractCreoStrings.cs) e [`scripts/BomViaDb.cs`](../scripts/BomViaDb.cs) — stessa BFS ricorsiva standalone da riga di comando, che riusa `AssemblyComponentScanner` del progetto principale via `#:project` (nessuna copia dell'euristica). Usati per confrontare l'euristica contro l'albero Creo di un singolo file durante il reverse engineering, senza dover passare dalla UI/dal DB `raw`. Nota: le query contro `files_ultima_versione` filtrano solo per `(cartella_radice, codice, tipo_file)` - se lo stesso codice esiste in più cartelle radice sulla share (duplicati/copie storiche), va sempre ordinato per `data_modifica DESC` e tenuta solo la riga più recente per tipo, altrimenti si rischia di risolvere un codice contro una copia diversa (e potenzialmente diversa nel contenuto) da quella realmente referenziata. ### Trappola nella BFS: un fallimento di placement non è definitivo Un codice può comparire come riferimento fantasma (senza vero placement, vedi sopra) in un file scansionato **prima** di quello in cui invece ha un vero placement — tipicamente perché lo stesso codice è mostrato anche nel file dell'assieme padre come promemoria/metadato, oltre che nel file del proprio assieme dove è effettivamente posizionato. Osservato su un caso reale (assieme `9244228558.asm`): il componente `9244236104` compare nel file dell'assieme generale come riferimento nudo senza placement (viene scartato lì, correttamente), ma ha un vero `Component id 45 (9244236104.PRT)` dentro `9244228558_c01.asm`, il sotto-assieme che lo contiene realmente — scansionato solo _dopo_ il file padre nella BFS. Una BFS che marca un candidato come "già visto" al primo incontro, anche quando quell'incontro è un fallimento di placement, blocca per sempre ogni ri-verifica successiva: il componente resta perso anche quando il file giusto viene scansionato più avanti. Nei casi più gravi il codice bloccato è esso stesso un **sotto-assieme** (osservato con `9256510047` dentro `9256518042.asm`): se il suo codice viene scartato prematuramente, il suo file non viene nemmeno accodato, e si perde l'intero sotto-albero sottostante (compresi `9056510047`/`9056510047_C01`/`9056510047_C02` in questo caso). `scripts/BomViaDb.cs` (versione iniziale) aveva esattamente questo bug: un set `codiciVisti` unico bloccava sia i candidati confermati sia quelli scartati per mancato placement. Corretto separando le due cose: un fallimento di placement non aggiunge il codice a nessun set di blocco, quindi resta ritestabile ogni volta che lo stesso candidato ricompare in un file successivo già in coda. L'accodamento di un nuovo file da scansionare resta invece condizionato al placement confermato (non al solo essere un codice noto nel DB) — senza questo secondo vincolo, un codice generico che per puro caso coincide con un codice noto (es. `"915"`, `"932"`) farebbe esplodere la scansione su file estranei in tutto l'indice (verificato: passa da ~100 a quasi 19000 "componenti trovati" e la scansione si blocca). **Il codice della GUI (`Form1.RisolviAlberoComponenti` + `AssemblyComponentScanner.EstraiCodiciReferenziati`) non ha questo bug**, verificato riproducendo lo stesso caso reale con un test dedicato: la classificazione placement-sì/no è già interamente delegata a `EstraiCodiciReferenziati`, chiamata da zero per ogni file scansionato — un fallimento in un file non lascia traccia nel set `codiciVisti` del chiamante, che accumula solo codici già confermati. Il file successivo che referenzia davvero il componente lo ritrova quindi senza ostacoli, senza bisogno di nessuna modifica. Vale comunque come nota di design da preservare: il giorno in cui la logica di `RisolviAlberoComponenti` venisse riscritta per fare la verifica di placement inline nella BFS (come `BomViaDb.cs`), andrebbe applicata la stessa correzione. ### Seconda discrepanza trovata: l'eccezione dei codici con suffisso Confrontando `scripts/BomViaDb.cs` (corretto come sopra) con la GUI sullo stesso assieme di un caso reale più grande (`9284218024b.asm`, ~530 componenti), le due liste **non coincidevano**: lo script perdeva l'intero sotto-assieme `2546220101` (`_C01`..`_C04`, inclusi i cuscinetti `INAFAG_NA6901-XL_...` dentro `_C04`), presente invece nella BOM ufficiale Creo e trovato correttamente dalla GUI. Causa: `2546220101_C01`..`_C04` hanno un vero placement nel proprio file (`2546220101.asm`), ma **senza il suffisso** - il testo riporta solo `Componente ID N (2546220101.PRT)`, il dettaglio `_C01` compare solo altrove nei metadati, non vicino al placement (stesso fenomeno già documentato sopra in "Placement senza il dettaglio del suffisso/bracket", osservato lì solo per famiglie UNI/dimensionali). `AssemblyComponentScanner.EstraiCodiciReferenziati` già gestisce questo caso: un codice con suffisso di revisione/variante (`SuffissoRegex`, es. `_C01`, `_D100`) o prefisso di family table nota (`UNI`, `CUSCI_`) è considerato "forma nota" ed esentato dal placement esplicito - basta che occorra almeno 2 volte nel file. `BomViaDb.cs` richiedeva invece sempre il placement letterale per ogni codice, senza questa eccezione. Corretto portando la stessa regola (`FormaNota` + conteggio occorrenze) in `BomViaDb.cs`. Riverificato su entrambi i casi reali (`9244228558.asm`, `9284218024b.asm`): script e GUI producono ora **esattamente lo stesso insieme** di componenti, e in entrambi coincide con la BOM ufficiale Creo (a meno delle sole istanze di family table risolte al nome del file realmente indicizzato, es. `2502020105` → `CUSCI_DGBB7` - comportamento corretto, non una discrepanza). ## Strumento di analisi manuale [`scripts/Analyze.cs`](../scripts/Analyze.cs) stampa il contesto testuale ed esadecimale attorno a ogni occorrenza di un pattern in un file binario — usato per tutte le osservazioni di questo documento: ```powershell dotnet run scripts/Analyze.cs -- raw/9244207061.asm.1 "9044207061" ``` Utile per verificare a mano un nuovo falso positivo prima di modificare la regex o l'esclusione nello script di estrazione.