From d9b8f054cc7b1559055920cff1393a8f32d0a244 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Fri, 16 Oct 2026 13:21:33 -0400 Subject: [PATCH] feat(core): mismatch drop and part copy Two remaining pin rules from the drawing library plan: - D3: a library drawing added to a nest whose material, grade or thickness differ from the library record now really arrives without its cut pins. The insert already warned and reported KeepPins = false; it now clears the copy's pins, because they were authored for the library material and thickness. The library link still records the revision the copy came from. - A copy of a placement (Clone, CloneAtOffset) keeps its drawing pins off, so copying or patterning an overridden part does not silently bring the drawing's pins back. Clearing lead-ins keeps it too: pins-off is a placement choice, not lead-in state. Co-Authored-By: Claude Opus 5.5 --- OpenNest.Core/Part.cs | 2 ++ OpenNest.Data/LibraryDrawingInsert.cs | 10 +++++++--- .../PartLeadInTransformTests.cs | 20 +++++++++++++++++++ .../Data/LibraryDrawingInsertTests.cs | 10 +++++++++- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/OpenNest.Core/Part.cs b/OpenNest.Core/Part.cs index 73e466bc..adf76d3a 100644 --- a/OpenNest.Core/Part.cs +++ b/OpenNest.Core/Part.cs @@ -432,11 +432,13 @@ namespace OpenNest /// Copies the lead-in state that goes with a copied program. Without it a copy of a /// lead-in part reads its rotation from the rebuilt program (zero), and lead-in /// assignment does not know to strip the copied lead-ins before adding new ones. + /// A copy of a placement with drawing pins off keeps them off. /// private void CopyLeadInStateFrom(Part source) { HasManualLeadIns = source.HasManualLeadIns; LeadInsLocked = source.LeadInsLocked; + DrawingPinsOff = source.DrawingPinsOff; CuttingParameters = source.CuttingParameters; preLeadInRotation = source.preLeadInRotation; } diff --git a/OpenNest.Data/LibraryDrawingInsert.cs b/OpenNest.Data/LibraryDrawingInsert.cs index 46a97ec5..808c56ef 100644 --- a/OpenNest.Data/LibraryDrawingInsert.cs +++ b/OpenNest.Data/LibraryDrawingInsert.cs @@ -74,9 +74,9 @@ public sealed class LibraryDrawingInsert public int Quantity { get; } /// - /// For a new copy, true when the library drawing's cut pins may be used in this nest: - /// the library material, grade and thickness match the nest's. An added quantity - /// leaves the existing copy as it is. + /// For a new copy, true when the library drawing's cut pins come with it: the library + /// material, grade and thickness match the nest's. Otherwise the copy has no pins. An + /// added quantity leaves the existing copy as it is. /// public bool KeepPins { get; } @@ -142,6 +142,10 @@ public sealed class LibraryDrawingInsert drawing.Material = new Material(record.MaterialName, record.MaterialGrade, drawing.Material?.Density ?? 0); drawing.Quantity.Required = quantity; drawing.Library = link; + // Pins were authored for the library material and thickness; elsewhere the nest's + // cutting settings apply (D3). The link still records the revision the copy came from. + if (differences.Count > 0) + drawing.CutPins.Clear(); return new LibraryDrawingInsert(nest, LibraryInsertKind.AddDrawing, drawing, quantity, keepPins: differences.Count == 0, warnings, ""); } diff --git a/OpenNest.Tests/CuttingStrategy/PartLeadInTransformTests.cs b/OpenNest.Tests/CuttingStrategy/PartLeadInTransformTests.cs index aec5be34..2a9b5b4d 100644 --- a/OpenNest.Tests/CuttingStrategy/PartLeadInTransformTests.cs +++ b/OpenNest.Tests/CuttingStrategy/PartLeadInTransformTests.cs @@ -182,4 +182,24 @@ public class PartLeadInTransformTests Assert.Equal(before.Width, after.Length, 6); Assert.Equal(before.Length, after.Width, 6); } + + [Fact] + public void Copies_KeepDrawingPinsOff() + { + var drawing = MakeSquareWithHole(); + var clean = MakeCleanPart(drawing, QuarterTurn, new Vector(2, 3)); + clean.DrawingPinsOff = true; + var withLeadIns = MakeLeadInPart(drawing, QuarterTurn); + withLeadIns.DrawingPinsOff = true; + + Assert.True(((Part)clean.Clone()).DrawingPinsOff); + Assert.True(clean.CloneAtOffset(new Vector(1, 1)).DrawingPinsOff); + Assert.True(((Part)withLeadIns.Clone()).DrawingPinsOff); + Assert.True(withLeadIns.CloneAtOffset(new Vector(1, 1)).DrawingPinsOff); + Assert.False(((Part)MakeCleanPart(drawing, 0, Vector.Zero).Clone()).DrawingPinsOff); + + // Pins-off is a placement choice, not lead-in state: clearing lead-ins keeps it. + withLeadIns.RemoveLeadIns(); + Assert.True(withLeadIns.DrawingPinsOff); + } } diff --git a/OpenNest.Tests/Data/LibraryDrawingInsertTests.cs b/OpenNest.Tests/Data/LibraryDrawingInsertTests.cs index ec14d9ca..d3dac08c 100644 --- a/OpenNest.Tests/Data/LibraryDrawingInsertTests.cs +++ b/OpenNest.Tests/Data/LibraryDrawingInsertTests.cs @@ -1,5 +1,7 @@ using System.Security.Cryptography; +using OpenNest.CNC.CuttingStrategy; using OpenNest.Data; +using OpenNest.Geometry; using OpenNest.Shapes; namespace OpenNest.Tests.Data; @@ -37,6 +39,8 @@ public class LibraryDrawingInsertTests var drawing = new RectangleShape { Name = "old package name", Length = 4, Width = 2 }.GetDrawing(); drawing.Material = new Material("Package material", "Package grade", 0.284); drawing.Customer = "Package customer"; + drawing.CutPins.Add(new ContourCutPin(new Vector(4, 2), new LineLeadIn { Length = 0.25 }, new NoLeadOut(), + new NormalTab { Size = 0.03 })); return new LibraryPackage(PackageBytes, drawing, units, warnings); } @@ -57,6 +61,7 @@ public class LibraryDrawingInsertTests var drawing = Assert.Single(nest.Drawings); Assert.Same(package.Drawing, drawing); + Assert.Equal(new Vector(4, 2), Assert.Single(drawing.CutPins).Point); Assert.Equal(("SYN-101", "Synthetic customer", 5), (drawing.Name, drawing.Customer, drawing.Quantity.Required)); Assert.Equal(("Synthetic Steel", "Grade A", 0.284), (drawing.Material.Name, drawing.Material.Grade, drawing.Material.Density)); var link = drawing.Library!; @@ -98,8 +103,11 @@ public class LibraryDrawingInsertTests Assert.Contains(expected, warning); Assert.Contains("'SYN-101'", warning); Assert.Contains("cut pins are dropped", warning); + Assert.Empty(insert.Drawing!.CutPins); insert.Apply(); - Assert.Single(nest.Drawings); + var drawing = Assert.Single(nest.Drawings); + Assert.Empty(drawing.CutPins); + Assert.NotNull(drawing.Library); } [Fact]