diff --git a/CLAUDE.md b/CLAUDE.md index 517fc18..5d808f5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -55,6 +55,7 @@ Dove scrivere cosa: | [`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 `` 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 @@ -100,10 +101,11 @@ designer visuale): (`ScegliPerCartella`), perché il `.asm` non registra quale usa e sbagliarla impedisce a Creo di aprire l'assieme. 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 - completezza assiemi" controlla ricorsivamente sull'indice (non su `raw/`, stessa - `AsmRecordReader`/fallback della fase 4) se tutti i sotto-componenti di ogni assieme sono - presenti. + Creo per l'esportazione STEP (Distributed Batch, manuale, fuori dall'app). + +Menu in cima alla finestra: "File → Nuova estrazione" (Ctrl+N) azzera lo stato delle fasi 2-5 +per ripartire con una nuova distinta, senza toccare DB/cartella di lavoro della fase 1; +"Aiuto → Guida" (F1) apre una sintesi interattiva delle 5 fasi (`GuidaForm.cs`). ## Script standalone (`scripts/`) diff --git a/PrepStep/Form1.cs b/PrepStep/Form1.cs index 614a1a5..1e22d19 100644 --- a/PrepStep/Form1.cs +++ b/PrepStep/Form1.cs @@ -10,7 +10,6 @@ public partial class Form1 : Form private readonly Button _btnCreaRaw = new(); private readonly Button _btnTrovaPrt = new(); private readonly Button _btnVerifica = new(); - private readonly Button _btnCompletezza = new(); private readonly ListView _lstAnteprimaAsm = new(); private readonly Button _btnPulisci = new(); private readonly Label _lblStatus = new(); @@ -22,6 +21,8 @@ public partial class Form1 : Form private readonly DataGridView _gridRaw = new(); private readonly Label _lblImportStatus = new(); private readonly ToolTip _tipImportStatus = new(); + private readonly MenuStrip _menu = new(); + private readonly ToolStripMenuItem _mnuNuovaEstrazione = new("Nuova estrazione"); // Wizard: una fase alla volta occupa tutta la finestra, si avanza solo quando quella // corrente è "pronta" (vedi AvantiConsentito). NomiFasi/_panelFasi sono paralleli per indice. @@ -146,10 +147,25 @@ public partial class Form1 : Form // 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 // 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); + + 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(statusPanel); Controls.Add(navContainer); Controls.Add(headerPanel); + Controls.Add(_menu); DragEnter += (_, e) => e.Effect = e.Data?.GetDataPresent(DataFormats.FileDrop) == true @@ -236,6 +252,30 @@ public partial class Form1 : Form private void AggiornaStatoAvanti() => _btnAvanti.Enabled = AvantiConsentito(_faseAttuale); + /// 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. + private void NuovaEstrazione() + { + if (_dbPath is null || _cartellaLavoro is null) + { + MessageBox.Show(this, "Carica prima il database in fase 1.", "Nuova estrazione", + 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); + + VaiAllaFase(1); + } + private Panel BuildFaseDatabase() { var contenuto = new TableLayoutPanel @@ -516,27 +556,6 @@ public partial class Form1 : Form }; 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 { AutoSize = true, @@ -1007,102 +1026,6 @@ public partial class Form1 : Form dialog.ShowDialog(this); } - /// 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. - 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>(); - - 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(); - daVisitare.Enqueue(percorsoAsmIniziale); - var fileVisitati = new HashSet { percorsoAsmIniziale }; - var codiceIniziale = Path.GetFileName(percorsoAsmIniziale).Split('.')[0]; - var codiciVisti = new HashSet - { - 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); - } - /// Copia i file trovati nella cartella "raw" indicata, creandola se necessario. /// 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 diff --git a/PrepStep/GuidaForm.cs b/PrepStep/GuidaForm.cs new file mode 100644 index 0000000..7655f1d --- /dev/null +++ b/PrepStep/GuidaForm.cs @@ -0,0 +1,91 @@ +namespace PrepStep; + +/// Guida interattiva richiamabile da "Aiuto ▸ Guida" (F1): sintesi delle 5 fasi del +/// wizard, per chi apre l'app senza aver letto i docs. Il dettaglio completo (formato .asm, +/// schema DB, ecc.) resta solo in docs/architettura.md e affini, per non duplicarlo qui. +public class GuidaForm : Form +{ + private static readonly (string Titolo, string Testo)[] Sezioni = + { + ("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". + + "File ▸ Nuova estrazione" (Ctrl+N) azzera distinta/raw/verifica per + ripartire con una nuova distinta base, senza dover ricaricare il database. + """), + ("1. Database", """ + Carica o aggiorna l'indice "cad_index.db" a partire dalle cartelle di primo + livello del disco/share (radice di default X:\). Qui si sceglie anche la + cartella di lavoro, dove finiscono CSV, "raw" e "step". + """), + ("2. Importa Distinta Base", """ + Converte il file della distinta (trascinabile nella finestra) in un CSV + componente;descrizione nella cartella di lavoro. Se il file segue la + convenzione del gestionale, aggiunge anche l'assieme generale come riga. + """), + ("3. Crea cartella raw", """ + Cerca nell'indice i file CAD dei componenti del CSV e li copia nella + sottocartella "raw" della cartella di lavoro. + """), + ("4. Trova componenti referenziati", """ + Legge i file .asm già in "raw" per risolvere ricorsivamente sotto-assiemi e + parti citate, copiando anche quelli in "raw": componenti soppressi (non in + distinta ma necessari per aprire l'assieme) e riferimenti esterni (assembly + feature, datum copiati) inclusi. + """), + ("5. Verifica completezza", """ + Mostra i file .asm della distinta già in "raw", trascinabili su Creo per + l'esportazione STEP (passo manuale, fuori da quest'app). "Verifica + completezza assiemi" controlla che tutti i sotto-componenti risultino + presenti nell'indice. + """), + }; + + public GuidaForm() + { + Text = "Guida"; + ClientSize = new Size(640, 420); + MinimumSize = new Size(480, 320); + StartPosition = FormStartPosition.CenterParent; + Font = new Font("Segoe UI", 9.5f); + + var lista = new ListBox + { + Dock = DockStyle.Left, + Width = 200, + IntegralHeight = false, + BorderStyle = BorderStyle.None, + }; + foreach (var (titolo, _) in Sezioni) + lista.Items.Add(titolo); + + 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) + testo.Text = Sezioni[lista.SelectedIndex].Testo.Trim(); + }; + + var btnChiudi = new Button { Text = "Chiudi", Dock = DockStyle.Bottom, Height = 34 }; + UiStile.Secondario(btnChiudi); + btnChiudi.Click += (_, _) => Close(); + + var contenuto = new Panel { Dock = DockStyle.Fill, Padding = new Padding(16) }; + contenuto.Controls.Add(testo); + Controls.Add(contenuto); + Controls.Add(lista); + Controls.Add(btnChiudi); + + lista.SelectedIndex = 0; + } +} diff --git a/PrepStep/ReportCompletezzaForm.cs b/PrepStep/ReportCompletezzaForm.cs deleted file mode 100644 index 05a3bde..0000000 --- a/PrepStep/ReportCompletezzaForm.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace PrepStep; - -/// Mostra, per ogni assieme della distinta base, se tutti i suoi componenti -/// (ricorsivamente fino ai PRT) sono presenti nell'indice (v) o mancano (x). -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); - } -} diff --git a/docs/architettura.md b/docs/architettura.md index 4e04b41..92dbd75 100644 --- a/docs/architettura.md +++ b/docs/architettura.md @@ -72,10 +72,8 @@ sbaglia, Creo Distributed Batch non dice quale file manca — dice solo `[-4] Da completi`. **5. Verifica e consegna a Creo.** `AggiornaAnteprimaAsm` mostra i `.asm` della distinta già -in `raw/` come icone trascinabili; "Verifica completezza assiemi" ricontrolla ricorsivamente -**sull'indice** (non su `raw/`) che ogni sotto-componente esista. L'esportazione STEP vera è -manuale e fuori dall'app: si trascinano i `.asm` su Creo Distributed Batch, che scrive in -`step/`. +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 @@ -98,12 +96,17 @@ manuale e fuori dall'app: si trascinano i `.asm` su Creo Distributed Batch, che | 2 | Importa Distinta Base | selezione file → `ImportaDistinta` → `DistintaBaseImporter.ConvertiInCsv` | Xls/Xml gestionale → CSV componente;descrizione, scritto nella cartella di lavoro | | 3 | Crea cartella raw | "Crea cartella raw" → `CreaCartellaRaw` | `TrovaPercorsiCad` + `CopiaFileTrovati` (parallela) → `raw/` dentro la cartella di lavoro | | 4 | Trova componenti referenziati | "Trova componenti referenziati" → `TrovaPrtReferenziati` → `RisolviAlberoComponenti` | Legge i `.asm` in `raw/`, risolve ricorsivamente sotto-assiemi/parti nell'indice e li copia anch'essi in `raw/`, insieme ai componenti soppressi e ai modelli citati per riferimento esterno che servono a Creo per aprire l'assieme | - | 5 | Verifica completezza | "Verifica completezza assiemi" → `VerificaCompletezzaAssiemi` | Completezza ricorsiva sui percorsi in indice. All'ingresso nella fase, `AggiornaAnteprimaAsm` mostra le icone trascinabili dei `.asm` della distinta già in `raw/`, da trascinare su Creo per l'esportazione STEP (Distributed Batch) | + | 5 | Verifica completezza | (ingresso in fase) → `AggiornaAnteprimaAsm` | Mostra le icone trascinabili dei `.asm` della distinta già in `raw/`, da trascinare su Creo per l'esportazione STEP (Distributed Batch) | Stato condiviso tra le fasi (campi privati di `Form1`): `_dbPath`, `_cartellaLavoro`, `_percorsoInputCorrente`, `_righeCorrenti` (righe del CSV), `_ultimaVerifica` (cache percorsi CAD), `_ultimaCartellaRaw`. + Menu (`MenuStrip`, in cima alla finestra): "File → Nuova estrazione" (Ctrl+N) azzera lo + stato delle fasi 2-5 (`NuovaEstrazione`) per ripartire con una nuova distinta senza + ricaricare il database/la cartella di lavoro (fase 1, che resta invariata); "Aiuto → Guida" + (F1) apre `GuidaForm`, sintesi interattiva delle 5 fasi per chi non ha letto questi docs. + Altre responsabilità in Form1.cs: - `TrovaPercorsiCad` / `CaricaCodiciNoti` — query sul DB (`files_ultima_versione`), la seconda carica tutti i codici prt/asm noti in un colpo solo (usata sia per risolvere i @@ -134,8 +137,7 @@ manuale e fuori dall'app: si trascinano i `.asm` su Creo Distributed Batch, che ## Lettura dei componenti referenziati in un `.asm` -`Form1.EstraiCodiciReferenziati` (usata da `RisolviAlberoComponenti`, fase 4, e da -`AssiemeCompleto` dentro `VerificaCompletezzaAssiemi`, fase 5) prova prima +`Form1.EstraiCodiciReferenziati` (usata da `RisolviAlberoComponenti`, fase 4) prova prima `AsmRecordReader.EstraiCodiciReferenziati` e ripiega su `AssemblyComponentScanner.EstraiCodiciReferenziati` solo se il primo lancia `InvalidDataException` (file che non rispetta il formato validato dal reverse engineering). @@ -169,9 +171,6 @@ parte, e la fase 4 le copia in `raw/` senza contarle fra i "non trovati": feature, datum copiati), elencati in una tabella a parte del binario (§7.5). Qui si ricorre dentro, perché Creo carica il modello citato con tutto il suo albero. -La fase 5 (`VerificaCompletezzaAssiemi`) usa invece **solo** i membri: è una domanda sulla -distinta, e contare le altre due liste produrrebbe falsi "componenti mancanti". - ## Quale copia di un componente si copia in `raw/` Lo stesso codice esiste spesso in più cartelle della share, con contenuto diverso e pari @@ -220,8 +219,8 @@ completi". sotto-assiemi trovati e copiati in `raw/`, più i codici referenziati che non corrispondono a nessun file indicizzato (possono essere disegni, componenti davvero mancanti, o falsi positivi della scansione `.asm`). -- **ReportCompletezzaForm.cs** — per ogni assieme della distinta, se tutti i suoi componenti - (ricorsivamente fino ai PRT) sono nell'indice (✓/✗/? per "non trovato"). +- **GuidaForm.cs** — guida interattiva ("Aiuto → Guida"/F1): lista delle 5 fasi a sinistra, + testo a destra; sintesi, il dettaglio resta solo in questi docs. ## Persistenza