Compare commits

6 Commits
Author SHA1 Message Date
davideandClaude Sonnet 5 379deec986 Remove the step_finali copy/report feature
Not needed anymore: the app no longer copies matched .stp files from
step/ into a separate step_finali/ folder, or reports which distinta
codes are still missing a STEP. Removed the "Crea cartella step_finali"
button, GeneraReportStep, and ReportStepForm/EsitoStep. Phase 5 keeps
"Verifica completezza assiemi" and the drag&drop .asm list (still how
files get sent to Creo Distributed Batch); renamed from "Verifica
completezza e STEP" to "Verifica completezza" since it's no longer about
STEP output. PulisciRawEStep now only clears raw/ and step/.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 11:23:46 +02:00
davideandClaude Sonnet 5 13798ad47c Add explicit working folder; rename project from CadSearchGui to PrepStep
Working folder (phase 1): CSV/raw/step/step_finali all used to live split
across two different implicit locations (CSV in the "repo root" found by
walking up for CLAUDE.md, raw/step next to the input distinta file) with
neither shown to the user. That repo-root lookup also silently breaks once
the app runs as a standalone published exe on a machine with no repo/
CLAUDE.md at all. Replaced both with one explicit "cartella di lavoro",
auto-suggested (last used, remembered the same way as the DB path) and
changeable from phase 1, with no dependency on any repo marker file.

Rename: CadSearchGui was the working name from before the app had a
product identity; renamed folders, csproj files, namespace and the
AppData settings folder to PrepStep throughout, matching the exe name
already in use. Product-facing name stays "Cevolani StepPrep".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 11:18:43 +02:00
davideandClaude Sonnet 5 6726dd2216 Add app icon, product name/version, and document them
- New app.ico (from the Cevolani favicon, background made transparent and
  cropped tighter to the logo so it stays legible at 16/24px), wired up via
  ApplicationIcon in the csproj and re-read at runtime for the window icon.
- Named the product "Cevolani StepPrep" and set Version=0.1.0 in the csproj;
  shown in the window title. The CadSearchGui folder/namespace/csproj keep
  their technical name, only the product-facing name changed.
- Updated CLAUDE.md, README.md and docs/architettura.md to reflect the
  product name/version, UiStile.cs, app.ico, and that this app is a
  pre-process step ahead of Creo Distributed Batch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 11:00:01 +02:00
davideandClaude Sonnet 5 ba18d682f3 Fix overflowing/clipped UI text and give buttons a consistent flat style
Instruction and status labels used AutoSize/fixed widths with no wrap
constraint, so long Italian paragraphs and network paths ran past the
window edge or got clipped instead of wrapping. Report dialogs had
fixed-height labels that clipped multi-line text. ProgressForm's folder
label could overflow its non-resizable dialog.

- Labels now wrap responsively (TableLayoutPanel column at 100% + Dock=Top)
  instead of a fixed pixel cap, so they reflow when the window is resized.
- Report dialog labels switched from fixed Height to AutoSize.
- ProgressForm's folder label truncates with ellipsis + tooltip, matching
  the existing pattern used by the import status label.
- Added ApplicationHighDpiMode=PerMonitorV2 to fix control proportions on
  scaled displays.
- Added UiStile.cs: shared flat button style (primary/secondary), rounded
  corners for the wizard nav buttons, and AutoSize+padding on all buttons
  so button size always matches its text (fixes text getting clipped when
  a hardcoded Size was too narrow for the label).
- Fase 5's drag&drop .asm list now fills available vertical space instead
  of a fixed 680x160 box.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 10:40:32 +02:00
davideandClaude Sonnet 5 6511d58b01 Parallelize raw/ copy and document overall architecture
CopiaFileTrovati now copies with Parallel.ForEach (degree 6) instead of
sequentially: the bottleneck is network latency to the share, not CPU,
so overlapping copies gives a large speedup (~5x in a synthetic
benchmark with simulated SMB latency, documented in
docs/copia-raw-parallela.md).

Also adds docs/architettura.md, a full architecture overview of every
source file, the wizard's 5 phases, dialogs, persistence and standalone
scripts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 10:07:46 +02:00
davideandClaude Sonnet 5 0966e66232 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 <noreply@anthropic.com>
2026-09-21 09:54:06 +02:00
25 changed files with 647 additions and 310 deletions

No files matched your search

