Copia in raw anche i modelli citati per riferimento esterno
Terzo caso di "[-4] Dati di input non completi" in Creo Distributed Batch, dopo i soppressi e le copie ambigue del commit precedente: falliva 9255728001, e solo lui fra i sei assiemi del batch. Il file in raw/ era identico bit per bit a quello sulla share, quindi non era il modello: mancava il contorno. Il suo sottoassieme _200 e' legato ad altri due (9255708017, 9255708018) da assembly feature e datum copiati, e monta una vite V6-12--_-_U5933_C il cui generico uni5933.prt non era in raw/. Nessuno dei sei file compariva in "members" ne' fra i piazzamenti: i due lettori esistenti non potevano vederli. Quei modelli stanno in una tabella a parte del .asm - array \xf2\xf7\x05 … \xf3 con elementi \xe3\xe2<tipo><nome>\0 - che ora si legge con EstraiCodiciRiferimentiEsterni, riusando la normalizzazione di #AF<n> e istanza<generico> gia' fatta per i membri. A differenza dei soppressi si ricorre dentro: Creo carica il modello citato con tutto il suo albero, e infatti 9255700182 arriva solo per quella via. La tabella contiene anche nomi di feature (MIRROR3, CHAMFER1) che dal record non si distinguono dai modelli. Non e' un problema: come per i soppressi si cerca ogni nome nell'indice e si ignora quello che non c'e'. Per lo stesso motivo questi codici restano fuori dai "non trovati", e la coda di scansione porta ora un flag che tiene fuori anche i rami raggiunti per riferimento esterno - la lista dei componenti mancanti continua a parlare solo della distinta. Documentato in docs/formato-asm-record.md §7.5. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
54f267b508
commit
13f6de6402
5 files changed
+147
-15
No files matched your search
@@ -63,12 +63,15 @@ designer visuale):
|
|||||||
sotto-assiemi/parti (`RisolviAlberoComponenti`), copiandoli anch'essi in `raw/`. Usa
|
sotto-assiemi/parti (`RisolviAlberoComponenti`), copiandoli anch'essi in `raw/`. Usa
|
||||||
`AsmRecordReader` (parser esatto dei record binari, `docs/formato-asm-record.md`) e ripiega
|
`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
|
su `AssemblyComponentScanner` (euristica testuale, `docs/formato-asm.md`) solo se il file
|
||||||
non rispetta il formato validato (`InvalidDataException`). Due punti da non confondere,
|
non rispetta il formato validato (`InvalidDataException`). Tre punti da non confondere,
|
||||||
entrambi imparati facendo fallire Creo (`docs/formato-asm-record.md` §7.3 e §7.4):
|
tutti imparati facendo fallire Creo (`docs/formato-asm-record.md` §7.3, §7.4 e §7.5):
|
||||||
i **componenti soppressi** non sono nella distinta ma il loro file serve per aprire
|
i **componenti soppressi** non sono nella distinta ma il loro file serve per aprire
|
||||||
l'assieme (`EstraiCodiciSoppressi`, copiati senza finire fra i "non trovati"); e quando lo
|
l'assieme (`EstraiCodiciSoppressi`, copiati senza finire fra i "non trovati"); i
|
||||||
stesso codice esiste in più cartelle della share — succede per il 13% dei codici — va
|
**riferimenti esterni** (assembly feature, datum copiati) citano modelli che non sono né
|
||||||
copiata la copia nella **stessa cartella dell'assieme che la referenzia**
|
membri né piazzamenti, e che Creo carica con tutto il loro albero
|
||||||
|
(`EstraiCodiciRiferimentiEsterni`, anche questi fuori dai "non trovati", ma con ricorsione);
|
||||||
|
e quando lo stesso codice esiste in più cartelle della share — succede per il 13% dei
|
||||||
|
codici — va copiata la copia nella **stessa cartella dell'assieme che la referenzia**
|
||||||
(`ScegliPerCartella`), perché il `.asm` non registra quale usa e sbagliarla impedisce a
|
(`ScegliPerCartella`), perché il `.asm` non registra quale usa e sbagliarla impedisce a
|
||||||
Creo di aprire l'assieme.
|
Creo di aprire l'assieme.
|
||||||
5. **Verifica completezza** — mostra i `.asm` della distinta già in `raw/`, trascinabili su
|
5. **Verifica completezza** — mostra i `.asm` della distinta già in `raw/`, trascinabili su
|
||||||
|
|||||||
@@ -48,6 +48,17 @@ public class AsmRecordReaderTests
|
|||||||
Bytes(0xe0, 0x01, "model_t_type", 0, 2, 0xe0, 0x0a, "model_name", 0, nome, 0,
|
Bytes(0xe0, 0x01, "model_t_type", 0, 2, 0xe0, 0x0a, "model_name", 0, nome, 0,
|
||||||
0xe0, 0x0a, "generic_name", 0, 0);
|
0xe0, 0x0a, "generic_name", 0, 0);
|
||||||
|
|
||||||
|
// array dei modelli referenziati: \xf2\xf7\x05 <elementi> \xf3, ogni elemento
|
||||||
|
// \xe3\xe2<tipo><nome>\0 - vedi docs/formato-asm-record.md §7.5
|
||||||
|
private static byte[] Riferimenti(params string[] nomi)
|
||||||
|
{
|
||||||
|
var parti = new List<object> { 0xf2, 0xf7, 0x05 };
|
||||||
|
foreach (var nome in nomi)
|
||||||
|
parti.AddRange(new object[] { 0xe3, 0xe2, 0x02, nome, 0 });
|
||||||
|
parti.Add(0xf3);
|
||||||
|
return Bytes(parti.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
private static byte[] Asm(int conteggioDichiarato, byte[] membri, byte[]? extra = null) =>
|
private static byte[] Asm(int conteggioDichiarato, byte[] membri, byte[]? extra = null) =>
|
||||||
Bytes("header ", extra ?? Array.Empty<byte>(), "\0members\0", 0xf1, 0xf8, conteggioDichiarato, membri, "#MdlStatus");
|
Bytes("header ", extra ?? Array.Empty<byte>(), "\0members\0", 0xf1, 0xf8, conteggioDichiarato, membri, "#MdlStatus");
|
||||||
|
|
||||||
@@ -229,6 +240,28 @@ public class AsmRecordReaderTests
|
|||||||
finally { File.Delete(percorso); }
|
finally { File.Delete(percorso); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Riferimenti_esterni_non_sono_fra_i_membri_e_si_risolvono_sul_generico()
|
||||||
|
{
|
||||||
|
// Un modello legato da riferimento esterno (assembly feature "#AFn", datum copiati) non
|
||||||
|
// compare ne' in "members" ne' fra i piazzamenti, ma senza il suo file Creo non apre
|
||||||
|
// l'assieme: e' il caso che faceva fallire 9255728001_200 in Distributed Batch. Per le
|
||||||
|
// istanze di family table il file su disco e' il generico fra parentesi angolari.
|
||||||
|
byte[] dati = Asm(1, Membro(1, 2, "9244206098"),
|
||||||
|
extra: Bytes(Riferimenti("9255708017#AF2<9255708017>", "9255708018"),
|
||||||
|
Riferimenti("V6-12--_-_U5933_C<UNI5933>")));
|
||||||
|
|
||||||
|
Assert.Equal(new[] { "9244206098" }, AsmRecordReader.LeggiMembri(dati).Select(m => m.Nome));
|
||||||
|
|
||||||
|
string percorso = ScriviFile(dati);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Assert.Equal(new[] { "9255708017", "9255708018", "UNI5933" },
|
||||||
|
AsmRecordReader.EstraiCodiciRiferimentiEsterni(percorso).OrderBy(c => c, StringComparer.Ordinal));
|
||||||
|
}
|
||||||
|
finally { File.Delete(percorso); }
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Sezione_members_assente_lancia_eccezione()
|
public void Sezione_members_assente_lancia_eccezione()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -225,6 +225,45 @@ public static class AsmRecordReader
|
|||||||
return soppressi;
|
return soppressi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Modelli che l'assieme cita nella tabella dei riferimenti (§7.5): oltre ai propri
|
||||||
|
/// membri comprende quelli legati da **riferimenti esterni** - assembly feature, datum
|
||||||
|
/// copiati - che non compaiono né in "members" né come piazzamento, ma che Creo carica
|
||||||
|
/// comunque per aprire l'assieme.
|
||||||
|
///
|
||||||
|
/// Terza lista dopo <see cref="EstraiCodiciReferenziati"/> ("cosa c'è in distinta") e <see
|
||||||
|
/// cref="EstraiCodiciSoppressi"/>: come quest'ultima risponde a "cosa serve su disco", non a
|
||||||
|
/// "cosa va nella BOM. L'array contiene anche nomi di feature (MIRROR3, CHAMFER1...) e non
|
||||||
|
/// solo di modelli: non si riesce a distinguerli dal record, ma non danno fastidio perché il
|
||||||
|
/// chiamante li cerca nell'indice e ignora quelli che non ci sono.</summary>
|
||||||
|
public static HashSet<string> EstraiCodiciRiferimentiEsterni(string percorsoAsm)
|
||||||
|
{
|
||||||
|
string testo = Encoding.Latin1.GetString(File.ReadAllBytes(percorsoAsm));
|
||||||
|
var codici = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
for (int i = testo.IndexOf(InizioRiferimenti, StringComparison.Ordinal); i >= 0;
|
||||||
|
i = testo.IndexOf(InizioRiferimenti, i + 1, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
int max = Math.Min(LunghezzaMassimaRiferimenti, testo.Length - i);
|
||||||
|
int fine = testo.IndexOf(FineRiferimenti, i, max);
|
||||||
|
foreach (Match m in RiferimentoRegex.Matches(testo.Substring(i, fine >= 0 ? fine - i : max)))
|
||||||
|
{
|
||||||
|
var bracket = IstanzaConGenericoRegex.Match(m.Groups[1].Value);
|
||||||
|
codici.Add(bracket.Success
|
||||||
|
? bracket.Groups[2].Value
|
||||||
|
: SuffissoArrayFeatureRegex.Replace(m.Groups[1].Value, ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return codici;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array dei modelli referenziati: "\xf2\xf7\x05" lo apre, "\xf3" lo chiude, ogni elemento e'
|
||||||
|
// "\xe3\xe2<tipo><nome>\x00" (§7.5). Il tetto di lunghezza limita il danno se il terminatore
|
||||||
|
// non si trova dove atteso: si perde qualche riferimento, non si raccoglie mezzo file.
|
||||||
|
private const string InizioRiferimenti = "ò÷\u0005ãâ";
|
||||||
|
private const char FineRiferimenti = 'ó';
|
||||||
|
private const int LunghezzaMassimaRiferimenti = 4096;
|
||||||
|
private static readonly Regex RiferimentoRegex = new(
|
||||||
|
@"\xe3\xe2[\x01\x02]([\x20-\x7e]{3,})\x00", RegexOptions.Compiled);
|
||||||
|
|
||||||
// "Componente ID 52 (9230100043.PRT)" - la stringa cambia con la localizzazione Creo del
|
// "Componente ID 52 (9230100043.PRT)" - la stringa cambia con la localizzazione Creo del
|
||||||
// file, come in AssemblyComponentScanner.HaPlacement. Minimo 3 caratteri per lo stesso
|
// file, come in AssemblyComponentScanner.HaPlacement. Minimo 3 caratteri per lo stesso
|
||||||
// motivo per cui li' i token generici partono da 3: sotto quella soglia si raccoglie
|
// motivo per cui li' i token generici partono da 3: sotto quella soglia si raccoglie
|
||||||
|
|||||||
+32
-5
@@ -875,14 +875,18 @@ public partial class Form1 : Form
|
|||||||
{
|
{
|
||||||
// Ogni assieme viaggia con la cartella della share da cui proviene: e' li' che Creo
|
// Ogni assieme viaggia con la cartella della share da cui proviene: e' li' che Creo
|
||||||
// cerca per primi i suoi componenti, quindi e' li' che li cerchiamo anche noi.
|
// cerca per primi i suoi componenti, quindi e' li' che li cerchiamo anche noi.
|
||||||
var daScansionare = new Queue<(string File, string? CartellaOrigine)>(
|
// "Esterno" marca i rami raggiunti da un riferimento esterno invece che dalla distinta:
|
||||||
asmIniziali.Select(f => (f, (string?)null)));
|
// servono su disco ma non sono componenti, quindi i loro codici mancanti non vanno fra i
|
||||||
|
// "non trovati" (sarebbero falsi allarmi, come per i soppressi).
|
||||||
|
var daScansionare = new Queue<(string File, string? CartellaOrigine, bool Esterno)>(
|
||||||
|
asmIniziali.Select(f => (f, (string?)null, false)));
|
||||||
var codiciIniziali = asmIniziali.Select(f => Path.GetFileName(f).Split('.')[0]).ToList();
|
var codiciIniziali = asmIniziali.Select(f => Path.GetFileName(f).Split('.')[0]).ToList();
|
||||||
var codiciVisti = new HashSet<string>(
|
var codiciVisti = new HashSet<string>(
|
||||||
codiciIniziali.Concat(codiciIniziali.Select(AssemblyComponentScanner.CodiceRadice)),
|
codiciIniziali.Concat(codiciIniziali.Select(AssemblyComponentScanner.CodiceRadice)),
|
||||||
StringComparer.OrdinalIgnoreCase);
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var codiciSoppressiVisti = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
var codiciSoppressiVisti = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var codiciEsterniVisti = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
var trovati = new List<(string Codice, string Tipo, string Percorso)>();
|
var trovati = new List<(string Codice, string Tipo, string Percorso)>();
|
||||||
var codiciNonTrovati = new List<string>();
|
var codiciNonTrovati = new List<string>();
|
||||||
if (daScansionare.Count == 0 || _dbPath is null)
|
if (daScansionare.Count == 0 || _dbPath is null)
|
||||||
@@ -894,7 +898,7 @@ public partial class Form1 : Form
|
|||||||
int processati = 0;
|
int processati = 0;
|
||||||
while (daScansionare.Count > 0)
|
while (daScansionare.Count > 0)
|
||||||
{
|
{
|
||||||
var (asm, cartellaOrigine) = daScansionare.Dequeue();
|
var (asm, cartellaOrigine, esterno) = daScansionare.Dequeue();
|
||||||
cartellaOrigine ??= CartellaDiOrigine(asm, candidati);
|
cartellaOrigine ??= CartellaDiOrigine(asm, candidati);
|
||||||
processati++;
|
processati++;
|
||||||
foreach (var codice in EstraiCodiciReferenziati(asm, codiciNoti.Keys))
|
foreach (var codice in EstraiCodiciReferenziati(asm, codiciNoti.Keys))
|
||||||
@@ -904,7 +908,8 @@ public partial class Form1 : Form
|
|||||||
|
|
||||||
if (!candidati.TryGetValue(codice, out var perCodice))
|
if (!candidati.TryGetValue(codice, out var perCodice))
|
||||||
{
|
{
|
||||||
codiciNonTrovati.Add(codice);
|
if (!esterno)
|
||||||
|
codiciNonTrovati.Add(codice);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -915,7 +920,7 @@ public partial class Form1 : Form
|
|||||||
if (tipo == "asm")
|
if (tipo == "asm")
|
||||||
{
|
{
|
||||||
daScansionare.Enqueue((Path.Combine(_ultimaCartellaRaw!, Path.GetFileName(percorso)),
|
daScansionare.Enqueue((Path.Combine(_ultimaCartellaRaw!, Path.GetFileName(percorso)),
|
||||||
Path.GetDirectoryName(percorso)));
|
Path.GetDirectoryName(percorso), esterno));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -936,6 +941,28 @@ public partial class Form1 : Form
|
|||||||
trovati.Add((codice, tipo, percorso));
|
trovati.Add((codice, tipo, percorso));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// I riferimenti esterni (assembly feature, datum copiati verso un altro modello) non
|
||||||
|
// sono componenti e non compaiono fra i piazzamenti, ma Creo carica il modello citato
|
||||||
|
// per aprire l'assieme - e con lui i *suoi* componenti, quindi qui, a differenza dei
|
||||||
|
// soppressi, si ricorre dentro. Vedi docs/formato-asm-record.md §7.5.
|
||||||
|
foreach (var codice in AsmRecordReader.EstraiCodiciRiferimentiEsterni(asm))
|
||||||
|
{
|
||||||
|
if (!codiciEsterniVisti.Add(codice) || codiciVisti.Contains(codice))
|
||||||
|
continue;
|
||||||
|
if (!candidati.TryGetValue(codice, out var perCodice))
|
||||||
|
continue;
|
||||||
|
foreach (var (tipo, percorso) in ScegliPerCartella(perCodice, cartellaOrigine))
|
||||||
|
{
|
||||||
|
CopiaFileTrovati(new[] { percorso }, _ultimaCartellaRaw!);
|
||||||
|
trovati.Add((codice, tipo, percorso));
|
||||||
|
if (tipo == "asm")
|
||||||
|
{
|
||||||
|
daScansionare.Enqueue((Path.Combine(_ultimaCartellaRaw!, Path.GetFileName(percorso)),
|
||||||
|
Path.GetDirectoryName(percorso), true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
progress?.Report((processati, processati + daScansionare.Count));
|
progress?.Report((processati, processati + daScansionare.Count));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -495,12 +495,12 @@ seconda è più lunga.
|
|||||||
| Lista | Serve per | Si ottiene con |
|
| Lista | Serve per | Si ottiene con |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Componenti della distinta | BOM, verifica completezza | `LeggiMembri` / `EstraiCodiciReferenziati` (sezione `members`) |
|
| Componenti della distinta | BOM, verifica completezza | `LeggiMembri` / `EstraiCodiciReferenziati` (sezione `members`) |
|
||||||
| File necessari al caricamento | popolare `raw/` per l'export STEP | i precedenti **più** `EstraiCodiciSoppressi` |
|
| File necessari al caricamento | popolare `raw/` per l'export STEP | i precedenti **più** `EstraiCodiciSoppressi` e `EstraiCodiciRiferimentiEsterni` (§7.5) |
|
||||||
|
|
||||||
La differenza sono i **componenti soppressi** (§7.1): fuori dalla BOM, ma senza il loro file
|
La differenza sono i **componenti soppressi** (§7.1) e i **riferimenti esterni** (§7.5): fuori
|
||||||
Creo non apre l'assieme. Chi popola `raw/` deve quindi usare l'unione; chi genera la BOM o
|
dalla BOM, ma senza il loro file Creo non apre l'assieme. Chi popola `raw/` deve quindi usare
|
||||||
verifica la completezza deve usare **solo** i membri, altrimenti i soppressi diventano falsi
|
l'unione; chi genera la BOM o verifica la completezza deve usare **solo** i membri, altrimenti
|
||||||
"componenti mancanti".
|
quelle voci diventano falsi "componenti mancanti".
|
||||||
|
|
||||||
Il sintomo, se si sbaglia, non assomiglia per niente alla causa: Creo Distributed Batch
|
Il sintomo, se si sbaglia, non assomiglia per niente alla causa: Creo Distributed Batch
|
||||||
fallisce con `Carica modello in Creo Pro | Non riuscito | [-4] Errore: Dati di input non
|
fallisce con `Carica modello in Creo Pro | Non riuscito | [-4] Errore: Dati di input non
|
||||||
@@ -539,6 +539,36 @@ Conseguenze per il codice:
|
|||||||
versione, una del 2011 in una cartella di file sciolti e una del 2026 accanto all'assieme,
|
versione, una del 2011 in una cartella di file sciolti e una del 2026 accanto all'assieme,
|
||||||
e in `raw/` finiva quella del 2011.
|
e in `raw/` finiva quella del 2011.
|
||||||
|
|
||||||
|
### 7.5 Riferimenti esterni: la tabella dei modelli citati
|
||||||
|
|
||||||
|
Terzo modo in cui un `.asm` dipende da un file che non è fra i suoi membri, dopo i soppressi
|
||||||
|
(§7.1) e le copie ambigue (§7.4). Un assieme che porta **assembly feature** o **datum copiati**
|
||||||
|
da un altro modello lo cita in una tabella a parte, che non è `members` e non lascia una stringa
|
||||||
|
`Componente ID …`: i due lettori esistenti non la vedevano.
|
||||||
|
|
||||||
|
Struttura: un array aperto da `\xf2\xf7\x05` e chiuso da `\xf3`, con elementi
|
||||||
|
`\xe3\xe2<tipo><nome>\x00`. Il nome può portare il suffisso `#AF<n>` di §4.0.1 e/o la notazione
|
||||||
|
`istanza<generico>` di §4.0.2 - il file su disco è il generico:
|
||||||
|
|
||||||
|
```
|
||||||
|
f2 f7 05 e3 e2 01 "9255708017#AF2<9255708017>" 00 ... -> 9255708017
|
||||||
|
e3 e2 02 "9255700181" 00 ... -> 9255700181
|
||||||
|
f3
|
||||||
|
```
|
||||||
|
|
||||||
|
Va letta con `EstraiCodiciRiferimentiEsterni`, e - a differenza dei soppressi - **si ricorre
|
||||||
|
dentro**: Creo carica il modello citato con tutti i suoi componenti, che quindi servono anch'essi
|
||||||
|
in `raw/`. La tabella contiene anche nomi di *feature* (`MIRROR3`, `CHAMFER1`,
|
||||||
|
`M10_TAPPED_HOLE1`), indistinguibili dai modelli guardando il record: non è un problema perché
|
||||||
|
chi la usa cerca ogni nome nell'indice e ignora quelli che non ci sono - per lo stesso motivo
|
||||||
|
questi codici non devono finire fra i "non trovati".
|
||||||
|
|
||||||
|
Il caso reale: `9255728001` falliva in Distributed Batch con lo stesso `[-4] Dati di input non
|
||||||
|
completi` di §7.3 perché il suo sottoassieme `9255728001_200` referenzia per assembly feature
|
||||||
|
`9255708017` e `9255708018` (e con loro `9255700181/182/183`), e monta una vite
|
||||||
|
`V6-12--_-_U5933_C<UNI5933>` il cui generico `uni5933.prt` non era in `raw/`. Nessuno dei sei
|
||||||
|
file compariva in `members` né fra i piazzamenti.
|
||||||
|
|
||||||
## 8. Uso
|
## 8. Uso
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
|
|||||||
Reference in new issue
Block a user