From cd858578166d2a3493ce963b0a243b6d02026279 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Sun, 15 Mar 2026 21:40:21 -0400 Subject: [PATCH] feat: integrate StripNestEngine into autonest_plate MCP tool Runs strip and sequential strategies in competition, picks the denser result. Reports scores for both strategies in output. Co-Authored-By: Claude Opus 4.6 (1M context) --- OpenNest.Mcp/Tools/NestingTools.cs | 92 +++++++++++++++--------------- 1 file changed, 47 insertions(+), 45 deletions(-) diff --git a/OpenNest.Mcp/Tools/NestingTools.cs b/OpenNest.Mcp/Tools/NestingTools.cs index 4ee5fc3..21f02ef 100644 --- a/OpenNest.Mcp/Tools/NestingTools.cs +++ b/OpenNest.Mcp/Tools/NestingTools.cs @@ -233,58 +233,28 @@ namespace OpenNest.Mcp.Tools items.Add(new NestItem { Drawing = drawing, Quantity = qtys[i] }); } - var fillItems = items - .Where(i => i.Quantity > 1) - .OrderBy(i => i.Priority) - .ThenByDescending(i => i.Drawing.Area) - .ToList(); + // Strategy 1: Strip nesting + var stripEngine = new StripNestEngine(plate); + var stripResult = stripEngine.Nest(items, null, CancellationToken.None); + var stripScore = FillScore.Compute(stripResult, plate.WorkArea()); - var packItems = items - .Where(i => i.Quantity == 1) - .ToList(); + // Strategy 2: Current sequential fill + var seqResult = SequentialFill(plate, items); + var seqScore = FillScore.Compute(seqResult, plate.WorkArea()); - var workArea = plate.WorkArea(); - var totalPlaced = 0; - - // Phase 1: Fill multi-quantity drawings with NestEngine. - foreach (var item in fillItems) - { - if (item.Quantity <= 0 || workArea.Width <= 0 || workArea.Length <= 0) - continue; - - var engine = NestEngineRegistry.Create(plate); - var parts = engine.FillExact(item, workArea, null, CancellationToken.None); - - if (parts.Count > 0) - { - plate.Parts.AddRange(parts); - // TODO: Compactor.Compact(parts, plate); - item.Quantity = System.Math.Max(0, item.Quantity - parts.Count); - totalPlaced += parts.Count; - var placedBox = parts.Cast().GetBoundingBox(); - workArea = ComputeRemainderWithin(workArea, placedBox, plate.PartSpacing); - } - } - - // Phase 2: Pack single-quantity items into remaining space. - packItems = packItems.Where(i => i.Quantity > 0).ToList(); - - if (packItems.Count > 0 && workArea.Width > 0 && workArea.Length > 0) - { - var engine = NestEngineRegistry.Create(plate); - var packParts = engine.PackArea(workArea, packItems, null, CancellationToken.None); - if (packParts.Count > 0) - { - plate.Parts.AddRange(packParts); - totalPlaced += packParts.Count; - } - } + // Pick winner and apply to plate. + var winner = stripScore >= seqScore ? stripResult : seqResult; + var winnerName = stripScore >= seqScore ? "strip" : "sequential"; + plate.Parts.AddRange(winner); + var totalPlaced = winner.Count; var sb = new StringBuilder(); - sb.AppendLine($"AutoNest plate {plateIndex}: {(totalPlaced > 0 ? "success" : "no parts placed")}"); + sb.AppendLine($"AutoNest plate {plateIndex} ({winnerName} strategy): {(totalPlaced > 0 ? "success" : "no parts placed")}"); sb.AppendLine($" Parts placed: {totalPlaced}"); sb.AppendLine($" Total parts: {plate.Parts.Count}"); sb.AppendLine($" Utilization: {plate.Utilization():P1}"); + sb.AppendLine($" Strip score: {stripScore.Count} parts, density {stripScore.Density:P1}"); + sb.AppendLine($" Sequential score: {seqScore.Count} parts, density {seqScore.Density:P1}"); var groups = plate.Parts.GroupBy(p => p.BaseDrawing.Name); foreach (var group in groups) @@ -293,6 +263,38 @@ namespace OpenNest.Mcp.Tools return sb.ToString(); } + private static List SequentialFill(Plate plate, List items) + { + var fillItems = items + .Where(i => i.Quantity != 1) + .OrderBy(i => i.Priority) + .ThenByDescending(i => i.Drawing.Area) + .ToList(); + + var workArea = plate.WorkArea(); + var allParts = new List(); + + foreach (var item in fillItems) + { + if (item.Quantity == 0 || workArea.Width <= 0 || workArea.Length <= 0) + continue; + + var engine = new DefaultNestEngine(plate); + var parts = engine.Fill( + new NestItem { Drawing = item.Drawing, Quantity = item.Quantity }, + workArea, null, CancellationToken.None); + + if (parts.Count > 0) + { + allParts.AddRange(parts); + var placedBox = parts.Cast().GetBoundingBox(); + workArea = ComputeRemainderWithin(workArea, placedBox, plate.PartSpacing); + } + } + + return allParts; + } + private static Box ComputeRemainderWithin(Box workArea, Box usedBox, double spacing) { var hWidth = workArea.Right - usedBox.Right - spacing;