diff --git a/OpenNest.Console/Program.cs b/OpenNest.Console/Program.cs index c2d3d0d..2997e1e 100644 --- a/OpenNest.Console/Program.cs +++ b/OpenNest.Console/Program.cs @@ -96,12 +96,8 @@ static class NestConsole o.Spacing = double.Parse(args[++i]); break; case "--size" when i + 1 < args.Length: - var parts = args[++i].Split('x'); - if (parts.Length == 2) - { - o.PlateHeight = double.Parse(parts[0]); - o.PlateWidth = double.Parse(parts[1]); - } + if (Size.TryParse(args[++i], out var sz)) + o.PlateSize = sz; break; case "--check-overlaps": o.CheckOverlaps = true; @@ -198,14 +194,14 @@ static class NestConsole return null; } - if (!options.PlateWidth.HasValue || !options.PlateHeight.HasValue) + if (!options.PlateSize.HasValue) { - Console.Error.WriteLine("Error: --size WxH is required when importing DXF files without a nest"); + Console.Error.WriteLine("Error: --size WxL is required when importing DXF files without a nest"); return null; } var newNest = new Nest { Name = "DXF Import" }; - var plate = new Plate { Size = new Size(options.PlateWidth.Value, options.PlateHeight.Value) }; + var plate = new Plate { Size = options.PlateSize.Value }; newNest.Plates.Add(plate); foreach (var dxf in dxfFiles) @@ -278,8 +274,8 @@ static class NestConsole // Only apply size override when it wasn't already used to create the plate. var hasDxfOnly = !options.InputFiles.Any(f => f.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)); - if (options.PlateWidth.HasValue && options.PlateHeight.HasValue && !hasDxfOnly) - plate.Size = new Size(options.PlateWidth.Value, options.PlateHeight.Value); + if (options.PlateSize.HasValue && !hasDxfOnly) + plate.Size = options.PlateSize.Value; } static Drawing ResolveDrawing(Nest nest, Options options) @@ -302,7 +298,7 @@ static class NestConsole { Console.WriteLine($"Nest: {nest.Name}"); var wa = plate.WorkArea(); - Console.WriteLine($"Plate: {options.PlateIndex} ({plate.Size.Length:F1} x {plate.Size.Width:F1}), spacing={plate.PartSpacing:F2}, edge=({plate.EdgeSpacing.Left},{plate.EdgeSpacing.Bottom},{plate.EdgeSpacing.Right},{plate.EdgeSpacing.Top}), workArea={wa.Length:F1}x{wa.Width:F1}"); + Console.WriteLine($"Plate: {options.PlateIndex} ({plate.Size.Width:F1} x {plate.Size.Length:F1}), spacing={plate.PartSpacing:F2}, edge=({plate.EdgeSpacing.Left},{plate.EdgeSpacing.Bottom},{plate.EdgeSpacing.Right},{plate.EdgeSpacing.Top}), workArea={wa.Width:F1}x{wa.Length:F1}"); Console.WriteLine($"Drawing: {drawing.Name}"); Console.WriteLine(options.KeepParts ? $"Keeping {existingCount} existing parts" @@ -392,7 +388,7 @@ static class NestConsole Console.Error.WriteLine(); Console.Error.WriteLine("Modes:"); Console.Error.WriteLine(" Load nest and fill (existing behavior)"); - Console.Error.WriteLine(" --size LxW Import DXF, create plate, and fill"); + Console.Error.WriteLine(" --size WxL Import DXF, create plate, and fill"); Console.Error.WriteLine(" Load nest and add imported DXF drawings"); Console.Error.WriteLine(); Console.Error.WriteLine("Options:"); @@ -400,7 +396,7 @@ static class NestConsole Console.Error.WriteLine(" --plate Plate index to fill (default: 0)"); Console.Error.WriteLine(" --quantity Max parts to place (default: 0 = unlimited)"); Console.Error.WriteLine(" --spacing Override part spacing"); - Console.Error.WriteLine(" --size Override plate size (e.g. 120x60); required for DXF-only mode"); + Console.Error.WriteLine(" --size Override plate size (e.g. 60x120); required for DXF-only mode"); Console.Error.WriteLine(" --output Output nest file path (default: -result.zip)"); Console.Error.WriteLine(" --template Nest template for plate defaults (thickness, quadrant, material, spacing)"); Console.Error.WriteLine(" --autonest Use NFP-based mixed-part autonesting instead of linear fill"); @@ -419,8 +415,7 @@ static class NestConsole public string OutputFile; public int Quantity; public double? Spacing; - public double? PlateWidth; - public double? PlateHeight; + public Size? PlateSize; public bool CheckOverlaps; public bool NoSave; public bool NoLog; diff --git a/OpenNest.Core/Geometry/Size.cs b/OpenNest.Core/Geometry/Size.cs index ae77887..e5aead9 100644 --- a/OpenNest.Core/Geometry/Size.cs +++ b/OpenNest.Core/Geometry/Size.cs @@ -6,14 +6,14 @@ namespace OpenNest.Geometry { public Size(double width, double length) { - Length = length; Width = width; + Length = length; } - public double Length; - public double Width; + public double Length; + public static Size Parse(string size) { var a = size.ToUpper().Split('X'); @@ -21,8 +21,8 @@ namespace OpenNest.Geometry if (a.Length > 2) throw new FormatException("Invalid size format."); - var length = double.Parse(a[0]); - var width = double.Parse(a[1]); + var width = double.Parse(a[0]); + var length = double.Parse(a[1]); return new Size(width, length); } @@ -42,14 +42,8 @@ namespace OpenNest.Geometry return true; } - public override string ToString() - { - return string.Format("{0} x {1}", Length, Width); - } - - public string ToString(int decimalPlaces) - { - return string.Format("{0} x {1}", System.Math.Round(Length, decimalPlaces), System.Math.Round(Width, decimalPlaces)); - } + public override string ToString() => $"{Width} x {Length}"; + + public string ToString(int decimalPlaces) => $"{System.Math.Round(Width, decimalPlaces)} x {System.Math.Round(Length, decimalPlaces)}"; } } diff --git a/OpenNest.Core/Plate.cs b/OpenNest.Core/Plate.cs index affa152..c3c4ba0 100644 --- a/OpenNest.Core/Plate.cs +++ b/OpenNest.Core/Plate.cs @@ -35,7 +35,7 @@ namespace OpenNest } public Plate(double width, double length) - : this(new Size(length, width)) + : this(new Size(width, length)) { } diff --git a/OpenNest/Controls/DrawControl.cs b/OpenNest/Controls/DrawControl.cs index 3d9f2fc..dbaf2ca 100644 --- a/OpenNest/Controls/DrawControl.cs +++ b/OpenNest/Controls/DrawControl.cs @@ -205,7 +205,7 @@ namespace OpenNest.Controls public virtual void ZoomToArea(Box box, bool redraw = true) { - ZoomToArea(box.X, box.Y, box.Width, box.Length, redraw); + ZoomToArea(box.X, box.Y, box.Length, box.Width, redraw); } public virtual void ZoomToArea(double x, double y, double width, double height, bool redraw = true) diff --git a/OpenNest/Controls/PlateView.cs b/OpenNest/Controls/PlateView.cs index f093930..630a950 100644 --- a/OpenNest/Controls/PlateView.cs +++ b/OpenNest/Controls/PlateView.cs @@ -415,14 +415,14 @@ namespace OpenNest.Controls { var plateRect = new RectangleF { - Width = LengthWorldToGui(Plate.Size.Width), - Height = LengthWorldToGui(Plate.Size.Length) + Width = LengthWorldToGui(Plate.Size.Length), + Height = LengthWorldToGui(Plate.Size.Width) }; var edgeSpacingRect = new RectangleF { - Width = LengthWorldToGui(Plate.Size.Width - Plate.EdgeSpacing.Left - Plate.EdgeSpacing.Right), - Height = LengthWorldToGui(Plate.Size.Length - Plate.EdgeSpacing.Top - Plate.EdgeSpacing.Bottom) + Width = LengthWorldToGui(Plate.Size.Length - Plate.EdgeSpacing.Left - Plate.EdgeSpacing.Right), + Height = LengthWorldToGui(Plate.Size.Width - Plate.EdgeSpacing.Top - Plate.EdgeSpacing.Bottom) }; switch (Plate.Quadrant) diff --git a/README.md b/README.md index a347e21..71ea8cc 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,51 @@ Or open `OpenNest.sln` in Visual Studio and run the `OpenNest` project. +## Command-Line Interface + +OpenNest includes a CLI for batch nesting without the GUI — useful for automation, scripting, and CI pipelines. + +```bash +dotnet run --project OpenNest.Console/OpenNest.Console.csproj -- [options] +``` + +**Import DXF files and nest onto a plate:** + +```bash +# Import a DXF and fill a 60x120 plate +dotnet run --project OpenNest.Console/OpenNest.Console.csproj -- part.dxf --size 60x120 + +# Import multiple DXFs with NFP-based auto-nesting +dotnet run --project OpenNest.Console/OpenNest.Console.csproj -- part1.dxf part2.dxf --size 60x120 --autonest +``` + +**Work with existing nest files:** + +```bash +# Re-fill an existing nest file +dotnet run --project OpenNest.Console/OpenNest.Console.csproj -- project.zip + +# Add a new DXF to an existing nest and auto-nest +dotnet run --project OpenNest.Console/OpenNest.Console.csproj -- project.zip extra-part.dxf --autonest +``` + +**Options:** + +| Option | Description | +|--------|-------------| +| `--size ` | Plate size (e.g. `60x120`). Required for DXF-only mode. | +| `--autonest` | Use NFP-based mixed-part nesting instead of linear fill | +| `--drawing ` | Select which drawing to fill with (default: first) | +| `--quantity ` | Max parts to place (default: unlimited) | +| `--spacing ` | Override part spacing | +| `--template ` | Load plate defaults (thickness, quadrant, material, spacing) from a nest file | +| `--output ` | Output file path (default: `-result.zip`) | +| `--keep-parts` | Keep existing parts instead of clearing before fill | +| `--check-overlaps` | Run overlap detection after fill (exits with code 1 if found) | +| `--engine ` | Select a registered nesting engine | +| `--no-save` | Skip saving the output file | +| `--no-log` | Skip writing the debug log | + ## Project Structure ``` @@ -68,11 +113,12 @@ OpenNest.sln └── OpenNest.Tests/ # Unit tests ``` -For most users, only the first four matter: +For most users, only these matter: | Project | What it does | |---------|-------------| | **OpenNest** | The app you run. WinForms UI with plate viewer, drawing list, and dialogs. | +| **OpenNest.Console** | Command-line interface for batch nesting, scripting, and automation. | | **OpenNest.Core** | The building blocks — parts, plates, drawings, geometry, G-code representation. | | **OpenNest.Engine** | The brains — algorithms that decide where parts go on a plate. | | **OpenNest.IO** | Reads and writes files — DXF (via ACadSharp), G-code, and the `.nest` ZIP format. | diff --git a/screenshots/screenshot-nest-2.png b/screenshots/screenshot-nest-2.png index c364c09..72b7b90 100644 Binary files a/screenshots/screenshot-nest-2.png and b/screenshots/screenshot-nest-2.png differ