Add family_name and bracket-adjacent placement signals for .asm scanning

Two more real placement forms found by comparing against Creo BOM reports:

- A sub-assembly can be referenced only through a "family_name\0<code>\0"
  metadata field instead of a textual "Componente ID" placement or the
  doubled-NUL pattern (case 9284202452 inside 9284208188_D400.asm, which
  contains 9284202452_C01/_C02/_C03 - previously dropped entirely, along
  with its three children).
- A bare numeric code immediately followed by a family-table bracket
  (<code><GENERIC>, e.g. bearing instances like 2510060303<CUSCI_ACBB7>)
  is a real reference to that instance, even though only the generic file
  is indexed on disk. Previously the instance code silently vanished before
  ever reaching the DB lookup; now it survives placement filtering and ends
  up in the "scartati" (discarded) list instead, so it's traceable rather
  than invisible.

docs/formato-asm.md documents both, bringing the total to four recognized
placement forms.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5 committed 2026-09-17 16:14:52 +02:00
1 parent a7e0df0247
commit e7b5d85f6b
3 files changed
+61 -17

No files matched your search

+16 -6
View File
@@ -36,12 +36,20 @@ namespace CadSearchGui;
/// basta: può trattarsi del riferimento al nome di un altro assieme che lo contiene (non
/// noto a priori, quindi non escludibile per confronto diretto come il proprio codice) o
/// di un residuo di feature soppresse/superate (vedi docs/formato-asm.md). Per questi si
/// richiede anche un vero placement, in una delle due forme osservate nel binario:
/// richiede anche un vero placement, in una delle quattro forme osservate nel binario:
/// "Componente ID N (&lt;codice&gt;...)"/"Component id N (&lt;codice&gt;...)" (la stringa
/// cambia con la localizzazione Creo del file), oppure il codice ripetuto due volte di
/// seguito separato da un singolo byte NUL ("&lt;codice&gt;\0&lt;codice&gt;\0", osservato per
/// componenti di family table non-UNI, es. cuscinetti "CUSCI_..."). Non si applica alle
/// varianti con suffisso o bracket UNI: in alcuni file il placement riporta solo il nome
/// cambia con la localizzazione Creo del file); il codice ripetuto due volte di seguito
/// separato da un singolo byte NUL ("&lt;codice&gt;\0&lt;codice&gt;\0", osservato per
/// componenti di family table non-UNI, es. cuscinetti "CUSCI_..."); il campo
/// "family_name\0&lt;codice&gt;\0" (osservato una sola volta per file, su un sotto-assieme
/// referenziato come family table generica invece che con un placement testuale esplicito);
/// oppure il codice seguito immediatamente da un bracket "&lt;codice&gt;&lt;GENERICO&gt;" -
/// l'istanza di una family table non-UNI vista nel nome (es. "2510060303&lt;CUSCI_ACBB7&gt;",
/// un cuscinetto): il codice dell'istanza non è mai indicizzato a sé (solo il generico tra
/// "&lt;" e "&gt;" lo è), ma questo segnale lo fa comunque sopravvivere fino alla ricerca nel
/// DB invece di scomparire in silenzio come "nudo senza placement" - lì verrà comunque
/// scartato come "non in indice", ma in modo tracciabile. Non si applica alle varianti con
/// suffisso o bracket 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ì resta necessaria la sola euristica delle occorrenze ripetute.
/// </summary>
@@ -74,7 +82,9 @@ static class AssemblyComponentScanner
bool HaPlacement(string codice) =>
Regex.IsMatch(testo, $@"Component(?:e)? id \d+ \([^()]*{Regex.Escape(codice)}[^()]*\)", RegexOptions.IgnoreCase)
|| testo.Contains(codice + "\0" + codice + "\0");
|| testo.Contains(codice + "\0" + codice + "\0")
|| testo.Contains("family_name\0" + codice + "\0")
|| testo.Contains(codice + "<");
candidati.RemoveWhere(c => CodiceNudoRegex.IsMatch(c) && !HaPlacement(c));
return candidati;
+23 -4
View File
@@ -73,7 +73,7 @@ non essendo un componente. A differenza del proprio codice, non è noto a
priori: dipende da dove/come il file è stato aperto o da chi lo referenzia,
quindi non si può escludere per confronto diretto come per il caso
precedente. La regola generale che discrimina questo caso: un componente
vero compare **sempre** anche dentro un vero placement, in una delle due
vero compare **sempre** anche dentro un vero placement, in una delle quattro
forme osservate nel binario:
- `Componente ID N (<codice>...)`/`Component id N (<codice>...)` (la stringa
@@ -82,13 +82,32 @@ forme osservate nel binario:
- 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_...` - vedi sotto; il placement testuale
"Componente ID" non li copre, ma questa coppia doppia sì).
"Componente ID" non li copre, ma questa coppia doppia sì);
- il campo `family_name\0<codice>\0` (con `name` e `family_name` uguali,
entrambi pari al codice). Osservato una sola volta per file, su un
sotto-assieme referenziato come family table generica invece che con un
placement testuale esplicito - caso `9284202452` dentro
`9284208188_D400.asm`: il sotto-assieme (che contiene a sua volta
`9284202452_C01/_C02/_C03`) non ha nessun `Componente ID (...)` né coppia
NUL, solo questo campo `family_name`. Verificato su 5 file diversi
(`9284208188_D400/D600/D610/D620/D630.asm`): l'unica occorrenza di
`family_name\0<codice>\0` in ciascuno corrisponde sempre a un componente
reale confermato dalla BOM Creo, mai al riferimento fantasma verso l'alto;
- il codice seguito immediatamente da un bracket, `<codice><GENERICO>`
(senza NUL né spazio in mezzo) - l'istanza di una family table non-UNI
(es. `2510060303<CUSCI_ACBB7>`, un cuscinetto, confermato aprendo l'assieme
in Creo: descrizione "CUSC.OBL.SFERE 2COR 17X40"). Il codice dell'istanza
non è mai indicizzato a sé sulla share (solo il generico tra `<` e `>` lo
è - stesso meccanismo di UNI, vedi sotto), quindi anche riconosciuto come
placement reale finisce comunque negli "scartati" alla ricerca nel DB - ma
in modo tracciabile, invece di sparire in silenzio prima ancora di essere
cercato.
Un riferimento incidentale al nome di un altro assieme, anche se ripetuto più
volte, non compare mai in nessuna delle due forme. `scripts/ExtractCreoStrings.cs`
volte, non compare mai in nessuna delle quattro forme. `scripts/ExtractCreoStrings.cs`
e `AssemblyComponentScanner.cs` applicano questa regola ai codici "nudi" (senza
suffisso `_...` e senza bracket family table - vedi sotto perché limitata a
questi): un codice ripetuto ma mai trovato dentro un placement/coppia NUL
questi): un codice ripetuto ma mai trovato dentro uno dei quattro segnali
viene scartato. Questo NON copre i casi fantasma/soppressi sotto, che hanno un
placement sintatticamente identico a uno vero e restano quindi indistinguibili
a livello testuale.
+22 -7
View File
@@ -49,18 +49,28 @@ 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, in una delle due forme osservate nel binario: "Componente ID N
// placement, in una delle quattro 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
// localizzazione Creo del file); 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_..."); il campo "family_name\0<codice>\0"
// (osservato una sola volta per file, su un sotto-assieme referenziato come family
// table generica invece che con un placement testuale esplicito); oppure il codice
// seguito immediatamente da un bracket "<codice><GENERICO>" - l'istanza di una family
// table non-UNI vista nel nome (es. "2510060303<CUSCI_ACBB7>", un cuscinetto): il
// codice dell'istanza non è mai indicizzato a sé (solo il generico tra "<" e ">" lo è,
// vedi sotto), ma questo segnale lo fa comunque sopravvivere fino alla ricerca nel DB
// invece di scomparire in silenzio come "nudo senza placement" - lì verrà comunque
// scartato come "non in indice", ma in modo tracciabile. 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)
|| testo.Contains(codice + "\0" + codice + "\0");
|| testo.Contains(codice + "\0" + codice + "\0")
|| testo.Contains("family_name\0" + codice + "\0")
|| testo.Contains(codice + "<");
candidati.RemoveWhere(c => Regex.IsMatch(c, @"^\d{8,12}[A-Za-z]{0,2}$") && !HaPlacement(c));
return candidati;
@@ -84,8 +94,13 @@ Dictionary<string, List<(string Tipo, string Percorso)>> CercaComponenti(IReadOn
using var command = connection.CreateCommand();
var nomiParametri = codici.Select((_, i) => $"@c{i}").ToList();
// Lo stesso codice+tipo può esistere in più cartelle radice (duplicati/copie storiche
// sulla share, es. un generico family table ripetuto in 5 progetti scollegati): ordinato
// per data_modifica DESC, si tiene solo il più recente per codice+tipo (stessa regola di
// Form1.RisolviAlberoComponenti, vedi docs/formato-asm.md).
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')";
+ $"WHERE LOWER(codice) IN ({string.Join(",", nomiParametri)}) AND LOWER(tipo_file) IN ('prt', 'asm') "
+ "ORDER BY codice, tipo_file, data_modifica DESC";
int i = 0;
foreach (var codice in codici)
command.Parameters.AddWithValue(nomiParametri[i++], codice.ToLowerInvariant());