La barra Avanti/Indietro era un footer bianco a tutta larghezza con una linea di separazione da 1px. Ora è una card bianca staccata su tutti e 4 i lati dalla finestra (UiStile.FloatBar, angoli arrotondati, bordo sottile), riusata anche in PrtMancantiForm per Chiudi/Apri cartella raw. Avanti e Indietro avevano dimensioni diverse (testi di lunghezza diversa, più il grassetto di Primario che richiede più altezza del testo normale) ed erano visivamente disomogenei (bordo sottile quasi invisibile su sfondo bianco per Indietro contro il blu pieno di Avanti). Aggiunti UiStile.SecondarioPieno (variante piena, senza bordo, stessa costruzione di Primario ma grigio chiaro) e UiStile.LarghezzaUguale (stessa larghezza e altezza tra due bottoni, chiamata PRIMA di arrotondare gli angoli: arrotondare e poi ridimensionare lasciava un bordo residuo della Region vecchia). Doc aggiornati nello stesso commit: docs/architettura.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
20 KiB
Architettura di PrepStep (Cevolani StepPrep)
Panoramica di tutti i file sorgente e di come collaborano. PrepStep è il nome
tecnico del progetto (cartella, namespace, csproj, eseguibile PrepStep.exe);
Cevolani StepPrep è il nome mostrato all'utente finale (titolo finestra, icona,
versione — vedi <Version>/<Product> in PrepStep.csproj). Per il "come si usa" vedi il README.md in root; per il
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. Qui si sceglie anche la cartella di lavoro, che da qui in poi ospita tutto il resto.
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); "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 seconda carica tutti i codici prt/asm noti in un colpo solo (usata sia per risolvere i componenti sia daAssemblyComponentScannerper validare i token generici).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).InizializzaDatabase/ScegliDatabase— localizzazione del DB (ultimo usato, salvato in%AppData%/PrepStep/ultimo_db.txt, ocad_index.dbaccanto all'eseguibile).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): lista delle 5 fasi a sinistra, testo a destra; sintesi, il dettaglio resta solo in questi docs.
Persistenza
- DB (
cad_index.db, SQLite) — vedi tabellafiles/vistafiles_ultima_versionesopra. Percorso ricordato tra un avvio e l'altro in%AppData%/PrepStep/ultimo_db.txt(SalvaUltimoDbUsato/LeggiUltimoDbUsato), scritto ogni volta che si carica un DB (all'avvio se già noto, oppure da "Apri DB..." o al termine di "Aggiorna indice"); se il file non c'è o punta a un percorso cancellato,InizializzaDatabaseripiega sucad_index.dbaccanto all'eseguibile o nella working directory. Radice disco di scansione di defaultX:\(Form1.RadiceDiscoDefault, hardcoded). - Cartella di lavoro — stesso meccanismo del DB sopra, salvata in
%AppData%/PrepStep/ultima_cartella_lavoro.txt; se non c'è una preferenza salvata (o punta a una cartella cancellata) si propone la cartella del DB caricato, o in ultima istanzaAppContext.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.
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.db(stessa logica diAssiemeCompletoin Form1, solo codici numerici Pro-E). - BomViaDb.cs — come sopra ma con la tokenizzazione generica validata contro il DB, per intercettare anche i nomi alfabetici delle parti commerciali.
- CreoBom.cs — usa
AsmRecordReader(stesso lettore esatto della GUI, fase 4/5, via#: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.