Add standalone .asm BOM-extraction scripts and format documentation
Reverse-engineered the Creo .asm binary format enough to extract referenced component codes via a text heuristic, mirroring AssemblyComponentScanner's approach and Form1's recursive resolution against cad_index.db. Documents known false-positive classes (ModelView# view IDs, self-references, and phantom references to superseded/renamed codes). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
52483177f0
commit
f28cb818bd
5 files changed
+231
No files matched your search
@@ -0,0 +1,21 @@
|
||||
// dotnet run scripts/Analyze.cs -- <file.asm> <pattern>
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
string path = args[0];
|
||||
string pattern = args[1];
|
||||
|
||||
byte[] data = File.ReadAllBytes(path);
|
||||
string testo = Encoding.Latin1.GetString(data);
|
||||
|
||||
foreach (Match m in Regex.Matches(testo, pattern))
|
||||
{
|
||||
int s = Math.Max(0, m.Index - 24);
|
||||
int e = Math.Min(testo.Length, m.Index + m.Length + 24);
|
||||
string ctx = testo.Substring(s, e - s).Replace("\r", "\\r").Replace("\n", "\\n");
|
||||
string hexCtx = string.Join(" ", data.Skip(s).Take(e - s).Select(b => b.ToString("x2")));
|
||||
Console.WriteLine($"offset={m.Index:x} val={m.Value}");
|
||||
Console.WriteLine($" txt: {ctx}");
|
||||
Console.WriteLine($" hex: {hexCtx}");
|
||||
}
|
||||
return 0;
|
||||
@@ -0,0 +1,91 @@
|
||||
#: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.
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Data.Sqlite;
|
||||
|
||||
if (args.Length < 1)
|
||||
{
|
||||
Console.WriteLine("Uso: dotnet run ExtractCreoStrings.cs -- <file.asm> [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";
|
||||
|
||||
var codiceRegex = new Regex(@"(?<!ModelView#)\b\d{8,12}(?:_[A-Za-z]\d{2,4}|[A-Za-z]{0,2})\b", RegexOptions.Compiled);
|
||||
const int occorrenzeMinime = 2;
|
||||
|
||||
HashSet<string> EstraiCodiciReferenziati(string percorsoAsm)
|
||||
{
|
||||
var testo = Encoding.Latin1.GetString(File.ReadAllBytes(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();
|
||||
}
|
||||
|
||||
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<string>();
|
||||
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<string>(StringComparer.OrdinalIgnoreCase) { Path.GetFileName(asmIniziale).Split('.')[0] };
|
||||
var daVisitare = new Queue<string>();
|
||||
daVisitare.Enqueue(asmIniziale);
|
||||
var componentiTrovati = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var nonTrovati = new List<string>();
|
||||
|
||||
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;
|
||||
Reference in new issue
Block a user