7 Commits
Author SHA1 Message Date
davideandClaude Sonnet 5.5 d5fb20a34e Rilascia la 1.1.0
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 08:52:57 +02:00
davideandClaude Sonnet 5.5 6d1e36a2ae Aggiungi la lista personalizzata di codici nella fase 2
In alternativa al file del gestionale si possono digitare i codici uno alla
volta (Invio/Aggiungi, Canc per togliere). Tutto a valle legge solo
_righeCorrenti, quindi basta una nuova sorgente; la selezione della griglia
mantiene il colore verde/rosso della verifica.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 08:52:05 +02:00
davideandClaude Sonnet 5.5 ccfa00ff52 Aggiungi docs-as-code: indice Diátaxis, nuovi doc e controllo automatico
Nuovi doc: tutorial-primo-giro, risoluzione-problemi, sviluppo, rilascio,
glossario e registro delle decisioni (docs/decisioni, 6 ADR). docs/README.md
e' l'indice per categoria e la tabella delle fonti uniche; il README di
root rimanda a sviluppo/rilascio invece di duplicarli.

scripts/CheckDocs.cs verifica link e ancore, <Version> contro CHANGELOG,
copertura di architettura.md e presenza di ogni doc nell'indice;
.markdownlint-cli2.jsonc configura il lint Markdown.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-02 08:05:10 +02:00
davideandClaude Opus 5.5 5fac2edf52 Rilascia la 1.0.0: licenza, CHANGELOG e documentazione riallineata al codice
Versione portata a 1.0.0 (csproj, CLAUDE.md) e voce [1.0.0] nel
CHANGELOG con tutto il lavoro dalla 0.9.0: setup per-utente, nuovo
nome "PrepStep - Cevolani", DB fisso nella cartella di installazione,
"Elimina CSV delle distinte", guide aggiornate.

Nuova LICENSE proprietaria (Davide Grilli, tutti i diritti riservati),
riportata anche in <Copyright> dell'exe e AppCopyright del setup.
README: sequenza completa di comandi per generare il setup.

Documentazione riallineata al codice dopo un controllo file per file:
nomi delle fasi come nella UI, "File → Nuovo" che non cancella file,
mappa dei file e test nel README, riferimenti ad AssiemeCompleto (non
esiste più) e ruolo di CaricaCodiciNoti/CaricaCandidati in fase 4.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 08:53:35 +02:00
davideandClaude Opus 5.5 4ae3d0e087 Aggiungi "Elimina CSV delle distinte" e allinea le guide alla cartella di installazione
Nuova voce "File → Elimina CSV delle distinte..." (EliminaCsv): elimina
i *.csv al primo livello della cartella di lavoro, che altrimenti si
accumulano uno per distinta importata. Chiede conferma mostrando
l'elenco dei file; gli .xls del gestionale e raw/step non sono toccati.

Guida utente e guida in-app: nuova sezione "Dove finiscono i file"
(DB sempre nella cartella di installazione, CSV/raw/step nella cartella
di lavoro, comandi di pulizia, come aprire la cartella nascosta in
AppData), fase 1 allineata al DB fisso e ad "Apri DB..." solo per la
sessione. Tolta dalla guida utente la sezione di installazione: chi la
legge ha già il programma installato (resta nel README).

Doc aggiornati nello stesso commit: CLAUDE.md, docs/architettura.md,
docs/guida-utente.md, CHANGELOG.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 08:39:58 +02:00
davideandClaude Opus 5.5 64ff1d8770 Rinomina l'app in "PrepStep - Cevolani"
Nome mostrato all'utente cambiato da "Cevolani StepPrep" a "PrepStep -
Cevolani": titolo finestra, <Product>/<AssemblyTitle>, guida in-app,
setup (nome, collegamenti nel menu Start, voce in App installate).
Aggiornando, il setup rimuove i collegamenti col vecchio nome
([InstallDelete]) per non lasciarli doppi nel menu Start.

Doc aggiornati nello stesso commit: README.md, CLAUDE.md,
docs/architettura.md, docs/guida-utente.md, CHANGELOG.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 08:33:34 +02:00
davideandClaude Opus 5.5 57020f9964 Aggiungi il setup per-utente e fissa il DB nella cartella di installazione
Nuovo installer/PrepStep.iss (Inno Setup 6): installa in
%LOCALAPPDATA%\Programs\PrepStep senza diritti di amministratore, con
collegamenti nel menu Start al programma e al disinstallatore. Se l'app
è già installata chiede se aggiornarla o disinstallarla (Inno da solo
aggiornerebbe sempre); con /VERYSILENT aggiorna senza chiedere.

Il DB è ora sempre cad_index.db accanto all'eseguibile, cioè nella
cartella di installazione: non si ricorda più l'ultimo DB aperto
(ultimo_db.txt) e "Apri DB..." vale solo per la sessione. Senza una
cartella di lavoro salvata si propone la cartella di installazione
invece di quella del DB.

Doc aggiornati nello stesso commit: README.md, CLAUDE.md,
docs/architettura.md, docs/db-cad-index.md, docs/guida-utente.md,
CHANGELOG.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 08:29:03 +02:00
29 changed files with 1162 additions and 217 deletions

No files matched your search

