diff --git a/OpenNest.Desktop.Tests/AutoNestJobTests.cs b/OpenNest.Desktop.Tests/AutoNestJobTests.cs
new file mode 100644
index 00000000..a8c8dbd0
--- /dev/null
+++ b/OpenNest.Desktop.Tests/AutoNestJobTests.cs
@@ -0,0 +1,93 @@
+using OpenNest.CNC;
+using OpenNest.Engine.Jobs;
+using OpenNest.Geometry;
+
+namespace OpenNest.Desktop.Tests;
+
+public class AutoNestJobTests
+{
+ private static Drawing Square(string name, double side, int required)
+ {
+ var program = new Program();
+ program.Codes.Add(new RapidMove(new Vector(0, 0)));
+ program.Codes.Add(new LinearMove(new Vector(side, 0)));
+ program.Codes.Add(new LinearMove(new Vector(side, side)));
+ program.Codes.Add(new LinearMove(new Vector(0, side)));
+ program.Codes.Add(new LinearMove(new Vector(0, 0)));
+ var drawing = new Drawing(name, program);
+ drawing.Quantity.Required = required;
+ return drawing;
+ }
+
+ private static Nest NestWith(params Drawing[] drawings)
+ {
+ var nest = new Nest { Name = "job" };
+ foreach (var drawing in drawings)
+ nest.Drawings.Add(drawing);
+ nest.Plates.Add(new Plate(new Size(48, 96)) { Quantity = 1 });
+ return nest;
+ }
+
+ [Fact]
+ public void ForRemaining_AsksForWhatIsNotPlacedYet_OnTheTemplateStock()
+ {
+ var square = Square("square", 10, 5);
+ var nest = NestWith(square);
+ nest.Plates[0].Parts.Add(new Part(square));
+
+ var request = AutoNestJob.ForRemaining(nest, nest.Plates[0], "Default");
+
+ var item = Assert.Single(request.Items);
+ Assert.Same(square, item.Drawing);
+ Assert.Equal(4, item.Quantity);
+ Assert.Equal("Default", request.EngineName);
+ Assert.Equal(nest.Plates[0].Size, Assert.Single(request.Stock).Size);
+ }
+
+ [Fact]
+ public void ForRemaining_SkipsCutOffsAndPlacedDrawings_AndIsNullWhenNothingRemains()
+ {
+ var placed = Square("placed", 10, 1);
+ var cutOff = Square("cut-off", 1, 3);
+ cutOff.IsCutOff = true;
+ var nest = NestWith(placed, cutOff);
+ nest.Plates[0].Parts.Add(new Part(placed));
+
+ Assert.Null(AutoNestJob.ForRemaining(nest, nest.Plates[0], "Default"));
+ }
+
+ [Fact]
+ public void ValidResult_IsCommittedOntoTheEmptyPlate()
+ {
+ var square = Square("square", 10, 6);
+ var nest = NestWith(square);
+ var request = AutoNestJob.ForRemaining(nest, nest.Plates[0], "Default");
+
+ var result = NestPipeline.Run(request);
+ var kept = AutoNestJob.Keep(nest, result);
+
+ Assert.True(kept.Nested, kept.Note);
+ Assert.Null(AutoNestJob.RefusalReason(result));
+ var plate = Assert.Single(nest.Plates);
+ Assert.Equal(6, plate.Parts.Count);
+ Assert.Equal(0, square.Quantity.Remaining);
+ }
+
+ [Fact]
+ public void ResultPlacingNothing_LeavesTheNestUnchanged()
+ {
+ var tooBig = Square("too big", 200, 1);
+ var nest = NestWith(tooBig);
+ var request = AutoNestJob.ForRemaining(nest, nest.Plates[0], "Default");
+
+ var result = NestPipeline.Run(request);
+ var kept = AutoNestJob.Keep(nest, result);
+
+ Assert.False(kept.Nested);
+ var reason = AutoNestJob.RefusalReason(result);
+ Assert.StartsWith("Auto Nest placed nothing; nothing was changed. Not placed: ", reason);
+ Assert.Contains("too big", reason);
+ Assert.Empty(Assert.Single(nest.Plates).Parts);
+ Assert.Equal(1, tooBig.Quantity.Remaining);
+ }
+}
diff --git a/OpenNest.Desktop/AutoNestJob.cs b/OpenNest.Desktop/AutoNestJob.cs
new file mode 100644
index 00000000..b3a640e4
--- /dev/null
+++ b/OpenNest.Desktop/AutoNestJob.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using OpenNest.Engine;
+using OpenNest.Engine.Jobs;
+
+namespace OpenNest.Desktop
+{
+ ///
+ /// Auto Nest with the desktop dialog's defaults and no dialog: every drawing's remaining
+ /// quantity, with its priority and rotation constraints, on sheets like a template plate,
+ /// through . Only a valid, keepable result that places something
+ /// is committed, onto the nest's empty plates first and then new plates from its defaults.
+ ///
+ public static class AutoNestJob
+ {
+ ///
+ /// The job for 's remaining quantities on stock like
+ /// ; null when nothing remains. The nest is not changed, so
+ /// the caller can solve it off its thread.
+ ///
+ public static NestPipelineRequest ForRemaining(Nest nest, Plate template, string engineName)
+ {
+ ArgumentNullException.ThrowIfNull(nest);
+ ArgumentNullException.ThrowIfNull(template);
+ var items = nest.Drawings
+ .Where(d => !d.IsCutOff && d.Quantity.Remaining > 0)
+ .Select(d => new NestItem
+ {
+ Drawing = d,
+ Quantity = d.Quantity.Remaining,
+ Priority = d.Priority,
+ RotationStart = d.Constraints.StartAngle,
+ RotationEnd = d.Constraints.EndAngle,
+ StepAngle = d.Constraints.StepAngle,
+ })
+ .ToList();
+ if (items.Count == 0)
+ return null;
+
+ return new NestPipelineRequest(engineName, items,
+ NestStockBuilder.FromTemplate(template, null),
+ new NestJobOptions(maxPlates: NestRenest.MaxPlates, salvageRate: 0,
+ minimumSalvageDimension: nest.MinimumRemnantDimension));
+ }
+
+ ///
+ /// Commits to on the nest's own thread
+ /// when it is valid, keepable and places something; otherwise leaves the nest unchanged
+ /// and says why in the note.
+ ///
+ public static NestRenestResult Keep(Nest nest, NestPipelineResult result)
+ {
+ ArgumentNullException.ThrowIfNull(nest);
+ return NestRenest.Keep(result,
+ valid => NestPipelineCommit.ApplyToPlates(valid, () => EmptyOrNewPlate(nest)));
+ }
+
+ private static Plate EmptyOrNewPlate(Nest nest) =>
+ nest.Plates.FirstOrDefault(p => p.Parts.Count == 0) ?? nest.CreatePlate();
+
+ ///
+ /// Why would commit nothing for , naming the
+ /// first problem; null when it would commit.
+ ///
+ public static string RefusalReason(NestPipelineResult result)
+ {
+ ArgumentNullException.ThrowIfNull(result);
+ if (!result.IsValid)
+ return $"Auto Nest found {result.Violations.Count} layout problem"
+ + $"{(result.Violations.Count == 1 ? "" : "s")} (first: {result.Violations[0]}); nothing was changed.";
+ if (!result.CanKeep)
+ return "Auto Nest's layout cannot be kept faithfully; nothing was changed.";
+ if (result.Plates.All(p => p.Parts.Count == 0))
+ return "Auto Nest placed nothing; nothing was changed."
+ + (result.Unplaced.Count > 0
+ ? " Not placed: " + string.Join("; ", result.Unplaced.Select(u => u.Describe())) + "."
+ : "");
+ return null;
+ }
+ }
+}