diff --git a/.gitignore b/.gitignore index 4183425..629568c 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ publish/ /*.csv /raw/ /step/ +*.dxc diff --git a/CadSearchGui/AssemblyComponentScanner.cs b/CadSearchGui/AssemblyComponentScanner.cs new file mode 100644 index 0000000..8329a6f --- /dev/null +++ b/CadSearchGui/AssemblyComponentScanner.cs @@ -0,0 +1,29 @@ +using System.Text; +using System.Text.RegularExpressions; + +namespace CadSearchGui; + +/// +/// Estrae i codici componente referenziati dentro un file .asm Pro-E/Creo. +/// Il formato è per lo più binario, ma i codici dei sotto-componenti compaiono +/// come stringhe ASCII leggibili al suo interno: si scansiona il file come testo +/// cercando pattern che assomigliano a un codice (stesso stile usato nei nomi file, +/// vedi Indexer.FilenameRegex) e si lascia poi al chiamante il compito di verificarli +/// contro l'indice DB, che scarta automaticamente i falsi positivi (numeri che non +/// corrispondono a nessun codice reale). +/// +static class AssemblyComponentScanner +{ + private static readonly Regex CodiceRegex = new(@"\b\d{8,12}[A-Za-z]{0,2}\b", RegexOptions.Compiled); + + public static HashSet EstraiCodiciReferenziati(string percorsoAsm) + { + var testo = Encoding.Latin1.GetString(File.ReadAllBytes(percorsoAsm)); + var codici = new HashSet(); + foreach (Match m in CodiceRegex.Matches(testo)) + { + codici.Add(m.Value); + } + return codici; + } +} diff --git a/CadSearchGui/Form1.cs b/CadSearchGui/Form1.cs index d34e7a6..2bb444d 100644 --- a/CadSearchGui/Form1.cs +++ b/CadSearchGui/Form1.cs @@ -8,6 +8,7 @@ public partial class Form1 : Form private readonly Button _btnApriDb = new(); private readonly Button _btnAggiorna = new(); private readonly Button _btnCreaRaw = new(); + private readonly Button _btnTrovaPrt = new(); private readonly Label _lblStatus = new(); private readonly Label _lblDb = new(); private readonly DataGridView _gridImport = new(); @@ -20,6 +21,7 @@ public partial class Form1 : Form private string? _dbPath; private string? _percorsoInputCorrente; private List<(string Componente, string Descrizione)>? _righeCorrenti; + private string? _ultimaCartellaRaw; public Form1() { @@ -201,9 +203,27 @@ public partial class Form1 : Form _btnCreaRaw.Click += (_, _) => CreaCartellaRaw(); contenuto.Controls.Add(_btnCreaRaw); + var lblIstruzioni3 = new Label + { + AutoSize = true, + ForeColor = SystemColors.GrayText, + Margin = new Padding(0, 12, 0, 0), + Text = "4. Gli assiemi referenziano altri PRT e sotto-assiemi al loro interno: premi " + + "qui per risolvere l'albero componenti (ricorsivamente) e copiare tutto ciò che " + + "viene trovato nell'indice dentro 'raw'.", + }; + contenuto.Controls.Add(lblIstruzioni3); + + _btnTrovaPrt.Text = "Trova componenti referenziati"; + _btnTrovaPrt.Size = new Size(220, 32); + _btnTrovaPrt.Margin = new Padding(0, 8, 0, 0); + _btnTrovaPrt.Enabled = false; + _btnTrovaPrt.Click += (_, _) => TrovaPrtReferenziati(); + contenuto.Controls.Add(_btnTrovaPrt); + return new GroupBox { - Text = "2-3. Importa Distinta Base e crea la cartella raw", + Text = "2-4. Importa Distinta Base, crea la cartella raw e risolvi i componenti referenziati", Dock = DockStyle.Top, AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink, @@ -276,6 +296,8 @@ public partial class Form1 : Form var cartellaRaw = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(_percorsoInputCorrente))!, "raw"); int copiati = CopiaFileTrovati(percorsiTrovati.Values.SelectMany(p => p).Distinct(), cartellaRaw); + _ultimaCartellaRaw = cartellaRaw; + _btnTrovaPrt.Enabled = true; ImpostaStatoImport( $"{percorsiTrovati.Count} componenti con percorso CAD trovato su {_righeCorrenti.Count}. " @@ -283,9 +305,75 @@ public partial class Form1 : Form Color.DarkGreen); } + /// Risolve ricorsivamente l'albero dei componenti a partire dagli .asm in "raw": + /// per ogni assieme legge i codici referenziati al suo interno, li cerca nell'indice, copia + /// in "raw" quelli trovati e, se sono a loro volta assiemi, li accoda per essere scansionati + /// finché non restano solo PRT (foglie) o codici non presenti nell'indice. + private void TrovaPrtReferenziati() + { + if (_ultimaCartellaRaw is null || !Directory.Exists(_ultimaCartellaRaw)) + return; + + var daScansionare = new Queue( + Directory.GetFiles(_ultimaCartellaRaw).Where(f => Path.GetFileName(f).Contains(".asm."))); + var codiciVisti = new HashSet(daScansionare.Select(f => Path.GetFileName(f).Split('.')[0])); + + var trovati = new List<(string Codice, string Tipo, string Percorso)>(); + int codiciNonTrovati = 0; + + if (_dbPath is not null) + { + using var connection = new SqliteConnection($"Data Source={_dbPath};Mode=ReadOnly"); + connection.Open(); + using var command = connection.CreateCommand(); + command.CommandText = "SELECT tipo_file, percorso_completo FROM files_ultima_versione " + + "WHERE codice = @codice AND tipo_file IN ('prt', 'asm')"; + var pCodice = command.Parameters.Add("@codice", Microsoft.Data.Sqlite.SqliteType.Text); + + while (daScansionare.Count > 0) + { + var asm = daScansionare.Dequeue(); + foreach (var codice in AssemblyComponentScanner.EstraiCodiciReferenziati(asm)) + { + if (!codiciVisti.Add(codice)) + continue; + + pCodice.Value = codice; + var componenti = new List<(string Tipo, string Percorso)>(); + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + componenti.Add((reader.GetString(0), reader.GetString(1))); + } + } + + if (componenti.Count == 0) + { + codiciNonTrovati++; + continue; + } + + foreach (var (tipo, percorso) in componenti) + { + CopiaFileTrovati(new[] { percorso }, _ultimaCartellaRaw); + trovati.Add((codice, tipo, percorso)); + if (tipo == "asm") + { + daScansionare.Enqueue(Path.Combine(_ultimaCartellaRaw, Path.GetFileName(percorso))); + } + } + } + } + } + + using var dialog = new PrtMancantiForm(trovati, codiciNonTrovati, _ultimaCartellaRaw); + dialog.ShowDialog(this); + } + /// Copia i file trovati nella cartella "raw" indicata, creandola se necessario. /// Ritorna il numero di file copiati con successo. - private static int CopiaFileTrovati(IEnumerable percorsi, string cartellaRaw) + internal static int CopiaFileTrovati(IEnumerable percorsi, string cartellaRaw) { int copiati = 0; foreach (var percorso in percorsi) diff --git a/CadSearchGui/PrtMancantiForm.cs b/CadSearchGui/PrtMancantiForm.cs new file mode 100644 index 0000000..0b2e0e8 --- /dev/null +++ b/CadSearchGui/PrtMancantiForm.cs @@ -0,0 +1,68 @@ +using System.Diagnostics; + +namespace CadSearchGui; + +/// 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. +public class PrtMancantiForm : Form +{ + public PrtMancantiForm(List<(string Codice, string Tipo, string Percorso)> righe, int codiciNonTrovati, string cartellaRaw) + { + int numPrt = righe.Count(r => r.Tipo == "prt"); + int numAsm = righe.Count(r => r.Tipo == "asm"); + + Text = "Componenti referenziati trovati"; + 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 = 44, + Padding = new Padding(12, 8, 12, 8), + Text = $"Copiati in \"{cartellaRaw}\": {numPrt} PRT e {numAsm} sotto-assiemi trovati nell'indice." + + (codiciNonTrovati > 0 + ? $" {codiciNonTrovati} codici referenziati non corrispondono a nessun file indicizzato " + + "(probabilmente disegni, o falsi positivi della scansione)." + : ""), + }; + + var grid = new DataGridView + { + Dock = DockStyle.Fill, + ReadOnly = true, + AllowUserToAddRows = false, + AllowUserToDeleteRows = false, + RowHeadersVisible = false, + SelectionMode = DataGridViewSelectionMode.FullRowSelect, + AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill, + }; + grid.Columns.Add("codice", "Codice"); + grid.Columns.Add("tipo", "Tipo"); + grid.Columns.Add("percorso", "Percorso CAD"); + grid.Columns["codice"]!.FillWeight = 20; + grid.Columns["tipo"]!.FillWeight = 10; + grid.Columns["percorso"]!.FillWeight = 70; + foreach (var (codice, tipo, percorso) in righe.OrderBy(r => r.Tipo).ThenBy(r => r.Codice)) + { + grid.Rows.Add(codice, tipo, percorso); + } + + var btnApriCartella = new Button { Text = "Apri cartella raw", Dock = DockStyle.Bottom, Height = 34 }; + btnApriCartella.Click += (_, _) => + { + try { Process.Start("explorer.exe", cartellaRaw); } + 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); + } +}