fix: add overlap validation guards to FillExtents and StripeFiller
FillExtents falls back to the unadjusted column when iterative pair adjustment shifts parts enough to cause genuine overlap. StripeFiller rejects grid results where bounding boxes overlap, which can occur when angle convergence produces slightly off-axis rotations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -47,13 +47,21 @@ namespace OpenNest.Engine.Fill
|
||||
|
||||
var adjusted = AdjustColumn(pair.Value, column, token);
|
||||
|
||||
// The iterative pair adjustment can shift parts enough to cause
|
||||
// genuine overlap. Fall back to the unadjusted column when this happens.
|
||||
if (HasOverlappingParts(adjusted))
|
||||
{
|
||||
Debug.WriteLine("[FillExtents] Adjusted column has overlaps, using unadjusted");
|
||||
adjusted = column;
|
||||
}
|
||||
|
||||
NestEngineBase.ReportProgress(progress, new ProgressReport
|
||||
{
|
||||
Phase = NestPhase.Extents,
|
||||
PlateNumber = plateNumber,
|
||||
Parts = adjusted,
|
||||
WorkArea = workArea,
|
||||
Description = $"Extents: adjusted column {adjusted.Count} parts",
|
||||
Description = $"Extents: column {adjusted.Count} parts",
|
||||
});
|
||||
|
||||
var result = RepeatColumns(adjusted, token);
|
||||
@@ -386,5 +394,31 @@ namespace OpenNest.Engine.Fill
|
||||
part.BoundingBox.Left >= workArea.Left - Tolerance.Epsilon &&
|
||||
part.BoundingBox.Bottom >= workArea.Bottom - Tolerance.Epsilon;
|
||||
}
|
||||
|
||||
private static bool HasOverlappingParts(List<Part> parts)
|
||||
{
|
||||
for (var i = 0; i < parts.Count; i++)
|
||||
{
|
||||
var b1 = parts[i].BoundingBox;
|
||||
|
||||
for (var j = i + 1; j < parts.Count; j++)
|
||||
{
|
||||
var b2 = parts[j].BoundingBox;
|
||||
|
||||
var overlapX = System.Math.Min(b1.Right, b2.Right)
|
||||
- System.Math.Max(b1.Left, b2.Left);
|
||||
var overlapY = System.Math.Min(b1.Top, b2.Top)
|
||||
- System.Math.Max(b1.Bottom, b2.Bottom);
|
||||
|
||||
if (overlapX <= Tolerance.Epsilon || overlapY <= Tolerance.Epsilon)
|
||||
continue;
|
||||
|
||||
if (parts[i].Intersects(parts[j], out _))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +158,15 @@ public class StripeFiller
|
||||
if (gridParts.Count == 0)
|
||||
return null;
|
||||
|
||||
// Reject results where bounding boxes overlap — the angle convergence
|
||||
// can produce slightly off-axis rotations where FillLinear's copy
|
||||
// distance calculation doesn't fully account for the rotated geometry.
|
||||
if (HasOverlappingParts(gridParts))
|
||||
{
|
||||
Debug.WriteLine($"[StripeFiller] Rejected grid: overlapping bounding boxes detected");
|
||||
return null;
|
||||
}
|
||||
|
||||
var allParts = new List<Part>(gridParts);
|
||||
|
||||
var remnantParts = FillRemnant(gridParts, primaryAxis);
|
||||
@@ -470,4 +479,34 @@ public class StripeFiller
|
||||
{
|
||||
return axis == NestDirection.Horizontal ? box.Width : box.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if any pair of parts geometrically overlap. Uses bounding box
|
||||
/// pre-filtering for performance, then falls back to shape intersection.
|
||||
/// </summary>
|
||||
private static bool HasOverlappingParts(List<Part> parts)
|
||||
{
|
||||
for (var i = 0; i < parts.Count; i++)
|
||||
{
|
||||
var b1 = parts[i].BoundingBox;
|
||||
|
||||
for (var j = i + 1; j < parts.Count; j++)
|
||||
{
|
||||
var b2 = parts[j].BoundingBox;
|
||||
|
||||
var overlapX = System.Math.Min(b1.Right, b2.Right)
|
||||
- System.Math.Max(b1.Left, b2.Left);
|
||||
var overlapY = System.Math.Min(b1.Top, b2.Top)
|
||||
- System.Math.Max(b1.Bottom, b2.Bottom);
|
||||
|
||||
if (overlapX <= Tolerance.Epsilon || overlapY <= Tolerance.Epsilon)
|
||||
continue;
|
||||
|
||||
if (parts[i].Intersects(parts[j], out _))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user