+45 -20
View File
@@ -4,14 +4,19 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Cos'è questo progetto
`CadSearchGui` è un'app Windows Forms (.NET 10, WinForms) che indicizza e permette di
`PrepStep` è il nome tecnico del progetto (cartella, namespace, csproj, eseguibile
`PrepStep.exe`); il nome mostrato all'utente finale è **Cevolani StepPrep** (`<Product>`/
`<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
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
SQLite locale (`cad_index.db`).
SQLite locale (`cad_index.db`). Il flusso a valle (fasi 2-5, vedi sotto) prepara i file
`raw/` da passare a Creo Distributed Batch per l'esportazione STEP: la app è quindi un
pre-processo per quel passaggio, non lo sostituisce.
## Comandi
Tutti da eseguire dentro `CadSearchGui/`.
Tutti da eseguire dentro `PrepStep/`.
```powershell
dotnet build -c Release
@@ -24,16 +29,16 @@ Pubblicazione come eseguibile standalone (single-file, self-contained, Windows x
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o publish
```
`CadSearchGui.Tests/` (xUnit) contiene unit e integration test — vedi
`PrepStep.Tests/` (xUnit) contiene unit e integration test — vedi
`dotnet test` da dentro quella cartella. Riferimento al progetto principale
via `ProjectReference` + `InternalsVisibleTo` (in `CadSearchGui.csproj`), così
via `ProjectReference` + `InternalsVisibleTo` (in `PrepStep.csproj`), così
i test possono chiamare direttamente `Indexer.ParseFilename` e
`AssemblyComponentScanner.EstraiCodiciReferenziati` senza esporli come API
pubblica.
## Architettura
Il programma è tutto in `CadSearchGui/` (nessun altro progetto nella solution — in
Il programma è tutto in `PrepStep/` (nessun altro progetto nella solution — in
passato esisteva anche una CLI separata e uno script Python di indicizzazione,
entrambi rimossi: ora l'unico programma è questo, con GUI e indicizzazione integrate).
@@ -45,26 +50,38 @@ entrambi rimossi: ora l'unico programma è questo, con GUI e indicizzazione inte
`Form1.Designer.cs` contiene solo lo scheletro minimo (`InitializeComponent`/`Dispose`)
generato dal template. Le fasi, in ordine:
1. **Database** — carica/aggiorna l'indice, mostra data ultimo aggiornamento; qui vive
anche "Pulisci raw e step" (manutenzione, non fa parte del flusso a valle).
anche la **cartella di lavoro** (`_cartellaLavoro`, mostrata in `_lblCartellaLavoro`,
cambiabile con "Cambia cartella..." → `ScegliCartellaLavoro`): destinazione di CSV,
`raw/` per tutte le fasi successive (gli STEP esportati a mano con Creo vanno in
`step/`, dentro la stessa cartella). All'avvio
`InizializzaCartellaLavoro` propone l'ultima usata (ricordata in
`%AppData%/PrepStep/ultima_cartella_lavoro.txt`, stesso pattern di
`ultimo_db.txt` per il DB) se esiste ancora, altrimenti `AppContext.BaseDirectory`
— non dipende da un marker di repo (niente più `CLAUDE.md`/`TrovaRadiceRepo`),
quindi funziona identica anche sull'exe pubblicato standalone (`PrepStep.exe`)
copiato su un'altra macchina. Qui vive anche "Pulisci raw e step" (manutenzione,
non fa parte del flusso a valle).
2. **Importa Distinta Base** — `DistintaBaseImporter.ConvertiInCsv` genera il CSV dei
componenti; se il nome del file segue la convenzione del gestionale
(`DBP_<codice>_<timestamp>`), 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.
componenti nella cartella di lavoro; se il nome del file segue la convenzione del
gestionale (`DBP_<codice>_<timestamp>`), 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.
3. **Crea cartella raw** — cerca nell'indice il percorso CAD di ogni componente del CSV
(`TrovaPercorsiCad`) e lo copia in `raw/` (`CopiaFileTrovati`).
(`TrovaPercorsiCad`) e lo copia in `raw/` dentro la cartella di lavoro
(`CopiaFileTrovati`, parallela — vedi `docs/copia-raw-parallela.md`).
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 `<codice>.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** — 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, per l'esportazione
STEP con Creo Distributed Batch. "Verifica completezza assiemi" controlla
ricorsivamente (sui percorsi originali in indice, non su `raw/`) se tutti i
sotto-componenti di ogni assieme della distinta sono nell'indice.
- **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)
@@ -81,6 +98,14 @@ entrambi rimossi: ora l'unico programma è questo, con GUI e indicizzazione inte
di progresso, conteggi nuovi/aggiornati/invariati/rimossi, tempo trascorso e stima
del tempo rimanente (calcolata dal ritmo file/secondo rispetto al totale atteso,
preso dal conteggio della scansione precedente salvato nel DB).
- **UiStile.cs** — stile bottoni condiviso da `Form1` e dai dialog secondari
(`Primario`/`Secondario`, entrambi `AutoSize` col padding invece di una `Size` fissa
in pixel, per evitare che il testo vada a capo e si tagli quando non ci sta) e
`Arrotonda` per gli angoli dei bottoni "Avanti"/"Indietro" del wizard.
- **app.ico** — icona dell'app (logo Cevolani + pezzo + STEP, sfondo trasparente),
referenziata da `<ApplicationIcon>` in `PrepStep.csproj` e riletta a runtime in
`Form1.BuildUi()` via `Icon.ExtractAssociatedIcon(Application.ExecutablePath)` per
la titlebar (stessa icona dell'exe, nessun file duplicato nell'output).
### Schema del DB (`cad_index.db`)
-85
View File
@@ -1,85 +0,0 @@
using System.Diagnostics;
namespace CadSearchGui;
public enum EsitoStep
{
/// <summary>File .stp trovato in "step" e copiato in "step_finali".</summary>
Creato,
/// <summary>Il componente è nell'indice CAD (asm/prt trovato), ma nessuno STEP esportato.</summary>
StepMancante,
/// <summary>Il componente non è nemmeno nell'indice CAD.</summary>
ComponenteNonTrovato,
}
/// <summary>Mostra, per ogni componente della distinta base, se lo STEP corrispondente è stato
/// trovato in "step" e copiato in "step_finali", se manca solo lo STEP (il CAD sorgente c'è,
/// ma non è ancora stato esportato) o se manca anche il CAD sorgente nell'indice.</summary>
public class ReportStepForm : Form
{
public ReportStepForm(List<(string Componente, string Descrizione, EsitoStep Esito)> righe, string cartellaStepFinali)
{
int creati = righe.Count(r => r.Esito == EsitoStep.Creato);
int stepMancanti = righe.Count(r => r.Esito == EsitoStep.StepMancante);
int nonTrovati = righe.Count(r => r.Esito == EsitoStep.ComponenteNonTrovato);
Text = "Report STEP finali";
ClientSize = new Size(720, 440);
StartPosition = FormStartPosition.CenterParent;
Font = new Font("Segoe UI", 9.5f);
var lblInfo = new Label
{
Dock = DockStyle.Top,
AutoSize = false,
Height = 32,
Padding = new Padding(12, 8, 12, 8),
Text = $"{creati} copiati, {stepMancanti} da esportare (CAD presente), "
+ $"{nonTrovati} non nell'indice, su {righe.Count} totali. In \"{cartellaStepFinali}\".",
};
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("step", "STEP");
grid.Columns["componente"]!.FillWeight = 20;
grid.Columns["descrizione"]!.FillWeight = 55;
grid.Columns["step"]!.FillWeight = 25;
foreach (var (componente, descrizione, esito) in righe.OrderBy(r => r.Esito).ThenBy(r => r.Componente))
{
var (testo, colore) = esito switch
{
EsitoStep.Creato => ("Creato", Color.DarkGreen),
EsitoStep.StepMancante => ("Da esportare (CAD presente)", Color.DarkOrange),
_ => ("Componente non trovato nel server", Color.DarkRed),
};
int i = grid.Rows.Add(componente, descrizione, testo);
grid.Rows[i].DefaultCellStyle.ForeColor = colore;
}
var btnApriCartella = new Button { Text = "Apri cartella step_finali", Dock = DockStyle.Bottom, Height = 34 };
btnApriCartella.Click += (_, _) =>
{
try { Process.Start("explorer.exe", cartellaStepFinali); }
catch (Exception ex)
{
MessageBox.Show(this, $"Impossibile aprire Esplora risorse:\n{ex.Message}", "Errore",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
};
Controls.Add(grid);
Controls.Add(lblInfo);
Controls.Add(btnApriCartella);
}
}
@@ -1,6 +1,6 @@
using CadSearchGui;
using PrepStep;
namespace CadSearchGui.Tests;
namespace PrepStep.Tests;
public class AssemblyComponentScannerTests
{
@@ -1,7 +1,7 @@
using CadSearchGui;
using PrepStep;
using Microsoft.Data.Sqlite;
namespace CadSearchGui.Tests;
namespace PrepStep.Tests;
/// <summary>Ricrea lo schema reale del DB (stessa costante SQL usata da Indexer.ScanAsync) e
/// verifica, con dati inseriti a mano, i due comportamenti che in passato hanno causato bug
@@ -1,6 +1,6 @@
using CadSearchGui;
using PrepStep;
namespace CadSearchGui.Tests;
namespace PrepStep.Tests;
public class DistintaBaseImporterTests
{
@@ -1,6 +1,6 @@
using CadSearchGui;
using PrepStep;
namespace CadSearchGui.Tests;
namespace PrepStep.Tests;
public class IndexerParseFilenameTests
{
@@ -16,7 +16,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CadSearchGui\CadSearchGui.csproj" />
<ProjectReference Include="..\PrepStep\PrepStep.csproj" />
</ItemGroup>
<ItemGroup>
@@ -1,7 +1,7 @@
using System.Text;
using System.Text.RegularExpressions;
namespace CadSearchGui;
namespace PrepStep;
/// <summary>
/// Estrae i codici componente referenziati dentro un file .asm Pro-E/Creo.
@@ -2,7 +2,7 @@ using System.Text;
using System.Xml.Linq;
using ExcelDataReader;
namespace CadSearchGui;
namespace PrepStep;
/// <summary>
/// Converte il file "Distinta Base" esportato dal gestionale (SpreadsheetML
@@ -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);
@@ -1,6 +1,6 @@
#nullable enable
namespace CadSearchGui;
namespace PrepStep;
partial class Form1
{
@@ -22,9 +22,7 @@ partial class Form1
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(1000, 650);
MinimumSize = new Size(700, 400);
Name = "Form1";
Text = "CadSearch — ricerca archivio CAD";
ResumeLayout(false);
}
}
+234 -148
View File
@@ -1,7 +1,7 @@
using System.Diagnostics;
using Microsoft.Data.Sqlite;
namespace CadSearchGui;
namespace PrepStep;
public partial class Form1 : Form
{
@@ -9,13 +9,15 @@ public partial class Form1 : Form
private readonly Button _btnAggiorna = new();
private readonly Button _btnCreaRaw = new();
private readonly Button _btnTrovaPrt = new();
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();
private readonly Label _lblUltimoAggiornamento = new();
private readonly Label _lblCartellaLavoro = new();
private readonly Button _btnCambiaCartella = new();
private readonly DataGridView _gridImport = new();
private readonly DataGridView _gridRaw = new();
private readonly Label _lblImportStatus = new();
@@ -29,7 +31,7 @@ public partial class Form1 : Form
"2. Importa Distinta Base",
"3. Crea cartella raw",
"4. Trova componenti referenziati",
"5. Verifica completezza e STEP",
"5. Verifica completezza",
};
private readonly Panel[] _panelFasi = new Panel[NomiFasi.Length];
private readonly Panel _faseContainer = new() { Dock = DockStyle.Fill };
@@ -42,6 +44,7 @@ public partial class Form1 : Form
private const string RadiceDiscoDefault = @"X:\";
private string? _dbPath;
private string? _cartellaLavoro;
private string? _percorsoInputCorrente;
private List<(string Componente, string Descrizione)>? _righeCorrenti;
private Dictionary<string, List<string>>? _ultimaVerifica;
@@ -51,7 +54,11 @@ public partial class Form1 : Form
{
InitializeComponent();
BuildUi();
Load += (_, _) => InizializzaDatabase();
Load += (_, _) =>
{
InizializzaCartellaLavoro();
InizializzaDatabase();
};
}
// Palette: blu primario per l'azione principale del flusso ("Crea cartella raw"),
@@ -61,9 +68,21 @@ public partial class Form1 : Form
private void BuildUi()
{
AllowDrop = true;
Text = "Distinta Base -> CSV con percorsi CAD";
// Versione letta dall'assembly (<Version> nel csproj) invece che duplicata qui a mano.
var versione = typeof(Form1).Assembly.GetName().Version;
Text = $"Cevolani StepPrep {versione?.ToString(3)}";
MinimumSize = new Size(760, 480);
Font = new Font("Segoe UI", 9.5f);
// Stessa icona di ApplicationIcon nel csproj: la si rilegge dall'exe compilato invece
// di duplicare il file .ico come Content, funziona identica in Debug/Release/publish.
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 };
_lblFase.Dock = DockStyle.Fill;
@@ -75,23 +94,23 @@ public partial class Form1 : Form
var navPanel = new FlowLayoutPanel
{
Dock = DockStyle.Bottom,
Height = 48,
// I bottoni si auto-dimensionano al testo (UiStile): l'altezza qui deve stare
// comoda sopra al loro MinimumSize (32) più il padding, altrimenti vengono
// tagliati sul fondo della barra invece di restarci dentro.
Height = 56,
FlowDirection = FlowDirection.RightToLeft,
Padding = new Padding(12, 8, 12, 8),
Padding = new Padding(12, 10, 12, 10),
};
_btnAvanti.Text = "Avanti >";
_btnAvanti.Size = new Size(110, 32);
_btnAvanti.FlatStyle = FlatStyle.Flat;
_btnAvanti.FlatAppearance.BorderSize = 0;
_btnAvanti.BackColor = ColorePrimario;
_btnAvanti.ForeColor = Color.White;
_btnAvanti.Font = new Font(Font, FontStyle.Bold);
_btnAvanti.Text = "Avanti ›";
UiStile.Primario(_btnAvanti);
UiStile.Arrotonda(_btnAvanti);
_btnAvanti.Click += (_, _) => VaiAllaFase(_faseAttuale + 1);
navPanel.Controls.Add(_btnAvanti);
_btnIndietro.Text = "< Indietro";
_btnIndietro.Size = new Size(110, 32);
_btnIndietro.Text = "‹ Indietro";
_btnIndietro.Margin = new Padding(8, 0, 0, 0);
UiStile.Secondario(_btnIndietro);
UiStile.Arrotonda(_btnIndietro);
_btnIndietro.Click += (_, _) => VaiAllaFase(_faseAttuale - 1);
navPanel.Controls.Add(_btnIndietro);
@@ -149,12 +168,53 @@ public partial class Form1 : Form
_btnIndietro.Visible = _faseAttuale > 0;
_btnAvanti.Visible = _faseAttuale < NomiFasi.Length - 1;
AggiornaStatoAvanti();
if (_faseAttuale == 4)
AggiornaAnteprimaAsm();
}
/// <summary>Popola le icone trascinabili (drag&amp;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.</summary>
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 });
}
}
/// <summary>Condizione per cui la fase indicata è "conclusa" e si può passare alla successiva.</summary>
private bool AvantiConsentito(int fase) => fase switch
{
0 => _dbPath is not null,
0 => _dbPath is not null && _cartellaLavoro is not null,
1 => _righeCorrenti is { Count: > 0 },
2 => _ultimaCartellaRaw is not null,
3 => true, // risoluzione ricorsiva opzionale: non tutti gli assiemi hanno sotto-componenti
@@ -173,9 +233,13 @@ public partial class Form1 : Form
ColumnCount = 1,
Padding = new Padding(16),
};
// Colonna al 100%: le label sotto (Dock=Top) vanno a capo sulla larghezza reale della
// finestra invece che su un limite fisso in pixel, e si riadattano se viene ridimensionata.
contenuto.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
var lblIstruzioni = new Label
{
Dock = DockStyle.Top,
AutoSize = true,
ForeColor = SystemColors.GrayText,
Margin = new Padding(0, 0, 0, 12),
@@ -185,9 +249,8 @@ public partial class Form1 : Form
};
contenuto.Controls.Add(lblIstruzioni);
_lblDb.Dock = DockStyle.Top;
_lblDb.AutoSize = true;
_lblDb.AutoEllipsis = true;
_lblDb.MaximumSize = new Size(900, 0);
_lblDb.ForeColor = SystemColors.GrayText;
_lblDb.Margin = new Padding(0, 0, 0, 4);
contenuto.Controls.Add(_lblDb);
@@ -200,30 +263,55 @@ public partial class Form1 : Form
var bottoni = new FlowLayoutPanel { AutoSize = true, FlowDirection = FlowDirection.LeftToRight };
_btnApriDb.Text = "Apri DB...";
_btnApriDb.Size = new Size(120, 32);
_btnApriDb.Margin = new Padding(0, 0, 8, 0);
UiStile.Secondario(_btnApriDb);
_btnApriDb.Click += (_, _) => ScegliDatabase();
bottoni.Controls.Add(_btnApriDb);
_btnAggiorna.Text = "Aggiorna indice";
_btnAggiorna.Size = new Size(120, 32);
UiStile.Secondario(_btnAggiorna);
_btnAggiorna.Click += (_, _) => AggiornaIndice();
bottoni.Controls.Add(_btnAggiorna);
contenuto.Controls.Add(bottoni);
var lblIstruzioniCartella = new Label
{
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 root prima di un "
+ "nuovo giro (non tocca l'indice, solo le copie locali).",
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";
_btnPulisci.Size = new Size(200, 30);
UiStile.Secondario(_btnPulisci);
_btnPulisci.Click += (_, _) => PulisciRawEStep();
contenuto.Controls.Add(_btnPulisci);
@@ -242,9 +330,11 @@ public partial class Form1 : Form
ColumnCount = 1,
Padding = new Padding(16),
};
contenuto.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
var lblIstruzioni = new Label
{
Dock = DockStyle.Top,
AutoSize = true,
ForeColor = SystemColors.GrayText,
Margin = new Padding(0, 0, 0, 8),
@@ -257,22 +347,22 @@ public partial class Form1 : Form
var btnSfoglia = new Button
{
Text = "Scegli file distinta...",
Size = new Size(220, 32),
Margin = new Padding(0, 0, 0, 8),
};
UiStile.Secondario(btnSfoglia);
btnSfoglia.Click += (_, _) => ScegliFileDistinta();
contenuto.Controls.Add(btnSfoglia);
_btnVerifica.Text = "Verifica presenza nel DB";
_btnVerifica.Size = new Size(220, 32);
_btnVerifica.Text = "Verifica presenza";
_btnVerifica.Margin = new Padding(0, 0, 0, 8);
UiStile.Secondario(_btnVerifica);
_btnVerifica.Click += (_, _) => VerificaCodiciNelDb();
contenuto.Controls.Add(_btnVerifica);
_lblImportStatus.Dock = DockStyle.Top;
_lblImportStatus.AutoSize = false;
_lblImportStatus.AutoEllipsis = true;
_lblImportStatus.Height = 24;
_lblImportStatus.Width = 900;
_lblImportStatus.ForeColor = SystemColors.GrayText;
contenuto.Controls.Add(_lblImportStatus);
@@ -315,25 +405,22 @@ public partial class Form1 : Form
ColumnCount = 1,
Padding = new Padding(16),
};
contenuto.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
var lblIstruzioni = new Label
{
Dock = DockStyle.Top,
AutoSize = true,
ForeColor = SystemColors.GrayText,
Margin = new Padding(0, 0, 0, 8),
Text = "Cerca nell'indice il percorso CAD di ogni componente importato e copia i "
+ "file trovati in una cartella 'raw' accanto al file di distinta originale.",
+ "file trovati in una cartella 'raw' dentro la cartella di lavoro (fase 1).",
};
contenuto.Controls.Add(lblIstruzioni);
_btnCreaRaw.Text = "Crea cartella raw";
_btnCreaRaw.Size = new Size(220, 36);
_btnCreaRaw.Margin = new Padding(0, 0, 0, 12);
_btnCreaRaw.FlatStyle = FlatStyle.Flat;
_btnCreaRaw.FlatAppearance.BorderSize = 0;
_btnCreaRaw.BackColor = ColorePrimario;
_btnCreaRaw.ForeColor = Color.White;
_btnCreaRaw.Font = new Font(Font, FontStyle.Bold);
UiStile.Primario(_btnCreaRaw);
_btnCreaRaw.Click += (_, _) => CreaCartellaRaw();
contenuto.Controls.Add(_btnCreaRaw);
@@ -376,9 +463,11 @@ public partial class Form1 : Form
ColumnCount = 1,
Padding = new Padding(16),
};
contenuto.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
var lblIstruzioni = new Label
{
Dock = DockStyle.Top,
AutoSize = true,
ForeColor = SystemColors.GrayText,
Margin = new Padding(0, 0, 0, 12),
@@ -390,7 +479,7 @@ public partial class Form1 : Form
contenuto.Controls.Add(lblIstruzioni);
_btnTrovaPrt.Text = "Trova componenti referenziati";
_btnTrovaPrt.Size = new Size(220, 32);
UiStile.Secondario(_btnTrovaPrt);
_btnTrovaPrt.Click += (_, _) => TrovaPrtReferenziati();
contenuto.Controls.Add(_btnTrovaPrt);
@@ -401,17 +490,22 @@ public partial class Form1 : Form
private Panel BuildFaseVerifica()
{
var contenuto = new TableLayoutPanel
// Padding qui invece che su ogni sotto-pannello: si applica uniformemente a top/fill/bottom
// (il layout docked usa il DisplayRectangle del contenitore, già ridotto dal Padding).
var panel = new Panel { Padding = new Padding(16) };
var top = new TableLayoutPanel
{
Dock = DockStyle.Top,
AutoSize = true,
AutoSizeMode = AutoSizeMode.GrowAndShrink,
ColumnCount = 1,
Padding = new Padding(16),
};
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),
@@ -419,32 +513,43 @@ public partial class Form1 : Form
+ "(ricorsivamente fino ai PRT) sono nell'indice. Nota: la scansione dei .asm "
+ "è euristica, quindi qualche falso 'incompleto' è possibile.",
};
contenuto.Controls.Add(lblIstruzioniCompletezza);
top.Controls.Add(lblIstruzioniCompletezza);
_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, 12);
UiStile.Secondario(_btnCompletezza);
_btnCompletezza.Click += (_, _) => VerificaCompletezzaAssiemi();
contenuto.Controls.Add(_btnCompletezza);
top.Controls.Add(_btnCompletezza);
var lblIstruzioniStep = new Label
var lblAnteprimaAsm = new Label
{
AutoSize = true,
ForeColor = SystemColors.GrayText,
Margin = new Padding(0, 0, 0, 8),
Text = "Dopo aver esportato gli STEP con Creo, premi qui per confrontare i componenti "
+ "della distinta (e i sotto-componenti trovati in fase 4) con i file .stp presenti "
+ "in 'step', copiare quelli trovati in 'step_finali' e vedere quali mancano ancora.",
Margin = new Padding(0, 0, 0, 4),
Text = "File .asm della distinta trovati nell'indice (trascinali su Creo/Esplora risorse):",
};
contenuto.Controls.Add(lblIstruzioniStep);
top.Controls.Add(lblAnteprimaAsm);
_btnReport.Text = "Crea cartella step_finali";
_btnReport.Size = new Size(220, 32);
_btnReport.Click += (_, _) => GeneraReportStep();
contenuto.Controls.Add(_btnReport);
// Dock=Fill invece di una Size fissa: la lista occupa tutto lo spazio verticale
// rimasto sotto le istruzioni, così si vedono più file .asm senza scroll anche a
// finestra ingrandita, invece dei ~160px fissi di prima.
_lstAnteprimaAsm.View = View.LargeIcon;
_lstAnteprimaAsm.MultiSelect = true;
_lstAnteprimaAsm.LargeImageList = new ImageList { ImageSize = new Size(32, 32) };
_lstAnteprimaAsm.Dock = DockStyle.Fill;
_lstAnteprimaAsm.Margin = new Padding(0, 4, 0, 12);
_lstAnteprimaAsm.BorderStyle = BorderStyle.FixedSingle;
_lstAnteprimaAsm.ItemDrag += (_, e) =>
{
var percorsi = _lstAnteprimaAsm.SelectedItems.Cast<ListViewItem>()
.Select(item => (string)item.Tag!)
.ToArray();
if (percorsi.Length > 0)
_lstAnteprimaAsm.DoDragDrop(new DataObject(DataFormats.FileDrop, percorsi), DragDropEffects.Copy);
};
var panel = new Panel();
panel.Controls.Add(contenuto);
panel.Controls.Add(_lstAnteprimaAsm);
panel.Controls.Add(top);
return panel;
}
@@ -463,7 +568,7 @@ public partial class Form1 : Form
{
try
{
var (csv, righe) = DistintaBaseImporter.ConvertiInCsv(percorso);
var (csv, righe) = DistintaBaseImporter.ConvertiInCsv(percorso, _cartellaLavoro!);
_percorsoInputCorrente = percorso;
_righeCorrenti = righe;
@@ -532,7 +637,7 @@ public partial class Form1 : Form
_gridRaw.Rows.Add(componente, percorsoCad);
}
var cartellaRaw = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(_percorsoInputCorrente))!, "raw");
var cartellaRaw = Path.Combine(_cartellaLavoro!, "raw");
int copiati = CopiaFileTrovati(percorsiTrovati.Values.SelectMany(p => p).Distinct(), cartellaRaw);
_ultimaCartellaRaw = cartellaRaw;
@@ -543,96 +648,18 @@ public partial class Form1 : Form
AggiornaStatoAvanti();
}
/// <summary>Confronta i componenti della distinta importata con i file .stp presenti nella
/// cartella "step" (esportati manualmente con Creo Distributed Batch), per dire quali sono
/// già stati realizzati e quali mancano ancora.</summary>
private void GeneraReportStep()
{
if (_percorsoInputCorrente is null || _righeCorrenti is null)
return;
string radice = Path.GetDirectoryName(Path.GetFullPath(_percorsoInputCorrente))!;
var cartellaStep = Path.Combine(radice, "step");
if (!Directory.Exists(cartellaStep))
{
ImpostaStatoImport($"Cartella 'step' non trovata in {cartellaStep}.", Color.DarkRed);
return;
}
var descrizioni = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var r in _righeCorrenti)
descrizioni[r.Componente] = r.Descrizione;
// Solo i componenti della distinta importata dal CSV: i sotto-componenti trovati in
// fase 4 (_componentiRisolti) servono a completare "raw", non entrano nello STEP finale.
var tuttiICodici = _righeCorrenti.Select(r => r.Componente)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
// Per i codici senza STEP, distingue "il CAD c'è ma lo STEP non è stato esportato" da
// "il componente non è nemmeno nell'indice CAD" (riusa la verifica già fatta in fase 2/3
// se disponibile, altrimenti la rifà).
var cadTrovati = _ultimaVerifica ?? TrovaPercorsiCad(tuttiICodici);
var cartellaStepFinali = Path.Combine(radice, "step_finali");
Directory.CreateDirectory(cartellaStepFinali);
var righeReport = new List<(string Componente, string Descrizione, EsitoStep Esito)>();
foreach (var codice in tuttiICodici)
{
var percorsoStp = Path.Combine(cartellaStep, codice + ".stp");
EsitoStep esito;
if (File.Exists(percorsoStp))
{
File.Copy(percorsoStp, Path.Combine(cartellaStepFinali, codice + ".stp"), overwrite: true);
esito = EsitoStep.Creato;
}
else
{
esito = cadTrovati.ContainsKey(codice) ? EsitoStep.StepMancante : EsitoStep.ComponenteNonTrovato;
}
righeReport.Add((codice, descrizioni.GetValueOrDefault(codice, ""), esito));
}
int creati = righeReport.Count(r => r.Esito == EsitoStep.Creato);
int stepMancanti = righeReport.Count(r => r.Esito == EsitoStep.StepMancante);
int nonTrovati = righeReport.Count(r => r.Esito == EsitoStep.ComponenteNonTrovato);
ImpostaStatoImport(
$"STEP finali: {creati} copiati, {stepMancanti} da esportare (CAD presente), "
+ $"{nonTrovati} componenti non nell'indice, su {righeReport.Count} totali. "
+ $"Cartella: {cartellaStepFinali}.",
Color.DarkGreen);
using var dialog = new ReportStepForm(righeReport, cartellaStepFinali);
dialog.ShowDialog(this);
}
/// <summary>Svuota in un colpo solo le cartelle "raw", "step" e "step_finali" nella root
/// (stessa cartella base del file distinta importato), previa un'unica conferma. Non tocca
/// l'indice CAD: solo le copie locali generate dalla pipeline.</summary>
/// <summary>Svuota in un colpo solo le cartelle "raw" e "step" nella cartella di lavoro,
/// previa un'unica conferma. Non tocca l'indice CAD: solo le copie locali generate dalla
/// pipeline.</summary>
private void PulisciRawEStep()
{
string radice;
if (_ultimaCartellaRaw is not null)
{
radice = Path.GetDirectoryName(_ultimaCartellaRaw)!;
}
else
{
// Non abbiamo ancora una cartella nota in questa sessione (es. app appena riaperta):
// si chiede all'utente di indicare la root invece di tenere il pulsante disabilitato.
using var dialog = new FolderBrowserDialog { Description = "Seleziona la cartella root (contiene 'raw' e 'step')" };
if (dialog.ShowDialog(this) != DialogResult.OK)
return;
radice = dialog.SelectedPath;
}
var cartelle = new[] { Path.Combine(radice, "raw"), Path.Combine(radice, "step"), Path.Combine(radice, "step_finali") }
string radice = _cartellaLavoro!;
var cartelle = new[] { Path.Combine(radice, "raw"), Path.Combine(radice, "step") }
.Where(Directory.Exists)
.ToList();
if (cartelle.Count == 0)
{
ImpostaStatoImport($"Nessuna cartella 'raw', 'step' o 'step_finali' trovata in {radice}.", Color.DarkRed);
ImpostaStatoImport($"Nessuna cartella 'raw' o 'step' trovata in {radice}.", Color.DarkRed);
return;
}
@@ -875,23 +902,25 @@ public partial class Form1 : Form
}
/// <summary>Copia i file trovati nella cartella "raw" indicata, creandola se necessario.
/// Ritorna il numero di file copiati con successo.</summary>
/// 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
/// concorrenti bastano a sovrapporre le attese senza saturare la share.</summary>
internal static int CopiaFileTrovati(IEnumerable<string> percorsi, string cartellaRaw)
{
Directory.CreateDirectory(cartellaRaw);
int copiati = 0;
foreach (var percorso in percorsi)
Parallel.ForEach(percorsi, new ParallelOptions { MaxDegreeOfParallelism = 6 }, percorso =>
{
try
{
Directory.CreateDirectory(cartellaRaw);
File.Copy(percorso, Path.Combine(cartellaRaw, Path.GetFileName(percorso)), overwrite: true);
copiati++;
Interlocked.Increment(ref copiati);
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{
// ponytail: file non raggiungibile (share offline, permessi) -> lo si salta, non blocca gli altri
}
}
});
return copiati;
}
@@ -941,8 +970,65 @@ public partial class Form1 : Form
return risultato;
}
private static readonly string PercorsoImpostazioni = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "CadSearchGui", "ultimo_db.txt");
private static readonly string CartellaImpostazioni = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "PrepStep");
private static readonly string PercorsoImpostazioni = Path.Combine(CartellaImpostazioni, "ultimo_db.txt");
private static readonly string PercorsoImpostazioniCartella = Path.Combine(CartellaImpostazioni, "ultima_cartella_lavoro.txt");
/// <summary>Propone l'ultima cartella di lavoro usata (se ancora esiste su disco) o, la prima
/// volta, la cartella dell'eseguibile: non richiede mai un CLAUDE.md/repo (a differenza del
/// vecchio TrovaRadiceRepo), quindi funziona identica anche sull'exe pubblicato standalone
/// copiato su un'altra macchina. Sempre modificabile con "Cambia cartella..." in fase 1.</summary>
private void InizializzaCartellaLavoro()
{
var ultima = LeggiUltimaCartellaLavoro();
_cartellaLavoro = ultima is not null && Directory.Exists(ultima) ? ultima : AppContext.BaseDirectory;
AggiornaLabelCartellaLavoro();
}
private void ScegliCartellaLavoro()
{
using var dialog = new FolderBrowserDialog
{
Description = "Seleziona la cartella di lavoro (CSV, raw, step)",
SelectedPath = _cartellaLavoro ?? string.Empty,
};
if (dialog.ShowDialog(this) != DialogResult.OK)
return;
string percorso = dialog.SelectedPath;
_cartellaLavoro = percorso;
AggiornaLabelCartellaLavoro();
SalvaUltimaCartellaLavoro(percorso);
AggiornaStatoAvanti();
}
private void AggiornaLabelCartellaLavoro() => _lblCartellaLavoro.Text = $"Cartella di lavoro: {_cartellaLavoro}";
private static string? LeggiUltimaCartellaLavoro()
{
try
{
return File.Exists(PercorsoImpostazioniCartella) ? File.ReadAllText(PercorsoImpostazioniCartella).Trim() : null;
}
catch (IOException)
{
return null;
}
}
private static void SalvaUltimaCartellaLavoro(string path)
{
try
{
Directory.CreateDirectory(CartellaImpostazioni);
File.WriteAllText(PercorsoImpostazioniCartella, path);
}
catch (IOException)
{
// ponytail: non critico se non si riesce a salvare, si ripropone il default al prossimo avvio
}
}
private void InizializzaDatabase()
{
@@ -1024,7 +1110,7 @@ public partial class Form1 : Form
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(PercorsoImpostazioni)!);
Directory.CreateDirectory(CartellaImpostazioni);
File.WriteAllText(PercorsoImpostazioni, path);
}
catch (IOException)
@@ -2,7 +2,7 @@ using System.Diagnostics;
using System.Text.RegularExpressions;
using Microsoft.Data.Sqlite;
namespace CadSearchGui;
namespace PrepStep;
public sealed record ScanProgress(
string Cartella,
@@ -6,6 +6,14 @@
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
<ApplicationIcon>app.ico</ApplicationIcon>
<!-- Nome tecnico (cartella/csproj/namespace) e nome del file .exe pubblicato: PrepStep.
Il nome prodotto mostrato all'utente ("Cevolani StepPrep") resta separato, sotto. -->
<AssemblyTitle>Cevolani StepPrep</AssemblyTitle>
<Product>Cevolani StepPrep</Product>
<Company>Cevolani</Company>
<Version>0.1.0</Version>
</PropertyGroup>
<ItemGroup>
@@ -16,7 +24,7 @@
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>CadSearchGui.Tests</_Parameter1>
<_Parameter1>PrepStep.Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
@@ -1,4 +1,4 @@
namespace CadSearchGui;
namespace PrepStep;
static class Program
{
@@ -1,4 +1,4 @@
namespace CadSearchGui;
namespace PrepStep;
public sealed class ProgressForm : Form
{
@@ -8,6 +8,7 @@ public sealed class ProgressForm : Form
private readonly Label _lblDettaglio = new();
private readonly Label _lblEta = new();
private readonly Button _btnAnnulla = new();
private readonly ToolTip _tipCartella = new();
public event EventHandler? Annullato;
@@ -23,7 +24,12 @@ public sealed class ProgressForm : Form
_lblCartella.Text = "Preparazione...";
_lblCartella.Location = new Point(16, 16);
_lblCartella.AutoSize = true;
// AutoSize=false + AutoEllipsis: un percorso lungo (share di rete) va troncato con "..."
// invece di sforare il bordo del dialog, che è FixedDialog e non si può allargare.
// Il testo completo resta disponibile nel tooltip (stesso pattern di Form1._lblImportStatus).
_lblCartella.AutoSize = false;
_lblCartella.AutoEllipsis = true;
_lblCartella.Size = new Size(428, 20);
_lblCartella.Font = new Font(Font, FontStyle.Bold);
_progressBar.Location = new Point(16, 44);
@@ -46,6 +52,7 @@ public sealed class ProgressForm : Form
_btnAnnulla.Text = "Annulla";
_btnAnnulla.Location = new Point(354, 152);
_btnAnnulla.Size = new Size(90, 28);
UiStile.Secondario(_btnAnnulla);
_btnAnnulla.Click += (_, e) => Annullato?.Invoke(this, e);
Controls.AddRange(new Control[]
@@ -57,6 +64,7 @@ public sealed class ProgressForm : Form
public void Aggiorna(ScanProgress p)
{
_lblCartella.Text = $"Scansione {p.Cartella}...";
_tipCartella.SetToolTip(_lblCartella, _lblCartella.Text);
if (p.PrevistiTotali > 0)
{
@@ -1,4 +1,4 @@
namespace CadSearchGui;
namespace PrepStep;
public sealed class ProgressoComponentiForm : Form
{
@@ -1,6 +1,6 @@
using System.Diagnostics;
namespace CadSearchGui;
namespace PrepStep;
/// <summary>Mostra l'esito della risoluzione ricorsiva dell'albero componenti: i PRT e i
/// sotto-assiemi trovati nell'indice e già copiati in "raw" a partire dagli assiemi iniziali.</summary>
@@ -19,8 +19,7 @@ public class PrtMancantiForm : Form
var lblInfo = new Label
{
Dock = DockStyle.Top,
AutoSize = false,
Height = 44,
AutoSize = true,
Padding = new Padding(12, 8, 12, 8),
Text = $"Copiati in \"{cartellaRaw}\": {numPrt} PRT e {numAsm} sotto-assiemi trovati nell'indice."
+ (codiciNonTrovati.Count > 0
@@ -57,6 +56,7 @@ public class PrtMancantiForm : Form
}
var btnApriCartella = new Button { Text = "Apri cartella raw", Dock = DockStyle.Bottom, Height = 34 };
UiStile.Secondario(btnApriCartella);
btnApriCartella.Click += (_, _) =>
{
try { Process.Start("explorer.exe", cartellaRaw); }
@@ -1,4 +1,4 @@
namespace CadSearchGui;
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>
@@ -18,8 +18,7 @@ public class ReportCompletezzaForm : Form
var lblInfo = new Label
{
Dock = DockStyle.Top,
AutoSize = false,
Height = 48,
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 "
+72
View File
@@ -0,0 +1,72 @@
using System.Drawing.Drawing2D;
namespace PrepStep;
/// <summary>Stile bottoni condiviso tra la form principale e i dialog secondari: piatto con
/// bordo sottile e hover, per un aspetto coerente sia per l'azione primaria di ogni fase
/// (blu pieno) sia per le azioni secondarie, al posto del bottone grigio di default.</summary>
internal static class UiStile
{
// 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
// tagliato in verticale quando non ci sta nella larghezza indovinata a mano.
private static void DimensionamentoAutomatico(Button b)
{
b.AutoSize = true;
b.AutoSizeMode = AutoSizeMode.GrowAndShrink;
b.Padding = new Padding(18, 4, 18, 4);
b.MinimumSize = new Size(0, 32);
}
public static void Primario(Button b)
{
DimensionamentoAutomatico(b);
b.FlatStyle = FlatStyle.Flat;
b.FlatAppearance.BorderSize = 0;
b.FlatAppearance.MouseOverBackColor = Color.FromArgb(0, 84, 152);
b.FlatAppearance.MouseDownBackColor = Color.FromArgb(0, 70, 130);
b.BackColor = Color.FromArgb(0, 99, 177);
b.ForeColor = Color.White;
b.Font = new Font(b.Font, FontStyle.Bold);
b.Cursor = Cursors.Hand;
}
public static void Secondario(Button b)
{
DimensionamentoAutomatico(b);
b.FlatStyle = FlatStyle.Flat;
b.FlatAppearance.BorderColor = Color.FromArgb(190, 195, 200);
b.FlatAppearance.BorderSize = 1;
b.FlatAppearance.MouseOverBackColor = Color.FromArgb(230, 238, 245);
b.FlatAppearance.MouseDownBackColor = Color.FromArgb(213, 226, 238);
b.BackColor = Color.White;
b.Cursor = Cursors.Hand;
}
/// <summary>Arrotonda gli angoli del bottone. 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(Button b, int raggio = 6)
{
b.SizeChanged += (_, _) => AggiornaRegione(b, raggio);
AggiornaRegione(b, raggio);
}
private static void AggiornaRegione(Button b, int raggio)
{
if (b.Width <= 0 || b.Height <= 0)
return;
int d = raggio * 2;
var rect = new Rectangle(Point.Empty, b.Size);
using var percorso = new GraphicsPath();
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.Bottom - d, d, d, 0, 90);
percorso.AddArc(rect.X, rect.Bottom - d, d, d, 90, 90);
percorso.CloseFigure();
b.Region?.Dispose();
b.Region = new Region(percorso);
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

+33 -25
View File
@@ -1,9 +1,11 @@
# CadSearchGui
# Cevolani StepPrep
App Windows Forms (.NET 10) per indicizzare, cercare e recuperare i file CAD
Pro-ENGINEER/Creo di un disco/share di rete, e per trasformare l'estrazione
"Distinta Base" del gestionale (file `.xls`/`.xml`) in un CSV con il percorso
CAD di ogni componente.
App Windows Forms (.NET 10, progetto `PrepStep`) per indicizzare, cercare e
recuperare i file CAD Pro-ENGINEER/Creo di un disco/share di rete, e per trasformare
l'estrazione "Distinta Base" del gestionale (file `.xls`/`.xml`) in un CSV con il
percorso CAD di ogni componente, fino a raccogliere in `raw/` tutto il necessario per
l'esportazione STEP con **Creo Distributed Batch**: è un pre-processo per quel
passaggio, non lo sostituisce.
## Funzionalità
@@ -11,24 +13,28 @@ CAD di ogni componente.
presente accanto all'eseguibile). Se manca, si può selezionare un DB
esistente con **Apri DB...** o generarne uno nuovo con **Aggiorna
indice**, che scansiona il disco (di default `X:\`, tutte le cartelle di primo
livello che trova) e indicizza tutti i file.
livello che trova) e indicizza tutti i file. In questa fase si vede/cambia anche
la **cartella di lavoro**: dove finiscono il CSV e la cartella `raw/` (gli STEP
esportati con Creo vanno in `step/`, dentro la stessa cartella). Proposta
automaticamente (ricordata da un avvio all'altro), modificabile con
**Cambia cartella...**.
2. **Importa Distinta Base** — trascina (o scegli da file) l'estrazione
`.xls`/`.xml` del gestionale (foglio "Distinta Base", colonne
Componente/Descrizione): viene generato un CSV con anteprima in griglia.
Se il file segue la convenzione di nome `DBP_<codice>_<timestamp>` del
gestionale, l'assieme generale dell'estrazione viene aggiunto come riga
in più nel CSV (il foglio elenca solo i componenti sotto l'assieme, non
l'assieme stesso).
Componente/Descrizione): viene generato un CSV nella cartella di lavoro, con
anteprima in griglia. Se il file segue la convenzione di nome
`DBP_<codice>_<timestamp>` del gestionale, l'assieme generale dell'estrazione
viene aggiunto come riga in più nel CSV (il foglio elenca solo i componenti
sotto l'assieme, non l'assieme stesso).
3. **Crea cartella raw** — cerca nell'indice il percorso CAD di ogni
componente importato (assieme generale incluso) e copia i file trovati
in una cartella `raw/` accanto al file di distinta originale.
in una cartella `raw/` dentro la cartella di lavoro.
4. **Trova componenti referenziati** — legge i metadati dei file `.asm` in
`raw/` per trovare ricorsivamente i sotto-assiemi/parti che li
compongono, copiandoli a loro volta in `raw/`.
5. **Verifica completezza assiemi** / **Report STEP realizzati** — confronta
la distinta con l'indice (per capire se manca qualche sotto-componente)
e con la cartella `step/` (per capire quali STEP sono già stati
realizzati).
5. **Verifica completezza assiemi** — confronta la distinta con l'indice per
capire se manca qualche sotto-componente, e mostra i file `.asm` della
distinta trascinabili su Creo/Esplora risorse per l'esportazione STEP con
Creo Distributed Batch.
## Requisiti
@@ -40,7 +46,7 @@ CAD di ogni componente.
## Build
```powershell
cd CadSearchGui
cd PrepStep
dotnet build -c Release
```
@@ -49,15 +55,15 @@ dotnet build -c Release
Da sorgente:
```powershell
cd CadSearchGui
cd PrepStep
dotnet run -c Release
```
Oppure l'eseguibile già compilato:
```powershell
cd CadSearchGui\bin\Release\net10.0-windows
.\CadSearchGui.exe
cd PrepStep\bin\Release\net10.0-windows
.\PrepStep.exe
```
## Pubblicazione (eseguibile standalone)
@@ -66,15 +72,17 @@ Genera un `.exe` self-contained per Windows x64, senza bisogno del runtime
.NET installato sulla macchina di destinazione:
```powershell
cd CadSearchGui
cd PrepStep
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o publish
```
L'eseguibile risultante è in `CadSearchGui\publish\CadSearchGui.exe`.
L'eseguibile risultante è in `PrepStep\publish\PrepStep.exe` — un unico
file, si copia e si lancia su un'altra macchina Windows senza bisogno di .NET
installato.
## Struttura del progetto
Tutto il codice è in `CadSearchGui/` (unico progetto nella solution, GUI e
Tutto il codice è in `PrepStep/` (unico progetto nella solution, GUI e
indicizzazione integrate):
- `Program.cs` — entry point.
@@ -91,12 +99,12 @@ sono tracciati in git (vedi `.gitignore`).
## Test
`CadSearchGui.Tests/` contiene unit test (regex di estrazione dei componenti
`PrepStep.Tests/` contiene unit test (regex di estrazione dei componenti
dai `.asm`, parsing del nome file Pro-E) e integration test (schema/vista SQL
reale, con focus sui bug di case-sensitivity/duplicati già risolti). Da
eseguire:
```powershell
cd CadSearchGui.Tests
cd PrepStep.Tests
dotnet test
```
+171
View File
@@ -0,0 +1,171 @@
# Architettura di PrepStep (Cevolani StepPrep)
Panoramica di tutti i file sorgente e di come collaborano. `PrepStep` è il nome
tecnico del progetto (cartella, namespace, csproj, eseguibile `PrepStep.exe`);
**Cevolani StepPrep** è il nome mostrato all'utente finale (titolo finestra, icona,
versione — vedi `<Version>`/`<Product>` in `PrepStep.csproj`). Per il "come si usa" vedi il `README.md` in root; per il
formato binario `.asm` vedi `docs/formato-asm.md`; per la parallelizzazione della
copia in `raw/` vedi `docs/copia-raw-parallela.md`.
## Vista d'insieme
Un solo eseguibile WinForms (nessuna CLI, nessun processo esterno): indicizza i file CAD di
una share di rete in un DB SQLite locale, poi guida l'utente in un wizard a 5 fasi che, a
partire da una distinta base esportata dal gestionale, raccoglie in locale (`raw/`) tutti i
file CAD dei componenti e dei loro sotto-assiemi e verifica che l'indice li copra, come
pre-process prima dell'esportazione STEP con Creo Distributed Batch (manuale, fuori
dall'app: i file esportati finiscono in `step/`, dentro la cartella di lavoro).
```
Distinta (.xls/.xml) → CSV (componente;descrizione) → raw/ (CAD copiati dall'indice)
│
scansione .asm ─────┘ (sotto-assiemi/parti mancanti)
│
drag&drop .asm su Creo → Distributed Batch → step/ (esportazione manuale)
```
## Entry point
- **Program.cs** — `Main()` standard WinForms: `Application.Run(new Form1())`. Nessuna logica.
## Finestra principale (wizard)
- **Form1.cs** — tutta la UI è costruita in codice (`BuildUi`/`BuildFase*`), non con il
designer visuale. È un wizard: `_panelFasi[]` contiene un `Panel` per fase, uno solo
visibile alla volta (`VaiAllaFase`); si avanza con "Avanti" solo se `AvantiConsentito`
ritorna vero per la fase corrente. `Form1.Designer.cs` contiene solo lo scheletro minimo
generato dal template (`InitializeComponent`/`Dispose`), non è usato per costruire i
controlli.
Le 5 fasi e i metodi principali dietro ciascuna:
| # | Fase | Bottone → metodo | Cosa fa |
|---|---|---|---|
| 1 | Database | "Aggiorna indice" → `Indexer.ScanAsync`; "Cambia cartella..." → `ScegliCartellaLavoro` | Crea/aggiorna `cad_index.db` scansionando la share; sceglie/mostra la cartella di lavoro |
| 2 | Importa Distinta Base | selezione file → `ImportaDistinta` → `DistintaBaseImporter.ConvertiInCsv` | Xls/Xml gestionale → CSV componente;descrizione, scritto nella cartella di lavoro |
| 3 | Crea cartella raw | "Crea cartella raw" → `CreaCartellaRaw` | `TrovaPercorsiCad` + `CopiaFileTrovati` (parallela) → `raw/` dentro la cartella di lavoro |
| 4 | Trova componenti referenziati | "Trova componenti referenziati" → `TrovaPrtReferenziati` → `RisolviAlberoComponenti` | Legge i `.asm` in `raw/` con `AssemblyComponentScanner`, risolve ricorsivamente sotto-assiemi/parti nell'indice, li copia anch'essi in `raw/` |
| 5 | Verifica completezza | "Verifica completezza assiemi" → `VerificaCompletezzaAssiemi` | Completezza ricorsiva sui percorsi in indice. All'ingresso nella fase, `AggiornaAnteprimaAsm` mostra le icone trascinabili dei `.asm` della distinta già in `raw/`, da trascinare su Creo per l'esportazione STEP (Distributed Batch) |
Stato condiviso tra le fasi (campi privati di `Form1`): `_dbPath`, `_cartellaLavoro`,
`_percorsoInputCorrente`, `_righeCorrenti` (righe del CSV), `_ultimaVerifica` (cache percorsi
CAD), `_ultimaCartellaRaw`.
Altre responsabilità in Form1.cs:
- `TrovaPercorsiCad` / `CaricaCodiciNoti` — query sul DB (`files_ultima_versione`), la
seconda carica tutti i codici prt/asm noti in un colpo solo (usata sia per risolvere i
componenti sia da `AssemblyComponentScanner` per validare i token generici).
- `CopiaFileTrovati` — copia (in parallelo, grado 6) i file trovati in `raw/`, saltando
silenziosamente quelli irraggiungibili (share offline/permessi).
- `PulisciRawEStep` — svuota `raw/` e `step/` nella cartella di lavoro (manutenzione,
richiede conferma).
- `InizializzaDatabase` / `ScegliDatabase` — localizzazione del DB (ultimo usato, salvato in
`%AppData%/PrepStep/ultimo_db.txt`, o `cad_index.db` accanto all'eseguibile).
- `InizializzaCartellaLavoro` / `ScegliCartellaLavoro` — stesso pattern per la cartella di
lavoro (`ultima_cartella_lavoro.txt`), fallback `AppContext.BaseDirectory` se non c'è
ancora nulla di salvato: nessuna dipendenza da un marker di repo, funziona identica
sull'exe pubblicato standalone spostato su un'altra macchina.
## Indicizzazione
- **Indexer.cs** — `ScanAsync` cammina tutte le cartelle di primo livello sotto la radice
(scoperte dinamicamente, non un elenco fisso) con un walk manuale a stack (`Walk`, più
veloce di `EnumerateFiles(..., AllDirectories)` su share con centinaia di migliaia di
file), esclude le cartelle `temp_*`, estrae da ogni nome file il pattern Pro-E
`<codice>.<tipo>.<versione>` (`ParseFilename`, regex), e fa un upsert incrementale riga per
riga confrontando `percorso_completo` + dimensione + data modifica. Progresso riportato via
`IProgress<ScanProgress>` (marshalato sul thread UI). Contiene anche lo schema SQL: tabella
`files` e vista `files_ultima_versione` (ultima versione per `codice`+`tipo_file` via
`ROW_NUMBER() OVER (...)`, non self-join+`GROUP BY` per evitare tempi quasi quadratici su
~200k righe).
## Scansione binaria dei `.asm`
- **AssemblyComponentScanner.cs** — estrae euristicamente i codici componente referenziati
dentro un `.asm` (formato per lo più binario, codici come stringhe ASCII leggibili al suo
interno). Due strategie: codici numerici Pro-E riconosciuti da soli via regex (come nei nomi
file), e nomi alfabetici di parti commerciali tokenizzati genericamente e validati contro
l'elenco di codici noti del DB (altrimenti il rumore binario sarebbe ingestibile). Dettagli
sul formato, l'euristica e i falsi positivi noti in `docs/formato-asm.md`.
## Import distinta base
- **DistintaBaseImporter.cs** — converte l'export del gestionale (SpreadsheetML XML con
estensione `.xls`/`.xml`, oppure `.xls`/`.xlsx` binario vero, distinti dai primi byte del
file) nelle sole colonne Componente/Descrizione, foglio `"Distinta Base"`. Se il nome del
file segue la convenzione `DBP_<codice>_<timestamp>`, aggiunge l'assieme generale come riga
in più (il foglio elenca solo i componenti sotto l'assieme). Scrive il CSV nella cartella
passata da `Form1` (la cartella di lavoro scelta in fase 1), non necessariamente accanto al
file di input.
## Finestre di dialogo (modali, mostrate a fine step)
- **ProgressForm.cs** — durante l'aggiornamento indice: barra di progresso, conteggi
nuovi/aggiornati/invariati/rimossi, tempo trascorso e stima del tempo rimanente (dal ritmo
file/secondo rispetto al totale atteso, preso dal conteggio della scansione precedente
salvato nel DB).
- **ProgressoComponentiForm.cs** — dialog minimale (etichetta + barra) durante
`RisolviAlberoComponenti` in fase 4.
## Stile UI condiviso
- **UiStile.cs** — `Primario`/`Secondario`: stile piatto per i bottoni (bordo sottile
per i secondari, blu pieno per l'azione principale di ogni fase), entrambi con
`AutoSize`+`Padding`+`MinimumSize` invece di una `Size` fissa in pixel indovinata a
mano (altrimenti il testo va a capo e si taglia in verticale se non ci sta nella
larghezza scelta). `Arrotonda` applica angoli arrotondati via `Region` calcolata su
`SizeChanged` (non una volta sola: con `AutoSize` la dimensione finale del bottone si
conosce solo a layout avvenuto), usata sui bottoni "Avanti"/"Indietro" del wizard.
- **app.ico** — icona dell'app, referenziata da `<ApplicationIcon>` nel csproj (icona
dell'exe in Esplora risorse/taskbar) e riletta esplicitamente in `Form1.BuildUi()`
con `Icon.ExtractAssociatedIcon(Application.ExecutablePath)` per la titlebar, senza
copiare il file `.ico` nell'output. Sfondo trasparente (nessuna card bianca), 7
risoluzioni da 16 a 256px.
- **PrtMancantiForm.cs** — esito della risoluzione ricorsiva dell'albero componenti: PRT e
sotto-assiemi trovati e copiati in `raw/`, più i codici referenziati che non corrispondono a
nessun file indicizzato (possono essere disegni, componenti davvero mancanti, o falsi
positivi della scansione `.asm`).
- **ReportCompletezzaForm.cs** — per ogni assieme della distinta, se tutti i suoi componenti
(ricorsivamente fino ai PRT) sono nell'indice (✓/✗/? per "non trovato").
## Persistenza
- **DB** (`cad_index.db`, SQLite) — vedi tabella `files`/vista `files_ultima_versione` sopra.
Percorso: cercato accanto all'eseguibile o nella working directory all'avvio; altrimenti
selezionabile con "Apri DB...". Radice disco di scansione di default `X:\`
(`Form1.RadiceDiscoDefault`, hardcoded).
- **Cartella di lavoro** — salvata in `%AppData%/PrepStep/ultima_cartella_lavoro.txt`
(stesso meccanismo del DB sopra), proposta di default `AppContext.BaseDirectory` la prima
volta; selezionabile/cambiabile con "Cambia cartella..." in fase 1. Tutti i file generati a
valle ci finiscono dentro: il CSV della distinta, `raw/` (CAD dei componenti e
sotto-componenti); `step/` non è scritta dall'app ma è dove l'utente esporta gli STEP a
mano con Creo Distributed Batch, a partire dai file trascinati da `raw/` in fase 5.
## Script standalone (`scripts/`)
C# file-based apps (.NET 10, `dotnet run scripts/Nome.cs -- arg1 arg2`, nessun `.csproj`,
dipendenze extra con `#:package Nome@versione` in testa al file):
- **ExtractCreoStrings.cs** — BOM di un `.asm` risolvendo ricorsivamente i sotto-assiemi
contro `cad_index.db` (stessa logica di `AssiemeCompleto` in Form1, solo codici numerici
Pro-E).
- **BomViaDb.cs** — come sopra ma con la tokenizzazione generica validata contro il DB, per
intercettare anche i nomi alfabetici delle parti commerciali.
- **Analyze.cs** — dump del contesto testo/hex attorno a un pattern in un file binario, per
investigare a mano nuovi falsi positivi prima di toccare la regex di
`AssemblyComponentScanner`.
- **Query.cs** — query SQL ad-hoc contro `cad_index.db` da riga di comando.
> ⚠️ Nota importante di sicurezza: se `dotnet run` viene lanciato dentro `PrepStep/`
> (che contiene un `.csproj` di tipo `WinExe`), esegue l'app GUI vera invece dello script
> standalone e resta bloccato in attesa che una finestra si chiuda. Lanciare gli script da
> una cartella senza `.csproj` (es. la root del repo o `scripts/` stessa).
## Test
- **PrepStep.Tests/** (xUnit) — riferimento al progetto principale via `ProjectReference`
+ `InternalsVisibleTo` (in `PrepStep.csproj`), così i test chiamano direttamente
`Indexer.ParseFilename` e `AssemblyComponentScanner.EstraiCodiciReferenziati` senza esporli
come API pubblica. File: `IndexerParseFilenameTests.cs`,
`AssemblyComponentScannerTests.cs`, `DistintaBaseImporterTests.cs`,
`CaseInsensitiveLookupIntegrationTests.cs`.
+46
View File
@@ -0,0 +1,46 @@
# Copia parallela dei file in `raw/`
`Form1.CopiaFileTrovati` (usata in fase 3 "Crea cartella raw" e in fase 4 "Trova componenti
referenziati" per copiare i file trovati nell'indice dentro `raw/`) copia i file in parallelo
invece che uno alla volta.
## Perché
I file vengono letti da una share di rete (`X:\`, vedi CLAUDE.md). Il tempo per ogni copia è
dominato dalla latenza di rete (round-trip SMB per aprire/leggere/chiudere il file), non dalla
CPU: un thread passa la maggior parte del tempo in attesa della risposta del server. Copiare
più file in parallelo sovrappone queste attese invece di sommarle, con un guadagno quasi
proporzionale al grado di parallelismo.
## Implementazione
`Parallel.ForEach` con `MaxDegreeOfParallelism = 6` (non un thread per file: troppa
concorrenza sulla stessa share rischia di saturare le connessioni SMB o subire throttling dal
server, risultando più lento invece che più veloce). Il contatore dei file copiati usa
`Interlocked.Increment` per essere thread-safe. La logica di skip per file irraggiungibili
(share offline, permessi — `catch (IOException or UnauthorizedAccessException)`) resta
invariata: essendo dentro il corpo della lambda, ogni iterazione ha il proprio try/catch
indipendente.
Il valore 6 è una stima di partenza, non misurata sulla share reale: se in pratica risulta
troppo aggressivo o troppo conservativo per la vostra rete, va aggiustato empiricamente.
## Benchmark (sintetico)
Non essendoci accesso diretto alla share `X:\` da questo ambiente, il confronto prima/dopo è
stato fatto con uno script standalone (`dotnet run <file>.cs`, pattern già usato in `scripts/`)
che copia file locali fittizi aggiungendo un ritardo artificiale di 80 ms per file, per simulare
la latenza di un round-trip SMB (il disco locale da solo è troppo veloce per mostrare qualunque
differenza).
50 file, ritardo simulato 80 ms/file:
| Versione | Tempo totale |
|---|---|
| Prima (sequenziale) | 4751 ms |
| Ora (parallela, grado 6) | 961 ms |
~5x più veloce, coerente con l'aspettativa teorica (limite di ~6x dato il grado di
parallelismo, scontato l'overhead di scheduling). Essendo una simulazione, il guadagno reale
sulla share va verificato sul campo: la latenza effettiva, la banda disponibile e il
comportamento del server sotto più connessioni concorrenti possono cambiare il risultato.
+3 -3
View File
@@ -33,7 +33,7 @@ ASCII leggibili in diversi campi ricorrenti:
Un codice reale compare **più volte** nel file (una per feature/placement
diverso); rumore binario che assomiglia casualmente a un codice compare quasi
sempre una sola volta. Questa è la base del filtro in
[`AssemblyComponentScanner.cs`](../CadSearchGui/AssemblyComponentScanner.cs)
[`AssemblyComponentScanner.cs`](../PrepStep/AssemblyComponentScanner.cs)
e in [`scripts/ExtractCreoStrings.cs`](../scripts/ExtractCreoStrings.cs):
```
@@ -249,7 +249,7 @@ regex aggiuntiva: (?<=<)(?:UNI\d+|CUSCI_[A-Za-z0-9]+)(?=>)
```
Implementata sia in [`scripts/ExtractCreoStrings.cs`](../scripts/ExtractCreoStrings.cs) sia in
[`AssemblyComponentScanner.cs`](../CadSearchGui/AssemblyComponentScanner.cs) (fase "Trova componenti
[`AssemblyComponentScanner.cs`](../PrepStep/AssemblyComponentScanner.cs) (fase "Trova componenti
referenziati" dell'app). Il prefisso family table non è garantito essere solo `UNI`/`CUSCI`:
se ricompaiono famiglie diverse (altri componenti normalizzati), vanno aggiunte allo stesso modo,
verificando prima con `scripts/Analyze.cs` che il contenuto tra `<` e `>` corrisponda a un codice
@@ -271,7 +271,7 @@ livello) serve una BFS:
Stessa logica di estrazione candidati ed euristica del codice radice (sopra),
duplicata in due punti del codice per scopi diversi:
- [`AssemblyComponentScanner.EstraiCodiciReferenziati`](../CadSearchGui/AssemblyComponentScanner.cs) + `CodiceRadice` — estrazione dei candidati da un file, usata dalla BFS in [`Form1.RisolviAlberoComponenti`](../CadSearchGui/Form1.cs) (fase "Trova componenti referenziati") e [`Form1.AssiemeCompleto`](../CadSearchGui/Form1.cs) (fase "Verifica completezza"). Entrambe seminano l'insieme dei codici visitati con codice + radice di ogni file di partenza, e caricano una volta sola tutti i codici noti all'indice (`Form1.CaricaCodiciNoti`) sia per validare i token generici delle parti commerciali sia per risolvere i percorsi dei componenti trovati, senza una query SQL per candidato.
- [`AssemblyComponentScanner.EstraiCodiciReferenziati`](../PrepStep/AssemblyComponentScanner.cs) + `CodiceRadice` — estrazione dei candidati da un file, usata dalla BFS in [`Form1.RisolviAlberoComponenti`](../PrepStep/Form1.cs) (fase "Trova componenti referenziati") e [`Form1.AssiemeCompleto`](../PrepStep/Form1.cs) (fase "Verifica completezza"). Entrambe seminano l'insieme dei codici visitati con codice + radice di ogni file di partenza, e caricano una volta sola tutti i codici noti all'indice (`Form1.CaricaCodiciNoti`) sia per validare i token generici delle parti commerciali sia per risolvere i percorsi dei componenti trovati, senza una query SQL per candidato.
- [`scripts/ExtractCreoStrings.cs`](../scripts/ExtractCreoStrings.cs) e [`scripts/BomViaDb.cs`](../scripts/BomViaDb.cs) — stessa BFS ricorsiva standalone da riga di comando (copia indipendente della stessa euristica, nessun `ProjectReference` verso la GUI). Usati per confrontare l'euristica contro l'albero Creo di un singolo file durante il reverse engineering, senza dover passare dalla UI/dal DB `raw`.
Nota: le query contro `files_ultima_versione` filtrano solo per
+1 -1
View File
@@ -3,7 +3,7 @@
//
// Estrae dal file .asm i codici dei componenti referenziati, espandendo ricorsivamente
// ogni sotto-assieme (.asm) trovato nell'indice, finché non restano solo PRT (foglie) o
// codici non presenti nell'indice. Stessa euristica di CadSearchGui/AssemblyComponentScanner.cs:
// codici non presenti nell'indice. Stessa euristica di PrepStep/AssemblyComponentScanner.cs:
// i codici veri compaiono più volte nel file, il rumore binario quasi sempre una sola volta;
// gli ID interni "ModelView#<numero>" sono esclusi via lookbehind - vedi scripts/Analyze.cs.
// Ogni codice trovato viene validato contro l'indice cad_index.db.