diff --git a/docs/plans/2026-03-06-geometry-push-design.md b/docs/plans/2026-03-06-geometry-push-design.md new file mode 100644 index 0000000..fc634e1 --- /dev/null +++ b/docs/plans/2026-03-06-geometry-push-design.md @@ -0,0 +1,63 @@ +# Geometry-Based Push + +## Problem + +`PlateView.PushSelected` uses bounding boxes to determine how far to slide parts. For irregular shapes, this leaves large gaps between actual cut geometry. Parts should nestle together based on their true shape. + +## Behavior + +When pushing a selected part in a cardinal direction: + +1. The moving part's geometry is offset outward by `Plate.PartSpacing` (the kerf buffer) +2. That offset geometry slides until it touches the **actual cut geometry** of stationary parts (zero gap between offset and cut) +3. The actual cut paths end up separated by exactly `PartSpacing` +4. For plate edges: the **actual geometry** bounding box stops at the work area boundary (no offset applied to edges — same as current behavior) + +## Algorithm: Analytical Polygon Directional-Distance + +### Conversion Pipeline + +``` +Part.Program → ConvertProgram.ToGeometry() → Helper.GetShapes() → Shape.ToPolygon() → Polygon.ToLines() +``` + +For the moving part, insert an offset step after getting shapes: + +``` +shapes → Shape.OffsetEntity(PartSpacing, OffsetSide.Left) → ToPolygon() → ToLines() +``` + +### Directional Distance + +For a given push direction, compute the minimum translation distance before any edge of the offset moving polygon contacts any edge of a stationary polygon. + +Three cases per polygon pair: + +1. **Moving vertex → stationary edge**: For each vertex of the offset polygon, cast a ray in the push direction. Find where it crosses a stationary edge. Record the distance. +2. **Stationary vertex → moving edge**: For each vertex of the stationary polygon, cast a ray in the opposite direction. Find where it crosses an offset-moving edge. Record the distance. +3. **Edge-edge sliding contact**: For non-parallel edge pairs, compute the translation distance along the push axis where they first intersect. + +The minimum positive distance across all cases and all polygon pairs is the push distance. + +### Early-Out + +Before polygon math, check bounding box overlap on the perpendicular axis. If a stationary part's bounding box doesn't overlap the moving part's bounding box on the axis perpendicular to the push direction, skip that pair. + +## Changes + +| File | Change | +|------|--------| +| `OpenNest.Core\Helper.cs` | Add `DirectionalDistance(List, List, PushDirection)` | +| `OpenNest.Core\Helper.cs` | Add `RayEdgeDistance(Vector, Line, PushDirection)` helper | +| `OpenNest\Controls\PlateView.cs` | Rewrite `PushSelected` to use polygon directional-distance | + +### What stays the same + +- Plate edge distance check (actual bounding box vs work area) +- Key bindings (X/Y/Shift combinations) +- Existing `ClosestDistance*` methods remain (may be used elsewhere) + +### What changes + +- `PushSelected` converts parts to polygons instead of using bounding boxes +- Distance calculation uses actual geometry edges instead of box edges