Refine .asm component extraction: batch DB queries, wider suffix regex, placement check
- Batch the DB lookup per file instead of per code in scripts/ExtractCreoStrings.cs: files_ultima_versione is an unmaterialized view, so each query rescanned the whole files table (~230ms) - batching cuts a multi-minute BFS to seconds. - Widen the code regex to accept a revision letter and a "_..." suffix together (e.g. "9284200366A_C01"), and purely numeric "_NNNN" suffixes (e.g. dimensional variants like "2702220182_8188"), not just letter+digits. - Require bare digit-only candidates to also appear inside a real "Componente ID N (...)"/"Component id N (...)" placement, filtering out incidental references to a containing assembly's own code. - scripts/ExtractCreoStrings.cs now reports only the direct references of the given .asm (one level, matching Creo's own tree for that file) instead of recursing into sub-assemblies. - Port the regex and placement-check refinements to the production AssemblyComponentScanner.cs used by the app's "Trova componenti referenziati" and "Verifica completezza" phases. - Document the new false-positive categories (parent/container self-reference, suppressed/removed components with residual placement, placement missing suffix/bracket detail) in docs/formato-asm.md, generalized beyond the specific assemblies used to find them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e6de563f60
commit
9660c4883d
4 files changed
+185
-50
No files matched your search
@@ -1,13 +1,12 @@
|
||||
#: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 (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) e poi
|
||||
// risolve ricorsivamente i sotto-assiemi cercandoli nell'indice cad_index.db, con lo
|
||||
// stesso BFS di Form1.AssiemeCompleto: un assieme può referenziare solo i propri
|
||||
// sotto-assiemi diretti, non l'intero albero, quindi va camminato livello per livello.
|
||||
// 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.
|
||||
// Ogni codice trovato viene validato contro l'indice cad_index.db.
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Data.Sqlite;
|
||||
@@ -27,8 +26,13 @@ string outPath = args.Length > 2 ? args[2] : "BOM.txt";
|
||||
// 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 ">".
|
||||
// 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]\d{2,4}|[A-Za-z]{0,2})\b|(?<=<)UNI\d+(?=>)",
|
||||
@"(?<!ModelView#)\b\d{8,12}[A-Za-z]{0,2}(?:_[A-Za-z]?\d{2,4})?\b|(?<=<)UNI\d+(?=>)",
|
||||
RegexOptions.Compiled);
|
||||
const int occorrenzeMinime = 2;
|
||||
|
||||
@@ -38,57 +42,78 @@ HashSet<string> EstraiCodiciReferenziati(string percorsoAsm)
|
||||
var occorrenze = new Dictionary<string, int>();
|
||||
foreach (Match m in codiceRegex.Matches(testo))
|
||||
occorrenze[m.Value] = occorrenze.GetValueOrDefault(m.Value) + 1;
|
||||
return occorrenze.Where(kv => kv.Value >= occorrenzeMinime).Select(kv => kv.Key).ToHashSet();
|
||||
var candidati = occorrenze.Where(kv => kv.Value >= occorrenzeMinime).Select(kv => kv.Key).ToHashSet();
|
||||
|
||||
// Ripetersi >=2 volte nel file non basta per un codice "nudo" (senza suffisso "_..."):
|
||||
// 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.
|
||||
bool HaPlacement(string codice) =>
|
||||
Regex.IsMatch(testo, $@"Component(?:e)? id \d+ \([^()]*{Regex.Escape(codice)}[^()]*\)", RegexOptions.IgnoreCase);
|
||||
|
||||
candidati.RemoveWhere(c => Regex.IsMatch(c, @"^\d{8,12}[A-Za-z]{0,2}$") && !HaPlacement(c));
|
||||
return candidati;
|
||||
}
|
||||
|
||||
using var connection = new SqliteConnection($"Data Source={dbPath};Mode=ReadOnly");
|
||||
connection.Open();
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = "SELECT tipo_file, percorso_completo FROM files_ultima_versione "
|
||||
+ "WHERE LOWER(codice) = LOWER(@codice) AND LOWER(tipo_file) IN ('prt', 'asm')";
|
||||
var pCodice = command.Parameters.Add("@codice", SqliteType.Text);
|
||||
|
||||
List<(string Tipo, string Percorso)> CercaComponenti(string codice)
|
||||
// "files_ultima_versione" è una VIEW con ROW_NUMBER() OVER (...), che SQLite non
|
||||
// materializza: ogni query la ricalcola sull'intera tabella "files" (~230ms anche per un
|
||||
// solo codice, su ~300k righe). Batchare tutti i codici trovati in un file in un'unica
|
||||
// query paga quel costo una volta per file invece che una volta per codice - decisivo
|
||||
// sugli assiemi con centinaia di sotto-componenti referenziati.
|
||||
Dictionary<string, List<(string Tipo, string Percorso)>> CercaComponenti(IReadOnlyCollection<string> codici)
|
||||
{
|
||||
pCodice.Value = codice;
|
||||
var risultato = new List<(string, string)>();
|
||||
var tipiVisti = new HashSet<string>();
|
||||
var risultato = new Dictionary<string, List<(string, string)>>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var codice in codici)
|
||||
risultato[codice] = new List<(string, string)>();
|
||||
if (codici.Count == 0)
|
||||
return risultato;
|
||||
|
||||
using var command = connection.CreateCommand();
|
||||
var nomiParametri = codici.Select((_, i) => $"@c{i}").ToList();
|
||||
command.CommandText = "SELECT codice, tipo_file, percorso_completo FROM files_ultima_versione "
|
||||
+ $"WHERE LOWER(codice) IN ({string.Join(",", nomiParametri)}) AND LOWER(tipo_file) IN ('prt', 'asm')";
|
||||
int i = 0;
|
||||
foreach (var codice in codici)
|
||||
command.Parameters.AddWithValue(nomiParametri[i++], codice.ToLowerInvariant());
|
||||
|
||||
var tipiVisti = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
|
||||
using var reader = command.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
string tipo = reader.GetString(0).ToLowerInvariant();
|
||||
if (tipiVisti.Add(tipo))
|
||||
risultato.Add((tipo, reader.GetString(1)));
|
||||
string codiceDb = reader.GetString(0);
|
||||
string tipo = reader.GetString(1).ToLowerInvariant();
|
||||
var visti = tipiVisti.TryGetValue(codiceDb, out var v) ? v : tipiVisti[codiceDb] = new HashSet<string>();
|
||||
if (visti.Add(tipo))
|
||||
risultato[codiceDb].Add((tipo, reader.GetString(2)));
|
||||
}
|
||||
return risultato;
|
||||
}
|
||||
|
||||
var codiciVisti = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { Path.GetFileName(asmIniziale).Split('.')[0] };
|
||||
var daVisitare = new Queue<string>();
|
||||
daVisitare.Enqueue(asmIniziale);
|
||||
// Solo i riferimenti diretti del file indicato (un livello), come li mostra Creo per
|
||||
// quell'assieme - nessuna espansione ricorsiva nei sotto-assiemi.
|
||||
var codiceProprio = Path.GetFileName(asmIniziale).Split('.')[0];
|
||||
var componentiTrovati = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var nonTrovati = new List<string>();
|
||||
|
||||
while (daVisitare.Count > 0)
|
||||
var candidati = EstraiCodiciReferenziati(asmIniziale)
|
||||
.Where(c => !string.Equals(c, codiceProprio, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
var trovatiPerCodice = CercaComponenti(candidati);
|
||||
foreach (var codice in candidati)
|
||||
{
|
||||
var file = daVisitare.Dequeue();
|
||||
foreach (var codice in EstraiCodiciReferenziati(file))
|
||||
{
|
||||
if (!codiciVisti.Add(codice))
|
||||
continue;
|
||||
|
||||
var componenti = CercaComponenti(codice);
|
||||
if (componenti.Count == 0)
|
||||
{
|
||||
nonTrovati.Add(codice);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trovatiPerCodice[codice].Count == 0)
|
||||
nonTrovati.Add(codice);
|
||||
else
|
||||
componentiTrovati.Add(codice);
|
||||
var percorsoAsm = componenti.FirstOrDefault(c => c.Tipo == "asm").Percorso;
|
||||
if (percorsoAsm is not null)
|
||||
daVisitare.Enqueue(percorsoAsm);
|
||||
}
|
||||
}
|
||||
|
||||
File.WriteAllLines(outPath, componentiTrovati);
|
||||
|
||||
Reference in new issue
Block a user