Compare commits
6
Commits
8a8a7d0417
...
379deec986
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
379deec986 | ||
|
|
13798ad47c | ||
|
|
6726dd2216 | ||
|
|
ba18d682f3 | ||
|
|
6511d58b01 | ||
|
|
0966e66232 |
No files matched your search
@@ -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`)
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
using CadSearchGui;
|
||||
using PrepStep;
|
||||
|
||||
namespace CadSearchGui.Tests;
|
||||
namespace PrepStep.Tests;
|
||||
|
||||
public class AssemblyComponentScannerTests
|
||||
{
|
||||
+2
-2
@@ -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
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
using CadSearchGui;
|
||||
using PrepStep;
|
||||
|
||||
namespace CadSearchGui.Tests;
|
||||
namespace PrepStep.Tests;
|
||||
|
||||
public class DistintaBaseImporterTests
|
||||
{
|
||||
+2
-2
@@ -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
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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&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 "
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
@@ -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
|
||||
```
|
||||
@@ -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`.
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user