#:package Microsoft.Data.Sqlite@10.0.12 // dotnet run scripts/ExtractCreoStrings.cs -- [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#" 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. using System.Text; using System.Text.RegularExpressions; using Microsoft.Data.Sqlite; if (args.Length < 1) { Console.WriteLine("Uso: dotnet run ExtractCreoStrings.cs -- [cad_index.db] [output.txt]"); return 1; } 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.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." - va quindi estratto // solo il contenuto tra "<" e ">". var codiceRegex = new Regex( @"(?)", RegexOptions.Compiled); const int occorrenzeMinime = 2; HashSet EstraiCodiciReferenziati(string percorsoAsm) { var testo = Encoding.Latin1.GetString(File.ReadAllBytes(percorsoAsm)); var occorrenze = new Dictionary(); 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(); } 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) { pCodice.Value = codice; var risultato = new List<(string, string)>(); var tipiVisti = new HashSet(); using var reader = command.ExecuteReader(); while (reader.Read()) { string tipo = reader.GetString(0).ToLowerInvariant(); if (tipiVisti.Add(tipo)) risultato.Add((tipo, reader.GetString(1))); } return risultato; } var codiciVisti = new HashSet(StringComparer.OrdinalIgnoreCase) { Path.GetFileName(asmIniziale).Split('.')[0] }; var daVisitare = new Queue(); daVisitare.Enqueue(asmIniziale); var componentiTrovati = new SortedSet(StringComparer.OrdinalIgnoreCase); var nonTrovati = new List(); while (daVisitare.Count > 0) { 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; } componentiTrovati.Add(codice); var percorsoAsm = componenti.FirstOrDefault(c => c.Tipo == "asm").Percorso; if (percorsoAsm is not null) daVisitare.Enqueue(percorsoAsm); } } File.WriteAllLines(outPath, componentiTrovati); Console.WriteLine($"{componentiTrovati.Count} componenti scritti in {outPath}"); if (nonTrovati.Count > 0) Console.WriteLine($"Codici trovati nel file ma non nell'indice (scartati): {string.Join(", ", nonTrovati)}"); return 0;