refactor(ui): optimize PushSelected with directional filtering and lazy line computation
Extract direction helpers to Helper class (EdgeDistance, DirectionalGap, DirectionToOffset, IsHorizontalDirection) and use them to skip parts not ahead in the push direction or further than the current best distance. Defer line computation until parts survive bounding box checks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1170,6 +1170,47 @@ namespace OpenNest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsHorizontalDirection(PushDirection direction)
|
||||||
|
{
|
||||||
|
return direction is PushDirection.Left or PushDirection.Right;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EdgeDistance(Box box, Box boundary, PushDirection direction)
|
||||||
|
{
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
case PushDirection.Left: return box.Left - boundary.Left;
|
||||||
|
case PushDirection.Right: return boundary.Right - box.Right;
|
||||||
|
case PushDirection.Up: return boundary.Top - box.Top;
|
||||||
|
case PushDirection.Down: return box.Bottom - boundary.Bottom;
|
||||||
|
default: return double.MaxValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector DirectionToOffset(PushDirection direction, double distance)
|
||||||
|
{
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
case PushDirection.Left: return new Vector(-distance, 0);
|
||||||
|
case PushDirection.Right: return new Vector(distance, 0);
|
||||||
|
case PushDirection.Up: return new Vector(0, distance);
|
||||||
|
case PushDirection.Down: return new Vector(0, -distance);
|
||||||
|
default: return new Vector();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double DirectionalGap(Box from, Box to, PushDirection direction)
|
||||||
|
{
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
case PushDirection.Left: return from.Left - to.Right;
|
||||||
|
case PushDirection.Right: return to.Left - from.Right;
|
||||||
|
case PushDirection.Up: return to.Bottom - from.Top;
|
||||||
|
case PushDirection.Down: return from.Bottom - to.Top;
|
||||||
|
default: return double.MaxValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static double ClosestDistanceLeft(Box box, List<Box> boxes)
|
public static double ClosestDistanceLeft(Box box, List<Box> boxes)
|
||||||
{
|
{
|
||||||
var closestDistance = double.MaxValue;
|
var closestDistance = double.MaxValue;
|
||||||
|
|||||||
@@ -937,94 +937,62 @@ namespace OpenNest.Controls
|
|||||||
|
|
||||||
public void PushSelected(PushDirection direction)
|
public void PushSelected(PushDirection direction)
|
||||||
{
|
{
|
||||||
// Build line segments for all stationary parts.
|
|
||||||
var stationaryParts = parts.Where(p => !p.IsSelected && !SelectedParts.Contains(p)).ToList();
|
var stationaryParts = parts.Where(p => !p.IsSelected && !SelectedParts.Contains(p)).ToList();
|
||||||
var stationaryLines = new List<List<Line>>(stationaryParts.Count);
|
var stationaryBoxes = new Box[stationaryParts.Count];
|
||||||
var stationaryBoxes = new List<Box>(stationaryParts.Count);
|
var stationaryLines = new List<Line>[stationaryParts.Count];
|
||||||
|
|
||||||
var opposite = Helper.OppositeDirection(direction);
|
var opposite = Helper.OppositeDirection(direction);
|
||||||
var halfSpacing = Plate.PartSpacing / 2;
|
var halfSpacing = Plate.PartSpacing / 2;
|
||||||
|
var isHorizontal = Helper.IsHorizontalDirection(direction);
|
||||||
|
|
||||||
foreach (var part in stationaryParts)
|
for (var i = 0; i < stationaryParts.Count; i++)
|
||||||
{
|
stationaryBoxes[i] = stationaryParts[i].BoundingBox;
|
||||||
stationaryLines.Add(halfSpacing > 0
|
|
||||||
? Helper.GetOffsetPartLines(part.BasePart, halfSpacing, opposite, OffsetTolerance)
|
|
||||||
: Helper.GetPartLines(part.BasePart, opposite, OffsetTolerance));
|
|
||||||
stationaryBoxes.Add(part.BoundingBox);
|
|
||||||
}
|
|
||||||
|
|
||||||
var workArea = Plate.WorkArea();
|
var workArea = Plate.WorkArea();
|
||||||
var distance = double.MaxValue;
|
var distance = double.MaxValue;
|
||||||
|
|
||||||
foreach (var selected in SelectedParts)
|
foreach (var selected in SelectedParts)
|
||||||
{
|
{
|
||||||
// Get offset lines for the moving part (half-spacing, symmetric with stationary).
|
// Check plate edge first to tighten the upper bound.
|
||||||
var movingLines = halfSpacing > 0
|
var edgeDist = Helper.EdgeDistance(selected.BoundingBox, workArea, direction);
|
||||||
? Helper.GetOffsetPartLines(selected.BasePart, halfSpacing, direction, OffsetTolerance)
|
if (edgeDist > 0 && edgeDist < distance)
|
||||||
: Helper.GetPartLines(selected.BasePart, direction, OffsetTolerance);
|
distance = edgeDist;
|
||||||
|
|
||||||
var movingBox = selected.BoundingBox;
|
var movingBox = selected.BoundingBox;
|
||||||
|
List<Line> movingLines = null;
|
||||||
|
|
||||||
// Check geometry distance against each stationary part.
|
for (var i = 0; i < stationaryBoxes.Length; i++)
|
||||||
for (int i = 0; i < stationaryLines.Count; i++)
|
|
||||||
{
|
{
|
||||||
// Early-out: skip if bounding boxes don't overlap on the perpendicular axis.
|
// Skip parts not ahead in the push direction or further than current best.
|
||||||
var stBox = stationaryBoxes[i];
|
var gap = Helper.DirectionalGap(movingBox, stationaryBoxes[i], direction);
|
||||||
bool perpOverlap;
|
if (gap < 0 || gap >= distance)
|
||||||
|
continue;
|
||||||
|
|
||||||
switch (direction)
|
var perpOverlap = isHorizontal
|
||||||
{
|
? movingBox.IsHorizontalTo(stationaryBoxes[i], out _)
|
||||||
case PushDirection.Left:
|
: movingBox.IsVerticalTo(stationaryBoxes[i], out _);
|
||||||
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 (!perpOverlap)
|
if (!perpOverlap)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// Compute lines lazily — only for parts that survive bounding box checks.
|
||||||
|
movingLines ??= halfSpacing > 0
|
||||||
|
? Helper.GetOffsetPartLines(selected.BasePart, halfSpacing, direction, OffsetTolerance)
|
||||||
|
: Helper.GetPartLines(selected.BasePart, direction, OffsetTolerance);
|
||||||
|
|
||||||
|
stationaryLines[i] ??= halfSpacing > 0
|
||||||
|
? Helper.GetOffsetPartLines(stationaryParts[i].BasePart, halfSpacing, opposite, OffsetTolerance)
|
||||||
|
: Helper.GetPartLines(stationaryParts[i].BasePart, opposite, OffsetTolerance);
|
||||||
|
|
||||||
var d = Helper.DirectionalDistance(movingLines, stationaryLines[i], direction);
|
var d = Helper.DirectionalDistance(movingLines, stationaryLines[i], direction);
|
||||||
if (d < distance)
|
if (d < distance)
|
||||||
distance = d;
|
distance = d;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check distance to plate edge (actual geometry bbox, not offset).
|
|
||||||
double edgeDist;
|
|
||||||
switch (direction)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (edgeDist > 0 && edgeDist < distance)
|
|
||||||
distance = edgeDist;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (distance < double.MaxValue && distance > 0)
|
if (distance < double.MaxValue && distance > 0)
|
||||||
{
|
{
|
||||||
var offset = new Vector();
|
var offset = Helper.DirectionToOffset(direction, distance);
|
||||||
|
|
||||||
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