- Convergence loop now uses FillLinear internally to measure actual waste with geometry-aware spacing instead of bounding-box arithmetic - Each candidate pair is tried in both Row and Column orientations to find the shortest perpendicular dimension (more complete stripes) - CompleteStripesOnly flag drops partial stripes; remnant strip is filled by a full engine run (injected via CreateRemnantEngine) - ConvergeStripeAngleShrink tries N+1 narrower pairs as alternative - FillResultCache avoids redundant engine runs on same-sized remnants - CLAUDE.md: note to not commit specs/plans Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
98 lines
3.1 KiB
C#
98 lines
3.1 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.CompilerServices;
|
|
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest.Engine.Fill;
|
|
|
|
/// <summary>
|
|
/// Caches fill results by drawing and box dimensions so repeated fills
|
|
/// of the same size don't recompute. Parts are stored normalized to origin
|
|
/// and offset to the actual location on retrieval.
|
|
/// </summary>
|
|
public static class FillResultCache
|
|
{
|
|
private static readonly ConcurrentDictionary<CacheKey, List<Part>> _cache = new();
|
|
|
|
/// <summary>
|
|
/// Returns a cached fill result for the given drawing and box dimensions,
|
|
/// offset to the target location. Returns null on cache miss.
|
|
/// </summary>
|
|
public static List<Part> Get(Drawing drawing, Box targetBox, double spacing)
|
|
{
|
|
var key = new CacheKey(drawing, targetBox.Width, targetBox.Length, spacing);
|
|
|
|
if (!_cache.TryGetValue(key, out var cached) || cached.Count == 0)
|
|
return null;
|
|
|
|
var offset = targetBox.Location;
|
|
var result = new List<Part>(cached.Count);
|
|
|
|
foreach (var part in cached)
|
|
result.Add(part.CloneAtOffset(offset));
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stores a fill result normalized to origin (0,0).
|
|
/// </summary>
|
|
public static void Store(Drawing drawing, Box sourceBox, double spacing, List<Part> parts)
|
|
{
|
|
if (parts == null || parts.Count == 0)
|
|
return;
|
|
|
|
var key = new CacheKey(drawing, sourceBox.Width, sourceBox.Length, spacing);
|
|
|
|
if (_cache.ContainsKey(key))
|
|
return;
|
|
|
|
var offset = new Vector(-sourceBox.X, -sourceBox.Y);
|
|
var normalized = new List<Part>(parts.Count);
|
|
|
|
foreach (var part in parts)
|
|
normalized.Add(part.CloneAtOffset(offset));
|
|
|
|
_cache.TryAdd(key, normalized);
|
|
}
|
|
|
|
public static void Clear() => _cache.Clear();
|
|
|
|
public static int Count => _cache.Count;
|
|
|
|
private readonly struct CacheKey : System.IEquatable<CacheKey>
|
|
{
|
|
public readonly Drawing Drawing;
|
|
public readonly double Width;
|
|
public readonly double Height;
|
|
public readonly double Spacing;
|
|
|
|
public CacheKey(Drawing drawing, double width, double height, double spacing)
|
|
{
|
|
Drawing = drawing;
|
|
Width = System.Math.Round(width, 2);
|
|
Height = System.Math.Round(height, 2);
|
|
Spacing = spacing;
|
|
}
|
|
|
|
public bool Equals(CacheKey other) =>
|
|
ReferenceEquals(Drawing, other.Drawing) &&
|
|
Width == other.Width && Height == other.Height &&
|
|
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 ^ Width.GetHashCode();
|
|
hash = hash * 397 ^ Height.GetHashCode();
|
|
hash = hash * 397 ^ Spacing.GetHashCode();
|
|
return hash;
|
|
}
|
|
}
|
|
}
|
|
}
|