mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-05 05:51:34 -05:00
fix(nudge): always move the full calculated step
Interlocked rows, such as staggered triangles, cannot slide straight past their neighbors, so capping the step with a push left them unable to pattern down. The calculated step now moves the full distance like a fixed nudge, even off the plate or onto a part; the layout check reports either. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d98bee87ec
commit
0547af2ff6
5 files changed
+26
-31
No files matched your search
@@ -8,7 +8,7 @@ namespace OpenNest.Engine.Tests.Fill;
|
||||
|
||||
/// <summary>
|
||||
/// Compactor.Step moves a selection by its own repeat distance at the part spacing,
|
||||
/// and like a push never passes the work-area edge or another part.
|
||||
/// always the full distance, whatever lies on the way or at the landing.
|
||||
/// </summary>
|
||||
public class CompactorStepTests
|
||||
{
|
||||
@@ -86,25 +86,29 @@ public class CompactorStepTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StopsAtAnotherPartInsteadOfPassingIt()
|
||||
public void MovesTheFullStepPastAnotherPart()
|
||||
{
|
||||
// Interlocked rows cannot slide straight past their neighbors, so patterning
|
||||
// them needs the jump, not a push.
|
||||
var plate = MakePlate();
|
||||
var part = AddSquare(plate, 2, 10);
|
||||
AddSquare(plate, 7, 10); // 2 clear of the mover: it may close 1 at 1.0 spacing.
|
||||
AddSquare(plate, 7, 10);
|
||||
|
||||
var moved = Compactor.Step(new List<Part> { part }, plate, PushDirection.Right);
|
||||
|
||||
Assert.Equal(1, moved, 3);
|
||||
Assert.Equal(4, moved, 3);
|
||||
Assert.Equal(6, part.Location.X, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StopsAtTheWorkAreaEdge()
|
||||
public void MovesTheFullStepOffThePlate()
|
||||
{
|
||||
var plate = MakePlate();
|
||||
var part = AddSquare(plate, 18, 10); // Right side at 21, 2.5 from the work area's.
|
||||
|
||||
var moved = Compactor.Step(new List<Part> { part }, plate, PushDirection.Right);
|
||||
|
||||
Assert.Equal(2.5, moved, 3);
|
||||
Assert.Equal(4, moved, 3);
|
||||
Assert.Equal(22, part.Location.X, 3);
|
||||
}
|
||||
}
|
||||
@@ -31,16 +31,13 @@ namespace OpenNest.Engine.Fill
|
||||
public static double Push(List<Part> movingParts, Plate plate, double angle) =>
|
||||
Push(movingParts, plate, new Vector(System.Math.Cos(angle), System.Math.Sin(angle)));
|
||||
|
||||
private static double Push(List<Part> movingParts, Plate plate, Vector direction) =>
|
||||
Push(movingParts, plate, direction, double.MaxValue);
|
||||
|
||||
private static double Push(List<Part> movingParts, Plate plate, Vector direction, double limit)
|
||||
private static double Push(List<Part> movingParts, Plate plate, Vector direction)
|
||||
{
|
||||
var obstacleParts = plate.Parts
|
||||
.Where(p => !movingParts.Contains(p) && !IntersectsAny(p, movingParts))
|
||||
.ToList();
|
||||
|
||||
return Push(movingParts, plate.WorkArea(), direction, limit,
|
||||
return Push(movingParts, plate.WorkArea(), direction,
|
||||
new ObstacleSet(obstacleParts, plate.PartSpacing), ZoneObstacles(plate));
|
||||
}
|
||||
|
||||
@@ -48,10 +45,9 @@ namespace OpenNest.Engine.Fill
|
||||
/// Moves movingParts one repeat step along <paramref name="direction"/>: the
|
||||
/// distance at which a copy left behind would sit at the plate's part spacing,
|
||||
/// measured on the parts' geometry like a fill's copy distance, so shapes that
|
||||
/// nest into their own copy step less than their size. Like
|
||||
/// <see cref="Push(List{Part}, Plate, PushDirection)"/>, the move stops early at
|
||||
/// the work-area edge, a clamp zone or another part, never passing one.
|
||||
/// Returns the distance moved.
|
||||
/// nest into their own copy step less than their size. Unlike a push it always
|
||||
/// moves the full step, like a nudge: it ignores the work-area edge and other
|
||||
/// parts, so it can land off the plate or on a part. Returns the distance moved.
|
||||
/// </summary>
|
||||
public static double Step(List<Part> movingParts, Plate plate, PushDirection direction)
|
||||
{
|
||||
@@ -68,7 +64,10 @@ namespace OpenNest.Engine.Fill
|
||||
if (!double.IsFinite(step) || step <= 0)
|
||||
return 0;
|
||||
|
||||
return Push(movingParts, plate, SpatialQuery.DirectionToOffset(direction, 1.0), step);
|
||||
var offset = SpatialQuery.DirectionToOffset(direction, step);
|
||||
foreach (var part in movingParts)
|
||||
part.Offset(offset);
|
||||
return step;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -82,27 +81,19 @@ namespace OpenNest.Engine.Fill
|
||||
Vector direction
|
||||
) => Push(movingParts, workArea, direction, new ObstacleSet(obstacleParts, partSpacing));
|
||||
|
||||
private static double Push(
|
||||
List<Part> movingParts,
|
||||
Box workArea,
|
||||
Vector direction,
|
||||
params ObstacleSet[] obstacleSets
|
||||
) => Push(movingParts, workArea, direction, double.MaxValue, obstacleSets);
|
||||
|
||||
/// <summary>
|
||||
/// Pushes movingParts at most <paramref name="limit"/> toward the first contact
|
||||
/// with the work-area edge or an obstacle, keeping each obstacle set's own spacing.
|
||||
/// Pushes movingParts to the first contact with the work-area edge or an obstacle,
|
||||
/// keeping each obstacle set's own spacing.
|
||||
/// </summary>
|
||||
private static double Push(
|
||||
List<Part> movingParts,
|
||||
Box workArea,
|
||||
Vector direction,
|
||||
double limit,
|
||||
params ObstacleSet[] obstacleSets
|
||||
)
|
||||
{
|
||||
var opposite = -direction;
|
||||
var distance = limit;
|
||||
var distance = double.MaxValue;
|
||||
|
||||
foreach (var moving in movingParts)
|
||||
{
|
||||
|
||||
@@ -110,8 +110,8 @@ namespace OpenNest.Controls
|
||||
/// <summary>
|
||||
/// Moves the selection one repeat step in <paramref name="direction"/>: its own
|
||||
/// size along that axis plus the part spacing, measured on its geometry
|
||||
/// (<see cref="Compactor.Step"/>). It stops early at an edge or another part.
|
||||
/// Repeated steps undo as one move, like nudges.
|
||||
/// (<see cref="Compactor.Step"/>). Like a nudge it always moves the full step, even
|
||||
/// off the plate or onto a part. Repeated steps undo as one move.
|
||||
/// </summary>
|
||||
public void StepSelected(PushDirection direction)
|
||||
{
|
||||
|
||||
@@ -113,7 +113,7 @@ Default compares the built-in Irregular, Rectangles and Circles engines and keep
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
`Ctrl+F` fill area · `F` zoom to fit · `Shift+wheel` / middle-click rotate · `X`/`Y` push · arrows nudge by the offset increment (`F5`), or with its calculated distance by the selection's own size plus part spacing, landing tight with no push back · `Shift+arrow` push.
|
||||
`Ctrl+F` fill area · `F` zoom to fit · `Shift+wheel` / middle-click rotate · `X`/`Y` push · arrows nudge by the offset increment (`F5`), or with its calculated distance by the selection's own size plus part spacing, landing tight with no push back (it always moves, even off the plate) · `Shift+arrow` push.
|
||||
|
||||
## Status & License
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Open/incomplete chains and ambiguous contacts conservatively block. Loops are re
|
||||
## Callers
|
||||
|
||||
- PlateView uses `SelectionManager.PushSelected` → `Compactor.Push`. The zero-spacing nudge-and-discard workaround is removed. Cutout contours on stationary obstacles are retained, and the plate entry's existing-overlap filter accounts for holes rather than relying solely on `Part.Intersects` (which compares outer perimeters).
|
||||
- With **Calculated distance** checked in Tools > Set Offset Increment, PlateView's arrow keys use `SelectionManager.StepSelected` → `Compactor.Step`. The step is the selection's linear-fill copy distance (`FillLinear.CopyDistance`) at the plate's part spacing, so a shape that nests into its own copy steps less than its size. That copy distance is the same in both directions along an axis. The move is then a push capped at that step, so it stops at the work-area edge, a clamp zone or another part and never passes one. A clone preview resting on the copy just placed ignores that copy, as a push does with overlapping parts, and so steps clear of it.
|
||||
- With **Calculated distance** checked in Tools > Set Offset Increment, PlateView's arrow keys use `SelectionManager.StepSelected` → `Compactor.Step`. The step is the selection's linear-fill copy distance (`FillLinear.CopyDistance`) at the plate's part spacing, so a shape that nests into its own copy steps less than its size. That copy distance is the same in both directions along an axis. The move is not a slide: like a fixed nudge it always moves the full step, ignoring the work-area edge, clamps and other parts, because interlocked rows (staggered triangles, say) cannot slide straight past their neighbors to the next row. It can land off the plate or on a part; the layout check reports that as it does for any nudge.
|
||||
- Linear fill inherits the shared native-entity query unchanged. Extents fill passes complete boundary loops instead of direction-filtered fragments.
|
||||
- CPU best-fit batches prepare contact topology once and use all vertices plus curve/line interior and curve/curve tangency events. The old leading-half vertex filter cannot establish the next blocker after a skipped touch.
|
||||
- GPU kernels retain nearest-hit reduction and return unsnapped contact witnesses. The shared CPU classifier accepts a blocking witness or replays the full query after a nonblocking witness, preserving tied/later blockers. Both batch APIs honor active buffer lengths and refresh mutated/reused segment arrays. The GPU distance adapter sends only exact cardinal directions to the axis-only slide interface; arbitrary directions and native curves use the shared CPU path.
|
||||
|
||||
Reference in new issue
Block a user