feat(mcp): add nesting tools — fill_plate, fill_area, fill_remnants, pack_plate
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
180
OpenNest.Mcp/Tools/NestingTools.cs
Normal file
180
OpenNest.Mcp/Tools/NestingTools.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using ModelContextProtocol.Server;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Mcp.Tools
|
||||
{
|
||||
public class NestingTools
|
||||
{
|
||||
private readonly NestSession _session;
|
||||
|
||||
public NestingTools(NestSession session)
|
||||
{
|
||||
_session = session;
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "fill_plate")]
|
||||
[Description("Fill an entire plate with a single drawing. Returns parts added and utilization.")]
|
||||
public string FillPlate(
|
||||
[Description("Index of the plate to fill")] int plateIndex,
|
||||
[Description("Name of the drawing to fill with")] string drawingName,
|
||||
[Description("Maximum quantity to place (0 = unlimited)")] int quantity = 0)
|
||||
{
|
||||
var plate = _session.GetPlate(plateIndex);
|
||||
if (plate == null)
|
||||
return $"Error: plate {plateIndex} not found";
|
||||
|
||||
var drawing = _session.GetDrawing(drawingName);
|
||||
if (drawing == null)
|
||||
return $"Error: drawing '{drawingName}' not found";
|
||||
|
||||
var countBefore = plate.Parts.Count;
|
||||
var engine = new NestEngine(plate);
|
||||
var item = new NestItem { Drawing = drawing, Quantity = quantity };
|
||||
var success = engine.Fill(item);
|
||||
|
||||
var countAfter = plate.Parts.Count;
|
||||
var added = countAfter - countBefore;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"Fill plate {plateIndex} with '{drawingName}': {(success ? "success" : "failed")}");
|
||||
sb.AppendLine($" Parts added: {added}");
|
||||
sb.AppendLine($" Total parts: {countAfter}");
|
||||
sb.AppendLine($" Utilization: {plate.Utilization():P1}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "fill_area")]
|
||||
[Description("Fill a specific rectangular area on a plate with a single drawing.")]
|
||||
public string FillArea(
|
||||
[Description("Index of the plate")] int plateIndex,
|
||||
[Description("Name of the drawing to fill with")] string drawingName,
|
||||
[Description("X origin of the area")] double x,
|
||||
[Description("Y origin of the area")] double y,
|
||||
[Description("Width of the area")] double width,
|
||||
[Description("Height of the area")] double height,
|
||||
[Description("Maximum quantity to place (0 = unlimited)")] int quantity = 0)
|
||||
{
|
||||
var plate = _session.GetPlate(plateIndex);
|
||||
if (plate == null)
|
||||
return $"Error: plate {plateIndex} not found";
|
||||
|
||||
var drawing = _session.GetDrawing(drawingName);
|
||||
if (drawing == null)
|
||||
return $"Error: drawing '{drawingName}' not found";
|
||||
|
||||
var countBefore = plate.Parts.Count;
|
||||
var engine = new NestEngine(plate);
|
||||
var item = new NestItem { Drawing = drawing, Quantity = quantity };
|
||||
var area = new Box(x, y, width, height);
|
||||
var success = engine.Fill(item, area);
|
||||
|
||||
var countAfter = plate.Parts.Count;
|
||||
var added = countAfter - countBefore;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"Fill area ({x:F1},{y:F1} {width:F1}x{height:F1}) on plate {plateIndex} with '{drawingName}': {(success ? "success" : "failed")}");
|
||||
sb.AppendLine($" Parts added: {added}");
|
||||
sb.AppendLine($" Total parts: {countAfter}");
|
||||
sb.AppendLine($" Utilization: {plate.Utilization():P1}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "fill_remnants")]
|
||||
[Description("Find empty remnant regions on a plate and fill each with a drawing.")]
|
||||
public string FillRemnants(
|
||||
[Description("Index of the plate")] int plateIndex,
|
||||
[Description("Name of the drawing to fill with")] string drawingName,
|
||||
[Description("Maximum quantity per remnant (0 = unlimited)")] int quantity = 0)
|
||||
{
|
||||
var plate = _session.GetPlate(plateIndex);
|
||||
if (plate == null)
|
||||
return $"Error: plate {plateIndex} not found";
|
||||
|
||||
var drawing = _session.GetDrawing(drawingName);
|
||||
if (drawing == null)
|
||||
return $"Error: drawing '{drawingName}' not found";
|
||||
|
||||
var remnants = plate.GetRemnants();
|
||||
|
||||
if (remnants.Count == 0)
|
||||
return $"No remnant areas found on plate {plateIndex}";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"Found {remnants.Count} remnant area(s) on plate {plateIndex}");
|
||||
|
||||
var totalAdded = 0;
|
||||
var engine = new NestEngine(plate);
|
||||
|
||||
for (var i = 0; i < remnants.Count; i++)
|
||||
{
|
||||
var remnant = remnants[i];
|
||||
var countBefore = plate.Parts.Count;
|
||||
var item = new NestItem { Drawing = drawing, Quantity = quantity };
|
||||
var success = engine.Fill(item, remnant);
|
||||
var added = plate.Parts.Count - countBefore;
|
||||
totalAdded += added;
|
||||
|
||||
sb.AppendLine($" Remnant {i}: ({remnant.X:F1},{remnant.Y:F1} {remnant.Width:F1}x{remnant.Height:F1}) -> {added} parts {(success ? "" : "(no fit)")}");
|
||||
}
|
||||
|
||||
sb.AppendLine($"Total parts added: {totalAdded}");
|
||||
sb.AppendLine($"Utilization: {plate.Utilization():P1}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "pack_plate")]
|
||||
[Description("Pack multiple drawings onto a plate using bin-packing. Specify drawings and quantities as comma-separated lists.")]
|
||||
public string PackPlate(
|
||||
[Description("Index of the plate")] int plateIndex,
|
||||
[Description("Comma-separated drawing names")] string drawingNames,
|
||||
[Description("Comma-separated quantities for each drawing")] string quantities)
|
||||
{
|
||||
var plate = _session.GetPlate(plateIndex);
|
||||
if (plate == null)
|
||||
return $"Error: plate {plateIndex} not found";
|
||||
|
||||
var names = drawingNames.Split(',').Select(n => n.Trim()).ToArray();
|
||||
var qtys = quantities.Split(',').Select(q => int.Parse(q.Trim())).ToArray();
|
||||
|
||||
if (names.Length != qtys.Length)
|
||||
return $"Error: drawing names count ({names.Length}) does not match quantities count ({qtys.Length})";
|
||||
|
||||
var items = new List<NestItem>();
|
||||
|
||||
for (var i = 0; i < names.Length; i++)
|
||||
{
|
||||
var drawing = _session.GetDrawing(names[i]);
|
||||
if (drawing == null)
|
||||
return $"Error: drawing '{names[i]}' not found";
|
||||
|
||||
items.Add(new NestItem { Drawing = drawing, Quantity = qtys[i] });
|
||||
}
|
||||
|
||||
var countBefore = plate.Parts.Count;
|
||||
var engine = new NestEngine(plate);
|
||||
var success = engine.Pack(items);
|
||||
var countAfter = plate.Parts.Count;
|
||||
var added = countAfter - countBefore;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"Pack plate {plateIndex}: {(success ? "success" : "failed")}");
|
||||
sb.AppendLine($" Parts added: {added}");
|
||||
sb.AppendLine($" Total parts: {countAfter}");
|
||||
sb.AppendLine($" Utilization: {plate.Utilization():P1}");
|
||||
|
||||
// Breakdown by drawing
|
||||
var groups = plate.Parts.GroupBy(p => p.BaseDrawing.Name);
|
||||
foreach (var group in groups)
|
||||
sb.AppendLine($" {group.Key}: {group.Count()}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user