Fix runaway recursion and two missed-component gaps in .asm scanning
The bare root code of an assembly (e.g. 9284208188, without a _D... suffix) is always referenced inside its own suffixed sub-assemblies as a genuine placement pointing back to the general assembly. Following it during recursive resolution pulled in the entire product tree instead of just the requested sub-assembly (598 components collected instead of ~65 on 9284208188_D600). Fixed by seeding the visited-codes set with each starting assembly's root code, in both scripts/ExtractCreoStrings.cs and the two BFS call sites in Form1.cs (RisolviAlberoComponenti, AssiemeCompleto). Also closes two recall gaps found by comparing against a real Creo BOM report: some real placements only show up as the code repeated twice separated by a NUL byte rather than the usual "Componente ID N (...)" text, and family-table bearing codes (CUSCI_...) use the same <...> bracket convention as UNI hardware but weren't recognized. docs/formato-asm.md documents all three findings. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9660c4883d
commit
a7e0df0247
4 files changed
+147
-69
No files matched your search
@@ -1,11 +1,11 @@
|
||||
#:package Microsoft.Data.Sqlite@10.0.12
|
||||
// dotnet run scripts/ExtractCreoStrings.cs -- <file.asm> [cad_index.db] [output.txt]
|
||||
//
|
||||
// Estrae dal file .asm i codici dei componenti referenziati DIRETTAMENTE (un solo
|
||||
// livello, come li mostra Creo per quell'assieme - nessuna espansione ricorsiva nei
|
||||
// sotto-assiemi). Stessa euristica di CadSearchGui/AssemblyComponentScanner.cs: i codici
|
||||
// veri compaiono più volte nel file, il rumore binario quasi sempre una sola volta; gli
|
||||
// ID interni "ModelView#<numero>" sono esclusi via lookbehind - vedi scripts/Analyze.cs.
|
||||
// Estrae dal file .asm i codici dei componenti referenziati, espandendo ricorsivamente
|
||||
// ogni sotto-assieme (.asm) trovato nell'indice, finché non restano solo PRT (foglie) o
|
||||
// codici non presenti nell'indice. Stessa euristica di CadSearchGui/AssemblyComponentScanner.cs:
|
||||
// i codici veri compaiono più volte nel file, il rumore binario quasi sempre una sola volta;
|
||||
// gli ID interni "ModelView#<numero>" sono esclusi via lookbehind - vedi scripts/Analyze.cs.
|
||||
// Ogni codice trovato viene validato contro l'indice cad_index.db.
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -21,18 +21,19 @@ string asmIniziale = args[0];
|
||||
string dbPath = args.Length > 1 ? args[1] : "cad_index.db";
|
||||
string outPath = args.Length > 2 ? args[2] : "BOM.txt";
|
||||
|
||||
// Seconda alternativa: viti/dadi/rosette normalizzati UNI, definiti come family table in
|
||||
// Creo. Compaiono come "Componente ID N (R12--_-_U6592<UNI6592>.PRT)": "R12--_-_U6592" è
|
||||
// il nome della singola istanza (riga della family table, non un file separato su disco),
|
||||
// il file reale indicizzato è il generico "uni6592.prt.<versione>" - va quindi estratto
|
||||
// solo il contenuto tra "<" e ">".
|
||||
// Seconda alternativa: componenti normalizzati definiti come family table in Creo (viti/
|
||||
// dadi/rosette UNI, ma anche cuscinetti CUSCI_...). Compaiono come "Componente ID N
|
||||
// (R12--_-_U6592<UNI6592>.PRT)": "R12--_-_U6592" è il nome della singola istanza (riga
|
||||
// della family table, non un file separato su disco), il file reale indicizzato è il
|
||||
// generico "uni6592.prt.<versione>"/"cusci_acbb7.prt.<versione>" - va quindi estratto solo
|
||||
// il contenuto tra "<" e ">".
|
||||
// Terza alternativa: varianti dimensionali con suffisso puramente numerico (es. cinghie
|
||||
// "2702220182_8188", dove "8188" è una misura, non una lettera di revisione/sotto-assieme
|
||||
// come "_C01"/"_D100") - la lettera dopo "_" è quindi opzionale, non obbligatoria. La
|
||||
// lettera di revisione e il suffisso "_" possono anche comparire insieme sullo stesso
|
||||
// codice (es. "9284200366A_C01", componente di un sotto-assieme già di per sé revisionato).
|
||||
var codiceRegex = new Regex(
|
||||
@"(?<!ModelView#)\b\d{8,12}[A-Za-z]{0,2}(?:_[A-Za-z]?\d{2,4})?\b|(?<=<)UNI\d+(?=>)",
|
||||
@"(?<!ModelView#)\b\d{8,12}[A-Za-z]{0,2}(?:_[A-Za-z]?\d{2,4})?\b|(?<=<)(?:UNI\d+|CUSCI_[A-Za-z0-9]+)(?=>)",
|
||||
RegexOptions.Compiled);
|
||||
const int occorrenzeMinime = 2;
|
||||
|
||||
@@ -48,14 +49,18 @@ HashSet<string> EstraiCodiciReferenziati(string percorsoAsm)
|
||||
// riferimenti al proprio assieme padre (nome sessione/file) o a codici superati/
|
||||
// rinominati (caso "fantasma", docs/formato-asm.md) ripetono comunque il codice più
|
||||
// volte pur non essendo mai un componente vero. Per questi si richiede un vero
|
||||
// placement "Componente ID N (<codice>...)". "Componente ID"/"Component id": la
|
||||
// stringa cambia con la localizzazione Creo con cui il file è stato salvato (osservate
|
||||
// sia italiana sia inglese). Non si applica alle varianti con suffisso "_..." né alle
|
||||
// istanze family table UNI: in alcuni file il placement riporta solo il nome
|
||||
// istanza/generico senza il dettaglio del suffisso o del bracket "<...>", che vive
|
||||
// altrove nei metadati - lì l'occorrenza ripetuta resta l'unico segnale disponibile.
|
||||
// placement, in una delle due forme osservate nel binario: "Componente ID N
|
||||
// (<codice>...)"/"Component id N (<codice>...)" (la stringa cambia con la
|
||||
// localizzazione Creo del file), oppure il codice ripetuto due volte di seguito
|
||||
// separato da un singolo byte NUL ("<codice>\0<codice>\0", osservato per componenti di
|
||||
// family table non-UNI, es. cuscinetti "CUSCI_..."). Non si applica alle varianti con
|
||||
// suffisso "_..." né alle istanze family table UNI: in alcuni file il placement
|
||||
// riporta solo il nome istanza/generico senza il dettaglio del suffisso o del bracket
|
||||
// "<...>", che vive altrove nei metadati - lì l'occorrenza ripetuta resta l'unico
|
||||
// segnale disponibile.
|
||||
bool HaPlacement(string codice) =>
|
||||
Regex.IsMatch(testo, $@"Component(?:e)? id \d+ \([^()]*{Regex.Escape(codice)}[^()]*\)", RegexOptions.IgnoreCase);
|
||||
Regex.IsMatch(testo, $@"Component(?:e)? id \d+ \([^()]*{Regex.Escape(codice)}[^()]*\)", RegexOptions.IgnoreCase)
|
||||
|| testo.Contains(codice + "\0" + codice + "\0");
|
||||
|
||||
candidati.RemoveWhere(c => Regex.IsMatch(c, @"^\d{8,12}[A-Za-z]{0,2}$") && !HaPlacement(c));
|
||||
return candidati;
|
||||
@@ -98,22 +103,42 @@ Dictionary<string, List<(string Tipo, string Percorso)>> CercaComponenti(IReadOn
|
||||
return risultato;
|
||||
}
|
||||
|
||||
// Solo i riferimenti diretti del file indicato (un livello), come li mostra Creo per
|
||||
// quell'assieme - nessuna espansione ricorsiva nei sotto-assiemi.
|
||||
// Espansione ricorsiva: ogni sotto-assieme (.asm) trovato viene a sua volta letto e i suoi
|
||||
// componenti aggiunti alla coda, finché non restano solo PRT (foglie) o codici non presenti
|
||||
// nell'indice - stessa logica di Form1.RisolviAlberoComponenti.
|
||||
var codiceProprio = Path.GetFileName(asmIniziale).Split('.')[0];
|
||||
// Un assieme con suffisso "_D..." referenzia sempre al suo interno anche l'assieme
|
||||
// generale (codice nudo, senza suffisso) a cui appartiene - riferimento fantasma verso
|
||||
// l'alto, non un componente vero (vedi docs/formato-asm.md). Va escluso subito: se
|
||||
// seguito, riporta dentro l'intero albero del macchinario invece del solo sotto-assieme
|
||||
// richiesto (visto su 9284208188_D600 -> 9284208188, che da solo genera 598 falsi
|
||||
// positivi invece di ~90 componenti reali).
|
||||
var codiceRadice = codiceProprio.Split('_')[0];
|
||||
var componentiTrovati = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var nonTrovati = new List<string>();
|
||||
var codiciVisti = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { codiceProprio, codiceRadice };
|
||||
var daScansionare = new Queue<string>(new[] { asmIniziale });
|
||||
|
||||
var candidati = EstraiCodiciReferenziati(asmIniziale)
|
||||
.Where(c => !string.Equals(c, codiceProprio, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
var trovatiPerCodice = CercaComponenti(candidati);
|
||||
foreach (var codice in candidati)
|
||||
while (daScansionare.Count > 0)
|
||||
{
|
||||
if (trovatiPerCodice[codice].Count == 0)
|
||||
nonTrovati.Add(codice);
|
||||
else
|
||||
var asm = daScansionare.Dequeue();
|
||||
var candidati = EstraiCodiciReferenziati(asm)
|
||||
.Where(codiciVisti.Add)
|
||||
.ToList();
|
||||
var trovatiPerCodice = CercaComponenti(candidati);
|
||||
foreach (var codice in candidati)
|
||||
{
|
||||
var componenti = trovatiPerCodice[codice];
|
||||
if (componenti.Count == 0)
|
||||
{
|
||||
nonTrovati.Add(codice);
|
||||
continue;
|
||||
}
|
||||
componentiTrovati.Add(codice);
|
||||
foreach (var (tipo, percorso) in componenti)
|
||||
if (tipo == "asm")
|
||||
daScansionare.Enqueue(percorso);
|
||||
}
|
||||
}
|
||||
|
||||
File.WriteAllLines(outPath, componentiTrovati);
|
||||
|
||||
Reference in new issue
Block a user