mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-29 05:51:05 -04:00
DefaultPlateFiller becomes PlateFiller(plate, policy, layout); PlateFillerBase and StripPlateFiller are gone. PlateLayout.FillThenPack is the old orchestrator path (Fill and both Remnants); PlateLayout.Strips is the old StripPlateFiller.Nest, and Strip is Fill policy plus Strips layout. Lifetimes are unchanged: a strip-layout filler runs every Fill, group Fill and PackArea on a fresh inner filler and gives each strip fresh remnant fillers, so learned angles and PlateNumber never leak into them. The comparer, preferred direction, trim axis and angle recording now read the policy directly. The base class's unsorted two-angle BuildAngles is dropped: only DefaultPlateFiller's pipeline called BuildAngles, and Strip never ran it. Layout-neutral: all 29 FillStackCharacterizationTests pins pass unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 lines
3.6 KiB
C#
97 lines
3.6 KiB
C#
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using OpenNest.Engine.Jobs.Placement.Fillers;
|
|
|
|
namespace OpenNest.Engine.ML
|
|
{
|
|
public class BruteForceResult
|
|
{
|
|
public int PartCount { get; set; }
|
|
public double Utilization { get; set; }
|
|
public long TimeMs { get; set; }
|
|
public string LayoutData { get; set; }
|
|
public List<Part> PlacedParts { get; set; }
|
|
public string WinnerEngine { get; set; } = "";
|
|
public long WinnerTimeMs { get; set; }
|
|
public string RunnerUpEngine { get; set; } = "";
|
|
public int RunnerUpPartCount { get; set; }
|
|
public long RunnerUpTimeMs { get; set; }
|
|
public string ThirdPlaceEngine { get; set; } = "";
|
|
public int ThirdPlacePartCount { get; set; }
|
|
public long ThirdPlaceTimeMs { get; set; }
|
|
public List<AngleResult> AngleResults { get; set; } = new();
|
|
}
|
|
|
|
public static class BruteForceRunner
|
|
{
|
|
public static BruteForceResult Run(
|
|
Drawing drawing,
|
|
Plate plate,
|
|
bool forceFullAngleSweep = false
|
|
)
|
|
{
|
|
var filler = new PlateFiller(plate);
|
|
filler.ForceFullAngleSweep = forceFullAngleSweep;
|
|
var item = new NestItem { Drawing = drawing };
|
|
|
|
var sw = Stopwatch.StartNew();
|
|
var parts = filler.Fill(
|
|
item,
|
|
plate.WorkArea(),
|
|
null,
|
|
System.Threading.CancellationToken.None
|
|
);
|
|
sw.Stop();
|
|
|
|
if (parts == null || parts.Count == 0)
|
|
return null;
|
|
|
|
// Rank phase results — winner is explicit, runners-up sorted by count.
|
|
var winner = filler.PhaseResults.FirstOrDefault(r => r.Phase == filler.WinnerPhase);
|
|
var runnerUps = filler
|
|
.PhaseResults.Where(r => r.PartCount > 0 && r.Phase != filler.WinnerPhase)
|
|
.OrderByDescending(r => r.PartCount)
|
|
.ToList();
|
|
|
|
return new BruteForceResult
|
|
{
|
|
PartCount = parts.Count,
|
|
Utilization = CalculateUtilization(parts, plate.Area()),
|
|
TimeMs = sw.ElapsedMilliseconds,
|
|
LayoutData = SerializeLayout(parts),
|
|
PlacedParts = parts,
|
|
WinnerEngine = filler.WinnerPhase.ToString(),
|
|
WinnerTimeMs = winner?.TimeMs ?? 0,
|
|
RunnerUpEngine = runnerUps.Count > 0 ? runnerUps[0].Phase.ToString() : "",
|
|
RunnerUpPartCount = runnerUps.Count > 0 ? runnerUps[0].PartCount : 0,
|
|
RunnerUpTimeMs = runnerUps.Count > 0 ? runnerUps[0].TimeMs : 0,
|
|
ThirdPlaceEngine = runnerUps.Count > 1 ? runnerUps[1].Phase.ToString() : "",
|
|
ThirdPlacePartCount = runnerUps.Count > 1 ? runnerUps[1].PartCount : 0,
|
|
ThirdPlaceTimeMs = runnerUps.Count > 1 ? runnerUps[1].TimeMs : 0,
|
|
AngleResults = filler.AngleResults.ToList(),
|
|
};
|
|
}
|
|
|
|
private static string SerializeLayout(List<Part> parts)
|
|
{
|
|
var data = parts
|
|
.Select(p => new
|
|
{
|
|
X = p.Location.X,
|
|
Y = p.Location.Y,
|
|
R = p.Rotation,
|
|
})
|
|
.ToList();
|
|
return System.Text.Json.JsonSerializer.Serialize(data);
|
|
}
|
|
|
|
private static double CalculateUtilization(List<Part> parts, double plateArea)
|
|
{
|
|
if (plateArea <= 0)
|
|
return 0;
|
|
return parts.Sum(p => p.BaseDrawing.Area) / plateArea;
|
|
}
|
|
}
|
|
}
|