feat(engine): add CompactIndividual to Compactor (disabled in strip nester)
Add plate-free Push overload and CompactIndividual method that pushes each part individually against all others as obstacles. Disabled in StripNestEngine pending investigation — compaction opens irregular gaps that the remnant finder scatters parts into. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -81,6 +81,12 @@ namespace OpenNest
|
|||||||
.Where(p => !movingParts.Contains(p))
|
.Where(p => !movingParts.Contains(p))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
return Push(movingParts, obstacleParts, plate.WorkArea(), plate.PartSpacing, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double Push(List<Part> movingParts, List<Part> obstacleParts,
|
||||||
|
Box workArea, double partSpacing, PushDirection direction)
|
||||||
|
{
|
||||||
var obstacleBoxes = new Box[obstacleParts.Count];
|
var obstacleBoxes = new Box[obstacleParts.Count];
|
||||||
var obstacleLines = new List<Line>[obstacleParts.Count];
|
var obstacleLines = new List<Line>[obstacleParts.Count];
|
||||||
|
|
||||||
@@ -88,9 +94,8 @@ namespace OpenNest
|
|||||||
obstacleBoxes[i] = obstacleParts[i].BoundingBox;
|
obstacleBoxes[i] = obstacleParts[i].BoundingBox;
|
||||||
|
|
||||||
var opposite = SpatialQuery.OppositeDirection(direction);
|
var opposite = SpatialQuery.OppositeDirection(direction);
|
||||||
var halfSpacing = plate.PartSpacing / 2;
|
var halfSpacing = partSpacing / 2;
|
||||||
var isHorizontal = SpatialQuery.IsHorizontalDirection(direction);
|
var isHorizontal = SpatialQuery.IsHorizontalDirection(direction);
|
||||||
var workArea = plate.WorkArea();
|
|
||||||
var distance = double.MaxValue;
|
var distance = double.MaxValue;
|
||||||
|
|
||||||
// BB gap at which offset geometries are expected to be touching.
|
// BB gap at which offset geometries are expected to be touching.
|
||||||
@@ -152,5 +157,60 @@ namespace OpenNest
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compacts parts individually toward the bottom-left of the work area.
|
||||||
|
/// Each part is pushed against all others as obstacles, closing geometry-based gaps.
|
||||||
|
/// Does not require parts to be on a plate.
|
||||||
|
/// </summary>
|
||||||
|
public static void CompactIndividual(List<Part> parts, Box workArea, double partSpacing)
|
||||||
|
{
|
||||||
|
if (parts == null || parts.Count < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var savedPositions = SavePositions(parts);
|
||||||
|
|
||||||
|
var leftFirst = CompactIndividualLoop(parts, workArea, partSpacing,
|
||||||
|
PushDirection.Left, PushDirection.Down);
|
||||||
|
|
||||||
|
RestorePositions(parts, savedPositions);
|
||||||
|
var downFirst = CompactIndividualLoop(parts, workArea, partSpacing,
|
||||||
|
PushDirection.Down, PushDirection.Left);
|
||||||
|
|
||||||
|
if (leftFirst > downFirst)
|
||||||
|
{
|
||||||
|
RestorePositions(parts, savedPositions);
|
||||||
|
CompactIndividualLoop(parts, workArea, partSpacing,
|
||||||
|
PushDirection.Left, PushDirection.Down);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double CompactIndividualLoop(List<Part> parts, Box workArea,
|
||||||
|
double partSpacing, PushDirection first, PushDirection second)
|
||||||
|
{
|
||||||
|
var total = 0.0;
|
||||||
|
|
||||||
|
for (var pass = 0; pass < MaxIterations; pass++)
|
||||||
|
{
|
||||||
|
var moved = 0.0;
|
||||||
|
|
||||||
|
foreach (var part in parts)
|
||||||
|
{
|
||||||
|
var single = new List<Part>(1) { part };
|
||||||
|
var obstacles = new List<Part>(parts.Count - 1);
|
||||||
|
foreach (var p in parts)
|
||||||
|
if (p != part) obstacles.Add(p);
|
||||||
|
|
||||||
|
moved += Push(single, obstacles, workArea, partSpacing, first);
|
||||||
|
moved += Push(single, obstacles, workArea, partSpacing, second);
|
||||||
|
}
|
||||||
|
|
||||||
|
total += moved;
|
||||||
|
if (moved <= RepeatThreshold)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,6 +214,16 @@ namespace OpenNest
|
|||||||
: trialPlacedBox.Right - workArea.X;
|
: trialPlacedBox.Right - workArea.X;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Compact strip parts individually to close geometry-based gaps.
|
||||||
|
// Disabled pending investigation — remnant finder picks up gaps created
|
||||||
|
// by compaction and scatters parts into them.
|
||||||
|
// Compactor.CompactIndividual(bestParts, workArea, Plate.PartSpacing);
|
||||||
|
//
|
||||||
|
// var compactedBox = bestParts.Cast<IBoundable>().GetBoundingBox();
|
||||||
|
// bestDim = direction == StripDirection.Bottom
|
||||||
|
// ? compactedBox.Top - workArea.Y
|
||||||
|
// : compactedBox.Right - workArea.X;
|
||||||
|
|
||||||
// Build remnant box with spacing gap.
|
// Build remnant box with spacing gap.
|
||||||
var spacing = Plate.PartSpacing;
|
var spacing = Plate.PartSpacing;
|
||||||
var remnantBox = direction == StripDirection.Bottom
|
var remnantBox = direction == StripDirection.Bottom
|
||||||
|
|||||||
Reference in New Issue
Block a user