feat: add Plate.CutOffs collection with materialization and transform support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-22 19:36:05 -04:00
parent 5fc7d1989a
commit d58a446eac
2 changed files with 127 additions and 4 deletions

View File

@@ -47,6 +47,7 @@ namespace OpenNest
Parts = new ObservableList<Part>();
Parts.ItemAdded += Parts_PartAdded;
Parts.ItemRemoved += Parts_PartRemoved;
CutOffs = new ObservableList<CutOff>();
Quadrant = 1;
}
@@ -92,6 +93,38 @@ namespace OpenNest
/// </summary>
public ObservableList<Part> Parts { get; set; }
/// <summary>
/// The cut-off lines defined on this plate.
/// </summary>
public ObservableList<CutOff> CutOffs { get; set; }
/// <summary>
/// Regenerates all cut-off drawings and materializes them as parts.
/// Existing cut-off parts are removed first, then each cut-off is
/// regenerated and added back if it produces any geometry.
/// </summary>
public void RegenerateCutOffs(CutOffSettings settings)
{
// Remove existing cut-off parts
for (var i = Parts.Count - 1; i >= 0; i--)
{
if (Parts[i].BaseDrawing.IsCutOff)
Parts.RemoveAt(i);
}
// Regenerate and materialize each cut-off
foreach (var cutoff in CutOffs)
{
cutoff.Regenerate(this, settings);
if (cutoff.Drawing.Program.Codes.Count == 0)
continue;
var part = new Part(cutoff.Drawing);
Parts.Add(part);
}
}
/// <summary>
/// The number of times to cut the plate.
/// </summary>
@@ -242,11 +275,20 @@ namespace OpenNest
/// <param name="angle"></param>
public void Rotate(double angle)
{
for (int i = 0; i < Parts.Count; ++i)
for (var i = Parts.Count - 1; i >= 0; i--)
{
if (Parts[i].BaseDrawing.IsCutOff)
Parts.RemoveAt(i);
}
for (var i = 0; i < Parts.Count; ++i)
{
var part = Parts[i];
part.Rotate(angle);
}
foreach (var cutoff in CutOffs)
cutoff.Position = cutoff.Position.Rotate(angle);
}
/// <summary>
@@ -256,11 +298,24 @@ namespace OpenNest
/// <param name="origin"></param>
public void Rotate(double angle, Vector origin)
{
for (int i = 0; i < Parts.Count; ++i)
for (var i = Parts.Count - 1; i >= 0; i--)
{
if (Parts[i].BaseDrawing.IsCutOff)
Parts.RemoveAt(i);
}
for (var i = 0; i < Parts.Count; ++i)
{
var part = Parts[i];
part.Rotate(angle, origin);
}
foreach (var cutoff in CutOffs)
{
var pos = cutoff.Position - origin;
pos = pos.Rotate(angle);
cutoff.Position = pos + origin;
}
}
/// <summary>
@@ -270,11 +325,22 @@ namespace OpenNest
/// <param name="y"></param>
public void Offset(double x, double y)
{
for (int i = 0; i < Parts.Count; ++i)
// Remove cut-off parts before transforming
for (var i = Parts.Count - 1; i >= 0; i--)
{
if (Parts[i].BaseDrawing.IsCutOff)
Parts.RemoveAt(i);
}
for (var i = 0; i < Parts.Count; ++i)
{
var part = Parts[i];
part.Offset(x, y);
}
// Transform cut-off positions
foreach (var cutoff in CutOffs)
cutoff.Position = new Vector(cutoff.Position.X + x, cutoff.Position.Y + y);
}
/// <summary>
@@ -283,11 +349,20 @@ namespace OpenNest
/// <param name="voffset"></param>
public void Offset(Vector voffset)
{
for (int i = 0; i < Parts.Count; ++i)
for (var i = Parts.Count - 1; i >= 0; i--)
{
if (Parts[i].BaseDrawing.IsCutOff)
Parts.RemoveAt(i);
}
for (var i = 0; i < Parts.Count; ++i)
{
var part = Parts[i];
part.Offset(voffset);
}
foreach (var cutoff in CutOffs)
cutoff.Position = new Vector(cutoff.Position.X + voffset.X, cutoff.Position.Y + voffset.Y);
}
/// <summary>

View File

@@ -215,4 +215,52 @@ public class CutOffTests
Assert.True(hCut.Drawing.Program.Codes.Count > 0);
Assert.True(vCut.Drawing.Program.Codes.Count > 0);
}
[Fact]
public void Plate_RegenerateCutOffs_MaterializesParts()
{
var plate = new Plate(100, 50);
var cutoff = new CutOff(new Geometry.Vector(25, 10), CutOffAxis.Vertical);
plate.CutOffs.Add(cutoff);
plate.RegenerateCutOffs(new CutOffSettings());
Assert.Single(plate.Parts);
Assert.True(plate.Parts[0].BaseDrawing.IsCutOff);
}
[Fact]
public void Plate_RegenerateCutOffs_ReplacesOldParts()
{
var plate = new Plate(100, 50);
var cutoff = new CutOff(new Geometry.Vector(25, 10), CutOffAxis.Vertical);
plate.CutOffs.Add(cutoff);
var settings = new CutOffSettings();
plate.RegenerateCutOffs(settings);
plate.RegenerateCutOffs(settings);
Assert.Single(plate.Parts);
}
[Fact]
public void Plate_RegenerateCutOffs_DoesNotAffectRegularParts()
{
var pgm = new OpenNest.CNC.Program();
pgm.Codes.Add(new OpenNest.CNC.RapidMove(new Geometry.Vector(0, 0)));
pgm.Codes.Add(new OpenNest.CNC.LinearMove(new Geometry.Vector(5, 5)));
var drawing = new Drawing("real", pgm);
var plate = new Plate(100, 50);
plate.Parts.Add(new Part(drawing));
var cutoff = new CutOff(new Geometry.Vector(25, 10), CutOffAxis.Vertical);
plate.CutOffs.Add(cutoff);
plate.RegenerateCutOffs(new CutOffSettings());
Assert.Equal(2, plate.Parts.Count);
Assert.False(plate.Parts[0].BaseDrawing.IsCutOff);
Assert.True(plate.Parts[1].BaseDrawing.IsCutOff);
}
}