Normalizza il suffisso #AF di ConfigName; documenta il limite sulle family table di assiemi

Test end-to-end su un albero reale (9284218024b.asm, confrontato con la sua
BOM Creo di riferimento tramite scripts/CreoBom.cs) oltre gli 8 campioni usati
per il reverse engineering iniziale.

Trovato: i componenti creati da un pattern/array feature hanno un ConfigName
con suffisso "<codice>#AF<n>" (n = indice nel pattern), non un'istanza di
family table vera - la BOM di Creo lo ignora e unisce le occorrenze duplicate.
AsmRecordReader ora rimuove questo suffisso prima di usare il nome istanza.

Trovato ma non risolto: un intero ASSIEME (non solo le parti normalizzate)
puo' avere una family table, risolta da Creo con un meccanismo diverso da
ConfigName (non decodificato). AsmRecordReader non lo rileva e non lancia
eccezione in questo caso: il fallback automatico di Form1 non scatta, e il
risultato puo' essere silenziosamente incompleto. Documentato come limite
noto in formato-asm-record.md e in CLAUDE.md - nessun fix applicato finche'
il meccanismo non e' meglio compreso.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5 committed 2026-09-21 16:27:15 +02:00
1 parent cce377a091
commit 28a72c80b2
4 files changed
+70 -3

No files matched your search

