mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-11 09:51:10 -05:00
A part's sequence-number label point was computed once from the drawing's program and cached forever. Editing a drawing in the converter replaces that program, sometimes in a different frame, so the labels of every placed copy stayed at the old frame's point and drifted off the parts. Keep the cached point only while the drawing program (and its rotation) is the one it came from. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
133 lines
5.2 KiB
C#
133 lines
5.2 KiB
C#
using System.Drawing;
|
|
using System.Drawing.Drawing2D;
|
|
using OpenNest.CNC;
|
|
using OpenNest.Controls;
|
|
using OpenNest.Geometry;
|
|
using Program = OpenNest.CNC.Program;
|
|
|
|
namespace OpenNest.WinForms.Tests.Controls;
|
|
|
|
/// <summary>
|
|
/// A zoom or a move maps a part's cached program-frame paths to the view instead of rebuilding
|
|
/// them; the result must match a part built from scratch in the same state.
|
|
/// </summary>
|
|
public class LayoutPartViewCacheTests
|
|
{
|
|
[Fact]
|
|
public void ZoomMoveAndRotateMatchAFreshBuild() => StaTestThread.Run(() =>
|
|
{
|
|
var part = new Part(new Drawing("Holes", PlateWithHoles()), new Vector(5, 7));
|
|
using var view = new PlateView(ColorSchemeRegistry.Get("Workshop"));
|
|
view.Matrix = new Matrix(2, 0, 0, -2, 0, 0);
|
|
var layout = LayoutPart.Create(part, view);
|
|
AssertMatchesFreshBuild(layout, view);
|
|
|
|
view.Matrix = new Matrix(3.5f, 0, 0, -3.5f, 0, 0);
|
|
layout.UpdateView(view);
|
|
AssertMatchesFreshBuild(layout, view);
|
|
|
|
layout.Offset(12.25, -3.5);
|
|
Assert.True(layout.IsDirty);
|
|
layout.UpdateView(view);
|
|
Assert.False(layout.IsDirty);
|
|
AssertMatchesFreshBuild(layout, view);
|
|
|
|
// A rotation of the base part, then a move through the layout, must not reuse stale paths.
|
|
part.Rotate(OpenNest.Math.Angle.ToRadians(30), part.BoundingBox.Center);
|
|
layout.Offset(1, 1);
|
|
layout.UpdateView(view);
|
|
AssertMatchesFreshBuild(layout, view);
|
|
}, TimeSpan.FromMinutes(1), "Layout part view cache test timed out.");
|
|
|
|
[Fact]
|
|
public void DirtyRebuildsAfterInPlaceProgramEdit() => StaTestThread.Run(() =>
|
|
{
|
|
var part = new Part(new Drawing("Edited", PlateWithHoles()));
|
|
using var view = new PlateView(ColorSchemeRegistry.Get("Workshop"));
|
|
view.Matrix = new Matrix(2, 0, 0, -2, 0, 0);
|
|
var layout = LayoutPart.Create(part, view);
|
|
var before = layout.Path.PointCount;
|
|
|
|
part.Program.MoveTo(5, 25);
|
|
part.Program.LineTo(10, 25);
|
|
layout.IsDirty = true;
|
|
layout.UpdateView(view);
|
|
|
|
Assert.True(layout.Path.PointCount > before);
|
|
AssertMatchesFreshBuild(layout, view);
|
|
}, TimeSpan.FromMinutes(1), "Layout part rebuild test timed out.");
|
|
|
|
[Fact]
|
|
public void LabelFollowsAReplacedDrawingProgram() => StaTestThread.Run(() =>
|
|
{
|
|
// Editing a drawing replaces its program, possibly in another frame; the part's sequence
|
|
// label must be found again in the new frame, not kept from the old program.
|
|
var drawing = new Drawing("Door stop", LShape(Vector.Zero));
|
|
var part = new Part(drawing, new Vector(5, 7));
|
|
using var view = new PlateView(ColorSchemeRegistry.Get("Workshop"));
|
|
view.Matrix = new Matrix(2, 0, 0, -2, 0, 0);
|
|
var layout = LayoutPart.Create(part, view);
|
|
var before = layout.LabelScreenPoint;
|
|
|
|
drawing.Program = LShape(new Vector(-10, -5));
|
|
layout.IsDirty = true;
|
|
layout.UpdateView(view);
|
|
|
|
var fresh = LayoutPart.Create(part, view);
|
|
Assert.NotEqual(before, fresh.LabelScreenPoint);
|
|
Assert.Equal(fresh.LabelScreenPoint.X, layout.LabelScreenPoint.X, 2);
|
|
Assert.Equal(fresh.LabelScreenPoint.Y, layout.LabelScreenPoint.Y, 2);
|
|
fresh.ReleaseDisplayResources();
|
|
}, TimeSpan.FromMinutes(1), "Layout part label test timed out.");
|
|
|
|
private static Program LShape(Vector origin)
|
|
{
|
|
var program = new Program();
|
|
program.MoveTo(origin.X, origin.Y);
|
|
program.LineTo(origin.X + 10, origin.Y);
|
|
program.LineTo(origin.X + 10, origin.Y + 30);
|
|
program.LineTo(origin.X + 40, origin.Y + 30);
|
|
program.LineTo(origin.X + 40, origin.Y + 40);
|
|
program.LineTo(origin.X, origin.Y + 40);
|
|
program.LineTo(origin.X, origin.Y);
|
|
return program;
|
|
}
|
|
|
|
private static void AssertMatchesFreshBuild(LayoutPart layout, PlateView view)
|
|
{
|
|
var fresh = LayoutPart.Create(layout.BasePart, view);
|
|
Assert.Equal(fresh.Path.PathTypes, layout.Path.PathTypes);
|
|
var expected = fresh.Path.PathPoints;
|
|
var actual = layout.Path.PathPoints;
|
|
Assert.Equal(expected.Length, actual.Length);
|
|
for (var i = 0; i < expected.Length; i++)
|
|
{
|
|
Assert.Equal(expected[i].X, actual[i].X, 2);
|
|
Assert.Equal(expected[i].Y, actual[i].Y, 2);
|
|
}
|
|
Assert.Equal(fresh.DisplayBounds.X, layout.DisplayBounds.X, 2);
|
|
Assert.Equal(fresh.DisplayBounds.Y, layout.DisplayBounds.Y, 2);
|
|
Assert.Equal(fresh.DisplayBounds.Width, layout.DisplayBounds.Width, 2);
|
|
Assert.Equal(fresh.DisplayBounds.Height, layout.DisplayBounds.Height, 2);
|
|
fresh.ReleaseDisplayResources();
|
|
}
|
|
|
|
private static Program PlateWithHoles()
|
|
{
|
|
var program = new Program();
|
|
program.MoveTo(0, 0);
|
|
program.LineTo(40, 0);
|
|
program.LineTo(40, 30);
|
|
program.LineTo(0, 30);
|
|
program.LineTo(0, 0);
|
|
for (var i = 0; i < 6; i++)
|
|
{
|
|
var center = new Vector(5 + i * 6, 15);
|
|
var start = new Vector(center.X + 2, center.Y);
|
|
program.MoveTo(start);
|
|
program.ArcTo(start, center, RotationType.CCW);
|
|
}
|
|
return program;
|
|
}
|
|
}
|