Files
prep-step/docs/architettura.md
T
davideandClaude Sonnet 5 3e9c78074c Sposta 'Pulisci raw e step' e 'Cambia cartella' nel menu File, rinomina 'Nuovo'
Fase 1 (Database) aveva 4 bottoni tutti sullo stesso piano visivo: due
configurano il DB, gli altri due (cambio cartella di lavoro, pulizia
raw/step) sono azioni occasionali di manutenzione, non passi del wizard.
Spostate in File → "Scegli cartella di lavoro..." e "Pulisci raw e
step...", raggiungibili in qualunque momento invece che solo dalla fase 1.

"File → Nuova estrazione" diventa "Nuovo"; corretto anche un bug per cui
InizializzaCartellaLavoro non richiamava AggiornaStatoAvanti, lasciando
"Avanti" disabilitato in fase 1 finché non si toccava un altro controllo
anche a DB e cartella già caricati all'avvio.

Doc aggiornati nello stesso commit: CLAUDE.md, docs/architettura.md,
GuidaForm.cs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 14:44:27 +02:00

19 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 un Panel per fase, uno solo visibile alla volta (VaiAllaFase); si avanza con "Avanti" solo se AvantiConsentito ritorna vero per la fase corrente. Form1.Designer.cs contiene 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.ScanAsync Crea/aggiorna cad_index.db scansionando la share; mostra anche la cartella di lavoro corrente (cambiabile da "File → Scegli cartella di lavoro...")
    2 Importa Distinta Base selezione file → ImportaDistinta → DistintaBaseImporter.ConvertiInCsv Xls/Xml gestionale → CSV componente;descrizione, scritto nella 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
    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)

    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 cartelle raw//step/ nella cartella di lavoro (PulisciRawEStep, non tocca l'indice); "Aiuto → Guida" (F1) apre GuidaForm, sintesi interattiva delle 5 fasi per chi non ha letto questi docs.

    Altre responsabilità in Form1.cs:

    • TrovaPercorsiCad / CaricaCodiciNoti — query sul DB (files_ultima_versione), la seconda carica tutti i codici prt/asm noti in un colpo solo (usata sia per risolvere i componenti sia da AssemblyComponentScanner per validare i token generici).
    • CopiaFileTrovati — copia (in parallelo, grado 6) i file trovati in raw/, saltando silenziosamente quelli irraggiungibili (share offline/permessi).
    • PulisciRawEStep — svuota raw/ e step/ nella cartella di lavoro (manutenzione, richiede conferma).
    • InizializzaDatabase / ScegliDatabase — localizzazione del DB (ultimo usato, salvato in %AppData%/PrepStep/ultimo_db.txt, o cad_index.db accanto all'eseguibile).
    • InizializzaCartellaLavoro / ScegliCartellaLavoro — stesso pattern per la cartella di lavoro (ultima_cartella_lavoro.txt), fallback AppContext.BaseDirectory se non c'è ancora nulla di salvato: nessuna dipendenza da un marker di repo, funziona identica sull'exe pubblicato standalone spostato su un'altra macchina.

Indicizzazione

  • Indexer.cs — ScanAsync cammina tutte le cartelle di primo livello sotto la radice (scoperte dinamicamente, non un elenco fisso) con un walk manuale a stack (Walk, più veloce di EnumerateFiles(..., AllDirectories) su share con centinaia di migliaia di file), esclude le cartelle temp_*, estrae da ogni nome file il pattern Pro-E <codice>.<tipo>.<versione> (ParseFilename, regex), e fa un upsert incrementale riga per riga confrontando percorso_completo + dimensione + data modifica. Progresso riportato via IProgress<ScanProgress> (marshalato sul thread UI). Contiene anche lo schema SQL: tabella files e vista files_ultima_versione (ultima versione per codice+tipo_file via ROW_NUMBER() OVER (...), non self-join+GROUP BY per 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 members del binario .asm (id/tipo/ nome dei componenti diretti, con risoluzione del nome istanza di family table dal parametro ConfigName): nessuna euristica, struttura a record. Lancia InvalidDataException se il file non ha una sezione members o se il conteggio dichiarato non torna. Espone anche le due liste "non di distinta": EstraiCodiciSoppressi (che passa da members per sapere quali codici sono già noti, quindi può lanciare la stessa eccezione — Form1.CodiciSoppressi la intercetta e restituisce vuoto) e EstraiCodiciRiferimentiEsterni (scansione a sé, non lancia). Dettagli in docs/formato-asm-record.md. Condivisa con scripts/CreoBom.cs via #: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à per ExtractCreoStrings.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 da members, 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/.xlsx binario vero, distinti dai primi byte del file) nelle sole colonne Componente/Descrizione, foglio "Distinta Base". Se il nome del file segue la convenzione DBP_<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 da Form1 (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 RisolviAlberoComponenti in 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 con AutoSize+Padding+MinimumSize invece di una Size fissa in pixel indovinata a mano (altrimenti il testo va a capo e si taglia in verticale se non ci sta nella larghezza scelta). Arrotonda applica angoli arrotondati via Region calcolata su SizeChanged (non una volta sola: con AutoSize la dimensione finale del bottone si conosce solo a layout avvenuto), usata sui bottoni "Avanti"/"Indietro" del wizard.
  • app.ico — icona dell'app, referenziata da <ApplicationIcon> nel csproj (icona dell'exe in Esplora risorse/taskbar) e riletta esplicitamente in Form1.BuildUi() con Icon.ExtractAssociatedIcon(Application.ExecutablePath) per la titlebar, senza copiare il file .ico nell'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 tabella files/vista files_ultima_versione sopra. Percorso ricordato tra un avvio e l'altro in %AppData%/PrepStep/ultimo_db.txt (SalvaUltimoDbUsato/LeggiUltimoDbUsato), scritto ogni volta che si carica un DB (all'avvio se già noto, oppure da "Apri DB..." o al termine di "Aggiorna indice"); se il file non c'è o punta a un percorso cancellato, InizializzaDatabase ripiega su cad_index.db accanto all'eseguibile o nella working directory. Radice disco di scansione di default X:\ (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 istanza AppContext.BaseDirectory; cambiabile in qualunque momento da "File → Scegli cartella di lavoro...". Tutti i file generati a valle ci finiscono dentro: il CSV della distinta, raw/ (CAD dei componenti e sotto-componenti); step/ non è scritta dall'app ma è dove l'utente esporta gli STEP a mano con Creo Distributed Batch, a partire dai file trascinati da raw/ in fase 5.

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 .asm risolvendo ricorsivamente i sotto-assiemi contro cad_index.db (stessa logica di AssiemeCompleto in 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 contro cad_index.db come ExtractCreoStrings.cs/BomViaDb.cs.
  • Analyze.cs — dump del contesto testo/hex attorno a un pattern in un file binario, per investigare a mano nuovi falsi positivi prima di toccare la regex di AssemblyComponentScanner.
  • Query.cs — query SQL ad-hoc contro cad_index.db da riga di comando.

⚠️ 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 standalone e resta bloccato in attesa che una finestra si chiuda. Lanciare gli script da una cartella senza .csproj (es. la root del repo o scripts/ stessa).

Test

  • PrepStep.Tests/ (xUnit) — riferimento al progetto principale via ProjectReference

    • InternalsVisibleTo (in PrepStep.csproj), così i test chiamano direttamente Indexer.ParseFilename e AssemblyComponentScanner.EstraiCodiciReferenziati senza esporli come API pubblica. File: IndexerParseFilenameTests.cs, IndexerScanTests.cs, AssemblyComponentScannerTests.cs, AsmRecordReaderTests.cs, DistintaBaseImporterTests.cs, CaseInsensitiveLookupIntegrationTests.cs, PipelineEndToEndTests.cs.

    AsmRecordReaderTests.cs costruisce i .asm byte 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.