From 301831e0965dd8fc60b6126caf02c43ddec195b2 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Fri, 3 Apr 2026 22:26:09 -0400 Subject: [PATCH] fix: correct Width/Length axis swap in best-fit slide offsets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BuildOffsets had Width and Length swapped after the Box axis correction in c5943e2. Horizontal pushes used Length (X) for perpendicular sweep and Width (Y) for push start — backwards. This caused part2 to start inside part1's footprint, producing overlapping best-fit pairs. Added regression test that verifies no kept best-fit pairs overlap. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../BestFit/RotationSlideStrategy.cs | 14 ++-- OpenNest.Tests/BestFit/BestFitOverlapTests.cs | 70 +++++++++++++++++++ 2 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 OpenNest.Tests/BestFit/BestFitOverlapTests.cs diff --git a/OpenNest.Engine/BestFit/RotationSlideStrategy.cs b/OpenNest.Engine/BestFit/RotationSlideStrategy.cs index a5706a7..7d76e16 100644 --- a/OpenNest.Engine/BestFit/RotationSlideStrategy.cs +++ b/OpenNest.Engine/BestFit/RotationSlideStrategy.cs @@ -89,16 +89,18 @@ namespace OpenNest.Engine.BestFit if (isHorizontalPush) { - perpMin = -(bbox2.Length + spacing); - perpMax = bbox1.Length + bbox2.Length + spacing; - pushStartOffset = bbox1.Width + bbox2.Width + spacing * 2; - } - else - { + // Perpendicular sweep along Y → Width; push extent along X → Length perpMin = -(bbox2.Width + spacing); perpMax = bbox1.Width + bbox2.Width + spacing; pushStartOffset = bbox1.Length + bbox2.Length + spacing * 2; } + else + { + // Perpendicular sweep along X → Length; push extent along Y → Width + perpMin = -(bbox2.Length + spacing); + perpMax = bbox1.Length + bbox2.Length + spacing; + pushStartOffset = bbox1.Width + bbox2.Width + spacing * 2; + } var alignedStart = System.Math.Ceiling(perpMin / stepSize) * stepSize; diff --git a/OpenNest.Tests/BestFit/BestFitOverlapTests.cs b/OpenNest.Tests/BestFit/BestFitOverlapTests.cs new file mode 100644 index 0000000..3e5a983 --- /dev/null +++ b/OpenNest.Tests/BestFit/BestFitOverlapTests.cs @@ -0,0 +1,70 @@ +using OpenNest.CNC; +using OpenNest.Converters; +using OpenNest.Engine.BestFit; +using OpenNest.Geometry; +using OpenNest.IO; +using Xunit.Abstractions; + +namespace OpenNest.Tests.BestFit; + +public class BestFitOverlapTests +{ + private const string DxfPath = @"C:\Users\AJ\Desktop\Templates\4526 A14 PT16.dxf"; + private readonly ITestOutputHelper _output; + + public BestFitOverlapTests(ITestOutputHelper output) => _output = output; + + private static Drawing MakeRoundedRect(double w = 7.25, double h = 3.31, double r = 0.5) + { + var pgm = new Program(); + pgm.Codes.Add(new RapidMove(new Vector(0, 0))); + pgm.Codes.Add(new LinearMove(new Vector(w - r, 0))); + pgm.Codes.Add(new ArcMove(new Vector(w, r), new Vector(w - r, r), RotationType.CW)); + pgm.Codes.Add(new LinearMove(new Vector(w, h - r))); + pgm.Codes.Add(new ArcMove(new Vector(w - r, h), new Vector(w - r, h - r), RotationType.CW)); + pgm.Codes.Add(new LinearMove(new Vector(0, h))); + pgm.Codes.Add(new LinearMove(new Vector(0, 0))); + return new Drawing("rounded-rect", pgm); + } + + private static Drawing ImportDxf() + { + if (!File.Exists(DxfPath)) + return null; + + var importer = new DxfImporter(); + importer.GetGeometry(DxfPath, out var geometry); + var pgm = ConvertGeometry.ToProgram(geometry); + return new Drawing("PT16", pgm); + } + + [Fact] + public void KeptPairs_NoOverlap() + { + var drawing = ImportDxf() ?? MakeRoundedRect(); + var bbox = drawing.Program.BoundingBox(); + _output.WriteLine($"Drawing: {drawing.Name}, bbox={bbox.Length:F2} x {bbox.Width:F2}"); + + var finder = new BestFitFinder(120, 60); + var results = finder.FindBestFits(drawing); + + var kept = results.Where(r => r.Keep).ToList(); + _output.WriteLine($"Total results: {results.Count}, Kept: {kept.Count}"); + + var overlapping = 0; + + foreach (var result in kept) + { + var parts = result.BuildParts(drawing); + if (parts[0].Intersects(parts[1], out var pts)) + { + overlapping++; + _output.WriteLine($" OVERLAP #{overlapping}: Test {result.Candidate.TestNumber} " + + $"Part2Rot={OpenNest.Math.Angle.ToDegrees(result.Candidate.Part2Rotation):F1}° " + + $"collision pts={pts.Count}"); + } + } + + Assert.Equal(0, overlapping); + } +}