feat: add NFP-based mixed-part autonesting

Implement geometry-aware nesting using No-Fit Polygons and simulated
annealing optimization. Parts interlock based on true shape rather than
bounding boxes, producing tighter layouts for mixed-part scenarios.

New types in Core/Geometry:
- ConvexDecomposition: ear-clipping triangulation for concave polygons
- NoFitPolygon: Minkowski sum via convex decomposition + Clipper2 union
- InnerFitPolygon: feasible region computation for plate placement

New types in Engine:
- NfpCache: caches NFPs keyed by (drawingId, rotation) pairs
- BottomLeftFill: places parts using feasible regions from IFP - NFP union
- INestOptimizer: abstraction for future GA/parallel upgrades
- SimulatedAnnealing: optimizes part ordering and rotation

Integration:
- NestEngine.AutoNest(): new public entry point for mixed-part nesting
- MainForm.RunAutoNest_Click: uses AutoNest instead of Pack
- NestingTools.autonest_plate: new MCP tool for Claude Code integration
- Drawing.Id: auto-incrementing identifier for NFP cache keys
- Clipper2 NuGet added to OpenNest.Core for polygon boolean operations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 08:08:22 -04:00
parent 9f84357c34
commit 3f3b07ef5d
12 changed files with 1447 additions and 7 deletions
+13 -7
View File
@@ -680,26 +680,32 @@ namespace OpenNest.Forms
return;
var items = form.GetNestItems();
var qty = new int[items.Count];
while (true)
{
for (int i = 0; i < items.Count; i++)
qty[i] = items[i].Drawing.Quantity.Remaining;
var remaining = items.Where(i => i.Quantity > 0).ToList();
if (remaining.Count == 0)
break;
var plate = activeForm.PlateView.Plate.Parts.Count > 0
? activeForm.Nest.CreatePlate()
: activeForm.PlateView.Plate;
var engine = new NestEngine(plate);
var parts = NestEngine.AutoNest(remaining, plate);
if (!engine.Pack(items))
if (parts.Count == 0)
break;
plate.Parts.AddRange(parts);
activeForm.Nest.UpdateDrawingQuantities();
for (int i = 0; i < items.Count; i++)
items[i].Quantity -= qty[i] - items[i].Drawing.Quantity.Remaining;
// Reduce remaining quantities by how many were placed per drawing.
foreach (var item in remaining)
{
var placed = parts.Count(p => p.BaseDrawing == item.Drawing);
item.Quantity -= placed;
}
}
}