8.5 KiB
Contour Re-Indexing Implementation Plan
For agentic workers: REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Add entity-splitting primitives (Line.SplitAt, Arc.SplitAt), a Shape.ReindexAt method, and wire them into ContourCuttingStrategy.Apply() to replace the NotImplementedException stubs.
Architecture: Bottom-up — build splitting primitives first, then the reindexing algorithm on top, then wire into the strategy. Each layer depends only on the one below it.
Tech Stack: C# / .NET 8, OpenNest.Core (Geometry + CNC namespaces)
Spec: docs/superpowers/specs/2026-03-12-contour-reindexing-design.md
File Structure
| File | Change | Responsibility |
|---|---|---|
OpenNest.Core/Geometry/Line.cs |
Add method | SplitAt(Vector) — split a line at a point into two halves |
OpenNest.Core/Geometry/Arc.cs |
Add method | SplitAt(Vector) — split an arc at a point into two halves |
OpenNest.Core/Geometry/Shape.cs |
Add method | ReindexAt(Vector, Entity) — reorder a closed contour to start at a given point |
OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs |
Add method + modify | ConvertShapeToMoves + replace two NotImplementedException blocks |
Chunk 1: Splitting Primitives
Task 1: Add Line.SplitAt(Vector)
Files:
-
Modify:
OpenNest.Core/Geometry/Line.cs -
Step 1: Add
SplitAtmethod toLine
Add the following method to the Line class (after the existing ClosestPointTo method):
public (Line first, Line second) SplitAt(Vector point)
{
var first = point.DistanceTo(StartPoint) < Tolerance.Epsilon
? null
: new Line(StartPoint, point);
var second = point.DistanceTo(EndPoint) < Tolerance.Epsilon
? null
: new Line(point, EndPoint);
return (first, second);
}
- Step 2: Build to verify
Run: dotnet build OpenNest.Core/OpenNest.Core.csproj
Expected: Build succeeded, 0 errors
- Step 3: Commit
git add OpenNest.Core/Geometry/Line.cs
git commit -m "feat: add Line.SplitAt(Vector) splitting primitive"
Task 2: Add Arc.SplitAt(Vector)
Files:
-
Modify:
OpenNest.Core/Geometry/Arc.cs -
Step 1: Add
SplitAtmethod toArc
Add the following method to the Arc class (after the existing EndPoint method):
public (Arc first, Arc second) SplitAt(Vector point)
{
if (point.DistanceTo(StartPoint()) < Tolerance.Epsilon)
return (null, new Arc(Center, Radius, StartAngle, EndAngle, IsReversed));
if (point.DistanceTo(EndPoint()) < Tolerance.Epsilon)
return (new Arc(Center, Radius, StartAngle, EndAngle, IsReversed), null);
var splitAngle = Angle.NormalizeRad(Center.AngleTo(point));
var firstArc = new Arc(Center, Radius, StartAngle, splitAngle, IsReversed);
var secondArc = new Arc(Center, Radius, splitAngle, EndAngle, IsReversed);
return (firstArc, secondArc);
}
Key details from spec:
-
Compare distances to
StartPoint()/EndPoint()rather than comparing angles (avoids 0/2π wrap-around issues). -
splitAngleis computed fromCenter.AngleTo(point), normalized. -
Both halves preserve center, radius, and
IsReverseddirection. -
Step 2: Build to verify
Run: dotnet build OpenNest.Core/OpenNest.Core.csproj
Expected: Build succeeded, 0 errors
- Step 3: Commit
git add OpenNest.Core/Geometry/Arc.cs
git commit -m "feat: add Arc.SplitAt(Vector) splitting primitive"
Chunk 2: Shape.ReindexAt
Task 3: Add Shape.ReindexAt(Vector, Entity)
Files:
-
Modify:
OpenNest.Core/Geometry/Shape.cs -
Step 1: Add
ReindexAtmethod toShape
Add the following method to the Shape class (after the existing ClosestPointTo(Vector, out Entity) method around line 201):
public Shape ReindexAt(Vector point, Entity entity)
{
// Circle case: return a new shape with just the circle
if (entity is Circle)
{
var result = new Shape();
result.Entities.Add(entity);
return result;
}
var i = Entities.IndexOf(entity);
if (i < 0)
throw new ArgumentException("Entity not found in shape", nameof(entity));
// Split the entity at the point
Entity firstHalf = null;
Entity secondHalf = null;
if (entity is Line line)
{
var (f, s) = line.SplitAt(point);
firstHalf = f;
secondHalf = s;
}
else if (entity is Arc arc)
{
var (f, s) = arc.SplitAt(point);
firstHalf = f;
secondHalf = s;
}
// Build reindexed entity list
var entities = new List<Entity>();
// secondHalf of split entity (if not null)
if (secondHalf != null)
entities.Add(secondHalf);
// Entities after the split index (wrapping)
for (var j = i + 1; j < Entities.Count; j++)
entities.Add(Entities[j]);
// Entities before the split index (wrapping)
for (var j = 0; j < i; j++)
entities.Add(Entities[j]);
// firstHalf of split entity (if not null)
if (firstHalf != null)
entities.Add(firstHalf);
var reindexed = new Shape();
reindexed.Entities.AddRange(entities);
return reindexed;
}
The Shape class already imports System and System.Collections.Generic, so no new usings needed.
- Step 2: Build to verify
Run: dotnet build OpenNest.Core/OpenNest.Core.csproj
Expected: Build succeeded, 0 errors
- Step 3: Commit
git add OpenNest.Core/Geometry/Shape.cs
git commit -m "feat: add Shape.ReindexAt(Vector, Entity) for contour reordering"
Chunk 3: Wire into ContourCuttingStrategy
Task 4: Add ConvertShapeToMoves and replace stubs
Files:
-
Modify:
OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs -
Step 1: Add
ConvertShapeToMovesprivate method
Add the following private method to ContourCuttingStrategy (after the existing SelectLeadOut method, before the closing brace of the class):
private List<ICode> ConvertShapeToMoves(Shape shape, Vector startPoint)
{
var moves = new List<ICode>();
foreach (var entity in shape.Entities)
{
if (entity is Line line)
{
moves.Add(new LinearMove(line.EndPoint));
}
else if (entity is Arc arc)
{
moves.Add(new ArcMove(arc.EndPoint(), arc.Center, arc.IsReversed ? RotationType.CW : RotationType.CCW));
}
else if (entity is Circle circle)
{
moves.Add(new ArcMove(startPoint, circle.Center, circle.Rotation));
}
else
{
throw new System.InvalidOperationException($"Unsupported entity type: {entity.Type}");
}
}
return moves;
}
This matches the ConvertGeometry.AddArc/AddCircle/AddLine patterns but without RapidMove between entities (they are contiguous in a reindexed shape).
- Step 2: Replace cutout
NotImplementedException(line 41)
In the Apply method, replace:
// Contour re-indexing: split shape entities at closestPt so cutting
// starts there, convert to ICode, and add to result.Codes
throw new System.NotImplementedException("Contour re-indexing not yet implemented");
With:
var reindexed = cutout.ReindexAt(closestPt, entity);
result.Codes.AddRange(ConvertShapeToMoves(reindexed, closestPt));
// TODO: MicrotabLeadOut — trim last cutting move by GapSize
- Step 3: Replace perimeter
NotImplementedException(line 57)
In the Apply method, replace:
throw new System.NotImplementedException("Contour re-indexing not yet implemented");
With:
var reindexed = profile.Perimeter.ReindexAt(perimeterPt, perimeterEntity);
result.Codes.AddRange(ConvertShapeToMoves(reindexed, perimeterPt));
// TODO: MicrotabLeadOut — trim last cutting move by GapSize
- Step 4: Build to verify
Run: dotnet build OpenNest.Core/OpenNest.Core.csproj
Expected: Build succeeded, 0 errors
- Step 5: Build full solution
Run: dotnet build OpenNest.sln
Expected: Build succeeded, 0 errors
- Step 6: Commit
git add OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs
git commit -m "feat: wire contour re-indexing into ContourCuttingStrategy.Apply()"