fix(core): copy-on-write for shared Program in tiled parts

CloneAtOffset shares the Program instance for tiling performance,
but rotating a part on the plate mutated the shared Program, causing
all parts from the same tile template to rotate together.

Added ownsProgram flag with EnsureOwnedProgram() that clones the
Program before first mutation, preserving tiling performance while
making user rotations independent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 21:10:20 -04:00
parent 683cb3c180
commit c552372f81

View File

@@ -20,6 +20,7 @@ namespace OpenNest
public class Part : IPart, IBoundable public class Part : IPart, IBoundable
{ {
private Vector location; private Vector location;
private bool ownsProgram;
public readonly Drawing BaseDrawing; public readonly Drawing BaseDrawing;
@@ -32,6 +33,7 @@ namespace OpenNest
{ {
BaseDrawing = baseDrawing; BaseDrawing = baseDrawing;
Program = baseDrawing.Program.Clone() as Program; Program = baseDrawing.Program.Clone() as Program;
ownsProgram = true;
this.location = location; this.location = location;
UpdateBounds(); UpdateBounds();
} }
@@ -67,6 +69,7 @@ namespace OpenNest
/// <param name="angle">Angle of rotation in radians.</param> /// <param name="angle">Angle of rotation in radians.</param>
public void Rotate(double angle) public void Rotate(double angle)
{ {
EnsureOwnedProgram();
Program.Rotate(angle); Program.Rotate(angle);
location = Location.Rotate(angle); location = Location.Rotate(angle);
UpdateBounds(); UpdateBounds();
@@ -79,6 +82,7 @@ namespace OpenNest
/// <param name="origin">The origin to rotate the part around.</param> /// <param name="origin">The origin to rotate the part around.</param>
public void Rotate(double angle, Vector origin) public void Rotate(double angle, Vector origin)
{ {
EnsureOwnedProgram();
Program.Rotate(angle); Program.Rotate(angle);
location = Location.Rotate(angle, origin); location = Location.Rotate(angle, origin);
UpdateBounds(); UpdateBounds();
@@ -222,6 +226,15 @@ namespace OpenNest
return part; return part;
} }
private void EnsureOwnedProgram()
{
if (!ownsProgram)
{
Program = Program.Clone() as Program;
ownsProgram = true;
}
}
private Part(Drawing baseDrawing, Program program, Vector location, Box boundingBox) private Part(Drawing baseDrawing, Program program, Vector location, Box boundingBox)
{ {
BaseDrawing = baseDrawing; BaseDrawing = baseDrawing;