12 Commits
Author SHA1 Message Date
davideandClaude Opus 5.5 206db85e12 Porta la versione a 0.9.0 e aggiorna il CHANGELOG
Voce 0.9.0 in CHANGELOG.md con le modifiche dal tag 0.1.0 (menu File,
card di stato in fase 1, verifica presenza in fase 2, floatbar, guida
utente e finestra Guida ridisegnata). Versione aggiornata anche in
CLAUDE.md; .vscode/ aggiunta al .gitignore.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 08:05:21 +02:00
davideandClaude Sonnet 5 d81fede4cc Ridisegna la finestra Guida con gerarchia visiva e callout colorati
La guida in-app era testo semplice senza gerarchia (stesso peso per
titoli, corpo, passi, avvisi) e una sidebar con la selezione blu di
sistema, fuori stile rispetto al resto dell'app.

Sezioni ristrutturate come dati (Titolo/Intro/Passi/Nota) invece di un
unico blob di testo libero, e renderizzate con gerarchia esplicita nel
RichTextBox: titolo blu primario grassetto, passi numerati con il
numero in blu, callout "Nota"/"Attenzione" con sfondo a tinta piatta
(blu tenue per le info, ambra tenue per gli avvisi che fanno perdere
tempo se ignorati, come l'errore Creo "[-4]"). Sidebar con selezione
disegnata a mano nel blu dell'app invece del blu di sistema. Bottone
Chiudi spostato sulla stessa floatbar già usata nelle altre finestre.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:29:42 +02:00
davideandClaude Sonnet 5 948937ba75 Aggiungi la guida utente e allinea i testi rimasti indietro sulla fase 5
Nuova docs/guida-utente.md: tutti i passi in ordine che deve fare
l'utente, dalla distinta esportata da Fata (gestionale) fino
all'esportazione STEP con Creo Distributed Batch (passi generali presi
da documentazione/community PTC, con avviso che i nomi dei menu
variano per versione/configurazione aziendale). Include anche i
consigli pratici di impostare la cartella di lavoro e pulire raw/step
prima di ripartire con una distinta diversa.

GuidaForm.cs e README.md citavano ancora una "Verifica completezza
assiemi" e un bottone "Cambia cartella..." rimossi da tempo dal
codice, mai allineati nei testi: corretti nello stesso commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:22:39 +02:00
davideandClaude Sonnet 5 ff4e7a71e0 Trasforma la barra di navigazione in una floatbar e uniforma i bottoni Avanti/Indietro
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>
2026-09-22 16:13:34 +02:00
davideandClaude Sonnet 5 be4989fc8d Sistema il testo delle fasi 3-4 e completa il dialogo "Componenti referenziati trovati"
Fase 3: spostata la spiegazione da paragrafo sopra il bottone a etichetta
affiancata ("Copia i file dal server nella cartella 'raw'."), stesso
pattern già usato in fase 2. Fase 4: tolta la frase finale sul passo
opzionale, ridondante con la frase precedente.

