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>
22 KiB
Architettura di PrepStep
Panoramica di tutti i file sorgente e di come collaborano. PrepStep è il nome
tecnico del progetto (cartella, namespace, csproj, eseguibile PrepStep.exe);
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
formato binario .asm vedi docs/formato-asm.md (euristica testuale) e
docs/formato-asm-record.md (parser esatto dei record); per la parallelizzazione della
copia in raw/ vedi docs/copia-raw-parallela.md.
Vista d'insieme
Un solo eseguibile WinForms (nessuna CLI, nessun processo esterno): indicizza i file CAD di
una share di rete in un DB SQLite locale, poi guida l'utente in un wizard a 5 fasi che, a
partire da una distinta base esportata dal gestionale, raccoglie in locale (raw/) tutti i
file CAD dei componenti e dei loro sotto-assiemi e verifica che l'indice li copra, come
pre-process prima dell'esportazione STEP con Creo Distributed Batch (manuale, fuori
dall'app: i file esportati finiscono in step/, dentro la cartella di lavoro).
Distinta (.xls/.xml) → CSV (componente;descrizione) → raw/ (CAD copiati dall'indice)
│
scansione .asm ─────┘ (sotto-assiemi/parti mancanti)
│
drag&drop .asm su Creo → Distributed Batch → step/ (esportazione manuale)
Il processo, dall'inizio alla fine
Le 5 fasi sono una pipeline: ognuna consuma quello che ha prodotto la precedente, e il
bottone "Avanti" si sblocca solo quando il risultato c'è (AvantiConsentito). Sotto, cosa
entra e cosa esce da ciascuna — il dettaglio dei metodi è nella tabella della sezione
successiva.
1. Indice della share → cad_index.db. Indexer.ScanAsync cammina tutte le cartelle di
primo livello sotto X:\, riconosce i nomi Pro-E <codice>.<tipo>.<versione> e fa un upsert
incrementale (confronto su percorso+dimensione+data), così i giri successivi costano solo il
delta. La cartella di lavoro, che da qui in poi ospita tutto il resto, non si sceglie qui
ma da "File → Scegli cartella di lavoro..." (default: cartella di installazione).
2. Distinta del gestionale → CSV. DistintaBaseImporter.ConvertiInCsv riduce l'export a
due colonne componente;descrizione. Se il nome del file segue la convenzione
DBP_<codice>_<timestamp> aggiunge l'assieme generale come riga in più: da quel momento è un
componente come tutti gli altri, nessun caso speciale a valle.
3. Componenti del CSV → raw/. Per ogni codice del CSV TrovaPercorsiCad cerca
nell'indice l'ultima versione (vista files_ultima_versione) e CopiaFileTrovati la copia in
raw/ in parallelo. Finisce qui il livello "distinta": in raw/ ci sono i componenti
elencati dal gestionale, non ancora quello che serve ad aprirli.
4. Chiusura ricorsiva dell'albero → raw/ completa. È il cuore del programma, ed è
l'unica fase che deve ragionare come Creo invece che come il gestionale.
RisolviAlberoComponenti tiene una coda di .asm da leggere, partendo da quelli già in
raw/, e per ognuno raccoglie tre liste diverse, che non vanno confuse (vedi
docs/formato-asm-record.md §7.3 e §7.5):
| Lista | Da dove | Ricorsione | Se non è nell'indice |
|---|---|---|---|
| Componenti della distinta | sezione members (EstraiCodiciReferenziati) |
sì | va nei "non trovati" |
| Componenti soppressi | stringhe di piazzamento (EstraiCodiciSoppressi) |
no — Creo non carica i figli di un soppresso | ignorato: non è un componente, segnalarlo sarebbe un falso allarme |
| Riferimenti esterni | tabella dei modelli citati (EstraiCodiciRiferimentiEsterni) |
sì — Creo carica il modello citato con tutto il suo albero | ignorato, stesso motivo |
Ogni codice trovato viene copiato in raw/ scegliendo quale copia con ScegliPerCartella
(§7.4: lo stesso codice esiste in più cartelle della share nel 13% dei casi, e vince quella
nella stessa cartella dell'assieme che lo referenzia); se è un .asm torna in coda e il giro
ricomincia, finché restano solo foglie. I rami raggiunti per riferimento esterno viaggiano con
un flag Esterno che li tiene fuori dall'elenco dei "non trovati": quell'elenco continua a
parlare solo della distinta, che è quello che l'utente si aspetta di leggerci.
Il motivo per cui questa fase è più complicata di come sembra: la lista dei componenti e la
lista dei file necessari al caricamento non coincidono, e la seconda è più lunga. Quando si
sbaglia, Creo Distributed Batch non dice quale file manca — dice solo [-4] Dati di input non completi.
5. Verifica e consegna a Creo. AggiornaAnteprimaAsm mostra i .asm della distinta già
in raw/ come icone trascinabili. L'esportazione STEP vera è manuale e fuori dall'app: si
trascinano i .asm su Creo Distributed Batch, che scrive in step/.
Entry point
- Program.cs —
Main()standard WinForms:Application.Run(new Form1()). Nessuna logica.
Finestra principale (wizard)
-
Form1.cs — tutta la UI è costruita in codice (
BuildUi/BuildFase*), non con il designer visuale. È un wizard:_panelFasi[]contiene unPanelper fase, uno solo visibile alla volta (VaiAllaFase); si avanza con "Avanti" solo seAvantiConsentitoritorna vero per la fase corrente.Form1.Designer.cscontiene solo lo scheletro minimo generato dal template (InitializeComponent/Dispose), non è usato per costruire i controlli.Le 5 fasi e i metodi principali dietro ciascuna:
# Fase Bottone → metodo Cosa fa 1 Database "Aggiorna indice" → Indexer.ScanAsyncCrea/aggiorna cad_index.dbscansionando 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" →VerificaCodiciNelDbXls/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 3 Crea cartella raw "Crea cartella raw" → CreaCartellaRawTrovaPercorsiCad+CopiaFileTrovati(parallela) →raw/dentro la cartella di lavoro4 Trova componenti referenziati "Trova componenti referenziati" → TrovaPrtReferenziati→RisolviAlberoComponentiLegge i .asminraw/, risolve ricorsivamente sotto-assiemi/parti nell'indice e li copia anch'essi inraw/, insieme ai componenti soppressi e ai modelli citati per riferimento esterno che servono a Creo per aprire l'assieme5 Verifica completezza (ingresso in fase) → AggiornaAnteprimaAsmMostra le icone trascinabili dei .asmdella distinta già inraw/, da trascinare su Creo per l'esportazione STEP (Distributed Batch)Stato condiviso tra le fasi (campi privati di
Form1):_dbPath,_cartellaLavoro,_percorsoInputCorrente,_righeCorrenti(righe del CSV),_ultimaVerifica(cache percorsi CAD),_ultimaCartellaRaw.Menu (
MenuStrip, in cima alla finestra): "File → Nuovo" (Ctrl+N) azzera lo stato delle fasi 2-5 (NuovaEstrazione) e va direttamente alla fase 2, senza passare dalla fase 1: database e cartella di lavoro restano quelli già caricati, non serve rifare lo scan; "File → Scegli cartella di lavoro..." →ScegliCartellaLavoro, cambia in qualunque momento la destinazione di CSV/raw//step/; "File → Pulisci raw e step..." svuota le cartelleraw//step/nella cartella di lavoro (PulisciRawEStep, non tocca l'indice); "File → Elimina CSV delle distinte..." elimina i*.csvdirettamente nella cartella di lavoro (EliminaCsv); "Aiuto → Guida" (F1) apreGuidaForm, sintesi interattiva delle 5 fasi per chi non ha letto questi docs.Altre responsabilità in Form1.cs:
TrovaPercorsiCad/CaricaCodiciNoti— query sul DB (files_ultima_versione): la prima risolve i componenti del CSV (fase 3), la seconda carica tutti i codici prt/asm noti in un colpo solo, usata in fase 4 solo per validare i token generici diAssemblyComponentScanner.CaricaCandidati— in fase 4, tutte le copie di ogni codice dalla tabellafiles(non dalla vista), da cuiScegliPerCartellasceglie quella da copiare inraw/.CopiaFileTrovati— copia (in parallelo, grado 6) i file trovati inraw/, saltando silenziosamente quelli irraggiungibili (share offline/permessi).PulisciRawEStep— svuotaraw/estep/nella cartella di lavoro (manutenzione, richiede conferma).EliminaCsv— elimina i*.csvgenerati dall'import (uno per distinta) nella cartella di lavoro, solo al primo livello; gli.xlsdel gestionale restano (richiede conferma, con l'elenco dei file).InizializzaDatabase/ScegliDatabase— localizzazione del DB (semprecad_index.dbaccanto all'eseguibile; "Apri DB..." ne carica un altro solo per la sessione).InizializzaCartellaLavoro/ScegliCartellaLavoro— stesso pattern per la cartella di lavoro (ultima_cartella_lavoro.txt), fallbackAppContext.BaseDirectoryse non c'è ancora nulla di salvato: nessuna dipendenza da un marker di repo, funziona identica sull'exe pubblicato standalone spostato su un'altra macchina.
Indicizzazione
- Indexer.cs —
ScanAsynccammina tutte le cartelle di primo livello sotto la radice (scoperte dinamicamente, non un elenco fisso) con un walk manuale a stack (Walk, più veloce diEnumerateFiles(..., AllDirectories)su share con centinaia di migliaia di file), esclude le cartelletemp_*, estrae da ogni nome file il pattern Pro-E<codice>.<tipo>.<versione>(ParseFilename, regex), e fa un upsert incrementale riga per riga confrontandopercorso_completo+ dimensione + data modifica. Progresso riportato viaIProgress<ScanProgress>(marshalato sul thread UI). Contiene anche lo schema SQL: tabellafilese vistafiles_ultima_versione(ultima versione percodice+tipo_fileviaROW_NUMBER() OVER (...), non self-join+GROUP BYper evitare tempi quasi quadratici su ~200k righe).
Lettura dei componenti referenziati in un .asm
Form1.EstraiCodiciReferenziati (usata da RisolviAlberoComponenti, fase 4) prova prima
AsmRecordReader.EstraiCodiciReferenziati e ripiega su
AssemblyComponentScanner.EstraiCodiciReferenziati solo se il primo lancia
InvalidDataException (file che non rispetta il formato validato dal reverse engineering).
- AsmRecordReader.cs — legge esattamente la sezione
membersdel binario.asm(id/tipo/ nome dei componenti diretti, con risoluzione del nome istanza di family table dal parametroConfigName): nessuna euristica, struttura a record. LanciaInvalidDataExceptionse il file non ha una sezionememberso se il conteggio dichiarato non torna. Espone anche le due liste "non di distinta":EstraiCodiciSoppressi(che passa damembersper sapere quali codici sono già noti, quindi può lanciare la stessa eccezione —Form1.CodiciSoppressila intercetta e restituisce vuoto) eEstraiCodiciRiferimentiEsterni(scansione a sé, non lancia). Dettagli indocs/formato-asm-record.md. Condivisa conscripts/CreoBom.csvia#:project. - AssemblyComponentScanner.cs — estrae euristicamente i codici componente referenziati
contando le occorrenze di stringhe candidate nel testo binario. Due strategie: codici
numerici Pro-E riconosciuti da soli via regex (come nei nomi file), e nomi alfabetici di
parti commerciali tokenizzati genericamente e validati contro l'elenco di codici noti del DB
(altrimenti il rumore binario sarebbe ingestibile). Dettagli sull'euristica e i falsi
positivi noti in
docs/formato-asm.md. Resta il fallback per i file che il parser esatto non copre, e la fonte di verità perExtractCreoStrings.cs/BomViaDb.cs.
Quella lista è la distinta, e non coincide con i file che servono a Creo per aprire
l'assieme: ne restano fuori due categorie, che il loro file serve comunque, altrimenti il
caricamento fallisce (docs/formato-asm-record.md §7.3). AsmRecordReader le restituisce a
parte, e la fase 4 le copia in raw/ senza contarle fra i "non trovati":
EstraiCodiciSoppressi— i componenti soppressi: spariti damembers, ancora citati dalla feature che li posiziona. Non si ricorre dentro (Creo non carica i figli di un componente soppresso).EstraiCodiciRiferimentiEsterni— i modelli legati da riferimento esterno (assembly feature, datum copiati), elencati in una tabella a parte del binario (§7.5). Qui si ricorre dentro, perché Creo carica il modello citato con tutto il suo albero.
Quale copia di un componente si copia in raw/
Lo stesso codice esiste spesso in più cartelle della share, con contenuto diverso e pari
versione (13% dei codici, vedi docs/formato-asm-record.md §7.4). Il .asm non registra
quale copia usa, quindi RisolviAlberoComponenti replica la regola di Creo:
Form1.ScegliPerCartella preferisce la copia nella stessa cartella dell'assieme che la
referenzia (Form1.CartellaDiOrigine ricostruisce quella cartella per gli assiemi di
partenza), e solo in mancanza ripiega su versione più alta e data più recente. Sbagliare copia
non dà un errore comprensibile: Creo non apre l'assieme e dice solo "dati di input non
completi".
Import distinta base
- DistintaBaseImporter.cs — converte l'export del gestionale (SpreadsheetML XML con
estensione
.xls/.xml, oppure.xls/.xlsxbinario vero, distinti dai primi byte del file) nelle sole colonne Componente/Descrizione, foglio"Distinta Base". Se il nome del file segue la convenzioneDBP_<codice>_<timestamp>, aggiunge l'assieme generale come riga in più (il foglio elenca solo i componenti sotto l'assieme). Scrive il CSV nella cartella passata daForm1(la cartella di lavoro, impostabile da "File → Scegli cartella di lavoro..."), non necessariamente accanto al file di input.
Finestre di dialogo (modali, mostrate a fine step)
- ProgressForm.cs — durante l'aggiornamento indice: barra di progresso, conteggi nuovi/aggiornati/invariati/rimossi, tempo trascorso e stima del tempo rimanente (dal ritmo file/secondo rispetto al totale atteso, preso dal conteggio della scansione precedente salvato nel DB).
- ProgressoComponentiForm.cs — dialog minimale (etichetta + barra) durante
RisolviAlberoComponentiin fase 4.
Stile UI condiviso
- UiStile.cs —
Primario/Secondario: stile piatto per i bottoni (bordo sottile per i secondari, blu pieno per l'azione principale di ogni fase), entrambi conAutoSize+Padding+MinimumSizeinvece di unaSizefissa in pixel indovinata a mano (altrimenti il testo va a capo e si taglia in verticale se non ci sta nella larghezza scelta).SecondarioPienoè la variante senza bordo (sfondo grigio chiaro pieno) per i bottoni secondari sopra le card bianche diCard/FloatBar, dove il bordo sottile diSecondarioè troppo poco visibile su sfondo bianco.Arrotondaapplica angoli arrotondati a unControlqualsiasi (bottone o card) viaRegioncalcolata suSizeChanged, non una volta sola: conAutoSizela dimensione finale si conosce solo a layout avvenuto — va chiamata dopo aver fissato le dimensioni finali del controllo (es. dopoLarghezzaUguale), altrimenti laRegionvecchia resta visibile come bordo residuo finché non arriva un repaint completo.Cardcostruisce una card di stato (sfondo/bordo distinti, angoli arrotondati), usata in fase 1 per il riepilogo Database.FloatBarcostruisce la barra di navigazione "flottante" in fondo alle finestre (card bianca staccata su tutti e 4 i lati, non un footer a tutta larghezza): usata per Avanti/Indietro nel wizard e per Chiudi/Apri cartella raw inPrtMancantiForm.LarghezzaUgualeforza la stessa larghezza e altezza tra due bottoni della stessa barra (es. Avanti/Indietro, che altrimenti avrebbero dimensioni diverse: testi di lunghezza diversa, e il font in grassetto diPrimariorichiede più altezza di quello normale diSecondario/SecondarioPieno). - app.ico — icona dell'app, referenziata da
<ApplicationIcon>nel csproj (icona dell'exe in Esplora risorse/taskbar) e riletta esplicitamente inForm1.BuildUi()conIcon.ExtractAssociatedIcon(Application.ExecutablePath)per la titlebar, senza copiare il file.iconell'output. Sfondo trasparente (nessuna card bianca), 7 risoluzioni da 16 a 256px. - PrtMancantiForm.cs — esito della risoluzione ricorsiva dell'albero componenti: PRT e
sotto-assiemi trovati e copiati in
raw/, più i codici referenziati che non corrispondono a nessun file indicizzato (possono essere disegni, componenti davvero mancanti, o falsi positivi della scansione.asm). - GuidaForm.cs — guida interattiva ("Aiuto → Guida"/F1): elenco delle sezioni a sinistra
(Panoramica, Dove finiscono i file, Prima di iniziare, le 5 fasi, Ripartire da capo), testo
a destra; stessa guida di
docs/guida-utente.md, il dettaglio tecnico resta solo in questi docs.
Persistenza
- DB (
cad_index.db, SQLite) — vedi tabellafiles/vistafiles_ultima_versionesopra. Semprecad_index.dbaccanto all'eseguibile (AppContext.BaseDirectory, cioè la cartella di installazione):InizializzaDatabasecarica solo quello, e "Aggiorna indice" lo crea lì se manca. "Apri DB..." carica un altro file solo per la sessione corrente, senza ricordarlo. Radice disco di scansione di defaultX:\(Form1.RadiceDiscoDefault, hardcoded). - Cartella di lavoro — ricordata tra un avvio e l'altro in
%AppData%/PrepStep/ultima_cartella_lavoro.txt; se non c'è una preferenza salvata (o punta a una cartella cancellata) si propone la cartella di installazione (AppContext.BaseDirectory); cambiabile in qualunque momento da "File → Scegli cartella di lavoro...". Tutti i file generati a valle ci finiscono dentro: il CSV della distinta,raw/(CAD dei componenti e sotto-componenti);step/non è scritta dall'app ma è dove l'utente esporta gli STEP a mano con Creo Distributed Batch, a partire dai file trascinati daraw/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 nel README.md.
Script standalone (scripts/)
C# file-based apps (.NET 10, dotnet run scripts/Nome.cs -- arg1 arg2, nessun .csproj,
dipendenze extra con #:package Nome@versione in testa al file):
- ExtractCreoStrings.cs — BOM di un
.asmrisolvendo ricorsivamente i sotto-assiemi controcad_index.dbconAssemblyComponentScanner(via#:project, solo codici numerici Pro-E). - BomViaDb.cs — come sopra ma con la tokenizzazione generica validata contro il DB, per intercettare anche i nomi alfabetici delle parti commerciali.
- CreoBom.cs — usa
AsmRecordReader(stesso lettore esatto della GUI, fase 4, via#:project) invece dell'euristica testuale. Risolve i sottoassiemi cercandoli in cartelle locali (-p), non controcad_index.dbcomeExtractCreoStrings.cs/BomViaDb.cs. - Analyze.cs — dump del contesto testo/hex attorno a un pattern in un file binario, per
investigare a mano nuovi falsi positivi prima di toccare la regex di
AssemblyComponentScanner. - Query.cs — query SQL ad-hoc contro
cad_index.dbda riga di comando.
⚠️ Nota importante di sicurezza: se
dotnet runviene lanciato dentroPrepStep/(che contiene un.csprojdi tipoWinExe), esegue l'app GUI vera invece dello script standalone e resta bloccato in attesa che una finestra si chiuda. Lanciare gli script da una cartella senza.csproj(es. la root del repo oscripts/stessa).
Test
-
PrepStep.Tests/ (xUnit) — riferimento al progetto principale via
ProjectReferenceInternalsVisibleTo(inPrepStep.csproj), così i test chiamano direttamenteIndexer.ParseFilenameeAssemblyComponentScanner.EstraiCodiciReferenziatisenza esporli come API pubblica. File:IndexerParseFilenameTests.cs,IndexerScanTests.cs,AssemblyComponentScannerTests.cs,AsmRecordReaderTests.cs,DistintaBaseImporterTests.cs,CaseInsensitiveLookupIntegrationTests.cs,PipelineEndToEndTests.cs.
AsmRecordReaderTests.cscostruisce i.asmbyte per byte con dei piccoli helper (Membro,ConfigName,ObjName,Riferimenti): ogni caso strano trovato sulla share diventa un test minimale invece di un file binario da versionare.