+3
View File
@@ -40,3 +40,6 @@ publish/
# Impostazioni locali dell'editor # Impostazioni locali dell'editor
.vscode/ .vscode/
# Setup generato da Inno Setup (installer/PrepStep.iss)
installer/Output/
+15
View File
@@ -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"]
}
+61
View File
@@ -5,6 +5,67 @@ 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 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/). aderisce a [Semantic Versioning](https://semver.org/lang/it/).
## [Non rilasciato]
## [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 ## [0.9.0] - 2026-09-23
### Aggiunto ### Aggiunto
+25 -9
View File
@@ -5,9 +5,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Cos'è questo progetto ## Cos'è questo progetto
`PrepStep` è il nome tecnico del progetto (cartella, namespace, csproj, eseguibile `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 `<AssemblyTitle>` in `PrepStep/PrepStep.csproj`, usato nel titolo della finestra insieme
alla versione, `<Version>`, attualmente `0.9.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 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 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 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 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 `PrepStep.Tests/` (xUnit) contiene unit e integration test — vedi
`dotnet test` da dentro quella cartella. Riferimento al progetto principale `dotnet test` da dentro quella cartella. Riferimento al progetto principale
via `ProjectReference` + `InternalsVisibleTo` (in `PrepStep.csproj`), così via `ProjectReference` + `InternalsVisibleTo` (in `PrepStep.csproj`), così
@@ -56,8 +63,14 @@ Dove scrivere cosa:
| [`docs/formato-asm.md`](docs/formato-asm.md) | euristica testuale di fallback | | [`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/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/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 | | [`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 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 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 riparte da zero (i §7.3/§7.4/§7.5 di `formato-asm-record.md` sono l'esempio del formato
@@ -65,7 +78,8 @@ atteso).
## Architettura ## 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 completo file-per-file in [`docs/architettura.md`](docs/architettura.md); qui solo il flusso
che conta per capire il prodotto. che conta per capire il prodotto.
@@ -76,10 +90,11 @@ designer visuale):
1. **Database** — carica/aggiorna `cad_index.db` (`Indexer.ScanAsync`, radice disco di 1. **Database** — carica/aggiorna `cad_index.db` (`Indexer.ScanAsync`, radice disco di
default `X:\` hardcoded in `RadiceDiscoDefault`); un pallino verde/rosso accanto al titolo 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/`) 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'è non è più mostrata qui — proposta dall'ultima usata o dalla cartella di installazione se
ancora nulla di salvato, cambiabile in qualunque momento da "File → Scegli cartella di 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 lavoro..." — vedi `docs/db-cad-index.md` per il dettaglio di schema e percorso del DB
percorso 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 2. **Importa Distinta Base** — `DistintaBaseImporter.ConvertiInCsv` genera il CSV
componente;descrizione nella cartella di lavoro (da file scelto o trascinato); se il file 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 segue la convenzione del gestionale (`DBP_<codice>_<timestamp>`), aggiunge l'assieme
@@ -114,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 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 → 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 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/`) ## Script standalone (`scripts/`)
@@ -131,5 +147,5 @@ intercambiabili:
- `ExtractCreoStrings.cs`/`BomViaDb.cs` usano `AssemblyComponentScanner` (euristica testuale, - `ExtractCreoStrings.cs`/`BomViaDb.cs` usano `AssemblyComponentScanner` (euristica testuale,
`docs/formato-asm.md`) e risolvono i sottoassiemi contro `cad_index.db`. `docs/formato-asm.md`) e risolvono i sottoassiemi contro `cad_index.db`.
- `CreoBom.cs` usa `AsmRecordReader` (parser esatto dei record binari, - `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`. cercandoli in cartelle locali (`-p`), non contro `cad_index.db`.
+17
View File
@@ -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
View File
@@ -12,6 +12,11 @@ public partial class Form1 : Form
private readonly Button _btnTrovaPrt = new(); private readonly Button _btnTrovaPrt = new();
private readonly Button _btnVerifica = new(); private readonly Button _btnVerifica = new();
private readonly FlowLayoutPanel _rigaVerifica = 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 ListView _lstAnteprimaAsm = new();
private readonly Label _lblStatus = new(); private readonly Label _lblStatus = new();
private readonly Label _lblDb = new(); private readonly Label _lblDb = new();
@@ -25,6 +30,7 @@ public partial class Form1 : Form
private readonly MenuStrip _menu = new(); private readonly MenuStrip _menu = new();
private readonly ToolStripMenuItem _mnuNuovaEstrazione = new("Nuovo"); private readonly ToolStripMenuItem _mnuNuovaEstrazione = new("Nuovo");
private readonly ToolStripMenuItem _mnuPulisci = new("Pulisci raw e step..."); 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..."); private readonly ToolStripMenuItem _mnuCambiaCartella = new("Scegli cartella di lavoro...");
// Wizard: una fase alla volta occupa tutta la finestra, si avanza solo quando quella // 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? _dbPath;
private string? _cartellaLavoro; private string? _cartellaLavoro;
private string? _percorsoInputCorrente; private bool _ripristinoModalita;
private List<(string Componente, string Descrizione)>? _righeCorrenti; private List<(string Componente, string Descrizione)>? _righeCorrenti;
private Dictionary<string, List<string>>? _ultimaVerifica; private Dictionary<string, List<string>>? _ultimaVerifica;
private string? _ultimaCartellaRaw; private string? _ultimaCartellaRaw;
@@ -74,7 +80,7 @@ public partial class Form1 : Form
AllowDrop = true; AllowDrop = true;
// Versione letta dall'assembly (<Version> nel csproj) invece che duplicata qui a mano. // Versione letta dall'assembly (<Version> nel csproj) invece che duplicata qui a mano.
var versione = typeof(Form1).Assembly.GetName().Version; var versione = typeof(Form1).Assembly.GetName().Version;
Text = $"Cevolani StepPrep {versione?.ToString(3)}"; Text = $"PrepStep - Cevolani {versione?.ToString(3)}";
MinimumSize = new Size(760, 480); MinimumSize = new Size(760, 480);
Font = new Font("Segoe UI", 9.5f); Font = new Font("Segoe UI", 9.5f);
// Stessa icona di ApplicationIcon nel csproj: la si rilegge dall'exe compilato invece // 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(); _mnuPulisci.Click += (_, _) => PulisciRawEStep();
mnuFile.DropDownItems.Add(_mnuPulisci); mnuFile.DropDownItems.Add(_mnuPulisci);
_mnuEliminaCsv.Click += (_, _) => EliminaCsv();
mnuFile.DropDownItems.Add(_mnuEliminaCsv);
var mnuAiuto = new ToolStripMenuItem("Aiuto"); var mnuAiuto = new ToolStripMenuItem("Aiuto");
var mnuGuida = new ToolStripMenuItem("Guida") { ShortcutKeys = Keys.F1 }; var mnuGuida = new ToolStripMenuItem("Guida") { ShortcutKeys = Keys.F1 };
mnuGuida.Click += (_, _) => new GuidaForm().ShowDialog(this); mnuGuida.Click += (_, _) => new GuidaForm().ShowDialog(this);
@@ -262,7 +271,12 @@ public partial class Form1 : Form
return; return;
} }
_percorsoInputCorrente = null; AzzeraDistinta();
VaiAllaFase(1);
}
private void AzzeraDistinta()
{
_righeCorrenti = null; _righeCorrenti = null;
_ultimaCartellaRaw = null; _ultimaCartellaRaw = null;
_ultimaVerifica = null; _ultimaVerifica = null;
@@ -271,8 +285,68 @@ public partial class Form1 : Form
_lstAnteprimaAsm.Items.Clear(); _lstAnteprimaAsm.Items.Clear();
ImpostaStatoImport("", Color.Black); ImpostaStatoImport("", Color.Black);
_rigaVerifica.Visible = false; _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() private Panel BuildFaseDatabase()
@@ -396,12 +470,51 @@ public partial class Form1 : Form
}; };
contenuto.Controls.Add(lblIstruzioni); contenuto.Controls.Add(lblIstruzioni);
var rigaImporta = new FlowLayoutPanel var rigaModalita = new FlowLayoutPanel
{ {
AutoSize = true, AutoSize = true,
FlowDirection = FlowDirection.LeftToRight, FlowDirection = FlowDirection.LeftToRight,
Margin = new Padding(0, 0, 0, 8), 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 var btnSfoglia = new Button
{ {
Text = "Scegli file distinta...", Text = "Scegli file distinta...",
@@ -642,10 +755,15 @@ public partial class Form1 : Form
private void ImportaDistinta(string percorso) 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 try
{ {
var (_, righe) = DistintaBaseImporter.ConvertiInCsv(percorso, _cartellaLavoro!); var (_, righe) = DistintaBaseImporter.ConvertiInCsv(percorso, _cartellaLavoro!);
_percorsoInputCorrente = percorso;
_righeCorrenti = righe; _righeCorrenti = righe;
_gridImport.Rows.Clear(); _gridImport.Rows.Clear();
@@ -662,7 +780,6 @@ public partial class Form1 : Form
} }
catch (Exception ex) catch (Exception ex)
{ {
_percorsoInputCorrente = null;
_righeCorrenti = null; _righeCorrenti = null;
_gridImport.Rows.Clear(); _gridImport.Rows.Clear();
ImpostaStatoImport($"Errore durante la conversione: {ex.Message}", Color.DarkRed); ImpostaStatoImport($"Errore durante la conversione: {ex.Message}", Color.DarkRed);
@@ -690,16 +807,20 @@ public partial class Form1 : Form
foreach (DataGridViewRow riga in _gridImport.Rows) foreach (DataGridViewRow riga in _gridImport.Rows)
{ {
string componente = (string)riga.Cells["componente"].Value!; 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(198, 239, 206)
: Color.FromArgb(255, 199, 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(); AggiornaStatoAvanti();
} }
private void CreaCartellaRaw() private void CreaCartellaRaw()
{ {
if (_percorsoInputCorrente is null || _righeCorrenti is null) if (_righeCorrenti is null)
return; return;
// L'assieme generale è già una riga di _righeCorrenti (aggiunta da // 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); "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 /// <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 /// 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 /// 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( private static readonly string CartellaImpostazioni = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "PrepStep"); 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"); 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 /// <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 /// c'è ancora una preferenza salvata (o punta a una cartella cancellata), la cartella di
/// trova il DB (più sensata dell'eseguibile: quella è dove l'utente tiene il proprio lavoro, /// installazione (%LOCALAPPDATA%\Programs\PrepStep col setup). Sempre modificabile con
/// non dov'è installata la app). Richiede quindi che InizializzaDatabase sia già stata /// "File → Scegli cartella di lavoro...".</summary>
/// chiamata. Sempre modificabile con "File → Scegli cartella di lavoro...".</summary>
private void InizializzaCartellaLavoro() private void InizializzaCartellaLavoro()
{ {
var ultima = LeggiUltimaCartellaLavoro(); var ultima = LeggiUltimaCartellaLavoro();
_cartellaLavoro = ultima is not null && Directory.Exists(ultima) _cartellaLavoro = ultima is not null && Directory.Exists(ultima)
? ultima ? ultima
: (_dbPath is not null ? Path.GetDirectoryName(_dbPath) : null) ?? AppContext.BaseDirectory; : AppContext.BaseDirectory;
AggiornaStatoAvanti(); AggiornaStatoAvanti();
} }
@@ -1190,15 +1350,10 @@ public partial class Form1 : Form
private void InizializzaDatabase() private void InizializzaDatabase()
{ {
var candidati = new[] // Il DB sta sempre nella cartella di installazione; "Apri DB..." ne carica un altro
{ // solo per la sessione corrente, senza ricordarlo al prossimo avvio.
LeggiUltimoDbUsato(), string predefinito = Path.Combine(AppContext.BaseDirectory, "cad_index.db");
Path.Combine(AppContext.BaseDirectory, "cad_index.db"), if (!File.Exists(predefinito))
Path.Combine(Directory.GetCurrentDirectory(), "cad_index.db"),
};
_dbPath = candidati.FirstOrDefault(p => p is not null && File.Exists(p));
if (_dbPath is null)
{ {
_lblStatus.Text = "Database non trovato. Usa 'Apri DB...' per selezionarlo oppure 'Aggiorna indice' per crearlo."; _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."; _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; return;
} }
CaricaDatabase(_dbPath); CaricaDatabase(predefinito);
} }
private void ScegliDatabase() 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: {ultimaScansione.ToLocalTime():dd/MM/yyyy HH:mm}"
: "Ultimo aggiornamento indice: sconosciuto"; : "Ultimo aggiornamento indice: sconosciuto";
_lblStatus.Text = "Pronto."; _lblStatus.Text = "Pronto.";
SalvaUltimoDbUsato(path);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -1254,33 +1408,6 @@ public partial class Form1 : Form
AggiornaStatoAvanti(); 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() private async void AggiornaIndice()
{ {
string dbPath = _dbPath ?? Path.Combine(AppContext.BaseDirectory, "cad_index.db"); string dbPath = _dbPath ?? Path.Combine(AppContext.BaseDirectory, "cad_index.db");
+88 -17
View File
@@ -1,3 +1,5 @@
using System.Runtime.InteropServices;
namespace PrepStep; namespace PrepStep;
/// <summary>Guida interattiva richiamabile da "Aiuto ▸ Guida" (F1): stessa guida completa di /// <summary>Guida interattiva richiamabile da "Aiuto ▸ Guida" (F1): stessa guida completa di
@@ -32,12 +34,34 @@ public class GuidaForm : Form
private static readonly Sezione[] Sezioni = private static readonly Sezione[] Sezioni =
{ {
new("Panoramica", new("Panoramica",
"Cevolani StepPrep prepara i file CAD Pro-ENGINEER/Creo da passare a Creo " + "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 " + "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\".", "e guida attraverso 5 fasi, una alla volta, con \"Avanti\"/\"Indietro\".",
NotaTesto: "\"File ▸ Nuovo\" (Ctrl+N) azzera distinta/raw/verifica per ripartire " + NotaTesto: "\"File ▸ Nuovo\" (Ctrl+N) azzera lo stato di distinta/raw/verifica per ripartire " +
"con una nuova distinta base, senza dover ricaricare il database."), "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", new("Prima di iniziare",
"Prima di importare la prima distinta:", "Prima di importare la prima distinta:",
Passi: Passi:
@@ -50,9 +74,10 @@ public class GuidaForm : Form
"Se il file segue la convenzione di nome DBP_<codice>_<timestamp>, l'app " + "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 " + "aggiunge da sola l'assieme generale come componente in più: non serve " +
"fare nulla di diverso.", "fare nulla di diverso.",
"Conviene impostare la cartella di lavoro giusta (fase 1, o \"File ▸ Scegli " + "Se non vuoi usare la cartella di installazione, scegli la cartella di " +
"cartella di lavoro...\") prima di importare la distinta: è lì che " + "lavoro (\"File ▸ Scegli cartella di lavoro...\") prima di importare la " +
"finiscono CSV, \"raw\" e, dopo l'esportazione con Creo, \"step\".", "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 " + "Se la cartella di lavoro è già stata usata per un'altra distinta, conviene " +
"pulire \"raw\" e \"step\" prima di ripartire (\"File ▸ Pulisci raw e " + "pulire \"raw\" e \"step\" prima di ripartire (\"File ▸ Pulisci raw e " +
"step...\"): \"File ▸ Nuovo\" azzera solo lo stato dell'app, non i file " + "step...\"): \"File ▸ Nuovo\" azzera solo lo stato dell'app, non i file " +
@@ -60,10 +85,18 @@ public class GuidaForm : Form
]), ]),
new("1. Database", new("1. Database",
"Carica o aggiorna l'indice \"cad_index.db\" a partire dalle cartelle di primo " + "All'avvio l'app carica da sola \"cad_index.db\" dalla cartella di installazione. " +
"livello del disco/share (radice di default X:\\). Un pallino verde/rosso indica " + "Un pallino verde/rosso indica se un database è caricato.",
"se un database è caricato. La cartella di lavoro, dove finiscono CSV, \"raw\" e " + Passi:
"\"step\", si cambia da \"File ▸ Scegli cartella di lavoro...\"."), [
"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", new("2. Importa Distinta Base",
"Due passi in sequenza:", "Due passi in sequenza:",
@@ -71,7 +104,10 @@ public class GuidaForm : Form
[ [
"Carica la distinta: trascina il file .xls nella finestra, oppure premi " + "Carica la distinta: trascina il file .xls nella finestra, oppure premi " +
"\"Scegli file distinta...\". Le righe importate compaiono nella griglia " + "\"Scegli file distinta...\". Le righe importate compaiono nella griglia " +
"(Componente/Descrizione) e viene scritto un CSV nella cartella di lavoro.", "(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 " + "Verifica la presenza dei file sul server: compare solo dopo un import " +
"riuscito. Cerca ogni componente nel database — le righe della griglia si " + "riuscito. Cerca ogni componente nel database — le righe della griglia si " +
"colorano di verde (trovato) o rosso (non trovato nell'indice).", "colorano di verde (trovato) o rosso (non trovato nell'indice).",
@@ -127,19 +163,21 @@ public class GuidaForm : Form
NotaAttenzione: true), NotaAttenzione: true),
new("Ripartire da capo", new("Ripartire da capo",
"Quando devi processare un'altra distinta, usa \"File ▸ Nuovo\" (Ctrl+N): azzera " + "Quando devi processare un'altra distinta, usa \"File ▸ Nuovo\" (Ctrl+N): svuota " +
"CSV, raw e verifica delle fasi 2-5 e riporta al passo 2, senza dover ricaricare " + "le griglie e la verifica delle fasi 2-5 e riporta al passo 2, senza dover " +
"il database o la cartella di lavoro (restano quelli già impostati in fase 1).", "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 " + 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 " + "prima di un nuovo giro (senza toccare l'indice), usa \"File ▸ Pulisci raw e " +
"step...\"."), "step...\"; per eliminare i CSV che ogni distinta importata lascia nella " +
"cartella di lavoro, usa \"File ▸ Elimina CSV delle distinte...\"."),
}; };
public GuidaForm() public GuidaForm()
{ {
Text = "Guida"; Text = "Guida";
ClientSize = new Size(780, 520); ClientSize = new Size(1000, 680);
MinimumSize = new Size(580, 400); MinimumSize = new Size(640, 440);
StartPosition = FormStartPosition.CenterParent; StartPosition = FormStartPosition.CenterParent;
Font = new Font("Segoe UI", 9.5f); Font = new Font("Segoe UI", 9.5f);
try try
@@ -191,10 +229,11 @@ public class GuidaForm : Form
colore, TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis); colore, TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis);
}; };
var testo = new RichTextBox var testo = new TestoSolaLettura
{ {
Dock = DockStyle.Fill, Dock = DockStyle.Fill,
ReadOnly = true, ReadOnly = true,
TabStop = false,
BorderStyle = BorderStyle.None, BorderStyle = BorderStyle.None,
BackColor = Color.White, BackColor = Color.White,
Font = new Font(Font.FontFamily, 10f), Font = new Font(Font.FontFamily, 10f),
@@ -278,4 +317,36 @@ public class GuidaForm : Form
box.SelectionStart = 0; box.SelectionStart = 0;
box.SelectionLength = 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);
}
}
} }
+4 -3
View File
@@ -8,10 +8,11 @@
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode> <ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
<ApplicationIcon>app.ico</ApplicationIcon> <ApplicationIcon>app.ico</ApplicationIcon>
<AssemblyTitle>Cevolani StepPrep</AssemblyTitle> <AssemblyTitle>PrepStep - Cevolani</AssemblyTitle>
<Product>Cevolani StepPrep</Product> <Product>PrepStep - Cevolani</Product>
<Company>Cevolani</Company> <Company>Cevolani</Company>
<Version>0.9.0</Version> <Copyright>Copyright (c) 2026 Davide Grilli</Copyright>
<Version>1.1.0</Version>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract> <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
</PropertyGroup> </PropertyGroup>
+35 -60
View File
@@ -1,4 +1,4 @@
# Cevolani StepPrep # PrepStep - Cevolani
App Windows Forms (.NET 10, progetto `PrepStep`) per indicizzare, cercare e 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 recuperare i file CAD Pro-ENGINEER/Creo di un disco/share di rete, e per trasformare
@@ -9,15 +9,16 @@ passaggio, non lo sostituisce.
## Funzionalità ## Funzionalità
1. **Indice CAD** — all'avvio carica automaticamente l'ultimo `cad_index.db` 1. **Indice CAD** — all'avvio carica automaticamente `cad_index.db` dalla
usato (ricordato tra un avvio e l'altro). Se manca, si può selezionare un DB cartella di installazione (`%LOCALAPPDATA%\Programs\PrepStep`). Se manca, lo si
esistente con **Apri DB...** o generarne uno nuovo con **Aggiorna crea con **Aggiorna indice**, oppure si apre un altro DB per la sola sessione
indice**, che scansiona il disco (di default `X:\`, tutte le cartelle di primo con **Apri DB...**. **Aggiorna indice** scansiona il disco (di default `X:\`,
livello che trova) e indicizza tutti i file. La **cartella di lavoro** (dove tutte le cartelle di primo livello che trova) e indicizza tutti i file. La
finiscono il CSV e la cartella `raw/`; gli STEP esportati con Creo vanno in **cartella di lavoro** (dove finiscono il CSV e la cartella `raw/`; gli STEP
`step/`, dentro la stessa cartella) è proposta automaticamente e ricordata tra esportati con Creo vanno in `step/`, dentro la stessa cartella) è di default la
un avvio e l'altro, modificabile in qualunque momento da **File → Scegli cartella di installazione, modificabile in qualunque momento da **File → Scegli
cartella di lavoro...**. 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 2. **Importa Distinta Base** — trascina (o scegli da file) l'estrazione
`.xls`/`.xml` del gestionale (foglio "Distinta Base", colonne `.xls`/`.xml` del gestionale (foglio "Distinta Base", colonne
Componente/Descrizione): viene generato un CSV nella cartella di lavoro, con Componente/Descrizione): viene generato un CSV nella cartella di lavoro, con
@@ -34,12 +35,13 @@ passaggio, non lo sostituisce.
`raw/` per trovare ricorsivamente i sotto-assiemi/parti che li `raw/` per trovare ricorsivamente i sotto-assiemi/parti che li
compongono, copiandoli a loro volta in `raw/` (passo opzionale se la compongono, copiandoli a loro volta in `raw/` (passo opzionale se la
distinta contiene solo parti singole). distinta contiene solo parti singole).
5. **Trascina su Creo** — mostra i file `.asm` della distinta già in `raw/`, 5. **Verifica completezza** — mostra i file `.asm` della distinta già in `raw/`,
trascinabili su Creo Distributed Batch per l'esportazione STEP (passo trascinabili su Creo Distributed Batch per l'esportazione STEP (passo
manuale, fuori da quest'app): scrive in `step/`, dentro la cartella di manuale, fuori da quest'app): scrive in `step/`, dentro la cartella di
lavoro. lavoro.
Guida utente passo-passo completa in [`docs/guida-utente.md`](docs/guida-utente.md) 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). (stessa sintesi anche in-app da **Aiuto → Guida**, F1).
## Requisiti ## Requisiti
@@ -49,68 +51,41 @@ Guida utente passo-passo completa in [`docs/guida-utente.md`](docs/guida-utente.
- Accesso alla share di rete con i file CAD (`X:\` di default) per - Accesso alla share di rete con i file CAD (`X:\` di default) per
l'indicizzazione l'indicizzazione
## Build ## Build, test e rilascio
```powershell Build, avvio e test: [`docs/sviluppo.md`](docs/sviluppo.md). Pubblicazione dell'eseguibile e
cd PrepStep generazione del setup di installazione: [`docs/rilascio.md`](docs/rilascio.md).
dotnet build -c Release
```
## Avvio Il setup installa **solo per l'utente corrente**, in `%LOCALAPPDATA%\Programs\PrepStep`, senza
diritti di amministratore; si disinstalla da "Impostazioni → App installate".
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.
## Struttura del progetto ## Struttura del progetto
Tutto il codice è in `PrepStep/` (unico progetto nella solution, GUI e Il codice dell'app è tutto in `PrepStep/` (GUI e indicizzazione integrate); accanto ci sono
indicizzazione integrate): 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. - `Program.cs` — entry point.
- `Form1.cs` — finestra principale (indice CAD, import Distinta Base, griglia - `Form1.cs` — finestra principale, wizard a 5 fasi e menu; UI costruita interamente in
risultati); UI costruita interamente in codice, nessun designer visuale. codice (`Form1.Designer.cs` è solo lo scheletro del template).
- `Indexer.cs` — scansione del disco e scrittura/aggiornamento del DB SQLite. - `Indexer.cs` — scansione del disco e scrittura/aggiornamento del DB SQLite.
- `DistintaBaseImporter.cs` — parsing del file Distinta Base (SpreadsheetML - `DistintaBaseImporter.cs` — parsing del file Distinta Base (SpreadsheetML
XML o Excel binario/OpenXML tramite ExcelDataReader) e generazione CSV. 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 Il database generato (`cad_index.db`), i file Excel/CSV della distinta
trascinati nella root e le cartelle `raw/`/`step/` sono rigenerabili e non trascinati nella root e le cartelle `raw/`/`step/` sono rigenerabili e non
sono tracciati in git (vedi `.gitignore`). 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 ## Licenza
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:
```powershell Software proprietario, © 2026 Davide Grilli, tutti i diritti riservati: vedi
cd PrepStep.Tests [`LICENSE`](LICENSE).
dotnet test
```
+54
View File
@@ -0,0 +1,54 @@
# 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.
- [`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) |
| 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
```
+54 -24
View File
@@ -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 Panoramica di tutti i file sorgente e di come collaborano. `PrepStep` è il nome
tecnico del progetto (cartella, namespace, csproj, eseguibile `PrepStep.exe`); tecnico del progetto (cartella, namespace, csproj, eseguibile `PrepStep.exe`);
**Cevolani StepPrep** è il nome mostrato all'utente finale (titolo finestra, icona, **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; per il 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 formato binario `.asm` vedi `docs/formato-asm.md` (euristica testuale) e
`docs/formato-asm-record.md` (parser esatto dei record); per la parallelizzazione della `docs/formato-asm-record.md` (parser esatto dei record); per la parallelizzazione della
copia in `raw/` vedi `docs/copia-raw-parallela.md`. 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 **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 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 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 **2. Distinta del gestionale → CSV.** `DistintaBaseImporter.ConvertiInCsv` riduce l'export a
due colonne `componente;descrizione`. Se il nome del file segue la convenzione 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 | | # | 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...") | | 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 | | 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 | | 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) | | 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; 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 "File → Scegli cartella di lavoro..." → `ScegliCartellaLavoro`, cambia in qualunque momento
la destinazione di CSV/`raw/`/`step/`; "File → Pulisci raw e step..." svuota le cartelle 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 Guida" (F1) apre `GuidaForm`, sintesi interattiva delle 5 fasi per chi non ha letto questi
docs. docs.
Altre responsabilità in Form1.cs: Altre responsabilità in Form1.cs:
- `TrovaPercorsiCad` / `CaricaCodiciNoti` — query sul DB (`files_ultima_versione`), la - `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 prima risolve i componenti del CSV (fase 3), la seconda carica tutti i codici prt/asm noti
componenti sia da `AssemblyComponentScanner` per validare i token generici). 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 - `CopiaFileTrovati` — copia (in parallelo, grado 6) i file trovati in `raw/`, saltando
silenziosamente quelli irraggiungibili (share offline/permessi). silenziosamente quelli irraggiungibili (share offline/permessi).
- `PulisciRawEStep` — svuota `raw/` e `step/` nella cartella di lavoro (manutenzione, - `PulisciRawEStep` — svuota `raw/` e `step/` nella cartella di lavoro (manutenzione,
richiede conferma). richiede conferma).
- `InizializzaDatabase` / `ScegliDatabase` — localizzazione del DB (ultimo usato, salvato in - `EliminaCsv` — elimina i `*.csv` generati dall'import (uno per distinta) nella cartella di
`%AppData%/PrepStep/ultimo_db.txt`, o `cad_index.db` accanto all'eseguibile). 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 - `InizializzaCartellaLavoro` / `ScegliCartellaLavoro` — stesso pattern per la cartella di
lavoro (`ultima_cartella_lavoro.txt`), fallback `AppContext.BaseDirectory` se non c'è lavoro (`ultima_cartella_lavoro.txt`), fallback `AppContext.BaseDirectory` se non c'è
ancora nulla di salvato: nessuna dipendenza da un marker di repo, funziona identica ancora nulla di salvato: nessuna dipendenza da un marker di repo, funziona identica
@@ -236,44 +244,66 @@ completi".
sotto-assiemi trovati e copiati in `raw/`, più i codici referenziati che non corrispondono a 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 nessun file indicizzato (possono essere disegni, componenti davvero mancanti, o falsi
positivi della scansione `.asm`). positivi della scansione `.asm`).
- **GuidaForm.cs** — guida interattiva ("Aiuto → Guida"/F1): lista delle 5 fasi a sinistra, - **GuidaForm.cs** — guida interattiva ("Aiuto → Guida"/F1): elenco delle sezioni a sinistra
testo a destra; sintesi, il dettaglio resta solo in questi docs. (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 ## Persistenza
- **DB** (`cad_index.db`, SQLite) — vedi tabella `files`/vista `files_ultima_versione` sopra. - **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` Sempre `cad_index.db` accanto all'eseguibile (`AppContext.BaseDirectory`, cioè la cartella
(`SalvaUltimoDbUsato`/`LeggiUltimoDbUsato`), scritto ogni volta che si carica un DB di installazione): `InizializzaDatabase` carica solo quello, e "Aggiorna indice" lo crea lì
(all'avvio se già noto, oppure da "Apri DB..." o al termine di "Aggiorna indice"); se il se manca. "Apri DB..." carica un altro file solo per la sessione corrente, senza
file non c'è o punta a un percorso cancellato, `InizializzaDatabase` ripiega su ricordarlo. Radice disco di scansione
`cad_index.db` accanto all'eseguibile o nella working directory. Radice disco di scansione
di default `X:\` (`Form1.RadiceDiscoDefault`, hardcoded). 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 `%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 a una cartella cancellata) si propone la cartella di installazione
`AppContext.BaseDirectory`; cambiabile in qualunque momento da "File → Scegli cartella di (`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/` 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 (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/` esporta gli STEP a mano con Creo Distributed Batch, a partire dai file trascinati da `raw/`
in fase 5. 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/`) ## Script standalone (`scripts/`)
C# file-based apps (.NET 10, `dotnet run scripts/Nome.cs -- arg1 arg2`, nessun `.csproj`, 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): dipendenze extra con `#:package Nome@versione` in testa al file):
- **ExtractCreoStrings.cs** — BOM di un `.asm` risolvendo ricorsivamente i sotto-assiemi - **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). Pro-E).
- **BomViaDb.cs** — come sopra ma con la tokenizzazione generica validata contro il DB, per - **BomViaDb.cs** — come sopra ma con la tokenizzazione generica validata contro il DB, per
intercettare anche i nomi alfabetici delle parti commerciali. 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 `#:project`) invece dell'euristica testuale. Risolve i sottoassiemi cercandoli in cartelle
locali (`-p`), non contro `cad_index.db` come `ExtractCreoStrings.cs`/`BomViaDb.cs`. 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 - **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 investigare a mano nuovi falsi positivi prima di toccare la regex di
`AssemblyComponentScanner`. `AssemblyComponentScanner`.
- **Query.cs** — query SQL ad-hoc contro `cad_index.db` da riga di comando. - **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/` > ⚠️ 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 > (che contiene un `.csproj` di tipo `WinExe`), esegue l'app GUI vera invece dello script
@@ -283,7 +313,7 @@ dipendenze extra con `#:package Nome@versione` in testa al file):
## Test ## Test
- **PrepStep.Tests/** (xUnit) — riferimento al progetto principale via `ProjectReference` - **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 `Indexer.ParseFilename` e `AssemblyComponentScanner.EstraiCodiciReferenziati` senza esporli
come API pubblica. File: `IndexerParseFilenameTests.cs`, `IndexerScanTests.cs`, come API pubblica. File: `IndexerParseFilenameTests.cs`, `IndexerScanTests.cs`,
`AssemblyComponentScannerTests.cs`, `AsmRecordReaderTests.cs`, `AssemblyComponentScannerTests.cs`, `AsmRecordReaderTests.cs`,
+25 -25
View File
@@ -29,10 +29,12 @@ componenti e nel filtro per cartella).
Vista `files_ultima_versione`: stessa struttura di `files`, filtrata alla sola riga con 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() `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 OVER (PARTITION BY ... ORDER BY versione DESC, data_modifica DESC, percorso_completo)`. Le
le ricerche di componenti (`Form1.TrovaPercorsiCad`, `Form1.CaricaCodiciNoti`) interrogano ricerche per codice (`Form1.TrovaPercorsiCad` in fase 3, `Form1.CaricaCodiciNoti`)
questa vista, non `files` direttamente, così un componente con più versioni sulla share interrogano questa vista, non `files` direttamente, così un componente con più versioni sulla
risolve sempre all'ultima. Deliberatamente una `ROW_NUMBER()` invece di un self-join con 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()` `GROUP BY`: su ~200k righe un self-join degenera in tempi quasi quadratici, `ROW_NUMBER()`
resta lineare. 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. 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 1. Crea lo schema se non esiste (`CREATE TABLE/INDEX IF NOT EXISTS`; la vista invece è
un DB vuoto (prima esecuzione) sia su uno esistente (aggiornamento incrementale). `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 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 `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 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 ## Come l'app trova il DB all'avvio
`Form1.InizializzaDatabase` prova, in ordine, i seguenti candidati e usa il primo che esiste `Form1.InizializzaDatabase` carica sempre e solo `cad_index.db` accanto all'eseguibile
su disco: (`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 Se non esiste, la fase 1 resta bloccata su "Database non trovato" finché l'utente non preme
`%AppData%/PrepStep/ultimo_db.txt` (`Form1.LeggiUltimoDbUsato`/`SalvaUltimoDbUsato`) "Aggiorna indice" (`Form1.AggiornaIndice`, che crea `cad_index.db` da zero accanto
2. `cad_index.db` accanto all'eseguibile (`AppContext.BaseDirectory`) all'eseguibile) o "Apri DB..." (`Form1.ScegliDatabase`, file picker libero: il DB scelto vale
3. `cad_index.db` nella working directory corrente solo per la sessione corrente, e "Aggiorna indice" aggiorna quello).
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`).
**Attenzione in sviluppo (dotnet run / IDE):** `AppContext.BaseDirectory` è la cartella di **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 build (`PrepStep/bin/Release/net10.0-windows` o `Debug/...`), non la radice del repo. L'app usa
quella cartella finisce per errore una copia di `cad_index.db` (es. creata lì da una vecchia quindi la copia di `cad_index.db` in quella cartella (creandola lì al primo "Aggiorna
sessione), l'app la userà invece di quella nella radice del repo, e le due copie divergeranno indice"), non quella nella radice del repo né quella installata: per usare un altro DB in
a ogni "Aggiorna indice" fatto nell'uno o nell'altro ambiente. Se i risultati di una ricerca 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 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 DB..." quale file è effettivamente caricato (mostrato in fase 1 accanto al conteggio dei file
indicizzati) prima di sospettare un bug nella ricerca. 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 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 il primo per `tipo_file` (il più recente, dato l'`ORDER BY data_modifica DESC`) e scarta gli
altri. altri.
- **Risoluzione codici noti per lo scanner assiemi** (`Form1.CaricaCodiciNoti`, usata sia dalla - **Codici noti per lo scanner assiemi** (`Form1.CaricaCodiciNoti`, fase 4;
GUI in fase 4 sia da `scripts/BomViaDb.cs`): carica in un'unica query tutti i codici `scripts/BomViaDb.cs` fa una query equivalente per conto suo): carica in un'unica query tutti
prt/asm noti con i relativi percorsi, invece di una query per codice — usata per validare i i codici prt/asm noti, invece di una query per codice — usata solo per validare i token
token generici trovati nei binari `.asm` (vedi `docs/formato-asm.md`). 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 ## 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.
+14
View File
@@ -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 |
+3 -3
View File
@@ -8,8 +8,8 @@ l'euristica testuale ("codice che compare almeno due volte") descritta in
struttura effettiva dei record. struttura effettiva dei record.
Implementazione: lettura in [`PrepStep/AsmRecordReader.cs`](../PrepStep/AsmRecordReader.cs) Implementazione: lettura in [`PrepStep/AsmRecordReader.cs`](../PrepStep/AsmRecordReader.cs)
(condivisa con la GUI, `Form1.EstraiCodiciReferenziati` in fase "Trova componenti (condivisa con la GUI, `Form1.EstraiCodiciReferenziati` in fase 4 "Trova componenti
referenziati"/"Verifica completezza" - con fallback su `AssemblyComponentScanner` per i file referenziati" - con fallback su `AssemblyComponentScanner` per i file
che non rispettano il formato validato), resa in formato BOM e ricorsione sui sottoassiemi che non rispettano il formato validato), resa in formato BOM e ricorsione sui sottoassiemi
in [`scripts/CreoBom.cs`](../scripts/CreoBom.cs) (via `#:project`). 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 disallineavano. Non è confermata da una specifica; regge sui campioni
disponibili al momento del reverse engineering. 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 della stringa corrisponde 1:1 a un byte, quindi indicizzare la stringa o l'array di byte
originale dà lo stesso offset). originale dà lo stesso offset).
+7 -6
View File
@@ -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 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 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). `PrepStep/AsmRecordReader.cs`, documentato in [`formato-asm-record.md`](formato-asm-record.md).
È il metodo **preferito** in GUI (fase "Trova componenti referenziati"/"Verifica È il metodo **preferito** in GUI (fase 4 "Trova componenti referenziati",
completezza", `Form1.EstraiCodiciReferenziati`) e nello script `CreoBom.cs`: `Form1.EstraiCodiciReferenziati`) e nello script `CreoBom.cs`:
`AssemblyComponentScanner` (l'euristica di questo documento) resta come **fallback**, usato `AssemblyComponentScanner` (l'euristica di questo documento) resta come **fallback**, usato
solo quando `AsmRecordReader` lancia `InvalidDataException` (file che non rispetta il solo quando `AsmRecordReader` lancia `InvalidDataException` (file che non rispetta il
formato validato dal reverse engineering — copertura limitata agli 8 campioni disponibili al 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 occorrenze: >= 2
``` ```
`scripts/ExtractCreoStrings.cs` ha una versione più raffinata della regex e un filtro La regex effettiva in `AssemblyComponentScanner.cs` (usata anche da
aggiuntivo (vedi sotto), non ancora portati in `AssemblyComponentScanner.cs`: `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 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), Stessa logica di estrazione candidati ed euristica del codice radice (sopra),
duplicata in due punti del codice per scopi diversi: 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. - [`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 (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`. - [`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 Nota: le query contro `files_ultima_versione` filtrano solo per
`(cartella_radice, codice, tipo_file)` - se lo stesso codice esiste in più `(cartella_radice, codice, tipo_file)` - se lo stesso codice esiste in più
+20
View File
@@ -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`) |
+51 -17
View File
@@ -1,12 +1,36 @@
# Guida utente # Guida utente
Questa guida descrive, in ordine, tutti i passi da fare in **Cevolani StepPrep** per 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 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 **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 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** cosa vede l'utente. La stessa sintesi è disponibile anche dentro l'app da **Aiuto → Guida**
(F1). (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 ## Prima di iniziare
- Serve accesso alla share di rete con i file CAD (di default `X:\`). - Serve accesso alla share di rete con i file CAD (di default `X:\`).
@@ -19,9 +43,9 @@ cosa vede l'utente. La stessa sintesi è disponibile anche dentro l'app da **Aiu
- Se il file segue la convenzione di nome del gestionale (`DBP_<codice>_<timestamp>`), - 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 l'app aggiunge da sola l'assieme generale come componente in più — non serve fare
nulla di diverso. nulla di diverso.
- Conviene definire la **cartella di lavoro** giusta (fase 1, o "File → Scegli cartella - Se non vuoi usare la cartella di installazione, scegli la **cartella di lavoro**
di lavoro...") prima di importare la distinta: è lì che finiscono CSV, `raw/` e, dopo ("File → Scegli cartella di lavoro...") prima di importare la distinta: è lì che
l'esportazione con Creo, `step/`. finiscono CSV, `raw/` e, dopo l'esportazione con Creo, `step/`.
- Se la cartella di lavoro è già stata usata per un'altra distinta, conviene **pulire - 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 `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 confondere i file CAD/STEP della copia precedente con quelli della nuova distinta (il
@@ -30,8 +54,8 @@ cosa vede l'utente. La stessa sintesi è disponibile anche dentro l'app da **Aiu
## 1. Database ## 1. Database
Alla prima apertura, se un database (`cad_index.db`) è già stato creato in precedenza, Se nella cartella di installazione c'è già un database (`cad_index.db`), l'app lo carica
l'app lo ricarica da sola: si vede il pallino verde accanto a "Database", il percorso del da sola all'avvio: si vede il pallino verde accanto a "Database", il percorso del
file e la data dell'ultimo aggiornamento. file e la data dell'ultimo aggiornamento.
- **Se il pallino è rosso** (nessun database trovato): premi **Aggiorna indice** per - **Se il pallino è rosso** (nessun database trovato): premi **Aggiorna indice** per
@@ -39,10 +63,10 @@ file e la data dell'ultimo aggiornamento.
minuto la prima volta, i giri successivi sono più veloci perché aggiornano solo quello minuto la prima volta, i giri successivi sono più veloci perché aggiornano solo quello
che è cambiato. che è cambiato.
- **Se hai già un database da un'altra postazione o cartella**: premi **Apri DB...** e - **Se hai già un database da un'altra postazione o cartella**: premi **Apri DB...** e
selezionalo a mano. selezionalo a mano. Vale solo finché non chiudi il programma: al prossimo avvio si riparte
- La **cartella di lavoro** (dove l'app scrive il CSV della distinta e la cartella dal database nella cartella di installazione. Per usarlo sempre, copialo in
`raw/`) è scelta automaticamente e ricordata tra un avvio e l'altro; per cambiarla usa `%LOCALAPPDATA%\Programs\PrepStep` come `cad_index.db`.
**File → Scegli cartella di lavoro...**. - La **cartella di lavoro** non si imposta qui: vedi [Dove finiscono i file](#dove-finiscono-i-file).
Premi **Avanti** quando il database è caricato. Premi **Avanti** quando il database è caricato.
@@ -52,12 +76,18 @@ Premi **Avanti** quando il database è caricato.
oppure premi **Scegli file distinta...** e selezionalo da Esplora risorse. Le righe importate 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 compaiono nella griglia (Componente/Descrizione) e viene scritto un CSV nella cartella
di lavoro. di lavoro.
- **2. Verifica la presenza dei file sul server**: compare solo dopo un import riuscito. 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 Premila per cercare ogni componente nel database — le righe della griglia si colorano
di verde (trovato) o rosso (non trovato nell'indice). di verde (trovato) o rosso (non trovato nell'indice).
**Avanti** si sblocca solo dopo aver premuto "Verifica la presenza dei file sul server", **Avanti** si sblocca solo dopo aver premuto **Verifica presenza**, non basta il solo
non basta il solo import: è il modo per accorgersi subito se il database è disallineato import: è il modo per accorgersi subito se il database è disallineato
rispetto alla share (es. indice non aggiornato) prima di andare avanti. rispetto alla share (es. indice non aggiornato) prima di andare avanti.
## 3. Crea cartella raw ## 3. Crea cartella raw
@@ -83,7 +113,7 @@ direttamente la cartella `raw/` con **Apri cartella raw**.
Questo passo è opzionale se la distinta contiene solo parti singole senza sotto-assiemi: Questo passo è opzionale se la distinta contiene solo parti singole senza sotto-assiemi:
in quel caso si può passare direttamente alla fase successiva. in quel caso si può passare direttamente alla fase successiva.
## 5. Trascina su Creo Distributed Batch ## 5. Verifica completezza e Creo Distributed Batch
L'ultima fase mostra l'elenco dei file `.asm` della distinta già presenti in `raw/`, 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: trascinabili. Da qui il passaggio a Creo è manuale, fuori dall'app:
@@ -117,9 +147,13 @@ prima di riprovare.
## Ripartire con una nuova distinta ## Ripartire con una nuova distinta
Quando devi processare un'altra distinta, usa **File → Nuovo** (Ctrl+N): azzera CSV, raw Quando devi processare un'altra distinta, usa **File → Nuovo** (Ctrl+N): svuota le griglie e
e verifica delle fasi 2-5 e riporta al passo 2, senza dover ricaricare il database o la verifica delle fasi 2-5 e riporta al passo 2, senza dover ricaricare il database né
la cartella di lavoro (restano quelli già impostati in fase 1). 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 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...**. (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).
+47
View File
@@ -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.
+46
View File
@@ -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.
+66
View File
@@ -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.
+64
View File
@@ -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...**.
+85
View File
@@ -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;
+59
View File
@@ -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;