+4 -1
View File
@@ -63,7 +63,10 @@ designer visuale):
sotto-assiemi/parti (`RisolviAlberoComponenti`), copiandoli anch'essi in `raw/`. Usa
`AsmRecordReader` (parser esatto dei record binari, `docs/formato-asm-record.md`) e ripiega
su `AssemblyComponentScanner` (euristica testuale, `docs/formato-asm.md`) solo se il file
non rispetta il formato validato (`InvalidDataException`).
non rispetta il formato validato (`InvalidDataException`). ⚠️ Limite noto non coperto dal
fallback: un **assieme** con family table (raro, non solo le parti normalizzate possono
averla) può dare un risultato silenziosamente incompleto senza lanciare eccezione — vedi
`docs/formato-asm-record.md` §7.
5. **Verifica completezza** — mostra i `.asm` della distinta già in `raw/`, trascinabili su
Creo per l'esportazione STEP (Distributed Batch, manuale, fuori dall'app); "Verifica
completezza assiemi" controlla ricorsivamente sull'indice (non su `raw/`, stessa
+19
View File
@@ -68,6 +68,25 @@ public class AsmRecordReaderTests
Assert.Equal("UNI6592", membri[0].Generico);
}
[Fact]
public void Rimuove_il_suffisso_array_feature_dal_nome_istanza()
{
// Regressione: caso reale 9244250136.asm -> ConfigName con suffisso "#AF<n>" per
// componenti creati da un pattern/array feature (id 45 e id 78, entrambi variante
// "_C03" con suffisso diverso "#AF0"/"#AF1"): non un'istanza di family table vera,
// la BOM di Creo mostra il codice pulito e unisce le occorrenze duplicate.
byte[] dati = Asm(2, Bytes(
Membro(45, 2, "9244250136_C03"),
Membro(78, 2, "9244250136_C03")),
extra: Bytes(
ConfigName("9244250136_C03#AF0", idMembro: 45),
ConfigName("9244250136_C03#AF1", idMembro: 78)));
var membri = AsmRecordReader.LeggiMembri(dati);
Assert.All(membri, m => Assert.Equal("9244250136_C03", m.Nome));
}
[Fact]
public void Componente_senza_ConfigName_non_e_toccato_da_istanze_di_altri_membri()
{
+9 -1
View File
@@ -41,6 +41,14 @@ public static class AsmRecordReader
@"\x00objname\x00\xe2\xe0\x01object_type\x00[\x00-\x7f]\xe0\x0aname\x00([^\x00]+)\x00",
RegexOptions.Compiled);
// Un componente creato da un pattern/array feature ha un ConfigName con un tag interno
// "<codice>#AF<n>" (Array Feature, n = indice nel pattern), non un'istanza di family
// table vera: la BOM reale di Creo lo ignora e mostra il codice pulito - osservato su
// 9244250136.asm (4 dei suoi 8 membri, due dei quali stesso codice "_C03" con "#AF0" e
// "#AF1" diversi, che la BOM unisce in una sola riga con quantita' 2). Senza questa
// normalizzazione ogni istanza del pattern risulterebbe un componente diverso.
private static readonly Regex SuffissoArrayFeatureRegex = new(@"#AF\d+$", RegexOptions.Compiled);
// primo elemento dell'array "members": porta le chiavi per esteso.
private static readonly Regex FirstMemberRegex = new(
@"\x01id\x00" + Int + @"\xe0\x01type\x00" + Int
@@ -78,7 +86,7 @@ public static class AsmRecordReader
var istanze = new Dictionary<int, string>();
foreach (Match m in ConfigNameRegex.Matches(testo))
istanze[AsInt(m.Groups[2].Value)] = m.Groups[1].Value;
istanze[AsInt(m.Groups[2].Value)] = SuffissoArrayFeatureRegex.Replace(m.Groups[1].Value, "");
const string chiaveMembers = "\0members\0";
int start = testo.IndexOf(chiaveMembers, StringComparison.Ordinal);
+38 -1
View File
@@ -184,7 +184,7 @@ Il collegamento sta in un record di **parametro** applicato al membro:
nessun altro. L'algoritmo è quindi: leggere `members`, costruire la mappa
`id membro → nome istanza` dai record `ConfigName`
(`AsmRecordReader.LeggiMembri`, dizionario `istanze`), e sostituire il nome dove la
mappa ha una voce. Il generico resta disponibile come `Membro.File`.
mappa ha una voce. Il generico resta disponibile come `Membro.Generico`.
> **Avvertenza.** `ConfigName` sta in mezzo a parametri palesemente aziendali
> (`TIPO_PARTE`, `CLASSE_RICAR`, `GR_PRODOTTO`, `TECN_RIORDINO`), quindi
@@ -192,6 +192,25 @@ mappa ha una voce. Il generico resta disponibile come `Membro.File`.
> interna. Regge sui campioni disponibili, ma su file provenienti da
> un'altra installazione va riverificato prima di fidarsene.
### 4.0.1 Eccezione: componenti da pattern/array feature (`#AF<n>`)
Un componente creato da un **pattern/array feature** (stessa geometria ripetuta N volte da
una feature di pattern, non N placement manuali distinti) ha un `ConfigName` con un nome
nella forma `<codice>#AF<n>` (`n` = indice dell'istanza nel pattern), es.
`9244250136_C01#AF0`. **Non è un'istanza di family table**: è un tag interno di
disambiguazione fra le ripetizioni del pattern. La BOM reale di Creo lo **ignora** e mostra
il codice pulito, unendo le occorrenze duplicate in una sola riga con la quantità totale.
Osservato su `9244250136.asm`: 4 dei suoi 8 membri hanno `ConfigName` con suffisso `#AF`,
due dei quali (`_C03#AF0` e `_C03#AF1`) sono lo **stesso codice** con `n` diverso — la BOM li
mostra come un'unica riga `9244250136_C03` con quantità 2. Senza normalizzare il suffisso,
ogni istanza del pattern risulterebbe un componente distinto (e con nome sbagliato).
`AsmRecordReader` rimuove `#AF\d+$` da ogni valore di `ConfigName` prima di usarlo
(`SuffissoArrayFeatureRegex`). Scoperto durante un test end-to-end su un albero reale più
ampio degli 8 campioni originali (`9284218024b.asm`, confrontato con la sua BOM Creo di
riferimento) — non era presente in nessuno degli 8 campioni usati per il reverse engineering
iniziale.
### 4.1 Una seconda pista, che NON va usata per la BOM
Altrove nel file compaiono stringhe nella forma `istanza<generico>`:
@@ -291,6 +310,24 @@ sbagliato.
Elencato esplicitamente per non far passare per dimostrato ciò che non lo è:
- **⚠️ Family table di ASSIEMI (non solo di parti), risultato silenziosamente sbagliato.**
Non solo le parti normalizzate (§4) possono essere family table: un intero **assieme** può
esserlo. Osservato su `2546220101.asm` (dentro l'albero di `9284218024b.asm`): il file
generico ha una propria sezione `members` che elenca il contenuto di UNA rappresentazione
(nell'esempio, 3 cuscinetti + 1 sotto-assieme), ma la BOM Creo per lo stesso file mostra 4
**istanze diverse** (`2546220101_C01`.."_C04", tre "Parte" e una "Sottoassieme" - tipi
diversi fra loro!), ciascuna con un proprio contenuto potenzialmente diverso. Il nome
istanza qui **non** passa da un record `ConfigName` (verificato: nessuna occorrenza
letterale di `ConfigName` nel file) ma da un meccanismo diverso, imparentato con la
"seconda pista" del §4.1 (`\xe3<id>\xe2\x02<stringa>\0`, senza il bracket
`<generico>` in questo caso) - non decodificato. **`AsmRecordReader.LeggiMembri` non lo
rileva e non lancia eccezione**: restituisce silenziosamente il contenuto della
rappresentazione di default sotto un solo nome, invece delle 4 istanze corrette - a
differenza degli altri limiti di questa sezione, qui il fallimento non è segnalato in
nessun modo al chiamante (né un conteggio che non torna, né un'eccezione). Scoperto sullo
stesso test end-to-end del punto `#AF` sopra, mai osservato negli 8 campioni originali.
**Prima di fidarsi del parser esatto su un albero con assiemi a family table, verificare a
mano contro la BOM Creo.**
- **Stato soppresso / escluso.** Nessuna evidenza cercata o trovata. Non si sa
se un componente soppresso compaia in `members`, né con quale marcatore. Sui
campioni disponibili il totale coincideva con la BOM, il che *suggerisce* che