From 7a1a55480421597a9f8b9713b4ddcdfeeca599a4 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Sat, 17 Oct 2026 22:09:07 -0400 Subject: [PATCH] fix(ui): center parts within the plate work area Center Parts centred the parts on the whole plate, ignoring edge spacing, so a wider clamp-side margin left the parts closer to the clamps than the opposite edge. It now centres them in Plate.WorkArea() via the new Plate.OffsetToCenterPartsInWorkArea, so each side keeps the same extra room beyond its edge spacing. Co-Authored-By: Claude Opus 5.5 --- OpenNest.Core/Plate.cs | 8 ++++ OpenNest.Tests/PlateCenterPartsTests.cs | 50 +++++++++++++++++++++++++ OpenNest/Forms/MainForm.Tools.cs | 5 +-- 3 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 OpenNest.Tests/PlateCenterPartsTests.cs diff --git a/OpenNest.Core/Plate.cs b/OpenNest.Core/Plate.cs index 674a1159..2c1aa62c 100644 --- a/OpenNest.Core/Plate.cs +++ b/OpenNest.Core/Plate.cs @@ -710,6 +710,14 @@ namespace OpenNest return box; } + /// + /// The offset that centers the parts' combined bounds in the , + /// so uneven edge spacing (such as a wider clamp-side margin) shifts them with it. + /// Zero when the plate has no parts. + /// + public Vector OffsetToCenterPartsInWorkArea() => + Parts.Count == 0 ? new Vector() : WorkArea().Center - Parts.GetBoundingBox().Center; + /// /// Automatically sizes the plate to fit the parts. /// diff --git a/OpenNest.Tests/PlateCenterPartsTests.cs b/OpenNest.Tests/PlateCenterPartsTests.cs new file mode 100644 index 00000000..51d76ad4 --- /dev/null +++ b/OpenNest.Tests/PlateCenterPartsTests.cs @@ -0,0 +1,50 @@ +using OpenNest.CNC; +using OpenNest.Geometry; + +namespace OpenNest.Tests; + +public class PlateCenterPartsTests +{ + private static Part MakeSquare(Vector location) + { + var pgm = new Program(); + pgm.Codes.Add(new RapidMove(new Vector(0, 0))); + pgm.Codes.Add(new LinearMove(new Vector(10, 0))); + pgm.Codes.Add(new LinearMove(new Vector(10, 10))); + pgm.Codes.Add(new LinearMove(new Vector(0, 10))); + pgm.Codes.Add(new LinearMove(new Vector(0, 0))); + return new Part(new Drawing("square", pgm), location); + } + + [Fact] + public void CentersPartsInWorkAreaNotPlate() + { + var plate = new Plate(100, 100) + { + // Wider clamp-side margin at the bottom. + EdgeSpacing = new Spacing(left: 0.25, bottom: 0.75, right: 0.25, top: 0.25), + }; + var part = MakeSquare(new Vector(5, 5)); + plate.Parts.Add(part); + + part.Offset(plate.OffsetToCenterPartsInWorkArea()); + + var box = part.BoundingBox; + // Work area is X 0.25..99.75, Y 0.75..99.75, so the centre sits 0.25 above the plate's. + Assert.Equal(50, box.Center.X, 6); + Assert.Equal(50.25, box.Center.Y, 6); + Assert.Equal(box.Left - 0.25, 99.75 - box.Right, 6); + Assert.Equal(box.Bottom - 0.75, 99.75 - box.Top, 6); + } + + [Fact] + public void EmptyPlateNeedsNoOffset() + { + var plate = new Plate(100, 100) { EdgeSpacing = new Spacing(0.25, 0.75, 0.25, 0.25) }; + + var offset = plate.OffsetToCenterPartsInWorkArea(); + + Assert.Equal(0, offset.X); + Assert.Equal(0, offset.Y); + } +} diff --git a/OpenNest/Forms/MainForm.Tools.cs b/OpenNest/Forms/MainForm.Tools.cs index 30837a40..2a846af6 100644 --- a/OpenNest/Forms/MainForm.Tools.cs +++ b/OpenNest/Forms/MainForm.Tools.cs @@ -229,10 +229,7 @@ namespace OpenNest.Forms if (view?.Plate == null || view.Parts.Count == 0) return; - var plateCenter = view.Plate.BoundingBox(false).Center; - var partsCenter = view.Parts.GetBoundingBox().Center; - - var offset = plateCenter - partsCenter; + var offset = view.Plate.OffsetToCenterPartsInWorkArea(); foreach (var part in view.Parts) part.Offset(offset);