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:
1 parent
9af426ca1b
commit
cf4c2e8e29
4 files changed
+106
-4
No files matched your search
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user