Fix runaway recursion and two missed-component gaps in .asm scanning

The bare root code of an assembly (e.g. 9284208188, without a _D... suffix)
is always referenced inside its own suffixed sub-assemblies as a genuine
placement pointing back to the general assembly. Following it during
recursive resolution pulled in the entire product tree instead of just the
requested sub-assembly (598 components collected instead of ~65 on
9284208188_D600). Fixed by seeding the visited-codes set with each starting
assembly's root code, in both scripts/ExtractCreoStrings.cs and the two BFS
call sites in Form1.cs (RisolviAlberoComponenti, AssiemeCompleto).

Also closes two recall gaps found by comparing against a real Creo BOM
report: some real placements only show up as the code repeated twice
separated by a NUL byte rather than the usual "Componente ID N (...)" text,
and family-table bearing codes (CUSCI_...) use the same <...> bracket
convention as UNI hardware but weren't recognized.

docs/formato-asm.md documents all three findings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5 committed 2026-09-17 15:32:55 +02:00
1 parent 9660c4883d
commit a7e0df0247
4 files changed
+147 -69

No files matched your search

+27 -12
View File
@@ -21,10 +21,12 @@ namespace CadSearchGui;
/// "ModelView#&lt;numero&gt;", hanno la stessa forma di un codice (8-12 cifre) e
/// compaiono più volte come un riferimento vero. Vedi docs/formato-asm.md.
///
/// Viti/dadi/rosette normalizzati UNI sono family table Creo: il nome che compare nel
/// binario (es. "R6--_-_U6592&lt;UNI6592&gt;") è la singola istanza, non un file separato
/// su disco - il file reale indicizzato è il generico "uni6592.prt.&lt;versione&gt;", quindi
/// va estratto solo il contenuto tra "&lt;" e "&gt;". Vedi docs/formato-asm.md.
/// Viti/dadi/rosette normalizzati UNI (e cuscinetti CUSCI_...) sono family table Creo: il
/// nome che compare nel binario (es. "R6--_-_U6592&lt;UNI6592&gt;",
/// "2510060303&lt;CUSCI_ACBB7&gt;") è la singola istanza, non un file separato su disco - il
/// file reale indicizzato è il generico "uni6592.prt.&lt;versione&gt;"/
/// "cusci_acbb7.prt.&lt;versione&gt;", quindi va estratto solo il contenuto tra "&lt;" e
/// "&gt;". Vedi docs/formato-asm.md.
///
/// Lettera di revisione e suffisso "_..." possono comparire insieme sullo stesso codice
/// (es. "9284200366A_C01"), e il suffisso dopo "_" può essere puramente numerico (es.
@@ -34,20 +36,32 @@ namespace CadSearchGui;
/// basta: può trattarsi del riferimento al nome di un altro assieme che lo contiene (non
/// noto a priori, quindi non escludibile per confronto diretto come il proprio codice) o
/// di un residuo di feature soppresse/superate (vedi docs/formato-asm.md). Per questi si
/// richiede anche un vero placement "Componente ID N (&lt;codice&gt;...)"/
/// "Component id N (&lt;codice&gt;...)" (la stringa cambia con la localizzazione Creo del
/// file). Non si applica alle varianti con suffisso o bracket UNI: in alcuni file il
/// placement riporta solo il nome istanza/generico senza il dettaglio del suffisso o del
/// bracket, che vive altrove nei metadati - lì resta necessaria la sola euristica delle
/// occorrenze ripetute.
/// richiede anche un vero placement, in una delle due forme osservate nel binario:
/// "Componente ID N (&lt;codice&gt;...)"/"Component id N (&lt;codice&gt;...)" (la stringa
/// cambia con la localizzazione Creo del file), oppure il codice ripetuto due volte di
/// seguito separato da un singolo byte NUL ("&lt;codice&gt;\0&lt;codice&gt;\0", osservato per
/// componenti di family table non-UNI, es. cuscinetti "CUSCI_..."). Non si applica alle
/// varianti con suffisso o bracket UNI: in alcuni file il placement riporta solo il nome
/// istanza/generico senza il dettaglio del suffisso o del bracket, che vive altrove nei
/// metadati - lì resta necessaria la sola euristica delle occorrenze ripetute.
/// </summary>
static class AssemblyComponentScanner
{
private static readonly Regex CodiceRegex = new(
@"(?<!ModelView#)\b\d{8,12}[A-Za-z]{0,2}(?:_[A-Za-z]?\d{2,4})?\b|(?<=<)UNI\d+(?=>)", RegexOptions.Compiled);
@"(?<!ModelView#)\b\d{8,12}[A-Za-z]{0,2}(?:_[A-Za-z]?\d{2,4})?\b|(?<=<)(?:UNI\d+|CUSCI_[A-Za-z0-9]+)(?=>)",
RegexOptions.Compiled);
private static readonly Regex CodiceNudoRegex = new(@"^\d{8,12}[A-Za-z]{0,2}$", RegexOptions.Compiled);
private const int OccorrenzeMinime = 2;
/// <summary>Codice radice (parte prima del primo "_") di un codice componente. Un
/// assieme con suffisso "_D.../_C..." referenzia sempre al suo interno anche l'assieme
/// generale a cui appartiene (codice radice, senza suffisso) - riferimento fantasma
/// verso l'alto, non un componente vero (vedi docs/formato-asm.md). Chi fa una
/// risoluzione ricorsiva dell'albero deve seminare l'insieme dei codici già visitati
/// anche con la radice del codice di partenza, non solo col codice stesso, altrimenti
/// risale fino all'assieme generale e riporta dentro l'intero albero del macchinario.</summary>
public static string CodiceRadice(string codice) => codice.Split('_')[0];
public static HashSet<string> EstraiCodiciReferenziati(string percorsoAsm)
{
var testo = Encoding.Latin1.GetString(File.ReadAllBytes(percorsoAsm));
@@ -59,7 +73,8 @@ static class AssemblyComponentScanner
var candidati = occorrenze.Where(kv => kv.Value >= OccorrenzeMinime).Select(kv => kv.Key).ToHashSet();
bool HaPlacement(string codice) =>
Regex.IsMatch(testo, $@"Component(?:e)? id \d+ \([^()]*{Regex.Escape(codice)}[^()]*\)", RegexOptions.IgnoreCase);
Regex.IsMatch(testo, $@"Component(?:e)? id \d+ \([^()]*{Regex.Escape(codice)}[^()]*\)", RegexOptions.IgnoreCase)
|| testo.Contains(codice + "\0" + codice + "\0");
candidati.RemoveWhere(c => CodiceNudoRegex.IsMatch(c) && !HaPlacement(c));
return candidati;
+9 -2
View File
@@ -671,8 +671,10 @@ public partial class Form1 : Form
IEnumerable<string> asmIniziali, IProgress<(int Fatti, int Totale)>? progress = null)
{
var daScansionare = new Queue<string>(asmIniziali);
var codiciIniziali = daScansionare.Select(f => Path.GetFileName(f).Split('.')[0]);
var codiciVisti = new HashSet<string>(
daScansionare.Select(f => Path.GetFileName(f).Split('.')[0]), StringComparer.OrdinalIgnoreCase);
codiciIniziali.Concat(codiciIniziali.Select(AssemblyComponentScanner.CodiceRadice)),
StringComparer.OrdinalIgnoreCase);
var trovati = new List<(string Codice, string Tipo, string Percorso)>();
var codiciNonTrovati = new List<string>();
@@ -827,7 +829,12 @@ public partial class Form1 : Form
var daVisitare = new Queue<string>();
daVisitare.Enqueue(percorsoAsmIniziale);
var fileVisitati = new HashSet<string> { percorsoAsmIniziale };
var codiciVisti = new HashSet<string> { Path.GetFileName(percorsoAsmIniziale).Split('.')[0] };
var codiceIniziale = Path.GetFileName(percorsoAsmIniziale).Split('.')[0];
var codiciVisti = new HashSet<string>
{
codiceIniziale,
AssemblyComponentScanner.CodiceRadice(codiceIniziale),
};
while (daVisitare.Count > 0)
{
+58 -27
View File
@@ -73,17 +73,43 @@ non essendo un componente. A differenza del proprio codice, non è noto a
priori: dipende da dove/come il file è stato aperto o da chi lo referenzia,
quindi non si può escludere per confronto diretto come per il caso
precedente. La regola generale che discrimina questo caso: un componente
vero compare **sempre** anche dentro un vero placement
`Componente ID N (<codice>...)`/`Component id N (<codice>...)` (la stringa
cambia con la localizzazione Creo con cui il file è stato salvato - osservate
sia italiana sia inglese); un riferimento incidentale al nome di un altro
assieme, anche se ripetuto più volte, non compare mai in quel contesto.
`scripts/ExtractCreoStrings.cs` applica questa regola ai codici "nudi" (senza
suffisso `_...` e senza bracket UNI - vedi sotto perché limitata a questi):
un codice ripetuto ma mai trovato dentro un placement viene scartato. Questo
NON copre i casi fantasma/soppressi sotto, che hanno un placement
sintatticamente identico a uno vero e restano quindi indistinguibili a
livello testuale.
vero compare **sempre** anche dentro un vero placement, in una delle due
forme osservate nel binario:
- `Componente ID N (<codice>...)`/`Component id N (<codice>...)` (la stringa
cambia con la localizzazione Creo con cui il file è stato salvato -
osservate sia italiana sia inglese);
- il codice ripetuto due volte di seguito separato da un singolo byte NUL,
`<codice>\0<codice>\0` (osservato per componenti di family table non-UNI,
es. cuscinetti `CUSCI_...` - vedi sotto; il placement testuale
"Componente ID" non li copre, ma questa coppia doppia sì).
Un riferimento incidentale al nome di un altro assieme, anche se ripetuto più
volte, non compare mai in nessuna delle due forme. `scripts/ExtractCreoStrings.cs`
e `AssemblyComponentScanner.cs` applicano questa regola ai codici "nudi" (senza
suffisso `_...` e senza bracket family table - vedi sotto perché limitata a
questi): un codice ripetuto ma mai trovato dentro un placement/coppia NUL
viene scartato. Questo NON copre i casi fantasma/soppressi sotto, che hanno un
placement sintatticamente identico a uno vero e restano quindi indistinguibili
a livello testuale.
**Il codice radice del proprio assieme (assieme generale, senza suffisso
`_D.../_C...`).** Caso particolare e sistematico del precedente, non solo
incidentale: un assieme suddiviso in sotto-assiemi con suffisso
(`9284208188_D600.asm`, `_D610.asm`, ...) referenzia **sempre** al suo interno
anche il codice radice dell'assieme generale (`9284208188`, senza suffisso) -
il "padre" a cui tutti i `_D...` appartengono nella struttura Creo - con un
vero placement sintatticamente identico a un componente reale. Non è un
componente di quel sotto-assieme, è la risalita verso l'assieme che lo
contiene. Osservato su `9284208188_D600.asm`: `Componente ID N (9284208188...)`.
Se seguito in una risoluzione ricorsiva, riporta dentro l'intero albero del
prodotto invece del solo sotto-assieme richiesto: 598 componenti raccolti
invece dei ~65 reali. A differenza del caso generico sopra, qui il codice
"padre" _è_ prevedibile a priori (è la parte del proprio codice prima del
primo `_`), quindi va escluso esplicitamente seminando l'insieme dei codici
già visitati anche con la radice del codice di partenza, non solo con il
codice stesso — non basta il controllo generico sul placement, perché il
placement qui è reale. Vedi `AssemblyComponentScanner.CodiceRadice`.
**Riferimenti fantasma a codici superati/rinominati.** Caso osservato:
`9244207061.asm` contiene un riferimento a `9044207061` (stesso numero, prima
@@ -134,30 +160,35 @@ table il placement può mancare del dettaglio, quindi resta necessaria la
sola euristica delle occorrenze ripetute - meno precisa, ma l'unica
applicabile senza rischiare falsi negativi su casi come questi.
## Viti/dadi/rosette normalizzati (family table UNI)
## Componenti normalizzati (family table UNI, CUSCI, ...)
I componenti di minuteria standard (viti, dadi, rosette UNI) sono definiti in
Creo come **family table**: un file generico (es. `uni6592.prt`) con tante
righe/istanze quante sono le varianti dimensionali. Nel binario dell'assieme
compaiono così:
I componenti di minuteria standard (viti, dadi, rosette UNI) e i cuscinetti
(`CUSCI_...`) sono definiti in Creo come **family table**: un file generico
(es. `uni6592.prt`, `cusci_acbb7.prt`) con tante righe/istanze quante sono le
varianti dimensionali. Nel binario dell'assieme compaiono così:
```
Componente ID 28755 (R6--_-_U6592<UNI6592>.PRT)
2510060303<CUSCI_ACBB7>
```
`R6--_-_U6592` è il nome della singola istanza (riga della family table), **non
un file separato su disco** — sulla share esiste solo `uni6592.prt.<versione>`
(verificato in `Unificati/`). Cercare l'istanza intera nell'indice fallisce
sempre; va estratto solo il contenuto tra `<` e `>` (`UNI6592`), che coincide
col nome del file generico realmente indicizzato:
`R6--_-_U6592` e `2510060303` sono il nome/codice della singola istanza (riga
della family table), **non un file separato su disco** — sulla share esiste
solo il generico (`uni6592.prt.<versione>`, `cusci_acbb7.prt.<versione>`,
verificato in `Unificati/`). Cercare l'istanza intera nell'indice fallisce
sempre; va estratto solo il contenuto tra `<` e `>` (`UNI6592`, `CUSCI_ACBB7`),
che coincide col nome del file generico realmente indicizzato:
```
regex aggiuntiva: (?<=<)UNI\d+(?=>)
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
referenziati" dell'app).
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
realmente indicizzato in `Unificati/`.
## Risoluzione ricorsiva dell'albero
@@ -172,11 +203,11 @@ livello) serve una BFS:
3. Un codice non trovato nell'indice va scartato/segnalato: o è rumore
binario, o è un componente non ancora indicizzato.
Stessa logica di estrazione candidati in due punti del codice, con scopi
diversi:
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) — solo 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").
- [`scripts/ExtractCreoStrings.cs`](../scripts/ExtractCreoStrings.cs) — estrazione standalone da riga di comando, ma **un solo livello**: riporta solo i riferimenti diretti del file passato (come li mostra Creo per quell'assieme), senza scendere ricorsivamente nei sotto-assiemi. Usato per confrontare l'euristica contro l'albero Creo di un singolo file durante il reverse engineering; non genera una distinta base completa multi-livello.
- [`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.
- [`scripts/ExtractCreoStrings.cs`](../scripts/ExtractCreoStrings.cs) — stessa BFS ricorsiva standalone da riga di comando (copia indipendente della stessa euristica, nessun `ProjectReference` verso la GUI). Usato 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
`(cartella_radice, codice, tipo_file)` - se lo stesso codice esiste in più
+53 -28
View File
@@ -1,11 +1,11 @@
#:package Microsoft.Data.Sqlite@10.0.12
// dotnet run scripts/ExtractCreoStrings.cs -- <file.asm> [cad_index.db] [output.txt]
//
// Estrae dal file .asm i codici dei componenti referenziati DIRETTAMENTE (un solo
// livello, come li mostra Creo per quell'assieme - nessuna espansione ricorsiva nei
// sotto-assiemi). Stessa euristica di CadSearchGui/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.
// 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:
// 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.
using System.Text;
using System.Text.RegularExpressions;
@@ -21,18 +21,19 @@ string asmIniziale = args[0];
string dbPath = args.Length > 1 ? args[1] : "cad_index.db";
string outPath = args.Length > 2 ? args[2] : "BOM.txt";
// Seconda alternativa: viti/dadi/rosette normalizzati UNI, definiti come family table in
// Creo. Compaiono come "Componente ID N (R12--_-_U6592<UNI6592>.PRT)": "R12--_-_U6592" è
// il nome della singola istanza (riga della family table, non un file separato su disco),
// il file reale indicizzato è il generico "uni6592.prt.<versione>" - va quindi estratto
// solo il contenuto tra "<" e ">".
// Seconda alternativa: componenti normalizzati definiti come family table in Creo (viti/
// dadi/rosette UNI, ma anche cuscinetti CUSCI_...). Compaiono come "Componente ID N
// (R12--_-_U6592<UNI6592>.PRT)": "R12--_-_U6592" è il nome della singola istanza (riga
// della family table, non un file separato su disco), il file reale indicizzato è il
// generico "uni6592.prt.<versione>"/"cusci_acbb7.prt.<versione>" - va quindi estratto solo
// il contenuto tra "<" e ">".
// Terza alternativa: varianti dimensionali con suffisso puramente numerico (es. cinghie
// "2702220182_8188", dove "8188" è una misura, non una lettera di revisione/sotto-assieme
// come "_C01"/"_D100") - la lettera dopo "_" è quindi opzionale, non obbligatoria. La
// lettera di revisione e il suffisso "_" possono anche comparire insieme sullo stesso
// codice (es. "9284200366A_C01", componente di un sotto-assieme già di per sé revisionato).
var codiceRegex = new Regex(
@"(?<!ModelView#)\b\d{8,12}[A-Za-z]{0,2}(?:_[A-Za-z]?\d{2,4})?\b|(?<=<)UNI\d+(?=>)",
@"(?<!ModelView#)\b\d{8,12}[A-Za-z]{0,2}(?:_[A-Za-z]?\d{2,4})?\b|(?<=<)(?:UNI\d+|CUSCI_[A-Za-z0-9]+)(?=>)",
RegexOptions.Compiled);
const int occorrenzeMinime = 2;
@@ -48,14 +49,18 @@ HashSet<string> EstraiCodiciReferenziati(string percorsoAsm)
// riferimenti al proprio assieme padre (nome sessione/file) o a codici superati/
// rinominati (caso "fantasma", docs/formato-asm.md) ripetono comunque il codice più
// volte pur non essendo mai un componente vero. Per questi si richiede un vero
// placement "Componente ID N (<codice>...)". "Componente ID"/"Component id": la
// stringa cambia con la localizzazione Creo con cui il file è stato salvato (osservate
// sia italiana sia inglese). Non si applica alle varianti con suffisso "_..." né alle
// istanze family table UNI: in alcuni file il placement riporta solo il nome
// istanza/generico senza il dettaglio del suffisso o del bracket "<...>", che vive
// altrove nei metadati - lì l'occorrenza ripetuta resta l'unico segnale disponibile.
// placement, in una delle due forme osservate nel binario: "Componente ID N
// (<codice>...)"/"Component id N (<codice>...)" (la stringa cambia con la
// localizzazione Creo del file), oppure il codice ripetuto due volte di seguito
// separato da un singolo byte NUL ("<codice>\0<codice>\0", osservato per componenti di
// family table non-UNI, es. cuscinetti "CUSCI_..."). Non si applica alle varianti con
// suffisso "_..." né alle istanze family table UNI: in alcuni file il placement
// riporta solo il nome istanza/generico senza il dettaglio del suffisso o del bracket
// "<...>", che vive altrove nei metadati - lì l'occorrenza ripetuta resta l'unico
// segnale disponibile.
bool HaPlacement(string codice) =>
Regex.IsMatch(testo, $@"Component(?:e)? id \d+ \([^()]*{Regex.Escape(codice)}[^()]*\)", RegexOptions.IgnoreCase);
Regex.IsMatch(testo, $@"Component(?:e)? id \d+ \([^()]*{Regex.Escape(codice)}[^()]*\)", RegexOptions.IgnoreCase)
|| testo.Contains(codice + "\0" + codice + "\0");
candidati.RemoveWhere(c => Regex.IsMatch(c, @"^\d{8,12}[A-Za-z]{0,2}$") && !HaPlacement(c));
return candidati;
@@ -98,22 +103,42 @@ Dictionary<string, List<(string Tipo, string Percorso)>> CercaComponenti(IReadOn
return risultato;
}
// Solo i riferimenti diretti del file indicato (un livello), come li mostra Creo per
// quell'assieme - nessuna espansione ricorsiva nei sotto-assiemi.
// Espansione ricorsiva: ogni sotto-assieme (.asm) trovato viene a sua volta letto e i suoi
// componenti aggiunti alla coda, finché non restano solo PRT (foglie) o codici non presenti
// nell'indice - stessa logica di Form1.RisolviAlberoComponenti.
var codiceProprio = Path.GetFileName(asmIniziale).Split('.')[0];
// Un assieme con suffisso "_D..." referenzia sempre al suo interno anche l'assieme
// generale (codice nudo, senza suffisso) a cui appartiene - riferimento fantasma verso
// l'alto, non un componente vero (vedi docs/formato-asm.md). Va escluso subito: se
// seguito, riporta dentro l'intero albero del macchinario invece del solo sotto-assieme
// richiesto (visto su 9284208188_D600 -> 9284208188, che da solo genera 598 falsi
// positivi invece di ~90 componenti reali).
var codiceRadice = codiceProprio.Split('_')[0];
var componentiTrovati = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
var nonTrovati = new List<string>();
var codiciVisti = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { codiceProprio, codiceRadice };
var daScansionare = new Queue<string>(new[] { asmIniziale });
var candidati = EstraiCodiciReferenziati(asmIniziale)
.Where(c => !string.Equals(c, codiceProprio, StringComparison.OrdinalIgnoreCase))
.ToList();
var trovatiPerCodice = CercaComponenti(candidati);
foreach (var codice in candidati)
while (daScansionare.Count > 0)
{
if (trovatiPerCodice[codice].Count == 0)
nonTrovati.Add(codice);
else
var asm = daScansionare.Dequeue();
var candidati = EstraiCodiciReferenziati(asm)
.Where(codiciVisti.Add)
.ToList();
var trovatiPerCodice = CercaComponenti(candidati);
foreach (var codice in candidati)
{
var componenti = trovatiPerCodice[codice];
if (componenti.Count == 0)
{
nonTrovati.Add(codice);
continue;
}
componentiTrovati.Add(codice);
foreach (var (tipo, percorso) in componenti)
if (tipo == "asm")
daScansionare.Enqueue(percorso);
}
}
File.WriteAllLines(outPath, componentiTrovati);