feat(console): improve training data collection and best-fit persistence

- Add verbose per-file and per-sheet-size console output during collection
- Skip already-processed parts at the sheet-size level instead of all-or-nothing
- Precompute best-fits once per part and reuse across all sheet sizes
- Clear best-fit cache after each part to prevent memory growth
- Save best-fits in separate bestfits/ zip entries instead of embedding in nest.json
- Filter to Keep=true results only and scope to plate sizes in the nest
- Set nest name to match filename (includes sheet size and part count)
- Add TrainingDatabase with per-run skip logic and SQLite schema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-14 12:39:24 -04:00
parent 3133228fc9
commit d6ffa77f35
8 changed files with 497 additions and 15 deletions
+27
View File
@@ -122,5 +122,32 @@ namespace OpenNest.IO
public double X { get; init; }
public double Y { get; init; }
}
public record BestFitSetDto
{
public double PlateWidth { get; init; }
public double PlateHeight { get; init; }
public double Spacing { get; init; }
public List<BestFitResultDto> Results { get; init; } = new();
}
public record BestFitResultDto
{
public double Part1Rotation { get; init; }
public double Part2Rotation { get; init; }
public double Part2OffsetX { get; init; }
public double Part2OffsetY { get; init; }
public int StrategyType { get; init; }
public int TestNumber { get; init; }
public double CandidateSpacing { get; init; }
public double RotatedArea { get; init; }
public double BoundingWidth { get; init; }
public double BoundingHeight { get; init; }
public double OptimalRotation { get; init; }
public bool Keep { get; init; }
public string Reason { get; init; } = "";
public double TrueArea { get; init; }
public List<double> HullAngles { get; init; } = new();
}
}
}
+50
View File
@@ -6,6 +6,7 @@ using System.IO.Compression;
using System.Linq;
using System.Text.Json;
using OpenNest.CNC;
using OpenNest.Engine.BestFit;
using OpenNest.Geometry;
using static OpenNest.IO.NestFormat;
@@ -35,6 +36,7 @@ namespace OpenNest.IO
var programs = ReadPrograms(dto.Drawings.Count);
var drawingMap = BuildDrawings(dto, programs);
ReadBestFits(drawingMap);
var nest = BuildNest(dto, drawingMap);
zipArchive.Dispose();
@@ -97,6 +99,54 @@ namespace OpenNest.IO
return map;
}
private void ReadBestFits(Dictionary<int, Drawing> drawingMap)
{
foreach (var kvp in drawingMap)
{
var entry = zipArchive.GetEntry($"bestfits/bestfit-{kvp.Key}");
if (entry == null) continue;
using var entryStream = entry.Open();
using var reader = new StreamReader(entryStream);
var json = reader.ReadToEnd();
var sets = JsonSerializer.Deserialize<List<BestFitSetDto>>(json, JsonOptions);
if (sets == null) continue;
PopulateBestFitSets(kvp.Value, sets);
}
}
private void PopulateBestFitSets(Drawing drawing, List<BestFitSetDto> sets)
{
foreach (var set in sets)
{
var results = set.Results.Select(r => new BestFitResult
{
Candidate = new PairCandidate
{
Drawing = drawing,
Part1Rotation = r.Part1Rotation,
Part2Rotation = r.Part2Rotation,
Part2Offset = new Vector(r.Part2OffsetX, r.Part2OffsetY),
StrategyType = r.StrategyType,
TestNumber = r.TestNumber,
Spacing = r.CandidateSpacing
},
RotatedArea = r.RotatedArea,
BoundingWidth = r.BoundingWidth,
BoundingHeight = r.BoundingHeight,
OptimalRotation = r.OptimalRotation,
Keep = r.Keep,
Reason = r.Reason,
TrueArea = r.TrueArea,
HullAngles = r.HullAngles
}).ToList();
BestFitCache.Populate(drawing, set.PlateWidth, set.PlateHeight, set.Spacing, results);
}
}
private Nest BuildNest(NestDto dto, Dictionary<int, Drawing> drawingMap)
{
var nest = new Nest();
+67
View File
@@ -6,6 +6,8 @@ using System.Linq;
using System.Text;
using System.Text.Json;
using OpenNest.CNC;
using OpenNest.Engine.BestFit;
using OpenNest.Geometry;
using OpenNest.Math;
using static OpenNest.IO.NestFormat;
@@ -35,6 +37,7 @@ namespace OpenNest.IO
WriteNestJson(zipArchive);
WritePrograms(zipArchive);
WriteBestFits(zipArchive);
return true;
}
@@ -185,6 +188,70 @@ namespace OpenNest.IO
return list;
}
private List<BestFitSetDto> BuildBestFitDtos(Drawing drawing)
{
var allBestFits = BestFitCache.GetAllForDrawing(drawing);
var sets = new List<BestFitSetDto>();
// Only save best-fit sets for plate sizes actually used in this nest.
var plateSizes = new HashSet<(double, double, double)>();
foreach (var plate in nest.Plates)
plateSizes.Add((plate.Size.Width, plate.Size.Length, plate.PartSpacing));
foreach (var kvp in allBestFits)
{
if (!plateSizes.Contains((kvp.Key.PlateWidth, kvp.Key.PlateHeight, kvp.Key.Spacing)))
continue;
var results = kvp.Value
.Where(r => r.Keep)
.Select(r => new BestFitResultDto
{
Part1Rotation = r.Candidate.Part1Rotation,
Part2Rotation = r.Candidate.Part2Rotation,
Part2OffsetX = r.Candidate.Part2Offset.X,
Part2OffsetY = r.Candidate.Part2Offset.Y,
StrategyType = r.Candidate.StrategyType,
TestNumber = r.Candidate.TestNumber,
CandidateSpacing = r.Candidate.Spacing,
RotatedArea = r.RotatedArea,
BoundingWidth = r.BoundingWidth,
BoundingHeight = r.BoundingHeight,
OptimalRotation = r.OptimalRotation,
Keep = r.Keep,
Reason = r.Reason ?? "",
TrueArea = r.TrueArea,
HullAngles = r.HullAngles ?? new List<double>()
}).ToList();
sets.Add(new BestFitSetDto
{
PlateWidth = kvp.Key.PlateWidth,
PlateHeight = kvp.Key.PlateHeight,
Spacing = kvp.Key.Spacing,
Results = results
});
}
return sets;
}
private void WriteBestFits(ZipArchive zipArchive)
{
foreach (var kvp in drawingDict.OrderBy(k => k.Key))
{
var sets = BuildBestFitDtos(kvp.Value);
if (sets.Count == 0)
continue;
var json = JsonSerializer.Serialize(sets, JsonOptions);
var entry = zipArchive.CreateEntry($"bestfits/bestfit-{kvp.Key}");
using var stream = entry.Open();
using var writer = new StreamWriter(stream, Encoding.UTF8);
writer.Write(json);
}
}
private void WritePrograms(ZipArchive zipArchive)
{
foreach (var kvp in drawingDict.OrderBy(k => k.Key))
+1
View File
@@ -6,6 +6,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\OpenNest.Core\OpenNest.Core.csproj" />
<ProjectReference Include="..\OpenNest.Engine\OpenNest.Engine.csproj" />
<PackageReference Include="ACadSharp" Version="3.1.32" />
</ItemGroup>
</Project>