fix(engine): prevent FillExtents overlap and add strategy filter API
FillExtents vertical copy distance was not clamped, allowing rows to be placed overlapping each other when slide calculations returned large values. Clamp to pairHeight + partSpacing minimum, matching FillLinear. Also add FillStrategyRegistry.SetEnabled() to restrict which strategies run — useful for isolating individual strategies during troubleshooting. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -172,18 +172,12 @@ namespace OpenNest.Engine.Fill
|
|||||||
if (minSlide >= double.MaxValue || minSlide < 0)
|
if (minSlide >= double.MaxValue || minSlide < 0)
|
||||||
return pairHeight + partSpacing;
|
return pairHeight + partSpacing;
|
||||||
|
|
||||||
// Boundaries are inflated by halfSpacing, so when inflated edges touch
|
// Match FillLinear.ComputeCopyDistance: copyDist = startOffset - slide,
|
||||||
// the actual parts have partSpacing gap. Match FillLinear's pattern:
|
// clamped so it never goes below pairHeight + partSpacing to prevent
|
||||||
// startOffset = pairHeight (no extra spacing), copyDist = height - slide.
|
// bounding-box overlap from spurious slide values.
|
||||||
var copyDist = pairHeight - minSlide;
|
var copyDist = pairHeight - minSlide;
|
||||||
|
|
||||||
// Boundaries are inflated by halfSpacing, so the geometry-aware
|
return System.Math.Max(copyDist, pairHeight + partSpacing);
|
||||||
// distance already guarantees partSpacing gap. Only fall back to
|
|
||||||
// bounding-box distance if the calculation produced a non-positive value.
|
|
||||||
if (copyDist <= Tolerance.Epsilon)
|
|
||||||
return pairHeight + partSpacing;
|
|
||||||
|
|
||||||
return copyDist;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double SlideDistance(
|
private static double SlideDistance(
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace OpenNest.Engine.Strategies
|
|||||||
{
|
{
|
||||||
private static readonly List<IFillStrategy> strategies = new();
|
private static readonly List<IFillStrategy> strategies = new();
|
||||||
private static List<IFillStrategy> sorted;
|
private static List<IFillStrategy> sorted;
|
||||||
|
private static HashSet<string> enabledFilter;
|
||||||
|
|
||||||
static FillStrategyRegistry()
|
static FillStrategyRegistry()
|
||||||
{
|
{
|
||||||
@@ -18,7 +19,21 @@ namespace OpenNest.Engine.Strategies
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static IReadOnlyList<IFillStrategy> Strategies =>
|
public static IReadOnlyList<IFillStrategy> Strategies =>
|
||||||
sorted ??= strategies.OrderBy(s => s.Order).ToList();
|
sorted ??= (enabledFilter != null
|
||||||
|
? strategies.Where(s => enabledFilter.Contains(s.Name)).OrderBy(s => s.Order).ToList()
|
||||||
|
: strategies.OrderBy(s => s.Order).ToList());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Restricts the active strategies to only those whose names are listed.
|
||||||
|
/// Pass null to restore all strategies.
|
||||||
|
/// </summary>
|
||||||
|
public static void SetEnabled(params string[] names)
|
||||||
|
{
|
||||||
|
enabledFilter = names != null && names.Length > 0
|
||||||
|
? new HashSet<string>(names, StringComparer.OrdinalIgnoreCase)
|
||||||
|
: null;
|
||||||
|
sorted = null;
|
||||||
|
}
|
||||||
|
|
||||||
public static void LoadFrom(Assembly assembly)
|
public static void LoadFrom(Assembly assembly)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user