merge: resolve conflicts from remote nesting progress changes
Kept using OpenNest.Api in Timing.cs and EditNestForm.cs alongside remote's reorganized usings and namespace changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,361 @@
|
||||
# Refactor Compactor Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Prune dead code from Compactor and deduplicate the Push overloads into a single scanning core.
|
||||
|
||||
**Architecture:** Delete 6 unused methods (Compact, CompactLoop, SavePositions, RestorePositions, CompactIndividual, CompactIndividualLoop). Unify the `Push(... PushDirection)` core overload to convert its PushDirection to a unit Vector and delegate to the `Push(... Vector)` overload, eliminating ~60 lines of duplicated obstacle scanning logic. PushBoundingBox stays separate since it's a fundamentally different algorithm (no geometry lines).
|
||||
|
||||
**Tech Stack:** C# / .NET 8
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Write Compactor Push tests as a safety net
|
||||
|
||||
No Compactor tests exist. Before changing anything, add tests for the public Push methods that have live callers: `Push(parts, obstacles, workArea, spacing, PushDirection)` and `Push(parts, obstacles, workArea, spacing, angle)`.
|
||||
|
||||
**Files:**
|
||||
- Create: `OpenNest.Tests/CompactorTests.cs`
|
||||
|
||||
- [ ] **Step 1: Write tests for Push with PushDirection**
|
||||
|
||||
```csharp
|
||||
using OpenNest;
|
||||
using OpenNest.Engine.Fill;
|
||||
using OpenNest.Geometry;
|
||||
using Xunit;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Tests
|
||||
{
|
||||
public class CompactorTests
|
||||
{
|
||||
private static Drawing MakeRectDrawing(double w, double h)
|
||||
{
|
||||
var pgm = new OpenNest.CNC.Program();
|
||||
pgm.Codes.Add(new OpenNest.CNC.RapidMove(new Vector(0, 0)));
|
||||
pgm.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(w, 0)));
|
||||
pgm.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(w, h)));
|
||||
pgm.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(0, h)));
|
||||
pgm.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(0, 0)));
|
||||
return new Drawing("rect", pgm);
|
||||
}
|
||||
|
||||
private static Part MakeRectPart(double x, double y, double w, double h)
|
||||
{
|
||||
var drawing = MakeRectDrawing(w, h);
|
||||
var part = new Part(drawing) { Location = new Vector(x, y) };
|
||||
part.UpdateBounds();
|
||||
return part;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Push_Left_MovesPartTowardEdge()
|
||||
{
|
||||
var workArea = new Box(0, 0, 100, 100);
|
||||
var part = MakeRectPart(50, 0, 10, 10);
|
||||
var moving = new List<Part> { part };
|
||||
var obstacles = new List<Part>();
|
||||
|
||||
var distance = Compactor.Push(moving, obstacles, workArea, 0, PushDirection.Left);
|
||||
|
||||
Assert.True(distance > 0);
|
||||
Assert.True(part.BoundingBox.Left < 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Push_Left_StopsAtObstacle()
|
||||
{
|
||||
var workArea = new Box(0, 0, 100, 100);
|
||||
var obstacle = MakeRectPart(0, 0, 10, 10);
|
||||
var part = MakeRectPart(50, 0, 10, 10);
|
||||
var moving = new List<Part> { part };
|
||||
var obstacles = new List<Part> { obstacle };
|
||||
|
||||
Compactor.Push(moving, obstacles, workArea, 0, PushDirection.Left);
|
||||
|
||||
Assert.True(part.BoundingBox.Left >= obstacle.BoundingBox.Right - 0.1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Push_Down_MovesPartTowardEdge()
|
||||
{
|
||||
var workArea = new Box(0, 0, 100, 100);
|
||||
var part = MakeRectPart(0, 50, 10, 10);
|
||||
var moving = new List<Part> { part };
|
||||
var obstacles = new List<Part>();
|
||||
|
||||
var distance = Compactor.Push(moving, obstacles, workArea, 0, PushDirection.Down);
|
||||
|
||||
Assert.True(distance > 0);
|
||||
Assert.True(part.BoundingBox.Bottom < 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Push_ReturnsZero_WhenAlreadyAtEdge()
|
||||
{
|
||||
var workArea = new Box(0, 0, 100, 100);
|
||||
var part = MakeRectPart(0, 0, 10, 10);
|
||||
var moving = new List<Part> { part };
|
||||
var obstacles = new List<Part>();
|
||||
|
||||
var distance = Compactor.Push(moving, obstacles, workArea, 0, PushDirection.Left);
|
||||
|
||||
Assert.Equal(0, distance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Push_WithSpacing_MaintainsGap()
|
||||
{
|
||||
var workArea = new Box(0, 0, 100, 100);
|
||||
var obstacle = MakeRectPart(0, 0, 10, 10);
|
||||
var part = MakeRectPart(50, 0, 10, 10);
|
||||
var moving = new List<Part> { part };
|
||||
var obstacles = new List<Part> { obstacle };
|
||||
|
||||
Compactor.Push(moving, obstacles, workArea, 2, PushDirection.Left);
|
||||
|
||||
Assert.True(part.BoundingBox.Left >= obstacle.BoundingBox.Right + 2 - 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Write tests for Push with angle (Vector-based)**
|
||||
|
||||
Add to the same file:
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public void Push_AngleLeft_MovesPartTowardEdge()
|
||||
{
|
||||
var workArea = new Box(0, 0, 100, 100);
|
||||
var part = MakeRectPart(50, 0, 10, 10);
|
||||
var moving = new List<Part> { part };
|
||||
var obstacles = new List<Part>();
|
||||
|
||||
// angle = π = push left
|
||||
var distance = Compactor.Push(moving, obstacles, workArea, 0, System.Math.PI);
|
||||
|
||||
Assert.True(distance > 0);
|
||||
Assert.True(part.BoundingBox.Left < 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Push_AngleDown_MovesPartTowardEdge()
|
||||
{
|
||||
var workArea = new Box(0, 0, 100, 100);
|
||||
var part = MakeRectPart(0, 50, 10, 10);
|
||||
var moving = new List<Part> { part };
|
||||
var obstacles = new List<Part>();
|
||||
|
||||
// angle = 3π/2 = push down
|
||||
var distance = Compactor.Push(moving, obstacles, workArea, 0, 3 * System.Math.PI / 2);
|
||||
|
||||
Assert.True(distance > 0);
|
||||
Assert.True(part.BoundingBox.Bottom < 1);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Write tests for PushBoundingBox**
|
||||
|
||||
Add to the same file:
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public void PushBoundingBox_Left_MovesPartTowardEdge()
|
||||
{
|
||||
var workArea = new Box(0, 0, 100, 100);
|
||||
var part = MakeRectPart(50, 0, 10, 10);
|
||||
var moving = new List<Part> { part };
|
||||
var obstacles = new List<Part>();
|
||||
|
||||
var distance = Compactor.PushBoundingBox(moving, obstacles, workArea, 0, PushDirection.Left);
|
||||
|
||||
Assert.True(distance > 0);
|
||||
Assert.True(part.BoundingBox.Left < 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PushBoundingBox_StopsAtObstacle()
|
||||
{
|
||||
var workArea = new Box(0, 0, 100, 100);
|
||||
var obstacle = MakeRectPart(0, 0, 10, 10);
|
||||
var part = MakeRectPart(50, 0, 10, 10);
|
||||
var moving = new List<Part> { part };
|
||||
var obstacles = new List<Part> { obstacle };
|
||||
|
||||
Compactor.PushBoundingBox(moving, obstacles, workArea, 0, PushDirection.Left);
|
||||
|
||||
Assert.True(part.BoundingBox.Left >= obstacle.BoundingBox.Right - 0.1);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run tests to verify they pass**
|
||||
|
||||
Run: `dotnet test OpenNest.Tests --filter "FullyQualifiedName~CompactorTests" -v n`
|
||||
Expected: All tests PASS (these test existing behavior before refactoring)
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add OpenNest.Tests/CompactorTests.cs
|
||||
git commit -m "test: add Compactor safety-net tests before refactor"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Delete dead code
|
||||
|
||||
Remove the 6 methods that have zero live callers: `Compact`, `CompactLoop`, `SavePositions`, `RestorePositions`, `CompactIndividual`, `CompactIndividualLoop`. Also remove the unused `contactGap` variable (line 181) and the misplaced XML doc comment above `RepeatThreshold` (lines 16-20, describes the deleted `Compact` method).
|
||||
|
||||
**Files:**
|
||||
- Modify: `OpenNest.Engine/Fill/Compactor.cs`
|
||||
|
||||
- [ ] **Step 1: Delete SavePositions and RestorePositions**
|
||||
|
||||
Delete `SavePositions` (lines 64-70) and `RestorePositions` (lines 72-76). These are only used by `Compact` and `CompactIndividual`.
|
||||
|
||||
- [ ] **Step 2: Delete Compact and CompactLoop**
|
||||
|
||||
Delete the `Compact` method (lines 24-44) and `CompactLoop` method (lines 46-62). Zero callers.
|
||||
|
||||
- [ ] **Step 3: Delete CompactIndividual and CompactIndividualLoop**
|
||||
|
||||
Delete `CompactIndividual` (lines 312-332) and `CompactIndividualLoop` (lines 334-360). Only caller is a commented-out line in `StripNestEngine.cs:189`.
|
||||
|
||||
- [ ] **Step 4: Remove the commented-out caller in StripNestEngine**
|
||||
|
||||
In `OpenNest.Engine/StripNestEngine.cs`, delete the entire commented-out block (lines 186-194):
|
||||
```csharp
|
||||
// TODO: Compact strip parts individually to close geometry-based gaps.
|
||||
// Disabled pending investigation — remnant finder picks up gaps created
|
||||
// by compaction and scatters parts into them.
|
||||
// Compactor.CompactIndividual(bestParts, workArea, Plate.PartSpacing);
|
||||
//
|
||||
// var compactedBox = bestParts.Cast<IBoundable>().GetBoundingBox();
|
||||
// bestDim = direction == StripDirection.Bottom
|
||||
// ? compactedBox.Top - workArea.Y
|
||||
// : compactedBox.Right - workArea.X;
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Clean up stale doc comment and dead variable**
|
||||
|
||||
Remove the orphaned XML doc comment above `RepeatThreshold` (lines 16-20 — it describes the deleted `Compact` method). Remove the `RepeatThreshold` and `MaxIterations` constants (only used by the deleted loop methods). Remove the unused `contactGap` variable from the `Push(... PushDirection)` method (line 181).
|
||||
|
||||
- [ ] **Step 6: Run tests**
|
||||
|
||||
Run: `dotnet test OpenNest.Tests --filter "FullyQualifiedName~CompactorTests" -v n`
|
||||
Expected: All tests PASS (deleted code was unused)
|
||||
|
||||
- [ ] **Step 7: Build full solution to verify no compilation errors**
|
||||
|
||||
Run: `dotnet build OpenNest.sln`
|
||||
Expected: Build succeeded, 0 errors
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add OpenNest.Engine/Fill/Compactor.cs OpenNest.Engine/StripNestEngine.cs
|
||||
git commit -m "refactor(compactor): remove dead code — Compact, CompactIndividual, and helpers"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Deduplicate Push overloads
|
||||
|
||||
The `Push(... PushDirection)` core overload (lines 166-238) duplicates the obstacle scanning loop from `Push(... Vector)` (lines 102-164). Convert `PushDirection` to a unit `Vector` and delegate.
|
||||
|
||||
**Files:**
|
||||
- Modify: `OpenNest.Engine/Fill/Compactor.cs`
|
||||
|
||||
- [ ] **Step 1: Replace the Push(... PushDirection) core overload**
|
||||
|
||||
Replace the full body of `Push(List<Part> movingParts, List<Part> obstacleParts, Box workArea, double partSpacing, PushDirection direction)` with a delegation to the Vector overload:
|
||||
|
||||
```csharp
|
||||
public static double Push(List<Part> movingParts, List<Part> obstacleParts,
|
||||
Box workArea, double partSpacing, PushDirection direction)
|
||||
{
|
||||
var vector = SpatialQuery.DirectionToOffset(direction, 1.0);
|
||||
return Push(movingParts, obstacleParts, workArea, partSpacing, vector);
|
||||
}
|
||||
```
|
||||
|
||||
This works because `DirectionToOffset(Left, 1.0)` returns `(-1, 0)`, which is the unit vector for "push left" — exactly what `new Vector(Math.Cos(π), Math.Sin(π))` produces. The Vector overload already handles edge distance, obstacle scanning, geometry lines, and offset application identically.
|
||||
|
||||
- [ ] **Step 2: Update the angle-based Push to accept Vector directly**
|
||||
|
||||
Rename the existing `Push(... double angle)` core overload to accept a `Vector` direction instead of computing it internally. This avoids a redundant cos/sin when the PushDirection overload already provides a unit vector.
|
||||
|
||||
Change the signature from:
|
||||
```csharp
|
||||
public static double Push(List<Part> movingParts, List<Part> obstacleParts,
|
||||
Box workArea, double partSpacing, double angle)
|
||||
```
|
||||
to:
|
||||
```csharp
|
||||
public static double Push(List<Part> movingParts, List<Part> obstacleParts,
|
||||
Box workArea, double partSpacing, Vector direction)
|
||||
```
|
||||
|
||||
Remove the `var direction = new Vector(...)` line from the body since `direction` is now a parameter.
|
||||
|
||||
- [ ] **Step 3: Update the angle convenience overload to convert**
|
||||
|
||||
The convenience overload `Push(List<Part> movingParts, Plate plate, double angle)` must now convert the angle to a Vector before calling the core:
|
||||
|
||||
```csharp
|
||||
public static double Push(List<Part> movingParts, Plate plate, double angle)
|
||||
{
|
||||
var obstacleParts = plate.Parts
|
||||
.Where(p => !movingParts.Contains(p))
|
||||
.ToList();
|
||||
|
||||
var direction = new Vector(System.Math.Cos(angle), System.Math.Sin(angle));
|
||||
return Push(movingParts, obstacleParts, plate.WorkArea(), plate.PartSpacing, direction);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run tests**
|
||||
|
||||
Run: `dotnet test OpenNest.Tests --filter "FullyQualifiedName~CompactorTests" -v n`
|
||||
Expected: All tests PASS
|
||||
|
||||
- [ ] **Step 5: Build full solution**
|
||||
|
||||
Run: `dotnet build OpenNest.sln`
|
||||
Expected: Build succeeded, 0 errors. All callers in FillExtents, ActionClone, PlateView, PatternTileForm compile without changes — their call signatures are unchanged.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add OpenNest.Engine/Fill/Compactor.cs
|
||||
git commit -m "refactor(compactor): deduplicate Push — PushDirection delegates to Vector overload"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Final cleanup and verify
|
||||
|
||||
**Files:**
|
||||
- Modify: `OpenNest.Engine/Fill/Compactor.cs` (if needed)
|
||||
|
||||
- [ ] **Step 1: Run full test suite**
|
||||
|
||||
Run: `dotnet test OpenNest.Tests -v n`
|
||||
Expected: All tests PASS
|
||||
|
||||
- [ ] **Step 2: Verify Compactor is clean**
|
||||
|
||||
The final Compactor should have 6 public methods:
|
||||
1. `Push(parts, plate, PushDirection)` — convenience, extracts plate fields
|
||||
2. `Push(parts, plate, angle)` — convenience, converts angle to Vector
|
||||
3. `Push(parts, obstacles, workArea, spacing, PushDirection)` — converts to Vector, delegates
|
||||
4. `Push(parts, obstacles, workArea, spacing, Vector)` — the single scanning core
|
||||
5. `PushBoundingBox(parts, plate, direction)` — convenience
|
||||
6. `PushBoundingBox(parts, obstacles, workArea, spacing, direction)` — BB-only core
|
||||
|
||||
Plus one constant: `ChordTolerance`.
|
||||
|
||||
File should be ~110-120 lines, down from 362.
|
||||
@@ -0,0 +1,660 @@
|
||||
# Two-Bucket Preview Parts Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Split PlateView nesting preview into stationary (overall best) and active (current strategy) layers so the preview never regresses and acceptance always uses the engine's result.
|
||||
|
||||
**Architecture:** Add `IsOverallBest` flag to `NestProgress` so the engine can distinguish overall-best reports from strategy-local progress. PlateView maintains two `List<LayoutPart>` buckets drawn at different opacities. Acceptance uses the engine's returned parts directly, decoupling preview from acceptance.
|
||||
|
||||
**Tech Stack:** C# / .NET 8 / WinForms / System.Drawing
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-03-18-two-bucket-preview-design.md`
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add IsOverallBest to NestProgress and ReportProgress
|
||||
|
||||
**Files:**
|
||||
- Modify: `OpenNest.Engine/NestProgress.cs:37-49`
|
||||
- Modify: `OpenNest.Engine/NestEngineBase.cs:188-236`
|
||||
|
||||
- [ ] **Step 1: Add property to NestProgress**
|
||||
|
||||
In `OpenNest.Engine/NestProgress.cs`, add after line 48 (`ActiveWorkArea`):
|
||||
|
||||
```csharp
|
||||
public bool IsOverallBest { get; set; }
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add parameter to ReportProgress**
|
||||
|
||||
In `OpenNest.Engine/NestEngineBase.cs`, change the `ReportProgress` signature (line 188) to:
|
||||
|
||||
```csharp
|
||||
internal static void ReportProgress(
|
||||
IProgress<NestProgress> progress,
|
||||
NestPhase phase,
|
||||
int plateNumber,
|
||||
List<Part> best,
|
||||
Box workArea,
|
||||
string description,
|
||||
bool isOverallBest = false)
|
||||
```
|
||||
|
||||
In the same method, add `IsOverallBest = isOverallBest` to the `NestProgress` initializer (after line 235 `ActiveWorkArea = workArea`):
|
||||
|
||||
```csharp
|
||||
IsOverallBest = isOverallBest,
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Build to verify**
|
||||
|
||||
Run: `dotnet build OpenNest.Engine/OpenNest.Engine.csproj`
|
||||
Expected: Build succeeded, 0 errors. Existing callers use the default `false`.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```
|
||||
feat(engine): add IsOverallBest flag to NestProgress
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Flag overall-best reports in DefaultNestEngine
|
||||
|
||||
**Files:**
|
||||
- Modify: `OpenNest.Engine/DefaultNestEngine.cs:55-58` (final report in Fill(NestItem))
|
||||
- Modify: `OpenNest.Engine/DefaultNestEngine.cs:83-85` (final report in Fill(List<Part>))
|
||||
- Modify: `OpenNest.Engine/DefaultNestEngine.cs:132-139` (RunPipeline strategy loop)
|
||||
|
||||
- [ ] **Step 1: Update RunPipeline — replace conditional report with unconditional overall-best report**
|
||||
|
||||
In `RunPipeline` (line 132-139), change from:
|
||||
|
||||
```csharp
|
||||
if (IsBetterFill(result, context.CurrentBest, context.WorkArea))
|
||||
{
|
||||
context.CurrentBest = result;
|
||||
context.CurrentBestScore = FillScore.Compute(result, context.WorkArea);
|
||||
context.WinnerPhase = strategy.Phase;
|
||||
ReportProgress(context.Progress, strategy.Phase, PlateNumber,
|
||||
result, context.WorkArea, BuildProgressSummary());
|
||||
}
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```csharp
|
||||
if (IsBetterFill(result, context.CurrentBest, context.WorkArea))
|
||||
{
|
||||
context.CurrentBest = result;
|
||||
context.CurrentBestScore = FillScore.Compute(result, context.WorkArea);
|
||||
context.WinnerPhase = strategy.Phase;
|
||||
}
|
||||
|
||||
if (context.CurrentBest != null && context.CurrentBest.Count > 0)
|
||||
{
|
||||
ReportProgress(context.Progress, context.WinnerPhase, PlateNumber,
|
||||
context.CurrentBest, context.WorkArea, BuildProgressSummary(),
|
||||
isOverallBest: true);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Flag final report in Fill(NestItem, Box, ...)**
|
||||
|
||||
In `Fill(NestItem item, Box workArea, ...)` (line 58), change:
|
||||
|
||||
```csharp
|
||||
ReportProgress(progress, WinnerPhase, PlateNumber, best, workArea, BuildProgressSummary());
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```csharp
|
||||
ReportProgress(progress, WinnerPhase, PlateNumber, best, workArea, BuildProgressSummary(),
|
||||
isOverallBest: true);
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Flag final report in Fill(List<Part>, Box, ...)**
|
||||
|
||||
In `Fill(List<Part> groupParts, Box workArea, ...)` (line 85), change:
|
||||
|
||||
```csharp
|
||||
ReportProgress(progress, NestPhase.Linear, PlateNumber, best, workArea, BuildProgressSummary());
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```csharp
|
||||
ReportProgress(progress, NestPhase.Linear, PlateNumber, best, workArea, BuildProgressSummary(),
|
||||
isOverallBest: true);
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Build to verify**
|
||||
|
||||
Run: `dotnet build OpenNest.Engine/OpenNest.Engine.csproj`
|
||||
Expected: Build succeeded, 0 errors.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```
|
||||
feat(engine): flag overall-best progress reports in DefaultNestEngine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Add active preview style to ColorScheme
|
||||
|
||||
**Files:**
|
||||
- Modify: `OpenNest/ColorScheme.cs:58-61` (pen/brush declarations)
|
||||
- Modify: `OpenNest/ColorScheme.cs:160-176` (PreviewPartColor setter)
|
||||
|
||||
- [ ] **Step 1: Add pen/brush declarations**
|
||||
|
||||
In `ColorScheme.cs`, after line 60 (`PreviewPartBrush`), add:
|
||||
|
||||
```csharp
|
||||
public Pen ActivePreviewPartPen { get; private set; }
|
||||
|
||||
public Brush ActivePreviewPartBrush { get; private set; }
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Create resources in PreviewPartColor setter**
|
||||
|
||||
In the `PreviewPartColor` setter (lines 160-176), change from:
|
||||
|
||||
```csharp
|
||||
set
|
||||
{
|
||||
previewPartColor = value;
|
||||
|
||||
if (PreviewPartPen != null)
|
||||
PreviewPartPen.Dispose();
|
||||
|
||||
if (PreviewPartBrush != null)
|
||||
PreviewPartBrush.Dispose();
|
||||
|
||||
PreviewPartPen = new Pen(value, 1);
|
||||
PreviewPartBrush = new SolidBrush(Color.FromArgb(60, value));
|
||||
}
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```csharp
|
||||
set
|
||||
{
|
||||
previewPartColor = value;
|
||||
|
||||
if (PreviewPartPen != null)
|
||||
PreviewPartPen.Dispose();
|
||||
|
||||
if (PreviewPartBrush != null)
|
||||
PreviewPartBrush.Dispose();
|
||||
|
||||
if (ActivePreviewPartPen != null)
|
||||
ActivePreviewPartPen.Dispose();
|
||||
|
||||
if (ActivePreviewPartBrush != null)
|
||||
ActivePreviewPartBrush.Dispose();
|
||||
|
||||
PreviewPartPen = new Pen(value, 1);
|
||||
PreviewPartBrush = new SolidBrush(Color.FromArgb(60, value));
|
||||
ActivePreviewPartPen = new Pen(Color.FromArgb(128, value), 1);
|
||||
ActivePreviewPartBrush = new SolidBrush(Color.FromArgb(30, value));
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Build to verify**
|
||||
|
||||
Run: `dotnet build OpenNest/OpenNest.csproj`
|
||||
Expected: Build succeeded, 0 errors.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```
|
||||
feat(ui): add active preview brush/pen to ColorScheme
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Two-bucket preview parts in PlateView
|
||||
|
||||
**Files:**
|
||||
- Modify: `OpenNest/Controls/PlateView.cs`
|
||||
|
||||
This task replaces the single `temporaryParts` list with `stationaryParts` and `activeParts`, updates the public API, drawing, and all internal references.
|
||||
|
||||
- [ ] **Step 1: Replace field and add new list**
|
||||
|
||||
Change line 34:
|
||||
|
||||
```csharp
|
||||
private List<LayoutPart> temporaryParts = new List<LayoutPart>();
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```csharp
|
||||
private List<LayoutPart> stationaryParts = new List<LayoutPart>();
|
||||
private List<LayoutPart> activeParts = new List<LayoutPart>();
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update SetPlate (line 152-153)**
|
||||
|
||||
Change:
|
||||
|
||||
```csharp
|
||||
temporaryParts.Clear();
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```csharp
|
||||
stationaryParts.Clear();
|
||||
activeParts.Clear();
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Update Refresh (line 411)**
|
||||
|
||||
Change:
|
||||
|
||||
```csharp
|
||||
temporaryParts.ForEach(p => p.Update(this));
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```csharp
|
||||
stationaryParts.ForEach(p => p.Update(this));
|
||||
activeParts.ForEach(p => p.Update(this));
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Update UpdateMatrix (line 1085)**
|
||||
|
||||
Change:
|
||||
|
||||
```csharp
|
||||
temporaryParts.ForEach(p => p.Update(this));
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```csharp
|
||||
stationaryParts.ForEach(p => p.Update(this));
|
||||
activeParts.ForEach(p => p.Update(this));
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Replace the temporary parts drawing block in DrawParts (lines 506-522)**
|
||||
|
||||
Change:
|
||||
|
||||
```csharp
|
||||
// Draw temporary (preview) parts
|
||||
for (var i = 0; i < temporaryParts.Count; i++)
|
||||
{
|
||||
var temp = temporaryParts[i];
|
||||
|
||||
if (temp.IsDirty)
|
||||
temp.Update(this);
|
||||
|
||||
var path = temp.Path;
|
||||
var pathBounds = path.GetBounds();
|
||||
|
||||
if (!pathBounds.IntersectsWith(viewBounds))
|
||||
continue;
|
||||
|
||||
g.FillPath(ColorScheme.PreviewPartBrush, path);
|
||||
g.DrawPath(ColorScheme.PreviewPartPen, path);
|
||||
}
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```csharp
|
||||
// Draw stationary preview parts (overall best — full opacity)
|
||||
for (var i = 0; i < stationaryParts.Count; i++)
|
||||
{
|
||||
var part = stationaryParts[i];
|
||||
|
||||
if (part.IsDirty)
|
||||
part.Update(this);
|
||||
|
||||
var path = part.Path;
|
||||
if (!path.GetBounds().IntersectsWith(viewBounds))
|
||||
continue;
|
||||
|
||||
g.FillPath(ColorScheme.PreviewPartBrush, path);
|
||||
g.DrawPath(ColorScheme.PreviewPartPen, path);
|
||||
}
|
||||
|
||||
// Draw active preview parts (current strategy — reduced opacity)
|
||||
for (var i = 0; i < activeParts.Count; i++)
|
||||
{
|
||||
var part = activeParts[i];
|
||||
|
||||
if (part.IsDirty)
|
||||
part.Update(this);
|
||||
|
||||
var path = part.Path;
|
||||
if (!path.GetBounds().IntersectsWith(viewBounds))
|
||||
continue;
|
||||
|
||||
g.FillPath(ColorScheme.ActivePreviewPartBrush, path);
|
||||
g.DrawPath(ColorScheme.ActivePreviewPartPen, path);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Replace public API methods (lines 882-910)**
|
||||
|
||||
Replace `SetTemporaryParts`, `ClearTemporaryParts`, and `AcceptTemporaryParts` with:
|
||||
|
||||
```csharp
|
||||
public void SetStationaryParts(List<Part> parts)
|
||||
{
|
||||
stationaryParts.Clear();
|
||||
|
||||
if (parts != null)
|
||||
{
|
||||
foreach (var part in parts)
|
||||
stationaryParts.Add(LayoutPart.Create(part, this));
|
||||
}
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
public void SetActiveParts(List<Part> parts)
|
||||
{
|
||||
activeParts.Clear();
|
||||
|
||||
if (parts != null)
|
||||
{
|
||||
foreach (var part in parts)
|
||||
activeParts.Add(LayoutPart.Create(part, this));
|
||||
}
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
public void ClearPreviewParts()
|
||||
{
|
||||
stationaryParts.Clear();
|
||||
activeParts.Clear();
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
public void AcceptPreviewParts(List<Part> parts)
|
||||
{
|
||||
if (parts != null)
|
||||
{
|
||||
foreach (var part in parts)
|
||||
Plate.Parts.Add(part);
|
||||
}
|
||||
|
||||
stationaryParts.Clear();
|
||||
activeParts.Clear();
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Update FillWithProgress (lines 912-957)**
|
||||
|
||||
Change the progress callback (lines 918-923):
|
||||
|
||||
```csharp
|
||||
var progress = new Progress<NestProgress>(p =>
|
||||
{
|
||||
progressForm.UpdateProgress(p);
|
||||
SetTemporaryParts(p.BestParts);
|
||||
ActiveWorkArea = p.ActiveWorkArea;
|
||||
});
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```csharp
|
||||
var progress = new Progress<NestProgress>(p =>
|
||||
{
|
||||
progressForm.UpdateProgress(p);
|
||||
|
||||
if (p.IsOverallBest)
|
||||
SetStationaryParts(p.BestParts);
|
||||
else
|
||||
SetActiveParts(p.BestParts);
|
||||
|
||||
ActiveWorkArea = p.ActiveWorkArea;
|
||||
});
|
||||
```
|
||||
|
||||
Change the acceptance block (lines 933-943):
|
||||
|
||||
```csharp
|
||||
if (parts.Count > 0 && (!cts.IsCancellationRequested || progressForm.Accepted))
|
||||
{
|
||||
SetTemporaryParts(parts);
|
||||
AcceptTemporaryParts();
|
||||
sw.Stop();
|
||||
Status = $"Fill: {parts.Count} parts in {sw.ElapsedMilliseconds} ms";
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearTemporaryParts();
|
||||
}
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```csharp
|
||||
if (parts.Count > 0 && (!cts.IsCancellationRequested || progressForm.Accepted))
|
||||
{
|
||||
AcceptPreviewParts(parts);
|
||||
sw.Stop();
|
||||
Status = $"Fill: {parts.Count} parts in {sw.ElapsedMilliseconds} ms";
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearPreviewParts();
|
||||
}
|
||||
```
|
||||
|
||||
Change the catch block (line 949):
|
||||
|
||||
```csharp
|
||||
ClearTemporaryParts();
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```csharp
|
||||
ClearPreviewParts();
|
||||
```
|
||||
|
||||
- [ ] **Step 8: Build to verify**
|
||||
|
||||
Run: `dotnet build OpenNest/OpenNest.csproj`
|
||||
Expected: Build errors in `MainForm.cs` (still references old API). That is expected — Task 5 fixes it.
|
||||
|
||||
- [ ] **Step 9: Commit**
|
||||
|
||||
```
|
||||
feat(ui): two-bucket preview parts in PlateView
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Update MainForm progress callbacks and acceptance
|
||||
|
||||
**Files:**
|
||||
- Modify: `OpenNest/Forms/MainForm.cs`
|
||||
|
||||
Three progress callback sites and their acceptance points need updating.
|
||||
|
||||
- [ ] **Step 1: Update auto-nest callback (RunAutoNest_Click, line 827)**
|
||||
|
||||
Change:
|
||||
|
||||
```csharp
|
||||
var progress = new Progress<NestProgress>(p =>
|
||||
{
|
||||
progressForm.UpdateProgress(p);
|
||||
activeForm.PlateView.SetTemporaryParts(p.BestParts);
|
||||
activeForm.PlateView.ActiveWorkArea = p.ActiveWorkArea;
|
||||
});
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```csharp
|
||||
var progress = new Progress<NestProgress>(p =>
|
||||
{
|
||||
progressForm.UpdateProgress(p);
|
||||
|
||||
if (p.IsOverallBest)
|
||||
activeForm.PlateView.SetStationaryParts(p.BestParts);
|
||||
else
|
||||
activeForm.PlateView.SetActiveParts(p.BestParts);
|
||||
|
||||
activeForm.PlateView.ActiveWorkArea = p.ActiveWorkArea;
|
||||
});
|
||||
```
|
||||
|
||||
Change `ClearTemporaryParts()` on line 866 to `ClearPreviewParts()`.
|
||||
|
||||
Change `ClearTemporaryParts()` in the catch block (line 884) to `ClearPreviewParts()`.
|
||||
|
||||
- [ ] **Step 2: Update fill-plate callback (FillPlate_Click, line 962)**
|
||||
|
||||
Replace the progress setup (lines 962-976):
|
||||
|
||||
```csharp
|
||||
var progressForm = new NestProgressForm(nestingCts, showPlateRow: false);
|
||||
var highWaterMark = 0;
|
||||
|
||||
var progress = new Progress<NestProgress>(p =>
|
||||
{
|
||||
progressForm.UpdateProgress(p);
|
||||
|
||||
if (p.BestParts != null && p.BestPartCount >= highWaterMark)
|
||||
{
|
||||
highWaterMark = p.BestPartCount;
|
||||
activeForm.PlateView.SetTemporaryParts(p.BestParts);
|
||||
}
|
||||
|
||||
activeForm.PlateView.ActiveWorkArea = p.ActiveWorkArea;
|
||||
});
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```csharp
|
||||
var progressForm = new NestProgressForm(nestingCts, showPlateRow: false);
|
||||
|
||||
var progress = new Progress<NestProgress>(p =>
|
||||
{
|
||||
progressForm.UpdateProgress(p);
|
||||
|
||||
if (p.IsOverallBest)
|
||||
activeForm.PlateView.SetStationaryParts(p.BestParts);
|
||||
else
|
||||
activeForm.PlateView.SetActiveParts(p.BestParts);
|
||||
|
||||
activeForm.PlateView.ActiveWorkArea = p.ActiveWorkArea;
|
||||
});
|
||||
```
|
||||
|
||||
Change acceptance (line 990-993):
|
||||
|
||||
```csharp
|
||||
if (parts.Count > 0)
|
||||
activeForm.PlateView.AcceptTemporaryParts();
|
||||
else
|
||||
activeForm.PlateView.ClearTemporaryParts();
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```csharp
|
||||
if (parts.Count > 0)
|
||||
activeForm.PlateView.AcceptPreviewParts(parts);
|
||||
else
|
||||
activeForm.PlateView.ClearPreviewParts();
|
||||
```
|
||||
|
||||
Change `ClearTemporaryParts()` in the catch block to `ClearPreviewParts()`.
|
||||
|
||||
- [ ] **Step 3: Update fill-area callback (FillArea_Click, line 1031)**
|
||||
|
||||
Replace the progress setup (lines 1031-1045):
|
||||
|
||||
```csharp
|
||||
var progressForm = new NestProgressForm(nestingCts, showPlateRow: false);
|
||||
var highWaterMark = 0;
|
||||
|
||||
var progress = new Progress<NestProgress>(p =>
|
||||
{
|
||||
progressForm.UpdateProgress(p);
|
||||
|
||||
if (p.BestParts != null && p.BestPartCount >= highWaterMark)
|
||||
{
|
||||
highWaterMark = p.BestPartCount;
|
||||
activeForm.PlateView.SetTemporaryParts(p.BestParts);
|
||||
}
|
||||
|
||||
activeForm.PlateView.ActiveWorkArea = p.ActiveWorkArea;
|
||||
});
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```csharp
|
||||
var progressForm = new NestProgressForm(nestingCts, showPlateRow: false);
|
||||
|
||||
var progress = new Progress<NestProgress>(p =>
|
||||
{
|
||||
progressForm.UpdateProgress(p);
|
||||
|
||||
if (p.IsOverallBest)
|
||||
activeForm.PlateView.SetStationaryParts(p.BestParts);
|
||||
else
|
||||
activeForm.PlateView.SetActiveParts(p.BestParts);
|
||||
|
||||
activeForm.PlateView.ActiveWorkArea = p.ActiveWorkArea;
|
||||
});
|
||||
```
|
||||
|
||||
Change the `onComplete` callback (lines 1047-1052):
|
||||
|
||||
```csharp
|
||||
Action<List<Part>> onComplete = parts =>
|
||||
{
|
||||
if (parts != null && parts.Count > 0)
|
||||
activeForm.PlateView.AcceptTemporaryParts();
|
||||
else
|
||||
activeForm.PlateView.ClearTemporaryParts();
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```csharp
|
||||
Action<List<Part>> onComplete = parts =>
|
||||
{
|
||||
if (parts != null && parts.Count > 0)
|
||||
activeForm.PlateView.AcceptPreviewParts(parts);
|
||||
else
|
||||
activeForm.PlateView.ClearPreviewParts();
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Build full solution**
|
||||
|
||||
Run: `dotnet build OpenNest.sln`
|
||||
Expected: Build succeeded, 0 errors (only pre-existing nullable warnings in OpenNest.Gpu).
|
||||
|
||||
- [ ] **Step 5: Run tests**
|
||||
|
||||
Run: `dotnet test OpenNest.Tests/OpenNest.Tests.csproj`
|
||||
Expected: All tests pass.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```
|
||||
feat(ui): route progress to stationary/active buckets in MainForm
|
||||
```
|
||||
Reference in New Issue
Block a user