feat: add reverse push directions for concave interlocking and cache best-fit results
Add PushDirection.Right and PushDirection.Up to RotationSlideStrategy so parts can approach from all four directions. This discovers concave interlocking arrangements (e.g. L-shaped parts nesting into each other's cavities) that the original Left/Down-only slides could never reach. Introduce BestFitCache so best-fit results are computed once at step size 0.25 and shared between the viewer and nesting engine. The GPU evaluator factory is configured once at startup instead of being wired per call site, and NestEngine.CreateEvaluator is removed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
100
OpenNest.Engine/BestFit/BestFitCache.cs
Normal file
100
OpenNest.Engine/BestFit/BestFitCache.cs
Normal file
@@ -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<CacheKey, List<BestFitResult>> _cache =
|
||||
new ConcurrentDictionary<CacheKey, List<BestFitResult>>();
|
||||
|
||||
public static Func<Drawing, double, IPairEvaluator> CreateEvaluator { get; set; }
|
||||
|
||||
public static List<BestFitResult> 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<CacheKey>
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user