fix: handle multi-part pattern copy distance in FillLinear

FindPatternCopyDistance now checks every pair of parts across adjacent
patterns so that multi-part patterns (e.g. interlocking pairs) maintain
correct spacing between ALL parts, not just the bounding boxes. The
original single-part logic is preserved as a fast path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 20:55:12 -05:00
parent 1d46ce2298
commit 43a27df73c

View File

@@ -85,8 +85,74 @@ namespace OpenNest
/// <summary>
/// Finds the geometry-aware copy distance between two identical patterns along an axis.
/// Checks every pair of parts across adjacent patterns so that multi-part
/// patterns (e.g. interlocking pairs) maintain spacing between ALL parts.
/// </summary>
private double FindPatternCopyDistance(Pattern patternA, NestDirection direction)
{
if (patternA.Parts.Count <= 1)
return FindSinglePartPatternCopyDistance(patternA, direction);
var bboxDim = GetDimension(patternA.BoundingBox, direction);
var pushDir = GetPushDirection(direction);
var opposite = Helper.OppositeDirection(pushDir);
// Compute a starting offset large enough that every part-pair in
// patternB has its offset geometry beyond patternA's raw geometry.
var startOffset = bboxDim;
foreach (var partA in patternA.Parts)
{
var aUpper = direction == NestDirection.Horizontal
? partA.BoundingBox.Right : partA.BoundingBox.Top;
foreach (var refB in patternA.Parts)
{
var bLower = direction == NestDirection.Horizontal
? refB.BoundingBox.Left : refB.BoundingBox.Bottom;
var required = aUpper - bLower + PartSpacing + Tolerance.Epsilon;
if (required > startOffset)
startOffset = required;
}
}
var patternB = patternA.Clone(MakeOffset(direction, startOffset));
var maxCopyDistance = 0.0;
foreach (var partB in patternB.Parts)
{
var movingLines = Helper.GetOffsetPartLines(partB, PartSpacing, pushDir);
foreach (var partA in patternA.Parts)
{
var stationaryLines = Helper.GetPartLines(partA, opposite);
var slideDistance = Helper.DirectionalDistance(movingLines, stationaryLines, pushDir);
if (slideDistance >= double.MaxValue || slideDistance < 0)
continue; // No geometric interaction — pair doesn't constrain distance.
var copyDist = startOffset - slideDistance;
if (copyDist > maxCopyDistance)
maxCopyDistance = copyDist;
}
}
// Fallback: if no pair interacted (shouldn't happen for real parts),
// use the simple bounding-box + spacing distance.
if (maxCopyDistance <= 0)
return bboxDim + PartSpacing;
return maxCopyDistance;
}
/// <summary>
/// Fast path for single-part patterns — no cross-part conflicts possible.
/// </summary>
private double FindSinglePartPatternCopyDistance(Pattern patternA, NestDirection direction)
{
var bboxDim = GetDimension(patternA.BoundingBox, direction);
var pushDir = GetPushDirection(direction);