mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-11 05:14:13 -04:00
feat(mcp): complete jobs with finite mixed sheet stock
This commit is contained in:
1 parent
f1d1df3f17
commit
41737a4f4c
4 files changed
+258
-18
No files matched your search
@@ -15,13 +15,13 @@ public class McpWholeJobTests : IDisposable
|
||||
public McpWholeJobTests() => Directory.CreateDirectory(directory);
|
||||
public void Dispose() => Directory.Delete(directory, true);
|
||||
|
||||
private static Drawing Square(string name)
|
||||
private static Drawing Square(string name, double size = 2)
|
||||
{
|
||||
var shape = new Shape();
|
||||
shape.Entities.Add(new Line(new Vector(0, 0), new Vector(2, 0)));
|
||||
shape.Entities.Add(new Line(new Vector(2, 0), new Vector(2, 2)));
|
||||
shape.Entities.Add(new Line(new Vector(2, 2), new Vector(0, 2)));
|
||||
shape.Entities.Add(new Line(new Vector(0, 2), new Vector(0, 0)));
|
||||
shape.Entities.Add(new Line(new Vector(0, 0), new Vector(size, 0)));
|
||||
shape.Entities.Add(new Line(new Vector(size, 0), new Vector(size, size)));
|
||||
shape.Entities.Add(new Line(new Vector(size, size), new Vector(0, size)));
|
||||
shape.Entities.Add(new Line(new Vector(0, size), new Vector(0, 0)));
|
||||
return new Drawing(name, OpenNest.Converters.ConvertGeometry.ToProgram(shape));
|
||||
}
|
||||
|
||||
@@ -38,6 +38,173 @@ public class McpWholeJobTests : IDisposable
|
||||
string engine, bool noNew = false, CancellationToken token = default) =>
|
||||
tools.AutoNestJob(plateIndex, names, quantities, engine, noNew, token);
|
||||
|
||||
[Fact]
|
||||
public void FiniteSheetInputsReachEngineWithTemplateSettings()
|
||||
{
|
||||
var session = Session();
|
||||
var template = session.GetPlate(0);
|
||||
template.Size = new Size(60, 120);
|
||||
template.EdgeSpacing = new Spacing(1, 2, 3, 4);
|
||||
template.Quadrant = 3;
|
||||
var engine = "StockMap-" + Guid.NewGuid();
|
||||
var called = false;
|
||||
NestingEngineRegistry.Register(engine, "inspect finite stock", () => new InspectStub(job =>
|
||||
{
|
||||
called = true;
|
||||
Assert.Equal(2, job.Plates.Count);
|
||||
Assert.Equal(["stock-0", "stock-1"], job.Plates.Select(s => s.Id));
|
||||
Assert.Equal([new Size(60, 120), new Size(48, 96)], job.Plates.Select(s => s.Size));
|
||||
Assert.Equal([1, 2], job.Plates.Select(s => s.Quantity));
|
||||
Assert.All(job.Plates, stock =>
|
||||
{
|
||||
Assert.Equal(0.25, stock.PartSpacing);
|
||||
Assert.Equal(template.EdgeSpacing, stock.EdgeSpacing);
|
||||
Assert.Equal(3, stock.Quadrant);
|
||||
});
|
||||
Assert.Equal(2, job.Options.MaxPlates); // capped by demand, not plate.Quantity
|
||||
return new NestJobResult(NestJobStatus.Incomplete, NestJobStopReason.StockExhausted, [], [], []);
|
||||
}));
|
||||
var rows = new[] { new SheetStockInput { Width = 60, Length = 120, Quantity = 1 },
|
||||
new SheetStockInput { Width = 48, Length = 96, Quantity = 2 } };
|
||||
var tool = new NestingTools(session);
|
||||
Assert.Contains("incomplete", tool.AutoNestJob(0, "A,B", "1,1", engine, sheets: rows));
|
||||
Assert.True(called);
|
||||
called = false;
|
||||
rows[1].Width = double.NaN;
|
||||
Assert.Contains("sheets[1]", tool.AutoNestJob(0, "A,B", "1,1", engine, sheets: rows));
|
||||
Assert.False(called);
|
||||
Assert.Single(session.AllPlates());
|
||||
Assert.Empty(template.Parts);
|
||||
Assert.Equal(1, template.Quantity); // no inventory drawn from the template quantity
|
||||
}
|
||||
|
||||
private sealed class InspectStub(Func<NestJob, NestJobResult> solve) : INestingEngine
|
||||
{
|
||||
public NestJobResult Solve(NestJob job, IProgress<NestJobProgress>? progress = null,
|
||||
CancellationToken token = default) => solve(job);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FiniteMixedSheetsCommitByStockIdAndRoundTrip()
|
||||
{
|
||||
var session = Session();
|
||||
var template = session.GetPlate(0);
|
||||
template.Size = new Size(6, 6);
|
||||
template.Quantity = 9; // repeat count is not inventory
|
||||
var small = session.Nest.Drawings.Single(d => d.Name == "B");
|
||||
var large = Square("large", 4); // four-by-four cannot fit a three-by-three sheet
|
||||
session.Nest.Drawings.Add(large);
|
||||
var rows = new[] { new SheetStockInput { Width = 6, Length = 6, Quantity = 1 },
|
||||
new SheetStockInput { Width = 3, Length = 3, Quantity = 2 } };
|
||||
var engine = "MixedStock-" + Guid.NewGuid();
|
||||
NestingEngineRegistry.Register(engine, "two different sheets", () => new InspectStub(job =>
|
||||
{
|
||||
Assert.Equal(2, job.Options.MaxPlates);
|
||||
Assert.Equal([1, 2], job.Plates.Select(s => s.Quantity));
|
||||
// Small stock first: result ordinal must not resize/reuse the large template.
|
||||
return new NestJobResult(NestJobStatus.Complete, NestJobStopReason.Completed,
|
||||
[new NestJobPlateResult(0, job.Plates[1], [new NestJobPlacement(job.Parts[1].Id, 0, 0.5, 0.5, 0)]),
|
||||
new NestJobPlateResult(1, job.Plates[0], [new NestJobPlacement(job.Parts[0].Id, 1, 0.5, 0.5, 0)])], [], []);
|
||||
}));
|
||||
var text = new NestingTools(session).AutoNestJob(0, "large,B", "1,1", engine, sheets: rows);
|
||||
Assert.Contains("Whole job complete", text);
|
||||
Assert.Contains("stock-0 (6x6): used=1, remaining=0, reused=1, created=0", text);
|
||||
Assert.Contains("stock-1 (3x3): used=1, remaining=1, reused=0, created=1", text);
|
||||
Assert.Contains("plate 0 -> stock-0", text);
|
||||
Assert.Contains("plate 1 -> stock-1", text);
|
||||
Assert.Same(template, session.GetPlate(0));
|
||||
Assert.Equal(new Size(6, 6), template.Size);
|
||||
Assert.Same(large, Assert.Single(template.Parts).BaseDrawing);
|
||||
Assert.Same(small, Assert.Single(session.GetPlate(1).Parts).BaseDrawing);
|
||||
Assert.Equal(1, template.Quantity);
|
||||
var path = Path.Combine(directory, "mixed.nest");
|
||||
Assert.Contains("Saved nest", new InputTools(session).SaveNest(path));
|
||||
var reloaded = new NestReader(path).Read();
|
||||
Assert.Equal([new Size(6, 6), new Size(3, 3)], reloaded.Plates.Select(p => p.Size));
|
||||
Assert.All(reloaded.Plates, p => Assert.Equal(1, p.Quantity));
|
||||
Assert.Equal(1, reloaded.Drawings.Single(d => d.Name == "large").Quantity.Nested);
|
||||
Assert.Equal(1, reloaded.Drawings.Single(d => d.Name == "B").Quantity.Nested);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FiniteStockOverdrawIsRejectedBeforeAnySessionMutation()
|
||||
{
|
||||
var session = Session();
|
||||
var plate = session.GetPlate(0);
|
||||
plate.Size = new Size(3, 3);
|
||||
var engine = "Overdraw-" + Guid.NewGuid();
|
||||
NestingEngineRegistry.Register(engine, "overdraw stock", () => new InspectStub(job =>
|
||||
new NestJobResult(NestJobStatus.Complete, NestJobStopReason.Completed,
|
||||
[new NestJobPlateResult(0, job.Plates[0], [new NestJobPlacement(job.Parts[0].Id, 0, 0.5, 0.5, 0)]),
|
||||
new NestJobPlateResult(1, job.Plates[0], [new NestJobPlacement(job.Parts[1].Id, 1, 0.5, 0.5, 0)])], [], [])));
|
||||
var text = new NestingTools(session).AutoNestJob(0, "A,B", "1,1", engine,
|
||||
sheets: [new SheetStockInput { Width = 3, Length = 3, Quantity = 1 },
|
||||
new SheetStockInput { Width = 6, Length = 6, Quantity = 2 }]);
|
||||
Assert.Contains("stock-0", text);
|
||||
Assert.Contains("invalid", text, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Single(session.AllPlates());
|
||||
Assert.Empty(plate.Parts);
|
||||
Assert.All(session.Nest.Drawings, d => Assert.Equal(0, d.Quantity.Nested));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FiniteStockRejectsInvalidInventoryBeforeSolving()
|
||||
{
|
||||
var session = Session();
|
||||
var plate = session.GetPlate(0);
|
||||
var engine = "NeverSolve-" + Guid.NewGuid();
|
||||
NestingEngineRegistry.Register(engine, "should not be called", () => new InspectStub(_ =>
|
||||
throw new Xunit.Sdk.XunitException("invalid input reached solver")));
|
||||
var tools = new NestingTools(session);
|
||||
var valid = new SheetStockInput { Width = 10, Length = 10, Quantity = 1 };
|
||||
Assert.Contains("at least one", tools.AutoNestJob(0, "A", "1", engine, sheets: []));
|
||||
Assert.Contains("sheets[0]", tools.AutoNestJob(0, "A", "1", engine,
|
||||
sheets: [new SheetStockInput { Width = 10, Length = 10, Quantity = 0 }]));
|
||||
Assert.Contains("sheets[0]", tools.AutoNestJob(0, "A", "1", engine,
|
||||
sheets: [new SheetStockInput { Width = double.PositiveInfinity, Length = 10, Quantity = 1 }]));
|
||||
Assert.Contains("sheets[0]", tools.AutoNestJob(0, "A", "1", engine,
|
||||
sheets: [new SheetStockInput { Width = 0, Length = 10, Quantity = 1 }]));
|
||||
Assert.Contains("sheets[1] duplicates sheets[0]", tools.AutoNestJob(0, "A", "1", engine,
|
||||
sheets: [valid, new SheetStockInput { Width = 10, Length = 10, Quantity = 2 }]));
|
||||
Assert.Contains("sheet inventory exceeds", tools.AutoNestJob(0, "A", "1", engine,
|
||||
sheets: [new SheetStockInput { Width = 10, Length = 10, Quantity = int.MaxValue },
|
||||
new SheetStockInput { Width = 11, Length = 11, Quantity = 1 }]));
|
||||
Assert.Contains("cannot be combined", tools.AutoNestJob(0, "A", "1", engine,
|
||||
no_new_plates: true, sheets: [valid]));
|
||||
Assert.Single(session.AllPlates());
|
||||
Assert.Empty(plate.Parts);
|
||||
Assert.Equal(1, plate.Quantity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FiniteInventoryUsesOnlyOneOfTwoMatchingEmptySheets()
|
||||
{
|
||||
var session = Session();
|
||||
var original = session.GetPlate(0);
|
||||
session.Plates.Add(new Plate(10, 10) { PartSpacing = 0.25, Quantity = 5 });
|
||||
var text = new NestingTools(session).AutoNestJob(0, "A", "1", "Rectangles",
|
||||
sheets: [new SheetStockInput { Width = 10, Length = 10, Quantity = 1 }]);
|
||||
Assert.Contains("Whole job complete", text);
|
||||
Assert.Contains("stock-0 (10x10): used=1, remaining=0", text);
|
||||
Assert.Single(original.Parts);
|
||||
Assert.Empty(session.GetPlate(1).Parts);
|
||||
Assert.Equal(5, session.GetPlate(1).Quantity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LaterSelectedTemplateIsReusedBeforeOtherCompatibleEmptySheet()
|
||||
{
|
||||
var session = Session();
|
||||
var earlier = session.GetPlate(0);
|
||||
var selected = new Plate(10, 10) { PartSpacing = 0.25, Quantity = 3 };
|
||||
session.Plates.Add(selected);
|
||||
var text = new NestingTools(session).AutoNestJob(1, "A", "1", "Rectangles");
|
||||
Assert.Contains("Whole job complete", text);
|
||||
Assert.Empty(earlier.Parts);
|
||||
Assert.Single(selected.Parts);
|
||||
Assert.Equal(1, selected.Quantity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreatesSheetsForCompleteMixedJobThenSavesAndReloads()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace OpenNest.Mcp;
|
||||
|
||||
/// <summary>Request-local physical inventory for one whole-job solve.</summary>
|
||||
public sealed class SheetStockInput
|
||||
{
|
||||
public double Width { get; set; }
|
||||
public double Length { get; set; }
|
||||
public int Quantity { get; set; }
|
||||
}
|
||||
@@ -336,14 +336,15 @@ namespace OpenNest.Mcp.Tools
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "autonest_job")]
|
||||
[Description("Complete requested drawing quantities across as many matching sheets as needed. The selected empty plate is the stock template; by default new sheets may be added. An incomplete or invalid proposal changes nothing.")]
|
||||
[Description("Complete requested drawing quantities across physical sheets. The selected empty plate supplies common settings; optional sheets offer finite sizes and quantities. An incomplete or invalid proposal changes nothing.")]
|
||||
public string AutoNestJob(
|
||||
[Description("Index of an empty plate to use as the stock template")] int plateIndex,
|
||||
[Description("Comma-separated drawing names")] string drawingNames,
|
||||
[Description("Comma-separated total requested quantities for the entire session")] string quantities,
|
||||
[Description(JobEngines)] string engine = null,
|
||||
[Description("Use only already-created empty plates with matching dimensions and spacing; do not add sheets")] bool no_new_plates = false,
|
||||
CancellationToken cancellationToken = default
|
||||
CancellationToken cancellationToken = default,
|
||||
[Description("Optional finite physical sheet stock: width, length and available quantity per size. Incompatible with no_new_plates.")] SheetStockInput[] sheets = null
|
||||
)
|
||||
{
|
||||
var template = _session.GetPlate(plateIndex);
|
||||
@@ -351,6 +352,11 @@ namespace OpenNest.Mcp.Tools
|
||||
return $"Error: plate {plateIndex} not found";
|
||||
if (template.Parts.Count != 0 || template.CutOffs.Count != 0)
|
||||
return "Error: stock template must be empty and have no cutoff definitions. Nothing committed.";
|
||||
if (sheets != null && no_new_plates)
|
||||
return "Error: sheets cannot be combined with no_new_plates. Nothing committed.";
|
||||
var offeredStock = BuildSheetStock(template, sheets);
|
||||
if (offeredStock.error != null)
|
||||
return offeredStock.error;
|
||||
if (string.IsNullOrWhiteSpace(drawingNames) || string.IsNullOrWhiteSpace(quantities))
|
||||
return "Error: drawingNames and quantities are required";
|
||||
var parsed = ParseItems(drawingNames, quantities);
|
||||
@@ -390,7 +396,8 @@ namespace OpenNest.Mcp.Tools
|
||||
var demand = remaining.Sum(item => (long)item.Quantity);
|
||||
if (demand > int.MaxValue)
|
||||
return "Error: total remaining demand exceeds the supported sheet limit. Nothing committed.";
|
||||
var limit = no_new_plates ? System.Math.Min(available.Count, (int)demand) : (int)demand;
|
||||
var limit = no_new_plates ? System.Math.Min(available.Count, (int)demand)
|
||||
: sheets != null ? (int)System.Math.Min(offeredStock.total, demand) : (int)demand;
|
||||
var engineName = string.IsNullOrWhiteSpace(engine) ? _session.DefaultEngineName : engine.Trim();
|
||||
if (NestingEngineRegistry.ResolveName(engineName) == null)
|
||||
return $"Error: unknown whole-job engine '{engineName}'. Nothing committed. Available: {string.Join(", ", NestingEngineRegistry.AvailableEngines.Select(e => e.Name))}";
|
||||
@@ -398,7 +405,7 @@ namespace OpenNest.Mcp.Tools
|
||||
try
|
||||
{
|
||||
result = NestPipeline.Run(new NestPipelineRequest(engineName, remaining,
|
||||
NestStockBuilder.FromTemplate(template, null, no_new_plates ? available.Count : null),
|
||||
sheets == null ? NestStockBuilder.FromTemplate(template, null, no_new_plates ? available.Count : null) : offeredStock.stock,
|
||||
new NestJobOptions(maxPlates: limit)), token: cancellationToken);
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
@@ -422,33 +429,88 @@ namespace OpenNest.Mcp.Tools
|
||||
if (result.Plates.Count > limit)
|
||||
return "Error: proposal exceeds the allowed sheet count. Nothing committed.";
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
for (var i = 0; i < result.Plates.Count; i++)
|
||||
// Build the entire target map before attaching a single part. Indexes in a mixed
|
||||
// stock proposal are solver ordinals, not indexes into the session's empty plates.
|
||||
var usedTargets = new HashSet<Plate>(ReferenceEqualityComparer.Instance);
|
||||
var targets = new List<(Plate plate, bool created, string stockId)>();
|
||||
foreach (var proposed in result.Plates)
|
||||
{
|
||||
var proposed = result.Plates[i];
|
||||
var target = i < available.Count ? available[i] : new Plate(proposed.Stock.Size)
|
||||
var target = new[] { template }.Concat(existing.Where(p => !ReferenceEquals(p, template)))
|
||||
.FirstOrDefault(p => !usedTargets.Contains(p)
|
||||
&& p.Parts.Count == 0 && p.CutOffs.Count == 0
|
||||
&& SameStock(p, template, proposed.Stock.Size));
|
||||
var created = target == null;
|
||||
target ??= new Plate(proposed.Stock.Size)
|
||||
{
|
||||
GrainAngle = template.GrainAngle,
|
||||
CuttingParameters = template.CuttingParameters,
|
||||
};
|
||||
usedTargets.Add(target);
|
||||
targets.Add((target, created, proposed.Stock.Id));
|
||||
}
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
for (var i = 0; i < result.Plates.Count; i++)
|
||||
{
|
||||
var proposed = result.Plates[i];
|
||||
var (target, created, _) = targets[i];
|
||||
target.Size = proposed.Stock.Size;
|
||||
target.PartSpacing = proposed.Stock.PartSpacing;
|
||||
target.EdgeSpacing = proposed.Stock.EdgeSpacing;
|
||||
target.Quadrant = proposed.Stock.Quadrant;
|
||||
target.Quantity = 1;
|
||||
if (i >= available.Count)
|
||||
if (created)
|
||||
_session.Plates.Add(target);
|
||||
target.Parts.AddRange(proposed.Parts);
|
||||
}
|
||||
var lines = new StringBuilder();
|
||||
lines.AppendLine($"Whole job complete: {result.Plates.Count} sheet(s) placed, {System.Math.Max(0, result.Plates.Count - available.Count)} new sheet(s) created.");
|
||||
lines.AppendLine($"Whole job complete: {result.Plates.Count} sheet(s) placed, {targets.Count(t => t.created)} new sheet(s) created.");
|
||||
if (sheets != null)
|
||||
{
|
||||
foreach (var stock in offeredStock.stock)
|
||||
{
|
||||
var used = targets.Count(t => t.stockId == stock.Id);
|
||||
lines.AppendLine($" {stock.Id} ({stock.Size.Width}x{stock.Size.Length}): used={used}, remaining={stock.Quantity - used}, reused={targets.Count(t => !t.created && t.stockId == stock.Id)}, created={targets.Count(t => t.created && t.stockId == stock.Id)}");
|
||||
}
|
||||
foreach (var (target, _, stockId) in targets)
|
||||
lines.AppendLine($" plate {_session.AllPlates().FindIndex(p => ReferenceEquals(p, target))} -> {stockId}");
|
||||
}
|
||||
foreach (var item in parsed.items)
|
||||
lines.AppendLine($" {item.Drawing.Name}: requested={item.Quantity}, already={counts[item.Drawing]}, newly placed={placed.GetValueOrDefault(item.Drawing)}, remaining=0");
|
||||
return lines.ToString();
|
||||
}
|
||||
|
||||
private static bool SameStock(Plate a, Plate b) =>
|
||||
a.Size.Width == b.Size.Width && a.Size.Length == b.Size.Length
|
||||
private static (List<NestPlateStock> stock, long total, string error) BuildSheetStock(Plate template, SheetStockInput[] sheets)
|
||||
{
|
||||
if (sheets == null)
|
||||
return (null, 0, null);
|
||||
if (sheets.Length == 0)
|
||||
return (null, 0, "Error: sheets must contain at least one stock size. Nothing committed.");
|
||||
var stock = new List<NestPlateStock>(sheets.Length);
|
||||
long total = 0;
|
||||
for (var i = 0; i < sheets.Length; i++)
|
||||
{
|
||||
var row = sheets[i];
|
||||
if (row == null || !double.IsFinite(row.Width) || !double.IsFinite(row.Length)
|
||||
|| row.Width <= 0 || row.Length <= 0 || row.Quantity <= 0
|
||||
|| row.Length <= template.EdgeSpacing.Left + template.EdgeSpacing.Right
|
||||
|| row.Width <= template.EdgeSpacing.Top + template.EdgeSpacing.Bottom)
|
||||
return (null, 0, $"Error: sheets[{i}] requires finite positive usable dimensions and a positive physical quantity. Nothing committed.");
|
||||
var duplicate = stock.FindIndex(s => s.Size.Width == row.Width && s.Size.Length == row.Length);
|
||||
if (duplicate >= 0)
|
||||
return (null, 0, $"Error: sheets[{i}] duplicates sheets[{duplicate}] size. Nothing committed.");
|
||||
total += row.Quantity;
|
||||
if (total > int.MaxValue)
|
||||
return (null, 0, "Error: total sheet inventory exceeds the supported sheet limit. Nothing committed.");
|
||||
stock.Add(new NestPlateStock($"stock-{i}", new Size(row.Width, row.Length), row.Quantity,
|
||||
template.PartSpacing, template.EdgeSpacing, template.Quadrant));
|
||||
}
|
||||
return (stock, total, null);
|
||||
}
|
||||
|
||||
private static bool SameStock(Plate a, Plate b) => SameStock(a, b, b.Size);
|
||||
|
||||
private static bool SameStock(Plate a, Plate b, Size size) =>
|
||||
a.Size.Width == size.Width && a.Size.Length == size.Length
|
||||
&& a.PartSpacing == b.PartSpacing && a.EdgeSpacing.Equals(b.EdgeSpacing)
|
||||
&& a.Quadrant == b.Quadrant && a.GrainAngle == b.GrainAngle
|
||||
&& (a.CuttingParameters == null && b.CuttingParameters == null
|
||||
|
||||
@@ -50,7 +50,9 @@ MCP `autonest_plate` requires an empty target. The stdio server serializes all t
|
||||
|
||||
MCP `pack_plate` and `autonest_plate` report each requested drawing's newly placed count and remaining quantity, indexed by request; partial fulfillment is explicitly warned about. Per-sheet ratios are not enforced or required: an uneven sheet is acceptable if the remaining quantities are placed elsewhere in the nest. These two plate tools operate on a single selected plate and do not automatically finish a multi-sheet job; callers can use `autonest_job` for that. Duplicate request tokens and ambiguous drawing names are refused before placement. A zero-quantity pack request places zero parts for that drawing; negative quantities are rejected.
|
||||
|
||||
`autonest_job(plateIndex, drawingNames, quantities, engine?, no_new_plates=false)` is the whole-session completion path. Quantities are total targets across existing placed parts (physical plate quantity included) plus newly proposed sheets. The selected empty cutoff-free plate supplies stock size, spacing, quadrant, grain and cutting settings; other empty plates with equivalent persisted settings may be reused first, then additional matching plates are created as needed. `no_new_plates=true` limits the solve to existing matching empty plates. The request uses the independently validated `NestPipeline` and commits only when every requested quantity is met; invalid, incomplete, cancelled or unknown-engine proposals leave existing plates and counts unchanged. A no-placement solver result is not proof that geometry cannot fit. `pack_plate` and `autonest_plate` remain single-sheet operations.
|
||||
`autonest_job(plateIndex, drawingNames, quantities, engine?, no_new_plates=false, sheets?)` is the whole-session completion path. Quantities are total targets across existing placed parts (physical plate quantity included) plus newly proposed sheets. Without `sheets`, the selected empty cutoff-free plate supplies stock size, spacing, quadrant, grain and cutting settings; other empty plates with equivalent persisted settings may be reused first, then additional matching plates are created as needed. `no_new_plates=true` limits this legacy solve to existing matching empty plates. The request uses the independently validated `NestPipeline` and commits only when every requested quantity is met; invalid, incomplete, cancelled or unknown-engine proposals leave existing plates and counts unchanged. A no-placement solver result is not proof that geometry cannot fit. `pack_plate` and `autonest_plate` remain single-sheet operations.
|
||||
|
||||
For finite mixed-size inventory, supply `sheets` as an array, for example `{"plateIndex":0,"drawingNames":"large,small","quantities":"1,1","engine":"Rectangles","sheets":[{"width":60,"length":120,"quantity":1},{"width":48,"length":96,"quantity":2}]}`. Width and length have the same units and orientation as `create_plate`. Every row's positive integer `quantity` is the total physical sheets available for this solve, including any compatible pre-created empty plates; it is never a `Plate.Quantity` repeat multiplier or additional stock. The selected plate supplies common spacing, edge spacing, quadrant, grain and cutting settings, but is reused only if its size matches a chosen sheet. Other compatible empty sheets can be reused; occupied or cutoff-bearing sheets cannot. Only needed different-sized sheets are created. Duplicate size pairs, nonpositive or nonfinite dimensions, unusable work areas, nonpositive quantities, an empty list and excessive total inventory are refused before solving. `sheets` cannot be combined with `no_new_plates=true`. Omit `sheets` to retain the previous single-size behavior. Successful output reports `stock-0`, `stock-1`, etc. in input order with used/remaining/reused/created physical counts, plus committed session plate index to stock ID and per-drawing totals. Stock is request-local, not a persistent purchasing catalog. Unused empty templates may be omitted by the `.nest` writer on save.
|
||||
|
||||
MCP `delete_plate(plateIndex)` removes only a sheet with no parts and no cutoff definitions, from either loaded or newly created session plates. Occupied, cutoff-bearing and invalid indices are refused without changing the session. After deletion every later index shifts down by one; inspect `get_plate_info` before further edits. This is session cleanup, not a data purge or a save-time option. `save_nest` lists all session plates in its response, but the existing `.nest` writer omits empty cutoff-free plates from the serialized file; reloading may therefore have fewer plates than the response says. There is no opt-in switch to preserve empty plates in the file.
|
||||
|
||||
|
||||
Reference in new issue
Block a user