diff --git a/OpenNest.Engine/BestFit/BestFitCache.cs b/OpenNest.Engine/BestFit/BestFitCache.cs new file mode 100644 index 0000000..04b7613 --- /dev/null +++ b/OpenNest.Engine/BestFit/BestFitCache.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace OpenNest.Engine.BestFit +{ + public static class BestFitCache + { + private const double StepSize = 0.25; + + private static readonly ConcurrentDictionary> _cache = + new ConcurrentDictionary>(); + + public static Func CreateEvaluator { get; set; } + + public static List GetOrCompute( + Drawing drawing, double plateWidth, double plateHeight, + double spacing) + { + var key = new CacheKey(drawing, plateWidth, plateHeight, spacing); + + if (_cache.TryGetValue(key, out var cached)) + return cached; + + IPairEvaluator evaluator = null; + + try + { + if (CreateEvaluator != null) + { + try { evaluator = CreateEvaluator(drawing, spacing); } + catch { /* fall back to default evaluator */ } + } + + var finder = new BestFitFinder(plateWidth, plateHeight, evaluator); + var results = finder.FindBestFits(drawing, spacing, StepSize); + + _cache.TryAdd(key, results); + return results; + } + finally + { + (evaluator as IDisposable)?.Dispose(); + } + } + + public static void Invalidate(Drawing drawing) + { + foreach (var key in _cache.Keys) + { + if (ReferenceEquals(key.Drawing, drawing)) + _cache.TryRemove(key, out _); + } + } + + public static void Clear() + { + _cache.Clear(); + } + + private readonly struct CacheKey : IEquatable + { + public readonly Drawing Drawing; + public readonly double PlateWidth; + public readonly double PlateHeight; + public readonly double Spacing; + + public CacheKey(Drawing drawing, double plateWidth, double plateHeight, double spacing) + { + Drawing = drawing; + PlateWidth = plateWidth; + PlateHeight = plateHeight; + Spacing = spacing; + } + + public bool Equals(CacheKey other) + { + return ReferenceEquals(Drawing, other.Drawing) && + PlateWidth == other.PlateWidth && + PlateHeight == other.PlateHeight && + Spacing == other.Spacing; + } + + public override bool Equals(object obj) => obj is CacheKey other && Equals(other); + + public override int GetHashCode() + { + unchecked + { + var hash = RuntimeHelpers.GetHashCode(Drawing); + hash = hash * 397 ^ PlateWidth.GetHashCode(); + hash = hash * 397 ^ PlateHeight.GetHashCode(); + hash = hash * 397 ^ Spacing.GetHashCode(); + return hash; + } + } + } + } +} diff --git a/OpenNest.Engine/BestFit/RotationSlideStrategy.cs b/OpenNest.Engine/BestFit/RotationSlideStrategy.cs index 6079385..4e09da2 100644 --- a/OpenNest.Engine/BestFit/RotationSlideStrategy.cs +++ b/OpenNest.Engine/BestFit/RotationSlideStrategy.cs @@ -35,6 +35,16 @@ namespace OpenNest.Engine.BestFit part1, part2Template, drawing, spacing, stepSize, PushDirection.Down, candidates, ref testNumber); + // Try pushing right (approach from left — finds concave interlocking) + GenerateCandidatesForAxis( + part1, part2Template, drawing, spacing, stepSize, + PushDirection.Right, candidates, ref testNumber); + + // Try pushing up (approach from below — finds concave interlocking) + GenerateCandidatesForAxis( + part1, part2Template, drawing, spacing, stepSize, + PushDirection.Up, candidates, ref testNumber); + return candidates; } @@ -77,11 +87,15 @@ namespace OpenNest.Engine.BestFit { var part2 = (Part)part2Template.Clone(); - // Place part2 far away along push axis, at perpendicular offset + // Place part2 far away along push axis, at perpendicular offset. + // Left/Down: start on the positive side; Right/Up: start on the negative side. + var isPositiveStart = pushDir == PushDirection.Left || pushDir == PushDirection.Down; + var startPos = isPositiveStart ? pushStartOffset : -pushStartOffset; + if (isHorizontalPush) - part2.Offset(pushStartOffset, offset); + part2.Offset(startPos, offset); else - part2.Offset(offset, pushStartOffset); + part2.Offset(offset, startPos); // Get part2's offset lines (half-spacing outward) var part2Lines = Helper.GetOffsetPartLines(part2, halfSpacing); diff --git a/OpenNest.Engine/NestEngine.cs b/OpenNest.Engine/NestEngine.cs index e829e2c..5b99476 100644 --- a/OpenNest.Engine/NestEngine.cs +++ b/OpenNest.Engine/NestEngine.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using OpenNest.Engine.BestFit; @@ -20,8 +19,6 @@ namespace OpenNest public NestDirection NestDirection { get; set; } - public Func CreateEvaluator { get; set; } - public bool Fill(NestItem item) { return Fill(item, Plate.WorkArea()); @@ -151,16 +148,9 @@ namespace OpenNest private List FillWithPairs(NestItem item, Box workArea) { - IPairEvaluator evaluator = null; - - if (CreateEvaluator != null) - { - try { evaluator = CreateEvaluator(item.Drawing, Plate.PartSpacing); } - catch { /* GPU not available, fall back to geometry */ } - } - - var finder = new BestFitFinder(Plate.Size.Width, Plate.Size.Height, evaluator); - var bestFits = finder.FindBestFits(item.Drawing, Plate.PartSpacing, stepSize: 0.25); + var bestFits = BestFitCache.GetOrCompute( + item.Drawing, Plate.Size.Width, Plate.Size.Height, + Plate.PartSpacing); var keptResults = bestFits.Where(r => r.Keep).Take(50).ToList(); Debug.WriteLine($"[FillWithPairs] Total: {bestFits.Count}, Kept: {bestFits.Count(r => r.Keep)}, Trying: {keptResults.Count}"); @@ -187,8 +177,6 @@ namespace OpenNest best = parts; } - (evaluator as IDisposable)?.Dispose(); - Debug.WriteLine($"[FillWithPairs] Best pair result: {best?.Count ?? 0} parts"); return best ?? new List(); } diff --git a/OpenNest/Actions/ActionClone.cs b/OpenNest/Actions/ActionClone.cs index bcc6497..5879048 100644 --- a/OpenNest/Actions/ActionClone.cs +++ b/OpenNest/Actions/ActionClone.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Windows.Forms; using OpenNest.Controls; using OpenNest.Geometry; -using OpenNest.Gpu; namespace OpenNest.Actions { @@ -173,7 +172,6 @@ namespace OpenNest.Actions { var plate = plateView.Plate; var engine = new NestEngine(plate); - engine.CreateEvaluator = GpuEvaluatorFactory.Create; var groupParts = parts.Select(p => p.BasePart).ToList(); var bounds = plate.WorkArea(); diff --git a/OpenNest/Actions/ActionFillArea.cs b/OpenNest/Actions/ActionFillArea.cs index 1a2cddf..aec5099 100644 --- a/OpenNest/Actions/ActionFillArea.cs +++ b/OpenNest/Actions/ActionFillArea.cs @@ -1,7 +1,6 @@ using System.ComponentModel; using System.Windows.Forms; using OpenNest.Controls; -using OpenNest.Gpu; namespace OpenNest.Actions { @@ -26,7 +25,6 @@ namespace OpenNest.Actions private void FillArea() { var engine = new NestEngine(plateView.Plate); - engine.CreateEvaluator = GpuEvaluatorFactory.Create; engine.Fill(new NestItem { Drawing = drawing }, SelectedArea); plateView.Invalidate(); diff --git a/OpenNest/Forms/BestFitViewerForm.cs b/OpenNest/Forms/BestFitViewerForm.cs index c801379..8d1f438 100644 --- a/OpenNest/Forms/BestFitViewerForm.cs +++ b/OpenNest/Forms/BestFitViewerForm.cs @@ -11,7 +11,6 @@ namespace OpenNest.Forms private const int Columns = 5; private const int RowHeight = 300; private const int MaxResults = 50; - private const double ViewerStepSize = 1.0; private static readonly Color KeptColor = Color.FromArgb(0, 0, 100); private static readonly Color DroppedColor = Color.FromArgb(100, 0, 0); @@ -56,8 +55,8 @@ namespace OpenNest.Forms { var sw = Stopwatch.StartNew(); - var finder = new BestFitFinder(plate.Size.Width, plate.Size.Height); - var results = finder.FindBestFits(drawing, plate.PartSpacing, ViewerStepSize); + var results = BestFitCache.GetOrCompute( + drawing, plate.Size.Width, plate.Size.Height, plate.PartSpacing); var findMs = sw.ElapsedMilliseconds; var total = results.Count; diff --git a/OpenNest/Forms/MainForm.cs b/OpenNest/Forms/MainForm.cs index 2628ac5..1b39959 100644 --- a/OpenNest/Forms/MainForm.cs +++ b/OpenNest/Forms/MainForm.cs @@ -688,7 +688,6 @@ namespace OpenNest.Forms : activeForm.PlateView.Plate; var engine = new NestEngine(plate); - engine.CreateEvaluator = GpuEvaluatorFactory.Create; if (!engine.Pack(items)) break; @@ -762,7 +761,6 @@ namespace OpenNest.Forms return; var engine = new NestEngine(activeForm.PlateView.Plate); - engine.CreateEvaluator = GpuEvaluatorFactory.Create; engine.Fill(new NestItem { Drawing = drawing diff --git a/OpenNest/MainApp.cs b/OpenNest/MainApp.cs index 67660c0..0669280 100644 --- a/OpenNest/MainApp.cs +++ b/OpenNest/MainApp.cs @@ -1,6 +1,8 @@ using System; using System.Windows.Forms; +using OpenNest.Engine.BestFit; using OpenNest.Forms; +using OpenNest.Gpu; namespace OpenNest { @@ -11,6 +13,7 @@ namespace OpenNest { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); + BestFitCache.CreateEvaluator = GpuEvaluatorFactory.Create; Application.Run(new MainForm()); } }