From cb30c20eb9a26f0a2ada46b5c14e8606d7fb2130 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 12 Mar 2026 22:43:05 -0400 Subject: [PATCH] docs: add cutting strategy design spec Lead-in, lead-out, and tab class hierarchy for CNC cutting approach/exit geometry, using ShapeProfile + ClosestPointTo for contour sequencing. Co-Authored-By: Claude Opus 4.6 --- .../2026-03-12-cutting-strategy-design.md | 372 ++++++++++++++++++ 1 file changed, 372 insertions(+) create mode 100644 docs/superpowers/specs/2026-03-12-cutting-strategy-design.md diff --git a/docs/superpowers/specs/2026-03-12-cutting-strategy-design.md b/docs/superpowers/specs/2026-03-12-cutting-strategy-design.md new file mode 100644 index 0000000..99eaccd --- /dev/null +++ b/docs/superpowers/specs/2026-03-12-cutting-strategy-design.md @@ -0,0 +1,372 @@ +# CNC Cutting Strategy Design + +## Overview + +Add lead-in, lead-out, and tab classes to `OpenNest.Core` that generate `ICode` instructions for CNC cutting approach/exit geometry. These get injected into a `Program` at post-processing time — the original `Drawing`/`Program` geometry stays untouched. + +All new code lives in `OpenNest.Core/CNC/CuttingStrategy/`. + +## File Structure + +``` +OpenNest.Core/CNC/CuttingStrategy/ +├── LeadIns/ +│ ├── LeadIn.cs +│ ├── NoLeadIn.cs +│ ├── LineLeadIn.cs +│ ├── LineArcLeadIn.cs +│ ├── ArcLeadIn.cs +│ ├── LineLineLeadIn.cs +│ └── CleanHoleLeadIn.cs +├── LeadOuts/ +│ ├── LeadOut.cs +│ ├── NoLeadOut.cs +│ ├── LineLeadOut.cs +│ ├── ArcLeadOut.cs +│ └── MicrotabLeadOut.cs +├── Tabs/ +│ ├── Tab.cs +│ ├── NormalTab.cs +│ ├── BreakerTab.cs +│ └── MachineTab.cs +├── CuttingParameters.cs +├── ContourCuttingStrategy.cs +└── SequenceParameters.cs +``` + +## Namespace + +All classes use `namespace OpenNest.CNC.CuttingStrategy`. + +## Type Mappings from Original Spec + +The original spec used placeholder names. These are the correct codebase types: + +| Spec type | Actual type | Notes | +|-----------|------------|-------| +| `PointD` | `Vector` | `OpenNest.Geometry.Vector` — struct with `X`, `Y` fields | +| `CircularMove` | `ArcMove` | Constructor: `ArcMove(Vector endPoint, Vector centerPoint, RotationType rotation)` | +| `CircularDirection` | `RotationType` | Enum with `CW`, `CCW` | +| `value.ToRadians()` | `Angle.ToRadians(value)` | Static method on `OpenNest.Math.Angle` | +| `new Program(codes)` | Build manually | Create `Program()`, add to `.Codes` list | + +## LeadIn Hierarchy + +### Abstract Base: `LeadIn` + +```csharp +public abstract class LeadIn +{ + public abstract List Generate(Vector contourStartPoint, double contourNormalAngle); + public abstract Vector GetPiercePoint(Vector contourStartPoint, double contourNormalAngle); +} +``` + +- `contourStartPoint`: where the contour cut begins (first point of the part profile). +- `contourNormalAngle`: inward-facing normal angle (radians) at the contour start point. For exterior contours this points away from the part; for interior contours it points into the scrap. +- `Generate` returns ICode instructions starting with a `RapidMove` to the pierce point, followed by cutting moves to reach the contour start. +- `GetPiercePoint` computes where the head rapids to before firing — useful for visualization and collision detection. + +### NoLeadIn (Type 0) + +Pierce directly on the contour start point. Returns a single `RapidMove(contourStartPoint)`. + +### LineLeadIn (Type 1) + +Straight line approach. + +Properties: +- `Length` (double): distance from pierce point to contour start (inches) +- `Angle` (double): approach angle in degrees relative to contour tangent. 90 = perpendicular, 135 = acute angle (common for plasma). Default: 90. + +Pierce point offset: `contourStartPoint + Length` along `contourNormalAngle + Angle.ToRadians(Angle)`. + +Generates: `RapidMove(piercePoint)` → `LinearMove(contourStartPoint)`. + +### LineArcLeadIn (Type 2) + +Line followed by tangential arc meeting the contour. Most common for plasma. + +Properties: +- `LineLength` (double): straight approach segment length +- `Angle` (double): line angle relative to contour. Default: 135. +- `ArcRadius` (double): radius of tangential arc + +Geometry: Pierce → [Line] → Arc start → [Arc CW] → Contour start. Arc center is at `contourStartPoint + ArcRadius` along normal. + +Generates: `RapidMove(piercePoint)` → `LinearMove(arcStart)` → `ArcMove(contourStartPoint, arcCenter, RotationType.CW)`. + +### ArcLeadIn (Type 3) + +Pure arc approach, no straight line segment. + +Properties: +- `Radius` (double): arc radius + +Pierce point is diametrically opposite the contour start on the arc circle. Arc center at `contourStartPoint + Radius` along normal. + +Generates: `RapidMove(piercePoint)` → `ArcMove(contourStartPoint, arcCenter, RotationType.CW)`. + +### LineLineLeadIn (Type 5) + +Two-segment straight line approach. + +Properties: +- `Length1` (double): first segment length +- `Angle1` (double): first segment angle. Default: 90. +- `Length2` (double): second segment length +- `Angle2` (double): direction change. Default: 90. + +Generates: `RapidMove(piercePoint)` → `LinearMove(midPoint)` → `LinearMove(contourStartPoint)`. + +### CleanHoleLeadIn + +Specialized for precision circular holes. Same geometry as `LineArcLeadIn` but with hard-coded 135° angle and a `Kerf` property. The overcut (cutting past start to close the hole) is handled at the lead-out, not here. + +Properties: +- `LineLength` (double) +- `ArcRadius` (double) +- `Kerf` (double) + +## LeadOut Hierarchy + +### Abstract Base: `LeadOut` + +```csharp +public abstract class LeadOut +{ + public abstract List Generate(Vector contourEndPoint, double contourNormalAngle); +} +``` + +- `contourEndPoint`: where the contour cut ends. For closed contours, same as start. +- Returns ICode instructions appended after the contour's last cut point. + +### NoLeadOut (Type 0) + +Returns empty list. Cut ends exactly at contour end. + +### LineLeadOut (Type 1) + +Straight line overcut past contour end. + +Properties: +- `Length` (double): overcut distance +- `Angle` (double): direction relative to contour tangent. Default: 90. + +Generates: `LinearMove(endPoint)` where endPoint is offset from contourEndPoint. + +### ArcLeadOut (Type 3) + +Arc overcut curving away from the part. + +Properties: +- `Radius` (double) + +Arc center at `contourEndPoint + Radius` along normal. End point is a quarter turn away. + +Generates: `ArcMove(endPoint, arcCenter, RotationType.CW)`. + +### MicrotabLeadOut (Type 4) + +Stops short of contour end, leaving an uncut bridge. Laser only. + +Properties: +- `GapSize` (double): uncut material length. Default: 0.03". + +Does NOT add instructions — returns empty list. The `ContourCuttingStrategy` detects this type and trims the last cutting move by `GapSize` instead. + +## Tab Hierarchy + +Tabs are mid-contour features that temporarily lift the beam to leave bridges holding the part in place. + +### Abstract Base: `Tab` + +```csharp +public abstract class Tab +{ + public double Size { get; set; } = 0.03; + public LeadIn TabLeadIn { get; set; } + public LeadOut TabLeadOut { get; set; } + + public abstract List Generate( + Vector tabStartPoint, Vector tabEndPoint, double contourNormalAngle); +} +``` + +### NormalTab + +Standard tab: cut up to tab start, lift/rapid over gap, resume cutting. + +Additional properties: +- `CutoutMinWidth`, `CutoutMinHeight` (double): minimum cutout size to receive this tab +- `CutoutMaxWidth`, `CutoutMaxHeight` (double): maximum cutout size to receive this tab +- `AppliesToCutout(double width, double height)` method for size filtering + +Generates: TabLeadOut codes → `RapidMove(tabEndPoint)` → TabLeadIn codes. + +### BreakerTab + +Like NormalTab but adds a scoring cut into the part at the tab location to make snapping easier. + +Additional properties: +- `BreakerDepth` (double): how far the score cuts into the part +- `BreakerLeadInLength` (double) +- `BreakerAngle` (double) + +Generates: TabLeadOut codes → `LinearMove(scoreEnd)` → `RapidMove(tabEndPoint)` → TabLeadIn codes. + +### MachineTab + +Tab behavior configured at the CNC controller level. OpenNest just signals the controller. + +Additional properties: +- `MachineTabId` (int): passed to post-processor for M-code translation + +Returns a placeholder `RapidMove(tabEndPoint)` — the post-processor plugin replaces this with machine-specific commands. + +## CuttingParameters + +One instance per material/machine combination. Ties everything together. + +```csharp +public class CuttingParameters +{ + public int Id { get; set; } + + // Material/Machine identification + public string MachineName { get; set; } + public string MaterialName { get; set; } + public string Grade { get; set; } + public double Thickness { get; set; } + + // Kerf and spacing + public double Kerf { get; set; } + public double PartSpacing { get; set; } + + // External contour lead-in/out + public LeadIn ExternalLeadIn { get; set; } = new NoLeadIn(); + public LeadOut ExternalLeadOut { get; set; } = new NoLeadOut(); + + // Internal contour lead-in/out + public LeadIn InternalLeadIn { get; set; } = new LineLeadIn { Length = 0.125, Angle = 90 }; + public LeadOut InternalLeadOut { get; set; } = new NoLeadOut(); + + // Arc/circle specific (overrides internal for circular features) + public LeadIn ArcCircleLeadIn { get; set; } = new NoLeadIn(); + public LeadOut ArcCircleLeadOut { get; set; } = new NoLeadOut(); + + // Tab configuration + public Tab TabConfig { get; set; } + public bool TabsEnabled { get; set; } = false; + + // Sequencing and assignment + public SequenceParameters Sequencing { get; set; } = new SequenceParameters(); + public AssignmentParameters Assignment { get; set; } = new AssignmentParameters(); +} +``` + +## SequenceParameters and AssignmentParameters + +```csharp +public enum SequenceMethod +{ + RightSide = 1, LeastCode = 2, Advanced = 3, + BottomSide = 4, EdgeStart = 5, LeftSide = 7, RightSideAlt = 8 +} + +public class SequenceParameters +{ + public SequenceMethod Method { get; set; } = SequenceMethod.Advanced; + public double SmallCutoutWidth { get; set; } = 1.5; + public double SmallCutoutHeight { get; set; } = 1.5; + public double MediumCutoutWidth { get; set; } = 8.0; + public double MediumCutoutHeight { get; set; } = 8.0; + public double DistanceMediumSmall { get; set; } + public bool AlternateRowsColumns { get; set; } = true; + public bool AlternateCutoutsWithinRowColumn { get; set; } = true; + public double MinDistanceBetweenRowsColumns { get; set; } = 0.25; +} + +public class AssignmentParameters +{ + public SequenceMethod Method { get; set; } = SequenceMethod.Advanced; + public string Preference { get; set; } = "ILAT"; + public double MinGeometryLength { get; set; } = 0.01; +} +``` + +## ContourCuttingStrategy + +The orchestrator. Uses `ShapeProfile` to decompose a part into perimeter + cutouts, then sequences and applies cutting parameters using nearest-neighbor chaining from an exit point. + +### Approach + +Instead of requiring `Program.GetStartPoint()` / `GetNormalAtStart()` (which don't exist), the strategy: + +1. Receives the **exit point** — where the head will be after cutting this part +2. Converts the program to geometry via `Program.ToGeometry()` +3. Builds a `ShapeProfile` from the geometry — gives `Perimeter` (Shape) and `Cutouts` (List<Shape>) +4. Uses `Shape.ClosestPointTo(point, out Entity entity)` to find lead-in points and the entity for normal computation +5. Chains cutouts by nearest-neighbor distance +6. Reverses the chain → cut order is cutouts first (nearest-last), perimeter last + +### ContourType Detection + +- `ShapeProfile.Perimeter` → `ContourType.External` +- Each cutout in `ShapeProfile.Cutouts`: + - If single entity and entity is `Circle` → `ContourType.ArcCircle` + - Otherwise → `ContourType.Internal` + +### Normal Angle Computation + +Derived from the `out Entity` returned by `ClosestPointTo`: + +- **Line**: normal is perpendicular to line direction. Tangent = `endPoint.AngleFrom(startPoint)`, normal = tangent + π/2 (pointing inward for exterior, outward for interior). +- **Arc**: normal is radial direction from arc center to the closest point. `closestPoint.AngleFrom(arc.Center)`. + +For interior contours the normal points into the scrap (away from part center). For exterior contours it points away from the part. + +### Method Signature + +```csharp +public class ContourCuttingStrategy +{ + public CuttingParameters Parameters { get; set; } + + /// + /// Apply cutting strategy to a part's program. + /// + /// Original part program (unmodified). + /// Where the head will be after cutting (for nearest-neighbor sequencing). + /// New Program with lead-ins, lead-outs, and tabs applied. Cutouts first, perimeter last. + public Program Apply(Program partProgram, Vector exitPoint) + { + // 1. Convert to geometry, build ShapeProfile + // 2. Find closest point on perimeter from exitPoint + // 3. Chain cutouts by nearest-neighbor from perimeter point + // 4. Reverse chain → cut order + // 5. For each contour: select lead-in/out by ContourType, generate codes + // 6. Handle MicrotabLeadOut by trimming last segment + // 7. Assemble and return new Program + } +} +``` + +### ContourType Enum + +```csharp +public enum ContourType +{ + External, + Internal, + ArcCircle +} +``` + +## Out of Scope (Deferred) + +- **Serialization** of CuttingParameters (JSON/XML discriminators) +- **UI integration** (parameter editor forms in WinForms app) +- **Post-processor integration** (feeding strategy-applied programs to `IPostProcessor`) +- **Tab insertion logic** (`InsertTabs` / `TrimLastSegment` — stubbed with `NotImplementedException`)