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:
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user