diff --git a/OpenNest.Engine.Opus55/Opus55NestingEngine.cs b/OpenNest.Engine.Opus55/Opus55NestingEngine.cs index e0cfd4e..9e86d0b 100644 --- a/OpenNest.Engine.Opus55/Opus55NestingEngine.cs +++ b/OpenNest.Engine.Opus55/Opus55NestingEngine.cs @@ -20,11 +20,15 @@ namespace OpenNest.Engine.Opus55; public sealed class Opus55NestingEngine : INestingEngine { /// - /// Extra clearance beyond the stock's part spacing, in job units. Validators polygonize arcs - /// circumscribed at 0.01 per side, so two tangent true arcs can read as up to 0.02 closer - /// than they are; the rest absorbs Clipper's 1e-4 grid and inner-fit clamping. + /// Extra clearance beyond the stock's part spacing, in job units. Footprints already contain + /// the true outline grown by half the clearance (chord tolerance is added separately), so this + /// only has to cover the validator's side: NestValidator flattens each perimeter within + /// 0.001 of the true arc (OutlineTolerance), so two true arcs can read up to 0.002 closer + /// than they are. The remaining 0.001 absorbs the 1e-4 Clipper grid on both sides (footprint + /// offset, Minkowski union, free-region difference, the validator's own offset); the + /// validator's round-join chord error (0.0001) only ever makes it more lenient. /// - internal const double ClearanceMargin = 0.022; + internal const double ClearanceMargin = 0.003; /// Strategy variants, tried in order: (front direction, area exponent beta). private static readonly (PackAxis Axis, double Beta)[] Variants = diff --git a/OpenNest.Engine.Opus55/PartCatalog.cs b/OpenNest.Engine.Opus55/PartCatalog.cs index 6402a17..87f53b7 100644 --- a/OpenNest.Engine.Opus55/PartCatalog.cs +++ b/OpenNest.Engine.Opus55/PartCatalog.cs @@ -111,7 +111,7 @@ internal static class PartCatalog { var entities = ConvertProgram .ToGeometry(DrawingJobMapper.ToProgram(geometry)) - .Where(e => !ReferenceEquals(e.Layer, SpecialLayers.Rapid)) + .Where(e => SpecialLayers.IsMaterial(e.Layer)) .ToList(); if (entities.Count == 0) return null; diff --git a/OpenNest.Engine.Opus55/README.md b/OpenNest.Engine.Opus55/README.md index c63ba4a..759fa94 100644 --- a/OpenNest.Engine.Opus55/README.md +++ b/OpenNest.Engine.Opus55/README.md @@ -15,9 +15,9 @@ Every placement decision (which part, which rotation, where, on which sheet) com angles plus the two orientations that axis-align the minimum-area bounding rectangle (`RotatingCalipers`); for sweeps, up to 8 evenly spaced legal steps. Point-symmetric duplicates are dropped. - Each orientation gets a **footprint**: outline inflated (miter joins, so it contains the exact - round offset) by `(spacing + 0.022) / 2 + chordTolerance`. Two parts respect the spacing - when their footprints don't overlap. The 0.022 covers validators that polygonize arcs - circumscribed at 0.01 per side, plus Clipper's 1e-4 grid. + round offset) by `(spacing + 0.003) / 2 + chordTolerance`. Two parts respect the spacing + when their footprints don't overlap. The 0.003 covers `NestValidator` flattening each arc + within 0.001 of true (0.002 for a pair), plus Clipper's 1e-4 grid on both sides. - **No-fit polygons** between footprints come from Clipper2 Minkowski sums: an O(n+m) edge merge for convex pairs, and for concave pairs the boundary sweep ∪ (A + p₀) ∪ (−B + a₀). The last two terms cover "B inside A" and "B swallows A". NFPs are cached per orientation pair. @@ -83,9 +83,10 @@ The engine reports as `Opus55NestingEngine`. ## Known limitations - **No part-in-part:** holes are treated as solid, so small parts never nest inside cutouts. -- **Clearance padding:** gaps are ~0.022 (plus up to the chord tolerance) wider than the - required spacing, to stay valid under circumscribed-polygon validators. That's negligible in mm - and about 0.02" in inches. The constants are absolute and assume job units near inch/mm scale. +- **Clearance padding:** gaps are ~0.003 (plus up to the chord tolerance) wider than the + required spacing, to stay valid under `NestValidator`'s 0.001 arc flattening. The margin was + 0.022 while it assumed a 0.01 validator tolerance. The constants are absolute and assume job + units near inch/mm scale. - **Rotation coverage:** `Automatic` parts try at most 8 orientations (fewer when a job has many distinct parts: `48 / partCount`, minimum 2). Free-angle rotations aren't explored beyond the MBR alignment. - **Greedy core:** there is no order/permutation search. The variants and tail re-plan are the only diff --git a/OpenNest.Engine.Opus55/tests/Opus55NestingEngineTests.cs b/OpenNest.Engine.Opus55/tests/Opus55NestingEngineTests.cs index 4beb32b..9cb4557 100644 --- a/OpenNest.Engine.Opus55/tests/Opus55NestingEngineTests.cs +++ b/OpenNest.Engine.Opus55/tests/Opus55NestingEngineTests.cs @@ -191,6 +191,24 @@ public class Opus55NestingEngineTests Assert.Equal(Describe(first), Describe(second)); } + [Fact] + public void EtchMarksAreLeftOutOfNestingGeometry() + { + // A bend tick starts on material and ends 1.0 into a side notch, outside the part but + // inside its bounding box (the PEP case that crashed nesting before 1b5e1b1). As + // material it is open geometry leaving the part; as a mark it must be ignored. + var etched = Polyline((0, 0), (10, 0), (10, 4), (8, 4), (8, 6), (10, 6), (10, 10), (0, 10)); + etched.Codes.Add(new RapidMove(7.5, 5)); + etched.Codes.Add(new LinearMove(9, 5) { Layer = LayerType.Scribe }); + var job = Job(new[] { Part("part", etched, 2, RotationPolicy.Fixed(0)) }, new[] { Stock("sheet", 10.4, 20.6, spacing: 0.2) }); + + var result = new Opus55NestingEngine().Solve(job); + + AssertValid(job, result); + Assert.Equal(NestJobStatus.Complete, result.Status); + Assert.Equal(2, Assert.Single(result.Plates).Placements.Count); + } + [Fact] public void HasPublicParameterlessConstructorForPluginDiscovery() {