Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d7ace07e2 | ||
|
|
1eac3ccaa7 | ||
|
|
5b11b3e8ca | ||
|
|
d5fb20a34e | ||
|
|
6d1e36a2ae | ||
|
|
ccfa00ff52 | ||
|
|
5fac2edf52 | ||
|
|
4ae3d0e087 | ||
|
|
64ff1d8770 | ||
|
|
57020f9964 | ||
|
|
206db85e12 | ||
|
|
d81fede4cc | ||
|
|
948937ba75 |
No files matched your search
@@ -37,3 +37,10 @@ publish/
|
||||
|
||||
# Output di scripts/ExtractCreoStrings.cs
|
||||
/BOM.txt
|
||||
|
||||
# Impostazioni locali dell'editor
|
||||
.vscode/
|
||||
|
||||
# Setup generato da Inno Setup (installer/PrepStep.iss)
|
||||
installer/Output/
|
||||
__pycache__/
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
// Regole base; disattivate quelle che non servono a prosa italiana con tabelle larghe
|
||||
"config": {
|
||||
"default": true,
|
||||
"MD013": false, // lunghezza riga
|
||||
"MD033": false, // HTML inline
|
||||
"MD024": { "siblings_only": true }, // CHANGELOG ripete "Aggiunto/Modificato"
|
||||
"MD036": false,
|
||||
"MD040": false, // dump esadecimali/testuali senza linguaggio
|
||||
"MD060": false, // stile colonne tabelle
|
||||
"MD041": false
|
||||
},
|
||||
"globs": ["**/*.md"],
|
||||
"ignores": ["node_modules", "raw", "step", "**/bin", "**/obj"]
|
||||
}
|
||||
@@ -5,6 +5,102 @@ Tutte le modifiche rilevanti di questo progetto sono documentate in questo file.
|
||||
Il formato segue [Keep a Changelog](https://keepachangelog.com/it/1.1.0/), e il progetto
|
||||
aderisce a [Semantic Versioning](https://semver.org/lang/it/).
|
||||
|
||||
## [Non rilasciato]
|
||||
|
||||
### Aggiunto
|
||||
|
||||
- `docs/formato-prt-feature.md`: decodifica (da reverse engineering) dell'albero delle feature di un `.prt`.
|
||||
- `docs/formato-prt-geometria.md`: numeri, quote e geometria degli schizzi di un `.prt`, con la tabella di cosa resta da decodificare.
|
||||
- `docs/formato-psb.md` e `scripts/psb/`: formato di serializzazione completo delle sezioni dei file Creo (reverse engineering su `9253000117a.prt.35`) e parser Python di riferimento.
|
||||
|
||||
## [1.1.0] - 2026-10-07
|
||||
|
||||
### Aggiunto
|
||||
|
||||
- Fase 2: modalità "Lista personalizzata" per inserire i codici a mano, uno alla volta, in alternativa al file del gestionale.
|
||||
- Docs-as-code: `scripts/CheckDocs.cs`, markdownlint, `docs/README.md` (indice Diátaxis e fonte unica).
|
||||
- Nuovi doc: `tutorial-primo-giro`, `risoluzione-problemi`, `sviluppo`, `rilascio`, `glossario` e registro delle decisioni `docs/decisioni/` (6 ADR).
|
||||
|
||||
### Modificato
|
||||
|
||||
- Il README rimanda a `docs/sviluppo.md` e `docs/rilascio.md` per build, test e setup, invece di duplicarli.
|
||||
- Fase 2: dopo "Verifica presenza" la riga selezionata mantiene il verde/rosso dell'esito invece di diventare blu.
|
||||
|
||||
## [1.0.0] - 2026-09-23
|
||||
|
||||
Prima versione distribuita agli utenti tramite setup di installazione.
|
||||
|
||||
### Aggiunto
|
||||
|
||||
- Setup di installazione per-utente (`installer/PrepStep.iss`, Inno Setup 6):
|
||||
`PrepStep-Setup-<versione>.exe` installa in `%LOCALAPPDATA%\Programs\PrepStep` senza diritti
|
||||
di amministratore, crea nel menu Start i collegamenti al programma e al disinstallatore
|
||||
(quello sul desktop è facoltativo) e registra l'app in "Impostazioni → App installate".
|
||||
- Il setup, se l'app è già installata, chiede se aggiornarla/reinstallarla, disinstallarla o
|
||||
uscire; con `/VERYSILENT` aggiorna senza chiedere. Aggiornando, database e impostazioni
|
||||
restano.
|
||||
- "File → Elimina CSV delle distinte...": elimina i CSV generati dall'import, che altrimenti si
|
||||
accumulano nella cartella di lavoro (uno per distinta). Chiede conferma mostrando l'elenco
|
||||
dei file; non tocca gli `.xls` del gestionale né `raw/`/`step/`.
|
||||
- Guida utente e guida in-app: nuova sezione "Dove finiscono i file" (database, CSV, `raw/`,
|
||||
`step/`, comandi di pulizia, come aprire la cartella nascosta in `AppData`, file che restano
|
||||
dopo la disinstallazione).
|
||||
- Licenza proprietaria (`LICENSE`, © 2026 Davide Grilli, tutti i diritti riservati), anche nei
|
||||
metadati dell'exe e del setup.
|
||||
- README: sequenza completa di comandi per generare il setup a ogni release.
|
||||
|
||||
### Modificato
|
||||
|
||||
- Nome dell'app mostrato all'utente: da "Cevolani StepPrep" a "PrepStep - Cevolani" (titolo
|
||||
finestra, guida, setup, menu Start, "App installate").
|
||||
- Il database è sempre `cad_index.db` nella cartella di installazione: all'avvio si carica
|
||||
solo quello e "Aggiorna indice" lo crea lì se manca. "Apri DB..." carica un altro database
|
||||
solo per la sessione corrente.
|
||||
- La cartella di lavoro predefinita (se non ne è stata scelta una con "File → Scegli cartella
|
||||
di lavoro...") è la cartella di installazione, invece della cartella del database.
|
||||
- Guida utente: fase 1 riscritta per il database fisso; tolta la sezione di installazione (chi
|
||||
legge la guida ha già il programma; le istruzioni restano nel `README.md`).
|
||||
- Finestra Guida più grande (1000×680) e testo non più "da editor": niente cursore
|
||||
lampeggiante, niente cursore a "I" e niente selezione col mouse.
|
||||
- Documentazione riallineata al codice: nomi delle fasi come nella UI ("5. Verifica
|
||||
completezza"), "File → Nuovo" che non cancella file su disco, mappa dei file e test nel
|
||||
README, riferimenti a metodi non più esistenti (`AssiemeCompleto`) e ruolo di
|
||||
`CaricaCodiciNoti`/`CaricaCandidati` in fase 4.
|
||||
|
||||
### Rimosso
|
||||
|
||||
- Memoria dell'ultimo database aperto (`%AppData%\PrepStep\ultimo_db.txt`) e ricerca di
|
||||
`cad_index.db` nella cartella corrente.
|
||||
|
||||
## [0.9.0] - 2026-09-23
|
||||
|
||||
### Aggiunto
|
||||
|
||||
- Guida utente `docs/guida-utente.md`: tutti i passi, dalla distinta esportata dal gestionale
|
||||
fino all'esportazione STEP con Creo Distributed Batch.
|
||||
- Fase 2: bottone "Verifica presenza", visibile solo dopo un import riuscito; è lui (non il
|
||||
solo import) a sbloccare "Avanti". Etichette numerate accanto ai bottoni.
|
||||
- Dialogo "Componenti referenziati trovati": bottone "Chiudi" (anche con Esc) e icona dell'app.
|
||||
|
||||
### Modificato
|
||||
|
||||
- "Scegli cartella di lavoro..." e "Pulisci raw e step..." spostati dalla fase 1 al menu File,
|
||||
raggiungibili in qualunque momento; "File → Nuova estrazione" rinominato in "Nuovo".
|
||||
- Fase 1 ridisegnata in una card di stato "Database" con indicatore verde/rosso; rimossa la card
|
||||
"Cartella di lavoro".
|
||||
- Barra Avanti/Indietro trasformata in floatbar staccata dai bordi, con bottoni di dimensioni e
|
||||
stile uniformi (riusata anche nel dialogo dei componenti referenziati).
|
||||
- Finestra Guida ridisegnata: sezioni con titolo, passi numerati e callout "Nota"/"Attenzione"
|
||||
colorati; sidebar con selezione nel blu dell'app.
|
||||
- Testi delle fasi 3-4 semplificati.
|
||||
|
||||
### Corretto
|
||||
|
||||
- "Avanti" restava disabilitato in fase 1 all'avvio anche con DB e cartella di lavoro già
|
||||
caricati.
|
||||
- Guida in-app e README citavano ancora "Verifica completezza assiemi" e "Cambia cartella...",
|
||||
rimossi da tempo.
|
||||
|
||||
## [0.1.0] - 2026-09-22
|
||||
|
||||
### Aggiunto
|
||||
|
||||
@@ -5,9 +5,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
## Cos'è questo progetto
|
||||
|
||||
`PrepStep` è il nome tecnico del progetto (cartella, namespace, csproj, eseguibile
|
||||
`PrepStep.exe`); il nome mostrato all'utente finale è **Cevolani StepPrep** (`<Product>`/
|
||||
`PrepStep.exe`); il nome mostrato all'utente finale è **PrepStep - Cevolani** (`<Product>`/
|
||||
`<AssemblyTitle>` in `PrepStep/PrepStep.csproj`, usato nel titolo della finestra insieme
|
||||
alla versione, `<Version>`, attualmente `0.1.0`). È un'app Windows Forms (.NET 10, WinForms) che indicizza e permette di
|
||||
alla versione, `<Version>`, attualmente `1.0.0`). È un'app Windows Forms (.NET 10, WinForms) che indicizza e permette di
|
||||
cercare i file CAD Pro-ENGINEER/Creo contenuti in tutte le cartelle di primo livello
|
||||
di un disco/share di rete (radice di default `X:\`), salvando i metadati in un DB
|
||||
SQLite locale (`cad_index.db`). Il flusso a valle (fasi 2-5, vedi sotto) prepara i file
|
||||
@@ -29,6 +29,13 @@ Pubblicazione come eseguibile standalone (single-file, self-contained, Windows x
|
||||
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o publish
|
||||
```
|
||||
|
||||
Setup di installazione per-utente (`%LOCALAPPDATA%\Programs\PrepStep`, niente admin), dalla
|
||||
root del repo dopo il publish — dettagli in `README.md` e `docs/architettura.md`:
|
||||
|
||||
```powershell
|
||||
& "$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe" installer\PrepStep.iss
|
||||
```
|
||||
|
||||
`PrepStep.Tests/` (xUnit) contiene unit e integration test — vedi
|
||||
`dotnet test` da dentro quella cartella. Riferimento al progetto principale
|
||||
via `ProjectReference` + `InternalsVisibleTo` (in `PrepStep.csproj`), così
|
||||
@@ -51,12 +58,19 @@ Dove scrivere cosa:
|
||||
|---|---|
|
||||
| `CLAUDE.md` (questo file) | il flusso essenziale e i punti che si sbaglierebbero senza saperli |
|
||||
| [`docs/architettura.md`](docs/architettura.md) | il processo delle 5 fasi e la mappa file-per-file |
|
||||
| [`docs/guida-utente.md`](docs/guida-utente.md) | cosa clicca l'utente, passo per passo, fino a Creo Distributed Batch |
|
||||
| [`docs/formato-asm-record.md`](docs/formato-asm-record.md) | parser esatto dei record `.asm`, con §7 per i casi limite |
|
||||
| [`docs/formato-asm.md`](docs/formato-asm.md) | euristica testuale di fallback |
|
||||
| [`docs/db-cad-index.md`](docs/db-cad-index.md) | schema e vista di `cad_index.db` |
|
||||
| [`docs/copia-raw-parallela.md`](docs/copia-raw-parallela.md) | parallelizzazione della copia in `raw/` |
|
||||
| [`docs/README.md`](docs/README.md) | indice Diátaxis di tutti i doc (tutorial, how-to, reference, decisioni) e regola di fonte unica |
|
||||
| [`CHANGELOG.md`](CHANGELOG.md) | storico delle versioni (formato Keep a Changelog); ogni release che cambia `<Version>` nel csproj aggiunge qui la sua voce |
|
||||
|
||||
I doc sono trattati come codice: stanno in git e si verificano con
|
||||
`dotnet run scripts/CheckDocs.cs` (link, versione/CHANGELOG, copertura di `architettura.md`) e
|
||||
`npx markdownlint-cli2` (config `.markdownlint-cli2.jsonc`) — da lanciare prima di ogni commit che
|
||||
tocca i doc. Organizzazione (Diátaxis) e regola di fonte unica: [`docs/README.md`](docs/README.md).
|
||||
|
||||
Quando il cambiamento nasce da un fallimento reale di Creo, scrivere anche **il sintomo**: i
|
||||
messaggi di Creo non nominano il file o la causa, e senza quel ponte la prossima volta si
|
||||
riparte da zero (i §7.3/§7.4/§7.5 di `formato-asm-record.md` sono l'esempio del formato
|
||||
@@ -64,7 +78,8 @@ atteso).
|
||||
|
||||
## Architettura
|
||||
|
||||
Il programma è tutto in `PrepStep/` (nessun altro progetto nella solution). Dettaglio
|
||||
Il programma è tutto in `PrepStep/` (accanto ci sono solo i test `PrepStep.Tests/`, gli
|
||||
script `scripts/` e il setup `installer/`). Dettaglio
|
||||
completo file-per-file in [`docs/architettura.md`](docs/architettura.md); qui solo il flusso
|
||||
che conta per capire il prodotto.
|
||||
|
||||
@@ -75,10 +90,11 @@ designer visuale):
|
||||
1. **Database** — carica/aggiorna `cad_index.db` (`Indexer.ScanAsync`, radice disco di
|
||||
default `X:\` hardcoded in `RadiceDiscoDefault`); un pallino verde/rosso accanto al titolo
|
||||
indica se un DB è caricato. La **cartella di lavoro** (destinazione di CSV/`raw/`/`step/`)
|
||||
non è più mostrata qui — proposta dall'ultima usata o dalla cartella del DB se non c'è
|
||||
ancora nulla di salvato, cambiabile in qualunque momento da "File → Scegli cartella di
|
||||
lavoro..." — vedi `docs/db-cad-index.md` per il dettaglio di schema e risoluzione del
|
||||
percorso DB.
|
||||
non è più mostrata qui — proposta dall'ultima usata o dalla cartella di installazione se
|
||||
non c'è ancora nulla di salvato, cambiabile in qualunque momento da "File → Scegli cartella di
|
||||
lavoro..." — vedi `docs/db-cad-index.md` per il dettaglio di schema e percorso del DB
|
||||
(sempre `cad_index.db` nella cartella di installazione; "Apri DB..." vale solo per la
|
||||
sessione).
|
||||
2. **Importa Distinta Base** — `DistintaBaseImporter.ConvertiInCsv` genera il CSV
|
||||
componente;descrizione nella cartella di lavoro (da file scelto o trascinato); se il file
|
||||
segue la convenzione del gestionale (`DBP_<codice>_<timestamp>`), aggiunge l'assieme
|
||||
@@ -113,7 +129,8 @@ per ripartire con una nuova distinta, senza toccare DB/cartella di lavoro della
|
||||
volutamente alla fase 2, non alla 1: quei dati restano quelli già caricati); "File → Scegli
|
||||
cartella di lavoro..." cambia la cartella di CSV/`raw/`/`step/` in qualunque momento; "File →
|
||||
Pulisci raw e step..." svuota le cartelle `raw/`/`step/` nella cartella di lavoro (non tocca
|
||||
l'indice); "Aiuto → Guida" (F1) apre una sintesi interattiva delle 5 fasi (`GuidaForm.cs`).
|
||||
l'indice); "File → Elimina CSV delle distinte..." elimina i `*.csv` generati in fase 2, che
|
||||
altrimenti si accumulano uno per distinta nella cartella di lavoro; "Aiuto → Guida" (F1) apre una sintesi interattiva delle 5 fasi (`GuidaForm.cs`).
|
||||
|
||||
## Script standalone (`scripts/`)
|
||||
|
||||
@@ -130,5 +147,5 @@ intercambiabili:
|
||||
- `ExtractCreoStrings.cs`/`BomViaDb.cs` usano `AssemblyComponentScanner` (euristica testuale,
|
||||
`docs/formato-asm.md`) e risolvono i sottoassiemi contro `cad_index.db`.
|
||||
- `CreoBom.cs` usa `AsmRecordReader` (parser esatto dei record binari,
|
||||
`docs/formato-asm-record.md`, condiviso con la GUI in fase 4/5) e risolve i sottoassiemi
|
||||
`docs/formato-asm-record.md`, condiviso con la GUI in fase 4) e risolve i sottoassiemi
|
||||
cercandoli in cartelle locali (`-p`), non contro `cad_index.db`.
|
||||
@@ -0,0 +1,17 @@
|
||||
Copyright (c) 2026 Davide Grilli. Tutti i diritti riservati.
|
||||
|
||||
Questo software, il codice sorgente, la documentazione e i file correlati ("il Software")
|
||||
sono di proprietà esclusiva di Davide Grilli.
|
||||
|
||||
Senza previa autorizzazione scritta del titolare è vietato copiare, modificare, unire,
|
||||
pubblicare, distribuire, concedere in sublicenza o vendere copie del Software, in tutto o
|
||||
in parte, e ricavarne opere derivate.
|
||||
|
||||
L'uso del Software è consentito solo alle persone e alle organizzazioni espressamente
|
||||
autorizzate dal titolare, e nei limiti di tale autorizzazione.
|
||||
|
||||
IL SOFTWARE È FORNITO "COSÌ COM'È", SENZA GARANZIE DI ALCUN TIPO, ESPRESSE O IMPLICITE,
|
||||
INCLUSE, SENZA LIMITAZIONE, LE GARANZIE DI COMMERCIABILITÀ, IDONEITÀ A UNO SCOPO
|
||||
PARTICOLARE E NON VIOLAZIONE. IN NESSUN CASO IL TITOLARE SARÀ RESPONSABILE PER QUALSIASI
|
||||
RECLAMO, DANNO O ALTRA RESPONSABILITÀ DERIVANTE DALL'USO DEL SOFTWARE O IN RELAZIONE AD
|
||||
ESSO.
|
||||
+180
-53
@@ -12,6 +12,11 @@ public partial class Form1 : Form
|
||||
private readonly Button _btnTrovaPrt = new();
|
||||
private readonly Button _btnVerifica = new();
|
||||
private readonly FlowLayoutPanel _rigaVerifica = new();
|
||||
private readonly FlowLayoutPanel _rigaFile = new();
|
||||
private readonly FlowLayoutPanel _rigaCustom = new();
|
||||
private readonly RadioButton _rbFile = new() { Text = "Da file del gestionale", Checked = true, AutoSize = true };
|
||||
private readonly RadioButton _rbCustom = new() { Text = "Lista personalizzata", AutoSize = true };
|
||||
private readonly TextBox _txtCodice = new() { Width = 220, Margin = new Padding(0, 3, 8, 0) };
|
||||
private readonly ListView _lstAnteprimaAsm = new();
|
||||
private readonly Label _lblStatus = new();
|
||||
private readonly Label _lblDb = new();
|
||||
@@ -25,6 +30,7 @@ public partial class Form1 : Form
|
||||
private readonly MenuStrip _menu = new();
|
||||
private readonly ToolStripMenuItem _mnuNuovaEstrazione = new("Nuovo");
|
||||
private readonly ToolStripMenuItem _mnuPulisci = new("Pulisci raw e step...");
|
||||
private readonly ToolStripMenuItem _mnuEliminaCsv = new("Elimina CSV delle distinte...");
|
||||
private readonly ToolStripMenuItem _mnuCambiaCartella = new("Scegli cartella di lavoro...");
|
||||
|
||||
// Wizard: una fase alla volta occupa tutta la finestra, si avanza solo quando quella
|
||||
@@ -49,7 +55,7 @@ public partial class Form1 : Form
|
||||
|
||||
private string? _dbPath;
|
||||
private string? _cartellaLavoro;
|
||||
private string? _percorsoInputCorrente;
|
||||
private bool _ripristinoModalita;
|
||||
private List<(string Componente, string Descrizione)>? _righeCorrenti;
|
||||
private Dictionary<string, List<string>>? _ultimaVerifica;
|
||||
private string? _ultimaCartellaRaw;
|
||||
@@ -74,7 +80,7 @@ public partial class Form1 : Form
|
||||
AllowDrop = true;
|
||||
// Versione letta dall'assembly (<Version> nel csproj) invece che duplicata qui a mano.
|
||||
var versione = typeof(Form1).Assembly.GetName().Version;
|
||||
Text = $"Cevolani StepPrep {versione?.ToString(3)}";
|
||||
Text = $"PrepStep - Cevolani {versione?.ToString(3)}";
|
||||
MinimumSize = new Size(760, 480);
|
||||
Font = new Font("Segoe UI", 9.5f);
|
||||
// Stessa icona di ApplicationIcon nel csproj: la si rilegge dall'exe compilato invece
|
||||
@@ -150,6 +156,9 @@ public partial class Form1 : Form
|
||||
_mnuPulisci.Click += (_, _) => PulisciRawEStep();
|
||||
mnuFile.DropDownItems.Add(_mnuPulisci);
|
||||
|
||||
_mnuEliminaCsv.Click += (_, _) => EliminaCsv();
|
||||
mnuFile.DropDownItems.Add(_mnuEliminaCsv);
|
||||
|
||||
var mnuAiuto = new ToolStripMenuItem("Aiuto");
|
||||
var mnuGuida = new ToolStripMenuItem("Guida") { ShortcutKeys = Keys.F1 };
|
||||
mnuGuida.Click += (_, _) => new GuidaForm().ShowDialog(this);
|
||||
@@ -262,7 +271,12 @@ public partial class Form1 : Form
|
||||
return;
|
||||
}
|
||||
|
||||
_percorsoInputCorrente = null;
|
||||
AzzeraDistinta();
|
||||
VaiAllaFase(1);
|
||||
}
|
||||
|
||||
private void AzzeraDistinta()
|
||||
{
|
||||
_righeCorrenti = null;
|
||||
_ultimaCartellaRaw = null;
|
||||
_ultimaVerifica = null;
|
||||
@@ -271,8 +285,68 @@ public partial class Form1 : Form
|
||||
_lstAnteprimaAsm.Items.Clear();
|
||||
ImpostaStatoImport("", Color.Black);
|
||||
_rigaVerifica.Visible = false;
|
||||
}
|
||||
|
||||
VaiAllaFase(1);
|
||||
private void CambiaModalitaDistinta()
|
||||
{
|
||||
if (_ripristinoModalita)
|
||||
return;
|
||||
if (_gridImport.Rows.Count > 0 && MessageBox.Show(this,
|
||||
"Cambiando modalità la distinta corrente viene azzerata. Continuare?", "Distinta",
|
||||
MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
|
||||
{
|
||||
_ripristinoModalita = true;
|
||||
(_rbCustom.Checked ? _rbFile : _rbCustom).Checked = true;
|
||||
_ripristinoModalita = false;
|
||||
return;
|
||||
}
|
||||
AzzeraDistinta();
|
||||
_rigaFile.Visible = _rbFile.Checked;
|
||||
_rigaCustom.Visible = _rbCustom.Checked;
|
||||
AggiornaStatoAvanti();
|
||||
}
|
||||
|
||||
private void AggiungiCodice()
|
||||
{
|
||||
string codice = _txtCodice.Text.Trim();
|
||||
if (codice.Length == 0)
|
||||
return;
|
||||
_righeCorrenti ??= new();
|
||||
if (_righeCorrenti.Any(r => string.Equals(r.Componente, codice, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
_txtCodice.SelectAll();
|
||||
return;
|
||||
}
|
||||
_righeCorrenti.Add((codice, ""));
|
||||
_gridImport.Rows.Add(codice, "");
|
||||
InvalidaVerifica();
|
||||
_txtCodice.Clear();
|
||||
_txtCodice.Focus();
|
||||
}
|
||||
|
||||
private void RimuoviCodiceSelezionato()
|
||||
{
|
||||
if (_gridImport.CurrentRow is not { } riga || _righeCorrenti is null)
|
||||
return;
|
||||
_righeCorrenti.RemoveAt(riga.Index); // griglia e lista hanno lo stesso ordine
|
||||
_gridImport.Rows.Remove(riga);
|
||||
InvalidaVerifica();
|
||||
}
|
||||
|
||||
/// <summary>La lista è cambiata: la verifica presenza e il raw già creato non valgono più.</summary>
|
||||
private void InvalidaVerifica()
|
||||
{
|
||||
_ultimaVerifica = null;
|
||||
_ultimaCartellaRaw = null;
|
||||
_gridRaw.Rows.Clear();
|
||||
foreach (DataGridViewRow r in _gridImport.Rows)
|
||||
{
|
||||
r.DefaultCellStyle.BackColor = Color.Empty;
|
||||
r.DefaultCellStyle.SelectionBackColor = Color.Empty;
|
||||
r.DefaultCellStyle.SelectionForeColor = Color.Empty;
|
||||
}
|
||||
_rigaVerifica.Visible = _righeCorrenti is { Count: > 0 };
|
||||
AggiornaStatoAvanti();
|
||||
}
|
||||
|
||||
private Panel BuildFaseDatabase()
|
||||
@@ -396,12 +470,51 @@ public partial class Form1 : Form
|
||||
};
|
||||
contenuto.Controls.Add(lblIstruzioni);
|
||||
|
||||
var rigaImporta = new FlowLayoutPanel
|
||||
var rigaModalita = new FlowLayoutPanel
|
||||
{
|
||||
AutoSize = true,
|
||||
FlowDirection = FlowDirection.LeftToRight,
|
||||
Margin = new Padding(0, 0, 0, 8),
|
||||
};
|
||||
rigaModalita.Controls.Add(_rbFile);
|
||||
rigaModalita.Controls.Add(_rbCustom);
|
||||
_rbCustom.CheckedChanged += (_, _) => CambiaModalitaDistinta();
|
||||
contenuto.Controls.Add(rigaModalita);
|
||||
|
||||
_rigaCustom.AutoSize = true;
|
||||
_rigaCustom.FlowDirection = FlowDirection.LeftToRight;
|
||||
_rigaCustom.Margin = new Padding(0, 0, 0, 8);
|
||||
_rigaCustom.Visible = false;
|
||||
var btnAggiungi = new Button { Text = "Aggiungi", Margin = new Padding(0, 0, 8, 0) };
|
||||
UiStile.Secondario(btnAggiungi);
|
||||
btnAggiungi.Click += (_, _) => AggiungiCodice();
|
||||
_txtCodice.KeyDown += (_, e) =>
|
||||
{
|
||||
if (e.KeyCode != Keys.Enter)
|
||||
return;
|
||||
e.SuppressKeyPress = true;
|
||||
AggiungiCodice();
|
||||
};
|
||||
_rigaCustom.Controls.Add(_txtCodice);
|
||||
_rigaCustom.Controls.Add(btnAggiungi);
|
||||
_rigaCustom.Controls.Add(new Label
|
||||
{
|
||||
AutoSize = true,
|
||||
ForeColor = SystemColors.GrayText,
|
||||
Margin = new Padding(0, 9, 0, 0),
|
||||
Text = "Digita un codice e premi Invio (Canc sulla riga per toglierlo)",
|
||||
});
|
||||
contenuto.Controls.Add(_rigaCustom);
|
||||
_gridImport.KeyDown += (_, e) =>
|
||||
{
|
||||
if (e.KeyCode == Keys.Delete && _rbCustom.Checked)
|
||||
RimuoviCodiceSelezionato();
|
||||
};
|
||||
|
||||
var rigaImporta = _rigaFile;
|
||||
rigaImporta.AutoSize = true;
|
||||
rigaImporta.FlowDirection = FlowDirection.LeftToRight;
|
||||
rigaImporta.Margin = new Padding(0, 0, 0, 8);
|
||||
var btnSfoglia = new Button
|
||||
{
|
||||
Text = "Scegli file distinta...",
|
||||
@@ -642,10 +755,15 @@ public partial class Form1 : Form
|
||||
|
||||
private void ImportaDistinta(string percorso)
|
||||
{
|
||||
if (_rbCustom.Checked)
|
||||
{
|
||||
_rbFile.Checked = true; // chiede conferma se c'è una lista; se rifiuta resta in custom
|
||||
if (_rbCustom.Checked)
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
var (_, righe) = DistintaBaseImporter.ConvertiInCsv(percorso, _cartellaLavoro!);
|
||||
_percorsoInputCorrente = percorso;
|
||||
_righeCorrenti = righe;
|
||||
|
||||
_gridImport.Rows.Clear();
|
||||
@@ -662,7 +780,6 @@ public partial class Form1 : Form
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_percorsoInputCorrente = null;
|
||||
_righeCorrenti = null;
|
||||
_gridImport.Rows.Clear();
|
||||
ImpostaStatoImport($"Errore durante la conversione: {ex.Message}", Color.DarkRed);
|
||||
@@ -690,16 +807,20 @@ public partial class Form1 : Form
|
||||
foreach (DataGridViewRow riga in _gridImport.Rows)
|
||||
{
|
||||
string componente = (string)riga.Cells["componente"].Value!;
|
||||
riga.DefaultCellStyle.BackColor = trovati.ContainsKey(componente)
|
||||
var colore = trovati.ContainsKey(componente)
|
||||
? Color.FromArgb(198, 239, 206)
|
||||
: Color.FromArgb(255, 199, 206);
|
||||
// la selezione mantiene il colore verde/rosso, altrimenti il blu nasconde l'esito
|
||||
riga.DefaultCellStyle.BackColor = colore;
|
||||
riga.DefaultCellStyle.SelectionBackColor = colore;
|
||||
riga.DefaultCellStyle.SelectionForeColor = Color.Black;
|
||||
}
|
||||
AggiornaStatoAvanti();
|
||||
}
|
||||
|
||||
private void CreaCartellaRaw()
|
||||
{
|
||||
if (_percorsoInputCorrente is null || _righeCorrenti is null)
|
||||
if (_righeCorrenti is null)
|
||||
return;
|
||||
|
||||
// L'assieme generale è già una riga di _righeCorrenti (aggiunta da
|
||||
@@ -770,6 +891,47 @@ public partial class Form1 : Form
|
||||
"Pulisci raw e step", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
|
||||
/// <summary>Elimina i CSV generati dall'import delle distinte (fase 2), che altrimenti si
|
||||
/// accumulano uno per distinta nella cartella di lavoro. Solo i *.csv direttamente nella
|
||||
/// cartella di lavoro, non in sottocartelle; i file originali del gestionale (.xls) restano.</summary>
|
||||
private void EliminaCsv()
|
||||
{
|
||||
string radice = _cartellaLavoro!;
|
||||
var files = Directory.Exists(radice) ? Directory.GetFiles(radice, "*.csv") : [];
|
||||
if (files.Length == 0)
|
||||
{
|
||||
MessageBox.Show(this, $"Nessun file CSV trovato in {radice}.",
|
||||
"Elimina CSV", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
var nomi = files.Select(Path.GetFileName).Take(15).ToList();
|
||||
if (files.Length > nomi.Count)
|
||||
nomi.Add($"... e altri {files.Length - nomi.Count}");
|
||||
var conferma = MessageBox.Show(this,
|
||||
$"Eliminare {files.Length} file CSV da {radice}?\n\n{string.Join("\n", nomi)}\n\nL'operazione non è reversibile.",
|
||||
"Elimina CSV", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
|
||||
if (conferma != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
int eliminati = 0;
|
||||
foreach (var file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(file);
|
||||
eliminati++;
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
|
||||
{
|
||||
// ponytail: file in uso (es. aperto in Excel) -> si salta, non blocca gli altri
|
||||
}
|
||||
}
|
||||
|
||||
MessageBox.Show(this, $"{eliminati} file CSV eliminati da {radice}.",
|
||||
"Elimina CSV", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
|
||||
/// <summary>Tutti i codici prt/asm noti all'indice, con i relativi percorsi (ultima
|
||||
/// versione, un solo file per tipo - stesso criterio di dedup per data_modifica DESC
|
||||
/// usato ovunque in questa classe). Caricato in un'unica query invece che una per
|
||||
@@ -1130,20 +1292,18 @@ public partial class Form1 : Form
|
||||
|
||||
private static readonly string CartellaImpostazioni = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "PrepStep");
|
||||
private static readonly string PercorsoImpostazioni = Path.Combine(CartellaImpostazioni, "ultimo_db.txt");
|
||||
private static readonly string PercorsoImpostazioniCartella = Path.Combine(CartellaImpostazioni, "ultima_cartella_lavoro.txt");
|
||||
|
||||
/// <summary>Propone l'ultima cartella di lavoro usata (se ancora esiste su disco) o, se non
|
||||
/// c'è ancora una preferenza salvata (o punta a una cartella cancellata), la cartella dove si
|
||||
/// trova il DB (più sensata dell'eseguibile: quella è dove l'utente tiene il proprio lavoro,
|
||||
/// non dov'è installata la app). Richiede quindi che InizializzaDatabase sia già stata
|
||||
/// chiamata. Sempre modificabile con "File → Scegli cartella di lavoro...".</summary>
|
||||
/// c'è ancora una preferenza salvata (o punta a una cartella cancellata), la cartella di
|
||||
/// installazione (%LOCALAPPDATA%\Programs\PrepStep col setup). Sempre modificabile con
|
||||
/// "File → Scegli cartella di lavoro...".</summary>
|
||||
private void InizializzaCartellaLavoro()
|
||||
{
|
||||
var ultima = LeggiUltimaCartellaLavoro();
|
||||
_cartellaLavoro = ultima is not null && Directory.Exists(ultima)
|
||||
? ultima
|
||||
: (_dbPath is not null ? Path.GetDirectoryName(_dbPath) : null) ?? AppContext.BaseDirectory;
|
||||
: AppContext.BaseDirectory;
|
||||
AggiornaStatoAvanti();
|
||||
}
|
||||
|
||||
@@ -1190,15 +1350,10 @@ public partial class Form1 : Form
|
||||
|
||||
private void InizializzaDatabase()
|
||||
{
|
||||
var candidati = new[]
|
||||
{
|
||||
LeggiUltimoDbUsato(),
|
||||
Path.Combine(AppContext.BaseDirectory, "cad_index.db"),
|
||||
Path.Combine(Directory.GetCurrentDirectory(), "cad_index.db"),
|
||||
};
|
||||
_dbPath = candidati.FirstOrDefault(p => p is not null && File.Exists(p));
|
||||
|
||||
if (_dbPath is null)
|
||||
// Il DB sta sempre nella cartella di installazione; "Apri DB..." ne carica un altro
|
||||
// solo per la sessione corrente, senza ricordarlo al prossimo avvio.
|
||||
string predefinito = Path.Combine(AppContext.BaseDirectory, "cad_index.db");
|
||||
if (!File.Exists(predefinito))
|
||||
{
|
||||
_lblStatus.Text = "Database non trovato. Usa 'Apri DB...' per selezionarlo oppure 'Aggiorna indice' per crearlo.";
|
||||
_lblDb.Text = "Nessun database caricato. Premi 'Aggiorna indice' per crearlo da X:\\, oppure 'Apri DB...' se ne hai gi\u00e0 uno.";
|
||||
@@ -1209,7 +1364,7 @@ public partial class Form1 : Form
|
||||
return;
|
||||
}
|
||||
|
||||
CaricaDatabase(_dbPath);
|
||||
CaricaDatabase(predefinito);
|
||||
}
|
||||
|
||||
private void ScegliDatabase()
|
||||
@@ -1242,7 +1397,6 @@ public partial class Form1 : Form
|
||||
? $"Ultimo aggiornamento indice: {ultimaScansione.ToLocalTime():dd/MM/yyyy HH:mm}"
|
||||
: "Ultimo aggiornamento indice: sconosciuto";
|
||||
_lblStatus.Text = "Pronto.";
|
||||
SalvaUltimoDbUsato(path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1254,33 +1408,6 @@ public partial class Form1 : Form
|
||||
AggiornaStatoAvanti();
|
||||
}
|
||||
|
||||
/// <summary>Ricorda l'ultimo DB caricato con successo, così al prossimo avvio si
|
||||
/// ricarica automaticamente anche se l'eseguibile gira da una cartella diversa.</summary>
|
||||
private static string? LeggiUltimoDbUsato()
|
||||
{
|
||||
try
|
||||
{
|
||||
return File.Exists(PercorsoImpostazioni) ? File.ReadAllText(PercorsoImpostazioni).Trim() : null;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SalvaUltimoDbUsato(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(CartellaImpostazioni);
|
||||
File.WriteAllText(PercorsoImpostazioni, path);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// ponytail: non critico se non si riesce a salvare, si torna a chiedere "Apri DB..." la prossima volta
|
||||
}
|
||||
}
|
||||
|
||||
private async void AggiornaIndice()
|
||||
{
|
||||
string dbPath = _dbPath ?? Path.Combine(AppContext.BaseDirectory, "cad_index.db");
|
||||
|
||||
+310
-53
@@ -1,74 +1,239 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PrepStep;
|
||||
|
||||
/// <summary>Guida interattiva richiamabile da "Aiuto ▸ Guida" (F1): sintesi delle 5 fasi del
|
||||
/// wizard, per chi apre l'app senza aver letto i docs. Il dettaglio completo (formato .asm,
|
||||
/// schema DB, ecc.) resta solo in docs/architettura.md e affini, per non duplicarlo qui.</summary>
|
||||
/// <summary>Guida interattiva richiamabile da "Aiuto ▸ Guida" (F1): stessa guida completa di
|
||||
/// docs/guida-utente.md (passo-passo fino a Creo Distributed Batch), pensata per chi apre
|
||||
/// l'app senza aver letto i docs e senza esperienza pregressa. Il dettaglio tecnico (formato
|
||||
/// .asm, schema DB, ecc.) resta solo in docs/architettura.md e affini, per non duplicarlo qui.
|
||||
///
|
||||
/// Gerarchia tipografica nel pannello di lettura (vedi ScriviSezione): titolo sezione in blu
|
||||
/// primario/grassetto/14pt, corpo in grigio quasi-nero/10pt, passi numerati con il numero in
|
||||
/// blu grassetto per farli scorrere a colpo d'occhio, callout con sfondo tinta piatta (nessun
|
||||
/// bordo/ombra: RichTextBox non li supporta) — blu tenue per le note informative, ambra tenue
|
||||
/// per gli avvisi importanti, con etichetta ("Nota"/"Attenzione") in grassetto a inizio blocco
|
||||
/// invece di un colore isolato, così restano leggibili anche senza vedere i colori.</summary>
|
||||
public class GuidaForm : Form
|
||||
{
|
||||
private static readonly (string Titolo, string Testo)[] Sezioni =
|
||||
{
|
||||
("Panoramica", """
|
||||
Cevolani StepPrep prepara i file CAD Pro-ENGINEER/Creo da passare a Creo
|
||||
Distributed Batch per l'esportazione STEP: indicizza i file di una share di
|
||||
rete e guida attraverso 5 fasi, una alla volta, con "Avanti"/"Indietro".
|
||||
private sealed record Sezione(
|
||||
string Titolo,
|
||||
string Intro,
|
||||
string[]? Passi = null,
|
||||
string? NotaTesto = null,
|
||||
bool NotaAttenzione = false);
|
||||
|
||||
"File ▸ Nuovo" (Ctrl+N) azzera distinta/raw/verifica per
|
||||
ripartire con una nuova distinta base, senza dover ricaricare il database.
|
||||
"""),
|
||||
("1. Database", """
|
||||
Carica o aggiorna l'indice "cad_index.db" a partire dalle cartelle di primo
|
||||
livello del disco/share (radice di default X:\). Un pallino verde/rosso indica
|
||||
se un database è caricato. La cartella di lavoro, dove finiscono CSV, "raw" e
|
||||
"step", si cambia da "File ▸ Scegli cartella di lavoro...".
|
||||
"""),
|
||||
("2. Importa Distinta Base", """
|
||||
Converte il file della distinta (trascinabile nella finestra, o scelto col
|
||||
bottone "1. Carica la distinta") in un CSV componente;descrizione nella cartella
|
||||
di lavoro. Se il file segue la convenzione del gestionale, aggiunge anche
|
||||
l'assieme generale come riga. Poi "2. Verifica la presenza dei file sul server"
|
||||
cerca i componenti nell'indice: è questo passo, non il solo import, a sbloccare
|
||||
"Avanti".
|
||||
"""),
|
||||
("3. Crea cartella raw", """
|
||||
Cerca nell'indice i file CAD dei componenti del CSV e li copia nella
|
||||
sottocartella "raw" della cartella di lavoro.
|
||||
"""),
|
||||
("4. Trova componenti referenziati", """
|
||||
Legge i file .asm già in "raw" per risolvere ricorsivamente sotto-assiemi e
|
||||
parti citate, copiando anche quelli in "raw": componenti soppressi (non in
|
||||
distinta ma necessari per aprire l'assieme) e riferimenti esterni (assembly
|
||||
feature, datum copiati) inclusi.
|
||||
"""),
|
||||
("5. Verifica completezza", """
|
||||
Mostra i file .asm della distinta già in "raw", trascinabili su Creo per
|
||||
l'esportazione STEP (passo manuale, fuori da quest'app). "Verifica
|
||||
completezza assiemi" controlla che tutti i sotto-componenti risultino
|
||||
presenti nell'indice.
|
||||
"""),
|
||||
// Palette: blu primario dell'app per titoli/passi, ambra tenue solo per gli avvisi che
|
||||
// possono far perdere tempo se ignorati (es. l'errore Creo [-4]) — il rosso resta riservato
|
||||
// ai messaggi di errore veri e propri altrove nell'app.
|
||||
private static readonly Color ColoreTesto = Color.FromArgb(40, 40, 40);
|
||||
private static readonly Color ColoreNotaInfoSfondo = Color.FromArgb(232, 240, 250);
|
||||
private static readonly Color ColoreNotaInfoTesto = Color.FromArgb(15, 65, 120);
|
||||
private static readonly Color ColoreNotaAttenzioneSfondo = Color.FromArgb(253, 240, 219);
|
||||
private static readonly Color ColoreNotaAttenzioneTesto = Color.FromArgb(140, 90, 10);
|
||||
|
||||
private static readonly Sezione[] Sezioni =
|
||||
{
|
||||
new("Panoramica",
|
||||
"PrepStep - Cevolani prepara i file CAD Pro-ENGINEER/Creo da passare a Creo " +
|
||||
"Distributed Batch per l'esportazione STEP: indicizza i file di una share di rete " +
|
||||
"e guida attraverso 5 fasi, una alla volta, con \"Avanti\"/\"Indietro\".",
|
||||
NotaTesto: "\"File ▸ Nuovo\" (Ctrl+N) azzera lo stato di distinta/raw/verifica per ripartire " +
|
||||
"con una nuova distinta base, senza dover ricaricare il database."),
|
||||
|
||||
new("Dove finiscono i file",
|
||||
"Tutto sta nella cartella di lavoro, che di default è la cartella di " +
|
||||
"installazione (%LOCALAPPDATA%\\Programs\\PrepStep):",
|
||||
Passi:
|
||||
[
|
||||
"Il database cad_index.db sta sempre nella cartella di installazione.",
|
||||
"Il CSV di ogni distinta importata, \"raw\" (file CAD delle fasi 3-4) e " +
|
||||
"\"step\" (STEP esportati da Creo, cartella che indichi tu a Creo) stanno " +
|
||||
"nella cartella di lavoro. Il file .xls del gestionale resta dove l'hai " +
|
||||
"preso.",
|
||||
"Per cambiare la cartella di lavoro: \"File ▸ Scegli cartella di lavoro...\" " +
|
||||
"(viene ricordata agli avvii successivi).",
|
||||
"Per fare pulizia: \"File ▸ Pulisci raw e step...\" svuota \"raw\" e " +
|
||||
"\"step\"; \"File ▸ Elimina CSV delle distinte...\" elimina i CSV, che " +
|
||||
"altrimenti si accumulano uno per distinta. Nessuno dei due tocca il " +
|
||||
"database.",
|
||||
],
|
||||
NotaTesto: "AppData è una cartella nascosta in Esplora file: per aprirla scrivi " +
|
||||
"%LOCALAPPDATA%\\Programs\\PrepStep nella barra degli indirizzi (vale anche " +
|
||||
"per scegliere \"step\" in Creo). Disinstallando il programma questi file " +
|
||||
"restano: cancellali a mano se non servono più."),
|
||||
|
||||
new("Prima di iniziare",
|
||||
"Prima di importare la prima distinta:",
|
||||
Passi:
|
||||
[
|
||||
"Serve accesso alla share di rete con i file CAD (di default X:\\).",
|
||||
"Serve l'estrazione \"Distinta Base\" da Fata (il gestionale): il file .xls " +
|
||||
"così come Fata lo esporta, senza risalvarlo o convertirlo in altri " +
|
||||
"formati — l'app riconosce da sola se sotto l'estensione .xls c'è XML o " +
|
||||
"Excel binario vero.",
|
||||
"Se il file segue la convenzione di nome DBP_<codice>_<timestamp>, l'app " +
|
||||
"aggiunge da sola l'assieme generale come componente in più: non serve " +
|
||||
"fare nulla di diverso.",
|
||||
"Se non vuoi usare la cartella di installazione, scegli la cartella di " +
|
||||
"lavoro (\"File ▸ Scegli cartella di lavoro...\") prima di importare la " +
|
||||
"distinta: è lì che finiscono CSV, \"raw\" e, dopo l'esportazione con " +
|
||||
"Creo, \"step\".",
|
||||
"Se la cartella di lavoro è già stata usata per un'altra distinta, conviene " +
|
||||
"pulire \"raw\" e \"step\" prima di ripartire (\"File ▸ Pulisci raw e " +
|
||||
"step...\"): \"File ▸ Nuovo\" azzera solo lo stato dell'app, non i file " +
|
||||
"già copiati su disco.",
|
||||
]),
|
||||
|
||||
new("1. Database",
|
||||
"All'avvio l'app carica da sola \"cad_index.db\" dalla cartella di installazione. " +
|
||||
"Un pallino verde/rosso indica se un database è caricato.",
|
||||
Passi:
|
||||
[
|
||||
"Pallino rosso (nessun database): premi \"Aggiorna indice\" per scansionare " +
|
||||
"le cartelle di primo livello della share (X:\\) e crearlo. La prima volta " +
|
||||
"richiede qualche minuto, i giri successivi aggiornano solo quello che è " +
|
||||
"cambiato.",
|
||||
"Database da un'altra postazione: \"Apri DB...\" lo carica solo finché non " +
|
||||
"chiudi il programma. Per usarlo sempre, copialo nella cartella di " +
|
||||
"installazione come cad_index.db.",
|
||||
]),
|
||||
|
||||
new("2. Importa Distinta Base",
|
||||
"Due passi in sequenza:",
|
||||
Passi:
|
||||
[
|
||||
"Carica la distinta: trascina il file .xls nella finestra, oppure premi " +
|
||||
"\"Scegli file distinta...\". Le righe importate compaiono nella griglia " +
|
||||
"(Componente/Descrizione) e viene scritto un CSV nella cartella di lavoro. " +
|
||||
"In alternativa scegli \"Lista personalizzata\" e digita i codici uno alla " +
|
||||
"volta (Invio per aggiungere, Canc per togliere): nessun CSV, l'assieme " +
|
||||
"generale va inserito a mano.",
|
||||
"Verifica la presenza dei file sul server: compare solo dopo un import " +
|
||||
"riuscito. Cerca ogni componente nel database — le righe della griglia si " +
|
||||
"colorano di verde (trovato) o rosso (non trovato nell'indice).",
|
||||
],
|
||||
NotaTesto: "\"Avanti\" si sblocca solo dopo il passo 2, non basta il solo import: " +
|
||||
"è il modo per accorgersi subito se il database è disallineato rispetto alla " +
|
||||
"share prima di andare avanti.",
|
||||
NotaAttenzione: true),
|
||||
|
||||
new("3. Crea cartella raw",
|
||||
"Premi \"Crea cartella raw\": per ogni componente della distinta trovato " +
|
||||
"nell'indice, l'app ne copia il file CAD dalla share nella cartella \"raw\" " +
|
||||
"(dentro la cartella di lavoro). La griglia mostra il percorso CAD copiato per " +
|
||||
"ogni componente."),
|
||||
|
||||
new("4. Trova componenti referenziati",
|
||||
"Gli assiemi (.asm) referenziano altri file (sotto-assiemi, parti, componenti " +
|
||||
"soppressi, riferimenti esterni) che Creo deve poter caricare insieme " +
|
||||
"all'assieme principale, anche se non compaiono nella distinta del gestionale. " +
|
||||
"Premi \"Trova componenti referenziati\": l'app legge i file .asm già in \"raw\", " +
|
||||
"risolve ricorsivamente tutto l'albero e copia anche quei file in \"raw\".\n\n" +
|
||||
"Si apre una finestra con l'elenco di quanto trovato e copiato, con in fondo " +
|
||||
"eventuali codici non corrispondenti a nessun file indicizzato (possono essere " +
|
||||
"disegni, componenti davvero mancanti, o falsi positivi — vale la pena " +
|
||||
"controllarli). Da lì si può anche aprire direttamente la cartella \"raw\".",
|
||||
NotaTesto: "Passo opzionale se la distinta contiene solo parti singole senza " +
|
||||
"sotto-assiemi: in quel caso si può passare direttamente alla fase successiva."),
|
||||
|
||||
new("5. Verifica completezza",
|
||||
"Mostra i file .asm della distinta già in \"raw\", trascinabili. Da qui il " +
|
||||
"passaggio a Creo è manuale, fuori da quest'app:",
|
||||
Passi:
|
||||
[
|
||||
"Apri Creo Distributed Batch (dal menu Start di Windows, o dal file " +
|
||||
"ptcdbatch.bat nella cartella bin dell'installazione Creo).",
|
||||
"Crea (o riusa, se già presente in azienda) un gruppo di attività per " +
|
||||
"l'esportazione STEP: menu Actions ▸ New Task Group.",
|
||||
"Aggiungi i file: trascina i .asm dalla finestra dell'app (o dalla cartella " +
|
||||
"\"raw\" in Esplora risorse) sul gruppo di attività, oppure sfoglia e " +
|
||||
"selezionali a mano.",
|
||||
"Verifica che il gruppo di attività punti al config.pro/profilo di export " +
|
||||
"STEP giusto (sezione Configuration Files) — di norma è già impostato " +
|
||||
"una volta sola per tutti.",
|
||||
"Avvia l'elaborazione (Start/Run): Distributed Batch legge ogni .asm, con " +
|
||||
"tutto l'albero di sotto-componenti già raccolto nelle fasi 3-4, ed " +
|
||||
"esporta i file STEP nella cartella \"step\" (dentro la cartella di " +
|
||||
"lavoro, accanto a \"raw\").",
|
||||
],
|
||||
NotaTesto: "Se Distributed Batch fallisce con \"[-4] Dati di input non " +
|
||||
"completi\", quasi sempre manca un file nell'albero dei componenti — Creo " +
|
||||
"non dice quale. Torna alla fase 4 e controlla l'elenco dei \"non trovati\": " +
|
||||
"è il punto di partenza per capire cosa recuperare prima di riprovare.",
|
||||
NotaAttenzione: true),
|
||||
|
||||
new("Ripartire da capo",
|
||||
"Quando devi processare un'altra distinta, usa \"File ▸ Nuovo\" (Ctrl+N): svuota " +
|
||||
"le griglie e la verifica delle fasi 2-5 e riporta al passo 2, senza dover " +
|
||||
"ricaricare il database né cambiare la cartella di lavoro. Non cancella nessun " +
|
||||
"file su disco: CSV, \"raw\" e \"step\" restano.",
|
||||
NotaTesto: "Per ripulire le cartelle \"raw\" e \"step\" della cartella di lavoro " +
|
||||
"prima di un nuovo giro (senza toccare l'indice), usa \"File ▸ Pulisci raw e " +
|
||||
"step...\"; per eliminare i CSV che ogni distinta importata lascia nella " +
|
||||
"cartella di lavoro, usa \"File ▸ Elimina CSV delle distinte...\"."),
|
||||
};
|
||||
|
||||
public GuidaForm()
|
||||
{
|
||||
Text = "Guida";
|
||||
ClientSize = new Size(640, 420);
|
||||
MinimumSize = new Size(480, 320);
|
||||
ClientSize = new Size(1000, 680);
|
||||
MinimumSize = new Size(640, 440);
|
||||
StartPosition = FormStartPosition.CenterParent;
|
||||
Font = new Font("Segoe UI", 9.5f);
|
||||
try
|
||||
{
|
||||
Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or ArgumentException)
|
||||
{
|
||||
// ponytail: nessuna icona nell'exe (es. lanciato da dotnet run) -> resta quella di default
|
||||
}
|
||||
|
||||
var headerPanel = new Panel { Dock = DockStyle.Top, Height = 40, BackColor = SystemColors.ControlLight };
|
||||
var lblHeader = new Label
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
Text = "Guida",
|
||||
TextAlign = ContentAlignment.MiddleLeft,
|
||||
Padding = new Padding(16, 0, 0, 0),
|
||||
Font = new Font(Font, FontStyle.Bold),
|
||||
};
|
||||
headerPanel.Controls.Add(lblHeader);
|
||||
|
||||
// Sidebar disegnata a mano (OwnerDrawFixed) invece della selezione blu di sistema:
|
||||
// stessa identità visiva del resto dell'app (UiStile.ColorePrimario) invece del colore
|
||||
// di selezione standard di Windows, che varia con il tema dell'utente.
|
||||
var lista = new ListBox
|
||||
{
|
||||
Dock = DockStyle.Left,
|
||||
Width = 200,
|
||||
Width = 230,
|
||||
IntegralHeight = false,
|
||||
BorderStyle = BorderStyle.None,
|
||||
DrawMode = DrawMode.OwnerDrawFixed,
|
||||
ItemHeight = 34,
|
||||
BackColor = UiStile.ColoreSfondoCard,
|
||||
};
|
||||
foreach (var sezione in Sezioni)
|
||||
lista.Items.Add(sezione.Titolo);
|
||||
lista.DrawItem += (_, e) =>
|
||||
{
|
||||
if (e.Index < 0)
|
||||
return;
|
||||
bool selezionato = (e.State & DrawItemState.Selected) != 0;
|
||||
using var sfondo = new SolidBrush(selezionato ? UiStile.ColorePrimario : UiStile.ColoreSfondoCard);
|
||||
e.Graphics.FillRectangle(sfondo, e.Bounds);
|
||||
var colore = selezionato ? Color.White : ColoreTesto;
|
||||
using var testoFont = new Font(lista.Font, selezionato ? FontStyle.Bold : FontStyle.Regular);
|
||||
var rettangoloTesto = new Rectangle(e.Bounds.X + 16, e.Bounds.Y, e.Bounds.Width - 20, e.Bounds.Height);
|
||||
TextRenderer.DrawText(e.Graphics, lista.Items[e.Index].ToString(), testoFont, rettangoloTesto,
|
||||
colore, TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis);
|
||||
};
|
||||
foreach (var (titolo, _) in Sezioni)
|
||||
lista.Items.Add(titolo);
|
||||
|
||||
var testo = new RichTextBox
|
||||
var testo = new TestoSolaLettura
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
ReadOnly = true,
|
||||
TabStop = false,
|
||||
BorderStyle = BorderStyle.None,
|
||||
BackColor = Color.White,
|
||||
Font = new Font(Font.FontFamily, 10f),
|
||||
@@ -77,19 +242,111 @@ public class GuidaForm : Form
|
||||
lista.SelectedIndexChanged += (_, _) =>
|
||||
{
|
||||
if (lista.SelectedIndex >= 0)
|
||||
testo.Text = Sezioni[lista.SelectedIndex].Testo.Trim();
|
||||
ScriviSezione(testo, Sezioni[lista.SelectedIndex]);
|
||||
};
|
||||
|
||||
var btnChiudi = new Button { Text = "Chiudi", Dock = DockStyle.Bottom, Height = 34 };
|
||||
UiStile.Secondario(btnChiudi);
|
||||
var (barraChiudi, azioniChiudi) = UiStile.FloatBar();
|
||||
var btnChiudi = new Button { Text = "Chiudi" };
|
||||
UiStile.Primario(btnChiudi);
|
||||
UiStile.Arrotonda(btnChiudi, 10);
|
||||
btnChiudi.Click += (_, _) => Close();
|
||||
azioniChiudi.Controls.Add(btnChiudi);
|
||||
CancelButton = btnChiudi;
|
||||
|
||||
var contenuto = new Panel { Dock = DockStyle.Fill, Padding = new Padding(16) };
|
||||
var contenuto = new Panel { Dock = DockStyle.Fill, Padding = new Padding(24, 20, 24, 20), BackColor = Color.White };
|
||||
contenuto.Controls.Add(testo);
|
||||
|
||||
Controls.Add(contenuto);
|
||||
Controls.Add(lista);
|
||||
Controls.Add(btnChiudi);
|
||||
Controls.Add(barraChiudi);
|
||||
Controls.Add(headerPanel);
|
||||
|
||||
lista.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>Costruisce il pannello di lettura con gerarchia tipografica esplicita: titolo
|
||||
/// (blu primario, grassetto, 14pt), corpo (grigio quasi-nero, 10pt), passi numerati (numero
|
||||
/// in blu grassetto, indentati) e callout finale con sfondo a tinta piatta ed etichetta in
|
||||
/// grassetto — informativo (blu tenue) o di attenzione (ambra tenue). Applica la
|
||||
/// formattazione DOPO aver scritto il testo, selezionando il range appena aggiunto: è il
|
||||
/// solo modo affidabile di formattare i paragrafi in un RichTextBox (impostare
|
||||
/// SelectionFont/Color su una selezione vuota prima di AppendText non è garantito che
|
||||
/// "si attacchi" al testo che segue).</summary>
|
||||
private static void ScriviSezione(RichTextBox box, Sezione sezione)
|
||||
{
|
||||
box.Clear();
|
||||
var titoloFont = new Font(box.Font.FontFamily, 14f, FontStyle.Bold);
|
||||
var corpoFont = new Font(box.Font.FontFamily, 10f, FontStyle.Regular);
|
||||
var corpoGrassetto = new Font(box.Font.FontFamily, 10f, FontStyle.Bold);
|
||||
|
||||
void Scrivi(string testo, Font font, Color colore, int indent = 0, Color? sfondo = null)
|
||||
{
|
||||
int inizio = box.TextLength;
|
||||
box.AppendText(testo);
|
||||
box.Select(inizio, box.TextLength - inizio);
|
||||
box.SelectionFont = font;
|
||||
box.SelectionColor = colore;
|
||||
box.SelectionBackColor = sfondo ?? box.BackColor;
|
||||
box.SelectionIndent = indent;
|
||||
}
|
||||
|
||||
Scrivi(sezione.Titolo + "\n\n", titoloFont, UiStile.ColorePrimario);
|
||||
Scrivi(sezione.Intro + "\n", corpoFont, ColoreTesto);
|
||||
|
||||
if (sezione.Passi is { Length: > 0 })
|
||||
{
|
||||
Scrivi("\n", corpoFont, ColoreTesto);
|
||||
for (int i = 0; i < sezione.Passi.Length; i++)
|
||||
{
|
||||
Scrivi($"{i + 1}. ", corpoGrassetto, UiStile.ColorePrimario, indent: 8);
|
||||
Scrivi(sezione.Passi[i] + "\n\n", corpoFont, ColoreTesto, indent: 8);
|
||||
}
|
||||
}
|
||||
|
||||
if (sezione.NotaTesto is not null)
|
||||
{
|
||||
var sfondo = sezione.NotaAttenzione ? ColoreNotaAttenzioneSfondo : ColoreNotaInfoSfondo;
|
||||
var coloreEtichetta = sezione.NotaAttenzione ? ColoreNotaAttenzioneTesto : ColoreNotaInfoTesto;
|
||||
string etichetta = sezione.NotaAttenzione ? "Attenzione" : "Nota";
|
||||
|
||||
Scrivi("\n", corpoFont, ColoreTesto);
|
||||
Scrivi(" " + etichetta + "\n", corpoGrassetto, coloreEtichetta, indent: 12, sfondo: sfondo);
|
||||
Scrivi(" " + sezione.NotaTesto + " \n", corpoFont, ColoreTesto, indent: 12, sfondo: sfondo);
|
||||
}
|
||||
|
||||
box.SelectionStart = 0;
|
||||
box.SelectionLength = 0;
|
||||
}
|
||||
|
||||
/// <summary>RichTextBox da sola lettura che non si comporta come un editor: niente cursore
|
||||
/// lampeggiante, niente cursore a "I" e niente selezione col mouse. I clic nell'area di testo
|
||||
/// vengono ignorati (così non prende il fuoco e non mostra il caret); la barra di scorrimento
|
||||
/// sta nell'area non-client e resta utilizzabile, la rotella scorre al passaggio del mouse.</summary>
|
||||
private sealed class TestoSolaLettura : RichTextBox
|
||||
{
|
||||
private const int WM_SETFOCUS = 0x0007;
|
||||
private const int WM_SETCURSOR = 0x0020;
|
||||
private const int WM_LBUTTONDOWN = 0x0201;
|
||||
private const int WM_LBUTTONDBLCLK = 0x0203;
|
||||
private const int WM_RBUTTONDOWN = 0x0204;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool HideCaret(IntPtr hWnd);
|
||||
|
||||
protected override void WndProc(ref Message m)
|
||||
{
|
||||
switch (m.Msg)
|
||||
{
|
||||
case WM_LBUTTONDOWN or WM_LBUTTONDBLCLK or WM_RBUTTONDOWN:
|
||||
return;
|
||||
case WM_SETCURSOR:
|
||||
Cursor.Current = Cursors.Arrow;
|
||||
m.Result = 1;
|
||||
return;
|
||||
}
|
||||
base.WndProc(ref m);
|
||||
if (m.Msg == WM_SETFOCUS)
|
||||
HideCaret(Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,11 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
|
||||
<ApplicationIcon>app.ico</ApplicationIcon>
|
||||
<AssemblyTitle>Cevolani StepPrep</AssemblyTitle>
|
||||
<Product>Cevolani StepPrep</Product>
|
||||
<AssemblyTitle>PrepStep - Cevolani</AssemblyTitle>
|
||||
<Product>PrepStep - Cevolani</Product>
|
||||
<Company>Cevolani</Company>
|
||||
<Version>0.1.0</Version>
|
||||
<Copyright>Copyright (c) 2026 Davide Grilli</Copyright>
|
||||
<Version>1.1.0</Version>
|
||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Cevolani StepPrep
|
||||
# PrepStep - Cevolani
|
||||
|
||||
App Windows Forms (.NET 10, progetto `PrepStep`) per indicizzare, cercare e
|
||||
recuperare i file CAD Pro-ENGINEER/Creo di un disco/share di rete, e per trasformare
|
||||
@@ -9,32 +9,40 @@ passaggio, non lo sostituisce.
|
||||
|
||||
## Funzionalità
|
||||
|
||||
1. **Indice CAD** — all'avvio carica automaticamente `cad_index.db` (se
|
||||
presente accanto all'eseguibile). Se manca, si può selezionare un DB
|
||||
esistente con **Apri DB...** o generarne uno nuovo con **Aggiorna
|
||||
indice**, che scansiona il disco (di default `X:\`, tutte le cartelle di primo
|
||||
livello che trova) e indicizza tutti i file. In questa fase si vede/cambia anche
|
||||
la **cartella di lavoro**: dove finiscono il CSV e la cartella `raw/` (gli STEP
|
||||
esportati con Creo vanno in `step/`, dentro la stessa cartella). Proposta
|
||||
automaticamente (ricordata da un avvio all'altro), modificabile con
|
||||
**Cambia cartella...**.
|
||||
1. **Indice CAD** — all'avvio carica automaticamente `cad_index.db` dalla
|
||||
cartella di installazione (`%LOCALAPPDATA%\Programs\PrepStep`). Se manca, lo si
|
||||
crea con **Aggiorna indice**, oppure si apre un altro DB per la sola sessione
|
||||
con **Apri DB...**. **Aggiorna indice** scansiona il disco (di default `X:\`,
|
||||
tutte le cartelle di primo livello che trova) e indicizza tutti i file. La
|
||||
**cartella di lavoro** (dove finiscono il CSV e la cartella `raw/`; gli STEP
|
||||
esportati con Creo vanno in `step/`, dentro la stessa cartella) è di default la
|
||||
cartella di installazione, modificabile in qualunque momento da **File → Scegli
|
||||
cartella di lavoro...** (la scelta viene ricordata). Dal menu **File** si
|
||||
ripuliscono anche `raw/`/`step/` e i CSV accumulati.
|
||||
2. **Importa Distinta Base** — trascina (o scegli da file) l'estrazione
|
||||
`.xls`/`.xml` del gestionale (foglio "Distinta Base", colonne
|
||||
Componente/Descrizione): viene generato un CSV nella cartella di lavoro, con
|
||||
anteprima in griglia. Se il file segue la convenzione di nome
|
||||
`DBP_<codice>_<timestamp>` del gestionale, l'assieme generale dell'estrazione
|
||||
viene aggiunto come riga in più nel CSV (il foglio elenca solo i componenti
|
||||
sotto l'assieme, non l'assieme stesso).
|
||||
sotto l'assieme, non l'assieme stesso). Il bottone **Verifica presenza**
|
||||
(compare solo dopo un import riuscito) cerca i componenti nel database — è
|
||||
questo passo, non il solo import, a sbloccare la fase successiva.
|
||||
3. **Crea cartella raw** — cerca nell'indice il percorso CAD di ogni
|
||||
componente importato (assieme generale incluso) e copia i file trovati
|
||||
in una cartella `raw/` dentro la cartella di lavoro.
|
||||
4. **Trova componenti referenziati** — legge i metadati dei file `.asm` in
|
||||
`raw/` per trovare ricorsivamente i sotto-assiemi/parti che li
|
||||
compongono, copiandoli a loro volta in `raw/`.
|
||||
5. **Verifica completezza assiemi** — confronta la distinta con l'indice per
|
||||
capire se manca qualche sotto-componente, e mostra i file `.asm` della
|
||||
distinta trascinabili su Creo/Esplora risorse per l'esportazione STEP con
|
||||
Creo Distributed Batch.
|
||||
compongono, copiandoli a loro volta in `raw/` (passo opzionale se la
|
||||
distinta contiene solo parti singole).
|
||||
5. **Verifica completezza** — mostra i file `.asm` della distinta già in `raw/`,
|
||||
trascinabili su Creo Distributed Batch per l'esportazione STEP (passo
|
||||
manuale, fuori da quest'app): scrive in `step/`, dentro la cartella di
|
||||
lavoro.
|
||||
|
||||
Primo uso: [`docs/tutorial-primo-giro.md`](docs/tutorial-primo-giro.md). Guida utente completa in
|
||||
[`docs/guida-utente.md`](docs/guida-utente.md)
|
||||
(stessa sintesi anche in-app da **Aiuto → Guida**, F1).
|
||||
|
||||
## Requisiti
|
||||
|
||||
@@ -43,68 +51,41 @@ passaggio, non lo sostituisce.
|
||||
- Accesso alla share di rete con i file CAD (`X:\` di default) per
|
||||
l'indicizzazione
|
||||
|
||||
## Build
|
||||
## Build, test e rilascio
|
||||
|
||||
```powershell
|
||||
cd PrepStep
|
||||
dotnet build -c Release
|
||||
```
|
||||
Build, avvio e test: [`docs/sviluppo.md`](docs/sviluppo.md). Pubblicazione dell'eseguibile e
|
||||
generazione del setup di installazione: [`docs/rilascio.md`](docs/rilascio.md).
|
||||
|
||||
## Avvio
|
||||
|
||||
Da sorgente:
|
||||
|
||||
```powershell
|
||||
cd PrepStep
|
||||
dotnet run -c Release
|
||||
```
|
||||
|
||||
Oppure l'eseguibile già compilato:
|
||||
|
||||
```powershell
|
||||
cd PrepStep\bin\Release\net10.0-windows
|
||||
.\PrepStep.exe
|
||||
```
|
||||
|
||||
## Pubblicazione (eseguibile standalone)
|
||||
|
||||
Genera un `.exe` self-contained per Windows x64, senza bisogno del runtime
|
||||
.NET installato sulla macchina di destinazione:
|
||||
|
||||
```powershell
|
||||
cd PrepStep
|
||||
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o publish
|
||||
```
|
||||
|
||||
L'eseguibile risultante è in `PrepStep\publish\PrepStep.exe` — un unico
|
||||
file, si copia e si lancia su un'altra macchina Windows senza bisogno di .NET
|
||||
installato.
|
||||
Il setup installa **solo per l'utente corrente**, in `%LOCALAPPDATA%\Programs\PrepStep`, senza
|
||||
diritti di amministratore; si disinstalla da "Impostazioni → App installate".
|
||||
|
||||
## Struttura del progetto
|
||||
|
||||
Tutto il codice è in `PrepStep/` (unico progetto nella solution, GUI e
|
||||
indicizzazione integrate):
|
||||
Il codice dell'app è tutto in `PrepStep/` (GUI e indicizzazione integrate); accanto ci sono
|
||||
il progetto di test `PrepStep.Tests/`, gli script standalone `scripts/` e lo script del setup
|
||||
`installer/`. Mappa file-per-file completa in [`docs/architettura.md`](docs/architettura.md); indice di tutti i doc in
|
||||
[`docs/README.md`](docs/README.md).
|
||||
|
||||
- `Program.cs` — entry point.
|
||||
- `Form1.cs` — finestra principale (indice CAD, import Distinta Base, griglia
|
||||
risultati); UI costruita interamente in codice, nessun designer visuale.
|
||||
- `Form1.cs` — finestra principale, wizard a 5 fasi e menu; UI costruita interamente in
|
||||
codice (`Form1.Designer.cs` è solo lo scheletro del template).
|
||||
- `Indexer.cs` — scansione del disco e scrittura/aggiornamento del DB SQLite.
|
||||
- `DistintaBaseImporter.cs` — parsing del file Distinta Base (SpreadsheetML
|
||||
XML o Excel binario/OpenXML tramite ExcelDataReader) e generazione CSV.
|
||||
- `ProgressForm.cs` — dialog di avanzamento durante l'aggiornamento indice.
|
||||
- `AsmRecordReader.cs` — lettura esatta dei record dei `.asm` (fase 4).
|
||||
- `AssemblyComponentScanner.cs` — euristica testuale di fallback per i `.asm` fuori formato.
|
||||
- `ProgressForm.cs`, `ProgressoComponentiForm.cs` — avanzamento di scansione e fase 4.
|
||||
- `PrtMancantiForm.cs` — esito di "Trova componenti referenziati".
|
||||
- `GuidaForm.cs` — guida in-app (Aiuto → Guida, F1).
|
||||
- `UiStile.cs` — stile condiviso di bottoni, card e barre.
|
||||
|
||||
Il database generato (`cad_index.db`), i file Excel/CSV della distinta
|
||||
trascinati nella root e le cartelle `raw/`/`step/` sono rigenerabili e non
|
||||
sono tracciati in git (vedi `.gitignore`).
|
||||
|
||||
## Test
|
||||
Per i test vedi [`docs/sviluppo.md`](docs/sviluppo.md#test).
|
||||
|
||||
`PrepStep.Tests/` contiene unit test (regex di estrazione dei componenti
|
||||
dai `.asm`, parsing del nome file Pro-E) e integration test (schema/vista SQL
|
||||
reale, con focus sui bug di case-sensitivity/duplicati già risolti). Da
|
||||
eseguire:
|
||||
## Licenza
|
||||
|
||||
```powershell
|
||||
cd PrepStep.Tests
|
||||
dotnet test
|
||||
```
|
||||
Software proprietario, © 2026 Davide Grilli, tutti i diritti riservati: vedi
|
||||
[`LICENSE`](LICENSE).
|
||||
@@ -0,0 +1,61 @@
|
||||
# Documentazione di PrepStep
|
||||
|
||||
Indice dei doc, organizzati secondo [Diátaxis](https://diataxis.fr/): ogni file ha **uno** scopo,
|
||||
e cambia solo quando cambia il programma.
|
||||
|
||||
## Tutorial — imparare facendo
|
||||
|
||||
- [`tutorial-primo-giro.md`](tutorial-primo-giro.md) — il primo giro completo con una distinta vera.
|
||||
|
||||
## How-to — svolgere un compito
|
||||
|
||||
- [`guida-utente.md`](guida-utente.md) — i passi dell'utente fino a Creo Distributed Batch.
|
||||
- [`risoluzione-problemi.md`](risoluzione-problemi.md) — sintomo → causa → cosa fare.
|
||||
- [`sviluppo.md`](sviluppo.md) — build, test, script e regole per scrivere i doc.
|
||||
- [`rilascio.md`](rilascio.md) — pubblicare e generare il setup di una nuova versione.
|
||||
|
||||
## Reference — consultare
|
||||
|
||||
- [`architettura.md`](architettura.md) — il processo delle 5 fasi e la mappa file-per-file.
|
||||
- [`db-cad-index.md`](db-cad-index.md) — schema, vista e query di `cad_index.db`.
|
||||
- [`formato-asm-record.md`](formato-asm-record.md) — parser esatto dei record `.asm`, casi limite (§7).
|
||||
- [`formato-asm.md`](formato-asm.md) — euristica testuale di fallback.
|
||||
- [`formato-prt-feature.md`](formato-prt-feature.md) — come leggere l'albero delle feature di un `.prt` (reverse engineering, non ancora nel codice).
|
||||
- [`formato-prt-geometria.md`](formato-prt-geometria.md) — numeri, quote e geometria degli schizzi di un `.prt`, con la tabella di cosa manca per determinarlo per intero.
|
||||
- [`formato-psb.md`](formato-psb.md) — il formato di serializzazione di tutte le sezioni (`.prt` e `.asm`): contenitore, grammatica, numeri, cosa c'è in ogni sezione, con un parser di riferimento in `scripts/psb/`.
|
||||
- [`history.md`](history.md) — stato del reverse engineering dei file Creo: cosa è provato, cosa manca, da dove ripartire.
|
||||
- [`glossario.md`](glossario.md) — i termini usati nei doc e nell'app.
|
||||
- [`../CHANGELOG.md`](../CHANGELOG.md) — storico delle versioni.
|
||||
|
||||
## Explanation — capire il perché
|
||||
|
||||
- [`decisioni/`](decisioni/README.md) — registro delle scelte di progetto (ADR).
|
||||
- [`copia-raw-parallela.md`](copia-raw-parallela.md) — perché e come la copia in `raw/` è parallela.
|
||||
|
||||
## Fonte unica (SSOT)
|
||||
|
||||
Ogni informazione vive in **un** posto; altrove si mette un link, non una copia.
|
||||
|
||||
| Informazione | Fonte |
|
||||
|---|---|
|
||||
| Versione e nome prodotto | `PrepStep/PrepStep.csproj` (`<Version>`, `<Product>`) |
|
||||
| Schema e vista del DB | [`db-cad-index.md`](db-cad-index.md) |
|
||||
| Formato dei record `.asm` e casi limite | [`formato-asm-record.md`](formato-asm-record.md) |
|
||||
| Albero delle feature di un `.prt` | [`formato-prt-feature.md`](formato-prt-feature.md) |
|
||||
| Quote, coordinate e geometria degli schizzi di un `.prt` | [`formato-prt-geometria.md`](formato-prt-geometria.md) |
|
||||
| Come è scritta una sezione qualsiasi di un `.prt`/`.asm` | [`formato-psb.md`](formato-psb.md) |
|
||||
| Processo delle 5 fasi, mappa dei file | [`architettura.md`](architettura.md) |
|
||||
| Passi dell'utente | [`guida-utente.md`](guida-utente.md) |
|
||||
| Build, test, regole sui doc | [`sviluppo.md`](sviluppo.md) |
|
||||
| Procedura di rilascio e setup | [`rilascio.md`](rilascio.md) |
|
||||
| Perché una scelta è fatta così | [`decisioni/`](decisioni/README.md) |
|
||||
| Storico release | [`../CHANGELOG.md`](../CHANGELOG.md) |
|
||||
|
||||
`CLAUDE.md` e `README.md` riassumono soltanto e rimandano a questi file.
|
||||
|
||||
## Verifica
|
||||
|
||||
```powershell
|
||||
dotnet run scripts/CheckDocs.cs
|
||||
npx markdownlint-cli2
|
||||
```
|
||||
+58
-24
@@ -1,9 +1,9 @@
|
||||
# Architettura di PrepStep (Cevolani StepPrep)
|
||||
# Architettura di PrepStep
|
||||
|
||||
Panoramica di tutti i file sorgente e di come collaborano. `PrepStep` è il nome
|
||||
tecnico del progetto (cartella, namespace, csproj, eseguibile `PrepStep.exe`);
|
||||
**Cevolani StepPrep** è il nome mostrato all'utente finale (titolo finestra, icona,
|
||||
versione — vedi `<Version>`/`<Product>` in `PrepStep.csproj`). Per il "come si usa" vedi il `README.md` in root; per il
|
||||
**PrepStep - Cevolani** è il nome mostrato all'utente finale (titolo finestra, icona,
|
||||
versione — vedi `<Version>`/`<Product>` in `PrepStep.csproj`). Per il "come si usa" vedi il `README.md` in root e la [`guida-utente.md`](guida-utente.md); per il
|
||||
formato binario `.asm` vedi `docs/formato-asm.md` (euristica testuale) e
|
||||
`docs/formato-asm-record.md` (parser esatto dei record); per la parallelizzazione della
|
||||
copia in `raw/` vedi `docs/copia-raw-parallela.md`.
|
||||
@@ -35,7 +35,8 @@ successiva.
|
||||
**1. Indice della share → `cad_index.db`.** `Indexer.ScanAsync` cammina tutte le cartelle di
|
||||
primo livello sotto `X:\`, riconosce i nomi Pro-E `<codice>.<tipo>.<versione>` e fa un upsert
|
||||
incrementale (confronto su percorso+dimensione+data), così i giri successivi costano solo il
|
||||
delta. Qui si sceglie anche la **cartella di lavoro**, che da qui in poi ospita tutto il resto.
|
||||
delta. La **cartella di lavoro**, che da qui in poi ospita tutto il resto, non si sceglie qui
|
||||
ma da "File → Scegli cartella di lavoro..." (default: cartella di installazione).
|
||||
|
||||
**2. Distinta del gestionale → CSV.** `DistintaBaseImporter.ConvertiInCsv` riduce l'export a
|
||||
due colonne `componente;descrizione`. Se il nome del file segue la convenzione
|
||||
@@ -93,7 +94,7 @@ trascinano i `.asm` su Creo Distributed Batch, che scrive in `step/`.
|
||||
| # | Fase | Bottone → metodo | Cosa fa |
|
||||
|---|---|---|---|
|
||||
| 1 | Database | "Aggiorna indice" → `Indexer.ScanAsync` | Crea/aggiorna `cad_index.db` scansionando la share; un pallino verde/rosso accanto al titolo indica se un DB è caricato. La cartella di lavoro non è mostrata qui (cambiabile in qualunque momento da "File → Scegli cartella di lavoro...") |
|
||||
| 2 | Importa Distinta Base | selezione file/drag&drop → `ImportaDistinta` → `DistintaBaseImporter.ConvertiInCsv`, poi "Verifica presenza" → `VerificaCodiciNelDb` | Xls/Xml gestionale → CSV componente;descrizione, scritto nella cartella di lavoro; "Verifica presenza" (visibile solo dopo un import riuscito) cerca i componenti nell'indice e colora le righe della griglia — è quella che sblocca "Avanti", non il solo import |
|
||||
| 2 | Importa Distinta Base | selezione file/drag&drop → `ImportaDistinta` → `DistintaBaseImporter.ConvertiInCsv`, poi "Verifica presenza" → `VerificaCodiciNelDb` | Xls/Xml gestionale → CSV componente;descrizione, scritto nella cartella di lavoro (oppure "Lista personalizzata": codici digitati uno alla volta con `AggiungiCodice`, solo in memoria, senza CSV; tutto a valle legge solo `_righeCorrenti`); "Verifica presenza" (visibile solo dopo un import riuscito) cerca i componenti nell'indice e colora le righe della griglia — è quella che sblocca "Avanti", non il solo import |
|
||||
| 3 | Crea cartella raw | "Crea cartella raw" → `CreaCartellaRaw` | `TrovaPercorsiCad` + `CopiaFileTrovati` (parallela) → `raw/` dentro la cartella di lavoro |
|
||||
| 4 | Trova componenti referenziati | "Trova componenti referenziati" → `TrovaPrtReferenziati` → `RisolviAlberoComponenti` | Legge i `.asm` in `raw/`, risolve ricorsivamente sotto-assiemi/parti nell'indice e li copia anch'essi in `raw/`, insieme ai componenti soppressi e ai modelli citati per riferimento esterno che servono a Creo per aprire l'assieme |
|
||||
| 5 | Verifica completezza | (ingresso in fase) → `AggiornaAnteprimaAsm` | Mostra le icone trascinabili dei `.asm` della distinta già in `raw/`, da trascinare su Creo per l'esportazione STEP (Distributed Batch) |
|
||||
@@ -107,20 +108,27 @@ trascinano i `.asm` su Creo Distributed Batch, che scrive in `step/`.
|
||||
fase 1: database e cartella di lavoro restano quelli già caricati, non serve rifare lo scan;
|
||||
"File → Scegli cartella di lavoro..." → `ScegliCartellaLavoro`, cambia in qualunque momento
|
||||
la destinazione di CSV/`raw/`/`step/`; "File → Pulisci raw e step..." svuota le cartelle
|
||||
`raw/`/`step/` nella cartella di lavoro (`PulisciRawEStep`, non tocca l'indice); "Aiuto →
|
||||
`raw/`/`step/` nella cartella di lavoro (`PulisciRawEStep`, non tocca l'indice); "File → Elimina CSV delle
|
||||
distinte..." elimina i `*.csv` direttamente nella cartella di lavoro (`EliminaCsv`); "Aiuto →
|
||||
Guida" (F1) apre `GuidaForm`, sintesi interattiva delle 5 fasi per chi non ha letto questi
|
||||
docs.
|
||||
|
||||
Altre responsabilità in Form1.cs:
|
||||
- `TrovaPercorsiCad` / `CaricaCodiciNoti` — query sul DB (`files_ultima_versione`), la
|
||||
seconda carica tutti i codici prt/asm noti in un colpo solo (usata sia per risolvere i
|
||||
componenti sia da `AssemblyComponentScanner` per validare i token generici).
|
||||
- `TrovaPercorsiCad` / `CaricaCodiciNoti` — query sul DB (`files_ultima_versione`): la
|
||||
prima risolve i componenti del CSV (fase 3), la seconda carica tutti i codici prt/asm noti
|
||||
in un colpo solo, usata in fase 4 solo per validare i token generici di
|
||||
`AssemblyComponentScanner`.
|
||||
- `CaricaCandidati` — in fase 4, tutte le copie di ogni codice dalla tabella `files` (non
|
||||
dalla vista), da cui `ScegliPerCartella` sceglie quella da copiare in `raw/`.
|
||||
- `CopiaFileTrovati` — copia (in parallelo, grado 6) i file trovati in `raw/`, saltando
|
||||
silenziosamente quelli irraggiungibili (share offline/permessi).
|
||||
- `PulisciRawEStep` — svuota `raw/` e `step/` nella cartella di lavoro (manutenzione,
|
||||
richiede conferma).
|
||||
- `InizializzaDatabase` / `ScegliDatabase` — localizzazione del DB (ultimo usato, salvato in
|
||||
`%AppData%/PrepStep/ultimo_db.txt`, o `cad_index.db` accanto all'eseguibile).
|
||||
- `EliminaCsv` — elimina i `*.csv` generati dall'import (uno per distinta) nella cartella di
|
||||
lavoro, solo al primo livello; gli `.xls` del gestionale restano (richiede conferma, con
|
||||
l'elenco dei file).
|
||||
- `InizializzaDatabase` / `ScegliDatabase` — localizzazione del DB (sempre `cad_index.db` accanto
|
||||
all'eseguibile; "Apri DB..." ne carica un altro solo per la sessione).
|
||||
- `InizializzaCartellaLavoro` / `ScegliCartellaLavoro` — stesso pattern per la cartella di
|
||||
lavoro (`ultima_cartella_lavoro.txt`), fallback `AppContext.BaseDirectory` se non c'è
|
||||
ancora nulla di salvato: nessuna dipendenza da un marker di repo, funziona identica
|
||||
@@ -236,44 +244,70 @@ completi".
|
||||
sotto-assiemi trovati e copiati in `raw/`, più i codici referenziati che non corrispondono a
|
||||
nessun file indicizzato (possono essere disegni, componenti davvero mancanti, o falsi
|
||||
positivi della scansione `.asm`).
|
||||
- **GuidaForm.cs** — guida interattiva ("Aiuto → Guida"/F1): lista delle 5 fasi a sinistra,
|
||||
testo a destra; sintesi, il dettaglio resta solo in questi docs.
|
||||
- **GuidaForm.cs** — guida interattiva ("Aiuto → Guida"/F1): elenco delle sezioni a sinistra
|
||||
(Panoramica, Dove finiscono i file, Prima di iniziare, le 5 fasi, Ripartire da capo), testo
|
||||
a destra; stessa guida di `docs/guida-utente.md`, il dettaglio tecnico resta solo in questi
|
||||
docs.
|
||||
|
||||
## Persistenza
|
||||
|
||||
- **DB** (`cad_index.db`, SQLite) — vedi tabella `files`/vista `files_ultima_versione` sopra.
|
||||
Percorso ricordato tra un avvio e l'altro in `%AppData%/PrepStep/ultimo_db.txt`
|
||||
(`SalvaUltimoDbUsato`/`LeggiUltimoDbUsato`), scritto ogni volta che si carica un DB
|
||||
(all'avvio se già noto, oppure da "Apri DB..." o al termine di "Aggiorna indice"); se il
|
||||
file non c'è o punta a un percorso cancellato, `InizializzaDatabase` ripiega su
|
||||
`cad_index.db` accanto all'eseguibile o nella working directory. Radice disco di scansione
|
||||
Sempre `cad_index.db` accanto all'eseguibile (`AppContext.BaseDirectory`, cioè la cartella
|
||||
di installazione): `InizializzaDatabase` carica solo quello, e "Aggiorna indice" lo crea lì
|
||||
se manca. "Apri DB..." carica un altro file solo per la sessione corrente, senza
|
||||
ricordarlo. Radice disco di scansione
|
||||
di default `X:\` (`Form1.RadiceDiscoDefault`, hardcoded).
|
||||
- **Cartella di lavoro** — stesso meccanismo del DB sopra, salvata in
|
||||
- **Cartella di lavoro** — ricordata tra un avvio e l'altro in
|
||||
`%AppData%/PrepStep/ultima_cartella_lavoro.txt`; se non c'è una preferenza salvata (o punta
|
||||
a una cartella cancellata) si propone la cartella del DB caricato, o in ultima istanza
|
||||
`AppContext.BaseDirectory`; cambiabile in qualunque momento da "File → Scegli cartella di
|
||||
a una cartella cancellata) si propone la cartella di installazione
|
||||
(`AppContext.BaseDirectory`); cambiabile in qualunque momento da "File → Scegli cartella di
|
||||
lavoro...". Tutti i file generati a valle ci finiscono dentro: il CSV della distinta, `raw/`
|
||||
(CAD dei componenti e sotto-componenti); `step/` non è scritta dall'app ma è dove l'utente
|
||||
esporta gli STEP a mano con Creo Distributed Batch, a partire dai file trascinati da `raw/`
|
||||
in fase 5.
|
||||
|
||||
## Installazione
|
||||
|
||||
`installer/PrepStep.iss` (Inno Setup 6) impacchetta `PrepStep/publish/PrepStep.exe` in un
|
||||
setup **per-utente** (`PrivilegesRequired=lowest`, quindi `{autopf}` =
|
||||
`%LOCALAPPDATA%\Programs`, cartella `PrepStep`): niente admin, e la cartella resta scrivibile —
|
||||
cosa che serve, perché `cad_index.db` e (di default) la cartella di lavoro con `raw/`
|
||||
stanno accanto all'eseguibile (`AppContext.BaseDirectory`, vedi Persistenza). In `C:\Program Files` fallirebbe per un utente
|
||||
normale. Versione del setup letta dall'`exe` (ProductVersion, tolto il `+<commit>`), quindi
|
||||
basta cambiare `<Version>` nel csproj. L'`AppId` fisso fa riconoscere a Windows le versioni
|
||||
nuove come aggiornamento della stessa app: non va cambiato
|
||||
(`AppGuid` nello script). Se l'app risulta già installata (chiave `…\Uninstall\{AppGuid}_is1`
|
||||
in HKCU), `InitializeSetup` nella sezione `[Code]` chiede se aggiornare o disinstallare (lancia
|
||||
l'`UninstallString`), perché Inno da solo aggiornerebbe sempre; con `/VERYSILENT` non chiede.
|
||||
La disinstallazione rimuove solo
|
||||
i file installati: `cad_index.db`, `raw/`/`step/`/CSV creati accanto all'exe e
|
||||
`%AppData%/PrepStep/` restano.
|
||||
Comandi di build del setup in [`rilascio.md`](rilascio.md).
|
||||
|
||||
## Script standalone (`scripts/`)
|
||||
|
||||
`scripts/psb/` fa eccezione: sono tre file Python (`prt.py`, `psb.py`, `unz.py`), lo strumento
|
||||
del reverse engineering descritto in [`formato-psb.md`](formato-psb.md). Non referenziano il
|
||||
progetto e l'app non li usa.
|
||||
|
||||
C# file-based apps (.NET 10, `dotnet run scripts/Nome.cs -- arg1 arg2`, nessun `.csproj`,
|
||||
dipendenze extra con `#:package Nome@versione` in testa al file):
|
||||
|
||||
- **ExtractCreoStrings.cs** — BOM di un `.asm` risolvendo ricorsivamente i sotto-assiemi
|
||||
contro `cad_index.db` (stessa logica di `AssiemeCompleto` in Form1, solo codici numerici
|
||||
contro `cad_index.db` con `AssemblyComponentScanner` (via `#:project`, solo codici numerici
|
||||
Pro-E).
|
||||
- **BomViaDb.cs** — come sopra ma con la tokenizzazione generica validata contro il DB, per
|
||||
intercettare anche i nomi alfabetici delle parti commerciali.
|
||||
- **CreoBom.cs** — usa `AsmRecordReader` (stesso lettore esatto della GUI, fase 4/5, via
|
||||
- **CreoBom.cs** — usa `AsmRecordReader` (stesso lettore esatto della GUI, fase 4, via
|
||||
`#:project`) invece dell'euristica testuale. Risolve i sottoassiemi cercandoli in cartelle
|
||||
locali (`-p`), non contro `cad_index.db` come `ExtractCreoStrings.cs`/`BomViaDb.cs`.
|
||||
- **Analyze.cs** — dump del contesto testo/hex attorno a un pattern in un file binario, per
|
||||
investigare a mano nuovi falsi positivi prima di toccare la regex di
|
||||
`AssemblyComponentScanner`.
|
||||
- **Query.cs** — query SQL ad-hoc contro `cad_index.db` da riga di comando.
|
||||
- **CheckDocs.cs** — controllo dei doc (docs-as-code): link relativi e ancore, `<Version>` del csproj
|
||||
uguale all'ultima voce del `CHANGELOG.md`, ogni `.cs` di `PrepStep/` e `scripts/` descritto qui.
|
||||
Exit 1 se qualcosa non torna.
|
||||
|
||||
> ⚠️ Nota importante di sicurezza: se `dotnet run` viene lanciato dentro `PrepStep/`
|
||||
> (che contiene un `.csproj` di tipo `WinExe`), esegue l'app GUI vera invece dello script
|
||||
@@ -283,7 +317,7 @@ dipendenze extra con `#:package Nome@versione` in testa al file):
|
||||
## Test
|
||||
|
||||
- **PrepStep.Tests/** (xUnit) — riferimento al progetto principale via `ProjectReference`
|
||||
+ `InternalsVisibleTo` (in `PrepStep.csproj`), così i test chiamano direttamente
|
||||
e `InternalsVisibleTo` (in `PrepStep.csproj`), così i test chiamano direttamente
|
||||
`Indexer.ParseFilename` e `AssemblyComponentScanner.EstraiCodiciReferenziati` senza esporli
|
||||
come API pubblica. File: `IndexerParseFilenameTests.cs`, `IndexerScanTests.cs`,
|
||||
`AssemblyComponentScannerTests.cs`, `AsmRecordReaderTests.cs`,
|
||||
|
||||
+25
-25
@@ -29,10 +29,12 @@ componenti e nel filtro per cartella).
|
||||
|
||||
Vista `files_ultima_versione`: stessa struttura di `files`, filtrata alla sola riga con
|
||||
`versione` più alta per ogni `(cartella_radice, codice, tipo_file)`, tramite `ROW_NUMBER()
|
||||
OVER (PARTITION BY ... ORDER BY versione DESC, data_modifica DESC, percorso_completo)`. Tutte
|
||||
le ricerche di componenti (`Form1.TrovaPercorsiCad`, `Form1.CaricaCodiciNoti`) interrogano
|
||||
questa vista, non `files` direttamente, così un componente con più versioni sulla share
|
||||
risolve sempre all'ultima. Deliberatamente una `ROW_NUMBER()` invece di un self-join con
|
||||
OVER (PARTITION BY ... ORDER BY versione DESC, data_modifica DESC, percorso_completo)`. Le
|
||||
ricerche per codice (`Form1.TrovaPercorsiCad` in fase 3, `Form1.CaricaCodiciNoti`)
|
||||
interrogano questa vista, non `files` direttamente, così un componente con più versioni sulla
|
||||
share risolve sempre all'ultima. Fa eccezione la fase 4 (`Form1.CaricaCandidati`), che legge
|
||||
`files` per avere tutte le copie di un codice in cartelle diverse e scegliere quella giusta
|
||||
(`ScegliPerCartella`). Deliberatamente una `ROW_NUMBER()` invece di un self-join con
|
||||
`GROUP BY`: su ~200k righe un self-join degenera in tempi quasi quadratici, `ROW_NUMBER()`
|
||||
resta lineare.
|
||||
|
||||
@@ -60,8 +62,9 @@ ciascuna — non una sola in assoluto. `TrovaPercorsiCad` gestisce questo caso d
|
||||
|
||||
Chiamato da "Aggiorna indice" (fase 1) e riutilizza lo stesso `dbPath` già caricato.
|
||||
|
||||
1. Crea lo schema se non esiste (`CREATE TABLE/VIEW IF NOT EXISTS`) — quindi funziona sia su
|
||||
un DB vuoto (prima esecuzione) sia su uno esistente (aggiornamento incrementale).
|
||||
1. Crea lo schema se non esiste (`CREATE TABLE/INDEX IF NOT EXISTS`; la vista invece è
|
||||
`DROP`+`CREATE` a ogni scan) — quindi funziona sia su un DB vuoto (prima esecuzione) sia su
|
||||
uno esistente (aggiornamento incrementale).
|
||||
2. Cancella subito tutte le righe con `cartella_radice LIKE 'temp\_%'`: le cartelle
|
||||
`temp_paris`, `temp_davide`, `temp_studi esterni`, ecc. sono lavori personali/temporanei che
|
||||
possono contenere copie datate con lo stesso codice numerico di file "ufficiali" altrove
|
||||
@@ -109,24 +112,20 @@ sulla share, non tipicamente cercato dalla UI).
|
||||
|
||||
## Come l'app trova il DB all'avvio
|
||||
|
||||
`Form1.InizializzaDatabase` prova, in ordine, i seguenti candidati e usa il primo che esiste
|
||||
su disco:
|
||||
`Form1.InizializzaDatabase` carica sempre e solo `cad_index.db` accanto all'eseguibile
|
||||
(`AppContext.BaseDirectory`: con il setup è `%LOCALAPPDATA%\Programs\PrepStep`). Nessun
|
||||
percorso viene ricordato fra un avvio e l'altro.
|
||||
|
||||
1. l'ultimo DB caricato con successo, salvato in
|
||||
`%AppData%/PrepStep/ultimo_db.txt` (`Form1.LeggiUltimoDbUsato`/`SalvaUltimoDbUsato`)
|
||||
2. `cad_index.db` accanto all'eseguibile (`AppContext.BaseDirectory`)
|
||||
3. `cad_index.db` nella working directory corrente
|
||||
|
||||
Se nessuno dei tre esiste, la fase 1 resta bloccata su "Database non trovato" finché l'utente
|
||||
non preme "Apri DB..." (`Form1.ScegliDatabase`, file picker libero) o "Aggiorna indice"
|
||||
(`Form1.AggiornaIndice`, che crea `cad_index.db` da zero accanto all'eseguibile se non c'è
|
||||
ancora un `_dbPath`).
|
||||
Se non esiste, la fase 1 resta bloccata su "Database non trovato" finché l'utente non preme
|
||||
"Aggiorna indice" (`Form1.AggiornaIndice`, che crea `cad_index.db` da zero accanto
|
||||
all'eseguibile) o "Apri DB..." (`Form1.ScegliDatabase`, file picker libero: il DB scelto vale
|
||||
solo per la sessione corrente, e "Aggiorna indice" aggiorna quello).
|
||||
|
||||
**Attenzione in sviluppo (dotnet run / IDE):** `AppContext.BaseDirectory` è la cartella di
|
||||
build (`PrepStep/bin/Release/net10.0-windows` o `Debug/...`), non la radice del repo. Se in
|
||||
quella cartella finisce per errore una copia di `cad_index.db` (es. creata lì da una vecchia
|
||||
sessione), l'app la userà invece di quella nella radice del repo, e le due copie divergeranno
|
||||
a ogni "Aggiorna indice" fatto nell'uno o nell'altro ambiente. Se i risultati di una ricerca
|
||||
build (`PrepStep/bin/Release/net10.0-windows` o `Debug/...`), non la radice del repo. L'app usa
|
||||
quindi la copia di `cad_index.db` in quella cartella (creandola lì al primo "Aggiorna
|
||||
indice"), non quella nella radice del repo né quella installata: per usare un altro DB in
|
||||
sviluppo va aperto con "Apri DB..." a ogni avvio. Se i risultati di una ricerca
|
||||
sembrano incoerenti con quello che ti aspetti di vedere sul DB "giusto", controlla con "Apri
|
||||
DB..." quale file è effettivamente caricato (mostrato in fase 1 accanto al conteggio dei file
|
||||
indicizzati) prima di sospettare un bug nella ricerca.
|
||||
@@ -142,10 +141,11 @@ indicizzati) prima di sospettare un bug nella ricerca.
|
||||
riga con quell'esatto `codice`. In presenza di duplicati su più `cartella_radice`, tiene solo
|
||||
il primo per `tipo_file` (il più recente, dato l'`ORDER BY data_modifica DESC`) e scarta gli
|
||||
altri.
|
||||
- **Risoluzione codici noti per lo scanner assiemi** (`Form1.CaricaCodiciNoti`, usata sia dalla
|
||||
GUI in fase 4 sia da `scripts/BomViaDb.cs`): carica in un'unica query tutti i codici
|
||||
prt/asm noti con i relativi percorsi, invece di una query per codice — usata per validare i
|
||||
token generici trovati nei binari `.asm` (vedi `docs/formato-asm.md`).
|
||||
- **Codici noti per lo scanner assiemi** (`Form1.CaricaCodiciNoti`, fase 4;
|
||||
`scripts/BomViaDb.cs` fa una query equivalente per conto suo): carica in un'unica query tutti
|
||||
i codici prt/asm noti, invece di una query per codice — usata solo per validare i token
|
||||
generici trovati nei binari `.asm` (vedi `docs/formato-asm.md`). I percorsi da copiare
|
||||
vengono invece da `Form1.CaricaCandidati` (tabella `files`, vedi sopra).
|
||||
|
||||
## Debug ad-hoc
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# 0001 — Indice dei file in un DB SQLite locale
|
||||
|
||||
**Stato:** accettata
|
||||
|
||||
## Contesto
|
||||
|
||||
La share `X:\` contiene centinaia di migliaia di file (ordine di ~200k righe indicizzate).
|
||||
Cercare un codice direttamente sul disco di rete a ogni distinta è lento: il costo è dominato
|
||||
dalla latenza di rete.
|
||||
|
||||
## Decisione
|
||||
|
||||
Scansionare la share una volta in un DB SQLite locale (`cad_index.db`) e interrogare quello.
|
||||
L'aggiornamento è incrementale (confronto su percorso, dimensione e data), la vista
|
||||
`files_ultima_versione` usa `ROW_NUMBER()` e non un self-join con `GROUP BY`.
|
||||
|
||||
## Conseguenze
|
||||
|
||||
- Le ricerche sono istantanee e non richiedono la share (la copia sì).
|
||||
- L'indice può essere vecchio: per questo la fase 2 ha "Verifica presenza".
|
||||
- Il DB è rigenerabile, quindi non è tracciato in git.
|
||||
- Schema e criteri di pareggio: [`../db-cad-index.md`](../db-cad-index.md).
|
||||
@@ -0,0 +1,18 @@
|
||||
# 0002 — UI WinForms costruita interamente in codice
|
||||
|
||||
**Stato:** accettata
|
||||
|
||||
## Contesto
|
||||
|
||||
L'app è un wizard a 5 fasi con stile condiviso (bottoni, card, barre) e stato comune tra fasi.
|
||||
|
||||
## Decisione
|
||||
|
||||
Nessun designer visuale: `Form1.BuildUi` e i `BuildFase*` costruiscono i controlli, `UiStile`
|
||||
centralizza lo stile. `Form1.Designer.cs` resta lo scheletro del template.
|
||||
|
||||
## Conseguenze
|
||||
|
||||
- Layout e stile modificabili e rivedibili come codice normale (diff leggibili).
|
||||
- Dimensioni con `AutoSize`+`Padding` invece di pixel fissi, per non tagliare il testo.
|
||||
- `Form1.cs` è grande (~1400 righe) perché contiene wizard e logica delle fasi.
|
||||
@@ -0,0 +1,29 @@
|
||||
# 0003 — Parser esatto dei `.asm` con fallback euristico
|
||||
|
||||
**Stato:** accettata
|
||||
|
||||
## Contesto
|
||||
|
||||
Il `.asm` Creo è un binario proprietario non documentato. La prima soluzione, un'euristica
|
||||
testuale (`AssemblyComponentScanner`), contava le occorrenze di stringhe candidate e produceva
|
||||
falsi positivi (riferimenti fantasma, componenti soppressi con placement residuo).
|
||||
|
||||
Inoltre la lista dei componenti della distinta **non coincide** con i file necessari a Creo per
|
||||
aprire l'assieme: ne servono di più (soppressi, riferimenti esterni). Se ne manca uno Creo
|
||||
risponde solo `[-4] Dati di input non completi`.
|
||||
|
||||
## Decisione
|
||||
|
||||
Per reverse engineering si è decodificata la struttura a record (`AsmRecordReader`), validata
|
||||
contro BOM esportate da Creo. È il metodo preferito. L'euristica resta come **fallback** quando
|
||||
il parser lancia `InvalidDataException`, perché la validazione copre solo un campione di file.
|
||||
La fase 4 tiene tre liste separate (distinta, soppressi, riferimenti esterni) con regole di
|
||||
ricorsione diverse.
|
||||
|
||||
## Conseguenze
|
||||
|
||||
- Estrazione esatta sui file che rispettano il formato; nessun falso positivo da conteggio.
|
||||
- Due implementazioni da mantenere, per ora; l'euristica serve anche a due script.
|
||||
- Ogni caso strano diventa un test minimale costruito byte per byte.
|
||||
- Dettagli: [`../formato-asm-record.md`](../formato-asm-record.md),
|
||||
[`../formato-asm.md`](../formato-asm.md).
|
||||
@@ -0,0 +1,27 @@
|
||||
# 0004 — Setup per-utente con DB nella cartella di installazione
|
||||
|
||||
**Stato:** accettata
|
||||
|
||||
## Contesto
|
||||
|
||||
Gli utenti non hanno diritti di amministratore. L'app scrive `cad_index.db` e, di default, la
|
||||
cartella di lavoro accanto all'eseguibile. In `C:\Program Files` non sarebbe scrivibile.
|
||||
|
||||
Prima il DB era scelto dall'utente e ricordato in `ultimo_db.txt`: si finiva con DB diversi tra
|
||||
postazioni e ricerche incoerenti.
|
||||
|
||||
## Decisione
|
||||
|
||||
- Setup Inno Setup 6 con `PrivilegesRequired=lowest`: installa in
|
||||
`%LOCALAPPDATA%\Programs\PrepStep`.
|
||||
- Il DB è sempre `cad_index.db` nella cartella di installazione; "Apri DB..." vale solo per la
|
||||
sessione e nulla viene ricordato.
|
||||
- `AppId` fisso per riconoscere gli aggiornamenti; se l'app è già installata il setup chiede se
|
||||
aggiornare o disinstallare.
|
||||
|
||||
## Conseguenze
|
||||
|
||||
- Nessun admin necessario; aggiornare non tocca DB e impostazioni.
|
||||
- La disinstallazione lascia DB, `raw/`, `step/`, CSV e `%AppData%\PrepStep\`.
|
||||
- In sviluppo il DB è nella cartella di build ([`../db-cad-index.md`](../db-cad-index.md)).
|
||||
- Procedura: [`../rilascio.md`](../rilascio.md).
|
||||
@@ -0,0 +1,22 @@
|
||||
# 0005 — Scegliere la copia di un componente dalla cartella dell'assieme
|
||||
|
||||
**Stato:** accettata
|
||||
|
||||
## Contesto
|
||||
|
||||
Per il 13% dei codici esiste più di una copia sulla share, con contenuto diverso e stessa
|
||||
versione. Il `.asm` non registra quale copia usa. Scegliere a caso, o per data, può mettere in
|
||||
`raw/` un modello diverso da quello atteso: Creo lo carica, i vincoli non si risolvono e
|
||||
l'assieme non si apre, con il solito `[-4]`.
|
||||
|
||||
## Decisione
|
||||
|
||||
`ScegliPerCartella` preferisce la copia nella stessa cartella dell'assieme che la referenzia
|
||||
(replica la regola di Creo) e solo in mancanza ripiega su versione più alta e data più recente.
|
||||
Nella vista `files_ultima_versione` il pareggio è deterministico (`data_modifica` e percorso
|
||||
nell'`ORDER BY`), ma serve solo a non sbagliare a caso.
|
||||
|
||||
## Conseguenze
|
||||
|
||||
- La fase 4 legge `files` (tutte le copie) e non la vista.
|
||||
- Dettaglio e casi reali: [`../formato-asm-record.md`](../formato-asm-record.md) §7.4.
|
||||
@@ -0,0 +1,21 @@
|
||||
# 0006 — Script standalone che referenziano il progetto principale
|
||||
|
||||
**Stato:** accettata
|
||||
|
||||
## Contesto
|
||||
|
||||
Servono strumenti da riga di comando (BOM, query, analisi di binari) senza duplicare la logica di
|
||||
lettura dei `.asm`.
|
||||
|
||||
## Decisione
|
||||
|
||||
Gli script in `scripts/` sono C# file-based apps (.NET 10) che referenziano
|
||||
`PrepStep/PrepStep.csproj` con `#:project`. La logica vive **solo** nel progetto principale: una
|
||||
sola fonte di verità per GUI e script.
|
||||
|
||||
## Conseguenze
|
||||
|
||||
- Si lanciano dalla root del repo; da dentro `PrepStep/` partirebbe la GUI.
|
||||
- Due famiglie non intercambiabili: euristica + DB (`ExtractCreoStrings`, `BomViaDb`) e parser
|
||||
esatto + cartelle locali (`CreoBom`). Vedi [`../architettura.md`](../architettura.md).
|
||||
- `CheckDocs.cs` segue lo stesso schema per i doc.
|
||||
@@ -0,0 +1,14 @@
|
||||
# Registro delle decisioni
|
||||
|
||||
Perché il programma è fatto così. Ogni voce spiega il contesto, la scelta e le conseguenze, così
|
||||
chi ci rimette mano non rifà il ragionamento da zero. Se cambia una scelta non si riscrive la
|
||||
vecchia voce: se ne aggiunge una nuova che la sostituisce.
|
||||
|
||||
| N. | Decisione | Stato |
|
||||
|---|---|---|
|
||||
| [0001](0001-indice-sqlite-locale.md) | Indice dei file in un DB SQLite locale | accettata |
|
||||
| [0002](0002-ui-winforms-in-codice.md) | UI WinForms costruita interamente in codice | accettata |
|
||||
| [0003](0003-parser-esatto-con-fallback.md) | Parser esatto dei `.asm` con fallback euristico | accettata |
|
||||
| [0004](0004-installazione-per-utente.md) | Setup per-utente con DB nella cartella di installazione | accettata |
|
||||
| [0005](0005-copia-nella-cartella-dell-assieme.md) | Scegliere la copia di un componente dalla cartella dell'assieme | accettata |
|
||||
| [0006](0006-script-che-riusano-il-progetto.md) | Script standalone che referenziano il progetto principale | accettata |
|
||||
@@ -8,8 +8,8 @@ l'euristica testuale ("codice che compare almeno due volte") descritta in
|
||||
struttura effettiva dei record.
|
||||
|
||||
Implementazione: lettura in [`PrepStep/AsmRecordReader.cs`](../PrepStep/AsmRecordReader.cs)
|
||||
(condivisa con la GUI, `Form1.EstraiCodiciReferenziati` in fase "Trova componenti
|
||||
referenziati"/"Verifica completezza" - con fallback su `AssemblyComponentScanner` per i file
|
||||
(condivisa con la GUI, `Form1.EstraiCodiciReferenziati` in fase 4 "Trova componenti
|
||||
referenziati" - con fallback su `AssemblyComponentScanner` per i file
|
||||
che non rispettano il formato validato), resa in formato BOM e ricorsione sui sottoassiemi
|
||||
in [`scripts/CreoBom.cs`](../scripts/CreoBom.cs) (via `#:project`).
|
||||
|
||||
@@ -91,7 +91,7 @@ conteggio dei membri: con la lettura a 1 byte fisso i campioni grandi si
|
||||
disallineavano. Non è confermata da una specifica; regge sui campioni
|
||||
disponibili al momento del reverse engineering.
|
||||
|
||||
In `CreoBom.cs` è `AsmRecordReader.AsInt`, applicata al testo decodificato Latin1 (ogni `char`
|
||||
Nel codice è `AsmRecordReader.AsInt` (privata, in `PrepStep/AsmRecordReader.cs`), applicata al testo decodificato Latin1 (ogni `char`
|
||||
della stringa corrisponde 1:1 a un byte, quindi indicizzare la stringa o l'array di byte
|
||||
originale dà lo stesso offset).
|
||||
|
||||
|
||||
+7
-6
@@ -12,8 +12,8 @@ Un reverse engineering successivo ha decodificato la **struttura a record** del
|
||||
solo pattern testuali): legge esattamente la sezione `members` di un assieme, senza contare
|
||||
occorrenze né rischiare i falsi positivi descritti in questo documento. Implementato in
|
||||
`PrepStep/AsmRecordReader.cs`, documentato in [`formato-asm-record.md`](formato-asm-record.md).
|
||||
È il metodo **preferito** in GUI (fase "Trova componenti referenziati"/"Verifica
|
||||
completezza", `Form1.EstraiCodiciReferenziati`) e nello script `CreoBom.cs`:
|
||||
È il metodo **preferito** in GUI (fase 4 "Trova componenti referenziati",
|
||||
`Form1.EstraiCodiciReferenziati`) e nello script `CreoBom.cs`:
|
||||
`AssemblyComponentScanner` (l'euristica di questo documento) resta come **fallback**, usato
|
||||
solo quando `AsmRecordReader` lancia `InvalidDataException` (file che non rispetta il
|
||||
formato validato dal reverse engineering — copertura limitata agli 8 campioni disponibili al
|
||||
@@ -55,8 +55,9 @@ regex: \b\d{8,12}(?:_[A-Za-z]\d{2,4}|[A-Za-z]{0,2})\b
|
||||
occorrenze: >= 2
|
||||
```
|
||||
|
||||
`scripts/ExtractCreoStrings.cs` ha una versione più raffinata della regex e un filtro
|
||||
aggiuntivo (vedi sotto), non ancora portati in `AssemblyComponentScanner.cs`:
|
||||
La regex effettiva in `AssemblyComponentScanner.cs` (usata anche da
|
||||
`scripts/ExtractCreoStrings.cs` via `#:project`) è una versione più raffinata, con in più il
|
||||
filtro sui falsi positivi descritto sotto:
|
||||
|
||||
```
|
||||
regex: \b\d{8,12}[A-Za-z]{0,2}(?:_[A-Za-z]?\d{2,4})?\b
|
||||
@@ -285,8 +286,8 @@ livello) serve una BFS:
|
||||
Stessa logica di estrazione candidati ed euristica del codice radice (sopra),
|
||||
duplicata in due punti del codice per scopi diversi:
|
||||
|
||||
- [`AssemblyComponentScanner.EstraiCodiciReferenziati`](../PrepStep/AssemblyComponentScanner.cs) + `CodiceRadice` — estrazione dei candidati da un file, usata dalla BFS in [`Form1.RisolviAlberoComponenti`](../PrepStep/Form1.cs) (fase "Trova componenti referenziati") e [`Form1.AssiemeCompleto`](../PrepStep/Form1.cs) (fase "Verifica completezza"). Entrambe seminano l'insieme dei codici visitati con codice + radice di ogni file di partenza, e caricano una volta sola tutti i codici noti all'indice (`Form1.CaricaCodiciNoti`) sia per validare i token generici delle parti commerciali sia per risolvere i percorsi dei componenti trovati, senza una query SQL per candidato.
|
||||
- [`scripts/ExtractCreoStrings.cs`](../scripts/ExtractCreoStrings.cs) e [`scripts/BomViaDb.cs`](../scripts/BomViaDb.cs) — stessa BFS ricorsiva standalone da riga di comando (copia indipendente della stessa euristica, nessun `ProjectReference` verso la GUI). Usati per confrontare l'euristica contro l'albero Creo di un singolo file durante il reverse engineering, senza dover passare dalla UI/dal DB `raw`.
|
||||
- [`AssemblyComponentScanner.EstraiCodiciReferenziati`](../PrepStep/AssemblyComponentScanner.cs) + `CodiceRadice` — estrazione dei candidati da un file, usata come fallback dalla BFS in [`Form1.RisolviAlberoComponenti`](../PrepStep/Form1.cs) (fase 4 "Trova componenti referenziati"). La BFS semina l'insieme dei codici visitati con codice + radice di ogni file di partenza e carica una volta sola i codici noti all'indice (`Form1.CaricaCodiciNoti`, per validare i token generici delle parti commerciali) e tutte le copie di ogni codice (`Form1.CaricaCandidati`, per scegliere il percorso da copiare), senza una query SQL per candidato.
|
||||
- [`scripts/ExtractCreoStrings.cs`](../scripts/ExtractCreoStrings.cs) e [`scripts/BomViaDb.cs`](../scripts/BomViaDb.cs) — stessa BFS ricorsiva standalone da riga di comando, che riusa `AssemblyComponentScanner` del progetto principale via `#:project` (nessuna copia dell'euristica). Usati per confrontare l'euristica contro l'albero Creo di un singolo file durante il reverse engineering, senza dover passare dalla UI/dal DB `raw`.
|
||||
|
||||
Nota: le query contro `files_ultima_versione` filtrano solo per
|
||||
`(cartella_radice, codice, tipo_file)` - se lo stesso codice esiste in più
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,538 @@
|
||||
# Decodifica del `.prt` Creo: numeri, quote e geometria degli schizzi
|
||||
|
||||
Seguito di [`formato-prt-feature.md`](formato-prt-feature.md), che spiega come trovare le sezioni
|
||||
del file e leggere l'**albero** delle feature (ordine, tipo, nome). Qui: come leggere i **numeri**
|
||||
(quote, coordinate) e come ricostruire da quelli la forma di una feature.
|
||||
|
||||
> **Aggiornamento.** La lettura campo per campo di tutte le sezioni, e la tabella completa dei
|
||||
> numeri, sono in [`formato-psb.md`](formato-psb.md), che corregge la §1 di questo doc (vedi lì §9).
|
||||
|
||||
**Stato, da leggere prima di fidarsi.** Questo doc contiene *tutto quello che è stato decodificato
|
||||
e verificato*, e dice per ogni cosa quanto è provata. **Non basta ancora a determinare in modo
|
||||
univoco com'è fatto un `.prt`**: manca il posizionamento delle feature nello spazio e il
|
||||
significato certo di alcuni codici (§7 elenca cosa manca, dove sta nel file, e come finire il
|
||||
lavoro). Nessun codice del progetto lo usa: è reverse engineering fatto con script di prova.
|
||||
|
||||
Livelli di prova, come negli altri doc:
|
||||
|
||||
1. **Contro Creo** (schermate del modello aperto): `9252000507.prt.11`, feature *Rivoluzione 1*
|
||||
(§5). Quote e coordinate dello schizzo coincidono con quelle mostrate da Creo.
|
||||
2. **Coerenza e plausibilità**: `test.prt.1` e le altre feature di `9252000507.prt.11`. I valori
|
||||
sono numeri "tondi" e coerenti fra sezioni diverse, ma nessuno li ha confrontati con Creo.
|
||||
|
||||
## 1. Come sono scritti i numeri reali
|
||||
|
||||
Nelle sezioni a marcatori ([`formato-asm-record.md`](formato-asm-record.md) §2) i `double` sono
|
||||
scritti in forma compatta. Si distinguono per il primo byte, `tag`:
|
||||
|
||||
| Tag (hex) | Byte dopo il tag | Valore | Prova |
|
||||
|---|---|---|---|
|
||||
| `18` | 0 | `0.0` | in ogni sezione |
|
||||
| `0f` | 0 | `1.0` | diagonale delle matrici identità |
|
||||
| `0e` | 0 | `0.5` | **livello 1**: la quota di *Smusso 1* (D = 0.500) è il solo byte `0e` |
|
||||
| `10` | 0 | `-1.0` | ipotesi dal contesto |
|
||||
| `2f` | 2 | `40 b1 b2 00 00 00 00 00` | **livello 1** (12,5; 69,5; 100; 139) e 2 (8, 20, 25, 75) |
|
||||
| `2e` | 2 | `40 b1 b2 b2 b2 b2 b2 b2` (l'ultimo byte si ripete) | **livello 1**: `2e 1b 33` = 6,8, il diametro della punta mostrato da Creo |
|
||||
| `2d` | 7 | `40 b1 .. b7` (precisione piena) | livello 2: `2d 39 7f ff ff ff ff ff` = 25,5 |
|
||||
| `2a` | 2 | `3f b1 b2 00...` | livello 2: `2a f4 00` = 1,25, il passo di M8x1.25 |
|
||||
| `48` | 2 | `c0 b1 b2 00...` (negativo) | **livello 1** (−12,5; −69,5) |
|
||||
| `47` / `46` | 2 / 7 | stessa famiglia di `48`, con ripetizione / precisione piena | non isolati |
|
||||
| `e4`-`e7` | 0 | `K − 3` zeri di seguito, `K` = byte − `e0` (solo dentro le matrici, §6.1) | **livello 1** (matrici di `9239902104`) |
|
||||
| `ed`, `32`... | 8-9 | altri byte alti | non decodificato |
|
||||
|
||||
**La regola, ricavata dai dati** (non da una specifica): a ogni *byte alto* `T` del `double`
|
||||
corrispondono **tre tag consecutivi**, e la base si sposta di 5 per ogni passo di `T`:
|
||||
|
||||
```
|
||||
tag_zero(T) = 0x2f + 5 * (T - 0x40) per i positivi (negativi: 0x48 + 5 * (T - 0xc0))
|
||||
tag_zero -> T, b1, b2 e 5 byte a zero (valori "tondi")
|
||||
tag_zero - 1 -> T, b1, b2 e b2 ripetuto 5 volte (es. 6,8 = 40 1b 33 33 33 33 33 33)
|
||||
tag_zero - 2 -> T e 7 byte pieni (precisione piena)
|
||||
```
|
||||
|
||||
Verifica: `T = 0x40` dà `2f`, `2e`, `2d`; `T = 0x3f` dà `2a` (1,25 con `f4 00`); `T = 0xc0` dà
|
||||
`48`. Funziona solo finché i tag non si sovrappongono: i byte alti `0x41`-`0x43` e `0xb8`-`0xbf`
|
||||
non li ho incontrati e non so se la regola regge (i tag di `0x45` collidono con quelli dei
|
||||
negativi). Per i valori "non tondi" con ripetizione (come 6,8 o 3,4) serve il tag `2e`: senza
|
||||
quello si leggerebbe `6.7998`.
|
||||
|
||||
Come si ricostruisce un valore a 3 byte: si prende il `double` big-endian `40 b1 b2 00...` (per
|
||||
`2f`) oppure `c0 b1 b2 00...` (per `48`). Esempi verificati (`struct.unpack('>d', ...)`):
|
||||
|
||||
| Byte | Valore | Dove |
|
||||
|---|---|---|
|
||||
| `2f 59 00` | `100.0` | quota |
|
||||
| `2f 52 c0` | `75.0` | quota |
|
||||
| `2f 51 60` | `69.5` | coordinata y dello schizzo di Rivoluzione 1 (= 139 / 2) |
|
||||
| `2f 29 00` | `12.5` | coordinata x (= 25 / 2) |
|
||||
| `48 29 00` | `-12.5` | idem, lato opposto all'asse |
|
||||
| `48 51 60` | `-69.5` | idem |
|
||||
| `2f 61 60` | `139.0` | quota (lunghezza) |
|
||||
| `48 59 00` | `-100.0` | estremo della linea d'asse |
|
||||
|
||||
Le unità non sono scritte nel record. Nei campioni sono millimetri, ma il file ha una sezione
|
||||
`UnitSystemDef` (nel contenitore compresso, §1.2 di `formato-prt-feature.md`) che non ho letto:
|
||||
l'unità va presa da lì prima di dare un numero come "mm".
|
||||
|
||||
## 2. Le quote: `dim_array` in `BasFullData`
|
||||
|
||||
Ogni quota (dimensione guida) di ogni feature e di ogni schizzo è un record della sezione
|
||||
`BasFullData`, array `dim_array`. Campi, nell'ordine in cui compaiono:
|
||||
|
||||
```
|
||||
\xe1 nome della quota (qui sempre nullo: il nome "d12" si calcola altrove)
|
||||
\x08 tipo del record
|
||||
<dim_type> 1 byte: classe della quota (tabella sotto)
|
||||
<feat_id> intero (formato asm-record §2.1): la feature a cui appartiene
|
||||
<attributi> \xf8 \x02 ... flag
|
||||
\xe3 <valore> <valore_di_backup> \xe3 ... due numeri uguali: value e bck_value
|
||||
\xe3 \x09 \x01 ... <tolleranza> ... blocco della tolleranza (nominal_value, bound...)
|
||||
```
|
||||
|
||||
Per leggere solo valore e feature basta ancorarsi a `\xe1 \x08 <dim_type> <feat_id>` e leggere il
|
||||
primo numero che segue (§1). Il primo record dell'array è scritto con i nomi di campo per esteso
|
||||
(layout diverso: non l'ho agganciato con lo stesso pattern, quindi **la prima quota dell'array può
|
||||
mancare**).
|
||||
|
||||
`feat_id` è lo stesso id di `AllFeatur` e dello storico (`formato-prt-feature.md` §2 e §5): è il
|
||||
modo per attribuire la quota a "Foro 1" o a "Estrusione 2".
|
||||
|
||||
### 2.1 Le quote lette sui due campioni
|
||||
|
||||
`test.prt.1` (id → feature come in `formato-prt-feature.md` §6):
|
||||
|
||||
| Feature | `dim_type` | Valore | Interpretazione |
|
||||
|---|---|---|---|
|
||||
| Estrusione 1 (79) | 62 | 75 | profondità (ipotesi) |
|
||||
| Estrusione 1 (79) | 4 | 100 | diametro del cerchio dello schizzo: nello schizzo il raggio è 50 (`entity(circle)`, `radius`) |
|
||||
| Foro 1 (100) | 4 | 19 | diametro (ipotesi) |
|
||||
| Foro 1 (100) | 62 | 40 | profondità (ipotesi) |
|
||||
| Arrotondamento 1 (125) | 3 | 3,5 | raggio (ipotesi) |
|
||||
| Estrusione 2 (149) | 62 / 1 / 1 | 11 / 25 / 53 | profondità e due quote dello schizzo (ipotesi) |
|
||||
| Smusso 1 (188) | 9 | 6 | distanza dello smusso (ipotesi) |
|
||||
|
||||
`9252000507.prt.11`:
|
||||
|
||||
| Feature | `dim_type` | Valore | Interpretazione |
|
||||
|---|---|---|---|
|
||||
| Rivoluzione 1 (73) | 2 | **25** | **diametro, confermato da Creo** (Ø25 h7) |
|
||||
| Rivoluzione 1 (73) | 1 | **139** | **lunghezza, confermata da Creo** |
|
||||
| Estrusione 1 / taglio (118) | 62 / 14 / 5 / 20 | 4 / 45 / 8 / 3 | non verificate |
|
||||
| Foro 1 (157) | 4 / 9 / 2 / 10 / 2 | 8 / 20 / 6,8 / 118 / 25 | **confermate da Creo** (§6.4): 8 = diametro nominale, 20 = profondità filetto, 118 = angolo punta, 25 = profondità foro |
|
||||
|
||||
`9239902104.prt.1` (piastra con tre fori, §6.4.1):
|
||||
|
||||
| Feature | `dim_type` | Valore | Interpretazione |
|
||||
|---|---|---|---|
|
||||
| Sketch 1 / Estrusione 1 (84) | 1 / 5 / 1 | 21 / 54 / 125 (+ 46) | quote del profilo a L (§3.2) |
|
||||
| Estrusione 1 (84) | 62 | 6 | profondità, coerente con y = 0 ... 6 delle facce |
|
||||
| Sketch 2 / Foro 1 (124 / 131) | 8 / 2 / 2 / 7 | 38 / 8 / 8 / 30 | quote dei 4 punti (§6.4.1) |
|
||||
| Foro 1 (131) | 4 | 5,5 | diametro |
|
||||
| Sketch 3 / Foro 2 (1095 / 1099) | 2 / 2 | 4 / 5 | distanze del punto dai bordi: l'origine `(67, 6, -5)` = 71 − 4 e 5 |
|
||||
| Foro 2 (1099) | 4 | 5 | diametro |
|
||||
| Foro 3 (1132) | 4 / 9 / 2 / 2 | 3 / 6 / 2,5 / 137,5 | diametro nominale M3, profondità filetto, punta, lunghezza (distanza fino all'estremo, §6.1) |
|
||||
| Sketch 4 / Estrusione 2 (1903 / 1911) | 1 / 62 | 3,5 / 3 | quota dello schizzo e profondità |
|
||||
| Arrotondamento 1 (2777) | 3 | 3 | raggio, confermato dallo storico |
|
||||
|
||||
**Cosa è provato e cosa no.** I valori `25` e `139` di *Rivoluzione 1* sono confermati da Creo
|
||||
(§5). Per tutte le altre righe il *numero* è decodificato in modo plausibile ma il *significato*
|
||||
(profondità? diametro?) è dedotto dal codice `dim_type`, da cui non ho una tabella certa. I codici
|
||||
visti: `1` e `2` quote lineari/diametrali di schizzo, `3` raggio, `4` diametro, `9` distanza
|
||||
(smusso), `62` profondità/estensione, altri (`5`, `10`, `14`, `37`) non interpretati. Per
|
||||
distinguere con certezza serve un confronto riga per riga con un modello noto, o leggere il
|
||||
nome della quota (`d0`, `d1`... del Creo).
|
||||
|
||||
La *tolleranza* (es. `h7` di Ø25) sta nello stesso record, ma non l'ho decodificata: nel campo
|
||||
`tol_class`/`tol_table_index` compaiono indici, non la stringa `h7`.
|
||||
|
||||
## 3. La geometria di uno schizzo: `p_saved_result`
|
||||
|
||||
Ogni schizzo (sezione) ha, dentro `BasFullData`, un blocco:
|
||||
|
||||
```
|
||||
gsec_ptr ... name "Sezione 1" <- nome mostrato nel tree
|
||||
gsec2d_ptr name "S2D0001"
|
||||
segtab_ptr / dimtab_ptr / dim_ref <- segmenti, quote e a cosa si riferiscono
|
||||
...
|
||||
p_saved_result
|
||||
entity(line) ... <- le linee dello schizzo con le coordinate
|
||||
entity(circle) ... <- i cerchi (centro, raggio)
|
||||
local_sys 4x3 <- sistema di coordinate dello schizzo
|
||||
```
|
||||
|
||||
Le coordinate delle entità sono **nel sistema dello schizzo**, non assolute (§6).
|
||||
|
||||
Ogni `entity(line)` ha due estremi con tre coordinate ciascuno `(x, y, z)`; con z sempre `0` nello
|
||||
schizzo. Le coordinate sono numeri come in §1.
|
||||
|
||||
### 3.1 Esempio verificato: *Sezione 1* di Rivoluzione 1
|
||||
|
||||
Decodificando i numeri del blocco `entity(line)` di `9252000507.prt.11` si ottengono quattro
|
||||
linee chiuse e una linea d'asse:
|
||||
|
||||
| Linea | Da `(x, y)` | A `(x, y)` |
|
||||
|---|---|---|
|
||||
| 1 | (0, 69,5) | (−12,5, 69,5) |
|
||||
| 2 | (−12,5, 69,5) | (−12,5, −69,5) |
|
||||
| 3 | (−12,5, −69,5) | (0, −69,5) |
|
||||
| 4 | (0, −69,5) | (0, 69,5) |
|
||||
| asse | (0, −100) | (0, 100) |
|
||||
|
||||
È il rettangolo di larghezza 12,5 (= raggio) e altezza 139, centrato in y sul piano di
|
||||
riferimento, con il lato destro sull'asse x = 0, e una linea d'asse più lunga della figura.
|
||||
**Coincide con quello che Creo mostra** nella vista dello schizzo (rettangolo di 139 di altezza,
|
||||
quotato 25 come diametro rispetto all'asse tratteggiato, simmetrico rispetto a `TOP`).
|
||||
|
||||
Nota: nel file la quota `25` è un **diametro** (Ø25), anche se in geometria il lato è 12,5. Per
|
||||
questo non si può dedurre la forma dalle sole quote: servono anche le linee.
|
||||
|
||||
### 3.2 Secondo esempio: il profilo a L di *Estrusione 1* (`9239902104.prt.1`)
|
||||
|
||||
Le linee del profilo (`entity(line)`, estremi `(x, y)` nel sistema dello schizzo, `z = 0`):
|
||||
|
||||
| Linea | Da | A |
|
||||
|---|---|---|
|
||||
| 1 | (71, 0) | (−54, 0) |
|
||||
| 2 | (−54, 0) | (−54, 46) |
|
||||
| 3 | (−54, 46) | (0, 46) |
|
||||
| 4 | (0, 46) | (0, 21) |
|
||||
| 5 | (0, 21) | (71, 21) |
|
||||
| 6 | (71, 21) | (71, 0) |
|
||||
|
||||
più una linea d'asse. Le quote 125 (= 54 + 71), 54, 46 e 21 sono tutte spiegate dal profilo. Qui le
|
||||
linee stanno in `FeatDefs` (record della feature, sezione `S2D0006`), non in `BasFullData` come in
|
||||
§3: la posizione del blocco `p_saved_result` dipende dal tipo di feature.
|
||||
|
||||
## 4. Da schizzo + quote alla forma: la rivoluzione
|
||||
|
||||
Con le due cose sopra, *Rivoluzione 1* è determinata così:
|
||||
|
||||
```
|
||||
profilo (nel piano dello schizzo): x in [-12,5 ; 0], y in [-69,5 ; 69,5]
|
||||
asse di rotazione: la linea x = 0 (la linea d'asse dello schizzo)
|
||||
angolo: 360 (non decodificato, vedi §7)
|
||||
risultato: cilindro Ø25 x 139, centrato sull'origine lungo y dello schizzo
|
||||
```
|
||||
|
||||
Nell'albero di Creo il cilindro risulta con il fondo sul piano `DTM1` e l'asse come `A_1`: la
|
||||
posizione assoluta (quale piano contiene lo schizzo, come è orientato `y`) **non è nello schizzo**
|
||||
ma nella definizione della feature (§7).
|
||||
|
||||
## 5. Verifica di *Rivoluzione 1* contro Creo
|
||||
|
||||
`9252000507.prt` aperto in Creo (schermate del model tree e dello schizzo):
|
||||
|
||||
| Cosa | Creo | Letto dal file |
|
||||
|---|---|---|
|
||||
| Lunghezza dello schizzo | 139.000 | quota `dim_type 1` = 139 |
|
||||
| Diametro | Ø25 (h7) | quota `dim_type 2` = 25 |
|
||||
| Rettangolo, simmetrico rispetto a `TOP` | sì | y da −69,5 a 69,5 |
|
||||
| Rettangolo a sinistra dell'asse | sì | x da −12,5 a 0 |
|
||||
| Asse (linea di costruzione) | più lungo della figura | da y = −100 a 100 |
|
||||
| Assi A_3, A_4, A_5, punto PNT0 | presenti nel tree | `AllFeatur` + storico (`formato-prt-feature.md`) |
|
||||
|
||||
## 6. Posizione nello spazio
|
||||
|
||||
### 6.1 Matrici 4×3
|
||||
|
||||
Piani di riferimento, assi, sistemi di coordinate, schizzi e feature hanno un `local_sys`: array
|
||||
`f9 04 03` = 4 righe per 3 colonne = 12 numeri: **tre versori e, per ultima, l'origine**. Si leggono
|
||||
con i numeri di §1 più un codice di ripetizione: un byte `e4`-`e7` vale **"altri `K − 3` zeri"**,
|
||||
con `K` = byte − `e0` (`e4` = 1 zero, `e5` = 2, `e6` = 3, `e7` = 4). L'identità è quindi
|
||||
`0f 18 e5 0f 18 e5 0f 18 e5` = `1,0,0,0, 1,0,0,0, 1,0,0,0`.
|
||||
|
||||
**Verificato su `9239902104.prt.1`** (livello 1: i valori coincidono con quelli mostrati da Creo,
|
||||
sotto), con le 4 righe `(e1, e2, e3, origine)`:
|
||||
|
||||
| Feature | Matrice letta | Significato |
|
||||
|---|---|---|
|
||||
| Foro 1 | `(1,0,0) (0,0,-1) (0,1,0)`, origine `(-46, 6, -38)` | uno dei 4 punti dello schizzo `(-46, 38)`: x = x dello schizzo, z = **−**y dello schizzo, y = 6 = faccia superiore (la piastra è spessa 6) |
|
||||
| Foro 2 | `(1,0,0) (0,0,-1) (0,1,0)`, origine `(67, 6, -5)` | 71 − 4 e 5 dal bordo: le due quote 4 e 5 della feature |
|
||||
| Foro 3 | `(0,1,0) (-1,0,0) (0,0,1)`, origine `(-66.5, 3, -5)` | y = 3: l'**offset 3,00** dalla faccia superiore (y = 6) che Creo mostra; z = −5: allineato all'asse di Foro 2 (**"Allinea" con `A_7`**); x = −66,5 = 71 − 137,5, cioè il foro parte 137,5 prima dell'estremo destro (71) |
|
||||
|
||||
Quindi **la posizione di un foro si legge dall'origine della sua matrice**: le quote di
|
||||
posizionamento (4, 5, 3,00) non servono, sono già il risultato. Nei tre fori l'`y` e la `z` tornano
|
||||
esattamente con le quote e con i riferimenti mostrati da Creo.
|
||||
|
||||
Dalla stessa matrice: nel modello di questa piastra lo schizzo di Estrusione 1 sta nel piano `x–z`
|
||||
(x dello schizzo = x, y dello schizzo = −z) e l'estrusione cresce lungo +y fino a 6.
|
||||
|
||||
**Resta incerta l'interpretazione dell'asse del foro.** Per Foro 1 e 2 il terzo versore è `(0,1,0)`
|
||||
(perpendicolare alla piastra, come deve essere). Per Foro 3 è `(0,0,1)`, ma il foro in Creo entra
|
||||
dalla faccia laterale: o la convenzione del versore dell'asse è diversa, o la direzione di foratura
|
||||
sta in un altro campo. Non risolto.
|
||||
|
||||
### 6.2 Dove stanno
|
||||
|
||||
| Cosa | Dove nel file |
|
||||
|---|---|
|
||||
| Piani di riferimento (RIGHT, TOP, FRONT, DTM1) | `ActDatums` (`srf_prim_ptr(plane)` con `local_sys`), `feat_id` = id della feature; vincoli di posizionamento in `FeatDefsDtm` (§6.3) |
|
||||
| Sistema di coordinate (`PRT_CSYS_DEF`) | `MdlRefInfo` (`ent_list(coord_sys)`) e `GeomDepen` |
|
||||
| Assi, punti (A_3, PNT0...) | `ent_list(arc_z)`, `ent_list(point)` in `MdlRefInfo` |
|
||||
| Piano che contiene lo schizzo | `gsec3d_ptr` -> `plane_id`, `ref_planes`; il piano è identificato da un id di geometria |
|
||||
| Orientamento dello schizzo | `sec_orient_data`, `place_instruction_ptrs` (nella definizione di feature `feat_defs_<tipo>`) |
|
||||
| Offset di un piano da un altro (DTM1) | quota di tipo "offset" nel `dim_array` della feature, se esiste |
|
||||
|
||||
### 6.3 Come è posizionato un piano: i vincoli di `FeatDefsDtm`
|
||||
|
||||
La finestra "Piano di riferimento → Posizionamento" di Creo (riferimenti con il loro vincolo,
|
||||
es. *Tangente* a una superficie, *Parallelo* a un piano) **si legge dal file**. Sta nella sezione
|
||||
`FeatDefsDtm`, record `feat_defs_923` (un record per piano; `feat_defs_926` assi, `feat_defs_931`
|
||||
punti, `feat_defs_979` sistemi di assi). Ogni record ha:
|
||||
|
||||
```
|
||||
id id della feature (come in AllFeatur)
|
||||
local_sys matrice 4x3 del piano risultante (§6.1)
|
||||
place_instruction_ptrs una istruzione per ogni riferimento della finestra:
|
||||
type codice del vincolo (intero a 2 byte)
|
||||
offset double, offset del vincolo
|
||||
dim_id, ref_id quota dell'offset e riferimento, se esistono
|
||||
geometry1_id id della geometria a cui ci si riferisce
|
||||
geometry2_id id della geometria che si sta creando (il piano stesso)
|
||||
```
|
||||
|
||||
Gli `id` di geometria (`geom_id`) si risolvono nelle sezioni `ActDatums` (piani e assi di
|
||||
riferimento), `VisibGeom` e `NovisGeom` (superfici del solido, visibili e no). Ogni voce di
|
||||
`srf_array` ha `geom_id`, `geom_type` (`"`, 0x22 = piano; `$`, 0x24 = cilindro) e **`feat_id`**: è il
|
||||
legame con la feature che l'ha generata.
|
||||
|
||||
**Verifica sul caso che hai mostrato**: *DTM1* di `9252000507.prt` (feature 116), finestra di Creo
|
||||
"Supfce:F5(RIVOLUZIONE_1) Tangente / FRONT:F3 Parallelo". Dal file:
|
||||
|
||||
| Istruzione | `type` | `geometry1_id` | Si risolve in | Corrisponde a |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `81 fb` (507) | 88 | `VisibGeom`: cilindro (`$`), `feat_id` 73 = Rivoluzione 1 | "Supfce:F5(RIVOLUZIONE_1)", **Tangente** |
|
||||
| 2 | `81 f9` (505) | 6 | `ActDatums`: piano (`"`), `feat_id` 5 = FRONT | "FRONT:F3", **Parallelo** |
|
||||
|
||||
`geometry2_id` è 117 in entrambe: `ActDatums` lo dà come piano con `feat_id` 116 = DTM1 (il piano
|
||||
risultante). Nella sua matrice compare il valore `12.5` (`2f 29 00`): è il raggio del cilindro,
|
||||
come deve essere per un piano tangente ad esso. Il campo `Traslazione` della finestra era vuoto.
|
||||
|
||||
Quindi **quali sono i riferimenti** (quale feature/superficie e quale piano) si legge con certezza.
|
||||
Resta da fissare:
|
||||
|
||||
- **Il significato di `type`.** 507 ↔ Tangente e 505 ↔ Parallelo è dedotto da un solo esempio e
|
||||
dall'ordine delle righe della finestra: non ho una tabella dei codici. Gli altri visti
|
||||
(`82 01`, `82 02`, `82 03`, `81 fd`, `81 ff`) sono quelli dei piani di default e degli altri
|
||||
piani del campione, non interpretati.
|
||||
- **`Feature number` di Creo** (`F3`, `F5`): non è l'`id` ma la **posizione** nel tree
|
||||
(RIGHT = F1, TOP = F2, FRONT = F3, PRT_CSYS_DEF = F4, Rivoluzione 1 = F5). I piani interni non
|
||||
contano. Per tradurre un `id` in `F<n>` si numerano le feature visibili in ordine
|
||||
(`formato-prt-feature.md` §3).
|
||||
- **L'offset.** Se un vincolo ha un offset (quota di tipo offset) il valore sta nel `dim_array`
|
||||
della feature (§2) con il `dim_id` dell'istruzione; non ho un esempio con offset non nullo.
|
||||
|
||||
### 6.4 Il foro: `feat_defs_911`
|
||||
|
||||
Riferimento: *Foro 1* di `9252000507.prt.11` (feature 157), confrontato con le schermate di Creo.
|
||||
Creo mostra: tipo **Standard**, profilo **Diritto** con **Maschiato**, filetto ISO **M8x1.25**,
|
||||
**profondità 25**, **Ø6,8**, **profondità del filetto 20**, **angolo della punta 118°**,
|
||||
"Includi la superficie del filetto" attivo, posizionamento **Coassiale** con riferimenti
|
||||
`Supfce:F5(RIVOLUZIONE_1)` e `A_1(ASSE):F5(RIVOLUZIONE_1)`.
|
||||
|
||||
La definizione del foro sta nella sezione `FeatDefs`, record `feat_defs_911`. Ha due parti.
|
||||
|
||||
**a) I parametri del filetto, in chiaro** (`data(ddt_plinfo_hole_fstn_param)`):
|
||||
|
||||
| Campo | Valore letto | In Creo |
|
||||
|---|---|---|
|
||||
| `name`, `series` | `ISO`, `ISO` | Tipo filetto: ISO |
|
||||
| `scrsize` | `M8x1.25` | Dimensione vite: M8x1.25 |
|
||||
| `scrsize_val` | `0x1d` (29) | indice di M8x1.25 nella tabella delle viti di Creo (nel segnaposto `M1x.25` vale 0) |
|
||||
| `hole_class` | `H` | (classe del foro) |
|
||||
| `drillnum` | `6.8` | Ø6,8 |
|
||||
|
||||
**b) Le istruzioni** (`place_instruction_ptrs`): una per ogni parametro numerico, nella forma
|
||||
`type`, `offset` (un `double`, §1), `dim_id`, `ref_id`, `geometry1_id`, `geometry2_id`. Quelle
|
||||
del foro filettato, con il valore atteso da Creo:
|
||||
|
||||
| `type` | `offset` letto | In Creo | Prova |
|
||||
|---|---|---|---|
|
||||
| 5104 (`0x13f0`) | 6,8 | Ø6,8 (anche nel foro semplice, vedi sotto) | **livello 1** |
|
||||
| 5528 (`0x1598`) | 118 | angolo della punta | **livello 1** |
|
||||
| 5538 (`0x15a2`) | 25 | profondità del foro | **livello 1** |
|
||||
| 5536 (`0x15a0`) | 1,25 | passo del filetto (M8**x1.25**) | livello 2 |
|
||||
| 6617 (`0x19d9`) | 11 | non corrisponde a nulla di mostrato | non interpretato |
|
||||
| 5542, 10103, 5533, 5534 | 0 | non mostrati | non interpretati |
|
||||
|
||||
Il codice `type` identifica il parametro; il valore è `offset`. La profondità del filetto (20) e il
|
||||
diametro nominale (8) **non** sono istruzioni ma quote del `dim_array` (§2), con `dim_type` 9 e 4
|
||||
e `feat_id` 157. In tutto il `dim_array` ha per il foro cinque quote: 8 (`dim_type` 4), 20 (9),
|
||||
Ø6,8 (2, scritto con il tag `2e`), 118 (10) e 25 (2). Tre di queste (6,8, 118, 25) compaiono
|
||||
quindi due volte, come quota e come parametro di un'istruzione.
|
||||
|
||||
**Foro semplice e foro standard.** `test.prt.1` ha un foro **semplice**: il suo `feat_defs_911`
|
||||
è lungo 862 byte e ha solo due istruzioni (`5104` con `dim_id` 7, e `1853`); i parametri del
|
||||
filetto ci sono comunque, ma con i valori segnaposto di Creo (`M1x.25`, `drillnum 0.75`,
|
||||
`scrsize_val 0`): **quindi le stringhe `scrsize`/`drillnum` da sole non dicono se il foro è
|
||||
filettato**. Quello di Fata ha 3212 byte e 12 istruzioni, valori diversi dal segnaposto. Il segno
|
||||
affidabile oggi è: **foro standard = presenza delle istruzioni 5536 (passo) e 5528 (angolo punta)**,
|
||||
e il foro è filettato quando anche i valori del filetto non sono quelli segnaposto.
|
||||
|
||||
**Non individuato nel file** (per ognuno servono due salvataggi che differiscono solo per
|
||||
quell'opzione):
|
||||
|
||||
- *Maschiato / Trapanato / Gioco*, *Diritto / Rastremato*: non so quale istruzione o flag le
|
||||
codifichi. `plin_info_hole_flag.flags` vale `2, 2` in entrambi i fori.
|
||||
- *Includi la superficie del filetto*: idem.
|
||||
- Il tipo *Standard*/*Semplice* come campo esplicito: oggi si deduce solo dal numero di istruzioni.
|
||||
|
||||
**Posizionamento Coassiale.** Gli ingredienti ci sono: un blocco `data(plin_info_ref)` per ogni
|
||||
riferimento, con gli id di geometria. Il piano del riferimento `Supfce:F5` dovrebbe essere la
|
||||
faccia piana di *Rivoluzione 1* (feature 73, in `VisibGeom` con `feat_id` 73) e il secondo
|
||||
riferimento l'asse `A_1`: non ho agganciato quegli id perché le coppie `id_type1`/`id_type2` non le
|
||||
ho decifrate. La geometria generata dal foro (cilindro, cono, piani con `feat_id` 157) è invece
|
||||
in `VisibGeom` e `NovisGeom`, e `geometry2_id` 175 delle istruzioni è un suo piano.
|
||||
|
||||
### 6.4.1 Foro a punti di schizzo e facce piane: `9239902104.prt.1`
|
||||
|
||||
*Foro 1* è definito da *Sketch 2* con **4 punti**. Cosa si legge, tutto da `local_sys` (§6.1):
|
||||
|
||||
**I punti.** Nello schizzo (`entity(point)`, campo `pnt`, un `[x, y, z]` per punto) sono
|
||||
`(-46, 8)`, `(-8, 8)`, `(-8, 38)`, `(-46, 38)`, con quote 8, 8, 30 e 38. Nel modello ogni punto
|
||||
diventa un'**istanza** del foro, e ogni istanza ha un piano proprio in `NovisGeom` (superficie
|
||||
piana con `feat_id` 131, §6.3) con la matrice del punto: l'**origine** è la posizione assoluta.
|
||||
|
||||
| Piano (`geom_id`) | Origine nel modello |
|
||||
|---|---|
|
||||
| 135 | `(-46, 6, -8)` |
|
||||
| 147 | `(-8, 6, -8)` |
|
||||
| 159 | `(-8, 6, -38)` |
|
||||
| 171 | `(-46, 6, -38)` |
|
||||
|
||||
Quindi `x` = `x` dello schizzo, `z` = **−**`y` dello schizzo (confermato dai quattro punti e da Foro
|
||||
2, §6.1) e `y` = 6.
|
||||
|
||||
**La faccia.** La superficie su cui stanno è la faccia piana superiore di *Estrusione 1*: ha
|
||||
`y = 6` e la stessa matrice del sistema di coordinate dello schizzo (origine `(0, 6, 0)`,
|
||||
versori `(1,0,0)`, `(0,0,1)`, `(0,-1,0)`). Le facce piane di *Estrusione 1* (`feat_id` 84, in
|
||||
`VisibGeom`) sono, con origine e normale (il terzo versore):
|
||||
|
||||
| `geom_id` | Origine | Normale | Faccia |
|
||||
|---|---|---|---|
|
||||
| 95 | `(0, 6, 0)` | `(0,-1,0)` | **superiore** (`y = 6`), dove sono i fori |
|
||||
| 107 | `(71, 0, 0)` | `(-1,0,0)` | estremo destro (`x = 71`) |
|
||||
| 113 | `(-54, 0, -46)` | `(1,0,0)` | estremo sinistro (`x = -54`) |
|
||||
| 115 | `(-54, 0, 0)` | `(0,0,1)` | lato `z = 0` |
|
||||
| 2999 | `(71, 0, -21)` | `(0,0,-1)` | lato del gradino (`z = -21`) |
|
||||
| 3001 | `(0, 0, -21)` | `(-1,0,0)` | alzata del gradino (`x = 0`) |
|
||||
| 3003 | `(0, 0, -46)` | `(0,0,-1)` | lato `z = -46` |
|
||||
|
||||
La piastra a L di Estrusione 1 (§3.2, profilo `x` da −54 a 71, `y` dello schizzo da 0 a 46, gradino
|
||||
a 21) torna in coordinate modello con `z = −y` dello schizzo, e spessore 6 lungo `y`. Gli altri
|
||||
tre fori cadono dentro il materiale (`x` −46 e −8, `z` −8 e −38).
|
||||
|
||||
**Non risolto:** quale `geom_id` il foro usi *formalmente* come faccia di riferimento (95, per
|
||||
coincidenza di piano e di origine) non è scritto in un campo che io abbia identificato: è
|
||||
un'identificazione per posizione, non una lettura.
|
||||
|
||||
### 6.5 Lo smusso: `feat_defs_914`
|
||||
|
||||
Riferimento: *Smusso 1* di `9252000507.prt.11` (feature 252), in modifica in Creo: modalità
|
||||
**Insiemi**, schema quote **D x D** con **D = 0.500**, un insieme ("Insieme 1") con **due
|
||||
riferimenti**, entrambi `Spigolo:F5(RIVOLUZIONE_1)`, e l'opzione "Superfici con offset".
|
||||
|
||||
| Cosa | Dove | Letto | Prova |
|
||||
|---|---|---|---|
|
||||
| Valore D | `dim_array`, `feat_id` 252, `dim_type` 9 | `0e` = **0,5** | **livello 1** |
|
||||
| Valore in `test.prt.1` | `dim_array`, feat 188, `dim_type` 9 | `2f 18 00` = 6,0 | livello 2 |
|
||||
| Superfici del bordo | `feat_defs_914`, istruzioni di tipo 4530 | `geometry1_id` = 88, 86, 106, 90 | vedi sotto |
|
||||
| Superfici generate dallo smusso | `VisibGeom`, `feat_id` 252 | superfici coniche (`geom_id` 253, 258, 261, 266) | presenti |
|
||||
|
||||
Il `dim_type` 9 è lo stesso del foro (profondità del filetto): dice la *classe* della quota, non
|
||||
cosa misura. Per lo smusso vale D, ma la stessa classe in un foro vale 20.
|
||||
|
||||
**I riferimenti.** Le istruzioni di tipo `4530` hanno nel campo `geometry1_id` gli id 88, 86, 106 e
|
||||
90, ripetuti a coppie. Risolti con `VisibGeom`: **88** e **106** sono superfici cilindriche con
|
||||
`feat_id` 73 (cioè *Rivoluzione 1*, F5), **86** è il piano di estremità della stessa rivoluzione
|
||||
(visto in §3.1 e §6.3). Quindi i riferimenti `Spigolo:F5(RIVOLUZIONE_1)` sono spigoli ricavati dalle
|
||||
superfici di Rivoluzione 1, come mostra Creo. Non so ancora quale coppia di id formi quale
|
||||
spigolo, né a quale dei due "Spigolo" della lista corrisponda (le istruzioni non sono in un ordine
|
||||
che io abbia decifrato), e **90** non l'ho risolto.
|
||||
|
||||
**Non decodificato:**
|
||||
|
||||
- lo schema quote (D x D, D1 x D2, angolo x D...): non ho trovato dove sta; compaiono vari tipi di
|
||||
istruzione (`7350`, `7360`, `4549`, `4552`, `4557`, `4558`, `1125`, `7265`, `14741`) senza
|
||||
corrispondenza con le voci mostrate;
|
||||
- "Insieme 1" e la sua composizione: compare la struttura (`data(plin_info_chut_info)`, array di
|
||||
riferimenti) ma non la ho letta;
|
||||
- "Superfici con offset" e il tipo di transizione;
|
||||
- l'istruzione `14741`, con un valore `11,476` in precisione piena: non corrisponde a niente di
|
||||
mostrato.
|
||||
|
||||
**Foro filettato con posizionamento lineare** (*Foro 3* di `9239902104.prt.1`). Creo mostra: tipo
|
||||
Standard, Diritto, Maschiato, ISO **M3x.5**, Ø2,50 (punta), campo profondità vuoto, posizionamento
|
||||
**Lineare** con riferimento primario `Supfce:F6(ESTRUSIONE_1)` e due riferimenti di offset
|
||||
(`Supfce:F6(ESTRUSIONE_1)` con offset 3,00 e `A_7(ASSE):F10(FORO 2)` allineato). Dal file:
|
||||
|
||||
| Informazione | Letto | Prova |
|
||||
|---|---|---|
|
||||
| Filetto | stringhe `ISO`, `M3x.5`, punta `2.5`; `scrsize_val` = 12 | concorda con Creo |
|
||||
| Ø della punta | istruzione 5104, offset 2,5 | concorda con Creo (Ø2,50) |
|
||||
| Quote del foro | 3 (`dim_type` 4), 6 (9), 2,5 (2), 137,5 (2) | numeri letti; 137,5 con campo "Profondità" vuoto in Creo: probabilmente un foro passante, valore non verificato |
|
||||
| Posizione risultante (offset 3,00 e allineamento a `A_7`) | **letta dalla matrice** `local_sys` (§6.1): origine `(-66.5, 3, -5)`, con y = 3 e z = −5 uguali a quelli di Foro 2 | concorda con Creo; la quota 3,00 non è una quota a sé nel `dim_array` |
|
||||
| Riferimenti `F6` e `A_7` | non risolti | gli id stanno in blocchi `plin_info_ref` come per DTM1 (§6.3) |
|
||||
|
||||
Correzione: il primo codice delle istruzioni è 5542 anche qui, come nel foro coassiale di
|
||||
`9252000507`; il `12266` che avevo letto nella prima versione di questo doc veniva da un'istruzione
|
||||
di un altro record. Il tipo di posizionamento (coassiale / lineare) **non** è quel codice. Quale sia
|
||||
non lo so: la posizione finale si legge comunque dalla matrice (§6.1).
|
||||
|
||||
## 7. Cosa manca per determinare un `.prt` in modo univoco
|
||||
|
||||
Tabella di verità: ogni informazione necessaria, se l'ho decodificata e quanto è provata.
|
||||
|
||||
| Informazione | Stato | Dove si trova |
|
||||
|---|---|---|
|
||||
| Elenco e ordine delle feature | **fatto**, provato (`test.prt.1`, `9252000507.prt.11` contro Creo) | `AllFeatur` |
|
||||
| Tipo di ogni feature | **fatto**; estrusione vs rivoluzione solo con lo storico | `AllFeatur`, storico |
|
||||
| Nome di ogni feature | **fatto**, ma solo se esiste lo storico (10 file su 31 leggibili) | `MdlStatus` |
|
||||
| Quote (valori numerici) | **fatto per i valori interi/semplici**, tag `2f`/`48` | `dim_array` |
|
||||
| Significato di ogni quota | **parziale**: provato solo per Rivoluzione 1 | `dim_type` (§2.1) |
|
||||
| Geometria dello schizzo (linee) | **fatto** per le linee, provato su Rivoluzione 1 | `p_saved_result` |
|
||||
| Cerchi, archi, spline dello schizzo | non verificato (cerchio visto in `test.prt.1`, raggio 50) | `entity(circle)`, `entity(arc_z)`, `entity(spline)` |
|
||||
| Valori non "tondi" | **non decodificato** (tag diversi da `2f`/`48`) | ovunque |
|
||||
| Piano e orientamento dello schizzo | **non decodificato** | `gsec3d_ptr`, `sec_orient_data` |
|
||||
| Posizione/rotazione di fori e feature | **fatto**: matrice 4×3 (§6.1) | `local_sys` |
|
||||
| Posizione/rotazione di piani, assi, punti | stessa matrice, da verificare su un piano ruotato | `ActDatums`, `MdlRefInfo` |
|
||||
| Vincoli e riferimenti di un piano (come DTM1) | **fatto per DTM1 di `9252000507`** (riferimenti sì, nome del vincolo da un esempio) | `FeatDefsDtm`, §6.3 |
|
||||
| Offset di un piano da un altro | **non decodificato**: mai visto un offset non nullo | quota in `dim_array` (`dim_id`) |
|
||||
| Direzione/lato e angolo di una rivoluzione, profondità e direzione di un'estrusione | **non decodificato** | `feat_defs_917`/`916` (`place_instruction_ptrs`, `plin_info_arr`) |
|
||||
| Parametri del foro (misura, punta, passo, profondità, angolo) | **fatto**, provato contro Creo su `9252000507` | `feat_defs_911`, §6.4 |
|
||||
| Opzioni del foro (maschiato, diritto, superficie del filetto) | **non individuato** | §6.4 |
|
||||
| Posizione assoluta del foro (origine e versori) | **fatto**, provato contro i riferimenti di Creo su `9239902104` (§6.1) | `local_sys` del record |
|
||||
| Riferimenti del posizionamento (quale faccia/asse) | **non decodificato** | `plin_info_ref` in `feat_defs_911` |
|
||||
| Valore di raccordi e smussi | **fatto**, provato contro Creo su `9252000507` (D = 0,5) | `dim_array` |
|
||||
| Spigoli a cui si applicano raccordi/smussi | **parziale**: si risolvono le superfici (feature di origine), non lo spigolo né l'insieme | `feat_defs_914`, §6.5 |
|
||||
| Schema quote e insiemi dello smusso | **non decodificato** | `feat_defs_914` |
|
||||
| Tolleranze (h7...) | **non decodificato** | blocco `tol_ptr` del `dim_array` |
|
||||
| Unità di misura | **non letto** | `UnitSystemDef` (sezione compressa) |
|
||||
| Feature nascoste / soppresse | **non decodificato** | non individuato nel file |
|
||||
| Parametri e materiale | parziale, via `ND:0:Model_*` (XML) | sezione compressa `.Z` |
|
||||
| File Wildfire (generazioni 1322/1375/1376) | **illeggibile** (sezioni di tipo `11`) | `formato-prt-feature.md` §1.3 |
|
||||
|
||||
**Conclusione onesta:** oggi si ricava con affidabilità *quali* feature ci sono, *che tipo* sono,
|
||||
*come si chiamano* (se c'è lo storico), *quanto misurano* le quote semplici e *che forma ha lo
|
||||
schizzo* se fatto di linee con numeri semplici. Non si ricava ancora *dove* stanno nello spazio né
|
||||
*come* sono orientate, né si può ricostruire il solido completo.
|
||||
|
||||
## 8. Come finire il lavoro
|
||||
|
||||
Per ogni riga "non decodificato" della §7 il metodo è lo stesso, ed è quello che ha dato Rivoluzione
|
||||
1:
|
||||
|
||||
1. Scegliere un `.prt` piccolo con **una sola feature** del tipo da decodificare, e annotare da
|
||||
Creo i valori veri (quote, posizione dei piani, angolo, direzione).
|
||||
2. Estrarre da `BasFullData`/`ActDatums` i record di quella feature e cercarvi quei numeri con il
|
||||
decodificatore di §1: i valori noti ancorano i byte.
|
||||
3. Variare **una cosa alla volta** nel modello (spostare il piano, cambiare l'angolo), risalvare e
|
||||
confrontare i due file: i byte che cambiano sono il campo cercato.
|
||||
4. Registrare in questo doc il campo, con il file di prova e il valore atteso, come in §5.
|
||||
|
||||
Tre modelli di prova che chiuderebbero la maggior parte della §7: un piano `DTM1` a offset non nullo
|
||||
dal piano `TOP` (e con un vincolo *Attraverso*, *Normale*, *Offset*...); una estrusione di profondità nota su un piano ruotato; una rivoluzione di 90°.
|
||||
|
||||
Una volta chiusa la §7 il passo successivo è un `PrtModelReader` in C# accanto ad `AsmRecordReader`,
|
||||
con le stesse regole: se un valore non si decodifica si solleva `InvalidDataException`, non si
|
||||
restituisce un modello parziale in silenzio.
|
||||
@@ -0,0 +1,371 @@
|
||||
# Il formato PSB: leggere tutte le sezioni di un `.prt`/`.asm` senza Creo
|
||||
|
||||
Come è scritto **per intero** il contenuto di un file Creo, e come leggerlo campo per campo con
|
||||
nome e valore, senza àncore a pattern. Ricavato per reverse engineering su
|
||||
`9253000117a.prt.35` (Creo 2.0 M200, generazione `1632`, 2 MB) e poi provato sugli 89 file di
|
||||
`raw/`. Il nome "PSB" è quello che il formato dà a se stesso (`PSB_Tokens`, §7).
|
||||
|
||||
Questo doc **sostituisce** le parti di [`formato-prt-feature.md`](formato-prt-feature.md) e
|
||||
[`formato-prt-geometria.md`](formato-prt-geometria.md) che leggevano i record cercando sequenze
|
||||
di byte: quelle àncore restano valide come esempi, ma il modo giusto di leggere è quello qui
|
||||
sotto. Le correzioni puntuali a quei due doc sono in §9.
|
||||
|
||||
Implementazione di riferimento: [`scripts/psb/psb.py`](../scripts/psb/psb.py) (parser),
|
||||
[`scripts/psb/prt.py`](../scripts/psb/prt.py) (contenitore),
|
||||
[`scripts/psb/unz.py`](../scripts/psb/unz.py) (sezioni compresse). È Python (solo libreria
|
||||
standard), non C#: è lo strumento con cui è stato fatto il reverse engineering, non codice
|
||||
dell'app. Nessun codice di `PrepStep/` lo usa.
|
||||
|
||||
**Livelli di prova** (come negli altri doc):
|
||||
|
||||
1. **Verifica interna forte**: la sezione si legge fino all'ultimo byte, con i conteggi degli
|
||||
array rispettati; i numeri reali sono controllati su vincoli geometrici (righe delle matrici
|
||||
di rotazione a norma 1: 5397 su 5397 in `9253000117a.prt.35`).
|
||||
2. **Su scala**: 2152 sezioni su 2226 lette fino in fondo negli 89 file di `raw/` (tutte le
|
||||
generazioni, da `1322` a `2063`). Le 74 che falliscono sono in §8.
|
||||
3. **Contro Creo**: nessuna. Nessun valore di questo doc è stato confrontato con il modello
|
||||
aperto in Creo. I nomi dei campi sono quelli scritti nel file; il loro *significato* è
|
||||
dedotto dal nome.
|
||||
|
||||
## 1. Il contenitore
|
||||
|
||||
```
|
||||
#UGC:2 PART 1632 1060 805 1 1 15 3300 2015440 000002cc \
|
||||
...
|
||||
#-END_OF_UGC_HEADER
|
||||
#Creo TM 2.0 (c) 2020 by PTC Inc. All Rights Reserved. M200
|
||||
#UGC_TOC 2 32 81 17####...
|
||||
DEPDB_DATA ab1 19 d 1632 9 -1 05e1 260f####
|
||||
...
|
||||
NEXT_TOC_ENTRY 1f8679 3cc 0 1632 1 -1 0000 0000####
|
||||
```
|
||||
|
||||
- **L'ultimo campo della prima riga** (`000002cc`) è la lunghezza dell'intestazione: è la
|
||||
costante da sommare a tutti gli offset dell'indice. `inizio = 0x2cc + offset`.
|
||||
- Ogni sezione comincia con una riga `#<Nome>\n`, **compresa** nell'offset e nella lunghezza.
|
||||
Il corpo è quindi `[0x2cc + offset + len(nome) + 2, 0x2cc + offset + lunghezza)`.
|
||||
- **L'indice è a catena**: la voce `NEXT_TOC_ENTRY` punta (stessa regola) a un secondo
|
||||
`#UGC_TOC`, in fondo al file, con altre sezioni. Vanno seguiti tutti fino a `#END_OF_UGC`.
|
||||
- Campi di una riga: `nome offset lunghezza dimensione generazione tipo metodo crc crc`.
|
||||
`dimensione` è la lunghezza del corpo **decompresso** (per le sezioni in chiaro è
|
||||
`lunghezza − len(nome) − 2`).
|
||||
|
||||
Il campo `tipo` in questo file:
|
||||
|
||||
| Tipo | Contenuto | Sezioni |
|
||||
|---|---|---|
|
||||
| `9` | PSB in chiaro | quasi tutte |
|
||||
| `a` | PSB compresso Unix `compress` (`1f 9d 10`, LZW a 16 bit): si decomprime e si legge come le altre; la dimensione ottenuta coincide con quella dichiarata | `ND:0:*`, `SolidPersistTable`, `SolidPrimdata` |
|
||||
| `c` | comincia con `1f 9d 10` ma **non** è un LZW standard | `DispDataTable` (non letto) |
|
||||
| `11` | JPEG (`ff d8 ff e0 ... JFIF`): la miniatura del modello | `THMB_IMG_MAIN` |
|
||||
| `b` | non decodificato: primi 4 byte in chiaro (`e0 00 Fi`, `e0 00 Fe`), poi entropia massima, stessa lunghezza del testo in chiaro | `IndentTag`, `FeatRefData` |
|
||||
|
||||
Il tipo `b` ha lo stesso aspetto delle sezioni "tipo `11`" dei file Wildfire descritte in
|
||||
`formato-prt-feature.md` §1.3. Resta il blocco principale.
|
||||
|
||||
## 2. La grammatica
|
||||
|
||||
Una sezione è un albero di campi. I byte `e0`-`ff` sono marcatori, gli altri dati.
|
||||
|
||||
| Byte | Significato |
|
||||
|---|---|
|
||||
| `e0 <tipo> <nome>\0` | **dichiara** un campo e ne dà subito il valore |
|
||||
| `e1` | puntatore nullo |
|
||||
| `e2`, `e3` | apre una struttura (non distinte dal lettore) |
|
||||
| `e4`, `e5`, `e6` | dentro un array: ripeti il valore precedente 1, 2, 3 volte |
|
||||
| `e7 <n>` | dentro un array: ripeti il valore precedente `n` volte |
|
||||
| `e8`-`ed` | forme "grezze" dei numeri (§3) |
|
||||
| `f0`-`f3` | chiudi 0, 1, 2, 3 contenitori (§2.3) |
|
||||
| `f4 <n>` | chiudi `n` contenitori |
|
||||
| `f5` | prefisso di un intero: bit 31 acceso |
|
||||
| `f6` | intero −1 |
|
||||
| `f7 <id>` | il prossimo valore è del campo numero `id` (§2.2) |
|
||||
| `f8 <n>` | array a una dimensione di `n` elementi |
|
||||
| `f9 <a> <b>` | array a due dimensioni (`a`×`b` numeri; per gli array di strutture `a` è il totale) |
|
||||
| `fa <k> <d1>..<dk>` | array a `k` dimensioni |
|
||||
| `fb` | segue l'intestazione di un array di strutture, prima del primo elemento |
|
||||
| `fc <n>` | array a due dimensioni: cambia solo il numero di righe, colonne come l'ultima volta |
|
||||
|
||||
Tipi di campo (il byte dopo `e0`):
|
||||
|
||||
| Tipo | Valore |
|
||||
|---|---|
|
||||
| `00` | puntatore: `e1`, struttura (`e2`/`e3`), o array di strutture (`f8 n [f7 id] [fb]`) |
|
||||
| `01`, `05`, `08`, `09` | intero (§3.1). `09` è senza segno (flag); `05`/`08` non distinti |
|
||||
| `02` | `double` (§3.2) |
|
||||
| `06` | `float` a 32 bit (§3.3) |
|
||||
| `04`, `07` | array di byte: ogni byte vale se stesso; un byte ≥ `e0` è preceduto da `e8` |
|
||||
| `03`, `0a` | stringa Latin1 terminata da `\0`, o `e1` |
|
||||
|
||||
Un campo numerico o stringa può essere un array: `f8`/`f9`/`fa` prima dei valori.
|
||||
|
||||
### 2.1 Gli id dei campi
|
||||
|
||||
Ogni `e0` riceve un **numero progressivo** (dal primo `e0` della sezione = 1). Lo stesso nome in
|
||||
due strutture diverse ha due id diversi. È la chiave di tutto: `f7 <id>` si riferisce a quel
|
||||
numero. Verifica: l'intestazione di ogni array di strutture ripete l'id del proprio campo
|
||||
(`unit_arr` è il 20° campo di `BasicText` e l'array è `f8 03 f7 14 fb`).
|
||||
|
||||
### 2.2 Modalità posizionale: la tabella "dopo X viene Y"
|
||||
|
||||
Un campo è scritto col nome **una sola volta**. Dopo, i valori arrivano nudi. Per sapere di
|
||||
quale campo è un valore nudo, chi scrive e chi legge tengono la stessa tabella:
|
||||
|
||||
```
|
||||
next[id] = l'id del campo scritto subito dopo, l'ultima volta che id è stato scritto
|
||||
```
|
||||
|
||||
- Se il prossimo campo è `next[ultimo]`, il valore è scritto e basta.
|
||||
- Altrimenti è preceduto da `f7 <id>`.
|
||||
- La tabella è **piatta** (non tiene conto dell'annidamento) e si aggiorna a ogni campo, anche
|
||||
dopo un `f7` e dopo un campo dichiarato per nome.
|
||||
- Ogni elemento di un array di strutture è una nuova occorrenza del campo-array: per questo
|
||||
dopo il primo elemento si trova `f1 f7 <id array>` (insegna che "dopo l'ultimo campo
|
||||
dell'elemento viene un altro elemento") e dopo gli altri non c'è nulla.
|
||||
- Un campo mai dichiarato e nullo **non viene scritto**: compare col nome la prima volta che ha
|
||||
un valore, anche in mezzo a un record posizionale.
|
||||
|
||||
**Lunghezza degli array in modalità posizionale**: `f8 <n>` compare solo se `n` è diverso
|
||||
dall'ultima volta che quel campo è stato scritto. Eccezione osservata: un array di stringhe più
|
||||
corto del precedente arriva senza `f8` (`edit_header` in `BasicData`: 6 stringhe, poi 5); il
|
||||
lettore si ferma al primo byte < `20`. È un'euristica, non una regola capita.
|
||||
|
||||
### 2.3 Dove va un campo: la pila dei contenitori
|
||||
|
||||
Strutture, array e loro elementi sono contenitori su una pila. Tre regole:
|
||||
|
||||
1. **Valore nudo senza `f7`**: va nel contenitore più vicino alla cima che possiede quel campo;
|
||||
quelli sopra si chiudono in silenzio.
|
||||
2. **Campo con nome o `f7`, con marcatore `f0`-`f4`**: si chiudono esattamente quel numero di
|
||||
contenitori (`f0` = nessuno). Il marcatore sta **dopo** il nome del campo.
|
||||
3. **Campo con nome o `f7`, senza marcatore**: se il campo atteso (`next[ultimo]`) appartiene al
|
||||
contenitore **padre** di quello in cui è stato scritto l'ultimo campo, quest'ultimo si chiude;
|
||||
altrimenti non si chiude nulla.
|
||||
|
||||
Casi particolari, tutti osservati:
|
||||
|
||||
- **Alias di tipo**: `e3 f7 <id>` con `id` di un'altra struttura vuol dire "questa struttura ha
|
||||
lo stesso schema di quella" (`next_feat_ptr 00 f2 e3 f7 03`: il record successivo usa i campi
|
||||
del primo).
|
||||
- **Puntatori "al successivo"** (`next_feat_ptr`, `next_cntr_ptr`, `relcode_tree_next`): sono
|
||||
scritti **dopo aver chiuso** la struttura a cui appartengono, quindi al livello del padre. Le
|
||||
liste concatenate risultano così sequenze piatte, non annidamenti.
|
||||
- **Campi a livello di array**: `next` in `ref_part_table` è dichiarato dopo aver chiuso il
|
||||
primo elemento e ricompare (`e1`) dopo ogni elemento.
|
||||
- **Varianti di unione**: il nome porta la variante tra parentesi (`data(sld_trace)`,
|
||||
`srf_prim_ptr(cylinder)`, `typed_data(MTTyped_CreateData)`). Lo stesso id può comparire in
|
||||
contenitori diversi.
|
||||
|
||||
**Cosa è solido e cosa no.** La sequenza *campo → valore* dipende solo da `next[]` e dai tipi:
|
||||
è la parte provata (tutte le sezioni lette fino all'ultimo byte). L'annidamento dipende dalle
|
||||
regole 1-3, che sono ricavate per tentativi: in `AllFeatur`, `MdlStatus` e `FeatDefs` di
|
||||
questo file la pila alla fine non torna a zero (un contenitore in più o in meno). I valori sono
|
||||
giusti, il livello di qualche campo può non esserlo.
|
||||
|
||||
## 3. I numeri
|
||||
|
||||
### 3.1 Interi
|
||||
|
||||
| Primo byte | Valore |
|
||||
|---|---|
|
||||
| `00`-`7f` | il byte |
|
||||
| `80`-`bf` | 2 byte, 14 bit |
|
||||
| `c0`-`df` | 3 byte, 21 bit |
|
||||
| `e8` / `e9` / `ea` / `eb` | 1 / 2 / 3 / 4 byte grezzi (`e9` ipotizzato) |
|
||||
| `f5 <intero>` | intero con il bit 31 acceso (`hostid` `f5 eb 2c 14 fd cf` = `ac14fdcf` = 172.20.253.207) |
|
||||
| `f6` | −1 |
|
||||
|
||||
### 3.2 `double`
|
||||
|
||||
Il primo byte è un tag. `T` è il byte alto del `double` IEEE big-endian.
|
||||
|
||||
| Tag | Byte dopo | Valore |
|
||||
|---|---|---|
|
||||
| `00`-`03` | 1 costante + (tag+1) byte | la costante, con gli ultimi (tag+1) byte sostituiti: `00 0c 9a` = 0,1 esatto; `02 11 66 89 30` = π con rumore |
|
||||
| `04`-`18` | 0 | costante (tabella sotto) |
|
||||
| `19`-`2f` | vedi modi | positivi, 5 tag per ogni `T` da `3c` a `40`: `T = 0x3c + (tag − 0x17) / 5` |
|
||||
| `30`-`48` | vedi modi | negativi, `T` da `bc` a `c0`: `T = 0xbc + (tag − 0x30) / 5` |
|
||||
| `49`-`a4` | 6 | positivi: i primi 2 byte sono `0x3f75 + tag` (da `3fbe` a `4019`) |
|
||||
| `a5`-`df` | 6 | negativi: i primi 2 byte sono, in ordine, `bfd0`...`c00d` **saltando** `bfd2`, `bfdf` e `bffe` |
|
||||
| `ea` | 3 | 3 byte alti, il resto zero |
|
||||
| `ec` | 7 | 7 byte alti, l'ultimo zero |
|
||||
| `ed` | 8 | il `double` intero |
|
||||
|
||||
Modi dei tag a famiglie di 5 (resto di `(tag − base) / 5`):
|
||||
|
||||
| Resto | Byte dopo | Composizione |
|
||||
|---|---|---|
|
||||
| 4 | 2 | `T b1 b2` e 5 zeri (`2f 59 00` = 100) |
|
||||
| 3 | 2 | `T b1 b2` e `b2` ripetuto (`2e 1b 33` = 6,8) |
|
||||
| 2 | 7 | `T` e 7 byte |
|
||||
| 1 | 6 | `T`, 6 byte, uno zero |
|
||||
| 0 | 3 | `T`, 3 byte, 4 zeri |
|
||||
|
||||
La famiglia di `T = 3c` ha solo i tag `19`, `1a`, `1b`: `17` e `18` sono costanti.
|
||||
|
||||
Costanti a un byte:
|
||||
|
||||
| Tag | Valore | Prova |
|
||||
|---|---|---|
|
||||
| `07` / `0a` / `0c` | 0,001 / 0,01 / 0,1 | tolleranze di default per numero di decimali (`BasicData`), ISO 2768 sulle quote |
|
||||
| `0d` | −0,5 | **ipotesi** (un solo caso) |
|
||||
| `0e` / `0f` / `10` | 0,5 / 1 / −1 | matrici ortonormali |
|
||||
| `11` / `12` / `13` / `14` | π / π/2 / 2π / 3π/2 | estremi angolari dei cilindri; stesso rumore (`+0x112e0c` sulla mantissa) nelle forme `02 xx ...` |
|
||||
| `15` / `16` / `17` | −π / −π/2 / −2π | idem |
|
||||
| `18` | 0 | ovunque |
|
||||
| `04` | **ignoto** | compare come componente di tangente e come estremo di un inviluppo |
|
||||
| `05`, `06`, `08`, `09`, `0b` | mai visti | |
|
||||
|
||||
Prove: le righe e colonne di tutte le matrici `local_sys` hanno norma 1 con questa tabella
|
||||
(5397 su 5397 in questo file); i prefissi `3fd0`-`3fef` e `bfd0`-`bfef` sono verificati uno a
|
||||
uno sugli 89 file risolvendo il modulo dalla norma. Fuori da quell'intervallo valgono solo
|
||||
alcuni punti fermi: `86` = `3ffb` (√3), `8c` = `4001` (2,2046 lb/kg), `8f` = `4004` (5π/6),
|
||||
`ce` = `bffb`, `d9` = `c007` (−3 + ε). Il buco `bffe` (e non `bffd`) è una **scelta non
|
||||
provata**: uno dei due manca, non so quale.
|
||||
|
||||
### 3.3 `float` a 32 bit (tipo `06`)
|
||||
|
||||
Usati nei dati di visualizzazione (`SolidPrimdata`, `SolidPersistTable`).
|
||||
|
||||
| Tag | Byte dopo | Valore |
|
||||
|---|---|---|
|
||||
| `00` / `0b` / `28` | 0 | 0 / +1 / −1 (segno ricavato dalle normali delle facce estreme) |
|
||||
| `30`-`3f` | 3 | byte alto `0x37 + (tag − 0x30)`, positivi |
|
||||
| `40`-`4f` | 3 | byte alto `0xb7 + (tag − 0x40)`, negativi |
|
||||
| `50`-`a3` | 2 | positivi: i primi 2 byte da una **tabella non lineare** |
|
||||
| `a4`-`df` | 2 | negativi: idem |
|
||||
| `e8` / `e9` / `ea` / `eb` | 1 / 2 / 3 / 4 | byte alti grezzi, il resto zero |
|
||||
|
||||
La tabella dei tag `50`-`df` **non è ricostruita**: sono prefissi scelti per frequenza, non una
|
||||
formula. Voci ricavate dalle normali unitarie: `63`=`3e1d`, `67`=`3e46`, `68`=`3e80`,
|
||||
`69`=`3e84`, `6a`=`3ea6`, `6d`=`3f08`, `6e`=`3f0a`, `6f`=`3f0b`, `70`=`3f0c`, `71`=`3f0d`,
|
||||
`72`=`3f0e`, `73`=`3f0f`, `74`=`3f10`, `75`=`3f17`, `76`=`3f21`, `77`=`3f57`; `bc`=`be80`,
|
||||
`bd`=`be84`, `bf`=`becf`, `c0`=`bee4`, `c1`=`beea`, `c4`=`bf0a`, `c5`=`bf1c`, `c6`=`bf20`,
|
||||
`c7`=`bf27`, `c8`=`bf32`, `ca`=`bf60`, `cb`=`bf66`, `cc`=`bf7a`. Il parser di riferimento usa
|
||||
una formula lineare **sbagliata** per questi tag: la lunghezza è giusta (la lettura prosegue),
|
||||
il valore no. Finché la tabella non è completa la mesh non si può esportare.
|
||||
|
||||
## 4. Algoritmo di lettura
|
||||
|
||||
```
|
||||
pila = [], ultimo = nessuno, next = {}, campi = {}
|
||||
finché ci sono byte:
|
||||
m = marcatore f0-f4 se presente
|
||||
se il byte è e0: # campo con nome
|
||||
leggi tipo e nome; m = marcatore dopo il nome, se presente
|
||||
applica la regola 2 (m presente) o 3 (m assente) di §2.3
|
||||
id = nuovo numero; il campo appartiene al contenitore in cima
|
||||
altrimenti:
|
||||
se m presente: chiudi m contenitori
|
||||
se il byte è f7: id = intero che segue (poi regola 3 se m assente)
|
||||
altrimenti: id = next[ultimo]
|
||||
porta in cima il contenitore di id (regola 1; alias se la cima è una struttura vuota)
|
||||
next[ultimo] = id; ultimo = id
|
||||
leggi il valore secondo il tipo di id; strutture e array vanno sulla pila
|
||||
```
|
||||
|
||||
Uso:
|
||||
|
||||
```powershell
|
||||
python scripts/psb/prt.py 9253000117a.prt.35 # indice delle sezioni
|
||||
python scripts/psb/psb.py 9253000117a.prt.35 '*' out # ogni sezione in out/<Nome>.txt
|
||||
python scripts/psb/psb.py 9253000117a.prt.35 FullMData out
|
||||
python scripts/psb/unz.py 9253000117a.prt.35 out # decomprime le sezioni .Z in out/*.bin
|
||||
```
|
||||
|
||||
## 5. Cosa c'è in ogni sezione
|
||||
|
||||
Nomi di radice e campi come nel file. "Letto" = arriva all'ultimo byte.
|
||||
|
||||
| Sezione | Radice | Contenuto |
|
||||
|---|---|---|
|
||||
| `BasicText` | `Sld_BasicText` | proprietà di massa (`density`, `volume`, `cg`, `inertia`, `surfarea`), unità (`unit_arr`: `MM`, `KILOGRAM`, `SECS`; `principal_sys_units` = `millimeter Kilogram Sec (mmKs)`) |
|
||||
| `BasicData` | `Sld_BasicData` | tolleranze di default, tabelle ISO (`iso_tol_tbls_ptr`, classi `Fine`...`Very Coarse`), tabelle dei fori (`holes_arr`) |
|
||||
| `FullMData` | `Sld_FullData` | **quote** (`dim_array`, 788 voci: `dim_type`, `feat_id`, `value`, `tol_ptr.value`), modelli referenziati (`ref_part_table`) |
|
||||
| `AllFeatur` | `Sld_Features` | lista delle feature (`first_feat_ptr`/`next_feat_ptr`): `id`, `feat_type_ptr.type`, `sect_name`, tabelle delle geometrie create, date |
|
||||
| `FeatDefs` | `Sld_FeatDefs` | definizione di ogni feature (`feat_defs_<tipo>`), schizzi, istruzioni di posizionamento |
|
||||
| `FeatDefsIndex` | `Sld_FeatDefsIndex` | i tipi di feature presenti (`feat_defs_index`: 923, 979, 1084, 917, 916, 914, 926, 1104, 1232, 911, 949, 1070, 913, 1052) |
|
||||
| `MdlStatus` | `Sld_MdlStatus` | storico: un record per azione, con utente, data e nome (`Extrude 9`, `Specchiare 4`) |
|
||||
| `VisibGeom` / `NovisGeom` | `Sld_VisGeom` / `Sld_NonVisGeom` | **B-rep**: `srf_array` (superfici: `geom_id`, `geom_type`, `feat_id`, `srf_prim_ptr(plane/cylinder/...)` con `local_sys` e raggio), contorni (`next_cntr_ptr`), `crv_array` (spigoli con i punti) |
|
||||
| `ActEntity` | `Sld_Entities` | entità di riferimento: punti (`PNT0`...), linee, sistemi di coordinate |
|
||||
| `GeomDepen` | `Sld_GeomDepend` | ultima feature rigenerata, data dell'ultima rigenerazione, proprietà delle geometrie |
|
||||
| `LargeText` | `Sld_LargeText` | **nomi** (`names_table`), **parametri** (`symtbl`: `DENOMINAZIONE`, `PRO_MP_*`...), **relazioni** come albero (`relset_code_tree`) con il testo sorgente e i commenti |
|
||||
| `DispCntrl` | `Sld_DispCntrl` | layer (`01___PRT_ALL_DTM_PLN`, ...) con gli id contenuti e le regole |
|
||||
| `FamilyInf` | `Sld_FamilyInfo` | family table (`gen_name`, `info_items`) |
|
||||
| `Xsections` | `Sld_Xsections` | sezioni trasversali, ognuna con la propria geometria (`xsec_geom`) |
|
||||
| `ModelView#n` | `View` | viste salvate: matrici `orient`/`local_sys` 4×4, nome |
|
||||
| `NeuPrtSld` | `Sld_NeutPart` | dati "neutral" della parte |
|
||||
| `ColorSchemeInfo*`, `DispDataColor`, `AssMrgPrt`, `PipeInfos`, `NeuAsmSld`, `DEPDB_DATA`, `THMB_IMG_INDX` | | piccole o vuote |
|
||||
| `SolidPrimdata` (compressa) | `pgl_primdata_group_table` | **geometria di visualizzazione**: archi (`prim_arc`: raggio, angoli, centro, piano), linee e la **mesh** (`mv_p_NxNyNzxyz`: normale + vertice, `float`) |
|
||||
| `SolidPersistTable` (compressa) | `pgl_persist_table` | ingombro (`xyz_min_max`: x ±45,57, y −74,07..90, z −807..0), `bounding_radius` 414,27 |
|
||||
| `ND:0:Model_*`, `ND:0:UnitSystemDef_*`, `ND:0:PDMTrail_*` (compresse) | `PSB_Tokens` | §7 |
|
||||
|
||||
Valori letti da questo file, come esempio (nessuno confrontato con Creo): densità
|
||||
7,85·10⁻⁶ kg/mm³, volume 3,11·10⁶ mm³, dipendenze `9053004005` e `9253018001`.
|
||||
|
||||
## 6. Cosa cambia rispetto alla lettura a pattern
|
||||
|
||||
- **Il primo record non è più un caso speciale**: è solo quello che dichiara i campi. "La prima
|
||||
quota dell'array può mancare" (`formato-prt-geometria.md` §2) non vale più.
|
||||
- **I record posizionali si leggono con i nomi**: ogni valore ha il nome del suo campo.
|
||||
- **Le quote**: `dim_array` → `dim_dat_ptr.value`, `tol_ptr.value` (i due scostamenti: 0,1 per
|
||||
la quota 2; 0,2 per 20; 0,3 per 59, cioè ISO 2768-m), `dim_cosm_ptr`.
|
||||
- **Le unità** si leggono (`BasicText`), non vanno più assunte.
|
||||
|
||||
## 7. Le sezioni `ND:0:*`: `PSB_Tokens`
|
||||
|
||||
Decompresse sono un PSB con una sola struttura:
|
||||
|
||||
```
|
||||
PSB_Tokens:
|
||||
format = 0
|
||||
type_har = "MOMOIOIOIOIOFCOFOM..." un carattere per token
|
||||
int_har = [0, 1, 2, 3, 4, 5, -1, ...]
|
||||
double_xar = [-1.0, 0.001, 1e-09, ...]
|
||||
string_xar = ['ndmodel_l05::Model_L05', 'Descriptor', 'Name', '9253000117a', 'Type', 'prt', ...]
|
||||
```
|
||||
|
||||
È un secondo livello di serializzazione: `type_har` è la sequenza dei token, gli altri tre array
|
||||
sono i valori a cui i token attingono in ordine. **Il significato delle lettere non è
|
||||
decodificato** (`M`, `O`, `I`, `F`, `C`, `P`, `Q`, `N`, `H`, `R`, `S`, `U`). Le stringhe però si
|
||||
leggono già: nome e tipo del modello, dipendenze, nomi dei parametri.
|
||||
|
||||
## 8. Dove non regge
|
||||
|
||||
| Problema | Dove | Stato |
|
||||
|---|---|---|
|
||||
| Sezioni tipo `b` (`IndentTag`, `FeatRefData`) e tipo `11` dei file Wildfire | tutti i file | **illeggibili** |
|
||||
| `DispDataTable` (tipo `c`) | file recenti | non letto |
|
||||
| Tabella dei prefissi `float` `50`-`df` | `SolidPrimdata` | parziale: la mesh non è esportabile |
|
||||
| Costante `double` `04` | geometria | valore ignoto |
|
||||
| Annidamento | `AllFeatur`, `MdlStatus`, `FeatDefs` | la pila non torna a zero a fine sezione |
|
||||
| Alberi bilanciati (`lesser_child`/`greater_child`/`balance_factor`) | `LargeText` di 24 file di `raw/` | il lettore si ferma: la regola dei puntatori "al successivo" non basta |
|
||||
| `srf_flip_dat`, `end2`, `backup_size` "contenitore non in pila" | 26 sezioni di `raw/` | stesso tipo di errore: è la pila, non i valori |
|
||||
| Array di stringhe senza `f8` | `BasicData` | euristica (§2.2) |
|
||||
| `PSB_Tokens` | `ND:0:*` | solo gli array, non la struttura |
|
||||
| Significato dei campi | ovunque | dedotto dal nome, mai verificato in Creo |
|
||||
|
||||
Sugli 89 file di `raw/`: 2152 sezioni lette per intero, 74 no. Tutti i fallimenti sono del tipo
|
||||
"contenitore non in pila" o "pila vuota": la sequenza dei valori fino a quel punto è corretta.
|
||||
|
||||
## 9. Correzioni agli altri doc
|
||||
|
||||
- `formato-prt-feature.md` §1.1: la costante `K` non va ricavata dall'ultima sezione: è
|
||||
l'ultimo campo della prima riga del file, e la sezione comincia con la riga `#Nome`. L'indice
|
||||
può essere a catena (`NEXT_TOC_ENTRY`). La formula data lì funziona solo per caso.
|
||||
- `formato-prt-feature.md` §1.2: le sezioni `ND:0:*` decompresse non sono XML ma `PSB_Tokens`
|
||||
(§7); i nomi tipo SOAP sono le stringhe di `string_xar`.
|
||||
- `formato-prt-geometria.md` §1: la tabella dei tag è sostituita da §3.2. In particolare
|
||||
`e4`-`e7` non sono "zeri" ma **ripetizioni del valore precedente**, e i tag `49`-`df`
|
||||
("non decodificato") sono i prefissi a 2 byte.
|
||||
- `formato-prt-geometria.md` §1: `UnitSystemDef` non serve per le unità, stanno in `BasicText`.
|
||||
|
||||
## 10. Come proseguire
|
||||
|
||||
1. **Confrontare con Creo** un file piccolo: è l'unica prova che manca a tutto il doc.
|
||||
2. Completare la tabella dei `float` raccogliendo le normali su più file (stesso metodo di
|
||||
§3.3): sblocca l'esportazione della mesh senza Creo.
|
||||
3. Sistemare la pila sugli alberi di `LargeText` (§8), poi portare il parser in C# accanto ad
|
||||
`AsmRecordReader`, con la stessa regola: se la sezione non si legge fino in fondo,
|
||||
`InvalidDataException`.
|
||||
4. Sezioni tipo `b`: servono due salvataggi dello stesso modello che differiscono di poco.
|
||||
@@ -0,0 +1,20 @@
|
||||
# Glossario
|
||||
|
||||
| Termine | Significato |
|
||||
|---|---|
|
||||
| Assieme generale | l'assieme di primo livello di una distinta; il foglio del gestionale non lo elenca, l'app lo aggiunge come riga se il file è `DBP_<codice>_<timestamp>` |
|
||||
| `.asm` / `.prt` | file Creo/Pro-E di assieme e di parte; il nome è `<codice>.<tipo>.<versione>` |
|
||||
| Cartella di lavoro | dove finiscono CSV, `raw/` e `step/`; di default la cartella di installazione |
|
||||
| Codice | identificativo del componente, ricavato dal nome del file (può avere una lettera di revisione attaccata) |
|
||||
| Creo Distributed Batch | strumento PTC che esporta in batch (qui in STEP); usato a mano, fuori dall'app |
|
||||
| Distinta Base (DBP) | estrazione dal gestionale (Fata) dei componenti di un assieme |
|
||||
| Family table | tabella di varianti di un modello; il nome dell'istanza sta nel parametro `ConfigName` |
|
||||
| Indice | il DB `cad_index.db` con i metadati dei file della share |
|
||||
| `members` | sezione del `.asm` con i componenti diretti dell'assieme |
|
||||
| `raw/` | cartella con i file CAD da passare a Creo |
|
||||
| Riferimento esterno | modello citato da un assembly feature o datum copiato; non è un membro, ma Creo lo carica con il suo albero |
|
||||
| Share | disco di rete con i file CAD, `X:\` di default |
|
||||
| Soppresso | componente tolto dalla distinta ma il cui file serve comunque ad aprire l'assieme |
|
||||
| `step/` | cartella degli STEP esportati da Creo; non scritta dall'app |
|
||||
| `temp_*` | cartelle personali della share, escluse dall'indice |
|
||||
| Ultima versione | per ogni `(cartella_radice, codice, tipo_file)` la riga con versione più alta (vista `files_ultima_versione`) |
|
||||
@@ -0,0 +1,159 @@
|
||||
# Guida utente
|
||||
|
||||
Questa guida descrive, in ordine, tutti i passi da fare in **PrepStep - Cevolani** per
|
||||
arrivare dalla distinta base esportata dal gestionale ai file STEP pronti, passando per
|
||||
**Creo Distributed Batch**. Per il dettaglio tecnico di come funziona internamente ogni
|
||||
fase vedi [`architettura.md`](architettura.md); questa guida si ferma a cosa clicca e
|
||||
cosa vede l'utente. La stessa sintesi è disponibile anche dentro l'app da **Aiuto → Guida**
|
||||
(F1).
|
||||
|
||||
## Dove finiscono i file
|
||||
|
||||
Tutto sta nella **cartella di lavoro**, che di default è la cartella di installazione
|
||||
`%LOCALAPPDATA%\Programs\PrepStep`:
|
||||
|
||||
| Cosa | Dove |
|
||||
|---|---|
|
||||
| database `cad_index.db` | sempre nella cartella di installazione |
|
||||
| CSV di ogni distinta importata | cartella di lavoro |
|
||||
| `raw/` — file CAD copiati nelle fasi 3-4 | cartella di lavoro |
|
||||
| `step/` — file STEP esportati da Creo | cartella di lavoro (la indichi tu a Creo) |
|
||||
|
||||
Il file `.xls` del gestionale resta dove l'hai preso: l'app lo legge soltanto.
|
||||
|
||||
- **Cambiare la cartella di lavoro**: **File → Scegli cartella di lavoro...** (viene ricordata
|
||||
agli avvii successivi). Il database resta comunque nella cartella di installazione.
|
||||
- **Aprire la cartella**: `AppData` è nascosta in Esplora file; scrivi
|
||||
`%LOCALAPPDATA%\Programs\PrepStep` nella barra degli indirizzi (vale anche per scegliere
|
||||
`step/` in Creo). Dalla fase 4 si apre `raw/` con **Apri cartella raw**.
|
||||
- **Fare pulizia** (menu **File**): **Pulisci raw e step...** svuota `raw/` e `step/`;
|
||||
**Elimina CSV delle distinte...** elimina i CSV, che altrimenti si accumulano uno per
|
||||
distinta. Nessuno dei due tocca il database. Entrambi chiedono conferma.
|
||||
- **Disinstallando** il programma questi file restano: cancellali a mano se non servono più.
|
||||
|
||||
## Prima di iniziare
|
||||
|
||||
- Serve accesso alla share di rete con i file CAD (di default `X:\`).
|
||||
- Serve l'estrazione "Distinta Base" dal gestionale (Fata): il file `.xls` così come
|
||||
Fata lo salva, con le colonne Componente/Descrizione (foglio `"Distinta Base"`) — va
|
||||
lasciato nel formato/estensione in cui viene esportato, senza risalvarlo o convertirlo
|
||||
in altri formati prima di usarlo nell'app. Sotto l'estensione `.xls` il contenuto può
|
||||
essere XML (SpreadsheetML) o Excel binario vero: l'app riconosce da sola quale dei due
|
||||
è, non serve fare nulla di diverso a seconda del caso.
|
||||
- Se il file segue la convenzione di nome del gestionale (`DBP_<codice>_<timestamp>`),
|
||||
l'app aggiunge da sola l'assieme generale come componente in più — non serve fare
|
||||
nulla di diverso.
|
||||
- Se non vuoi usare la cartella di installazione, scegli la **cartella di lavoro**
|
||||
("File → Scegli cartella di lavoro...") prima di importare la distinta: è lì che
|
||||
finiscono CSV, `raw/` e, dopo l'esportazione con Creo, `step/`.
|
||||
- Se la cartella di lavoro è già stata usata per un'altra distinta, conviene **pulire
|
||||
`raw/` e `step/`** prima di ripartire ("File → Pulisci raw e step..."): evita di
|
||||
confondere i file CAD/STEP della copia precedente con quelli della nuova distinta (il
|
||||
"File → Nuovo" descritto più sotto azzera solo lo stato di CSV/griglie dell'app, non
|
||||
tocca i file già copiati su disco).
|
||||
|
||||
## 1. Database
|
||||
|
||||
Se nella cartella di installazione c'è già un database (`cad_index.db`), l'app lo carica
|
||||
da sola all'avvio: si vede il pallino verde accanto a "Database", il percorso del
|
||||
file e la data dell'ultimo aggiornamento.
|
||||
|
||||
- **Se il pallino è rosso** (nessun database trovato): premi **Aggiorna indice** per
|
||||
scansionare la share (`X:\`) e crearne uno nuovo — l'operazione può richiedere qualche
|
||||
minuto la prima volta, i giri successivi sono più veloci perché aggiornano solo quello
|
||||
che è cambiato.
|
||||
- **Se hai già un database da un'altra postazione o cartella**: premi **Apri DB...** e
|
||||
selezionalo a mano. Vale solo finché non chiudi il programma: al prossimo avvio si riparte
|
||||
dal database nella cartella di installazione. Per usarlo sempre, copialo in
|
||||
`%LOCALAPPDATA%\Programs\PrepStep` come `cad_index.db`.
|
||||
- La **cartella di lavoro** non si imposta qui: vedi [Dove finiscono i file](#dove-finiscono-i-file).
|
||||
|
||||
Premi **Avanti** quando il database è caricato.
|
||||
|
||||
## 2. Importa Distinta Base
|
||||
|
||||
- **1. Carica la distinta**: trascina il file `.xls` esportato da Fata nella finestra,
|
||||
oppure premi **Scegli file distinta...** e selezionalo da Esplora risorse. Le righe importate
|
||||
compaiono nella griglia (Componente/Descrizione) e viene scritto un CSV nella cartella
|
||||
di lavoro.
|
||||
In alternativa scegli **Lista personalizzata**: digita un codice e premi **Invio** (o
|
||||
**Aggiungi**) per inserirlo, ripeti per ogni codice; **Canc** sulla riga selezionata lo
|
||||
toglie. La lista non scrive nessun CSV e, passando da una modalità all'altra, viene azzerata
|
||||
(con conferma). Inserisci anche l'assieme generale, se serve: qui non viene aggiunto da solo.
|
||||
- **2. Verifica la presenza dei file sul server**: compare solo dopo un import riuscito
|
||||
(o dopo il primo codice aggiunto alla lista personalizzata; ogni modifica della lista
|
||||
richiede di rifare la verifica).
|
||||
Premila per cercare ogni componente nel database — le righe della griglia si colorano
|
||||
di verde (trovato) o rosso (non trovato nell'indice).
|
||||
|
||||
**Avanti** si sblocca solo dopo aver premuto **Verifica presenza**, non basta il solo
|
||||
import: è il modo per accorgersi subito se il database è disallineato
|
||||
rispetto alla share (es. indice non aggiornato) prima di andare avanti.
|
||||
|
||||
## 3. Crea cartella raw
|
||||
|
||||
Premi **Crea cartella raw**: per ogni componente della distinta trovato nell'indice,
|
||||
l'app ne copia il file CAD dalla share nella cartella `raw/` (dentro la cartella di
|
||||
lavoro). La griglia mostra il percorso CAD copiato per ogni componente.
|
||||
|
||||
## 4. Trova componenti referenziati
|
||||
|
||||
Gli assiemi (`.asm`) referenziano altri file (sotto-assiemi, parti, componenti soppressi,
|
||||
riferimenti esterni) che Creo deve poter caricare insieme all'assieme principale, anche
|
||||
se non compaiono nella distinta del gestionale. Premi **Trova componenti referenziati**:
|
||||
l'app legge i file `.asm` già in `raw/`, risolve ricorsivamente tutto l'albero e copia
|
||||
anche quei file in `raw/`.
|
||||
|
||||
Si apre una finestra con l'elenco di quanto trovato e copiato (PRT e sotto-assiemi), con
|
||||
in fondo eventuali codici referenziati che non corrispondono a nessun file indicizzato
|
||||
(possono essere disegni, componenti davvero mancanti, o falsi positivi della scansione —
|
||||
non bloccano il lavoro, ma vale la pena controllarli). Da qui puoi anche aprire
|
||||
direttamente la cartella `raw/` con **Apri cartella raw**.
|
||||
|
||||
Questo passo è opzionale se la distinta contiene solo parti singole senza sotto-assiemi:
|
||||
in quel caso si può passare direttamente alla fase successiva.
|
||||
|
||||
## 5. Verifica completezza e Creo Distributed Batch
|
||||
|
||||
L'ultima fase mostra l'elenco dei file `.asm` della distinta già presenti in `raw/`,
|
||||
trascinabili. Da qui il passaggio a Creo è manuale, fuori dall'app:
|
||||
|
||||
1. Apri **Creo Distributed Batch** (dal menu Start di Windows, o dal file
|
||||
`ptcdbatch.bat` nella cartella `bin` dell'installazione Creo).
|
||||
2. Crea un nuovo gruppo di attività per l'esportazione STEP (menu **Actions → New Task
|
||||
Group**), scegliendo il tipo di attività per l'export STEP — se in azienda è già
|
||||
presente un profilo/gruppo predefinito per l'export STEP, riusa quello invece di
|
||||
crearne uno nuovo.
|
||||
3. Aggiungi i file: trascina i `.asm` dalla finestra dell'app (o direttamente da Esplora
|
||||
risorse, dalla cartella `raw/`) sul gruppo di attività, oppure sfoglia e selezionali a
|
||||
mano da Distributed Batch.
|
||||
4. Verifica che il gruppo di attività punti al `config.pro`/profilo di export STEP giusto
|
||||
(sezione **Configuration Files** del gruppo) — di norma è già impostato una volta sola
|
||||
per tutti, non richiede modifiche a ogni giro.
|
||||
5. Avvia l'elaborazione (**Start**/**Run**): Distributed Batch legge ogni `.asm`, con
|
||||
tutto l'albero di sotto-componenti già raccolto in `raw/` nelle fasi 3-4, ed esporta i
|
||||
file STEP nella cartella `step/` (dentro la cartella di lavoro, accanto a `raw/`).
|
||||
|
||||
I nomi esatti dei menu possono variare leggermente a seconda della versione di Creo e di
|
||||
come Distributed Batch è già configurato in azienda — questi passi descrivono il flusso
|
||||
generale (fonte: [documentazione e community PTC](https://www.ptc.com/en/support/article/CS28241));
|
||||
in caso di dubbio su profili/config già pronti, chiedi a chi ha impostato Distributed
|
||||
Batch la prima volta.
|
||||
|
||||
**Se Distributed Batch fallisce con `[-4] Dati di input non completi`**, quasi sempre
|
||||
manca un file nell'albero dei componenti — Creo non dice quale. Torna alla fase 4 e
|
||||
controlla l'elenco dei "non trovati": è il punto di partenza per capire cosa recuperare
|
||||
prima di riprovare.
|
||||
|
||||
## Ripartire con una nuova distinta
|
||||
|
||||
Quando devi processare un'altra distinta, usa **File → Nuovo** (Ctrl+N): svuota le griglie e
|
||||
la verifica delle fasi 2-5 e riporta al passo 2, senza dover ricaricare il database né
|
||||
cambiare la cartella di lavoro. Non cancella nessun file su disco: CSV, `raw/` e `step/`
|
||||
restano (vedi sotto per ripulirli).
|
||||
|
||||
Per ripulire le cartelle `raw/` e `step/` della cartella di lavoro prima di un nuovo giro
|
||||
(senza toccare l'indice), usa **File → Pulisci raw e step...**.
|
||||
|
||||
Ogni distinta importata lascia un CSV nella cartella di lavoro: per eliminarli tutti usa
|
||||
**File → Elimina CSV delle distinte...** (i file `.xls` del gestionale non vengono toccati).
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
# Reverse engineering dei file Creo: stato e ripresa
|
||||
|
||||
Diario di passaggio di consegne per il lavoro sul formato dei `.prt`/`.asm`. Serve a chi riprende
|
||||
(anche in un'altra sessione) per sapere **dove siamo, cosa è provato, da dove ripartire**, senza
|
||||
rifare la strada. Il *risultato* del lavoro sta in [`formato-psb.md`](formato-psb.md): qui c'è
|
||||
solo lo stato, le decisioni e i vicoli ciechi. Quando un punto si chiude, il contenuto va nel doc
|
||||
di formato e qui resta una riga.
|
||||
|
||||
Ultimo aggiornamento: 2026-11-10, ramo `reverse-engineering-creo`.
|
||||
|
||||
## Stato in una riga
|
||||
|
||||
Il formato di serializzazione (PSB) è decodificato e tutte le sezioni in chiaro di
|
||||
`9253000117a.prt.35` si leggono fino all'ultimo byte con nome e valore di ogni campo; **nessun
|
||||
valore è stato ancora confrontato con Creo**, e tre cose bloccano "tutti i dati senza Creo"
|
||||
(vedi "Cosa manca").
|
||||
|
||||
## Da non perdere
|
||||
|
||||
- **Niente è committato.** In sospeso: `docs/formato-psb.md`, `docs/history.md`, `scripts/psb/`,
|
||||
e le modifiche a `CHANGELOG.md`, `docs/README.md`, `docs/architettura.md`,
|
||||
`docs/formato-prt-geometria.md`.
|
||||
- `9253000117a.prt.35` è nella root, non tracciato: è il campione di tutto il lavoro. Decidere
|
||||
se tenerlo nel repo (2 MB) o in `raw/`.
|
||||
- `scripts/psb/__pycache__/` va ignorato, non committato.
|
||||
- `docs/formato-prt-feature.md` contiene byte NUL: gli strumenti di modifica lo rifiutano. Le
|
||||
correzioni che lo riguardano sono in `formato-psb.md` §9, non applicate al file. Va
|
||||
ripulito dai NUL (conservando la codifica) prima di poterlo correggere.
|
||||
|
||||
## Come ripartire in cinque minuti
|
||||
|
||||
Dalla root del repo, con Python 3.12 (solo libreria standard):
|
||||
|
||||
```powershell
|
||||
python scripts/psb/prt.py 9253000117a.prt.35 # indice delle sezioni
|
||||
python scripts/psb/psb.py 9253000117a.prt.35 '*' out # dump di ogni sezione in out/
|
||||
python scripts/psb/psb.py 9253000117a.prt.35 LargeText - t # traccia campo per campo (debug)
|
||||
python scripts/psb/unz.py 9253000117a.prt.35 out # decomprime le sezioni .Z
|
||||
python scripts/psb/pbin.py out/SolidPrimdata.bin # legge una sezione decompressa
|
||||
python scripts/psb/batch.py "raw/*" # regressione sugli 89 file
|
||||
python scripts/psb/norms.py 9253000117a.prt.35 # controllo norma 1 delle matrici
|
||||
python scripts/psb/tagmap.py 9253000117a.prt.35 "raw/*" # tag double -> prefisso, dalle norme
|
||||
python scripts/psb/ctx.py raw/x.prt.1,VisibGeom,cf8 # hexdump attorno a un offset
|
||||
```
|
||||
|
||||
Valori attesi oggi, da usare come regressione prima e dopo ogni modifica al parser:
|
||||
|
||||
| Controllo | Atteso |
|
||||
|---|---|
|
||||
| `psb.py 9253000117a.prt.35` | 29 sezioni `OK`, 5 no: `AllFeatur` (`pop underflow` sull'ultimo byte), `MdlStatus` e `FeatDefs` (pila non a zero), più l'intestazione delle due ultime |
|
||||
| `norms.py` | `good rows 5397 bad 0` |
|
||||
| `batch.py "raw/*"` | 2152 sezioni lette, 74 no (misurato **prima** delle ultime correzioni ai tag: va rilanciato) |
|
||||
|
||||
Se `norms.py` dà righe "bad" dopo una modifica, si è rotta la tabella dei `double`: è il
|
||||
controllo più sensibile che abbiamo.
|
||||
|
||||
## Cosa è provato, e come
|
||||
|
||||
| Cosa | Prova |
|
||||
|---|---|
|
||||
| Contenitore (offset = ultimo campo della prima riga, riga `#Nome` inclusa, indice a catena) | tutte le sezioni cominciano con `e0 00 <radice>` |
|
||||
| Grammatica e tabella "dopo X viene Y" | lettura fino all'ultimo byte di tutte le sezioni; l'id nell'intestazione degli array coincide con quello calcolato |
|
||||
| Tag dei `double` | norma 1 su 5397 righe; prefissi `3fd0`-`3fef` e `bfd0`-`bfef` verificati uno a uno sugli 89 file |
|
||||
| Costanti π, π/2, 2π, 3π/2 e negative | stesso rumore di mantissa nelle forme modificate (`02 xx ...`) |
|
||||
| Sezioni `.Z` | la dimensione decompressa coincide con quella dichiarata nell'indice |
|
||||
| Significato dei campi | **nessuna**: dedotto dai nomi |
|
||||
|
||||
## Cosa manca, in ordine di valore
|
||||
|
||||
1. **Confronto con Creo.** Aprire un file piccolo, annotare quote, parametri, massa, posizione
|
||||
di una faccia, e confrontare con il dump. Finché non c'è, tutto il doc è "livello 1 e 2".
|
||||
2. **Tabella dei `float` (tag `50`-`df`).** È una tabella di prefissi scelti per frequenza, non
|
||||
una formula. Il parser usa `FBASE` lineare, **sbagliato** per quei tag (lunghezza giusta,
|
||||
valore no). Senza, la mesh di `SolidPrimdata` (`mv_p_NxNyNzxyz`) non si esporta. Metodo che
|
||||
ha funzionato per le 29 voci note: normali unitarie, risolvere il modulo dalla norma. Va
|
||||
ripetuto su tutti i file di `raw/` (ognuno ha `SolidPrimdata`), poi sostituire `FBASE` con
|
||||
un dizionario e far fallire il parser sui tag ignoti invece di inventare.
|
||||
3. **La pila dei contenitori.** I valori sono giusti, l'annidamento no in alcuni punti:
|
||||
- `LargeText` di 24 file di `raw/`: alberi bilanciati (`lesser_child`, `greater_child`,
|
||||
`balance_factor`, `backup_size`). La regola `post` (puntatore "al successivo" scritto al
|
||||
livello del padre) copre un solo campo per struttura; qui i figli sono due.
|
||||
- `srf_flip_dat` (geometria), `end2` (`FeatDefs`): "contenitore non in pila".
|
||||
- fine di `AllFeatur`/`MdlStatus`/`FeatDefs`: pila non a zero.
|
||||
Punto di partenza: `default_close` e `locate` in `psb.py`, con la traccia (`- t`).
|
||||
4. **Sezioni tipo `b`** (`IndentTag`, `FeatRefData`) e **tipo `11` Wildfire**: illeggibili.
|
||||
Stessa lunghezza del chiaro, primi 4 byte in chiaro. Già escluso (vedi
|
||||
`formato-prt-feature.md` §1.3): XOR a chiave fissa, zlib, LZW. Non riprovare senza una
|
||||
coppia chiaro/cifrato dello stesso modello.
|
||||
5. **`DispDataTable`** (tipo `c`): comincia con `1f 9d 10` ma l'LZW standard fallisce al
|
||||
secondo codice. Non indagato.
|
||||
6. **`PSB_Tokens`** (`ND:0:*`): letti i quattro array, non il significato delle lettere di
|
||||
`type_har`. Ipotesi da cui partire: `I` consuma un intero, `F` un double, una lettera per le
|
||||
stringhe; `M`/`O` aprono oggetto e campo. Contare le occorrenze contro le lunghezze degli
|
||||
array la conferma o la smentisce in pochi minuti.
|
||||
7. **Costanti `double` ignote**: `04` (vista come componente di tangente e come estremo di
|
||||
inviluppo), `0d` (−0,5 da un solo caso), buco `bffe` contro `bffd` non deciso.
|
||||
8. **Portare il parser in C#** accanto ad `AsmRecordReader`: solo dopo i punti 1 e 3.
|
||||
|
||||
## Il ragionamento da adottare
|
||||
|
||||
È il metodo che ha portato fin qui. Va seguito anche per i punti aperti: ogni volta che è stato
|
||||
saltato, si è perso tempo.
|
||||
|
||||
1. **Partire dal modello di chi scrive, non dai byte.** Il file è l'uscita di un serializzatore
|
||||
generico che percorre strutture C. La domanda utile non è "cosa significano questi byte" ma
|
||||
"che regola semplice userebbe un programma per scriverli". Ogni marcatore ridondante (`f7`,
|
||||
`f0`-`f4`) esiste perché il lettore, da solo, avrebbe previsto un'altra cosa: capire la
|
||||
previsione spiega il marcatore.
|
||||
2. **Un'ipotesi alla volta, e un controllo che la possa smentire.** Prima di scrivere codice
|
||||
dire cosa ci si aspetta di vedere se l'ipotesi è vera. Esempio: "se gli id sono progressivi,
|
||||
l'id nell'intestazione di `unit_arr` deve essere 0x14" (contato a mano: lo era).
|
||||
3. **Il parser è lo strumento di misura.** Una regola è buona se fa arrivare la lettura
|
||||
all'ultimo byte di *tutte* le sezioni; l'errore dice dove guardare. Si corregge la regola
|
||||
generale, mai il caso singolo: un'eccezione scritta per una sezione ha sempre rotto un'altra.
|
||||
4. **Cercare i controesempi, non le conferme.** Quando una regola spiega cinque casi, elencare
|
||||
per iscritto i casi noti (con i byte) e verificare che li spieghi tutti, compreso quello
|
||||
scomodo. La regola sulle chiusure implicite è nata mettendo in tabella dieci casi e cercando
|
||||
l'unica formulazione che non ne contraddiceva nessuno.
|
||||
5. **Ancorare i numeri a vincoli fisici, non all'aspetto.** Un valore "plausibile" non è una
|
||||
prova. Lo sono: la norma 1 delle righe di una matrice di rotazione, valori noti (π, √2/2,
|
||||
0,1, 25,4), la coerenza tra sezioni (lo stesso punto scritto due volte). Per i tag dei
|
||||
numeri: far risolvere il prefisso al vincolo (`tagmap.py`), non estrapolare una formula.
|
||||
6. **Separare ciò che è provato da ciò che è dedotto.** Tokenizzazione (sequenza campo-valore),
|
||||
annidamento e significato dei campi sono tre cose con tre livelli di prova diversi. Nel doc
|
||||
ognuna va dichiarata col suo livello; "regge sui campioni" non è "è così".
|
||||
7. **Allargare il campione appena una regola regge.** Un file solo nasconde le tabelle: i buchi
|
||||
nei prefissi negativi e la tabella dei `float` sono emersi solo confrontando più file.
|
||||
Dopo ogni modifica: `norms.py` sul campione, poi `batch.py` su `raw/`.
|
||||
8. **Quando due spiegazioni reggono entrambe, cercare il dato che le distingue** invece di
|
||||
scegliere la più elegante (esempio: i tag `91`-`a0` sembravano negativi; li ha decisi una
|
||||
curva i cui parametri dovevano crescere da 3π/2).
|
||||
9. **Scrivere subito nel doc, con il sintomo e i byte.** Una scoperta non scritta si rifà.
|
||||
Scrivere anche ciò che è stato escluso e perché.
|
||||
10. **Dire quello che non si sa.** Una costante ignota resta "ignota" (`<c04>` nel dump), non
|
||||
riceve un valore inventato; un parser che non capisce si ferma, non restituisce un risultato
|
||||
parziale in silenzio.
|
||||
|
||||
## Vicoli ciechi e trappole, per non rifarli
|
||||
|
||||
- **Modello "pop = chiudi N livelli" puro**: non basta. I marcatori `f0`-`f4` compaiono solo
|
||||
quando la chiusura reale è diversa da quella che il lettore farebbe da solo; `f0` vuol dire
|
||||
"resta qui". Ci sono volute quattro riscritture per arrivarci.
|
||||
- **Ordine dei campi per struttura** (primo/successivo per schema): sbagliato. La tabella è
|
||||
piatta, per id, e vale l'ultima transizione vista.
|
||||
- **`e4`-`e7` come "zeri"**: sono ripetizioni del valore precedente.
|
||||
- **Lo stesso id in contenitori diversi**: succede con le varianti di unione
|
||||
(`typed_data(...)`); il parser le accetta e le conta in `lenient`.
|
||||
- **Tag `double` negativi lineari**: no, ci sono tre buchi. Non fidarsi di un'estrapolazione
|
||||
fuori dagli intervalli verificati.
|
||||
- **Heredoc in Bash con `\\0`**: lo strumento collassa le barre e scrive un NUL nel sorgente.
|
||||
Per creare file Python usare lo strumento di scrittura, non un heredoc.
|
||||
- **Git Bash e `cd`**: gli script vanno lanciati dalla root del repo; `dotnet run` dentro
|
||||
`PrepStep/` avvia la GUI.
|
||||
|
||||
## Cronologia
|
||||
|
||||
| Data | Fatto |
|
||||
|---|---|
|
||||
| 2026-10-21 | Albero delle feature, quote e geometria degli schizzi letti a pattern (`formato-prt-feature.md`, `formato-prt-geometria.md`) |
|
||||
| 2026-11-10 | Contenitore corretto; grammatica PSB completa; tabella dei `double`; sezioni `.Z`; catalogo delle sezioni; parser di riferimento in `scripts/psb/`; `formato-psb.md` |
|
||||
@@ -0,0 +1,47 @@
|
||||
# Rilasciare una nuova versione
|
||||
|
||||
Procedura per produrre il setup `PrepStep-Setup-<versione>.exe`, l'unico file da distribuire.
|
||||
Il comportamento del setup (installazione per-utente, aggiornamento, `/VERYSILENT`) è descritto
|
||||
in [`architettura.md`](architettura.md#installazione); qui ci sono solo i passi da eseguire.
|
||||
|
||||
## Una volta sola
|
||||
|
||||
Installa Inno Setup 6 per l'utente corrente (niente admin):
|
||||
|
||||
```powershell
|
||||
winget install --id JRSoftware.InnoSetup -e --scope user
|
||||
```
|
||||
|
||||
## A ogni release
|
||||
|
||||
1. Aggiorna `<Version>` in `PrepStep/PrepStep.csproj` (SemVer).
|
||||
2. In [`CHANGELOG.md`](../CHANGELOG.md) trasforma "Non rilasciato" nella nuova versione con la
|
||||
data e riscrivi sotto le modifiche (Aggiunto/Modificato/Corretto/Rimosso).
|
||||
3. Controlla i doc: `dotnet run scripts/CheckDocs.cs` deve confermare che versione e CHANGELOG
|
||||
coincidono.
|
||||
4. Esegui i test: `dotnet test` in `PrepStep.Tests/`.
|
||||
5. Chiudi l'app se è aperta (l'exe in uso blocca la build), poi dalla root del repo:
|
||||
|
||||
```powershell
|
||||
dotnet publish PrepStep -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o PrepStep\publish
|
||||
& "$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe" installer\PrepStep.iss
|
||||
```
|
||||
|
||||
6. Il setup è in `installer\Output\PrepStep-Setup-<versione>.exe`; la versione è letta
|
||||
dall'exe, quindi coincide con `<Version>`. Provalo lanciandolo.
|
||||
7. Commit con codice, doc e CHANGELOG insieme (regola in [`README.md`](README.md)).
|
||||
|
||||
## Solo l'eseguibile, senza setup
|
||||
|
||||
```powershell
|
||||
cd PrepStep
|
||||
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o publish
|
||||
```
|
||||
|
||||
Produce `PrepStep\publish\PrepStep.exe`, un file unico che gira su un Windows x64 senza .NET
|
||||
installato.
|
||||
|
||||
## Da non toccare
|
||||
|
||||
`AppGuid` in `installer/PrepStep.iss`: è ciò che fa riconoscere a Windows le versioni nuove
|
||||
come aggiornamento della stessa app. Cambiarlo produce un'installazione doppia.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Risoluzione dei problemi
|
||||
|
||||
Sintomo → causa probabile → cosa fare. I messaggi di Creo non nominano il file né la causa, per
|
||||
questo il sintomo è il punto di partenza.
|
||||
|
||||
## Dentro l'app
|
||||
|
||||
| Sintomo | Causa probabile | Cosa fare |
|
||||
|---|---|---|
|
||||
| Pallino rosso in fase 1, "Database non trovato" | non c'è `cad_index.db` nella cartella di installazione | **Aggiorna indice** (lo crea lì), oppure **Apri DB...** per usarne un altro solo per la sessione |
|
||||
| "Disco non raggiungibile: X:\" | la share non è montata o non hai accesso | monta `X:\` e riprova; la radice è fissa in `Form1.RadiceDiscoDefault` |
|
||||
| Componente rosso in "Verifica presenza" | il codice non è nell'indice: indice vecchio, oppure il nome su disco ha una lettera di revisione in più o in meno | **Aggiorna indice**; poi cerca il codice con `LIKE` (vedi sotto) |
|
||||
| "Avanti" resta grigio in fase 2 | manca il passo **Verifica presenza** | premila: non basta l'import |
|
||||
| Alcuni file non compaiono in `raw/` dopo fase 3/4 | la copia salta in silenzio i file irraggiungibili (share offline, permessi) | controlla l'accesso alla share e ripeti la fase |
|
||||
| In fase 4 compaiono codici "non trovati" | disegni, componenti davvero assenti dalla share o falsi positivi della scansione | controllali a mano; non bloccano il lavoro |
|
||||
| Risultati incoerenti in sviluppo | l'app sta usando il DB della cartella di build, non quello che credi | guarda il percorso mostrato in fase 1; apri il DB giusto con **Apri DB...** |
|
||||
|
||||
Per capire se un codice è nell'indice:
|
||||
|
||||
```powershell
|
||||
dotnet run scripts/Query.cs -- cad_index.db "SELECT codice, tipo_file, percorso_completo FROM files_ultima_versione WHERE codice LIKE '925%' LIMIT 20"
|
||||
```
|
||||
|
||||
Nessuna riga nemmeno con `LIKE` → il file non è indicizzato. Una riga con codice leggermente
|
||||
diverso → revisione, maiuscole o spazi.
|
||||
|
||||
## In Creo Distributed Batch
|
||||
|
||||
| Sintomo | Causa probabile | Cosa fare |
|
||||
|---|---|---|
|
||||
| `[-4] Dati di input non completi` | nell'albero manca un file che Creo deve caricare, ma non dice quale | rifai la fase 4 e leggi l'elenco dei "non trovati"; poi vedi sotto |
|
||||
| L'assieme non si apre, nessun messaggio chiaro | in `raw/` c'è una copia sbagliata di un componente (lo stesso codice esiste in più cartelle, 13% dei casi) | l'app sceglie la copia nella cartella dell'assieme: [`formato-asm-record.md`](formato-asm-record.md) §7.4 |
|
||||
| Assieme con un componente soppresso che non si apre | il file del soppresso non era in `raw/` | §7.3; oggi la fase 4 lo copia |
|
||||
| Assieme con feature di assembly o datum copiati che non si apre | manca il modello citato per riferimento esterno | §7.5; oggi la fase 4 lo copia con il suo albero |
|
||||
|
||||
## Da sviluppatore
|
||||
|
||||
| Sintomo | Causa | Cosa fare |
|
||||
|---|---|---|
|
||||
| `dotnet run` apre la GUI e si blocca | lanciato dentro `PrepStep/` | lanciare gli script dalla root del repo |
|
||||
| `dotnet publish` fallisce con file in uso | l'app è aperta | chiudila e ripeti |
|
||||
| `CheckDocs.cs` segnala un file non descritto | nuovo `.cs` o doc non documentato | aggiungilo a `architettura.md` o a `docs/README.md` |
|
||||
|
||||
## Dopo la disinstallazione
|
||||
|
||||
Restano `cad_index.db`, `raw/`, `step/`, i CSV e `%AppData%\PrepStep\`: vanno cancellati a mano.
|
||||
@@ -0,0 +1,66 @@
|
||||
# Guida per chi sviluppa
|
||||
|
||||
Come lavorare sul codice e sui doc. Per capire com'è fatto il programma parti da
|
||||
[`architettura.md`](architettura.md); per rilasciare vedi [`rilascio.md`](rilascio.md).
|
||||
|
||||
## Requisiti
|
||||
|
||||
- Windows e [.NET 10 SDK](https://dotnet.microsoft.com/download).
|
||||
- Accesso alla share `X:\` solo per indicizzare davvero; i test non ne hanno bisogno.
|
||||
- Node.js (per `npx markdownlint-cli2`, solo per i doc).
|
||||
|
||||
## Build e avvio
|
||||
|
||||
```powershell
|
||||
cd PrepStep
|
||||
dotnet build -c Release
|
||||
dotnet run -c Release
|
||||
```
|
||||
|
||||
In sviluppo `cad_index.db` si cerca nella cartella di build, non nella radice del repo: vedi
|
||||
[`db-cad-index.md`](db-cad-index.md#come-lapp-trova-il-db-allavvio).
|
||||
|
||||
## Test
|
||||
|
||||
```powershell
|
||||
cd PrepStep.Tests
|
||||
dotnet test
|
||||
```
|
||||
|
||||
I test xUnit chiamano gli `internal` del progetto principale grazie a `InternalsVisibleTo`. I
|
||||
`.asm` dei test si costruiscono byte per byte con piccoli helper invece di versionare file
|
||||
binari: ogni caso strano trovato sulla share diventa un test minimale. Elenco dei file di test in
|
||||
[`architettura.md`](architettura.md#test).
|
||||
|
||||
## Script standalone
|
||||
|
||||
Si lanciano dalla **root del repo**, mai da dentro `PrepStep/` (che è un `WinExe`: `dotnet run`
|
||||
avvierebbe la GUI e resterebbe bloccato). Elenco in
|
||||
[`architettura.md`](architettura.md#script-standalone-scripts).
|
||||
|
||||
```powershell
|
||||
dotnet run scripts/Query.cs -- cad_index.db "SELECT COUNT(*) FROM files"
|
||||
```
|
||||
|
||||
## Documentazione come codice
|
||||
|
||||
I doc stanno in git, si modificano nello stesso commit del codice e si verificano a mano:
|
||||
|
||||
```powershell
|
||||
dotnet run scripts/CheckDocs.cs # link, ancore, versione/CHANGELOG, copertura dei doc
|
||||
npx markdownlint-cli2 # stile Markdown (.markdownlint-cli2.jsonc)
|
||||
```
|
||||
|
||||
Regole di scrittura:
|
||||
|
||||
- **Una sola fonte per ogni informazione**, altrove un link (tabella in [`README.md`](README.md)).
|
||||
- **Ogni doc ha un solo scopo** (Diátaxis): non mescolare istruzioni, riferimento e motivazioni.
|
||||
- Un cambiamento strutturale (fasi, formato `.asm`, schema DB, criterio di scelta di un file)
|
||||
aggiorna i doc e il CHANGELOG **nello stesso commit**.
|
||||
- Se la modifica nasce da un fallimento reale di Creo scrivi **il sintomo**: i messaggi di Creo
|
||||
non nominano il file né la causa (esempio: §7.3-7.5 di
|
||||
[`formato-asm-record.md`](formato-asm-record.md)).
|
||||
- Una scelta con alternative reali non banali merita una voce in
|
||||
[`decisioni/`](decisioni/README.md).
|
||||
- Un nuovo `.cs` va descritto in `architettura.md` e un nuovo doc elencato in `docs/README.md`:
|
||||
`CheckDocs.cs` lo verifica.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Tutorial: il tuo primo giro con PrepStep
|
||||
|
||||
Questo tutorial ti porta, con una distinta vera, dall'app appena installata ai file `raw/` pronti
|
||||
per Creo. Alla fine saprai cosa produce ogni fase e come capire se è andata bene. Per il
|
||||
dettaglio di ogni schermata e per l'esportazione in Creo vedi [`guida-utente.md`](guida-utente.md).
|
||||
|
||||
**Ti serve:** il programma installato (setup in [`rilascio.md`](rilascio.md)), la share `X:\`
|
||||
raggiungibile e un'estrazione "Distinta Base" `.xls` del gestionale, meglio se piccola (poche
|
||||
decine di righe) per il primo giro.
|
||||
|
||||
## 1. Avvia e carica l'indice
|
||||
|
||||
Apri **PrepStep - Cevolani**. Accanto a "Database" c'è un pallino.
|
||||
|
||||
- **Verde:** l'indice c'è già, vai al passo 2.
|
||||
- **Rosso:** premi **Aggiorna indice**. La prima volta scansiona tutta la share e richiede
|
||||
qualche minuto; vedi una barra di avanzamento con i conteggi.
|
||||
|
||||
Risultato atteso: pallino verde, percorso di `cad_index.db` e data dell'aggiornamento.
|
||||
Premi **Avanti**.
|
||||
|
||||
## 2. Importa la distinta
|
||||
|
||||
Trascina il `.xls` nella finestra. Risultato atteso: la griglia si riempie con
|
||||
Componente/Descrizione e nella cartella di lavoro compare un CSV con lo stesso nome base.
|
||||
Poi premi **Verifica presenza**: le righe diventano verdi (trovato nell'indice) o rosse (non
|
||||
trovato). Solo ora **Avanti** si sblocca.
|
||||
|
||||
Se vedi righe rosse non fermarti: nel primo giro è normale scoprire che l'indice è vecchio o
|
||||
che un componente non è sulla share. Vedi [`risoluzione-problemi.md`](risoluzione-problemi.md).
|
||||
|
||||
## 3. Crea la cartella raw
|
||||
|
||||
Premi **Crea cartella raw**. Risultato atteso: nella cartella di lavoro nasce `raw/` con i file
|
||||
`.prt`/`.asm` dei componenti trovati, e la griglia mostra il percorso CAD di ciascuno.
|
||||
|
||||
## 4. Trova i componenti referenziati
|
||||
|
||||
Premi **Trova componenti referenziati**. L'app legge i `.asm` in `raw/` e copia anche tutto
|
||||
quello che serve a Creo per aprirli (sotto-assiemi, componenti soppressi, riferimenti esterni).
|
||||
Risultato atteso: una finestra con l'elenco di quanto copiato e, in fondo, i codici "non
|
||||
trovati". `raw/` ora contiene più file di prima: è giusto così, perché la lista dei file
|
||||
necessari è più lunga di quella della distinta (il perché, in
|
||||
[`decisioni/0003-parser-esatto-con-fallback.md`](decisioni/0003-parser-esatto-con-fallback.md)).
|
||||
|
||||
## 5. Porta i `.asm` a Creo
|
||||
|
||||
La fase 5 mostra i `.asm` della distinta come icone trascinabili. Trascinali su **Creo
|
||||
Distributed Batch** (passi in
|
||||
[`guida-utente.md`](guida-utente.md#5-verifica-completezza-e-creo-distributed-batch)):
|
||||
i file STEP finiscono in `step/`.
|
||||
|
||||
## Cosa hai imparato
|
||||
|
||||
| Fase | Entra | Esce |
|
||||
|---|---|---|
|
||||
| 1 | share `X:\` | `cad_index.db` |
|
||||
| 2 | `.xls` del gestionale | CSV + verifica sull'indice |
|
||||
| 3 | CSV + indice | `raw/` con i componenti della distinta |
|
||||
| 4 | `.asm` in `raw/` | `raw/` completa dell'albero |
|
||||
| 5 | `raw/` | `.asm` da trascinare su Creo |
|
||||
|
||||
Per un secondo giro con un'altra distinta usa **File → Nuovo** (Ctrl+N), e prima ripulisci
|
||||
`raw/`/`step/` da **File → Pulisci raw e step...**.
|
||||
@@ -0,0 +1,85 @@
|
||||
; Setup per-utente di PrepStep - Cevolani: installa in %LOCALAPPDATA%\Programs\PrepStep
|
||||
; senza richiedere diritti di amministratore.
|
||||
; Prima: dotnet publish (vedi README), poi: iscc installer\PrepStep.iss
|
||||
|
||||
#define Exe "..\PrepStep\publish\PrepStep.exe"
|
||||
; Versione letta dall'exe (<Version> nel csproj), tolto l'eventuale "+<commit>" di .NET.
|
||||
#define AppVersion GetStringFileInfo(Exe, "ProductVersion")
|
||||
#if Pos("+", AppVersion) > 0
|
||||
#define AppVersion Copy(AppVersion, 1, Pos("+", AppVersion) - 1)
|
||||
#endif
|
||||
; Non cambiare AppGuid: è quello che fa riconoscere a Windows gli aggiornamenti come la stessa app.
|
||||
#define AppGuid "FAC1B760-4735-422F-86F0-888484CF2830"
|
||||
|
||||
[Setup]
|
||||
AppId={{{#AppGuid}}
|
||||
AppName=PrepStep - Cevolani
|
||||
AppVersion={#AppVersion}
|
||||
AppPublisher=Cevolani
|
||||
AppCopyright=Copyright (c) 2026 Davide Grilli
|
||||
; Con PrivilegesRequired=lowest {autopf} diventa %LOCALAPPDATA%\Programs.
|
||||
PrivilegesRequired=lowest
|
||||
DefaultDirName={autopf}\PrepStep
|
||||
DisableDirPage=yes
|
||||
DisableProgramGroupPage=yes
|
||||
ArchitecturesAllowed=x64compatible
|
||||
ArchitecturesInstallIn64BitMode=x64compatible
|
||||
SetupIconFile=..\PrepStep\app.ico
|
||||
UninstallDisplayIcon={app}\PrepStep.exe
|
||||
UninstallDisplayName=PrepStep - Cevolani
|
||||
OutputDir=Output
|
||||
OutputBaseFilename=PrepStep-Setup-{#AppVersion}
|
||||
Compression=lzma2
|
||||
SolidCompression=yes
|
||||
|
||||
[Languages]
|
||||
Name: "it"; MessagesFile: "compiler:Languages\Italian.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||
|
||||
[Files]
|
||||
Source: "{#Exe}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
|
||||
[InstallDelete]
|
||||
; Collegamenti col vecchio nome dell'app (fino a 0.9.0), altrimenti restano nel menu Start.
|
||||
Type: files; Name: "{autoprograms}\Cevolani StepPrep.lnk"
|
||||
Type: files; Name: "{autoprograms}\Disinstalla Cevolani StepPrep.lnk"
|
||||
Type: files; Name: "{autodesktop}\Cevolani StepPrep.lnk"
|
||||
|
||||
[Icons]
|
||||
Name: "{autoprograms}\PrepStep - Cevolani"; Filename: "{app}\PrepStep.exe"
|
||||
Name: "{autodesktop}\PrepStep - Cevolani"; Filename: "{app}\PrepStep.exe"; Tasks: desktopicon
|
||||
Name: "{autoprograms}\Disinstalla PrepStep - Cevolani"; Filename: "{uninstallexe}"
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\PrepStep.exe"; Description: "{cm:LaunchProgram,PrepStep - Cevolani}"; Flags: nowait postinstall skipifsilent
|
||||
|
||||
[Code]
|
||||
{ Se l'app è già installata, chiede se aggiornarla o disinstallarla (Inno da solo aggiornerebbe
|
||||
sempre, senza offrire la disinstallazione). In modalità silenziosa aggiorna senza chiedere. }
|
||||
function InitializeSetup(): Boolean;
|
||||
var
|
||||
Disinstallatore: String;
|
||||
Codice: Integer;
|
||||
begin
|
||||
Result := True;
|
||||
if WizardSilent then
|
||||
Exit;
|
||||
if not RegQueryStringValue(HKCU,
|
||||
'Software\Microsoft\Windows\CurrentVersion\Uninstall\{' + '{#AppGuid}' + '}_is1',
|
||||
'UninstallString', Disinstallatore) then
|
||||
Exit;
|
||||
case MsgBox('PrepStep - Cevolani è già installato.' + #13#10#13#10 +
|
||||
'Sì: aggiorna / reinstalla' + #13#10 +
|
||||
'No: disinstalla' + #13#10 +
|
||||
'Annulla: esci senza fare nulla', mbConfirmation, MB_YESNOCANCEL) of
|
||||
IDNO:
|
||||
begin
|
||||
Exec(RemoveQuotes(Disinstallatore), '', '', SW_SHOW, ewWaitUntilTerminated, Codice);
|
||||
Result := False;
|
||||
end;
|
||||
IDCANCEL:
|
||||
Result := False;
|
||||
end;
|
||||
end;
|
||||
@@ -0,0 +1,59 @@
|
||||
// dotnet run scripts/CheckDocs.cs (dalla root del repo; exit 1 se i doc sono fuori allineamento)
|
||||
// Controlla anche che ogni doc di docs/ e docs/decisioni/ sia nell'indice.
|
||||
// Controlla: link relativi (file e #ancora), <Version> del csproj == ultima voce del CHANGELOG,
|
||||
// ogni .cs di PrepStep/ e scripts/ citato in docs/architettura.md.
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
var errori = new List<string>();
|
||||
var mdFiles = Directory.EnumerateFiles(".", "*.md", SearchOption.AllDirectories)
|
||||
.Where(f => !f.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}")
|
||||
&& !f.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}")
|
||||
&& !f.Contains($"{Path.DirectorySeparatorChar}raw{Path.DirectorySeparatorChar}")
|
||||
&& !f.Contains($"{Path.DirectorySeparatorChar}step{Path.DirectorySeparatorChar}")).ToList();
|
||||
|
||||
// slug delle intestazioni come lo calcola GitHub/Gitea
|
||||
static HashSet<string> Ancore(string path) =>
|
||||
File.ReadLines(path).Where(l => l.StartsWith('#'))
|
||||
.Select(l => Regex.Replace(Regex.Replace(l.TrimStart('#').Trim().ToLowerInvariant(), @"[`*]", ""), @"[^\p{L}\p{N}\- _]", "").Replace(' ', '-'))
|
||||
.ToHashSet();
|
||||
|
||||
// 1. link relativi
|
||||
foreach (var f in mdFiles)
|
||||
{
|
||||
var testo = Regex.Replace(File.ReadAllText(f), @"```.*?```", "", RegexOptions.Singleline);
|
||||
foreach (Match m in Regex.Matches(testo, @"\]\(([^)\s]+)\)"))
|
||||
{
|
||||
var url = m.Groups[1].Value;
|
||||
if (Regex.IsMatch(url, @"^[a-z]+:")) continue; // http:, mailto:, ...
|
||||
var parti = url.Split('#', 2);
|
||||
var target = parti[0] == "" ? f : Path.GetFullPath(Path.Combine(Path.GetDirectoryName(f)!, parti[0]));
|
||||
if (!File.Exists(target) && !Directory.Exists(target)) { errori.Add($"{f}: link rotto -> {url}"); continue; }
|
||||
if (parti.Length == 2 && target.EndsWith(".md") && !Ancore(target).Contains(parti[1].ToLowerInvariant()))
|
||||
errori.Add($"{f}: ancora inesistente -> {url}");
|
||||
}
|
||||
}
|
||||
|
||||
// 2. versione
|
||||
var version = Regex.Match(File.ReadAllText("PrepStep/PrepStep.csproj"), @"<Version>(.+?)</Version>").Groups[1].Value;
|
||||
var ultima = Regex.Match(File.ReadAllText("CHANGELOG.md"), @"^## \[(\d[^\]]*)\]", RegexOptions.Multiline).Groups[1].Value;
|
||||
if (version != ultima) errori.Add($"CHANGELOG.md: ultima voce {ultima} != <Version> {version} del csproj");
|
||||
|
||||
// 3. ogni sorgente citato nella mappa file-per-file
|
||||
var arch = File.ReadAllText("docs/architettura.md");
|
||||
foreach (var cs in Directory.EnumerateFiles("PrepStep", "*.cs").Concat(Directory.EnumerateFiles("scripts", "*.cs")))
|
||||
{
|
||||
var nome = Path.GetFileName(cs);
|
||||
if (!arch.Contains(nome) && !nome.EndsWith(".Designer.cs")) errori.Add($"docs/architettura.md: {cs} non è descritto");
|
||||
}
|
||||
|
||||
// 4. ogni doc in docs/ è raggiungibile dall'indice della sua cartella
|
||||
foreach (var dir in new[] { "docs", Path.Combine("docs", "decisioni") })
|
||||
{
|
||||
var indice = File.ReadAllText(Path.Combine(dir, "README.md")) + (dir == "docs" ? File.ReadAllText(Path.Combine(dir, "decisioni", "README.md")) : "");
|
||||
foreach (var md in Directory.EnumerateFiles(dir, "*.md").Where(f => Path.GetFileName(f) != "README.md"))
|
||||
if (!indice.Contains(Path.GetFileName(md))) errori.Add($"{dir}/README.md: {Path.GetFileName(md)} non è nell'indice");
|
||||
}
|
||||
|
||||
errori.ForEach(Console.Error.WriteLine);
|
||||
Console.WriteLine(errori.Count == 0 ? $"Doc OK ({mdFiles.Count} file md)" : $"{errori.Count} problemi");
|
||||
return errori.Count == 0 ? 0 : 1;
|
||||
@@ -0,0 +1,25 @@
|
||||
import sys,os,glob,collections
|
||||
sys.path.insert(0,os.path.dirname(os.path.abspath(__file__)))
|
||||
import prt,psb
|
||||
files=sorted(glob.glob(sys.argv[1]))
|
||||
tot=collections.Counter(); errs=collections.Counter(); bygen=collections.defaultdict(collections.Counter)
|
||||
samples={}
|
||||
for f in files:
|
||||
try: d,secs,order=prt.load(f)
|
||||
except Exception as e:
|
||||
print('LOADERR',f,e); continue
|
||||
gen=d[:60].split()[2].decode()
|
||||
for n in order:
|
||||
o,l,fl=secs[n]
|
||||
typ=fl[2]
|
||||
if typ!='9': bygen[gen]['type_'+typ]+=1; continue
|
||||
b=d[o:o+l]
|
||||
p,root,e=psb.parse(b)
|
||||
full = p.p==len(b)
|
||||
if e is None and full: bygen[gen]['ok']+=1; bygen[gen]['okbytes']+=l
|
||||
else:
|
||||
bygen[gen]['err']+=1; bygen[gen]['errbytes']+=l
|
||||
key=(n.split('#')[0], str(e).split('@')[0][:50])
|
||||
errs[key]+=1; samples.setdefault(key,(os.path.basename(f),str(e)))
|
||||
for g,c in sorted(bygen.items()): print(g,dict(c))
|
||||
for k,v in errs.most_common(40): print(v,k,samples[k])
|
||||
@@ -0,0 +1,9 @@
|
||||
import sys,os
|
||||
sys.path.insert(0,os.path.dirname(os.path.abspath(__file__)))
|
||||
import prt
|
||||
for a in sys.argv[1:]:
|
||||
f,sec,off=a.split(',')
|
||||
off=int(off,16)
|
||||
d,secs,order=prt.load(f); o,l,_=secs[sec]; b=d[o:o+l]
|
||||
print('==',a)
|
||||
prt.hexdump(b,max(0,off-96),192)
|
||||
@@ -0,0 +1,30 @@
|
||||
import sys,os,math,collections
|
||||
sys.path.insert(0,os.path.dirname(os.path.abspath(__file__)))
|
||||
import prt,psb
|
||||
d,secs,order=prt.load(sys.argv[1])
|
||||
bad=collections.Counter(); good=0; unk=collections.Counter()
|
||||
def walk(v,name=''):
|
||||
global good
|
||||
if isinstance(v,psb.Node):
|
||||
for n,x in v: walk(x,n)
|
||||
elif isinstance(v,psb.Arr):
|
||||
for x in v: walk(x,name)
|
||||
elif isinstance(v,list) and v and isinstance(v[0],list) and len(v) in (3,4) and len(v[0])==3 and name in ('local_sys','orient','matrix'):
|
||||
for r in v[:3]:
|
||||
if any(isinstance(c,psb.Unk) for c in r):
|
||||
u=[c for c in r if isinstance(c,psb.Unk)]
|
||||
if len(u)==1:
|
||||
rest=sum(c*c for c in r if not isinstance(c,psb.Unk))
|
||||
unk[(u[0].code, round(math.sqrt(max(0,1-rest)),6))]+=1
|
||||
continue
|
||||
n=math.sqrt(sum(c*c for c in r))
|
||||
if abs(n-1)<1e-6: good+=1
|
||||
else: bad[(name,tuple(round(c,4) for c in r))]+=1
|
||||
for nme in order:
|
||||
o,l,f=secs[nme]
|
||||
if f[2]!='9': continue
|
||||
p,root,e=psb.parse(d[o:o+l])
|
||||
if root: walk(root[1])
|
||||
print('good rows',good,'bad',sum(bad.values()))
|
||||
for k,v in bad.most_common(15): print(v,k)
|
||||
print('unknown consts solved by norm:',unk.most_common(20))
|
||||
@@ -0,0 +1,11 @@
|
||||
import sys,os
|
||||
sys.path.insert(0,os.path.dirname(os.path.abspath(__file__)))
|
||||
import prt,psb
|
||||
for f in sys.argv[1:]:
|
||||
b=open(f,'rb').read()
|
||||
p,root,e=psb.parse(b)
|
||||
print(os.path.basename(f),len(b),p.p,'fields',p.nid,'lenient',p.lenient,'OK' if e is None else 'ERR '+repr(e))
|
||||
if e is not None: prt.hexdump(b,max(0,p.p-64),128)
|
||||
if root:
|
||||
out=[root[0]+':']; psb.fmt(root[1],1,out)
|
||||
open(f[:-4]+'.txt','w',encoding='utf-8').write('\n'.join(out))
|
||||
@@ -0,0 +1,40 @@
|
||||
import sys,re
|
||||
sys.stdout.reconfigure(encoding='utf-8',errors='replace')
|
||||
def load(p):
|
||||
d=open(p,'rb').read()
|
||||
base=int(d[:d.find(b'\n')].split()[-2],16)
|
||||
secs={}
|
||||
order=[]
|
||||
i=d.find(b'#UGC_TOC')
|
||||
while True:
|
||||
e=d.find(b'\n',i); pos=e+1; nxt=None
|
||||
while True:
|
||||
e2=d.find(b'\n',pos); line=d[pos:e2]
|
||||
if line.startswith(b'#'): break
|
||||
f=line.rstrip(b'#').split()
|
||||
name=f[0].decode('latin1'); off=int(f[1],16); ln=int(f[2],16)
|
||||
if name=='NEXT_TOC_ENTRY': nxt=base+off
|
||||
else:
|
||||
mk=len(name)+2
|
||||
secs[name]=(base+off+mk,ln-mk,[x.decode() for x in f[3:]]); order.append(name)
|
||||
pos=e2+1
|
||||
if nxt is None: break
|
||||
i=nxt
|
||||
return d,secs,order
|
||||
def sec(p,name):
|
||||
d,secs,_=load(p); o,l,_=secs[name]; return d[o:o+l]
|
||||
def hexdump(b,start=0,n=None,w=32):
|
||||
b=b[start:start+n] if n else b[start:]
|
||||
for i in range(0,len(b),w):
|
||||
c=b[i:i+w]
|
||||
print(f"{start+i:6x} "+' '.join(f"{x:02x}" for x in c).ljust(w*3)+' '+''.join(chr(x) if 32<=x<127 else '.' for x in c))
|
||||
if __name__=='__main__':
|
||||
d,secs,order=load(sys.argv[1])
|
||||
if len(sys.argv)>2:
|
||||
o,l,_=secs[sys.argv[2]]; b=d[o:o+l]
|
||||
st=int(sys.argv[3],0) if len(sys.argv)>3 else 0
|
||||
n=int(sys.argv[4],0) if len(sys.argv)>4 else None
|
||||
hexdump(b,st,n)
|
||||
else:
|
||||
for nme in order:
|
||||
o,l,f=secs[nme]; print(f"{nme:26} {o:8} {l:8} {' '.join(f):34} {d[o:o+20]!r}")
|
||||
@@ -0,0 +1,529 @@
|
||||
"""Parser generico delle sezioni a marcatori dei file Creo (.prt/.asm). Reverse engineering.
|
||||
Modello: ogni campo dichiarato per nome (e0 <tipo> nome\\0) riceve un id progressivo.
|
||||
In modalita' posizionale il campo atteso e' next[ultimo id letto]; f7 <id> lo forza.
|
||||
"""
|
||||
import struct, sys, os
|
||||
|
||||
|
||||
class Err(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Node(list):
|
||||
"""struct: lista di (nome, valore)"""
|
||||
|
||||
def get(s, k, d=None):
|
||||
for n, v in s:
|
||||
if n == k:
|
||||
return v
|
||||
return d
|
||||
|
||||
def all(s, k):
|
||||
return [v for n, v in s if n == k]
|
||||
|
||||
|
||||
class Arr(list):
|
||||
pass
|
||||
|
||||
|
||||
class Unk(float):
|
||||
"""costante a un byte non ancora identificata"""
|
||||
def __new__(cls, code):
|
||||
o = float.__new__(cls, 'nan')
|
||||
o.code = code
|
||||
return o
|
||||
|
||||
def __repr__(s):
|
||||
return f"<c{s.code:02x}>"
|
||||
|
||||
|
||||
class P:
|
||||
def __init__(s, b, trace=False):
|
||||
s.b = b
|
||||
s.p = 0
|
||||
s.fields = {}
|
||||
s.nid = 0
|
||||
s.next = {}
|
||||
s.alias = {}
|
||||
s.stack = []
|
||||
s.trace = trace
|
||||
s.root = None
|
||||
s.last = None
|
||||
s.lenient = 0
|
||||
s.lastc = None
|
||||
s.popped = None
|
||||
|
||||
def u8(s):
|
||||
v = s.b[s.p]
|
||||
s.p += 1
|
||||
return v
|
||||
|
||||
def peek(s):
|
||||
return s.b[s.p] if s.p < len(s.b) else None
|
||||
|
||||
def rint(s):
|
||||
b = s.u8()
|
||||
if b < 0x80:
|
||||
return b
|
||||
if b < 0xc0:
|
||||
return (b & 0x3f) << 8 | s.u8()
|
||||
if b < 0xe0:
|
||||
v = (b & 0x1f) << 16 | s.b[s.p] << 8 | s.b[s.p + 1]
|
||||
s.p += 2
|
||||
return v
|
||||
if b == 0xe8:
|
||||
return s.u8()
|
||||
if b == 0xe9:
|
||||
v = int.from_bytes(s.b[s.p:s.p + 2], 'big')
|
||||
s.p += 2
|
||||
return v
|
||||
if b == 0xea:
|
||||
v = int.from_bytes(s.b[s.p:s.p + 3], 'big')
|
||||
s.p += 3
|
||||
return v
|
||||
if b == 0xeb:
|
||||
v = int.from_bytes(s.b[s.p:s.p + 4], 'big')
|
||||
s.p += 4
|
||||
return v
|
||||
if b == 0xf5:
|
||||
return s.rint() | 0x80000000
|
||||
if b == 0xf6:
|
||||
return -1
|
||||
raise Err(f"int tag {b:02x} @{s.p - 1:x}")
|
||||
|
||||
CONST = {0x17: -6.283185307179586, 0x14: 4.71238898038469, 0x0d: -0.5, 0x11: 3.141592653589793, 0x15: -3.141592653589793, 0x12: 1.5707963267948966, 0x16: -1.5707963267948966, 0x13: 6.283185307179586, 0x18: 0.0, 0x0f: 1.0, 0x0e: 0.5, 0x10: -1.0, 0x0c: 0.1, 0x0a: 0.01, 0x07: 0.001}
|
||||
|
||||
FBASE = 0x3f9d
|
||||
FCONST = {0x0b: 1.0, 0x28: -1.0}
|
||||
|
||||
def rflt(s):
|
||||
t = s.u8()
|
||||
if 0x30 <= t <= 0x4f:
|
||||
T = (0x37 + t - 0x30) if t < 0x40 else (0xb7 + t - 0x40)
|
||||
raw = bytes([T]) + s.b[s.p:s.p + 3]
|
||||
s.p += 3
|
||||
elif t == 0xea:
|
||||
raw = s.b[s.p:s.p + 3] + bytes(1)
|
||||
s.p += 3
|
||||
elif t == 0xeb:
|
||||
raw = s.b[s.p:s.p + 4]
|
||||
s.p += 4
|
||||
elif t == 0xe9:
|
||||
raw = s.b[s.p:s.p + 2] + bytes(2)
|
||||
s.p += 2
|
||||
elif t == 0xe8:
|
||||
raw = s.b[s.p:s.p + 1] + bytes(3)
|
||||
s.p += 1
|
||||
elif t == 0x00:
|
||||
return 0.0
|
||||
elif t in s.FCONST:
|
||||
return s.FCONST[t]
|
||||
elif t < 0x30:
|
||||
return Unk(t)
|
||||
elif 0x50 <= t <= 0xdf:
|
||||
raw = (s.FBASE + t).to_bytes(2, 'big') + s.b[s.p:s.p + 2]
|
||||
s.p += 2
|
||||
else:
|
||||
raise Err(f"flt tag {t:02x} @{s.p - 1:x}")
|
||||
return struct.unpack('>f', raw)[0]
|
||||
|
||||
def rbyte(s):
|
||||
b = s.u8()
|
||||
if b == 0xe8:
|
||||
return s.u8()
|
||||
return b
|
||||
|
||||
# prefissi negativi a 16 bit dei tag a5-df: bfd0..c00d meno tre buchi (bfd2, bfdf, bffe)
|
||||
NEG = [x for x in range(0xbfd0, 0xc00e) if x not in (0xbfd2, 0xbfdf, 0xbffe)]
|
||||
|
||||
def rdbl(s):
|
||||
t = s.u8()
|
||||
if t in s.CONST:
|
||||
return s.CONST[t]
|
||||
if t <= 0x16 and t > 0x03:
|
||||
return Unk(t)
|
||||
if t <= 0x03:
|
||||
c = s.u8()
|
||||
k = t + 1
|
||||
pl = s.b[s.p:s.p + k]
|
||||
s.p += k
|
||||
if c not in s.CONST:
|
||||
if 0x04 <= c <= 0x16:
|
||||
return Unk(c)
|
||||
raise Err(f"dbl const {c:02x} after modifier @{s.p - 1:x}")
|
||||
raw = bytearray(struct.pack('>d', s.CONST[c]))
|
||||
raw[8 - k:] = pl
|
||||
return struct.unpack('>d', bytes(raw))[0]
|
||||
if t == 0xed:
|
||||
v = struct.unpack('>d', s.b[s.p:s.p + 8])[0]
|
||||
s.p += 8
|
||||
return v
|
||||
if t == 0xea:
|
||||
v = struct.unpack('>d', s.b[s.p:s.p + 3] + bytes(5))[0]
|
||||
s.p += 3
|
||||
return v
|
||||
if t == 0xec:
|
||||
v = struct.unpack('>d', s.b[s.p:s.p + 7] + bytes(1))[0]
|
||||
s.p += 7
|
||||
return v
|
||||
if 0x19 <= t <= 0x48:
|
||||
if t <= 0x2f:
|
||||
T = 0x3c + (t - 0x17) // 5
|
||||
m = (t - 0x17) % 5
|
||||
else:
|
||||
T = 0xbc + (t - 0x30) // 5
|
||||
m = (t - 0x30) % 5
|
||||
if m == 4:
|
||||
pl = s.b[s.p:s.p + 2]
|
||||
s.p += 2
|
||||
raw = bytes([T]) + pl + b'\x00' * 5
|
||||
elif m == 3:
|
||||
pl = s.b[s.p:s.p + 2]
|
||||
s.p += 2
|
||||
raw = bytes([T]) + pl + pl[1:] * 5
|
||||
elif m == 2:
|
||||
pl = s.b[s.p:s.p + 7]
|
||||
s.p += 7
|
||||
raw = bytes([T]) + pl
|
||||
elif m == 1:
|
||||
pl = s.b[s.p:s.p + 6]
|
||||
s.p += 6
|
||||
raw = bytes([T]) + pl + b'\x00'
|
||||
else:
|
||||
pl = s.b[s.p:s.p + 3]
|
||||
s.p += 3
|
||||
raw = bytes([T]) + pl + bytes(4)
|
||||
return struct.unpack('>d', raw)[0]
|
||||
if 0x49 <= t <= 0xdf:
|
||||
hi = (0x3f75 + t) if t <= 0xa4 else s.NEG[t - 0xa5]
|
||||
pl = s.b[s.p:s.p + 6]
|
||||
s.p += 6
|
||||
return struct.unpack('>d', hi.to_bytes(2, 'big') + pl)[0]
|
||||
raise Err(f"dbl tag {t:02x} @{s.p - 1:x}")
|
||||
|
||||
def rstr(s):
|
||||
if s.peek() == 0xe1:
|
||||
s.p += 1
|
||||
return None
|
||||
e = s.b.index(0, s.p)
|
||||
v = s.b[s.p:e].decode('latin1')
|
||||
s.p = e + 1
|
||||
return v
|
||||
|
||||
def rarr(s, n, rd):
|
||||
out = []
|
||||
while len(out) < n:
|
||||
b = s.peek()
|
||||
if 0xe4 <= b <= 0xe6 and out:
|
||||
s.p += 1
|
||||
out += [out[-1]] * (b - 0xe3)
|
||||
elif b == 0xe7 and out:
|
||||
s.p += 1
|
||||
k = s.rint()
|
||||
out += [out[-1]] * k
|
||||
else:
|
||||
out.append(rd())
|
||||
if len(out) != n:
|
||||
raise Err(f"array overrun {len(out)}>{n} @{s.p:x}")
|
||||
return out
|
||||
|
||||
def scalar(s, f):
|
||||
t = f['t']
|
||||
if t in (0x0a, 0x03):
|
||||
if s.peek() == 0xf8:
|
||||
s.p += 1
|
||||
f['dim'] = (s.rint(),)
|
||||
return [s.rstr() for _ in range(f['dim'][0])]
|
||||
if f.get('dim'):
|
||||
out = []
|
||||
while len(out) < f['dim'][0] and 0x20 <= s.peek() < 0xe0:
|
||||
out.append(s.rstr())
|
||||
return out
|
||||
return s.rstr()
|
||||
rd = s.rdbl if t == 0x02 else s.rbyte if t in (0x07, 0x04) else s.rflt if t == 0x06 else s.rint
|
||||
b = s.peek()
|
||||
if b == 0xe1:
|
||||
s.p += 1
|
||||
return None
|
||||
if b == 0xf8:
|
||||
s.p += 1
|
||||
f['dim'] = (s.rint(),)
|
||||
elif b == 0xf9:
|
||||
s.p += 1
|
||||
f['dim'] = (s.rint(), s.rint())
|
||||
elif b == 0xfc:
|
||||
s.p += 1
|
||||
f['dim'] = (s.rint(),) + tuple(f['dim'][1:])
|
||||
elif b == 0xfa:
|
||||
s.p += 1
|
||||
f['dim'] = tuple(s.rint() for _ in range(s.rint()))
|
||||
d = f.get('dim')
|
||||
if d is None:
|
||||
return rd()
|
||||
n = 1
|
||||
for k in d:
|
||||
n *= k
|
||||
v = s.rarr(n, rd)
|
||||
if len(d) == 2:
|
||||
v = [v[i * d[1]:(i + 1) * d[1]] for i in range(d[0])]
|
||||
return v
|
||||
|
||||
def sch(s, fid):
|
||||
return s.alias.get(fid, fid)
|
||||
|
||||
# --- contenitori -------------------------------------------------
|
||||
def owner_index(s, E, post=False):
|
||||
own = s.fields[E]['own']
|
||||
for i in range(len(s.stack) - 1, -1, -1):
|
||||
c = s.stack[i]
|
||||
if c['kind'] == 'struct':
|
||||
if c['schema'] in own:
|
||||
if post and s.fields[E].get('post') and i > 0:
|
||||
return i - 1
|
||||
return i
|
||||
elif c['occ'] == E or ('A', c['occ']) in own:
|
||||
return i
|
||||
return None
|
||||
|
||||
def default_close(s):
|
||||
"""chiusura implicita prima di un campo con nome o di un salto senza marcatore f0-f4:
|
||||
se il campo atteso appartiene al contenitore padre di quello in cui e' stato scritto
|
||||
l'ultimo campo, quest'ultimo contenitore si considera chiuso."""
|
||||
if s.last is None or not s.stack or s.lastc is None:
|
||||
return
|
||||
E = s.next.get(s.last)
|
||||
if E is None:
|
||||
return
|
||||
p = None
|
||||
for k in range(len(s.stack) - 1, -1, -1):
|
||||
if s.stack[k] is s.lastc:
|
||||
p = k
|
||||
break
|
||||
if not p:
|
||||
return
|
||||
i = s.owner_index(E, True)
|
||||
if i == p - 1:
|
||||
del s.stack[p:]
|
||||
|
||||
def marker(s):
|
||||
b = s.peek()
|
||||
if b is None or not (0xf0 <= b <= 0xf4):
|
||||
return None
|
||||
s.p += 1
|
||||
if b == 0xf4:
|
||||
return s.rint()
|
||||
return b - 0xf0
|
||||
|
||||
def explicit(s, n):
|
||||
for _ in range(n):
|
||||
if not s.stack:
|
||||
raise Err(f"pop underflow @{s.p:x}")
|
||||
s.popped = s.stack.pop()
|
||||
|
||||
def trans(s, E):
|
||||
if s.last is not None:
|
||||
s.next[s.last] = E
|
||||
s.last = E
|
||||
|
||||
def locate(s, E, jump, stay):
|
||||
"""porta in cima allo stack il contenitore che riceve il campo E. Ritorna 'elem' o 'field'."""
|
||||
f = s.fields[E]
|
||||
hdr = s.peek() in (0xf8, 0xf9)
|
||||
top = s.stack[-1]
|
||||
if not hdr and f['t'] == 0:
|
||||
for i in range(len(s.stack) - 1, -1, -1):
|
||||
c = s.stack[i]
|
||||
if c['kind'] == 'array' and c['occ'] == E:
|
||||
del s.stack[i + 1:]
|
||||
return 'elem'
|
||||
par = f['parent']
|
||||
i = s.owner_index(E, not jump)
|
||||
if i is None and jump and top['kind'] == 'struct' and top['node']:
|
||||
# variante di unione riusata in un altro contenitore: resta dove siamo
|
||||
f['own'].add(top['schema'])
|
||||
s.lenient += 1
|
||||
return 'field'
|
||||
fresh = top['kind'] == 'struct' and not top['node']
|
||||
if jump and fresh and i != len(s.stack) - 1:
|
||||
s.alias[top['field']] = par
|
||||
top['schema'] = par
|
||||
return 'field'
|
||||
if i is not None:
|
||||
if jump and i != len(s.stack) - 1:
|
||||
s.lenient += 1
|
||||
del s.stack[i + 1:]
|
||||
return 'field'
|
||||
if jump and top['kind'] == 'array' and not top['node'] and f['t'] == 0 and not hdr:
|
||||
top['occ'] = E
|
||||
return 'elem'
|
||||
raise Err(f"field {E} {f['name']}: container {par} not on stack @{s.p:x}")
|
||||
|
||||
def element(s, E):
|
||||
c = s.stack[-1]
|
||||
s.trans(E)
|
||||
s.lastc = c
|
||||
if s.peek() == 0xfb:
|
||||
s.p += 1
|
||||
b = s.u8()
|
||||
if len(c['node']) >= c['n']:
|
||||
raise Err(f"too many elements in {s.fields[c['field']]['name']} @{s.p:x}")
|
||||
if b == 0xe1:
|
||||
c['node'].append(None)
|
||||
elif b in (0xe2, 0xe3):
|
||||
v = Node()
|
||||
c['node'].append(v)
|
||||
s.stack.append(dict(kind='struct', field=c['field'], schema=s.sch(c['occ']), node=v, elem=True, occ=None, occ0=c['occ']))
|
||||
else:
|
||||
raise Err(f"array elem tag {b:02x} @{s.p - 1:x} in {s.fields[c['field']]['name']}")
|
||||
|
||||
def value(s, fid):
|
||||
c = s.stack[-1] if s.stack else None
|
||||
f = s.fields[fid]
|
||||
s.trans(fid)
|
||||
s.lastc = c
|
||||
if f['t'] != 0:
|
||||
v = s.scalar(f)
|
||||
else:
|
||||
b = s.u8()
|
||||
if b == 0xe1:
|
||||
v = None
|
||||
elif b in (0xe2, 0xe3):
|
||||
v = Node()
|
||||
elif b == 0xf8:
|
||||
v = Arr()
|
||||
n = s.rint()
|
||||
elif b == 0xf9:
|
||||
v = Arr()
|
||||
n = s.rint()
|
||||
s.rint()
|
||||
else:
|
||||
raise Err(f"ptr tag {b:02x} @{s.p - 1:x} field {f['name']}")
|
||||
if c is None:
|
||||
s.root = (f['name'], v)
|
||||
elif c['kind'] == 'array':
|
||||
c.setdefault('tail', []).append((f['name'], v))
|
||||
else:
|
||||
c['node'].append((f['name'], v))
|
||||
if isinstance(v, Arr):
|
||||
s.stack.append(dict(kind='array', field=fid, occ=fid, n=n, node=v))
|
||||
elif isinstance(v, Node):
|
||||
s.stack.append(dict(kind='struct', field=fid, schema=s.sch(fid), node=v, occ=None))
|
||||
|
||||
def run(s):
|
||||
b = s.b
|
||||
while s.p < len(b):
|
||||
m = s.marker()
|
||||
x = s.peek()
|
||||
if x is None:
|
||||
s.explicit(m or 0)
|
||||
break
|
||||
if x == 0xe0:
|
||||
s.p += 1
|
||||
t = s.u8()
|
||||
e = b.index(0, s.p)
|
||||
name = b[s.p:e].decode('latin1')
|
||||
s.p = e + 1
|
||||
m2 = s.marker()
|
||||
if m2 is not None:
|
||||
m = (m or 0) + m2
|
||||
s.popped = None
|
||||
if m is None:
|
||||
s.default_close()
|
||||
else:
|
||||
s.explicit(m)
|
||||
c = s.stack[-1] if s.stack else None
|
||||
s.nid += 1
|
||||
fid = s.nid
|
||||
if c is None:
|
||||
par = 0
|
||||
elif c['kind'] == 'array':
|
||||
par = ('A', c['occ'])
|
||||
else:
|
||||
par = c['schema']
|
||||
s.fields[fid] = dict(name=name, t=t, parent=par, own={par})
|
||||
pp = s.popped
|
||||
if pp is not None and c is not None and pp['kind'] == 'struct' and c['kind'] == 'struct' and pp['schema'] == c['schema'] and t == 0:
|
||||
s.fields[fid]['post'] = True
|
||||
if s.trace:
|
||||
print(f"{s.p:6x} {' ' * len(s.stack)}#{fid} {name} t={t}")
|
||||
s.value(fid)
|
||||
continue
|
||||
if m:
|
||||
s.explicit(m)
|
||||
jump = False
|
||||
if x == 0xf7:
|
||||
s.p += 1
|
||||
E = s.rint()
|
||||
jump = True
|
||||
if m is None:
|
||||
s.default_close()
|
||||
if E not in s.fields:
|
||||
raise Err(f"jump to unknown field {E} @{s.p:x}")
|
||||
else:
|
||||
E = s.next.get(s.last)
|
||||
if E is None:
|
||||
raise Err(f"no expected field @{s.p:x} after {s.last} "
|
||||
f"{s.fields[s.last]['name'] if s.last else ''}")
|
||||
kind = s.locate(E, jump or m is not None, m == 0)
|
||||
if s.trace:
|
||||
print(f"{s.p:6x} {' ' * len(s.stack)}{'>' if jump else '='}{E} {s.fields[E]['name']} {kind}")
|
||||
if kind == 'elem':
|
||||
s.element(E)
|
||||
else:
|
||||
s.value(E)
|
||||
return s.root
|
||||
|
||||
|
||||
def short(x, maxarr=40):
|
||||
if isinstance(x, Unk):
|
||||
return repr(x)
|
||||
if isinstance(x, float):
|
||||
return repr(round(x, 9))
|
||||
if isinstance(x, list):
|
||||
r = ', '.join(short(y) for y in x[:maxarr])
|
||||
return f"[{r}{', ...' if len(x) > maxarr else ''}] (n={len(x)})"
|
||||
return repr(x)
|
||||
|
||||
|
||||
def fmt(v, ind=0, out=None, maxarr=40):
|
||||
pad = ' ' * ind
|
||||
if isinstance(v, Node):
|
||||
for n, x in v:
|
||||
if isinstance(x, (Node, Arr)):
|
||||
out.append(f"{pad}{n}:")
|
||||
fmt(x, ind + 1, out, maxarr)
|
||||
else:
|
||||
out.append(f"{pad}{n} = {short(x, maxarr)}")
|
||||
elif isinstance(v, Arr):
|
||||
for i, x in enumerate(v):
|
||||
out.append(f"{pad}[{i}]")
|
||||
fmt(x, ind + 1, out, maxarr)
|
||||
|
||||
|
||||
def parse(b, trace=False):
|
||||
p = P(b, trace)
|
||||
try:
|
||||
root = p.run()
|
||||
return p, root, None
|
||||
except (Err, IndexError, ValueError, KeyError) as e:
|
||||
return p, p.root, e
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import prt
|
||||
d, secs, order = prt.load(sys.argv[1])
|
||||
names = sys.argv[2].split(',') if len(sys.argv) > 2 and sys.argv[2] != '*' else [n for n in order if secs[n][2][2] == '9']
|
||||
outdir = sys.argv[3] if len(sys.argv) > 3 and sys.argv[3] != '-' else None
|
||||
for nme in names:
|
||||
o, l, f = secs[nme]
|
||||
b = d[o:o + l]
|
||||
p, root, e = parse(b, trace=len(sys.argv) > 4)
|
||||
ok = e is None and not p.stack
|
||||
print(f"{nme:22} len={l:7} parsed={p.p:7} fields={p.nid:5} lenient={p.lenient} {'OK' if ok else 'ERR ' + repr(e) + ' stack=' + str(len(p.stack))}")
|
||||
if e is not None:
|
||||
prt.hexdump(b, max(0, p.p - 64), 128)
|
||||
if outdir and root:
|
||||
out = [root[0] + ':']
|
||||
fmt(root[1], 1, out)
|
||||
open(f"{outdir}/{nme.replace(':', '_').replace('#', '_')}.txt", 'w', encoding='utf-8').write('\n'.join(out))
|
||||
@@ -0,0 +1,47 @@
|
||||
import sys,os,math,collections,struct,glob,itertools
|
||||
sys.path.insert(0,os.path.dirname(os.path.abspath(__file__)))
|
||||
import prt,psb
|
||||
class TF(float):
|
||||
pass
|
||||
orig=psb.P.rdbl
|
||||
def rdbl(s):
|
||||
p0=s.p; t=s.b[p0]
|
||||
v=orig(s)
|
||||
if 0x49<=t<=0xdf and not isinstance(v,psb.Unk):
|
||||
o=TF(v); o.tag=t; o.pl=bytes(s.b[p0+1:p0+7]); return o
|
||||
return v
|
||||
psb.P.rdbl=rdbl
|
||||
OFFS=[0]+list(range(0x44,0x4d))
|
||||
def mag(tf,off):
|
||||
return struct.unpack('>d',(0x3f75+tf.tag-off).to_bytes(2,'big')+tf.pl)[0]
|
||||
votes=collections.defaultdict(collections.Counter)
|
||||
def walk(v,name=''):
|
||||
if isinstance(v,psb.Node):
|
||||
for n,x in v: walk(x,n)
|
||||
elif isinstance(v,psb.Arr):
|
||||
for x in v: walk(x,name)
|
||||
elif isinstance(v,list) and v and isinstance(v[0],list) and len(v) in (3,4) and len(v[0])==3 and name in ('local_sys','orient','matrix','transf_to_local'):
|
||||
rows=v[:3]
|
||||
if any(isinstance(c,psb.Unk) for r in rows for c in r): return
|
||||
vecs=rows+[[rows[i][j] for i in range(3)] for j in range(3)]
|
||||
for r in vecs:
|
||||
tf=[c for c in r if isinstance(c,TF)]
|
||||
if not tf or len(tf)>2: continue
|
||||
rest=sum(c*c for c in r if not isinstance(c,TF))
|
||||
sols=[]
|
||||
for offs in itertools.product(OFFS,repeat=len(tf)):
|
||||
n=rest+sum(mag(c,o)**2 for c,o in zip(tf,offs))
|
||||
if abs(n-1)<1e-9: sols.append(offs)
|
||||
if len(sols)==1:
|
||||
for c,o in zip(tf,sols[0]): votes[c.tag][o]+=1
|
||||
for f in sys.argv[1:]:
|
||||
for g in glob.glob(f):
|
||||
try: d,secs,order=prt.load(g)
|
||||
except Exception: continue
|
||||
for nme in order:
|
||||
o,l,fl=secs[nme]
|
||||
if fl[2]!='9' or l<200: continue
|
||||
p,root,e=psb.parse(d[o:o+l])
|
||||
if root: walk(root[1])
|
||||
for t in sorted(votes):
|
||||
print(hex(t), {hex(o):n for o,n in votes[t].items()}, ' -> prefix', [hex((0x3f75+t-o) | (0x8000 if o else 0)) for o in votes[t]])
|
||||
@@ -0,0 +1,75 @@
|
||||
"""Decompressore Unix compress (.Z, LZW) minimale, come ncompress."""
|
||||
import sys, os
|
||||
|
||||
|
||||
def unz(data):
|
||||
assert data[0] == 0x1f and data[1] == 0x9d, "non e' un flusso .Z"
|
||||
flags = data[2]
|
||||
maxbits = flags & 0x1f
|
||||
block = bool(flags & 0x80)
|
||||
out = bytearray()
|
||||
n_bits = 9
|
||||
maxcode = (1 << n_bits) - 1
|
||||
table = {i: bytes([i]) for i in range(256)}
|
||||
free = 257 if block else 256
|
||||
pos = 3 * 8 # posizione in bit
|
||||
total = len(data) * 8
|
||||
start = pos # inizio del gruppo corrente di codici (per il riallineamento)
|
||||
old = None
|
||||
while pos + n_bits <= total:
|
||||
byte = pos >> 3
|
||||
chunk = int.from_bytes(data[byte:byte + 4], 'little')
|
||||
code = (chunk >> (pos & 7)) & ((1 << n_bits) - 1)
|
||||
pos += n_bits
|
||||
if old is None:
|
||||
old = table[code]
|
||||
out += old
|
||||
continue
|
||||
if code == 256 and block:
|
||||
table = {i: bytes([i]) for i in range(256)}
|
||||
free = 256
|
||||
# riallinea a multipli di 8 codici
|
||||
grp = n_bits * 8
|
||||
pos = start + ((pos - start + grp - 1) // grp) * grp
|
||||
n_bits = 9
|
||||
maxcode = (1 << n_bits) - 1
|
||||
start = pos
|
||||
old = None
|
||||
free = 257
|
||||
continue
|
||||
if code in table:
|
||||
entry = table[code]
|
||||
elif code == free:
|
||||
entry = old + old[:1]
|
||||
else:
|
||||
raise ValueError(f"codice LZW non valido {code} (free={free}) a bit {pos}")
|
||||
out += entry
|
||||
if free <= (1 << maxbits) - 1:
|
||||
table[free] = old + entry[:1]
|
||||
free += 1
|
||||
old = entry
|
||||
if free > maxcode and n_bits < maxbits:
|
||||
grp = n_bits * 8
|
||||
pos = start + ((pos - start + grp - 1) // grp) * grp
|
||||
n_bits += 1
|
||||
maxcode = (1 << n_bits) - 1
|
||||
start = pos
|
||||
return bytes(out)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import prt
|
||||
d, secs, order = prt.load(sys.argv[1])
|
||||
outdir = sys.argv[2]
|
||||
for n in order:
|
||||
o, l, f = secs[n]
|
||||
b = d[o:o + l]
|
||||
if b[:2] == b'\x1f\x9d':
|
||||
try:
|
||||
u = unz(b)
|
||||
except Exception as e:
|
||||
print(n, 'ERR', e)
|
||||
continue
|
||||
print(n, 'compressa', l, '->', len(u), 'dichiarata', int(f[0], 16), u[:60])
|
||||
open(os.path.join(outdir, n.replace(':', '_') + '.bin'), 'wb').write(u)
|
||||
Reference in new issue
Block a user