From 0966e6623209db81cfcc73031e7d5ac71213cf1a Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Mon, 21 Sep 2026 09:54:06 +0200 Subject: [PATCH] Add drag&drop .asm preview and force CSV output to repo root Fase 5 now shows the distinta's .asm files already copied to raw/ as draggable icons, so they can be dropped straight onto Creo or Explorer without re-hitting the DB/share. The CSV from fase 2 is now always written to the repo root (found by walking up to CLAUDE.md) instead of next to the input file, wherever that happens to be. Co-Authored-By: Claude Sonnet 5 --- CLAUDE.md | 24 +++++--- CadSearchGui/DistintaBaseImporter.cs | 5 +- CadSearchGui/Form1.cs | 83 +++++++++++++++++++++++++++- 3 files changed, 100 insertions(+), 12 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c9c721e..f605ee1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,20 +51,28 @@ entrambi rimossi: ora l'unico programma è questo, con GUI e indicizzazione inte (`DBP__`), l'assieme generale viene aggiunto come riga in più nel CSV stesso (il foglio elenca solo i componenti sotto l'assieme, non l'assieme in sé). Da qui in avanti l'assieme generale è un componente come tutti gli altri, nessun - caso speciale nel resto del codice. + caso speciale nel resto del codice. Il CSV viene sempre scritto nella root del repo + (`Form1.TrovaRadiceRepo`, che risale da `AppContext.BaseDirectory` fino alla cartella + che contiene `CLAUDE.md`), indipendentemente da dove si trova il file `.xls`/`.xml` + di input scelto dall'utente. 3. **Crea cartella raw** — cerca nell'indice il percorso CAD di ogni componente del CSV (`TrovaPercorsiCad`) e lo copia in `raw/` (`CopiaFileTrovati`). 4. **Trova componenti referenziati** — legge euristicamente i metadati binari dei `.asm` già in `raw/` (`AssemblyComponentScanner`) per risolvere ricorsivamente i sotto-assiemi/parti che contengono (`RisolviAlberoComponenti`), copiandoli anch'essi in `raw/`. - 5. **Verifica completezza e STEP** — "Verifica completezza assiemi" (ricorsivo sui - percorsi originali in indice, non su `raw/`) e "Crea cartella step_finali": solo per i - codici della distinta importata dal CSV (non i sotto-componenti di fase 4, che servono - solo a completare `raw/`) cerca in `step/` il file `.stp` e lo copia in - `step_finali/` se lo trova. Per i mancanti distingue, incrociando l'indice CAD, - `EsitoStep.StepMancante` (il CAD c'è, manca solo l'esportazione STEP) da - `ComponenteNonTrovato` (il componente non è nemmeno nell'indice) — vedi `ReportStepForm`. + 5. **Verifica completezza e STEP** — appena si entra nella fase, `AggiornaAnteprimaAsm` + mostra in una `ListView` a icone i file `.asm` della distinta importata (esclusi i + sotto-assiemi di fase 4) già presenti in `raw/` — letti dal filesystem locale, non + dall'indice/share — trascinabili (drag&drop, `ItemDrag` → `DoDragDrop` con + `DataFormats.FileDrop`) direttamente su Creo o Esplora risorse. "Verifica completezza + assiemi" (ricorsivo sui percorsi originali in indice, non su `raw/`) e "Crea cartella + step_finali": solo per i codici della distinta importata dal CSV (non i + sotto-componenti di fase 4, che servono solo a completare `raw/`) cerca in `step/` il + file `.stp` e lo copia in `step_finali/` se lo trova. Per i mancanti distingue, + incrociando l'indice CAD, `EsitoStep.StepMancante` (il CAD c'è, manca solo + l'esportazione STEP) da `ComponenteNonTrovato` (il componente non è nemmeno + nell'indice) — vedi `ReportStepForm`. - **Indexer.cs** — logica di scansione del disco e scrittura/aggiornamento del DB SQLite (`Indexer.ScanAsync`). Cammina tutte le cartelle di primo livello trovate sotto la radice (scoperte dinamicamente con `Directory.GetDirectories`, non un elenco fisso) diff --git a/CadSearchGui/DistintaBaseImporter.cs b/CadSearchGui/DistintaBaseImporter.cs index 9d79374..1d90380 100644 --- a/CadSearchGui/DistintaBaseImporter.cs +++ b/CadSearchGui/DistintaBaseImporter.cs @@ -25,7 +25,8 @@ static class DistintaBaseImporter Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); } - public static (string PercorsoCsv, List<(string Componente, string Descrizione)> Righe) ConvertiInCsv(string percorsoInput) + public static (string PercorsoCsv, List<(string Componente, string Descrizione)> Righe) ConvertiInCsv( + string percorsoInput, string? cartellaOutput = null) { var righeGrezze = EImmagineSpreadsheetMl(percorsoInput) ? LeggiRigheSpreadsheetMl(percorsoInput) @@ -44,7 +45,7 @@ static class DistintaBaseImporter } var percorsoCsv = Path.Combine( - Path.GetDirectoryName(Path.GetFullPath(percorsoInput))!, + cartellaOutput ?? Path.GetDirectoryName(Path.GetFullPath(percorsoInput))!, Path.GetFileNameWithoutExtension(percorsoInput) + ".csv"); using var writer = new StreamWriter(percorsoCsv, false, Encoding.UTF8); diff --git a/CadSearchGui/Form1.cs b/CadSearchGui/Form1.cs index ec9ac67..7952ef9 100644 --- a/CadSearchGui/Form1.cs +++ b/CadSearchGui/Form1.cs @@ -12,6 +12,7 @@ public partial class Form1 : Form private readonly Button _btnReport = 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(); private readonly Label _lblDb = new(); @@ -41,6 +42,19 @@ public partial class Form1 : Form private static readonly string[] TipiPredefiniti = { "prt", "asm" }; private const string RadiceDiscoDefault = @"X:\"; + /// Risale da AppContext.BaseDirectory (variabile tra Debug/Release/publish) fino + /// alla cartella che contiene CLAUDE.md, cioè la root del repo; se non la trova (es. exe + /// pubblicato e spostato altrove) ripiega su AppContext.BaseDirectory. + private static string TrovaRadiceRepo() + { + for (var dir = new DirectoryInfo(AppContext.BaseDirectory); dir is not null; dir = dir.Parent) + { + if (File.Exists(Path.Combine(dir.FullName, "CLAUDE.md"))) + return dir.FullName; + } + return AppContext.BaseDirectory; + } + private string? _dbPath; private string? _percorsoInputCorrente; private List<(string Componente, string Descrizione)>? _righeCorrenti; @@ -149,6 +163,47 @@ public partial class Form1 : Form _btnIndietro.Visible = _faseAttuale > 0; _btnAvanti.Visible = _faseAttuale < NomiFasi.Length - 1; AggiornaStatoAvanti(); + + if (_faseAttuale == 4) + AggiornaAnteprimaAsm(); + } + + /// Popola le icone trascinabili (drag&drop) dei file .asm della distinta importata + /// (non i sotto-assiemi di fase 4, che in "raw" servono solo a completarla) già copiati in + /// "raw" (non riletti dal DB/share: sono già in locale), mostrate in fase 5 prima ancora di + /// avviare la verifica. + private void AggiornaAnteprimaAsm() + { + _lstAnteprimaAsm.Items.Clear(); + _lstAnteprimaAsm.LargeImageList!.Images.Clear(); + if (_ultimaCartellaRaw is null || !Directory.Exists(_ultimaCartellaRaw) || _righeCorrenti is null) + return; + + var codiciDistinta = _righeCorrenti.Select(r => r.Componente).ToHashSet(StringComparer.OrdinalIgnoreCase); + var percorsiAsm = Directory.GetFiles(_ultimaCartellaRaw) + .Where(p => + { + var (codice, tipo, _) = Indexer.ParseFilename(Path.GetFileName(p)); + return tipo.Equals("asm", StringComparison.OrdinalIgnoreCase) && codiciDistinta.Contains(codice); + }); + + foreach (var percorso in percorsiAsm) + { + Icon? icona = null; + try + { + icona = Icon.ExtractAssociatedIcon(percorso); + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or ArgumentException) + { + // ponytail: file irraggiungibile (share offline) -> icona generica, non blocca gli altri + } + + int indice = _lstAnteprimaAsm.LargeImageList.Images.Count; + _lstAnteprimaAsm.LargeImageList.Images.Add(icona?.ToBitmap() ?? SystemIcons.WinLogo.ToBitmap()); + icona?.Dispose(); + _lstAnteprimaAsm.Items.Add(new ListViewItem(Path.GetFileName(percorso), indice) { Tag = percorso }); + } } /// Condizione per cui la fase indicata è "conclusa" e si può passare alla successiva. @@ -423,10 +478,34 @@ public partial class Form1 : Form _btnCompletezza.Text = "Verifica completezza assiemi"; _btnCompletezza.Size = new Size(220, 32); - _btnCompletezza.Margin = new Padding(0, 0, 0, 20); + _btnCompletezza.Margin = new Padding(0, 0, 0, 8); _btnCompletezza.Click += (_, _) => VerificaCompletezzaAssiemi(); contenuto.Controls.Add(_btnCompletezza); + var lblAnteprimaAsm = new Label + { + AutoSize = true, + ForeColor = SystemColors.GrayText, + Margin = new Padding(0, 0, 0, 4), + Text = "File .asm della distinta trovati nell'indice (trascinali su Creo/Esplora risorse):", + }; + contenuto.Controls.Add(lblAnteprimaAsm); + + _lstAnteprimaAsm.View = View.LargeIcon; + _lstAnteprimaAsm.MultiSelect = true; + _lstAnteprimaAsm.LargeImageList = new ImageList { ImageSize = new Size(32, 32) }; + _lstAnteprimaAsm.Size = new Size(680, 160); + _lstAnteprimaAsm.Margin = new Padding(0, 0, 0, 20); + _lstAnteprimaAsm.ItemDrag += (_, e) => + { + var percorsi = _lstAnteprimaAsm.SelectedItems.Cast() + .Select(item => (string)item.Tag!) + .ToArray(); + if (percorsi.Length > 0) + _lstAnteprimaAsm.DoDragDrop(new DataObject(DataFormats.FileDrop, percorsi), DragDropEffects.Copy); + }; + contenuto.Controls.Add(_lstAnteprimaAsm); + var lblIstruzioniStep = new Label { AutoSize = true, @@ -463,7 +542,7 @@ public partial class Form1 : Form { try { - var (csv, righe) = DistintaBaseImporter.ConvertiInCsv(percorso); + var (csv, righe) = DistintaBaseImporter.ConvertiInCsv(percorso, TrovaRadiceRepo()); _percorsoInputCorrente = percorso; _righeCorrenti = righe;