Recognize commercial parts with alphabetic names in .asm scanning

Pro-E numeric codes cover most components, but parts downloaded as STEP
from online catalogs (rings, bushings, knobs...) get alphabetic Creo names
with no predictable shape. Add a second extraction path that tokenizes the
binary generically and validates candidates against the known codes in
cad_index.db, applying the same placement/occurrence rule used for bare
numeric codes. Numeric extraction is unchanged, so missing-from-index
reporting for renamed/moved Pro-E codes still works.

Also load all known prt/asm codes once per scan instead of one query per
candidate, in both RisolviAlberoComponenti and VerificaCompletezzaAssiemi.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5 committed 2026-09-18 08:41:01 +02:00
1 parent c75dd21551
commit 8a8a7d0417
7 files changed
+363 -106

No files matched your search

+32 -58
View File
@@ -662,6 +662,31 @@ public partial class Form1 : Form
ImpostaStatoImport($"{eliminati} file eliminati da {string.Join(" e ", cartelle)}.", Color.DarkGreen);
}
/// <summary>Tutti i codici prt/asm noti all'indice, con i relativi percorsi (ultima
/// versione, un solo file per tipo - stesso criterio di dedup per data_modifica DESC
/// usato ovunque in questa classe). Caricato in un'unica query invece che una per
/// codice: serve sia a risolvere i percorsi dei componenti trovati, sia a
/// AssemblyComponentScanner per validare i token generici (parti commerciali).</summary>
private static Dictionary<string, List<(string Tipo, string Percorso)>> CaricaCodiciNoti(string dbPath)
{
var codiciNoti = new Dictionary<string, List<(string Tipo, string Percorso)>>(StringComparer.OrdinalIgnoreCase);
using var connection = new SqliteConnection($"Data Source={dbPath};Mode=ReadOnly");
connection.Open();
using var command = connection.CreateCommand();
command.CommandText = "SELECT codice, tipo_file, percorso_completo FROM files_ultima_versione "
+ "WHERE LOWER(tipo_file) IN ('prt', 'asm') ORDER BY codice, tipo_file, data_modifica DESC";
using var reader = command.ExecuteReader();
while (reader.Read())
{
string codice = reader.GetString(0);
string tipo = reader.GetString(1).ToLowerInvariant();
var lista = codiciNoti.TryGetValue(codice, out var l) ? l : codiciNoti[codice] = new();
if (!lista.Any(x => x.Tipo == tipo))
lista.Add((tipo, reader.GetString(2)));
}
return codiciNoti;
}
/// <summary>Risolve ricorsivamente l'albero dei componenti a partire dai file .asm indicati
/// (già copiati in "raw"): per ogni assieme legge i codici referenziati al suo interno, li
/// cerca nell'indice, copia in "raw" quelli trovati e, se sono a loro volta assiemi, li
@@ -681,42 +706,19 @@ public partial class Form1 : Form
if (daScansionare.Count == 0 || _dbPath is null)
return (trovati, codiciNonTrovati);
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') "
+ "ORDER BY tipo_file, data_modifica DESC";
var pCodice = command.Parameters.Add("@codice", Microsoft.Data.Sqlite.SqliteType.Text);
var codiciNoti = CaricaCodiciNoti(_dbPath);
int processati = 0;
while (daScansionare.Count > 0)
{
var asm = daScansionare.Dequeue();
processati++;
foreach (var codice in AssemblyComponentScanner.EstraiCodiciReferenziati(asm))
foreach (var codice in AssemblyComponentScanner.EstraiCodiciReferenziati(asm, codiciNoti.Keys))
{
if (!codiciVisti.Add(codice))
continue;
pCodice.Value = codice;
// Lo stesso codice+tipo può esistere in più cartelle radice (duplicati sulla
// share): ordinato per data_modifica DESC, si tiene solo il più recente per tipo.
var componenti = new List<(string Tipo, string Percorso)>();
var tipiVisti = new HashSet<string>();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
string tipoLower = reader.GetString(0).ToLowerInvariant();
if (tipiVisti.Add(tipoLower))
{
componenti.Add((tipoLower, reader.GetString(1)));
}
}
}
if (componenti.Count == 0)
if (!codiciNoti.TryGetValue(codice, out var componenti))
{
codiciNonTrovati.Add(codice);
continue;
@@ -790,39 +792,11 @@ public partial class Form1 : Form
return;
}
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') "
+ "ORDER BY tipo_file, data_modifica DESC";
var pCodice = command.Parameters.Add("@codice", Microsoft.Data.Sqlite.SqliteType.Text);
var cacheComponenti = new Dictionary<string, List<(string Tipo, string Percorso)>>();
var codiciNoti = CaricaCodiciNoti(_dbPath);
var cacheCodiciFile = new Dictionary<string, HashSet<string>>();
List<(string Tipo, string Percorso)> CercaComponenti(string codice)
{
if (cacheComponenti.TryGetValue(codice, out var giaTrovati))
return giaTrovati;
pCodice.Value = codice;
var risultato = new List<(string Tipo, string Percorso)>();
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)));
}
}
}
cacheComponenti[codice] = risultato;
return risultato;
}
List<(string Tipo, string Percorso)> CercaComponenti(string codice) =>
codiciNoti.TryGetValue(codice, out var trovati) ? trovati : new List<(string, string)>();
bool AssiemeCompleto(string percorsoAsmIniziale)
{
@@ -843,7 +817,7 @@ public partial class Form1 : Form
{
try
{
codiciReferenziati = AssemblyComponentScanner.EstraiCodiciReferenziati(file);
codiciReferenziati = AssemblyComponentScanner.EstraiCodiciReferenziati(file, codiciNoti.Keys);
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{