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 <noreply@anthropic.com>
This commit is contained in:
372
docs/superpowers/specs/2026-03-12-cutting-strategy-design.md
Normal file
372
docs/superpowers/specs/2026-03-12-cutting-strategy-design.md
Normal file
@@ -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<ICode> 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<ICode> 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<ICode> 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; }
|
||||
|
||||
/// <summary>
|
||||
/// Apply cutting strategy to a part's program.
|
||||
/// </summary>
|
||||
/// <param name="partProgram">Original part program (unmodified).</param>
|
||||
/// <param name="exitPoint">Where the head will be after cutting (for nearest-neighbor sequencing).</param>
|
||||
/// <returns>New Program with lead-ins, lead-outs, and tabs applied. Cutouts first, perimeter last.</returns>
|
||||
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`)
|
||||
Reference in New Issue
Block a user