PrtMancantiForm (l'esito di "Trova componenti referenziati"): mancava un
modo esplicito per chiudere la finestra (solo la X) e usava l'icona
default di WinForms invece di quella dell'app. Aggiunto "Chiudi" accanto
ad "Apri cartella raw" (Esc la chiude) e impostata l'icona dall'eseguibile,
stesso pattern già usato in Form1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:00:25 +02:00
davideandClaude Sonnet 5 e94331b69f Ridisegna la fase 1 in card di stato e aggiunge un passo esplicito di verifica in fase 2
Fase 1: le info su DB e cartella di lavoro erano testo grigio uniforme
disallineato con bottoni squadrati senza gerarchia. Ora vivono in una
card di stato ("Database") con un indicatore verde/rosso per lo stato
di caricamento; la card "Cartella di lavoro" è stata rimossa (resta
gestibile da File → Scegli cartella di lavoro..., già presente nel
menu). Le icone sui bottoni (prima Segoe MDL2 Assets, poi disegnate a
mano via GDI+) sono state provate e infine tolte su richiesta: restano
solo testo, più semplici da mantenere per due bottoni.

Fase 2: "Verifica presenza" ora compare solo dopo un import riuscito
(dialogo o drag&drop) invece di essere sempre visibile, ed è lei —non
il solo import— a sbloccare "Avanti" (mancava la constatazione che
l'utente può ancora avanzare senza aver verificato che i file esistano
sul server). Aggiunte etichette numerate "1. Carica la distinta" /
"2. Verifica la presenza dei file sul server" accanto ai bottoni, e
rimosso il messaggio verde ridondante dopo l'import.

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

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 15:50:29 +02:00
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
davideandClaude Sonnet 5 e29a1bf90a Aggiungi CHANGELOG.md con la voce 0.1.0
Formato Keep a Changelog; elenca le funzionalità attuali del programma come
prima voce di storico versioni.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 14:25:30 +02:00
davideandClaude Sonnet 5 28aa16837b Aggiungi menu Nuovo estrazione/Guida, rimuovi verifica completezza assiemi
"File -> Nuova estrazione" (Ctrl+N) azzera distinta/raw/verifica per ripartire
senza ricaricare DB e cartella di lavoro; "Aiuto -> Guida" (F1) apre una sintesi
interattiva delle 5 fasi. Rimossa "Verifica completezza assiemi": bottone,
ReportCompletezzaForm e VerificaCompletezzaAssiemi non più necessari.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 14:24:57 +02:00
davide d9f16f2844 Regola in CLAUDE.md: i doc si aggiornano nello stesso commit del codice
docs/ descrive il programma com'e' adesso, non com'e' stato: e' la prima
cosa che si legge per capirlo, quindi un doc che racconta un
comportamento che non esiste piu' fa prendere decisioni sbagliate - e'
peggio di un doc mancante.

Aggiunta la regola, la tabella di quale doc ospita cosa, e la richiesta
di annotare anche il sintomo quando il cambiamento nasce da un fallimento
di Creo: i suoi messaggi non nominano ne' il file ne' la causa, e senza
quel ponte la volta dopo si riparte da zero.
2026-09-22 14:13:07 +02:00
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
davideandClaude Opus 5 13f6de6402 Copia in raw anche i modelli citati per riferimento esterno
Terzo caso di "[-4] Dati di input non completi" in Creo Distributed
Batch, dopo i soppressi e le copie ambigue del commit precedente: falliva
9255728001, e solo lui fra i sei assiemi del batch.

Il file in raw/ era identico bit per bit a quello sulla share, quindi non
era il modello: mancava il contorno. Il suo sottoassieme _200 e' legato
ad altri due (9255708017, 9255708018) da assembly feature e datum
copiati, e monta una vite V6-12--_-_U5933_C il cui generico uni5933.prt
non era in raw/. Nessuno dei sei file compariva in "members" ne' fra i
piazzamenti: i due lettori esistenti non potevano vederli.

Quei modelli stanno in una tabella a parte del .asm - array \xf2\xf7\x05
… \xf3 con elementi \xe3\xe2<tipo><nome>\0 - che ora si legge con
EstraiCodiciRiferimentiEsterni, riusando la normalizzazione di #AF<n> e
istanza<generico> gia' fatta per i membri. A differenza dei soppressi si
ricorre dentro: Creo carica il modello citato con tutto il suo albero, e
infatti 9255700182 arriva solo per quella via.

La tabella contiene anche nomi di feature (MIRROR3, CHAMFER1) che dal
record non si distinguono dai modelli. Non e' un problema: come per i
soppressi si cerca ogni nome nell'indice e si ignora quello che non c'e'.
Per lo stesso motivo questi codici restano fuori dai "non trovati", e la
coda di scansione porta ora un flag che tiene fuori anche i rami
raggiunti per riferimento esterno - la lista dei componenti mancanti
continua a parlare solo della distinta.

Documentato in docs/formato-asm-record.md §7.5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 14:09:48 +02:00
15 changed files with 1126 additions and 374 deletions

No files matched your search

+3
View File
@@ -37,3 +37,6 @@ publish/
# Output di scripts/ExtractCreoStrings.cs # Output di scripts/ExtractCreoStrings.cs
/BOM.txt /BOM.txt
# Impostazioni locali dell'editor
.vscode/
+64
View File
@@ -0,0 +1,64 @@
# Changelog
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
aderisce a [Semantic Versioning](https://semver.org/lang/it/).
## [0.9.0] - 2026-09-23
### Aggiunto
- Guida utente `docs/guida-utente.md`: tutti i passi, dalla distinta esportata dal gestionale
fino all'esportazione STEP con Creo Distributed Batch.
- Fase 2: bottone "Verifica presenza", visibile solo dopo un import riuscito; è lui (non il
solo import) a sbloccare "Avanti". Etichette numerate accanto ai bottoni.
- Dialogo "Componenti referenziati trovati": bottone "Chiudi" (anche con Esc) e icona dell'app.
### Modificato
- "Scegli cartella di lavoro..." e "Pulisci raw e step..." spostati dalla fase 1 al menu File,
raggiungibili in qualunque momento; "File → Nuova estrazione" rinominato in "Nuovo".
- Fase 1 ridisegnata in una card di stato "Database" con indicatore verde/rosso; rimossa la card
"Cartella di lavoro".
- Barra Avanti/Indietro trasformata in floatbar staccata dai bordi, con bottoni di dimensioni e
stile uniformi (riusata anche nel dialogo dei componenti referenziati).
- Finestra Guida ridisegnata: sezioni con titolo, passi numerati e callout "Nota"/"Attenzione"
colorati; sidebar con selezione nel blu dell'app.
- Testi delle fasi 3-4 semplificati.
### Corretto
- "Avanti" restava disabilitato in fase 1 all'avvio anche con DB e cartella di lavoro già
caricati.
- Guida in-app e README citavano ancora "Verifica completezza assiemi" e "Cambia cartella...",
rimossi da tempo.
## [0.1.0] - 2026-09-22
### Aggiunto
- Wizard a 5 fasi (Database → Importa Distinta Base → Crea cartella raw → Trova componenti
referenziati → Verifica completezza) per preparare i file `raw/` da passare a Creo
Distributed Batch per l'esportazione STEP.
- Indicizzazione dei file CAD Pro-ENGINEER/Creo di un disco/share di rete (radice di default
`X:\`) in un DB SQLite locale (`cad_index.db`), con aggiornamento incrementale e barra di
progresso.
- Import della distinta base dall'export del gestionale (`.xls`/`.xml`) in CSV
componente;descrizione, incluso il riconoscimento della convenzione `DBP_<codice>_<timestamp>`
per aggiungere l'assieme generale come riga.
- Creazione della cartella `raw/` con copia parallela dei file CAD dei componenti trovati
nell'indice.
- Risoluzione ricorsiva dell'albero componenti dai file `.asm` (sotto-assiemi, parti,
componenti soppressi, riferimenti esterni), con parser esatto dei record binari
(`AsmRecordReader`) e fallback euristico testuale (`AssemblyComponentScanner`) per i file
fuori formato.
- Scelta automatica della copia corretta quando lo stesso codice esiste in più cartelle della
share, preferendo quella della cartella dell'assieme che la referenzia.
- Anteprima trascinabile (drag&drop) dei file `.asm` della distinta già in `raw/`, pronti per
Creo Distributed Batch.
- Menu "File → Nuova estrazione" (Ctrl+N) per ripartire con una nuova distinta senza ricaricare
database e cartella di lavoro, e "Aiuto → Guida" (F1) con una sintesi interattiva delle 5 fasi.
- Script standalone C# file-based (`scripts/`): `ExtractCreoStrings.cs`, `BomViaDb.cs`,
`CreoBom.cs`, per estrarre la distinta componenti da riga di comando.
- Pubblicazione come eseguibile standalone single-file self-contained (Windows x64).
+57 -18
View File
@@ -7,7 +7,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
`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 è **Cevolani StepPrep** (`<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.1.0`). È un'app Windows Forms (.NET 10, WinForms) che indicizza e permette di alla versione, `<Version>`, attualmente `0.9.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
@@ -36,6 +36,33 @@ i test possono chiamare direttamente `Indexer.ParseFilename` e
`AssemblyComponentScanner.EstraiCodiciReferenziati` senza esporli come API `AssemblyComponentScanner.EstraiCodiciReferenziati` senza esporli come API
pubblica. pubblica.
## Documentazione: va tenuta allineata
`docs/` non è un archivio di note storiche: descrive **il programma com'è adesso**, ed è la
prima cosa che si legge per capirlo. Ogni modifica strutturale — un passo in più o in meno nel
flusso delle fasi, un nuovo modo di leggere il `.asm`, un cambio nello schema del DB o nel
criterio con cui si sceglie un file — va riflessa nei doc **nello stesso commit** del codice,
non "dopo". Un doc che descrive un comportamento che non esiste più è peggio di un doc
mancante: fa prendere decisioni sbagliate.
Dove scrivere cosa:
| Doc | Contenuto |
|---|---|
| `CLAUDE.md` (questo file) | il flusso essenziale e i punti che si sbaglierebbero senza saperli |
| [`docs/architettura.md`](docs/architettura.md) | il processo delle 5 fasi e la mappa file-per-file |
| [`docs/guida-utente.md`](docs/guida-utente.md) | cosa clicca l'utente, passo per passo, fino a Creo Distributed Batch |
| [`docs/formato-asm-record.md`](docs/formato-asm-record.md) | parser esatto dei record `.asm`, con §7 per i casi limite |
| [`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/copia-raw-parallela.md`](docs/copia-raw-parallela.md) | parallelizzazione della copia in `raw/` |
| [`CHANGELOG.md`](CHANGELOG.md) | storico delle versioni (formato Keep a Changelog); ogni release che cambia `<Version>` nel csproj aggiunge qui la sua voce |
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
riparte da zero (i §7.3/§7.4/§7.5 di `formato-asm-record.md` sono l'esempio del formato
atteso).
## Architettura ## Architettura
Il programma è tutto in `PrepStep/` (nessun altro progetto nella solution). Dettaglio Il programma è tutto in `PrepStep/` (nessun altro progetto nella solution). Dettaglio
@@ -47,15 +74,20 @@ che conta per capire il prodotto.
designer visuale): 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`); qui vive anche la **cartella di lavoro** default `X:\` hardcoded in `RadiceDiscoDefault`); un pallino verde/rosso accanto al titolo
(destinazione di CSV/`raw/`/`step/`), proposta dall'ultima usata o dalla cartella del DB se indica se un DB è caricato. La **cartella di lavoro** (destinazione di CSV/`raw/`/`step/`)
non c'è ancora nulla di salvato — vedi `docs/db-cad-index.md` per il dettaglio di schema e non è più mostrata qui — proposta dall'ultima usata o dalla cartella del DB se non c'è
risoluzione del percorso DB. 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
percorso DB.
2. **Importa Distinta Base** — `DistintaBaseImporter.ConvertiInCsv` genera il CSV 2. **Importa Distinta Base** — `DistintaBaseImporter.ConvertiInCsv` genera il CSV
componente;descrizione nella cartella di lavoro; se il file segue la convenzione del componente;descrizione nella cartella di lavoro (da file scelto o trascinato); se il file
gestionale (`DBP_<codice>_<timestamp>`), aggiunge l'assieme generale come riga in più (da segue la convenzione del gestionale (`DBP_<codice>_<timestamp>`), aggiunge l'assieme
quel momento è un componente come tutti gli altri, nessun caso speciale nel resto del generale come riga in più (da quel momento è un componente come tutti gli altri, nessun
codice). caso speciale nel resto del codice). Il bottone "Verifica presenza" compare solo dopo un
import riuscito e cerca i componenti nell'indice (`VerificaCodiciNelDb`, righe colorate in
verde/rosso in griglia): è quello — non il solo import — a sbloccare "Avanti"
(`AvantiConsentito`).
3. **Crea cartella raw** — `TrovaPercorsiCad` + `CopiaFileTrovati` (parallela, vedi 3. **Crea cartella raw** — `TrovaPercorsiCad` + `CopiaFileTrovati` (parallela, vedi
`docs/copia-raw-parallela.md`) copiano in `raw/` i file CAD dei componenti del CSV trovati `docs/copia-raw-parallela.md`) copiano in `raw/` i file CAD dei componenti del CSV trovati
nell'indice. nell'indice.
@@ -63,19 +95,26 @@ designer visuale):
sotto-assiemi/parti (`RisolviAlberoComponenti`), copiandoli anch'essi in `raw/`. Usa sotto-assiemi/parti (`RisolviAlberoComponenti`), copiandoli anch'essi in `raw/`. Usa
`AsmRecordReader` (parser esatto dei record binari, `docs/formato-asm-record.md`) e ripiega `AsmRecordReader` (parser esatto dei record binari, `docs/formato-asm-record.md`) e ripiega
su `AssemblyComponentScanner` (euristica testuale, `docs/formato-asm.md`) solo se il file su `AssemblyComponentScanner` (euristica testuale, `docs/formato-asm.md`) solo se il file
non rispetta il formato validato (`InvalidDataException`). Due punti da non confondere, non rispetta il formato validato (`InvalidDataException`). Tre punti da non confondere,
entrambi imparati facendo fallire Creo (`docs/formato-asm-record.md` §7.3 e §7.4): tutti imparati facendo fallire Creo (`docs/formato-asm-record.md` §7.3, §7.4 e §7.5):
i **componenti soppressi** non sono nella distinta ma il loro file serve per aprire i **componenti soppressi** non sono nella distinta ma il loro file serve per aprire
l'assieme (`EstraiCodiciSoppressi`, copiati senza finire fra i "non trovati"); e quando lo l'assieme (`EstraiCodiciSoppressi`, copiati senza finire fra i "non trovati"); i
stesso codice esiste in più cartelle della share — succede per il 13% dei codici — va **riferimenti esterni** (assembly feature, datum copiati) citano modelli che non sono né
copiata la copia nella **stessa cartella dell'assieme che la referenzia** membri né piazzamenti, e che Creo carica con tutto il loro albero
(`EstraiCodiciRiferimentiEsterni`, anche questi fuori dai "non trovati", ma con ricorsione);
e quando lo stesso codice esiste in più cartelle della share — succede per il 13% dei
codici — va copiata la copia nella **stessa cartella dell'assieme che la referenzia**
(`ScegliPerCartella`), perché il `.asm` non registra quale usa e sbagliarla impedisce a (`ScegliPerCartella`), perché il `.asm` non registra quale usa e sbagliarla impedisce a
Creo di aprire l'assieme. Creo di aprire l'assieme.
5. **Verifica completezza** — mostra i `.asm` della distinta già in `raw/`, trascinabili su 5. **Verifica completezza** — mostra i `.asm` della distinta già in `raw/`, trascinabili su
Creo per l'esportazione STEP (Distributed Batch, manuale, fuori dall'app); "Verifica Creo per l'esportazione STEP (Distributed Batch, manuale, fuori dall'app).
completezza assiemi" controlla ricorsivamente sull'indice (non su `raw/`, stessa
`AsmRecordReader`/fallback della fase 4) se tutti i sotto-componenti di ogni assieme sono Menu in cima alla finestra: "File → Nuovo" (Ctrl+N) azzera lo stato delle fasi 2-5
presenti. per ripartire con una nuova distinta, senza toccare DB/cartella di lavoro della fase 1 (va
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 →
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`).
## Script standalone (`scripts/`) ## Script standalone (`scripts/`)
+33
View File
@@ -48,6 +48,17 @@ public class AsmRecordReaderTests
Bytes(0xe0, 0x01, "model_t_type", 0, 2, 0xe0, 0x0a, "model_name", 0, nome, 0, Bytes(0xe0, 0x01, "model_t_type", 0, 2, 0xe0, 0x0a, "model_name", 0, nome, 0,
0xe0, 0x0a, "generic_name", 0, 0); 0xe0, 0x0a, "generic_name", 0, 0);
// array dei modelli referenziati: \xf2\xf7\x05 <elementi> \xf3, ogni elemento
// \xe3\xe2<tipo><nome>\0 - vedi docs/formato-asm-record.md §7.5
private static byte[] Riferimenti(params string[] nomi)
{
var parti = new List<object> { 0xf2, 0xf7, 0x05 };
foreach (var nome in nomi)
parti.AddRange(new object[] { 0xe3, 0xe2, 0x02, nome, 0 });
parti.Add(0xf3);
return Bytes(parti.ToArray());
}
private static byte[] Asm(int conteggioDichiarato, byte[] membri, byte[]? extra = null) => private static byte[] Asm(int conteggioDichiarato, byte[] membri, byte[]? extra = null) =>
Bytes("header ", extra ?? Array.Empty<byte>(), "\0members\0", 0xf1, 0xf8, conteggioDichiarato, membri, "#MdlStatus"); Bytes("header ", extra ?? Array.Empty<byte>(), "\0members\0", 0xf1, 0xf8, conteggioDichiarato, membri, "#MdlStatus");
@@ -229,6 +240,28 @@ public class AsmRecordReaderTests
finally { File.Delete(percorso); } finally { File.Delete(percorso); }
} }
[Fact]
public void Riferimenti_esterni_non_sono_fra_i_membri_e_si_risolvono_sul_generico()
{
// Un modello legato da riferimento esterno (assembly feature "#AFn", datum copiati) non
// compare ne' in "members" ne' fra i piazzamenti, ma senza il suo file Creo non apre
// l'assieme: e' il caso che faceva fallire 9255728001_200 in Distributed Batch. Per le
// istanze di family table il file su disco e' il generico fra parentesi angolari.
byte[] dati = Asm(1, Membro(1, 2, "9244206098"),
extra: Bytes(Riferimenti("9255708017#AF2<9255708017>", "9255708018"),
Riferimenti("V6-12--_-_U5933_C<UNI5933>")));
Assert.Equal(new[] { "9244206098" }, AsmRecordReader.LeggiMembri(dati).Select(m => m.Nome));
string percorso = ScriviFile(dati);
try
{
Assert.Equal(new[] { "9255708017", "9255708018", "UNI5933" },
AsmRecordReader.EstraiCodiciRiferimentiEsterni(percorso).OrderBy(c => c, StringComparer.Ordinal));
}
finally { File.Delete(percorso); }
}
[Fact] [Fact]
public void Sezione_members_assente_lancia_eccezione() public void Sezione_members_assente_lancia_eccezione()
{ {
+39
View File
@@ -225,6 +225,45 @@ public static class AsmRecordReader
return soppressi; return soppressi;
} }
/// <summary>Modelli che l'assieme cita nella tabella dei riferimenti (§7.5): oltre ai propri
/// membri comprende quelli legati da **riferimenti esterni** - assembly feature, datum
/// copiati - che non compaiono né in "members" né come piazzamento, ma che Creo carica
/// comunque per aprire l'assieme.
///
/// Terza lista dopo <see cref="EstraiCodiciReferenziati"/> ("cosa c'è in distinta") e <see
/// cref="EstraiCodiciSoppressi"/>: come quest'ultima risponde a "cosa serve su disco", non a
/// "cosa va nella BOM. L'array contiene anche nomi di feature (MIRROR3, CHAMFER1...) e non
/// solo di modelli: non si riesce a distinguerli dal record, ma non danno fastidio perché il
/// chiamante li cerca nell'indice e ignora quelli che non ci sono.</summary>
public static HashSet<string> EstraiCodiciRiferimentiEsterni(string percorsoAsm)
{
string testo = Encoding.Latin1.GetString(File.ReadAllBytes(percorsoAsm));
var codici = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
for (int i = testo.IndexOf(InizioRiferimenti, StringComparison.Ordinal); i >= 0;
i = testo.IndexOf(InizioRiferimenti, i + 1, StringComparison.Ordinal))
{
int max = Math.Min(LunghezzaMassimaRiferimenti, testo.Length - i);
int fine = testo.IndexOf(FineRiferimenti, i, max);
foreach (Match m in RiferimentoRegex.Matches(testo.Substring(i, fine >= 0 ? fine - i : max)))
{
var bracket = IstanzaConGenericoRegex.Match(m.Groups[1].Value);
codici.Add(bracket.Success
? bracket.Groups[2].Value
: SuffissoArrayFeatureRegex.Replace(m.Groups[1].Value, ""));
}
}
return codici;
}
// Array dei modelli referenziati: "\xf2\xf7\x05" lo apre, "\xf3" lo chiude, ogni elemento e'
// "\xe3\xe2<tipo><nome>\x00" (§7.5). Il tetto di lunghezza limita il danno se il terminatore
// non si trova dove atteso: si perde qualche riferimento, non si raccoglie mezzo file.
private const string InizioRiferimenti = "ò÷\u0005ãâ";
private const char FineRiferimenti = 'ó';
private const int LunghezzaMassimaRiferimenti = 4096;
private static readonly Regex RiferimentoRegex = new(
@"\xe3\xe2[\x01\x02]([\x20-\x7e]{3,})\x00", RegexOptions.Compiled);
// "Componente ID 52 (9230100043.PRT)" - la stringa cambia con la localizzazione Creo del // "Componente ID 52 (9230100043.PRT)" - la stringa cambia con la localizzazione Creo del
// file, come in AssemblyComponentScanner.HaPlacement. Minimo 3 caratteri per lo stesso // file, come in AssemblyComponentScanner.HaPlacement. Minimo 3 caratteri per lo stesso
// motivo per cui li' i token generici partono da 3: sotto quella soglia si raccoglie // motivo per cui li' i token generici partono da 3: sotto quella soglia si raccoglie
+214 -230
View File
@@ -1,4 +1,5 @@
using System.Diagnostics; using System.Diagnostics;
using System.Drawing.Drawing2D;
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
namespace PrepStep; namespace PrepStep;
@@ -10,18 +11,21 @@ public partial class Form1 : Form
private readonly Button _btnCreaRaw = new(); private readonly Button _btnCreaRaw = new();
private readonly Button _btnTrovaPrt = new(); private readonly Button _btnTrovaPrt = new();
private readonly Button _btnVerifica = new(); private readonly Button _btnVerifica = new();
private readonly Button _btnCompletezza = new(); private readonly FlowLayoutPanel _rigaVerifica = new();
private readonly ListView _lstAnteprimaAsm = new(); private readonly ListView _lstAnteprimaAsm = new();
private readonly Button _btnPulisci = new();
private readonly Label _lblStatus = new(); private readonly Label _lblStatus = new();
private readonly Label _lblDb = new(); private readonly Label _lblDb = new();
private readonly Label _lblUltimoAggiornamento = new(); private readonly Label _lblUltimoAggiornamento = new();
private readonly Label _lblCartellaLavoro = new(); private readonly Panel _indicatoreDb = new();
private readonly Button _btnCambiaCartella = new(); private bool _dbCaricato;
private readonly DataGridView _gridImport = new(); private readonly DataGridView _gridImport = new();
private readonly DataGridView _gridRaw = new(); private readonly DataGridView _gridRaw = new();
private readonly Label _lblImportStatus = new(); private readonly Label _lblImportStatus = new();
private readonly ToolTip _tipImportStatus = new(); private readonly ToolTip _tipImportStatus = new();
private readonly MenuStrip _menu = new();
private readonly ToolStripMenuItem _mnuNuovaEstrazione = new("Nuovo");
private readonly ToolStripMenuItem _mnuPulisci = new("Pulisci raw e step...");
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
// corrente è "pronta" (vedi AvantiConsentito). NomiFasi/_panelFasi sono paralleli per indice. // corrente è "pronta" (vedi AvantiConsentito). NomiFasi/_panelFasi sono paralleli per indice.
@@ -91,38 +95,27 @@ public partial class Form1 : Form
_lblFase.Font = new Font(Font, FontStyle.Bold); _lblFase.Font = new Font(Font, FontStyle.Bold);
headerPanel.Controls.Add(_lblFase); headerPanel.Controls.Add(_lblFase);
// Barra di navigazione come footer separato dal contenuto: linea sottile in cima e // Floatbar di navigazione: card staccata dai bordi della finestra invece di un footer
// sfondo bianco, invece dei bottoni appoggiati direttamente sul contenuto/sulla barra // a tutta larghezza appoggiato direttamente sul contenuto/sulla barra di stato.
// di stato senza alcuna divisione visiva. var (navContainer, navPanel) = UiStile.FloatBar();
// Padding destro = 16, lo stesso usato dai pannelli di ogni fase (Padding(16)): prima
// era 12, quindi "Avanti" sporgeva 4px oltre il margine destro del contenuto sopra.
var navContainer = new Panel { Dock = DockStyle.Bottom, Height = 64, BackColor = Color.White };
var navSeparatore = new Panel
{
Dock = DockStyle.Top,
Height = 1,
BackColor = Color.FromArgb(220, 220, 220),
};
var navPanel = new FlowLayoutPanel
{
Dock = DockStyle.Fill,
FlowDirection = FlowDirection.RightToLeft,
Padding = new Padding(16, 14, 16, 14),
};
_btnAvanti.Text = "Avanti ›"; _btnAvanti.Text = "Avanti ›";
UiStile.Primario(_btnAvanti); UiStile.Primario(_btnAvanti);
UiStile.Arrotonda(_btnAvanti);
_btnAvanti.Click += (_, _) => VaiAllaFase(_faseAttuale + 1);
navPanel.Controls.Add(_btnAvanti);
_btnIndietro.Text = "‹ Indietro"; _btnIndietro.Text = "‹ Indietro";
_btnIndietro.Margin = new Padding(8, 0, 0, 0); _btnIndietro.Margin = new Padding(8, 0, 0, 0);
UiStile.Secondario(_btnIndietro); UiStile.SecondarioPieno(_btnIndietro);
UiStile.Arrotonda(_btnIndietro);
// Dimensioni uguali decise PRIMA di arrotondare: se si arrotonda un bottone e solo dopo
// lo si ridimensiona, la Region calcolata sulla dimensione vecchia lascia un bordo
// "fantasma" finché non arriva un repaint completo della finestra.
UiStile.LarghezzaUguale(_btnAvanti, _btnIndietro);
UiStile.Arrotonda(_btnAvanti, 10);
UiStile.Arrotonda(_btnIndietro, 10);
_btnAvanti.Click += (_, _) => VaiAllaFase(_faseAttuale + 1);
navPanel.Controls.Add(_btnAvanti);
_btnIndietro.Click += (_, _) => VaiAllaFase(_faseAttuale - 1); _btnIndietro.Click += (_, _) => VaiAllaFase(_faseAttuale - 1);
navPanel.Controls.Add(_btnIndietro); navPanel.Controls.Add(_btnIndietro);
navContainer.Controls.Add(navPanel);
navContainer.Controls.Add(navSeparatore);
var statusPanel = new Panel { Dock = DockStyle.Bottom, Height = 28, BackColor = SystemColors.ControlLight }; var statusPanel = new Panel { Dock = DockStyle.Bottom, Height = 28, BackColor = SystemColors.ControlLight };
_lblStatus.Dock = DockStyle.Fill; _lblStatus.Dock = DockStyle.Fill;
@@ -146,10 +139,31 @@ public partial class Form1 : Form
// Ordine dei Dock.Bottom: l'ultimo aggiunto è il più esterno (il vero bordo inferiore // Ordine dei Dock.Bottom: l'ultimo aggiunto è il più esterno (il vero bordo inferiore
// della finestra). La barra di stato va tra contenuto e azioni; i bottoni Avanti/Indietro // della finestra). La barra di stato va tra contenuto e azioni; i bottoni Avanti/Indietro
// restano il footer più esterno, come in ogni wizard/dialog standard di Windows. // restano il footer più esterno, come in ogni wizard/dialog standard di Windows.
var mnuFile = new ToolStripMenuItem("File");
_mnuNuovaEstrazione.ShortcutKeys = Keys.Control | Keys.N;
_mnuNuovaEstrazione.Click += (_, _) => NuovaEstrazione();
mnuFile.DropDownItems.Add(_mnuNuovaEstrazione);
_mnuCambiaCartella.Click += (_, _) => ScegliCartellaLavoro();
mnuFile.DropDownItems.Add(_mnuCambiaCartella);
_mnuPulisci.Click += (_, _) => PulisciRawEStep();
mnuFile.DropDownItems.Add(_mnuPulisci);
var mnuAiuto = new ToolStripMenuItem("Aiuto");
var mnuGuida = new ToolStripMenuItem("Guida") { ShortcutKeys = Keys.F1 };
mnuGuida.Click += (_, _) => new GuidaForm().ShowDialog(this);
mnuAiuto.DropDownItems.Add(mnuGuida);
_menu.Items.Add(mnuFile);
_menu.Items.Add(mnuAiuto);
MainMenuStrip = _menu;
Controls.Add(_faseContainer); Controls.Add(_faseContainer);
Controls.Add(statusPanel); Controls.Add(statusPanel);
Controls.Add(navContainer); Controls.Add(navContainer);
Controls.Add(headerPanel); Controls.Add(headerPanel);
Controls.Add(_menu);
DragEnter += (_, e) => DragEnter += (_, e) =>
e.Effect = e.Data?.GetDataPresent(DataFormats.FileDrop) == true e.Effect = e.Data?.GetDataPresent(DataFormats.FileDrop) == true
@@ -228,7 +242,7 @@ public partial class Form1 : Form
private bool AvantiConsentito(int fase) => fase switch private bool AvantiConsentito(int fase) => fase switch
{ {
0 => _dbPath is not null && _cartellaLavoro is not null, 0 => _dbPath is not null && _cartellaLavoro is not null,
1 => _righeCorrenti is { Count: > 0 }, 1 => _righeCorrenti is { Count: > 0 } && _ultimaVerifica is not null,
2 => _ultimaCartellaRaw is not null, 2 => _ultimaCartellaRaw is not null,
3 => true, // risoluzione ricorsiva opzionale: non tutti gli assiemi hanno sotto-componenti 3 => true, // risoluzione ricorsiva opzionale: non tutti gli assiemi hanno sotto-componenti
_ => true, _ => true,
@@ -236,6 +250,31 @@ public partial class Form1 : Form
private void AggiornaStatoAvanti() => _btnAvanti.Enabled = AvantiConsentito(_faseAttuale); private void AggiornaStatoAvanti() => _btnAvanti.Enabled = AvantiConsentito(_faseAttuale);
/// <summary>Azzera distinta/raw/verifica (fasi 2-5) per ripartire con una nuova distinta;
/// DB indicizzato e cartella di lavoro (fase 1) restano quelli già caricati, non serve
/// rifare lo scan del disco per ogni estrazione.</summary>
private void NuovaEstrazione()
{
if (_dbPath is null || _cartellaLavoro is null)
{
MessageBox.Show(this, "Carica prima il database in fase 1.", "Nuovo",
MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
_percorsoInputCorrente = null;
_righeCorrenti = null;
_ultimaCartellaRaw = null;
_ultimaVerifica = null;
_gridImport.Rows.Clear();
_gridRaw.Rows.Clear();
_lstAnteprimaAsm.Items.Clear();
ImpostaStatoImport("", Color.Black);
_rigaVerifica.Visible = false;
VaiAllaFase(1);
}
private Panel BuildFaseDatabase() private Panel BuildFaseDatabase()
{ {
var contenuto = new TableLayoutPanel var contenuto = new TableLayoutPanel
@@ -255,78 +294,78 @@ public partial class Form1 : Form
Dock = DockStyle.Top, Dock = DockStyle.Top,
AutoSize = true, AutoSize = true,
ForeColor = SystemColors.GrayText, ForeColor = SystemColors.GrayText,
Margin = new Padding(0, 0, 0, 12), Margin = new Padding(0, 0, 0, 16),
Text = "L'indice CAD viene caricato automaticamente all'avvio, se già presente. " Text = "L'indice CAD viene caricato automaticamente all'avvio, se già presente.",
+ "Usa 'Apri DB...' per selezionarne un altro, oppure 'Aggiorna indice' per "
+ "scansionare di nuovo il disco (X:\\).",
}; };
contenuto.Controls.Add(lblIstruzioni); contenuto.Controls.Add(lblIstruzioni);
var cardDb = UiStile.Card();
var contenutoCardDb = new TableLayoutPanel
{
Dock = DockStyle.Top,
AutoSize = true,
AutoSizeMode = AutoSizeMode.GrowAndShrink,
ColumnCount = 1,
};
contenutoCardDb.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
var rigaTitoloDb = new FlowLayoutPanel
{
AutoSize = true,
FlowDirection = FlowDirection.LeftToRight,
Margin = new Padding(0, 0, 0, 6),
};
_indicatoreDb.Size = new Size(10, 10);
_indicatoreDb.Margin = new Padding(0, 5, 8, 0);
_indicatoreDb.Paint += (_, e) =>
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
var colore = _dbCaricato ? Color.FromArgb(30, 160, 90) : Color.FromArgb(200, 60, 60);
using var pennello = new SolidBrush(colore);
e.Graphics.FillEllipse(pennello, 0, 0, _indicatoreDb.Width - 1, _indicatoreDb.Height - 1);
};
rigaTitoloDb.Controls.Add(_indicatoreDb);
var lblTitoloDb = new Label
{
AutoSize = true,
Font = new Font(Font, FontStyle.Bold),
Text = "Database",
};
rigaTitoloDb.Controls.Add(lblTitoloDb);
contenutoCardDb.Controls.Add(rigaTitoloDb);
_lblDb.Dock = DockStyle.Top; _lblDb.Dock = DockStyle.Top;
_lblDb.AutoSize = true; _lblDb.AutoSize = true;
_lblDb.ForeColor = SystemColors.GrayText; _lblDb.ForeColor = SystemColors.ControlText;
_lblDb.Margin = new Padding(0, 0, 0, 4); _lblDb.Margin = new Padding(0, 0, 0, 2);
contenuto.Controls.Add(_lblDb); contenutoCardDb.Controls.Add(_lblDb);
_lblUltimoAggiornamento.AutoSize = true; _lblUltimoAggiornamento.AutoSize = true;
_lblUltimoAggiornamento.ForeColor = SystemColors.GrayText; _lblUltimoAggiornamento.ForeColor = SystemColors.GrayText;
_lblUltimoAggiornamento.Margin = new Padding(0, 0, 0, 12); _lblUltimoAggiornamento.Margin = new Padding(0, 0, 0, 14);
contenuto.Controls.Add(_lblUltimoAggiornamento); contenutoCardDb.Controls.Add(_lblUltimoAggiornamento);
var bottoni = new FlowLayoutPanel { AutoSize = true, FlowDirection = FlowDirection.LeftToRight }; var bottoniDb = new FlowLayoutPanel { AutoSize = true, FlowDirection = FlowDirection.LeftToRight };
_btnApriDb.Text = "Apri DB..."; _btnApriDb.Text = "Apri DB...";
_btnApriDb.Margin = new Padding(0, 0, 8, 0); _btnApriDb.Margin = new Padding(0, 0, 8, 0);
UiStile.Secondario(_btnApriDb); UiStile.Secondario(_btnApriDb);
UiStile.Arrotonda(_btnApriDb);
_btnApriDb.Click += (_, _) => ScegliDatabase(); _btnApriDb.Click += (_, _) => ScegliDatabase();
bottoni.Controls.Add(_btnApriDb); bottoniDb.Controls.Add(_btnApriDb);
_btnAggiorna.Text = "Aggiorna indice"; _btnAggiorna.Text = "Aggiorna indice";
UiStile.Secondario(_btnAggiorna); UiStile.Secondario(_btnAggiorna);
UiStile.Arrotonda(_btnAggiorna);
_btnAggiorna.Click += (_, _) => AggiornaIndice(); _btnAggiorna.Click += (_, _) => AggiornaIndice();
bottoni.Controls.Add(_btnAggiorna); bottoniDb.Controls.Add(_btnAggiorna);
contenuto.Controls.Add(bottoni); contenutoCardDb.Controls.Add(bottoniDb);
cardDb.Controls.Add(contenutoCardDb);
var lblIstruzioniCartella = new Label contenuto.Controls.Add(cardDb);
{
Dock = DockStyle.Top,
AutoSize = true,
ForeColor = SystemColors.GrayText,
Margin = new Padding(0, 20, 0, 4),
Text = "Cartella di lavoro: qui vengono scritti il CSV della distinta e la "
+ "cartella 'raw' (gli STEP esportati con Creo vanno invece in 'step', "
+ "dentro la stessa cartella). Proposta automaticamente (ricordata da un "
+ "avvio all'altro), modificabile con 'Cambia cartella...'.",
};
contenuto.Controls.Add(lblIstruzioniCartella);
_lblCartellaLavoro.Dock = DockStyle.Top;
_lblCartellaLavoro.AutoSize = true;
_lblCartellaLavoro.ForeColor = SystemColors.GrayText;
_lblCartellaLavoro.Margin = new Padding(0, 0, 0, 4);
contenuto.Controls.Add(_lblCartellaLavoro);
_btnCambiaCartella.Text = "Cambia cartella...";
UiStile.Secondario(_btnCambiaCartella);
_btnCambiaCartella.Click += (_, _) => ScegliCartellaLavoro();
contenuto.Controls.Add(_btnCambiaCartella);
var lblManutenzione = new Label
{
Dock = DockStyle.Top,
AutoSize = true,
ForeColor = SystemColors.GrayText,
Margin = new Padding(0, 20, 0, 8),
Text = "Manutenzione: svuota le cartelle 'raw' e 'step' nella cartella di lavoro "
+ "prima di un nuovo giro (non tocca l'indice, solo le copie locali).",
};
contenuto.Controls.Add(lblManutenzione);
_btnPulisci.Text = "Pulisci raw e step";
UiStile.Secondario(_btnPulisci);
_btnPulisci.Click += (_, _) => PulisciRawEStep();
contenuto.Controls.Add(_btnPulisci);
var panel = new Panel(); var panel = new Panel();
panel.Controls.Add(contenuto); panel.Controls.Add(contenuto);
@@ -357,20 +396,47 @@ public partial class Form1 : Form
}; };
contenuto.Controls.Add(lblIstruzioni); contenuto.Controls.Add(lblIstruzioni);
var rigaImporta = new FlowLayoutPanel
{
AutoSize = true,
FlowDirection = FlowDirection.LeftToRight,
Margin = new Padding(0, 0, 0, 8),
};
var btnSfoglia = new Button var btnSfoglia = new Button
{ {
Text = "Scegli file distinta...", Text = "Scegli file distinta...",
Margin = new Padding(0, 0, 0, 8), Margin = new Padding(0, 0, 8, 0),
}; };
UiStile.Secondario(btnSfoglia); UiStile.Secondario(btnSfoglia);
btnSfoglia.Click += (_, _) => ScegliFileDistinta(); btnSfoglia.Click += (_, _) => ScegliFileDistinta();
contenuto.Controls.Add(btnSfoglia); rigaImporta.Controls.Add(btnSfoglia);
rigaImporta.Controls.Add(new Label
{
AutoSize = true,
ForeColor = SystemColors.GrayText,
Margin = new Padding(0, 9, 0, 0),
Text = "1. Carica la distinta",
});
contenuto.Controls.Add(rigaImporta);
_rigaVerifica.AutoSize = true;
_rigaVerifica.FlowDirection = FlowDirection.LeftToRight;
_rigaVerifica.Margin = new Padding(0, 0, 0, 8);
_rigaVerifica.Visible = false; // compare solo dopo un import riuscito (file o drag&drop)
_btnVerifica.Text = "Verifica presenza"; _btnVerifica.Text = "Verifica presenza";
_btnVerifica.Margin = new Padding(0, 0, 0, 8); _btnVerifica.Margin = new Padding(0, 0, 8, 0);
UiStile.Secondario(_btnVerifica); UiStile.Secondario(_btnVerifica);
_btnVerifica.Click += (_, _) => VerificaCodiciNelDb(); _btnVerifica.Click += (_, _) => VerificaCodiciNelDb();
contenuto.Controls.Add(_btnVerifica); _rigaVerifica.Controls.Add(_btnVerifica);
_rigaVerifica.Controls.Add(new Label
{
AutoSize = true,
ForeColor = SystemColors.GrayText,
Margin = new Padding(0, 9, 0, 0),
Text = "2. Verifica la presenza dei file sul server",
});
contenuto.Controls.Add(_rigaVerifica);
_lblImportStatus.Dock = DockStyle.Top; _lblImportStatus.Dock = DockStyle.Top;
_lblImportStatus.AutoSize = false; _lblImportStatus.AutoSize = false;
@@ -420,22 +486,25 @@ public partial class Form1 : Form
}; };
contenuto.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); contenuto.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
var lblIstruzioni = new Label var rigaCreaRaw = new FlowLayoutPanel
{ {
Dock = DockStyle.Top,
AutoSize = true, AutoSize = true,
ForeColor = SystemColors.GrayText, FlowDirection = FlowDirection.LeftToRight,
Margin = new Padding(0, 0, 0, 8), Margin = new Padding(0, 0, 0, 12),
Text = "Cerca nell'indice il percorso CAD di ogni componente importato e copia i "
+ "file trovati in una cartella 'raw' dentro la cartella di lavoro (fase 1).",
}; };
contenuto.Controls.Add(lblIstruzioni);
_btnCreaRaw.Text = "Crea cartella raw"; _btnCreaRaw.Text = "Crea cartella raw";
_btnCreaRaw.Margin = new Padding(0, 0, 0, 12); _btnCreaRaw.Margin = new Padding(0, 0, 8, 0);
UiStile.Primario(_btnCreaRaw); UiStile.Primario(_btnCreaRaw);
_btnCreaRaw.Click += (_, _) => CreaCartellaRaw(); _btnCreaRaw.Click += (_, _) => CreaCartellaRaw();
contenuto.Controls.Add(_btnCreaRaw); rigaCreaRaw.Controls.Add(_btnCreaRaw);
rigaCreaRaw.Controls.Add(new Label
{
AutoSize = true,
ForeColor = SystemColors.GrayText,
Margin = new Padding(0, 9, 0, 0),
Text = "Copia i file dal server nella cartella 'raw'.",
});
contenuto.Controls.Add(rigaCreaRaw);
_gridRaw.Dock = DockStyle.Fill; _gridRaw.Dock = DockStyle.Fill;
_gridRaw.ReadOnly = true; _gridRaw.ReadOnly = true;
@@ -486,8 +555,7 @@ public partial class Form1 : Form
Margin = new Padding(0, 0, 0, 12), Margin = new Padding(0, 0, 0, 12),
Text = "Gli assiemi referenziano altri PRT e sotto-assiemi al loro interno: premi " Text = "Gli assiemi referenziano altri PRT e sotto-assiemi al loro interno: premi "
+ "qui per risolvere l'albero componenti (ricorsivamente) e copiare tutto ciò che " + "qui per risolvere l'albero componenti (ricorsivamente) e copiare tutto ciò che "
+ "viene trovato nell'indice dentro 'raw'. Passo opzionale: se la distinta contiene " + "viene trovato nell'indice dentro 'raw'.",
+ "solo parti singole puoi anche passare direttamente alla fase successiva.",
}; };
contenuto.Controls.Add(lblIstruzioni); contenuto.Controls.Add(lblIstruzioni);
@@ -516,27 +584,6 @@ public partial class Form1 : Form
}; };
top.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); top.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
var lblIstruzioniCompletezza = new Label
{
Dock = DockStyle.Top,
AutoSize = true,
ForeColor = SystemColors.GrayText,
Margin = new Padding(0, 0, 0, 8),
Text = "Per ogni assieme della distinta, verifica se tutti i suoi componenti "
+ "(ricorsivamente fino ai PRT) sono nell'indice. Nota: la scansione dei .asm "
+ "è euristica, quindi qualche falso 'incompleto' è possibile.",
};
top.Controls.Add(lblIstruzioniCompletezza);
// Primario: è l'azione principale di questa fase, non una scelta secondaria — prima
// era uno stile grigio che si confondeva con lo sfondo.
_btnCompletezza.Text = "Verifica completezza assiemi";
_btnCompletezza.Margin = new Padding(0, 0, 0, 12);
UiStile.Primario(_btnCompletezza);
UiStile.Arrotonda(_btnCompletezza);
_btnCompletezza.Click += (_, _) => VerificaCompletezzaAssiemi();
top.Controls.Add(_btnCompletezza);
var lblAnteprimaAsm = new Label var lblAnteprimaAsm = new Label
{ {
AutoSize = true, AutoSize = true,
@@ -597,7 +644,7 @@ public partial class Form1 : Form
{ {
try try
{ {
var (csv, righe) = DistintaBaseImporter.ConvertiInCsv(percorso, _cartellaLavoro!); var (_, righe) = DistintaBaseImporter.ConvertiInCsv(percorso, _cartellaLavoro!);
_percorsoInputCorrente = percorso; _percorsoInputCorrente = percorso;
_righeCorrenti = righe; _righeCorrenti = righe;
@@ -610,7 +657,8 @@ public partial class Form1 : Form
_ultimaCartellaRaw = null; _ultimaCartellaRaw = null;
_ultimaVerifica = null; _ultimaVerifica = null;
ImpostaStatoImport($"CSV generato ({righe.Count} righe): {csv}", Color.DarkGreen); ImpostaStatoImport("", Color.Black);
_rigaVerifica.Visible = true;
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -618,6 +666,7 @@ public partial class Form1 : Form
_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);
_rigaVerifica.Visible = false;
} }
AggiornaStatoAvanti(); AggiornaStatoAvanti();
} }
@@ -645,6 +694,7 @@ public partial class Form1 : Form
? Color.FromArgb(198, 239, 206) ? Color.FromArgb(198, 239, 206)
: Color.FromArgb(255, 199, 206); : Color.FromArgb(255, 199, 206);
} }
AggiornaStatoAvanti();
} }
private void CreaCartellaRaw() private void CreaCartellaRaw()
@@ -688,7 +738,8 @@ public partial class Form1 : Form
.ToList(); .ToList();
if (cartelle.Count == 0) if (cartelle.Count == 0)
{ {
ImpostaStatoImport($"Nessuna cartella 'raw' o 'step' trovata in {radice}.", Color.DarkRed); MessageBox.Show(this, $"Nessuna cartella 'raw' o 'step' trovata in {radice}.",
"Pulisci raw e step", MessageBoxButtons.OK, MessageBoxIcon.Information);
return; return;
} }
@@ -715,7 +766,8 @@ public partial class Form1 : Form
} }
} }
ImpostaStatoImport($"{eliminati} file eliminati da {string.Join(" e ", cartelle)}.", Color.DarkGreen); MessageBox.Show(this, $"{eliminati} file eliminati da {string.Join(" e ", cartelle)}.",
"Pulisci raw e step", 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
@@ -875,14 +927,18 @@ public partial class Form1 : Form
{ {
// Ogni assieme viaggia con la cartella della share da cui proviene: e' li' che Creo // Ogni assieme viaggia con la cartella della share da cui proviene: e' li' che Creo
// cerca per primi i suoi componenti, quindi e' li' che li cerchiamo anche noi. // cerca per primi i suoi componenti, quindi e' li' che li cerchiamo anche noi.
var daScansionare = new Queue<(string File, string? CartellaOrigine)>( // "Esterno" marca i rami raggiunti da un riferimento esterno invece che dalla distinta:
asmIniziali.Select(f => (f, (string?)null))); // servono su disco ma non sono componenti, quindi i loro codici mancanti non vanno fra i
// "non trovati" (sarebbero falsi allarmi, come per i soppressi).
var daScansionare = new Queue<(string File, string? CartellaOrigine, bool Esterno)>(
asmIniziali.Select(f => (f, (string?)null, false)));
var codiciIniziali = asmIniziali.Select(f => Path.GetFileName(f).Split('.')[0]).ToList(); var codiciIniziali = asmIniziali.Select(f => Path.GetFileName(f).Split('.')[0]).ToList();
var codiciVisti = new HashSet<string>( var codiciVisti = new HashSet<string>(
codiciIniziali.Concat(codiciIniziali.Select(AssemblyComponentScanner.CodiceRadice)), codiciIniziali.Concat(codiciIniziali.Select(AssemblyComponentScanner.CodiceRadice)),
StringComparer.OrdinalIgnoreCase); StringComparer.OrdinalIgnoreCase);
var codiciSoppressiVisti = new HashSet<string>(StringComparer.OrdinalIgnoreCase); var codiciSoppressiVisti = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var codiciEsterniVisti = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var trovati = new List<(string Codice, string Tipo, string Percorso)>(); var trovati = new List<(string Codice, string Tipo, string Percorso)>();
var codiciNonTrovati = new List<string>(); var codiciNonTrovati = new List<string>();
if (daScansionare.Count == 0 || _dbPath is null) if (daScansionare.Count == 0 || _dbPath is null)
@@ -894,7 +950,7 @@ public partial class Form1 : Form
int processati = 0; int processati = 0;
while (daScansionare.Count > 0) while (daScansionare.Count > 0)
{ {
var (asm, cartellaOrigine) = daScansionare.Dequeue(); var (asm, cartellaOrigine, esterno) = daScansionare.Dequeue();
cartellaOrigine ??= CartellaDiOrigine(asm, candidati); cartellaOrigine ??= CartellaDiOrigine(asm, candidati);
processati++; processati++;
foreach (var codice in EstraiCodiciReferenziati(asm, codiciNoti.Keys)) foreach (var codice in EstraiCodiciReferenziati(asm, codiciNoti.Keys))
@@ -904,7 +960,8 @@ public partial class Form1 : Form
if (!candidati.TryGetValue(codice, out var perCodice)) if (!candidati.TryGetValue(codice, out var perCodice))
{ {
codiciNonTrovati.Add(codice); if (!esterno)
codiciNonTrovati.Add(codice);
continue; continue;
} }
@@ -915,7 +972,7 @@ public partial class Form1 : Form
if (tipo == "asm") if (tipo == "asm")
{ {
daScansionare.Enqueue((Path.Combine(_ultimaCartellaRaw!, Path.GetFileName(percorso)), daScansionare.Enqueue((Path.Combine(_ultimaCartellaRaw!, Path.GetFileName(percorso)),
Path.GetDirectoryName(percorso))); Path.GetDirectoryName(percorso), esterno));
} }
} }
} }
@@ -936,6 +993,28 @@ public partial class Form1 : Form
trovati.Add((codice, tipo, percorso)); trovati.Add((codice, tipo, percorso));
} }
} }
// I riferimenti esterni (assembly feature, datum copiati verso un altro modello) non
// sono componenti e non compaiono fra i piazzamenti, ma Creo carica il modello citato
// per aprire l'assieme - e con lui i *suoi* componenti, quindi qui, a differenza dei
// soppressi, si ricorre dentro. Vedi docs/formato-asm-record.md §7.5.
foreach (var codice in AsmRecordReader.EstraiCodiciRiferimentiEsterni(asm))
{
if (!codiciEsterniVisti.Add(codice) || codiciVisti.Contains(codice))
continue;
if (!candidati.TryGetValue(codice, out var perCodice))
continue;
foreach (var (tipo, percorso) in ScegliPerCartella(perCodice, cartellaOrigine))
{
CopiaFileTrovati(new[] { percorso }, _ultimaCartellaRaw!);
trovati.Add((codice, tipo, percorso));
if (tipo == "asm")
{
daScansionare.Enqueue((Path.Combine(_ultimaCartellaRaw!, Path.GetFileName(percorso)),
Path.GetDirectoryName(percorso), true));
}
}
}
progress?.Report((processati, processati + daScansionare.Count)); progress?.Report((processati, processati + daScansionare.Count));
} }
@@ -980,102 +1059,6 @@ public partial class Form1 : Form
dialog.ShowDialog(this); dialog.ShowDialog(this);
} }
/// <summary>Per ogni assieme della distinta, verifica ricorsivamente (leggendo i .asm dai
/// percorsi originali sulla share) se tutti i componenti fino ai PRT sono nell'indice.
/// I risultati di ogni file/codice vengono in cache e condivisi tra gli assiemi controllati
/// nella stessa passata, per non rileggere più volte gli stessi sotto-componenti.</summary>
private void VerificaCompletezzaAssiemi()
{
if (_righeCorrenti is null)
return;
if (_dbPath is null)
{
ImpostaStatoImport("Nessun database caricato: impossibile verificare la completezza.", Color.DarkRed);
return;
}
var codiciNoti = CaricaCodiciNoti(_dbPath);
var cacheCodiciFile = new Dictionary<string, HashSet<string>>();
List<(string Tipo, string Percorso)> CercaComponenti(string codice) =>
codiciNoti.TryGetValue(codice, out var trovati) ? trovati : new List<(string, string)>();
bool AssiemeCompleto(string percorsoAsmIniziale)
{
var daVisitare = new Queue<string>();
daVisitare.Enqueue(percorsoAsmIniziale);
var fileVisitati = new HashSet<string> { percorsoAsmIniziale };
var codiceIniziale = Path.GetFileName(percorsoAsmIniziale).Split('.')[0];
var codiciVisti = new HashSet<string>
{
codiceIniziale,
AssemblyComponentScanner.CodiceRadice(codiceIniziale),
};
while (daVisitare.Count > 0)
{
var file = daVisitare.Dequeue();
if (!cacheCodiciFile.TryGetValue(file, out var codiciReferenziati))
{
try
{
codiciReferenziati = EstraiCodiciReferenziati(file, codiciNoti.Keys).ToHashSet(StringComparer.OrdinalIgnoreCase);
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{
return false; // file irraggiungibile (share offline) -> non verificabile, per prudenza "incompleto"
}
cacheCodiciFile[file] = codiciReferenziati;
}
foreach (var codice in codiciReferenziati)
{
if (!codiciVisti.Add(codice))
continue;
var componenti = CercaComponenti(codice);
if (componenti.Count == 0)
return false;
foreach (var (tipo, percorso) in componenti)
{
if (tipo == "asm" && fileVisitati.Add(percorso))
{
daVisitare.Enqueue(percorso);
}
}
}
}
return true;
}
// L'assieme generale è già una riga di _righeCorrenti (vedi DistintaBaseImporter), quindi
// la sua completezza viene verificata insieme a quella di tutti gli altri componenti.
var righeReport = new List<(string Componente, string Descrizione, bool? Completo)>();
foreach (var (componente, descrizione) in _righeCorrenti)
{
var componenti = CercaComponenti(componente);
string? percorsoAsm = componenti.Where(c => c.Tipo == "asm").Select(c => c.Percorso).FirstOrDefault();
bool? completo = componenti.Count == 0 ? null
: percorsoAsm is null ? true // solo prt trovato: nessuna sotto-struttura da verificare
: AssiemeCompleto(percorsoAsm);
righeReport.Add((componente, descrizione, completo));
}
int completi = righeReport.Count(r => r.Completo == true);
int incompleti = righeReport.Count(r => r.Completo == false);
int nonTrovati = righeReport.Count(r => r.Completo is null);
ImpostaStatoImport(
$"Completezza assiemi: {completi} completi, {incompleti} incompleti, "
+ $"{nonTrovati} non trovati nell'indice.",
Color.DarkGreen);
using var dialogCompletezza = new ReportCompletezzaForm(righeReport);
dialogCompletezza.ShowDialog(this);
}
/// <summary>Copia i file trovati nella cartella "raw" indicata, creandola se necessario. /// <summary>Copia i file trovati nella cartella "raw" indicata, creandola se necessario.
/// Ritorna il numero di file copiati con successo. Parallelizzata (grado limitato): il /// Ritorna il numero di file copiati con successo. Parallelizzata (grado limitato): il
/// collo di bottiglia è la latenza di rete verso la share, non la CPU, quindi poche copie /// collo di bottiglia è la latenza di rete verso la share, non la CPU, quindi poche copie
@@ -1154,14 +1137,14 @@ public partial class Form1 : Form
/// 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 dove si
/// trova il DB (più sensata dell'eseguibile: quella è dove l'utente tiene il proprio lavoro, /// trova il DB (più sensata dell'eseguibile: quella è dove l'utente tiene il proprio lavoro,
/// non dov'è installata la app). Richiede quindi che InizializzaDatabase sia già stata /// non dov'è installata la app). Richiede quindi che InizializzaDatabase sia già stata
/// chiamata. Sempre modificabile con "Cambia cartella..." in fase 1.</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; : (_dbPath is not null ? Path.GetDirectoryName(_dbPath) : null) ?? AppContext.BaseDirectory;
AggiornaLabelCartellaLavoro(); AggiornaStatoAvanti();
} }
private void ScegliCartellaLavoro() private void ScegliCartellaLavoro()
@@ -1176,13 +1159,10 @@ public partial class Form1 : Form
string percorso = dialog.SelectedPath; string percorso = dialog.SelectedPath;
_cartellaLavoro = percorso; _cartellaLavoro = percorso;
AggiornaLabelCartellaLavoro();
SalvaUltimaCartellaLavoro(percorso); SalvaUltimaCartellaLavoro(percorso);
AggiornaStatoAvanti(); AggiornaStatoAvanti();
} }
private void AggiornaLabelCartellaLavoro() => _lblCartellaLavoro.Text = $"Cartella di lavoro: {_cartellaLavoro}";
private static string? LeggiUltimaCartellaLavoro() private static string? LeggiUltimaCartellaLavoro()
{ {
try try
@@ -1223,6 +1203,8 @@ public partial class Form1 : Form
_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.";
_lblUltimoAggiornamento.Text = ""; _lblUltimoAggiornamento.Text = "";
_dbCaricato = false;
_indicatoreDb.Invalidate();
AggiornaStatoAvanti(); AggiornaStatoAvanti();
return; return;
} }
@@ -1267,6 +1249,8 @@ public partial class Form1 : Form
MessageBox.Show(this, $"Impossibile aprire il database:\n{ex.Message}", "Errore", MessageBox.Show(this, $"Impossibile aprire il database:\n{ex.Message}", "Errore",
MessageBoxButtons.OK, MessageBoxIcon.Error); MessageBoxButtons.OK, MessageBoxIcon.Error);
} }
_dbCaricato = _dbPath is not null;
_indicatoreDb.Invalidate();
AggiornaStatoAvanti(); AggiornaStatoAvanti();
} }
+281
View File
@@ -0,0 +1,281 @@
namespace PrepStep;
/// <summary>Guida interattiva richiamabile da "Aiuto ▸ Guida" (F1): stessa guida completa di
/// docs/guida-utente.md (passo-passo fino a Creo Distributed Batch), pensata per chi apre
/// l'app senza aver letto i docs e senza esperienza pregressa. Il dettaglio tecnico (formato
/// .asm, schema DB, ecc.) resta solo in docs/architettura.md e affini, per non duplicarlo qui.
///
/// Gerarchia tipografica nel pannello di lettura (vedi ScriviSezione): titolo sezione in blu
/// primario/grassetto/14pt, corpo in grigio quasi-nero/10pt, passi numerati con il numero in
/// blu grassetto per farli scorrere a colpo d'occhio, callout con sfondo tinta piatta (nessun
/// bordo/ombra: RichTextBox non li supporta) — blu tenue per le note informative, ambra tenue
/// per gli avvisi importanti, con etichetta ("Nota"/"Attenzione") in grassetto a inizio blocco
/// invece di un colore isolato, così restano leggibili anche senza vedere i colori.</summary>
public class GuidaForm : Form
{
private sealed record Sezione(
string Titolo,
string Intro,
string[]? Passi = null,
string? NotaTesto = null,
bool NotaAttenzione = false);
// Palette: blu primario dell'app per titoli/passi, ambra tenue solo per gli avvisi che
// possono far perdere tempo se ignorati (es. l'errore Creo [-4]) — il rosso resta riservato
// ai messaggi di errore veri e propri altrove nell'app.
private static readonly Color ColoreTesto = Color.FromArgb(40, 40, 40);
private static readonly Color ColoreNotaInfoSfondo = Color.FromArgb(232, 240, 250);
private static readonly Color ColoreNotaInfoTesto = Color.FromArgb(15, 65, 120);
private static readonly Color ColoreNotaAttenzioneSfondo = Color.FromArgb(253, 240, 219);
private static readonly Color ColoreNotaAttenzioneTesto = Color.FromArgb(140, 90, 10);
private static readonly Sezione[] Sezioni =
{
new("Panoramica",
"Cevolani StepPrep 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 " +
"e guida attraverso 5 fasi, una alla volta, con \"Avanti\"/\"Indietro\".",
NotaTesto: "\"File ▸ Nuovo\" (Ctrl+N) azzera distinta/raw/verifica per ripartire " +
"con una nuova distinta base, senza dover ricaricare il database."),
new("Prima di iniziare",
"Prima di importare la prima distinta:",
Passi:
[
"Serve accesso alla share di rete con i file CAD (di default X:\\).",
"Serve l'estrazione \"Distinta Base\" da Fata (il gestionale): il file .xls " +
"così come Fata lo esporta, senza risalvarlo o convertirlo in altri " +
"formati — l'app riconosce da sola se sotto l'estensione .xls c'è XML o " +
"Excel binario vero.",
"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 " +
"fare nulla di diverso.",
"Conviene impostare la cartella di lavoro giusta (fase 1, o \"File ▸ Scegli " +
"cartella di lavoro...\") prima di importare la 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 " +
"pulire \"raw\" e \"step\" prima di ripartire (\"File ▸ Pulisci raw e " +
"step...\"): \"File ▸ Nuovo\" azzera solo lo stato dell'app, non i file " +
"già copiati su disco.",
]),
new("1. Database",
"Carica o aggiorna l'indice \"cad_index.db\" a partire dalle cartelle di primo " +
"livello del disco/share (radice di default X:\\). Un pallino verde/rosso indica " +
"se un database è caricato. La cartella di lavoro, dove finiscono CSV, \"raw\" e " +
"\"step\", si cambia da \"File ▸ Scegli cartella di lavoro...\"."),
new("2. Importa Distinta Base",
"Due passi in sequenza:",
Passi:
[
"Carica la distinta: trascina il file .xls nella finestra, oppure premi " +
"\"Scegli file distinta...\". Le righe importate compaiono nella griglia " +
"(Componente/Descrizione) e viene scritto un CSV nella cartella di lavoro.",
"Verifica la presenza dei file sul server: compare solo dopo un import " +
"riuscito. Cerca ogni componente nel database — le righe della griglia si " +
"colorano di verde (trovato) o rosso (non trovato nell'indice).",
],
NotaTesto: "\"Avanti\" si sblocca solo dopo il passo 2, non basta il solo import: " +
"è il modo per accorgersi subito se il database è disallineato rispetto alla " +
"share prima di andare avanti.",
NotaAttenzione: true),
new("3. Crea cartella raw",
"Premi \"Crea cartella raw\": per ogni componente della distinta trovato " +
"nell'indice, l'app ne copia il file CAD dalla share nella cartella \"raw\" " +
"(dentro la cartella di lavoro). La griglia mostra il percorso CAD copiato per " +
"ogni componente."),
new("4. Trova componenti referenziati",
"Gli assiemi (.asm) referenziano altri file (sotto-assiemi, parti, componenti " +
"soppressi, riferimenti esterni) che Creo deve poter caricare insieme " +
"all'assieme principale, anche se non compaiono nella distinta del gestionale. " +
"Premi \"Trova componenti referenziati\": l'app legge i file .asm già in \"raw\", " +
"risolve ricorsivamente tutto l'albero e copia anche quei file in \"raw\".\n\n" +
"Si apre una finestra con l'elenco di quanto trovato e copiato, con in fondo " +
"eventuali codici non corrispondenti a nessun file indicizzato (possono essere " +
"disegni, componenti davvero mancanti, o falsi positivi — vale la pena " +
"controllarli). Da lì si può anche aprire direttamente la cartella \"raw\".",
NotaTesto: "Passo opzionale se la distinta contiene solo parti singole senza " +
"sotto-assiemi: in quel caso si può passare direttamente alla fase successiva."),
new("5. Verifica completezza",
"Mostra i file .asm della distinta già in \"raw\", trascinabili. Da qui il " +
"passaggio a Creo è manuale, fuori da quest'app:",
Passi:
[
"Apri Creo Distributed Batch (dal menu Start di Windows, o dal file " +
"ptcdbatch.bat nella cartella bin dell'installazione Creo).",
"Crea (o riusa, se già presente in azienda) un gruppo di attività per " +
"l'esportazione STEP: menu Actions ▸ New Task Group.",
"Aggiungi i file: trascina i .asm dalla finestra dell'app (o dalla cartella " +
"\"raw\" in Esplora risorse) sul gruppo di attività, oppure sfoglia e " +
"selezionali a mano.",
"Verifica che il gruppo di attività punti al config.pro/profilo di export " +
"STEP giusto (sezione Configuration Files) — di norma è già impostato " +
"una volta sola per tutti.",
"Avvia l'elaborazione (Start/Run): Distributed Batch legge ogni .asm, con " +
"tutto l'albero di sotto-componenti già raccolto nelle fasi 3-4, ed " +
"esporta i file STEP nella cartella \"step\" (dentro la cartella di " +
"lavoro, accanto a \"raw\").",
],
NotaTesto: "Se Distributed Batch fallisce con \"[-4] Dati di input non " +
"completi\", quasi sempre manca un file nell'albero dei componenti — Creo " +
"non dice quale. Torna alla fase 4 e controlla l'elenco dei \"non trovati\": " +
"è il punto di partenza per capire cosa recuperare prima di riprovare.",
NotaAttenzione: true),
new("Ripartire da capo",
"Quando devi processare un'altra distinta, usa \"File ▸ Nuovo\" (Ctrl+N): azzera " +
"CSV, raw e verifica delle fasi 2-5 e riporta al passo 2, senza dover ricaricare " +
"il database o la cartella di lavoro (restano quelli già impostati in fase 1).",
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 " +
"step...\"."),
};
public GuidaForm()
{
Text = "Guida";
ClientSize = new Size(780, 520);
MinimumSize = new Size(580, 400);
StartPosition = FormStartPosition.CenterParent;
Font = new Font("Segoe UI", 9.5f);
try
{
Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
}
catch (Exception ex) when (ex is IOException or ArgumentException)
{
// ponytail: nessuna icona nell'exe (es. lanciato da dotnet run) -> resta quella di default
}
var headerPanel = new Panel { Dock = DockStyle.Top, Height = 40, BackColor = SystemColors.ControlLight };
var lblHeader = new Label
{
Dock = DockStyle.Fill,
Text = "Guida",
TextAlign = ContentAlignment.MiddleLeft,
Padding = new Padding(16, 0, 0, 0),
Font = new Font(Font, FontStyle.Bold),
};
headerPanel.Controls.Add(lblHeader);
// Sidebar disegnata a mano (OwnerDrawFixed) invece della selezione blu di sistema:
// stessa identità visiva del resto dell'app (UiStile.ColorePrimario) invece del colore
// di selezione standard di Windows, che varia con il tema dell'utente.
var lista = new ListBox
{
Dock = DockStyle.Left,
Width = 230,
IntegralHeight = false,
BorderStyle = BorderStyle.None,
DrawMode = DrawMode.OwnerDrawFixed,
ItemHeight = 34,
BackColor = UiStile.ColoreSfondoCard,
};
foreach (var sezione in Sezioni)
lista.Items.Add(sezione.Titolo);
lista.DrawItem += (_, e) =>
{
if (e.Index < 0)
return;
bool selezionato = (e.State & DrawItemState.Selected) != 0;
using var sfondo = new SolidBrush(selezionato ? UiStile.ColorePrimario : UiStile.ColoreSfondoCard);
e.Graphics.FillRectangle(sfondo, e.Bounds);
var colore = selezionato ? Color.White : ColoreTesto;
using var testoFont = new Font(lista.Font, selezionato ? FontStyle.Bold : FontStyle.Regular);
var rettangoloTesto = new Rectangle(e.Bounds.X + 16, e.Bounds.Y, e.Bounds.Width - 20, e.Bounds.Height);
TextRenderer.DrawText(e.Graphics, lista.Items[e.Index].ToString(), testoFont, rettangoloTesto,
colore, TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis);
};
var testo = new RichTextBox
{
Dock = DockStyle.Fill,
ReadOnly = true,
BorderStyle = BorderStyle.None,
BackColor = Color.White,
Font = new Font(Font.FontFamily, 10f),
};
lista.SelectedIndexChanged += (_, _) =>
{
if (lista.SelectedIndex >= 0)
ScriviSezione(testo, Sezioni[lista.SelectedIndex]);
};
var (barraChiudi, azioniChiudi) = UiStile.FloatBar();
var btnChiudi = new Button { Text = "Chiudi" };
UiStile.Primario(btnChiudi);
UiStile.Arrotonda(btnChiudi, 10);
btnChiudi.Click += (_, _) => Close();
azioniChiudi.Controls.Add(btnChiudi);
CancelButton = btnChiudi;
var contenuto = new Panel { Dock = DockStyle.Fill, Padding = new Padding(24, 20, 24, 20), BackColor = Color.White };
contenuto.Controls.Add(testo);
Controls.Add(contenuto);
Controls.Add(lista);
Controls.Add(barraChiudi);
Controls.Add(headerPanel);
lista.SelectedIndex = 0;
}
/// <summary>Costruisce il pannello di lettura con gerarchia tipografica esplicita: titolo
/// (blu primario, grassetto, 14pt), corpo (grigio quasi-nero, 10pt), passi numerati (numero
/// in blu grassetto, indentati) e callout finale con sfondo a tinta piatta ed etichetta in
/// grassetto — informativo (blu tenue) o di attenzione (ambra tenue). Applica la
/// formattazione DOPO aver scritto il testo, selezionando il range appena aggiunto: è il
/// solo modo affidabile di formattare i paragrafi in un RichTextBox (impostare
/// SelectionFont/Color su una selezione vuota prima di AppendText non è garantito che
/// "si attacchi" al testo che segue).</summary>
private static void ScriviSezione(RichTextBox box, Sezione sezione)
{
box.Clear();
var titoloFont = new Font(box.Font.FontFamily, 14f, FontStyle.Bold);
var corpoFont = new Font(box.Font.FontFamily, 10f, FontStyle.Regular);
var corpoGrassetto = new Font(box.Font.FontFamily, 10f, FontStyle.Bold);
void Scrivi(string testo, Font font, Color colore, int indent = 0, Color? sfondo = null)
{
int inizio = box.TextLength;
box.AppendText(testo);
box.Select(inizio, box.TextLength - inizio);
box.SelectionFont = font;
box.SelectionColor = colore;
box.SelectionBackColor = sfondo ?? box.BackColor;
box.SelectionIndent = indent;
}
Scrivi(sezione.Titolo + "\n\n", titoloFont, UiStile.ColorePrimario);
Scrivi(sezione.Intro + "\n", corpoFont, ColoreTesto);
if (sezione.Passi is { Length: > 0 })
{
Scrivi("\n", corpoFont, ColoreTesto);
for (int i = 0; i < sezione.Passi.Length; i++)
{
Scrivi($"{i + 1}. ", corpoGrassetto, UiStile.ColorePrimario, indent: 8);
Scrivi(sezione.Passi[i] + "\n\n", corpoFont, ColoreTesto, indent: 8);
}
}
if (sezione.NotaTesto is not null)
{
var sfondo = sezione.NotaAttenzione ? ColoreNotaAttenzioneSfondo : ColoreNotaInfoSfondo;
var coloreEtichetta = sezione.NotaAttenzione ? ColoreNotaAttenzioneTesto : ColoreNotaInfoTesto;
string etichetta = sezione.NotaAttenzione ? "Attenzione" : "Nota";
Scrivi("\n", corpoFont, ColoreTesto);
Scrivi(" " + etichetta + "\n", corpoGrassetto, coloreEtichetta, indent: 12, sfondo: sfondo);
Scrivi(" " + sezione.NotaTesto + " \n", corpoFont, ColoreTesto, indent: 12, sfondo: sfondo);
}
box.SelectionStart = 0;
box.SelectionLength = 0;
}
}
+1 -1
View File
@@ -11,7 +11,7 @@
<AssemblyTitle>Cevolani StepPrep</AssemblyTitle> <AssemblyTitle>Cevolani StepPrep</AssemblyTitle>
<Product>Cevolani StepPrep</Product> <Product>Cevolani StepPrep</Product>
<Company>Cevolani</Company> <Company>Cevolani</Company>
<Version>0.1.0</Version> <Version>0.9.0</Version>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract> <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
</PropertyGroup> </PropertyGroup>
+22 -3
View File
@@ -15,6 +15,14 @@ public class PrtMancantiForm : Form
ClientSize = new Size(720, 440); ClientSize = new Size(720, 440);
StartPosition = FormStartPosition.CenterParent; StartPosition = FormStartPosition.CenterParent;
Font = new Font("Segoe UI", 9.5f); Font = new Font("Segoe UI", 9.5f);
try
{
Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
}
catch (Exception ex) when (ex is IOException or ArgumentException)
{
// ponytail: nessuna icona nell'exe (es. lanciato da dotnet run) -> resta quella di default
}
var lblInfo = new Label var lblInfo = new Label
{ {
@@ -56,8 +64,17 @@ public class PrtMancantiForm : Form
grid.Rows[i].DefaultCellStyle.ForeColor = Color.DarkRed; grid.Rows[i].DefaultCellStyle.ForeColor = Color.DarkRed;
} }
var btnApriCartella = new Button { Text = "Apri cartella raw", Dock = DockStyle.Bottom, Height = 34 }; var (pannelloAzioni, flowAzioni) = UiStile.FloatBar();
UiStile.Secondario(btnApriCartella);
var btnChiudi = new Button { Text = "Chiudi" };
UiStile.Primario(btnChiudi);
UiStile.Arrotonda(btnChiudi, 10);
btnChiudi.Click += (_, _) => Close();
flowAzioni.Controls.Add(btnChiudi);
var btnApriCartella = new Button { Text = "Apri cartella raw", Margin = new Padding(8, 0, 0, 0) };
UiStile.SecondarioPieno(btnApriCartella);
UiStile.Arrotonda(btnApriCartella, 10);
btnApriCartella.Click += (_, _) => btnApriCartella.Click += (_, _) =>
{ {
try { Process.Start("explorer.exe", cartellaRaw); } try { Process.Start("explorer.exe", cartellaRaw); }
@@ -67,10 +84,12 @@ public class PrtMancantiForm : Form
MessageBoxButtons.OK, MessageBoxIcon.Error); MessageBoxButtons.OK, MessageBoxIcon.Error);
} }
}; };
flowAzioni.Controls.Add(btnApriCartella);
Controls.Add(grid); Controls.Add(grid);
Controls.Add(lblInfo); Controls.Add(lblInfo);
Controls.Add(btnApriCartella); Controls.Add(pannelloAzioni);
CancelButton = btnChiudi;
Resize += (_, _) => lblInfo.MaximumSize = new Size(ClientSize.Width - 24, 0); Resize += (_, _) => lblInfo.MaximumSize = new Size(ClientSize.Width - 24, 0);
} }
-61
View File
@@ -1,61 +0,0 @@
namespace PrepStep;
/// <summary>Mostra, per ogni assieme della distinta base, se tutti i suoi componenti
/// (ricorsivamente fino ai PRT) sono presenti nell'indice (v) o mancano (x).</summary>
public class ReportCompletezzaForm : Form
{
public ReportCompletezzaForm(List<(string Componente, string Descrizione, bool? Completo)> righe)
{
int completi = righe.Count(r => r.Completo == true);
int incompleti = righe.Count(r => r.Completo == false);
int nonTrovati = righe.Count(r => r.Completo is null);
Text = "Completezza assiemi";
ClientSize = new Size(720, 440);
StartPosition = FormStartPosition.CenterParent;
Font = new Font("Segoe UI", 9.5f);
var lblInfo = new Label
{
Dock = DockStyle.Top,
AutoSize = true,
Padding = new Padding(12, 8, 12, 8),
Text = $"{completi} completi, {incompleti} incompleti, {nonTrovati} non trovati nell'indice "
+ $"su {righe.Count} componenti. Nota: la scansione dei .asm è euristica, qualche "
+ "falso 'incompleto' è possibile.",
};
var grid = new DataGridView
{
Dock = DockStyle.Fill,
ReadOnly = true,
AllowUserToAddRows = false,
AllowUserToDeleteRows = false,
RowHeadersVisible = false,
SelectionMode = DataGridViewSelectionMode.FullRowSelect,
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill,
};
grid.Columns.Add("componente", "Componente");
grid.Columns.Add("descrizione", "Descrizione");
grid.Columns.Add("completo", "Completo");
grid.Columns["componente"]!.FillWeight = 20;
grid.Columns["descrizione"]!.FillWeight = 55;
grid.Columns["completo"]!.FillWeight = 25;
foreach (var (componente, descrizione, completo) in righe.OrderBy(r => r.Completo).ThenBy(r => r.Componente))
{
string testo = completo switch { true => "✓", false => "✗", null => "?" };
int i = grid.Rows.Add(componente, descrizione, testo);
grid.Rows[i].DefaultCellStyle.ForeColor = completo switch
{
true => Color.DarkGreen,
false => Color.DarkRed,
null => Color.Gray,
};
grid.Rows[i].Cells["completo"].Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
}
Controls.Add(grid);
Controls.Add(lblInfo);
}
}
+109 -11
View File
@@ -12,6 +12,12 @@ internal static class UiStile
/// l'RGB.</summary> /// l'RGB.</summary>
public static readonly Color ColorePrimario = Color.FromArgb(0, 99, 177); public static readonly Color ColorePrimario = Color.FromArgb(0, 99, 177);
/// <summary>Sfondo/bordo delle card di stato (es. riepilogo Database/cartella di lavoro in
/// fase 1): un grigio-azzurro appena percettibile rispetto al bianco del resto della
/// pagina, sufficiente a separare "stato attuale" da "istruzioni" senza ombre/gradienti.</summary>
public static readonly Color ColoreSfondoCard = Color.FromArgb(247, 249, 251);
public static readonly Color ColoreBordoCard = Color.FromArgb(226, 231, 236);
// AutoSize+Padding invece di una Size fissa in pixel: il bottone si dimensiona sempre // AutoSize+Padding invece di una Size fissa in pixel: il bottone si dimensiona sempre
// al proprio testo (a qualunque lunghezza/font), niente più testo che va a capo e viene // al proprio testo (a qualunque lunghezza/font), niente più testo che va a capo e viene
// tagliato in verticale quando non ci sta nella larghezza indovinata a mano. // tagliato in verticale quando non ci sta nella larghezza indovinata a mano.
@@ -48,22 +54,38 @@ internal static class UiStile
b.Cursor = Cursors.Hand; b.Cursor = Cursors.Hand;
} }
/// <summary>Arrotonda gli angoli del bottone. Si aggancia a SizeChanged invece di calcolare /// <summary>Come Secondario, ma pieno invece che a bordo: sulle card bianche delle floatbar
/// la Region una volta sola: con AutoSize la dimensione finale si conosce solo a layout /// (vedi FloatBar) il bordo sottile di Secondario è quasi invisibile su sfondo bianco, e il
/// avvenuto (dopo Text/Padding), non subito dopo la creazione del controllo.</summary> /// bottone risulterebbe visivamente diverso dal Primario pieno accanto — stessa costruzione
public static void Arrotonda(Button b, int raggio = 6) /// (nessun bordo, riempimento pieno), solo grigio chiaro invece che blu.</summary>
public static void SecondarioPieno(Button b)
{ {
b.SizeChanged += (_, _) => AggiornaRegione(b, raggio); DimensionamentoAutomatico(b);
AggiornaRegione(b, raggio); b.FlatStyle = FlatStyle.Flat;
b.FlatAppearance.BorderSize = 0;
b.FlatAppearance.MouseOverBackColor = Color.FromArgb(230, 233, 237);
b.FlatAppearance.MouseDownBackColor = Color.FromArgb(213, 217, 222);
b.BackColor = Color.FromArgb(241, 243, 245);
b.Cursor = Cursors.Hand;
} }
private static void AggiornaRegione(Button b, int raggio) /// <summary>Arrotonda gli angoli del controllo (bottone o card). Si aggancia a SizeChanged
/// invece di calcolare la Region una volta sola: con AutoSize la dimensione finale si
/// conosce solo a layout avvenuto (dopo Text/Padding), non subito dopo la creazione del
/// controllo.</summary>
public static void Arrotonda(Control c, int raggio = 6)
{ {
if (b.Width <= 0 || b.Height <= 0) c.SizeChanged += (_, _) => AggiornaRegione(c, raggio);
AggiornaRegione(c, raggio);
}
private static void AggiornaRegione(Control c, int raggio)
{
if (c.Width <= 0 || c.Height <= 0)
return; return;
int d = raggio * 2; int d = raggio * 2;
var rect = new Rectangle(Point.Empty, b.Size); var rect = new Rectangle(Point.Empty, c.Size);
using var percorso = new GraphicsPath(); using var percorso = new GraphicsPath();
percorso.AddArc(rect.X, rect.Y, d, d, 180, 90); percorso.AddArc(rect.X, rect.Y, d, d, 180, 90);
percorso.AddArc(rect.Right - d, rect.Y, d, d, 270, 90); percorso.AddArc(rect.Right - d, rect.Y, d, d, 270, 90);
@@ -71,7 +93,83 @@ internal static class UiStile
percorso.AddArc(rect.X, rect.Bottom - d, d, d, 90, 90); percorso.AddArc(rect.X, rect.Bottom - d, d, d, 90, 90);
percorso.CloseFigure(); percorso.CloseFigure();
b.Region?.Dispose(); c.Region?.Dispose();
b.Region = new Region(percorso); c.Region = new Region(percorso);
}
/// <summary>Card di stato: sfondo/bordo leggermente distinti dalla pagina, angoli arrotondati,
/// padding interno. Il bordo va disegnato a mano (Paint) perché Region ritaglia il
/// controllo ma non lascia spazio a un Border WinForms nativo sugli angoli arrotondati.</summary>
public static Panel Card()
{
var card = new Panel
{
Dock = DockStyle.Top,
AutoSize = true,
AutoSizeMode = AutoSizeMode.GrowAndShrink,
BackColor = ColoreSfondoCard,
Padding = new Padding(18, 16, 18, 16),
Margin = new Padding(0, 0, 0, 16),
};
card.Paint += (_, e) =>
{
using var penna = new Pen(ColoreBordoCard);
e.Graphics.DrawRectangle(penna, 0, 0, card.Width - 1, card.Height - 1);
};
Arrotonda(card, 8);
return card;
}
/// <summary>Barra di bottoni "flottante" (Avanti/Indietro, Apri cartella raw/Chiudi, ecc.):
/// una card bianca staccata su tutti e 4 i lati dal bordo della finestra — non un footer a
/// tutta larghezza — con angoli arrotondati e bordo sottile, sospesa sopra lo sfondo grigio
/// della pagina invece che ancorata al bordo. Lo sfondo del contenitore esterno deve
/// combaciare con quello di default della Form (nessun BackColor impostato altrove:
/// SystemColors.Control) perché il "distacco" si veda solo nel margine attorno alla card,
/// non in una fascia di colore diverso.</summary>
public static (Panel Contenitore, FlowLayoutPanel Bottoni) FloatBar()
{
// Altezza minima per non tagliare il contenuto: bottone (MinimumSize.Height = 32) +
// padding verticale della riga bottoni (12+12) + padding verticale del contenitore
// esterno (10+16) = 82, con un po' di margine in più.
var contenitore = new Panel
{
Dock = DockStyle.Bottom,
Height = 88,
BackColor = SystemColors.Control,
Padding = new Padding(16, 10, 16, 16),
};
var card = new Panel { Dock = DockStyle.Fill, BackColor = Color.White };
card.Paint += (_, e) =>
{
using var penna = new Pen(ColoreBordoCard);
e.Graphics.DrawRectangle(penna, 0, 0, card.Width - 1, card.Height - 1);
};
Arrotonda(card, 14);
var bottoni = new FlowLayoutPanel
{
Dock = DockStyle.Fill,
FlowDirection = FlowDirection.RightToLeft,
Padding = new Padding(16, 12, 16, 12),
};
card.Controls.Add(bottoni);
contenitore.Controls.Add(card);
return (contenitore, bottoni);
}
/// <summary>Forza le stesse dimensioni per due bottoni della stessa barra (es. Avanti/
/// Indietro): AutoSize li dimensiona in base al proprio testo, e con testi di lunghezza
/// diversa (o solo uno dei due in grassetto, come Primario/Secondario) risulterebbero
/// disallineati sia in larghezza che in altezza — un dettaglio che un designer non
/// lascerebbe passare. Va chiamato dopo aver impostato Text/Font su entrambi.</summary>
public static void LarghezzaUguale(params Button[] bottoni)
{
int larghezza = bottoni.Max(b => b.PreferredSize.Width);
int altezza = bottoni.Max(b => b.PreferredSize.Height);
foreach (var b in bottoni)
b.MinimumSize = new Size(larghezza, altezza);
} }
} }
+19 -13
View File
@@ -9,32 +9,38 @@ passaggio, non lo sostituisce.
## Funzionalità ## Funzionalità
1. **Indice CAD** — all'avvio carica automaticamente `cad_index.db` (se 1. **Indice CAD** — all'avvio carica automaticamente l'ultimo `cad_index.db`
presente accanto all'eseguibile). Se manca, si può selezionare un DB usato (ricordato tra un avvio e l'altro). Se manca, si può selezionare un DB
esistente con **Apri DB...** o generarne uno nuovo con **Aggiorna esistente con **Apri DB...** o generarne uno nuovo con **Aggiorna
indice**, che scansiona il disco (di default `X:\`, tutte le cartelle di primo indice**, che scansiona il disco (di default `X:\`, tutte le cartelle di primo
livello che trova) e indicizza tutti i file. In questa fase si vede/cambia anche livello che trova) e indicizza tutti i file. La **cartella di lavoro** (dove
la **cartella di lavoro**: dove finiscono il CSV e la cartella `raw/` (gli STEP finiscono il CSV e la cartella `raw/`; gli STEP esportati con Creo vanno in
esportati con Creo vanno in `step/`, dentro la stessa cartella). Proposta `step/`, dentro la stessa cartella) è proposta automaticamente e ricordata tra
automaticamente (ricordata da un avvio all'altro), modificabile con un avvio e l'altro, modificabile in qualunque momento da **File → Scegli
**Cambia cartella...**. cartella di lavoro...**.
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
anteprima in griglia. Se il file segue la convenzione di nome anteprima in griglia. Se il file segue la convenzione di nome
`DBP_<codice>_<timestamp>` del gestionale, l'assieme generale dell'estrazione `DBP_<codice>_<timestamp>` del gestionale, l'assieme generale dell'estrazione
viene aggiunto come riga in più nel CSV (il foglio elenca solo i componenti viene aggiunto come riga in più nel CSV (il foglio elenca solo i componenti
sotto l'assieme, non l'assieme stesso). sotto l'assieme, non l'assieme stesso). Il bottone **Verifica presenza**
(compare solo dopo un import riuscito) cerca i componenti nel database — è
questo passo, non il solo import, a sbloccare la fase successiva.
3. **Crea cartella raw** — cerca nell'indice il percorso CAD di ogni 3. **Crea cartella raw** — cerca nell'indice il percorso CAD di ogni
componente importato (assieme generale incluso) e copia i file trovati componente importato (assieme generale incluso) e copia i file trovati
in una cartella `raw/` dentro la cartella di lavoro. in una cartella `raw/` dentro la cartella di lavoro.
4. **Trova componenti referenziati** — legge i metadati dei file `.asm` in 4. **Trova componenti referenziati** — legge i metadati dei file `.asm` in
`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/`. compongono, copiandoli a loro volta in `raw/` (passo opzionale se la
5. **Verifica completezza assiemi** — confronta la distinta con l'indice per distinta contiene solo parti singole).
capire se manca qualche sotto-componente, e mostra i file `.asm` della 5. **Trascina su Creo** — mostra i file `.asm` della distinta già in `raw/`,
distinta trascinabili su Creo/Esplora risorse per l'esportazione STEP con trascinabili su Creo Distributed Batch per l'esportazione STEP (passo
Creo Distributed Batch. manuale, fuori da quest'app): scrive in `step/`, dentro la cartella di
lavoro.
Guida utente passo-passo completa in [`docs/guida-utente.md`](docs/guida-utente.md)
(stessa sintesi anche in-app da **Aiuto → Guida**, F1).
## Requisiti ## Requisiti
+124 -32
View File
@@ -4,7 +4,8 @@ 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, **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 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`; per la parallelizzazione della 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`. copia in `raw/` vedi `docs/copia-raw-parallela.md`.
## Vista d'insieme ## Vista d'insieme
@@ -24,6 +25,56 @@ Distinta (.xls/.xml) → CSV (componente;descrizione) → raw/ (CAD copiati
drag&drop .asm su Creo → Distributed Batch → step/ (esportazione manuale) 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 ## Entry point
- **Program.cs** — `Main()` standard WinForms: `Application.Run(new Form1())`. Nessuna logica. - **Program.cs** — `Main()` standard WinForms: `Application.Run(new Form1())`. Nessuna logica.
@@ -41,16 +92,25 @@ Distinta (.xls/.xml) → CSV (componente;descrizione) → raw/ (CAD copiati
| # | Fase | Bottone → metodo | Cosa fa | | # | 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 | | 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 → `ImportaDistinta` → `DistintaBaseImporter.ConvertiInCsv` | Xls/Xml gestionale → CSV componente;descrizione, scritto nella 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 |
| 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 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 | "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) | | 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`, Stato condiviso tra le fasi (campi privati di `Form1`): `_dbPath`, `_cartellaLavoro`,
`_percorsoInputCorrente`, `_righeCorrenti` (righe del CSV), `_ultimaVerifica` (cache percorsi `_percorsoInputCorrente`, `_righeCorrenti` (righe del CSV), `_ultimaVerifica` (cache percorsi
CAD), `_ultimaCartellaRaw`. 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: 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 seconda carica tutti i codici prt/asm noti in un colpo solo (usata sia per risolvere i
@@ -81,8 +141,7 @@ Distinta (.xls/.xml) → CSV (componente;descrizione) → raw/ (CAD copiati
## Lettura dei componenti referenziati in un `.asm` ## Lettura dei componenti referenziati in un `.asm`
`Form1.EstraiCodiciReferenziati` (usata da `RisolviAlberoComponenti`, fase 4, e da `Form1.EstraiCodiciReferenziati` (usata da `RisolviAlberoComponenti`, fase 4) prova prima
`AssiemeCompleto` dentro `VerificaCompletezzaAssiemi`, fase 5) prova prima
`AsmRecordReader.EstraiCodiciReferenziati` e ripiega su `AsmRecordReader.EstraiCodiciReferenziati` e ripiega su
`AssemblyComponentScanner.EstraiCodiciReferenziati` solo se il primo lancia `AssemblyComponentScanner.EstraiCodiciReferenziati` solo se il primo lancia
`InvalidDataException` (file che non rispetta il formato validato dal reverse engineering). `InvalidDataException` (file che non rispetta il formato validato dal reverse engineering).
@@ -90,8 +149,12 @@ Distinta (.xls/.xml) → CSV (componente;descrizione) → raw/ (CAD copiati
- **AsmRecordReader.cs** — legge esattamente la sezione `members` del binario `.asm` (id/tipo/ - **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 nome dei componenti diretti, con risoluzione del nome istanza di family table dal parametro
`ConfigName`): nessuna euristica, struttura a record. Lancia `InvalidDataException` se il `ConfigName`): nessuna euristica, struttura a record. Lancia `InvalidDataException` se il
file non ha una sezione `members` o se il conteggio dichiarato non torna. Dettagli in file non ha una sezione `members` o se il conteggio dichiarato non torna. Espone anche le
`docs/formato-asm-record.md`. Condivisa con `scripts/CreoBom.cs` via `#:project`. 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 - **AssemblyComponentScanner.cs** — estrae euristicamente i codici componente referenziati
contando le occorrenze di stringhe candidate nel testo binario. Due strategie: codici 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 numerici Pro-E riconosciuti da soli via regex (come nei nomi file), e nomi alfabetici di
@@ -101,10 +164,16 @@ Distinta (.xls/.xml) → CSV (componente;descrizione) → raw/ (CAD copiati
non copre, e la fonte di verità per `ExtractCreoStrings.cs`/`BomViaDb.cs`. 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 Quella lista è **la distinta**, e non coincide con i file che servono a Creo per aprire
l'assieme: i componenti **soppressi** ne restano fuori (non sono nella BOM) ma il loro file l'assieme: ne restano fuori due categorie, che il loro file serve comunque, altrimenti il
serve lo stesso, altrimenti il caricamento fallisce. `AsmRecordReader.EstraiCodiciSoppressi` caricamento fallisce (`docs/formato-asm-record.md` §7.3). `AsmRecordReader` le restituisce a
li restituisce a parte, e la fase 4 li copia in `raw/` senza contarli fra i "non trovati" — parte, e la fase 4 le copia in `raw/` senza contarle fra i "non trovati":
vedi `docs/formato-asm-record.md` §7.3.
- `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/` ## Quale copia di un componente si copia in `raw/`
@@ -124,8 +193,8 @@ completi".
file) nelle sole colonne Componente/Descrizione, foglio `"Distinta Base"`. Se il nome 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 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 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 passata da `Form1` (la cartella di lavoro, impostabile da "File → Scegli cartella di lavoro..."),
file di input. non necessariamente accanto al file di input.
## Finestre di dialogo (modali, mostrate a fine step) ## Finestre di dialogo (modali, mostrate a fine step)
@@ -142,9 +211,22 @@ completi".
per i secondari, blu pieno per l'azione principale di ogni fase), entrambi con 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 `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 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 larghezza scelta). `SecondarioPieno` è la variante senza bordo (sfondo grigio chiaro
`SizeChanged` (non una volta sola: con `AutoSize` la dimensione finale del bottone si pieno) per i bottoni secondari sopra le card bianche di `Card`/`FloatBar`, dove il
conosce solo a layout avvenuto), usata sui bottoni "Avanti"/"Indietro" del wizard. bordo sottile di `Secondario` è troppo poco visibile su sfondo bianco. `Arrotonda`
applica angoli arrotondati a un `Control` qualsiasi (bottone o card) via `Region`
calcolata su `SizeChanged`, non una volta sola: con `AutoSize` la dimensione finale si
conosce solo a layout avvenuto — va chiamata dopo aver fissato le dimensioni finali del
controllo (es. dopo `LarghezzaUguale`), altrimenti la `Region` vecchia resta visibile
come bordo residuo finché non arriva un repaint completo. `Card` costruisce una card di
stato (sfondo/bordo distinti, angoli arrotondati), usata in fase 1 per il riepilogo
Database. `FloatBar` costruisce 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 in
`PrtMancantiForm`. `LarghezzaUguale` forza 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 di `Primario` richiede più
altezza di quello normale di `Secondario`/`SecondarioPieno`).
- **app.ico** — icona dell'app, referenziata da `<ApplicationIcon>` nel csproj (icona - **app.ico** — icona dell'app, referenziata da `<ApplicationIcon>` nel csproj (icona
dell'exe in Esplora risorse/taskbar) e riletta esplicitamente in `Form1.BuildUi()` dell'exe in Esplora risorse/taskbar) e riletta esplicitamente in `Form1.BuildUi()`
con `Icon.ExtractAssociatedIcon(Application.ExecutablePath)` per la titlebar, senza con `Icon.ExtractAssociatedIcon(Application.ExecutablePath)` per la titlebar, senza
@@ -154,21 +236,26 @@ 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`).
- **ReportCompletezzaForm.cs** — per ogni assieme della distinta, se tutti i suoi componenti - **GuidaForm.cs** — guida interattiva ("Aiuto → Guida"/F1): lista delle 5 fasi a sinistra,
(ricorsivamente fino ai PRT) sono nell'indice (✓/✗/? per "non trovato"). testo a destra; sintesi, il dettaglio 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: cercato accanto all'eseguibile o nella working directory all'avvio; altrimenti Percorso ricordato tra un avvio e l'altro in `%AppData%/PrepStep/ultimo_db.txt`
selezionabile con "Apri DB...". Radice disco di scansione di default `X:\` (`SalvaUltimoDbUsato`/`LeggiUltimoDbUsato`), scritto ogni volta che si carica un DB
(`Form1.RadiceDiscoDefault`, hardcoded). (all'avvio se già noto, oppure da "Apri DB..." o al termine di "Aggiorna indice"); se il
- **Cartella di lavoro** — salvata in `%AppData%/PrepStep/ultima_cartella_lavoro.txt` file non c'è o punta a un percorso cancellato, `InizializzaDatabase` ripiega su
(stesso meccanismo del DB sopra), proposta di default `AppContext.BaseDirectory` la prima `cad_index.db` accanto all'eseguibile o nella working directory. Radice disco di scansione
volta; selezionabile/cambiabile con "Cambia cartella..." in fase 1. Tutti i file generati a di default `X:\` (`Form1.RadiceDiscoDefault`, hardcoded).
valle ci finiscono dentro: il CSV della distinta, `raw/` (CAD dei componenti e - **Cartella di lavoro** — stesso meccanismo del DB sopra, salvata in
sotto-componenti); `step/` non è scritta dall'app ma è dove l'utente esporta gli STEP a `%AppData%/PrepStep/ultima_cartella_lavoro.txt`; se non c'è una preferenza salvata (o punta
mano con Creo Distributed Batch, a partire dai file trascinati da `raw/` in fase 5. 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/`) ## Script standalone (`scripts/`)
@@ -198,6 +285,11 @@ dipendenze extra con `#:package Nome@versione` in testa al file):
- **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 + `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`, come API pubblica. File: `IndexerParseFilenameTests.cs`, `IndexerScanTests.cs`,
`AssemblyComponentScannerTests.cs`, `DistintaBaseImporterTests.cs`, `AssemblyComponentScannerTests.cs`, `AsmRecordReaderTests.cs`,
`CaseInsensitiveLookupIntegrationTests.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.
+35 -5
View File
@@ -495,12 +495,12 @@ seconda è più lunga.
| Lista | Serve per | Si ottiene con | | Lista | Serve per | Si ottiene con |
| --- | --- | --- | | --- | --- | --- |
| Componenti della distinta | BOM, verifica completezza | `LeggiMembri` / `EstraiCodiciReferenziati` (sezione `members`) | | Componenti della distinta | BOM, verifica completezza | `LeggiMembri` / `EstraiCodiciReferenziati` (sezione `members`) |
| File necessari al caricamento | popolare `raw/` per l'export STEP | i precedenti **più** `EstraiCodiciSoppressi` | | File necessari al caricamento | popolare `raw/` per l'export STEP | i precedenti **più** `EstraiCodiciSoppressi` e `EstraiCodiciRiferimentiEsterni` (§7.5) |
La differenza sono i **componenti soppressi** (§7.1): fuori dalla BOM, ma senza il loro file La differenza sono i **componenti soppressi** (§7.1) e i **riferimenti esterni** (§7.5): fuori
Creo non apre l'assieme. Chi popola `raw/` deve quindi usare l'unione; chi genera la BOM o dalla BOM, ma senza il loro file Creo non apre l'assieme. Chi popola `raw/` deve quindi usare
verifica la completezza deve usare **solo** i membri, altrimenti i soppressi diventano falsi l'unione; chi genera la BOM o verifica la completezza deve usare **solo** i membri, altrimenti
"componenti mancanti". quelle voci diventano falsi "componenti mancanti".
Il sintomo, se si sbaglia, non assomiglia per niente alla causa: Creo Distributed Batch Il sintomo, se si sbaglia, non assomiglia per niente alla causa: Creo Distributed Batch
fallisce con `Carica modello in Creo Pro | Non riuscito | [-4] Errore: Dati di input non fallisce con `Carica modello in Creo Pro | Non riuscito | [-4] Errore: Dati di input non
@@ -539,6 +539,36 @@ Conseguenze per il codice:
versione, una del 2011 in una cartella di file sciolti e una del 2026 accanto all'assieme, versione, una del 2011 in una cartella di file sciolti e una del 2026 accanto all'assieme,
e in `raw/` finiva quella del 2011. e in `raw/` finiva quella del 2011.
### 7.5 Riferimenti esterni: la tabella dei modelli citati
Terzo modo in cui un `.asm` dipende da un file che non è fra i suoi membri, dopo i soppressi
(§7.1) e le copie ambigue (§7.4). Un assieme che porta **assembly feature** o **datum copiati**
da un altro modello lo cita in una tabella a parte, che non è `members` e non lascia una stringa
`Componente ID …`: i due lettori esistenti non la vedevano.
Struttura: un array aperto da `\xf2\xf7\x05` e chiuso da `\xf3`, con elementi
`\xe3\xe2<tipo><nome>\x00`. Il nome può portare il suffisso `#AF<n>` di §4.0.1 e/o la notazione
`istanza<generico>` di §4.0.2 - il file su disco è il generico:
```
f2 f7 05 e3 e2 01 "9255708017#AF2<9255708017>" 00 ... -> 9255708017
e3 e2 02 "9255700181" 00 ... -> 9255700181
f3
```
Va letta con `EstraiCodiciRiferimentiEsterni`, e - a differenza dei soppressi - **si ricorre
dentro**: Creo carica il modello citato con tutti i suoi componenti, che quindi servono anch'essi
in `raw/`. La tabella contiene anche nomi di *feature* (`MIRROR3`, `CHAMFER1`,
`M10_TAPPED_HOLE1`), indistinguibili dai modelli guardando il record: non è un problema perché
chi la usa cerca ogni nome nell'indice e ignora quelli che non ci sono - per lo stesso motivo
questi codici non devono finire fra i "non trovati".
Il caso reale: `9255728001` falliva in Distributed Batch con lo stesso `[-4] Dati di input non
completi` di §7.3 perché il suo sottoassieme `9255728001_200` referenzia per assembly feature
`9255708017` e `9255708018` (e con loro `9255700181/182/183`), e monta una vite
`V6-12--_-_U5933_C<UNI5933>` il cui generico `uni5933.prt` non era in `raw/`. Nessuno dei sei
file compariva in `members` né fra i piazzamenti.
## 8. Uso ## 8. Uso
```powershell ```powershell
+125
View File
@@ -0,0 +1,125 @@
# Guida utente
Questa guida descrive, in ordine, tutti i passi da fare in **Cevolani StepPrep** 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
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**
(F1).
## Prima di iniziare
- Serve accesso alla share di rete con i file CAD (di default `X:\`).
- Serve l'estrazione "Distinta Base" dal gestionale (Fata): il file `.xls` così come
Fata lo salva, con le colonne Componente/Descrizione (foglio `"Distinta Base"`) — va
lasciato nel formato/estensione in cui viene esportato, senza risalvarlo o convertirlo
in altri formati prima di usarlo nell'app. Sotto l'estensione `.xls` il contenuto può
essere XML (SpreadsheetML) o Excel binario vero: l'app riconosce da sola quale dei due
è, non serve fare nulla di diverso a seconda del caso.
- 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
nulla di diverso.
- Conviene definire la **cartella di lavoro** giusta (fase 1, o "File → Scegli cartella
di lavoro...") prima di importare la 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 **pulire
`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
"File → Nuovo" descritto più sotto azzera solo lo stato di CSV/griglie dell'app, non
tocca i file già copiati su disco).
## 1. Database
Alla prima apertura, se un database (`cad_index.db`) è già stato creato in precedenza,
l'app lo ricarica da sola: si vede il pallino verde accanto a "Database", il percorso del
file e la data dell'ultimo aggiornamento.
- **Se il pallino è rosso** (nessun database trovato): premi **Aggiorna indice** per
scansionare la share (`X:\`) e crearne uno nuovo — l'operazione può richiedere qualche
minuto la prima volta, i giri successivi sono più veloci perché aggiornano solo quello
che è cambiato.
- **Se hai già un database da un'altra postazione o cartella**: premi **Apri DB...** e
selezionalo a mano.
- La **cartella di lavoro** (dove l'app scrive il CSV della distinta e la cartella
`raw/`) è scelta automaticamente e ricordata tra un avvio e l'altro; per cambiarla usa
**File → Scegli cartella di lavoro...**.
Premi **Avanti** quando il database è caricato.
## 2. Importa Distinta Base
- **1. Carica la distinta**: trascina il file `.xls` esportato da Fata nella finestra,
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
di lavoro.
- **2. Verifica la presenza dei file sul server**: compare solo dopo un import riuscito.
Premila per cercare ogni componente nel database — le righe della griglia si colorano
di verde (trovato) o rosso (non trovato nell'indice).
**Avanti** si sblocca solo dopo aver premuto "Verifica la presenza dei file sul server",
non basta il solo import: è il modo per accorgersi subito se il database è disallineato
rispetto alla share (es. indice non aggiornato) prima di andare avanti.
## 3. Crea cartella raw
Premi **Crea cartella raw**: per ogni componente della distinta trovato nell'indice,
l'app ne copia il file CAD dalla share nella cartella `raw/` (dentro la cartella di
lavoro). La griglia mostra il percorso CAD copiato per ogni componente.
## 4. Trova componenti referenziati
Gli assiemi (`.asm`) referenziano altri file (sotto-assiemi, parti, componenti soppressi,
riferimenti esterni) che Creo deve poter caricare insieme all'assieme principale, anche
se non compaiono nella distinta del gestionale. Premi **Trova componenti referenziati**:
l'app legge i file `.asm` già in `raw/`, risolve ricorsivamente tutto l'albero e copia
anche quei file in `raw/`.
Si apre una finestra con l'elenco di quanto trovato e copiato (PRT e sotto-assiemi), con
in fondo eventuali codici referenziati che non corrispondono a nessun file indicizzato
(possono essere disegni, componenti davvero mancanti, o falsi positivi della scansione —
non bloccano il lavoro, ma vale la pena controllarli). Da qui puoi anche aprire
direttamente la cartella `raw/` con **Apri cartella raw**.
Questo passo è opzionale se la distinta contiene solo parti singole senza sotto-assiemi:
in quel caso si può passare direttamente alla fase successiva.
## 5. Trascina su Creo Distributed Batch
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:
1. Apri **Creo Distributed Batch** (dal menu Start di Windows, o dal file
`ptcdbatch.bat` nella cartella `bin` dell'installazione Creo).
2. Crea un nuovo gruppo di attività per l'esportazione STEP (menu **Actions → New Task
Group**), scegliendo il tipo di attività per l'export STEP — se in azienda è già
presente un profilo/gruppo predefinito per l'export STEP, riusa quello invece di
crearne uno nuovo.
3. Aggiungi i file: trascina i `.asm` dalla finestra dell'app (o direttamente da Esplora
risorse, dalla cartella `raw/`) sul gruppo di attività, oppure sfoglia e selezionali a
mano da Distributed Batch.
4. Verifica che il gruppo di attività punti al `config.pro`/profilo di export STEP giusto
(sezione **Configuration Files** del gruppo) — di norma è già impostato una volta sola
per tutti, non richiede modifiche a ogni giro.
5. Avvia l'elaborazione (**Start**/**Run**): Distributed Batch legge ogni `.asm`, con
tutto l'albero di sotto-componenti già raccolto in `raw/` nelle fasi 3-4, ed esporta i
file STEP nella cartella `step/` (dentro la cartella di lavoro, accanto a `raw/`).
I nomi esatti dei menu possono variare leggermente a seconda della versione di Creo e di
come Distributed Batch è già configurato in azienda — questi passi descrivono il flusso
generale (fonte: [documentazione e community PTC](https://www.ptc.com/en/support/article/CS28241));
in caso di dubbio su profili/config già pronti, chiedi a chi ha impostato Distributed
Batch la prima volta.
**Se Distributed Batch fallisce con `[-4] Dati di input non completi`**, quasi sempre
manca un file nell'albero dei componenti — Creo non dice quale. Torna alla fase 4 e
controlla l'elenco dei "non trovati": è il punto di partenza per capire cosa recuperare
prima di riprovare.
## Ripartire con una nuova distinta
Quando devi processare un'altra distinta, usa **File → Nuovo** (Ctrl+N): azzera CSV, raw
e verifica delle fasi 2-5 e riporta al passo 2, senza dover ricaricare il database o
la cartella di lavoro (restano quelli già impostati in fase 1).
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...**.