feat: rewrite PushSelected to use polygon directional-distance

Parts now push based on actual cut geometry instead of bounding boxes,
allowing irregular shapes to nestle together with PartSpacing gap.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 18:29:50 -05:00
parent 6332298912
commit 3931012079
+67 -65
View File
@@ -752,87 +752,89 @@ namespace OpenNest.Controls
public void PushSelected(PushDirection direction) public void PushSelected(PushDirection direction)
{ {
var boxes = new List<Box>(); // Build line segments for all stationary parts.
var stationaryParts = parts.Where(p => !p.IsSelected && !SelectedParts.Contains(p)).ToList();
var stationaryLines = new List<List<Line>>(stationaryParts.Count);
var stationaryBoxes = new List<Box>(stationaryParts.Count);
foreach (var part in parts.Where(p => !p.IsSelected).ToList()) foreach (var part in stationaryParts)
{ {
if (!SelectedParts.Contains(part)) stationaryLines.Add(Helper.GetPartLines(part.BasePart));
boxes.Add(part.BoundingBox); stationaryBoxes.Add(part.BoundingBox);
} }
var workArea = Plate.WorkArea(); var workArea = Plate.WorkArea();
var distance = double.MaxValue; var distance = double.MaxValue;
var offset = new Vector();
switch (direction) foreach (var selected in SelectedParts)
{ {
case PushDirection.Down: // Get offset lines for the moving part.
SelectedParts.ForEach(part => var movingLines = Plate.PartSpacing > 0
? Helper.GetOffsetPartLines(selected.BasePart, Plate.PartSpacing)
: Helper.GetPartLines(selected.BasePart);
var movingBox = selected.BoundingBox;
// Check geometry distance against each stationary part.
for (int i = 0; i < stationaryLines.Count; i++)
{
// Early-out: skip if bounding boxes don't overlap on the perpendicular axis.
var stBox = stationaryBoxes[i];
bool perpOverlap;
switch (direction)
{ {
var d1 = Helper.ClosestDistanceDown(part.BoundingBox.Offset(Plate.PartSpacing), boxes); case PushDirection.Left:
case PushDirection.Right:
perpOverlap = !(movingBox.Bottom >= stBox.Top || movingBox.Top <= stBox.Bottom);
break;
default: // Up, Down
perpOverlap = !(movingBox.Left >= stBox.Right || movingBox.Right <= stBox.Left);
break;
}
if (d1 < distance) if (!perpOverlap)
distance = d1; continue;
var d2 = part.Bottom - workArea.Bottom; var d = Helper.DirectionalDistance(movingLines, stationaryLines[i], direction);
if (d < distance)
distance = d;
}
if (d2 > 0 && d2 < distance) // Check distance to plate edge (actual geometry bbox, not offset).
distance = d2; double edgeDist;
}); switch (direction)
offset.Y = -distance; {
break; case PushDirection.Left:
edgeDist = selected.Left - workArea.Left;
break;
case PushDirection.Right:
edgeDist = workArea.Right - selected.Right;
break;
case PushDirection.Up:
edgeDist = workArea.Top - selected.Top;
break;
default: // Down
edgeDist = selected.Bottom - workArea.Bottom;
break;
}
case PushDirection.Left: if (edgeDist > 0 && edgeDist < distance)
SelectedParts.ForEach(part => distance = edgeDist;
{
var d1 = Helper.ClosestDistanceLeft(part.BoundingBox.Offset(Plate.PartSpacing), boxes);
if (d1 < distance)
distance = d1;
var d2 = part.Left - workArea.Left;
if (d2 > 0 && d2 < distance)
distance = d2;
});
offset.X = -distance;
break;
case PushDirection.Right:
SelectedParts.ForEach(part =>
{
var d1 = Helper.ClosestDistanceRight(part.BoundingBox.Offset(Plate.PartSpacing), boxes);
if (d1 < distance)
distance = d1;
var d2 = workArea.Right - part.Right;
if (d2 > 0 && d2 < distance)
distance = d2;
});
offset.X = distance;
break;
case PushDirection.Up:
SelectedParts.ForEach(part =>
{
var d1 = Helper.ClosestDistanceUp(part.BoundingBox.Offset(Plate.PartSpacing), boxes);
if (d1 < distance)
distance = d1;
var d2 = workArea.Top - part.Top;
if (d2 > 0 && d2 < distance)
distance = d2;
});
offset.Y = distance;
break;
} }
if (distance < double.MaxValue) if (distance < double.MaxValue && distance > 0)
{ {
var offset = new Vector();
switch (direction)
{
case PushDirection.Left: offset.X = -distance; break;
case PushDirection.Right: offset.X = distance; break;
case PushDirection.Up: offset.Y = distance; break;
case PushDirection.Down: offset.Y = -distance; break;
}
SelectedParts.ForEach(p => p.Offset(offset)); SelectedParts.ForEach(p => p.Offset(offset));
Invalidate(); Invalidate();
} }