Files
prep-step/docs/architettura.md
T
davide db4c444473 Documenta il processo delle 5 fasi in architettura.md
Il doc elencava i file sorgente e una tabella delle fasi, ma non il
processo: cosa entra e cosa esce da ogni fase, e perche' la 4 e' piu'
complicata di "leggi i componenti e copiali". Aggiunta una sezione che lo
descrive come pipeline, con in chiaro le tre liste che la fase 4 raccoglie
da ogni .asm - distinta, soppressi, riferimenti esterni - e per ognuna se
si ricorre dentro e se un codice mancante va segnalato.

Allineati alla stessa modifica i punti rimasti indietro: la riga della
fase 4 nella tabella, la sezione sulla lettura dei .asm (che citava solo i
soppressi), e l'elenco dei file di test, fermo a quattro su sette.
2026-09-22 14:12:27 +02:00

18 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; "Verifica completezza assiemi" ricontrolla ricorsivamente sull'indice (non su raw/) che ogni sotto-componente esista. 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; "Cambia cartella..." → ScegliCartellaLavoro Crea/aggiorna cad_index.db scansionando la share; sceglie/mostra la 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 "Verifica completezza assiemi" → VerificaCompletezzaAssiemi Completezza ricorsiva sui percorsi in indice. All'ingresso nella 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.

    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, e da AssiemeCompleto dentro VerificaCompletezzaAssiemi, fase 5) 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.

La fase 5 (VerificaCompletezzaAssiemi) usa invece solo i membri: è una domanda sulla distinta, e contare le altre due liste produrrebbe falsi "componenti mancanti".

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 scelta in fase 1), 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).
  • ReportCompletezzaForm.cs — per ogni assieme della distinta, se tutti i suoi componenti (ricorsivamente fino ai PRT) sono nell'indice (✓/✗/? per "non trovato").

Persistenza

  • DB (cad_index.db, SQLite) — vedi tabella files/vista files_ultima_versione sopra. Percorso: cercato accanto all'eseguibile o nella working directory all'avvio; altrimenti selezionabile con "Apri DB...". Radice disco di scansione di default X:\ (Form1.RadiceDiscoDefault, hardcoded).
  • Cartella di lavoro — salvata in %AppData%/PrepStep/ultima_cartella_lavoro.txt (stesso meccanismo del DB sopra), proposta di default AppContext.BaseDirectory la prima volta; selezionabile/cambiabile con "Cambia cartella..." in fase 1. 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.