mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-15 11:15:36 -05:00
refactor(core): refresh Drawing's derived state through ProgramChanged
Area and the canonical angle were refreshed together only by the Program setter; an in-place program edit needed two separate calls, and UpdateArea kept a stale Area when no material was left. ProgramChanged is now the single entry point (the setter calls it), a program with no material or a null program gives Area 0, and UpdateArea/RecomputeCanonicalAngle are private. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
1 parent
dec685361c
commit
986167a411
12 files changed
+125
-24
No files matched your search
@@ -57,8 +57,7 @@ public static class DrawingMarkRecovery
|
||||
return false;
|
||||
|
||||
AppendMarks(drawing.Program, marks);
|
||||
drawing.UpdateArea();
|
||||
drawing.RecomputeCanonicalAngle();
|
||||
drawing.ProgramChanged();
|
||||
|
||||
foreach (var part in placedParts.Where(part => ReferenceEquals(part.BaseDrawing, drawing)
|
||||
&& !part.HasManualLeadIns))
|
||||
|
||||
@@ -79,17 +79,24 @@ namespace OpenNest
|
||||
set
|
||||
{
|
||||
program = value;
|
||||
UpdateArea();
|
||||
RecomputeCanonicalAngle();
|
||||
ProgramChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recomputes and stores the canonical angle from the current Program.
|
||||
/// Callers that mutate Program in place (rather than reassigning it) must invoke this explicitly.
|
||||
/// Cut-off drawings are left with Angle=0.
|
||||
/// Refreshes everything derived from <see cref="Program"/>: <see cref="Area"/> and the
|
||||
/// canonical angle in <see cref="SourceInfo.Angle"/>. Assigning <see cref="Program"/>
|
||||
/// calls it; callers that mutate the program in place (rather than reassigning it) must
|
||||
/// call it afterwards.
|
||||
/// </summary>
|
||||
public void RecomputeCanonicalAngle()
|
||||
public void ProgramChanged()
|
||||
{
|
||||
UpdateArea();
|
||||
RecomputeCanonicalAngle();
|
||||
}
|
||||
|
||||
// Cut-off drawings are left with Angle=0.
|
||||
private void RecomputeCanonicalAngle()
|
||||
{
|
||||
if (Source == null)
|
||||
Source = new SourceInfo();
|
||||
@@ -188,15 +195,25 @@ namespace OpenNest
|
||||
|
||||
public double Area { get; protected set; }
|
||||
|
||||
public void UpdateArea()
|
||||
private void UpdateArea()
|
||||
{
|
||||
if (program == null)
|
||||
{
|
||||
Area = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
var geometry = ConvertProgram
|
||||
.ToGeometry(Program)
|
||||
.ToGeometry(program)
|
||||
.Where(entity => SpecialLayers.IsMaterial(entity.Layer));
|
||||
var shapes = ShapeBuilder.GetShapes(geometry);
|
||||
|
||||
// No material left (e.g. only marks): a stale area would still count as material.
|
||||
if (shapes.Count == 0)
|
||||
{
|
||||
Area = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
var areas = new double[shapes.Count];
|
||||
|
||||
@@ -255,7 +272,7 @@ namespace OpenNest
|
||||
/// setter via <see cref="CanonicalAngle.Compute"/>. A value of 0 means the drawing is
|
||||
/// already canonical or <see cref="Drawing.IsCutOff"/> is true. Callers that mutate
|
||||
/// <see cref="Drawing.Program"/> in place must invoke
|
||||
/// <see cref="Drawing.RecomputeCanonicalAngle"/> to refresh.
|
||||
/// <see cref="Drawing.ProgramChanged"/> to refresh.
|
||||
/// </summary>
|
||||
public double Angle { get; set; }
|
||||
}
|
||||
|
||||
@@ -336,8 +336,7 @@ namespace OpenNest.IO
|
||||
&& ScribeLayerRepair.Apply(drawing.Program, marks, drawing.Source.Offset) > 0
|
||||
)
|
||||
{
|
||||
drawing.UpdateArea();
|
||||
drawing.RecomputeCanonicalAngle();
|
||||
drawing.ProgramChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -194,7 +194,7 @@ public class BestFitCacheStoreTests
|
||||
{
|
||||
var drawing = MakeDrawing();
|
||||
drawing.Program.Rotate(0.5, drawing.Program.BoundingBox().Center);
|
||||
drawing.RecomputeCanonicalAngle();
|
||||
drawing.ProgramChanged();
|
||||
Assert.NotEqual(0.0, drawing.Source.Angle);
|
||||
return drawing;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ public class BestFitCacheTests
|
||||
{
|
||||
var drawing = new TShape { Width = 10, Height = 8 }.GetDrawing();
|
||||
drawing.Program.Rotate(Angle.ToRadians(30), drawing.Program.BoundingBox().Center);
|
||||
drawing.RecomputeCanonicalAngle();
|
||||
drawing.ProgramChanged();
|
||||
return drawing;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ public class BestFitResultFrameTests
|
||||
{
|
||||
var drawing = new TShape { Width = 10, Height = 8 }.GetDrawing();
|
||||
drawing.Program.Rotate(Angle.ToRadians(30), drawing.Program.BoundingBox().Center);
|
||||
drawing.RecomputeCanonicalAngle();
|
||||
drawing.ProgramChanged();
|
||||
|
||||
var canonical = CanonicalFrame.AsCanonicalCopy(drawing);
|
||||
var result = EvaluateOffsetPair(canonical, new Vector(40, 30));
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
using System.Linq;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Tests;
|
||||
|
||||
public class DrawingProgramChangedTests
|
||||
{
|
||||
private static Program Rectangle(double width, double height)
|
||||
{
|
||||
var program = new Program();
|
||||
program.Codes.Add(new RapidMove(new Vector(0, 0)));
|
||||
program.Codes.Add(new LinearMove(new Vector(width, 0)));
|
||||
program.Codes.Add(new LinearMove(new Vector(width, height)));
|
||||
program.Codes.Add(new LinearMove(new Vector(0, height)));
|
||||
program.Codes.Add(new LinearMove(new Vector(0, 0)));
|
||||
return program;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InPlaceEditIsStaleUntilProgramChangedRefreshesAreaAndAngle()
|
||||
{
|
||||
var drawing = new Drawing("r", Rectangle(100, 50));
|
||||
Assert.Equal(5000, drawing.Area, 6);
|
||||
Assert.Equal(0.0, drawing.Source.Angle, 6);
|
||||
|
||||
// Neither edit goes through the Program setter.
|
||||
foreach (var move in drawing.Program.Codes.OfType<LinearMove>())
|
||||
move.EndPoint = new Vector(move.EndPoint.X * 2, move.EndPoint.Y);
|
||||
drawing.Program.Rotate(0.5, drawing.Program.BoundingBox().Center);
|
||||
Assert.Equal(5000, drawing.Area, 6);
|
||||
Assert.Equal(0.0, drawing.Source.Angle, 6);
|
||||
|
||||
drawing.ProgramChanged();
|
||||
|
||||
Assert.Equal(10000, drawing.Area, 6);
|
||||
Assert.InRange(drawing.Source.Angle, -0.52, -0.48);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClearingTheMaterialZeroesTheArea()
|
||||
{
|
||||
var drawing = new Drawing("r", Rectangle(100, 50));
|
||||
|
||||
foreach (var move in drawing.Program.Codes.OfType<LinearMove>())
|
||||
move.Layer = LayerType.Scribe;
|
||||
drawing.ProgramChanged();
|
||||
|
||||
Assert.Equal(0.0, drawing.Area);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnEmptiedProgramZeroesTheArea()
|
||||
{
|
||||
var drawing = new Drawing("r", Rectangle(100, 50));
|
||||
|
||||
drawing.Program.Codes.Clear();
|
||||
drawing.ProgramChanged();
|
||||
|
||||
Assert.Equal(0.0, drawing.Area);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ANullProgramZeroesAreaAndAngle()
|
||||
{
|
||||
var program = Rectangle(100, 50);
|
||||
program.Rotate(0.5, program.BoundingBox().Center);
|
||||
var drawing = new Drawing("r", program);
|
||||
Assert.NotEqual(0.0, drawing.Source.Angle);
|
||||
|
||||
drawing.Program = null;
|
||||
|
||||
Assert.Equal(0.0, drawing.Area);
|
||||
Assert.Equal(0.0, drawing.Source.Angle);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ACutOffDrawingKeepsAngleZero()
|
||||
{
|
||||
var drawing = new Drawing("cut", Rectangle(100, 50)) { IsCutOff = true };
|
||||
|
||||
drawing.Program.Rotate(0.5, drawing.Program.BoundingBox().Center);
|
||||
drawing.ProgramChanged();
|
||||
|
||||
Assert.Equal(0.0, drawing.Source.Angle);
|
||||
Assert.Equal(5000, drawing.Area, 6);
|
||||
}
|
||||
}
|
||||
@@ -185,7 +185,7 @@ public class DrawingCanonicalAngleWiringTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecomputeCanonicalAngle_UpdatesAfterMutation()
|
||||
public void ProgramChanged_UpdatesAngleAfterMutation()
|
||||
{
|
||||
var d = new Drawing("r", RotatedRectProgram(100, 50, 0.0));
|
||||
Assert.Equal(0.0, d.Source.Angle, precision: 6);
|
||||
@@ -194,7 +194,7 @@ public class DrawingCanonicalAngleWiringTests
|
||||
d.Program.Rotate(0.5, d.Program.BoundingBox().Center);
|
||||
Assert.Equal(0.0, d.Source.Angle, precision: 6); // still stale
|
||||
|
||||
d.RecomputeCanonicalAngle();
|
||||
d.ProgramChanged();
|
||||
Assert.InRange(d.Source.Angle, -0.52, -0.48);
|
||||
}
|
||||
}
|
||||
@@ -237,7 +237,7 @@ public class BestFitPersistenceTests
|
||||
{
|
||||
var drawing = MakeTShape();
|
||||
drawing.Program.Rotate(Angle.ToRadians(30), drawing.Program.BoundingBox().Center);
|
||||
drawing.RecomputeCanonicalAngle();
|
||||
drawing.ProgramChanged();
|
||||
return drawing;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ public class FillHelpersTests
|
||||
{
|
||||
// Program.Rotate mutates in place; refresh the canonical angle the same way
|
||||
// a rotated CAD import carries it, or AsCanonicalCopy takes the zero-angle path.
|
||||
drawing.RecomputeCanonicalAngle();
|
||||
drawing.ProgramChanged();
|
||||
canonicalAngle = drawing.Source.Angle;
|
||||
Assert.False(OpenNest.Math.Tolerance.IsEqualTo(canonicalAngle, 0));
|
||||
drawing = CanonicalFrame.AsCanonicalCopy(drawing);
|
||||
|
||||
@@ -167,7 +167,6 @@ int RunDataCollection(string dir, string dbPath, string saveDir, double s, strin
|
||||
continue;
|
||||
}
|
||||
|
||||
drawing.UpdateArea();
|
||||
drawing.Color = PartColors[colorIndex % PartColors.Length];
|
||||
colorIndex++;
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using OpenNest.Engine;
|
||||
using OpenNest.Engine.ML;
|
||||
using OpenNest.IO;
|
||||
using OpenNest.Training.Data;
|
||||
using OpenNest.Engine;
|
||||
|
||||
namespace OpenNest.Training
|
||||
{
|
||||
@@ -137,7 +137,6 @@ namespace OpenNest.Training
|
||||
var program = programReader.Read();
|
||||
|
||||
var drawing = new Drawing("backfill") { Program = program };
|
||||
drawing.UpdateArea();
|
||||
|
||||
var features = FeatureExtractor.Extract(drawing);
|
||||
if (features == null)
|
||||
@@ -175,7 +174,7 @@ namespace OpenNest.Training
|
||||
{
|
||||
try
|
||||
{
|
||||
_db.Database.ExecuteSqlRaw($"ALTER TABLE Runs ADD COLUMN {name} {type}");
|
||||
_db.Database.ExecuteSql($"ALTER TABLE Runs ADD COLUMN {name} {type}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user