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));
|
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]
|
[Fact]
|
||||||
public void Componente_senza_ConfigName_non_e_toccato_da_istanze_di_altri_membri()
|
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",
|
@"\xe0\x01model_t_type\x00" + Int + @"\xe0\x0amodel_name\x00([\x20-\x7e]+)\x00\xe0\x0ageneric_name\x00\x00",
|
||||||
RegexOptions.Compiled);
|
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
|
// 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,
|
// un'istanza di family table (il suo "model_name" ha il suffisso "_C0n" delle istanze,
|
||||||
// stessa convenzione di SuffissoArrayFeatureRegex/ConfigName sopra) - "members" descrive
|
// stessa convenzione di SuffissoArrayFeatureRegex/ConfigName sopra) - "members" descrive
|
||||||
@@ -163,7 +171,15 @@ public static class AsmRecordReader
|
|||||||
if (grezzi.Count != conteggio)
|
if (grezzi.Count != conteggio)
|
||||||
throw new InvalidDataException($"attesi {conteggio} membri, trovati {grezzi.Count}: lettura disallineata");
|
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),
|
/// <summary>Codici dei componenti diretti (il generico su disco, non il nome istanza),
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ public class PrtMancantiForm : Form
|
|||||||
{
|
{
|
||||||
Dock = DockStyle.Top,
|
Dock = DockStyle.Top,
|
||||||
AutoSize = true,
|
AutoSize = true,
|
||||||
|
MaximumSize = new Size(ClientSize.Width - 24, 0),
|
||||||
Padding = new Padding(12, 8, 12, 8),
|
Padding = new Padding(12, 8, 12, 8),
|
||||||
Text = $"Copiati in \"{cartellaRaw}\": {numPrt} PRT e {numAsm} sotto-assiemi trovati nell'indice."
|
Text = $"Copiati in \"{cartellaRaw}\": {numPrt} PRT e {numAsm} sotto-assiemi trovati nell'indice."
|
||||||
+ (codiciNonTrovati.Count > 0
|
+ (codiciNonTrovati.Count > 0
|
||||||
@@ -70,5 +71,7 @@ public class PrtMancantiForm : Form
|
|||||||
Controls.Add(grid);
|
Controls.Add(grid);
|
||||||
Controls.Add(lblInfo);
|
Controls.Add(lblInfo);
|
||||||
Controls.Add(btnApriCartella);
|
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
|
riferimento) — non era presente in nessuno degli 8 campioni usati per il reverse engineering
|
||||||
iniziale.
|
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
|
### 4.1 Una seconda pista, che NON va usata per la BOM
|
||||||
|
|
||||||
Altrove nel file compaiono stringhe nella forma `istanza<generico>`:
|
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) |
|
| 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) |
|
| 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) |
|
| 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** (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 | — (§7.2) |
|
| 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) |
|
| 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) |
|
| 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) |
|
| 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 è
|
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
|
`model_t_type`+`model_name` (con suffisso `_C0n`)+`generic_name` vuoto
|
||||||
(`AsmRecordReader.OwnModelIdentityRegex` + `SuffissoIstanzaFamilyTableRegex`) - **non** la
|
(`AsmRecordReader.OwnModelIdentityRegex` + `SuffissoIstanzaFamilyTableRegex`) - **non** la
|
||||||
sola presenza di `generic_name`, che su altri 119 file del corpus da 350 compare in un
|
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
|
affidabile, e su quei 25 `AsmRecordReader.LeggiMembri` lancia `InvalidDataException` invece
|
||||||
di restituire il contenuto sbagliato della rappresentazione di default.
|
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**
|
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
|
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
|
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.
|
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
|
Aperto anche cosa sia davvero il record `generic_name` sui 119 file dove non è questo
|
||||||
sintomo.
|
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 -
|
- **Costante `\x82\x7b`** nei record `ConfigName`: significato ignoto. Rischio coperto -
|
||||||
`LeggiMembri` conta le occorrenze grezze di `ConfigName` e le confronta col numero di match
|
`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:
|
del regex; se un campione avesse un valore diverso, il regex non farebbe più match (prima:
|
||||||
|
|||||||
Reference in new issue
Block a user