docs: add GPU debug, test harness, and contour reindexing plans
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
367
docs/superpowers/plans/2026-03-11-test-harness.md
Normal file
367
docs/superpowers/plans/2026-03-11-test-harness.md
Normal file
@@ -0,0 +1,367 @@
|
||||
# OpenNest Test Harness 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:** Create a console app + MCP tool that builds and runs OpenNest.Engine against a nest file, writing debug output to a file for grepping and saving the resulting nest.
|
||||
|
||||
**Architecture:** A new `OpenNest.TestHarness` console app references Core, Engine, and IO. It loads a nest file, clears a plate, runs `NestEngine.Fill()`, writes `Debug.WriteLine` output to a timestamped log file via `TextWriterTraceListener`, prints a summary to stdout, and saves the nest. An MCP tool `test_engine` in OpenNest.Mcp shells out to `dotnet run --project OpenNest.TestHarness` and returns the summary + log file path.
|
||||
|
||||
**Tech Stack:** .NET 8, System.Diagnostics tracing, OpenNest.Core/Engine/IO
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
| Action | File | Responsibility |
|
||||
|--------|------|----------------|
|
||||
| Create | `OpenNest.TestHarness/OpenNest.TestHarness.csproj` | Console app project, references Core + Engine + IO. Forces `DEBUG` constant. |
|
||||
| Create | `OpenNest.TestHarness/Program.cs` | Entry point: parse args, load nest, run fill, write debug to file, save nest |
|
||||
| Modify | `OpenNest.sln` | Add new project to solution |
|
||||
| Create | `OpenNest.Mcp/Tools/TestTools.cs` | MCP `test_engine` tool that shells out to the harness |
|
||||
|
||||
---
|
||||
|
||||
## Chunk 1: Console App + MCP Tool
|
||||
|
||||
### Task 1: Create the OpenNest.TestHarness project
|
||||
|
||||
**Files:**
|
||||
- Create: `OpenNest.TestHarness/OpenNest.TestHarness.csproj`
|
||||
|
||||
- [ ] **Step 1: Create the project file**
|
||||
|
||||
Note: `DEBUG` is defined for all configurations so `Debug.WriteLine` output is always captured — that's the whole point of this tool.
|
||||
|
||||
```xml
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<RootNamespace>OpenNest.TestHarness</RootNamespace>
|
||||
<AssemblyName>OpenNest.TestHarness</AssemblyName>
|
||||
<DefineConstants>$(DefineConstants);DEBUG;TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OpenNest.Core\OpenNest.Core.csproj" />
|
||||
<ProjectReference Include="..\OpenNest.Engine\OpenNest.Engine.csproj" />
|
||||
<ProjectReference Include="..\OpenNest.IO\OpenNest.IO.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add project to solution**
|
||||
|
||||
```bash
|
||||
dotnet sln OpenNest.sln add OpenNest.TestHarness/OpenNest.TestHarness.csproj
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify it builds**
|
||||
|
||||
```bash
|
||||
dotnet build OpenNest.TestHarness/OpenNest.TestHarness.csproj
|
||||
```
|
||||
|
||||
Expected: Build succeeded (with warning about empty Program.cs — that's fine, we create it next).
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Write the TestHarness Program.cs
|
||||
|
||||
**Files:**
|
||||
- Create: `OpenNest.TestHarness/Program.cs`
|
||||
|
||||
The console app does:
|
||||
1. Parse command-line args for nest file path, optional drawing name, plate index, output path
|
||||
2. Create a timestamped log file and attach a `TextWriterTraceListener` so `Debug.WriteLine` goes to the file
|
||||
3. Load the nest file via `NestReader`
|
||||
4. Find the drawing and plate
|
||||
5. Clear existing parts from the plate
|
||||
6. Run `NestEngine.Fill()`
|
||||
7. Print summary (part count, utilization, log file path) to stdout
|
||||
8. Save the nest via `NestWriter`
|
||||
|
||||
- [ ] **Step 1: Write Program.cs**
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenNest;
|
||||
using OpenNest.IO;
|
||||
|
||||
// Parse arguments.
|
||||
var nestFile = args.Length > 0 ? args[0] : null;
|
||||
var drawingName = (string)null;
|
||||
var plateIndex = 0;
|
||||
var outputFile = (string)null;
|
||||
|
||||
for (var i = 1; i < args.Length; i++)
|
||||
{
|
||||
switch (args[i])
|
||||
{
|
||||
case "--drawing" when i + 1 < args.Length:
|
||||
drawingName = args[++i];
|
||||
break;
|
||||
case "--plate" when i + 1 < args.Length:
|
||||
plateIndex = int.Parse(args[++i]);
|
||||
break;
|
||||
case "--output" when i + 1 < args.Length:
|
||||
outputFile = args[++i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(nestFile) || !File.Exists(nestFile))
|
||||
{
|
||||
Console.Error.WriteLine("Usage: OpenNest.TestHarness <nest-file> [--drawing <name>] [--plate <index>] [--output <path>]");
|
||||
Console.Error.WriteLine(" nest-file Path to a .zip nest file");
|
||||
Console.Error.WriteLine(" --drawing Drawing name to fill with (default: first drawing)");
|
||||
Console.Error.WriteLine(" --plate Plate index to fill (default: 0)");
|
||||
Console.Error.WriteLine(" --output Output nest file path (default: <input>-result.zip)");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Set up debug log file.
|
||||
var logDir = Path.Combine(Path.GetDirectoryName(nestFile), "test-harness-logs");
|
||||
Directory.CreateDirectory(logDir);
|
||||
var logFile = Path.Combine(logDir, $"debug-{DateTime.Now:yyyyMMdd-HHmmss}.log");
|
||||
var logWriter = new StreamWriter(logFile) { AutoFlush = true };
|
||||
Trace.Listeners.Add(new TextWriterTraceListener(logWriter));
|
||||
|
||||
// Load nest.
|
||||
var reader = new NestReader(nestFile);
|
||||
var nest = reader.Read();
|
||||
|
||||
if (nest.Plates.Count == 0)
|
||||
{
|
||||
Console.Error.WriteLine("Error: nest file contains no plates");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (plateIndex >= nest.Plates.Count)
|
||||
{
|
||||
Console.Error.WriteLine($"Error: plate index {plateIndex} out of range (0-{nest.Plates.Count - 1})");
|
||||
return 1;
|
||||
}
|
||||
|
||||
var plate = nest.Plates[plateIndex];
|
||||
|
||||
// Find drawing.
|
||||
var drawing = drawingName != null
|
||||
? nest.Drawings.FirstOrDefault(d => d.Name == drawingName)
|
||||
: nest.Drawings.FirstOrDefault();
|
||||
|
||||
if (drawing == null)
|
||||
{
|
||||
Console.Error.WriteLine(drawingName != null
|
||||
? $"Error: drawing '{drawingName}' not found"
|
||||
: "Error: nest file contains no drawings");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Clear existing parts.
|
||||
var existingCount = plate.Parts.Count;
|
||||
plate.Parts.Clear();
|
||||
|
||||
Console.WriteLine($"Nest: {nest.Name}");
|
||||
Console.WriteLine($"Plate: {plateIndex} ({plate.Size.Width:F1} x {plate.Size.Height:F1}), spacing={plate.PartSpacing:F2}");
|
||||
Console.WriteLine($"Drawing: {drawing.Name}");
|
||||
Console.WriteLine($"Cleared {existingCount} existing parts");
|
||||
Console.WriteLine("---");
|
||||
|
||||
// Run fill.
|
||||
var sw = Stopwatch.StartNew();
|
||||
var engine = new NestEngine(plate);
|
||||
var item = new NestItem { Drawing = drawing, Quantity = 0 };
|
||||
var success = engine.Fill(item);
|
||||
sw.Stop();
|
||||
|
||||
// Flush and close the log.
|
||||
Trace.Flush();
|
||||
logWriter.Dispose();
|
||||
|
||||
// Print results.
|
||||
Console.WriteLine($"Result: {(success ? "success" : "failed")}");
|
||||
Console.WriteLine($"Parts placed: {plate.Parts.Count}");
|
||||
Console.WriteLine($"Utilization: {plate.Utilization():P1}");
|
||||
Console.WriteLine($"Time: {sw.ElapsedMilliseconds}ms");
|
||||
Console.WriteLine($"Debug log: {logFile}");
|
||||
|
||||
// Save output.
|
||||
if (outputFile == null)
|
||||
{
|
||||
var dir = Path.GetDirectoryName(nestFile);
|
||||
var name = Path.GetFileNameWithoutExtension(nestFile);
|
||||
outputFile = Path.Combine(dir, $"{name}-result.zip");
|
||||
}
|
||||
|
||||
var writer = new NestWriter(nest);
|
||||
writer.Write(outputFile);
|
||||
Console.WriteLine($"Saved: {outputFile}");
|
||||
|
||||
return 0;
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Build the project**
|
||||
|
||||
```bash
|
||||
dotnet build OpenNest.TestHarness/OpenNest.TestHarness.csproj
|
||||
```
|
||||
|
||||
Expected: Build succeeded with 0 errors.
|
||||
|
||||
- [ ] **Step 3: Run a smoke test with the real nest file**
|
||||
|
||||
```bash
|
||||
dotnet run --project OpenNest.TestHarness -- "C:\Users\AJ\Desktop\4980 A24 PT02 60x120 45pcs v2.zip"
|
||||
```
|
||||
|
||||
Expected: Prints nest info and results to stdout, writes debug log file, saves a `-result.zip` file.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add OpenNest.TestHarness/ OpenNest.sln
|
||||
git commit -m "feat: add OpenNest.TestHarness console app for engine testing"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Add the MCP test_engine tool
|
||||
|
||||
**Files:**
|
||||
- Create: `OpenNest.Mcp/Tools/TestTools.cs`
|
||||
|
||||
The MCP tool:
|
||||
1. Accepts optional `nestFile`, `drawingName`, `plateIndex` parameters
|
||||
2. Runs `dotnet run --project <path> -- <args>` capturing stdout (results) and stderr (errors only)
|
||||
3. Returns the summary + debug log file path (Claude can then Grep the log file)
|
||||
|
||||
Note: The solution root is hard-coded because the MCP server is published to `~/.claude/mcp/OpenNest.Mcp/`, far from the source tree.
|
||||
|
||||
- [ ] **Step 1: Create TestTools.cs**
|
||||
|
||||
```csharp
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace OpenNest.Mcp.Tools
|
||||
{
|
||||
[McpServerToolType]
|
||||
public class TestTools
|
||||
{
|
||||
private const string SolutionRoot = @"C:\Users\AJ\Desktop\Projects\OpenNest";
|
||||
|
||||
private static readonly string HarnessProject = Path.Combine(
|
||||
SolutionRoot, "OpenNest.TestHarness", "OpenNest.TestHarness.csproj");
|
||||
|
||||
[McpServerTool(Name = "test_engine")]
|
||||
[Description("Build and run the nesting engine against a nest file. Returns fill results and a debug log file path for grepping. Use this to test engine changes without restarting the MCP server.")]
|
||||
public string TestEngine(
|
||||
[Description("Path to the nest .zip file")] string nestFile = @"C:\Users\AJ\Desktop\4980 A24 PT02 60x120 45pcs v2.zip",
|
||||
[Description("Drawing name to fill with (default: first drawing)")] string drawingName = null,
|
||||
[Description("Plate index to fill (default: 0)")] int plateIndex = 0,
|
||||
[Description("Output nest file path (default: <input>-result.zip)")] string outputFile = null)
|
||||
{
|
||||
if (!File.Exists(nestFile))
|
||||
return $"Error: nest file not found: {nestFile}";
|
||||
|
||||
var processArgs = new StringBuilder();
|
||||
processArgs.Append($"\"{nestFile}\"");
|
||||
|
||||
if (!string.IsNullOrEmpty(drawingName))
|
||||
processArgs.Append($" --drawing \"{drawingName}\"");
|
||||
|
||||
processArgs.Append($" --plate {plateIndex}");
|
||||
|
||||
if (!string.IsNullOrEmpty(outputFile))
|
||||
processArgs.Append($" --output \"{outputFile}\"");
|
||||
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = "dotnet",
|
||||
Arguments = $"run --project \"{HarnessProject}\" -- {processArgs}",
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WorkingDirectory = SolutionRoot
|
||||
};
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
try
|
||||
{
|
||||
using var process = Process.Start(psi);
|
||||
var stderrTask = process.StandardError.ReadToEndAsync();
|
||||
var stdout = process.StandardOutput.ReadToEnd();
|
||||
process.WaitForExit(120_000);
|
||||
var stderr = stderrTask.Result;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(stdout))
|
||||
sb.Append(stdout.TrimEnd());
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(stderr))
|
||||
{
|
||||
sb.AppendLine();
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("=== Errors ===");
|
||||
sb.Append(stderr.TrimEnd());
|
||||
}
|
||||
|
||||
if (process.ExitCode != 0)
|
||||
{
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"Process exited with code {process.ExitCode}");
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
sb.AppendLine($"Error running test harness: {ex.Message}");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Build the MCP project**
|
||||
|
||||
```bash
|
||||
dotnet build OpenNest.Mcp/OpenNest.Mcp.csproj
|
||||
```
|
||||
|
||||
Expected: Build succeeded.
|
||||
|
||||
- [ ] **Step 3: Republish the MCP server**
|
||||
|
||||
```bash
|
||||
dotnet publish OpenNest.Mcp/OpenNest.Mcp.csproj -c Release -o "$USERPROFILE/.claude/mcp/OpenNest.Mcp"
|
||||
```
|
||||
|
||||
Expected: Publish succeeded. The MCP server now has the `test_engine` tool.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add OpenNest.Mcp/Tools/TestTools.cs
|
||||
git commit -m "feat: add test_engine MCP tool for iterative engine testing"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
After implementation, the workflow for iterating on FillLinear becomes:
|
||||
|
||||
1. **Other session** makes changes to `FillLinear.cs` or `NestEngine.cs`
|
||||
2. **This session** calls `test_engine` (no args needed — defaults to the test nest file)
|
||||
3. The tool builds the latest code and runs it in a fresh process
|
||||
4. Returns: part count, utilization, timing, and **debug log file path**
|
||||
5. Grep the log file for specific patterns (e.g., `[FillLinear]`, `[FindBestFill]`)
|
||||
6. Repeat
|
||||
281
docs/superpowers/plans/2026-03-12-contour-reindexing.md
Normal file
281
docs/superpowers/plans/2026-03-12-contour-reindexing.md
Normal file
@@ -0,0 +1,281 @@
|
||||
# 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 `SplitAt` method to `Line`**
|
||||
|
||||
Add the following method to the `Line` class (after the existing `ClosestPointTo` method):
|
||||
|
||||
```csharp
|
||||
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**
|
||||
|
||||
```bash
|
||||
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 `SplitAt` method to `Arc`**
|
||||
|
||||
Add the following method to the `Arc` class (after the existing `EndPoint` method):
|
||||
|
||||
```csharp
|
||||
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).
|
||||
- `splitAngle` is computed from `Center.AngleTo(point)`, normalized.
|
||||
- Both halves preserve center, radius, and `IsReversed` direction.
|
||||
|
||||
- [ ] **Step 2: Build to verify**
|
||||
|
||||
Run: `dotnet build OpenNest.Core/OpenNest.Core.csproj`
|
||||
Expected: Build succeeded, 0 errors
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
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 `ReindexAt` method to `Shape`**
|
||||
|
||||
Add the following method to the `Shape` class (after the existing `ClosestPointTo(Vector, out Entity)` method around line 201):
|
||||
|
||||
```csharp
|
||||
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**
|
||||
|
||||
```bash
|
||||
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 `ConvertShapeToMoves` private method**
|
||||
|
||||
Add the following private method to `ContourCuttingStrategy` (after the existing `SelectLeadOut` method, before the closing brace of the class):
|
||||
|
||||
```csharp
|
||||
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:
|
||||
```csharp
|
||||
// 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:
|
||||
```csharp
|
||||
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:
|
||||
```csharp
|
||||
throw new System.NotImplementedException("Contour re-indexing not yet implemented");
|
||||
```
|
||||
|
||||
With:
|
||||
```csharp
|
||||
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**
|
||||
|
||||
```bash
|
||||
git add OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs
|
||||
git commit -m "feat: wire contour re-indexing into ContourCuttingStrategy.Apply()"
|
||||
```
|
||||
Reference in New Issue
Block a user