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 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-10-16 15:33:22 -04:00
1 parent 734346eeec
commit d9b8f054cc
4 files changed
+38 -4

No files matched your search

+2
View File
@@ -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.
/// </summary>
private void CopyLeadInStateFrom(Part source)
{
HasManualLeadIns = source.HasManualLeadIns;
LeadInsLocked = source.LeadInsLocked;
DrawingPinsOff = source.DrawingPinsOff;
CuttingParameters = source.CuttingParameters;
preLeadInRotation = source.preLeadInRotation;
}
+7 -3
View File
@@ -74,9 +74,9 @@ public sealed class LibraryDrawingInsert
public int Quantity { get; }
/// <summary>
/// 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.
/// </summary>
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, "");
}
@@ -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);
}
}
@@ -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]