diff --git a/OpenNest.Avalonia/Rendering/ProgramPaths.cs b/OpenNest.Avalonia/Rendering/ProgramPaths.cs deleted file mode 100644 index 7cd6fd27..00000000 --- a/OpenNest.Avalonia/Rendering/ProgramPaths.cs +++ /dev/null @@ -1,175 +0,0 @@ -using System; -using OpenNest.CNC; -using OpenNest.Geometry; -using OpenNest.Math; -using SkiaSharp; - -namespace OpenNest.Avalonia.Rendering -{ - /// - /// A program's display geometry as Skia paths in the program frame, before placement. - /// A Skia port of the desktop's GraphicsHelper.GetDisplayPaths, without tab bridging. - /// - internal sealed class ProgramPaths : IDisposable - { - public SKPath Cut { get; } = new() { FillType = SKPathFillType.EvenOdd }; - - public SKPath Lead { get; } = new(); - - public SKPath Etch { get; } = new(); - - /// The material outline to fill: cuts and tabs, without open break cuts. - public SKPath Fill { get; } = new() { FillType = SKPathFillType.EvenOdd }; - - public SKRect Bounds { get; private set; } - - public static ProgramPaths Build(CNC.Program program) - { - var paths = new ProgramPaths(); - var position = Vector.Zero; - paths.AddProgram(program, ref position); - var bounds = paths.Cut.Bounds; - if (!paths.Etch.IsEmpty) - bounds = paths.Cut.IsEmpty ? paths.Etch.Bounds : SKRect.Union(bounds, paths.Etch.Bounds); - paths.Bounds = bounds; - return paths; - } - - private SKPath TargetFor(LayerType layer) => layer switch - { - LayerType.Leadin or LayerType.Leadout => Lead, - LayerType.Scribe => Etch, - LayerType.Display or LayerType.Tab => null, - _ => Cut, - }; - - private void AddProgram(CNC.Program program, ref Vector position) - { - // Sub-program offsets are relative to the frame origin at entry, not the tool position. - var frameOrigin = position; - var mode = program.Mode; - SKPath open = null; - - for (var i = 0; i < program.Length; ++i) - { - var code = program[i]; - switch (code) - { - case LinearMove line: - { - var end = mode == Mode.Incremental ? line.EndPoint + position : line.EndPoint; - var target = line.Suppressed ? null : TargetFor(line.Layer); - if (target != null) - { - Continue(ref open, target, position); - target.LineTo((float)end.X, (float)end.Y); - } - if (!line.Suppressed && line.Layer != LayerType.Break) - AddFillLine(line.Layer, position, end); - position = end; - break; - } - - case ArcMove arc: - { - var end = arc.EndPoint; - var center = arc.CenterPoint; - if (mode == Mode.Incremental) - { - end += position; - center += position; - } - var target = arc.Suppressed ? null : TargetFor(arc.Layer); - if (target != null) - { - Continue(ref open, target, position); - AddArc(target, position, end, center, arc.Rotation); - } - if (!arc.Suppressed && arc.Layer != LayerType.Break) - AddFillArc(arc.Layer, position, end, center, arc.Rotation); - position = end; - break; - } - - case RapidMove rapid: - { - var end = mode == Mode.Incremental ? rapid.EndPoint + position : rapid.EndPoint; - if (end.DistanceTo(position) > 0.001) - open = null; - position = end; - break; - } - - case SubProgramCall call when call.Program != null: - open = null; - position = new Vector(frameOrigin.X + call.Offset.X, frameOrigin.Y + call.Offset.Y); - AddProgram(call.Program, ref position); - open = null; - break; - } - } - } - - // Starts a new contour unless the move continues the one already open on the same path. - private SKPath openFill; - - private static void Continue(ref SKPath open, SKPath target, Vector from) - { - if (!ReferenceEquals(open, target) || target.LastPoint != new SKPoint((float)from.X, (float)from.Y)) - target.MoveTo((float)from.X, (float)from.Y); - open = target; - } - - private bool IsMaterial(LayerType layer) => - layer is not (LayerType.Scribe or LayerType.Leadin or LayerType.Leadout or LayerType.Display); - - private void AddFillLine(LayerType layer, Vector from, Vector to) - { - if (!IsMaterial(layer)) - return; - Continue(ref openFill, Fill, from); - Fill.LineTo((float)to.X, (float)to.Y); - } - - private void AddFillArc(LayerType layer, Vector from, Vector to, Vector center, RotationType rotation) - { - if (!IsMaterial(layer)) - return; - Continue(ref openFill, Fill, from); - AddArc(Fill, from, to, center, rotation); - } - - private static void AddArc(SKPath path, Vector from, Vector to, Vector center, RotationType rotation) - { - var startAngle = Angle.NormalizeDeg(Angle.ToDegrees(System.Math.Atan2(from.Y - center.Y, from.X - center.X))); - var endAngle = Angle.NormalizeDeg(Angle.ToDegrees(System.Math.Atan2(to.Y - center.Y, to.X - center.X))); - var radius = (float)center.DistanceTo(to); - var oval = new SKRect((float)center.X - radius, (float)center.Y - radius, - (float)center.X + radius, (float)center.Y + radius); - - if (startAngle.IsEqualTo(endAngle)) - { - // A full circle: two half sweeps keep the contour starting at the current point. - var sweep = rotation == RotationType.CCW ? 180f : -180f; - path.ArcTo(oval, (float)startAngle, sweep, false); - path.ArcTo(oval, (float)startAngle + sweep, sweep, false); - return; - } - - if (rotation == RotationType.CCW && endAngle < startAngle) - endAngle += 360.0; - else if (rotation == RotationType.CW && startAngle < endAngle) - startAngle += 360.0; - - path.ArcTo(oval, (float)startAngle, (float)(endAngle - startAngle), false); - } - - public void Dispose() - { - Cut.Dispose(); - Lead.Dispose(); - Etch.Dispose(); - Fill.Dispose(); - } - } -} diff --git a/OpenNest.Desktop.Tests/Rendering/ProgramGeometryTests.cs b/OpenNest.Desktop.Tests/Rendering/ProgramGeometryTests.cs index abe32f6b..60785387 100644 --- a/OpenNest.Desktop.Tests/Rendering/ProgramGeometryTests.cs +++ b/OpenNest.Desktop.Tests/Rendering/ProgramGeometryTests.cs @@ -266,6 +266,8 @@ public class ProgramGeometryTests hole.Codes.Add(new RapidMove(new Vector(1, 0))); hole.Codes.Add(new ArcMove(Vector.Zero, new Vector(-1, 0), RotationType.CW)); var program = Square(); + // The tool is elsewhere when the call runs; the offset is from the frame origin. + program.Codes.Add(new RapidMove(new Vector(20, 20))); program.Codes.Add(new SubProgramCall(hole, 0) { Offset = new Vector(5, 5) }); var cut = ProgramGeometry.Layers(program).Cut; diff --git a/OpenNest.Desktop.Tests/Rendering/ViewTransformTests.cs b/OpenNest.Desktop.Tests/Rendering/ViewTransformTests.cs index 9dd2b8ad..c3450992 100644 --- a/OpenNest.Desktop.Tests/Rendering/ViewTransformTests.cs +++ b/OpenNest.Desktop.Tests/Rendering/ViewTransformTests.cs @@ -44,6 +44,10 @@ public class ViewTransformTests var centre = view.WorldToScreen(new Vector(60, 45)); Assert.Equal(400, centre.X, 9); Assert.Equal(300, centre.Y, 9); + + // Height-limited: (600 - 50) / 100. + Assert.True(view.ZoomToArea(new Box(0, 0, 10, 100))); + Assert.Equal(5.5, view.Scale, 9); } [Fact]