Risolve i membri family table senza ConfigName e la scritta troncata in "Componenti referenziati"

Alcuni membri di family table non-UNI portano la notazione "<istanza><generico>"
direttamente nel campo name/family_name di members, senza un ConfigName
associato: AsmRecordReader cercava quella stringa intera (parentesi comprese)
nell'indice e non la trovava mai. Ora separa istanza e generico.

Corretta anche l'etichetta di PrtMancantiForm, che usciva dal bordo della
finestra invece di andare a capo.

Documentato il nuovo meccanismo in docs/formato-asm-record.md, e aggiunta una
nota sul segnale di rilevamento family table di assiemi: validato su un caso
reale ma non affidabile al 100% su scala (trovato un falso positivo).
This commit is contained in:
davide committed 2026-09-22 09:57:48 +02:00
1 parent 9af426ca1b
commit cf4c2e8e29
4 files changed
+106 -4

No files matched your search

+17
View File
@@ -87,6 +87,23 @@ public class AsmRecordReaderTests
Assert.All(membri, m => Assert.Equal("9244250136_C03", m.Nome));
}
[Fact]
public void Membro_con_notazione_istanza_generico_diretta_nel_nome_usa_il_generico()
{
// Regressione: 9244248577a_c01.asm - alcuni membri di family table non-UNI non
// passano da ConfigName, il campo "name"/"family_name" del record members porta gia'
// la forma "<istanza><generico>" (stessa notazione di §4.1). Senza lo split, il codice
// cercato nell'indice sarebbe "V6-12--_-_U5931_GC<UNI5931>" per intero, mai trovato -
// va cercato invece il generico "UNI5931".
byte[] dati = Asm(1, Membro(1, 2, "V6-12--_-_U5931_GC<UNI5931>"));
var membri = AsmRecordReader.LeggiMembri(dati);
Assert.Single(membri);
Assert.Equal("V6-12--_-_U5931_GC", membri[0].Nome);
Assert.Equal("UNI5931", membri[0].Generico);
}
[Fact]
public void Componente_senza_ConfigName_non_e_toccato_da_istanze_di_altri_membri()
{
+17 -1
View File
@@ -59,6 +59,14 @@ public static class AsmRecordReader
@"\xe0\x01model_t_type\x00" + Int + @"\xe0\x0amodel_name\x00([\x20-\x7e]+)\x00\xe0\x0ageneric_name\x00\x00",
RegexOptions.Compiled);
// Alcuni membri di family table non-UNI (es. viti/dadi commerciali) non passano da
// ConfigName: il campo "name" del record members porta gia' la forma "<istanza><generico>"
// (stessa notazione di §4.1, li' sconsigliata per la BOM perche' e' un elenco di
// riferimenti geometrici - qui invece e' il valore vero del componente, va solo separato).
// Osservato su 9244248577a_c01.asm: "V6-12--_-_U5931_GC<UNI5931>" - senza questo split il
// codice cercato nell'indice sarebbe la stringa intera con le parentesi, mai trovata.
private static readonly Regex IstanzaConGenericoRegex = new(@"^([\x20-\x7e]+?)<([\x20-\x7e]+)>$", RegexOptions.Compiled);
// sintomo vero e proprio (docs/formato-asm-record.md §7): il file e' esso stesso
// un'istanza di family table (il suo "model_name" ha il suffisso "_C0n" delle istanze,
// stessa convenzione di SuffissoArrayFeatureRegex/ConfigName sopra) - "members" descrive
@@ -163,7 +171,15 @@ public static class AsmRecordReader
if (grezzi.Count != conteggio)
throw new InvalidDataException($"attesi {conteggio} membri, trovati {grezzi.Count}: lettura disallineata");
return grezzi.Select(g => new Membro(g.Id, g.Tipo, istanze.GetValueOrDefault(g.Id, g.Nome), g.Nome)).ToList();
return grezzi.Select(g =>
{
if (istanze.TryGetValue(g.Id, out var nomeIstanza))
return new Membro(g.Id, g.Tipo, nomeIstanza, g.Nome);
var bracket = IstanzaConGenericoRegex.Match(g.Nome);
return bracket.Success
? new Membro(g.Id, g.Tipo, bracket.Groups[1].Value, bracket.Groups[2].Value)
: new Membro(g.Id, g.Tipo, g.Nome, g.Nome);
}).ToList();
}
/// <summary>Codici dei componenti diretti (il generico su disco, non il nome istanza),
+3
View File
@@ -20,6 +20,7 @@ public class PrtMancantiForm : Form
{
Dock = DockStyle.Top,
AutoSize = true,
MaximumSize = new Size(ClientSize.Width - 24, 0),
Padding = new Padding(12, 8, 12, 8),
Text = $"Copiati in \"{cartellaRaw}\": {numPrt} PRT e {numAsm} sotto-assiemi trovati nell'indice."
+ (codiciNonTrovati.Count > 0
@@ -70,5 +71,7 @@ public class PrtMancantiForm : Form
Controls.Add(grid);
Controls.Add(lblInfo);
Controls.Add(btnApriCartella);
Resize += (_, _) => lblInfo.MaximumSize = new Size(ClientSize.Width - 24, 0);
}
}
+69 -3
View File
@@ -223,6 +223,30 @@ ampio degli 8 campioni originali (`9284218024b.asm`, confrontato con la sua BOM
riferimento) — non era presente in nessuno degli 8 campioni usati per il reverse engineering
iniziale.
### 4.0.2 Istanza risolta direttamente in `members`, senza passare da `ConfigName`
Non tutti i membri che sono istanze di family table hanno un record `ConfigName` associato.
Per alcune parti commerciali/normalizzate non-UNI, il campo `name` (e allo stesso modo
`family_name`) del record `members` (§3) porta già per intero la notazione
`<nome istanza><generico>` invece del solo nome generico:
```
\xe3 <id> <type> "<istanza><generico>"\0 "<istanza><generico>"\0
```
È la stessa notazione istanza→generico della §4.1 sotto, ma qui compare **dentro** il record
`members` stesso, come valore diretto del componente - non nell'elenco di riferimenti
geometrici di cui parla la §4.1 (quello sì da ignorare per la BOM). Il generico fra `<` e `>`
è il file realmente indicizzato sul disco; il nome prima di `<` è quello che la BOM
mostrerebbe.
`AsmRecordReader.LeggiMembri` lo riconosce per i membri **senza** un `ConfigName`
corrispondente il cui campo `name` rispetta per intero il pattern `<testo><testo>`: il testo
dopo l'ultima `<` (fino alla `>` finale) diventa il generico da cercare nell'indice, quello
prima diventa il nome mostrato. Senza questo split, il codice cercato nell'indice sarebbe la
stringa intera con le parentesi comprese, mai presente nell'indice - il componente
risulterebbe erroneamente "non trovato".
### 4.1 Una seconda pista, che NON va usata per la BOM
Altrove nel file compaiono stringhe nella forma `istanza<generico>`:
@@ -335,8 +359,8 @@ di ciascun punto segue sotto, in due gruppi.
| Deduplica blocchi + moltiplicazione quantità (§6.2/§6.3) | Risolto | BOM reale (§7.1) |
| Revisione del figlio che il padre si aspetta (§6.4) | Risolto | BOM reale (§7.1) |
| Stato soppresso/escluso in `members` | Risolto | BOM reale (§7.1) |
| Family table di **assiemi** (rilevamento) | Risolto | BOM reale + scala 350 file (§7.1) |
| Family table di **assiemi** (contenuto di ogni istanza) | Non risolto | — (§7.2) |
| Family table di **assiemi** (rilevamento) | Risolto lato sicurezza (mai un falso negativo pericoloso), segnale non affidabile al 100% | BOM reale + scala intera share (§7.1) |
| Family table di **assiemi** (contenuto di ogni istanza) | Non risolto, nessun impatto sulla BOM (fallback già corretto) | Scan sull'intera share (§7.2) |
| Costante `\x82\x7b` in `ConfigName` | Significato ignoto, rischio coperto | Scala 350 file (§7.2) |
| Occorrenze multiple di `members` (simplified rep) | Mai osservato, rischio coperto | Scala 350 file (§7.2) |
| Matrice di posizionamento (`e1`/`e2`/`e3`/`origin`) | Non letto, nessun impatto sulla BOM | — (§7.2) |
@@ -388,7 +412,7 @@ di sbagliare in silenzio.
```
Il file generico è già di per sé un'istanza (`model_name` porta il suffisso `_C0n`, non è
neutro), e `stamp` elenca le sorelle. **Segnale affidabile**: la combinazione
neutro), e `stamp` elenca le sorelle. Il segnale usato per riconoscerlo è la combinazione
`model_t_type`+`model_name` (con suffisso `_C0n`)+`generic_name` vuoto
(`AsmRecordReader.OwnModelIdentityRegex` + `SuffissoIstanzaFamilyTableRegex`) - **non** la
sola presenza di `generic_name`, che su altri 119 file del corpus da 350 compare in un
@@ -397,6 +421,15 @@ di sbagliare in silenzio.
affidabile, e su quei 25 `AsmRecordReader.LeggiMembri` lancia `InvalidDataException` invece
di restituire il contenuto sbagliato della rappresentazione di default.
> **Non è un segnale affidabile al 100%.** Un controllo su scala più ampia (tutta la share,
> §7.2) ha trovato un caso in cui il segnale scatta ma **non c'è nessuna family table**:
> `9053005060.asm` è un assieme ordinario con due parti figlie `_C01`/`_C02` (geometrie
> diverse, nessuna family table nell'albero Creo - verificato dall'utente). Il perché il
> record di identità propria porti comunque il suffisso `_C0n` in quel caso resta ignoto.
> Non cambia la sicurezza del comportamento (`InvalidDataException` → fallback su
> `AssemblyComponentScanner`, che legge comunque il file correttamente), ma il segnale va
> trattato come "candidato da verificare", non come conferma di family table.
Verificato anche che il fallback su `AssemblyComponentScanner` **risolve già correttamente**
il caso vero: i nomi delle istanze compaiono come testo puro nel campo `stamp`, e
l'euristica ("codice che compare almeno due volte") li trova senza bisogno di decodificare
@@ -413,6 +446,39 @@ di sbagliare in silenzio.
dietro `stamp` non è stato decodificato oltre a nome+tipo di ciascuna istanza sorella.
Aperto anche cosa sia davvero il record `generic_name` sui 119 file dove non è questo
sintomo.
**Aggiornamento (scan sull'intera share, non solo sul campione da 350 file).** Applicando
il segnale (`OwnModelIdentityRegex` + `SuffissoIstanzaFamilyTableRegex`) a tutti i `.asm`
indicizzati su `X:\` (7157 combinazioni cartella+codice uniche, ultima revisione ciascuna):
**95 candidati**, non solo i 25/350 del campione originale.
Sul primo candidato controllato (`9053005060.asm.1`, un'unica istanza sorella secondo il
segnale), `stamp` si decodifica a mano byte per byte:
```
\xe0\x00 "stamp"\0 \xe2\xf2\xf7\x07 \xe3\x02 "9053005060_C02"\0\0 \xe2
```
Confermato: **`stamp` porta solo i nomi** delle istanze sorelle, non il loro contenuto -
non è quindi la struttura giusta da cui partire per risolvere questo punto in generale.
**Ma il candidato stesso era un falso positivo.** Verificato in Creo (albero del modello,
screenshot dell'utente): `9053005060.asm` **non** è una family table - è un assieme
ordinario con due parti figlie distinte `9053005060_C01.prt` e `9053005060_C02.prt` (pesi e
geometrie diverse, nessuna icona di family table nell'albero). Il segnale ha comunque
acceso perché, in quella revisione, il record di identità propria del file portava
`model_name = 9053005060_C01` con `generic_name` vuoto - non si sa ancora perché (forse
legato all'ultimo componente attivo/rigenerato quando il file fu salvato, non a una vera
family table). **Conseguenza pratica: il segnale, validato con BOM reale solo su
`2546220101.asm` (§7.1), non è affidabile su larga scala senza verifica caso per caso** - i
95 candidati trovati dallo scan sono solo *papabili*, non confermati; non c'è motivo di
credere che la maggioranza sia una vera family table di assieme.
**Resta comunque un punto aperto solo per completezza documentale, non un problema da
correggere**: qualunque sia la causa del falso positivo, il fallback su
`AssemblyComponentScanner` gestisce comunque correttamente il file (legge i due membri via
euristica testuale, non tenta di interpretare `stamp`), quindi nessun comportamento della
app dipende da questa decodifica.
- **Costante `\x82\x7b`** nei record `ConfigName`: significato ignoto. Rischio coperto -
`LeggiMembri` conta le occorrenze grezze di `ConfigName` e le confronta col numero di match
del regex; se un campione avesse un valore diverso, il regex non farebbe più match (prima: