test: share the circle, square, rectangle and contour fixtures

Cutting-planning, diagnostics and fill tests drew the same circles,
squares, clockwise rectangles, closed polygons, rounded rectangles and
right-midpoint rectangle contours in private helpers. They now use
TestHelpers; files whose helper had its own default size keep a
one-line wrapper for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-11-01 10:50:50 -05:00
1 parent a5e219008e
commit f0d84e041c
20 files changed
+193 -278

No files matched your search

+2 -12
View File
@@ -14,18 +14,8 @@ public class BestFitOverlapTests
public BestFitOverlapTests(ITestOutputHelper output) => _output = output;
private static Drawing MakeRoundedRect(double w = 7.25, double h = 3.31, double r = 0.5)
{
var pgm = new Program();
pgm.Codes.Add(new RapidMove(new Vector(0, 0)));
pgm.Codes.Add(new LinearMove(new Vector(w - r, 0)));
pgm.Codes.Add(new ArcMove(new Vector(w, r), new Vector(w - r, r), RotationType.CW));
pgm.Codes.Add(new LinearMove(new Vector(w, h - r)));
pgm.Codes.Add(new ArcMove(new Vector(w - r, h), new Vector(w - r, h - r), RotationType.CW));
pgm.Codes.Add(new LinearMove(new Vector(0, h)));
pgm.Codes.Add(new LinearMove(new Vector(0, 0)));
return new Drawing("rounded-rect", pgm);
}
private static Drawing MakeRoundedRect(double w = 7.25, double h = 3.31, double r = 0.5) =>
TestHelpers.MakeRoundedRectDrawing(w, h, r);
private static Drawing ImportDxf()
{
@@ -1,6 +1,7 @@
using OpenNest.CNC;
using OpenNest.CNC.CuttingPlanning;
using OpenNest.Geometry;
using static OpenNest.Tests.TestHelpers;
namespace OpenNest.Tests.CuttingPlanning;
@@ -16,15 +17,6 @@ public class ContourEntryCandidateTests
// --- fixtures ---------------------------------------------------------------
private static Program ClosedContour(IEnumerable<Vector> vertices)
{
var p = new Program();
p.MoveTo(vertices.First().X, vertices.First().Y);
foreach (var v in vertices.Skip(1).Append(vertices.First()))
p.LineTo(v.X, v.Y);
return p;
}
/// <summary>Square perimeter with an L-notch hole slug; its concave vertex (3,3.5) is reflex for the slug's own travel.</summary>
private static Program NotchedHole()
{
@@ -185,7 +177,7 @@ public class ContourEntryCandidateTests
public void CollinearSplit_IsNotATangentJoint_ButBothMidpointsRemain()
{
// Bottom edge split into two collinear lines at (5,0).
var p = ClosedContour(new[]
var p = MakeClosedProgram(new[]
{
new Vector(0, 0), new Vector(5, 0), new Vector(10, 0), new Vector(10, 10), new Vector(0, 10),
});
@@ -225,7 +217,7 @@ public class ContourEntryCandidateTests
public void ReflexVertexOfOuterOutline_IsNeverEnumerated()
{
// L-shaped outline: (5,5) is reflex for the part's own CCW travel.
var p = ClosedContour(new[]
var p = MakeClosedProgram(new[]
{
new Vector(0, 0), new Vector(10, 0), new Vector(10, 10), new Vector(5, 10),
new Vector(5, 5), new Vector(0, 5),
@@ -255,7 +247,7 @@ public class ContourEntryCandidateTests
public void ManualEntry_StillAcceptsAReflexPoint()
{
// The manual path is unchanged: an explicit reflex vertex is a valid choice.
var prepared = Capture(ClosedContour(new[]
var prepared = Capture(MakeClosedProgram(new[]
{
new Vector(0, 0), new Vector(10, 0), new Vector(10, 10), new Vector(5, 10),
new Vector(5, 5), new Vector(0, 5),
@@ -437,7 +429,7 @@ public class ContourEntryCandidateTests
{
// 10 x 0.4 rectangle: every convex corner's vertical edge (0.4) is shorter than
// 2 x lead-in (0.6), so only the horizontal edges carry near-corner points.
var p = ClosedContour(new[]
var p = MakeClosedProgram(new[]
{
new Vector(0, 0), new Vector(10, 0), new Vector(10, 0.4), new Vector(0, 0.4),
});
@@ -464,7 +456,7 @@ public class ContourEntryCandidateTests
// Boundary notch 0.6 deep — exactly 2 x lead-in. The inset from each convex
// opening corner lands exactly on the reflex inner corner, so neither inner
// corner may appear in the automatic catalog at all.
var p = ClosedContour(new[]
var p = MakeClosedProgram(new[]
{
new Vector(0, 0), new Vector(10, 0), new Vector(10, 10), new Vector(0, 10),
new Vector(0, 4.6), new Vector(0.6, 4.6), new Vector(0.6, 4), new Vector(0, 4),
@@ -480,7 +472,7 @@ public class ContourEntryCandidateTests
{
// L-outline; (5,5) is reflex for its own travel. The raw closest point from that
// position lands exactly on the reflex vertex and must be dropped.
var p = ClosedContour(new[]
var p = MakeClosedProgram(new[]
{
new Vector(0, 0), new Vector(10, 0), new Vector(10, 10), new Vector(5, 10),
new Vector(5, 5), new Vector(0, 5),
@@ -1,6 +1,7 @@
using OpenNest.CNC;
using OpenNest.CNC.CuttingPlanning;
using OpenNest.Geometry;
using static OpenNest.Tests.TestHelpers;
namespace OpenNest.Tests.CuttingPlanning;
@@ -17,16 +18,6 @@ public class ContourEntryRankingTests
// --- fixtures ---------------------------------------------------------------
/// <summary>Square 0..10 in travel order, optionally cyclically reindexed or reversed.</summary>
private static Program Square(IEnumerable<Vector> vertices)
{
var p = new Program();
p.MoveTo(vertices.First().X, vertices.First().Y);
foreach (var v in vertices.Skip(1).Append(vertices.First()))
p.LineTo(v.X, v.Y);
return p;
}
private static readonly Vector[] SquareVertices =
{
new(0, 0), new(10, 0), new(10, 10), new(0, 10),
@@ -55,7 +46,8 @@ public class ContourEntryRankingTests
[Fact]
public void LowerRightTarget_PrefersTheBottomRightCorner()
{
var ranked = Catalog(Square(SquareVertices)).RankTowardNextCut(new Vector(14, -2));
var ranked = Catalog(MakeClosedProgram(SquareVertices))
.RankTowardNextCut(new Vector(14, -2));
// Facing sides right and bottom; the shared corner is the ideal start.
var first = Assert.Single(ranked.Take(1));
@@ -70,7 +62,8 @@ public class ContourEntryRankingTests
public void FacingMidpoint_BeatsANonFacingCorner()
{
// Target due right: only the right side faces it; the left corners face away.
var ranked = Catalog(Square(SquareVertices)).RankTowardNextCut(new Vector(14, 5));
var ranked = Catalog(MakeClosedProgram(SquareVertices))
.RankTowardNextCut(new Vector(14, 5));
var order = ranked.ToList();
var rightMid = order.FindIndex(c => c.Kind == AutomaticEntryKind.StraightMidpoint && At(c, 10, 5));
@@ -81,7 +74,8 @@ public class ContourEntryRankingTests
[Fact]
public void SameFacingClass_CornerBeatsMidpoint()
{
var ranked = Catalog(Square(SquareVertices)).RankTowardNextCut(new Vector(14, -2));
var ranked = Catalog(MakeClosedProgram(SquareVertices))
.RankTowardNextCut(new Vector(14, -2));
var order = ranked.ToList();
var corner = order.FindIndex(c => c.Kind == AutomaticEntryKind.ConvexCorner && At(c, 10, 0));
@@ -140,8 +134,10 @@ public class ContourEntryRankingTests
var target = new Vector(14, -2);
Assert.Equal(
Shape(Catalog(Square(SquareVertices)).RankTowardNextCut(target, new Vector(-1, -1))),
Shape(Catalog(Square(vertices)).RankTowardNextCut(target, new Vector(-1, -1))));
Shape(Catalog(MakeClosedProgram(SquareVertices))
.RankTowardNextCut(target, new Vector(-1, -1))),
Shape(Catalog(MakeClosedProgram(vertices))
.RankTowardNextCut(target, new Vector(-1, -1))));
}
[Fact]
@@ -162,7 +158,7 @@ public class ContourEntryRankingTests
public void Ranking_NeverIntroducesReflexCandidates()
{
// L-outline: (5,5) is reflex for its own travel and absent from the catalog.
var p = Square(new[]
var p = MakeClosedProgram(new[]
{
new Vector(0, 0), new Vector(10, 0), new Vector(10, 10), new Vector(5, 10),
new Vector(5, 5), new Vector(0, 5),
@@ -178,7 +174,7 @@ public class ContourEntryRankingTests
[Fact]
public void NonFiniteTarget_IsRejected()
{
var catalog = Catalog(Square(SquareVertices));
var catalog = Catalog(MakeClosedProgram(SquareVertices));
Assert.Throws<ArgumentException>(() =>
catalog.RankTowardNextCut(new Vector(double.NaN, 5)));
}
@@ -188,7 +184,8 @@ public class ContourEntryRankingTests
[Fact]
public void NoTarget_TierFirstThenDistanceToArrival()
{
var ranked = Catalog(Square(SquareVertices)).RankTowardNextCut(arrival: new Vector(5, 10));
var ranked = Catalog(MakeClosedProgram(SquareVertices))
.RankTowardNextCut(arrival: new Vector(5, 10));
var order = ranked.ToList();
// Tier first: an outside corner outranks the midpoint the arrival sits on.
@@ -208,7 +205,7 @@ public class ContourEntryRankingTests
{
// The last part faces where the head already is; the ranking must not drag it to
// the plate origin when the arrival is elsewhere.
var ranked = Catalog(Square(SquareVertices))
var ranked = Catalog(MakeClosedProgram(SquareVertices))
.RankTowardNextCut(arrival: new Vector(arrivalX, arrivalY));
Assert.True(At(ranked[0], expectedX, expectedY));
@@ -3,6 +3,7 @@ using OpenNest.CNC.CuttingPlanning;
using OpenNest.CNC.CuttingStrategy;
using OpenNest.Engine.CuttingPlanning;
using OpenNest.Geometry;
using static OpenNest.Tests.TestHelpers;
namespace OpenNest.Tests.CuttingPlanning;
@@ -15,7 +16,7 @@ public class CuttingCaptureRotationTests
[InlineData(0.3, true)]
public void SharedDiamond_CaptureAndReadyPreserveOnceRotatedCleanGeometry(double angle, bool incrementalRoot)
{
var leaf = Rectangle(1, 2);
var leaf = MakeClockwiseRectProgram(1, 2);
leaf.Mode = Mode.Incremental;
var left = new Program();
left.Codes.Add(new SubProgramCall(leaf, 0));
@@ -23,7 +24,7 @@ public class CuttingCaptureRotationTests
var right = new Program();
right.Codes.Add(new SubProgramCall(leaf, 0));
right.Mode = Mode.Incremental;
var clean = Rectangle(10, 10);
var clean = MakeClockwiseRectProgram(10, 10);
clean.Codes.Add(new SubProgramCall(left, 0) { Offset = new Vector(3, 3) });
clean.Codes.Add(new SubProgramCall(right, 0) { Offset = new Vector(7, 3) });
if (incrementalRoot) clean.Mode = Mode.Incremental;
@@ -80,15 +81,4 @@ public class CuttingCaptureRotationTests
}
private static bool IsCut(ExecutionMotion motion) => !motion.Rapid && motion.Layer is LayerType.Cut or LayerType.Display;
private static Program Rectangle(double width, double height)
{
var program = new Program();
program.MoveTo(0, 0);
program.LineTo(0, height);
program.LineTo(width, height);
program.LineTo(width, 0);
program.LineTo(0, 0);
return program;
}
}
@@ -3,6 +3,7 @@ using OpenNest.CNC.CuttingPlanning;
using OpenNest.CNC.CuttingStrategy;
using OpenNest.Engine.CuttingPlanning;
using OpenNest.Geometry;
using static OpenNest.Tests.TestHelpers;
namespace OpenNest.Tests.CuttingPlanning;
@@ -482,26 +483,17 @@ public class CuttingDependencyTests
{
var clean = new Program();
clean.MoveTo(width, height / 2);
Contour(clean, width, height);
TraceRectangleFromRightMidpoint(clean, width, height);
var part = new Part(new Drawing("same", clean), new Vector(x, y));
var placed = new Program();
placed.MoveTo(width + 0.25, height / 2);
placed.Codes.Add(new LinearMove(width, height / 2) { Layer = LayerType.Leadin });
Contour(placed, width, height);
TraceRectangleFromRightMidpoint(placed, width, height);
placed.Codes.Add(new LinearMove(width + 0.25, height / 2) { Layer = LayerType.Leadout });
Assert.True(part.RestoreLeadInProgram(placed, false));
return part;
}
private static void Contour(Program program, double width, double height)
{
program.LineTo(width, 0);
program.LineTo(0, 0);
program.LineTo(0, height);
program.LineTo(width, height);
program.LineTo(width, height / 2);
}
private static string Describe(CuttingPlanResult r) =>
$"{r.Status}, expanded {r.Expansions}: " + string.Join("; ", r.Findings.Select(f => f.Message));
}
@@ -6,6 +6,7 @@ using OpenNest.Collections;
using OpenNest.Diagnostics;
using OpenNest.Engine.CuttingPlanning;
using OpenNest.Geometry;
using static OpenNest.Tests.TestHelpers;
namespace OpenNest.Tests.CuttingPlanning;
@@ -552,24 +553,15 @@ public class CuttingPlanCommitTests
{
var clean = new Program();
clean.MoveTo(width, height / 2);
Contour(clean, width, height);
TraceRectangleFromRightMidpoint(clean, width, height);
var part = new Part(new Drawing("same", clean), new Vector(x, y));
var placed = new Program();
placed.MoveTo(width + 0.25, height / 2);
placed.Codes.Add(new LinearMove(width, height / 2) { Layer = LayerType.Leadin });
Contour(placed, width, height);
TraceRectangleFromRightMidpoint(placed, width, height);
placed.Codes.Add(new LinearMove(width + 0.25, height / 2) { Layer = LayerType.Leadout });
Assert.True(part.RestoreLeadInProgram(placed, false));
part.CuttingParameters = new CuttingParameters();
return part;
}
private static void Contour(Program program, double width, double height)
{
program.LineTo(width, 0);
program.LineTo(0, 0);
program.LineTo(0, height);
program.LineTo(width, height);
program.LineTo(width, height / 2);
}
}
@@ -4,6 +4,7 @@ using OpenNest.CNC.CuttingStrategy;
using OpenNest.Diagnostics;
using OpenNest.Engine.CuttingPlanning;
using OpenNest.Geometry;
using static OpenNest.Tests.TestHelpers;
namespace OpenNest.Tests.CuttingPlanning;
@@ -384,27 +385,18 @@ public class CuttingPlanServiceTests
{
var clean = new Program();
clean.MoveTo(width, height / 2);
Contour(clean, width, height);
TraceRectangleFromRightMidpoint(clean, width, height);
var part = new Part(new Drawing(name, clean), new Vector(x, y));
var placed = new Program();
placed.MoveTo(width + 0.25, height / 2);
placed.Codes.Add(new LinearMove(width, height / 2) { Layer = LayerType.Leadin });
Contour(placed, width, height);
TraceRectangleFromRightMidpoint(placed, width, height);
placed.Codes.Add(new LinearMove(width + 0.25, height / 2) { Layer = LayerType.Leadout });
Assert.True(part.RestoreLeadInProgram(placed, false));
part.CuttingParameters = new CuttingParameters();
return part;
}
private static void Contour(Program program, double width, double height)
{
program.LineTo(width, 0);
program.LineTo(0, 0);
program.LineTo(0, height);
program.LineTo(width, height);
program.LineTo(width, height / 2);
}
// Independent replay uses private placements/drawings, never an event-wired plate
// around the source drawings (which would change their quantity accounting).
private static PostVerificationReport Analyze(params Part[] parts) => AnalyzeFrom(Vector.Zero, parts);
@@ -3,6 +3,7 @@ using OpenNest.CNC.CuttingPlanning;
using OpenNest.CNC.CuttingStrategy;
using OpenNest.Engine.CuttingPlanning;
using OpenNest.Geometry;
using static OpenNest.Tests.TestHelpers;
namespace OpenNest.Tests.CuttingPlanning;
@@ -42,7 +43,7 @@ public class ErodedPierceTests
{
var program = new Program();
program.Merge(Rectangle(2, 1, 2.4, 1.05));
program.Merge(Circle(5, 1.5, 0.03));
program.Merge(MakeCircleProgram(5, 1.5, 0.03));
program.Merge(Rectangle(0, 0, 10, 3));
var part = new Part(new Drawing("tag", program));
var parameters = new CuttingParameters
@@ -89,7 +90,7 @@ public class ErodedPierceTests
public void Planner_RoundHoleSmallerThanItsLeadIsPiercedAtItsCenter(double approach)
{
var program = new Program();
program.Merge(Circle(2, 1.5, 0.09));
program.Merge(MakeCircleProgram(2, 1.5, 0.09));
program.Merge(Rectangle(0, 0, 4, 3));
var part = new Part(new Drawing("plate", program));
var parameters = new CuttingParameters
@@ -244,14 +245,6 @@ public class ErodedPierceTests
private static Program Rectangle(double x1, double y1, double x2, double y2) =>
LeadPathValidationTests.Rectangle(x1, y1, x2, y2);
private static Program Circle(double x, double y, double radius)
{
var p = new Program();
p.MoveTo(x + radius, y);
p.ArcTo(x + radius, y, x, y, RotationType.CCW);
return p;
}
private static string Describe(CuttingPlanResult result) =>
$"{result.Status}: " + string.Join("; ", result.Findings.Select(f => f.Message).Take(5));
}
@@ -2,6 +2,7 @@ using OpenNest.CNC;
using OpenNest.CNC.CuttingPlanning;
using OpenNest.CNC.CuttingStrategy;
using OpenNest.Geometry;
using static OpenNest.Tests.TestHelpers;
namespace OpenNest.Tests.CuttingPlanning;
@@ -79,7 +80,7 @@ public class LeadPathValidationTests
var hole = LeadMaterialSnapshot.Capture(ring, Vector.Zero);
Assert.True(hole.IsComplete, hole.Reason);
AssertClear(ExternalLead(), target, hole);
var tangent = LeadMaterialSnapshot.Capture(Circle(-1.5, 6, 1), Vector.Zero);
var tangent = LeadMaterialSnapshot.Capture(MakeCircleProgram(-1.5, 6, 1), Vector.Zero);
Assert.True(tangent.IsComplete, tangent.Reason);
AssertUnsafe(ExternalLead(), target, tangent);
var arc = new Program();
@@ -87,7 +88,8 @@ public class LeadPathValidationTests
arc.Codes.Add(new ArcMove(new Vector(0, 5), new Vector(-1, 5), RotationType.CW) { Layer = LayerType.Leadin });
arc.LineTo(0, 10);
AssertClear(arc, target);
AssertUnsafe(arc, target, LeadMaterialSnapshot.Capture(Circle(-1, 7, 1), Vector.Zero));
AssertUnsafe(arc, target,
LeadMaterialSnapshot.Capture(MakeCircleProgram(-1, 7, 1), Vector.Zero));
}
[Fact]
@@ -102,7 +104,7 @@ public class LeadPathValidationTests
var arbitrary = ExternalLead();
arbitrary.Codes[2] = new LinearMove(5, 5); // Adjacent move is NOT nominal contour.
AssertUnsafe(arbitrary, target);
var circular = LeadMaterialSnapshot.Capture(Circle(0, 0, 2), Vector.Zero);
var circular = LeadMaterialSnapshot.Capture(MakeCircleProgram(0, 0, 2), Vector.Zero);
var coincident = new Program();
coincident.MoveTo(2, 0);
coincident.Codes.Add(new ArcMove(new Vector(-2, 0), Vector.Zero) { Layer = LayerType.Leadin });
@@ -124,7 +126,8 @@ public class LeadPathValidationTests
public void TinyNativeUncertainContactRefusesRatherThanApproves()
{
var target = LeadMaterialSnapshot.Capture(Rectangle(0, 0, 10, 10), Vector.Zero);
var obstacle = LeadMaterialSnapshot.Capture(Circle(-0.000005, 5, 0.000001), Vector.Zero);
var obstacle = LeadMaterialSnapshot.Capture(
MakeCircleProgram(-0.000005, 5, 0.000001), Vector.Zero);
Assert.True(obstacle.IsComplete, obstacle.Reason);
var p = ExternalLead(); p.Codes[0] = new RapidMove(-0.00001, 5);
var check = LeadPathValidator.Check(Read(p), target, [obstacle]);
@@ -152,7 +155,8 @@ public class LeadPathValidationTests
var p = new Program(); p.MoveTo(0, 10); p.LineTo(0, 5);
p.Codes.Add(new ArcMove(new Vector(-1, 4), new Vector(-1, 5), RotationType.CW) { Layer = LayerType.Leadout });
AssertClear(p, target);
AssertUnsafe(p, target, LeadMaterialSnapshot.Capture(Circle(-1, 3, 1), Vector.Zero));
AssertUnsafe(p, target,
LeadMaterialSnapshot.Capture(MakeCircleProgram(-1, 3, 1), Vector.Zero));
p.Codes[^1] = new ArcMove(new Vector(-1, 4), new Vector(0, 4), RotationType.CW) { Layer = LayerType.Leadout };
AssertUnsafe(p, target);
}
@@ -292,7 +296,7 @@ public class LeadPathValidationTests
public void RealStrategyEmission_ClearExternalAndInternalLeads(string style)
{
var clean = ExplicitContourTests.Square(false);
clean.Codes.AddRange(Circle(5, 5, 2).Codes);
clean.Codes.AddRange(MakeCircleProgram(5, 5, 2).Codes);
var target = LeadMaterialSnapshot.Capture(clean, Vector.Zero);
Assert.True(target.IsComplete, target.Reason);
var parameters = ExplicitContourTests.Parameters(style);
@@ -310,7 +314,7 @@ public class LeadPathValidationTests
public void RealStrategyEmission_UnsafeCompositePierceRefusesWithoutRepair()
{
var clean = ExplicitContourTests.Square(false);
clean.Codes.AddRange(Circle(5, 5, 2).Codes);
clean.Codes.AddRange(MakeCircleProgram(5, 5, 2).Codes);
var target = LeadMaterialSnapshot.Capture(clean, Vector.Zero);
var emitted = new ContourCuttingStrategy { Parameters = ExplicitContourTests.Parameters("linearc") }.Apply(clean, new Vector(-2, 5));
AssertUnsafe(emitted.Program, target);
@@ -319,11 +323,6 @@ public class LeadPathValidationTests
AssertUnsafe(orphan, target);
}
private static Program Circle(double x, double y, double radius)
{
var p = new Program(); p.MoveTo(x + radius, y); p.ArcTo(x + radius, y, x, y, RotationType.CCW); return p;
}
private static void AssertClear(Program p, LeadMaterialSnapshot target, params LeadMaterialSnapshot[] others)
{
var result = LeadPathValidator.Check(Read(p), target, others);
@@ -4,6 +4,7 @@ using OpenNest.CNC.CuttingStrategy;
using OpenNest.Diagnostics;
using OpenNest.Engine.CuttingPlanning;
using OpenNest.Geometry;
using static OpenNest.Tests.TestHelpers;
namespace OpenNest.Tests.CuttingPlanning;
@@ -107,8 +108,8 @@ public class PierceZoneTests
public void RoundHole_ShortLeadPiercesAtTheZoneEdge_TooSmallHolePiercesAtItsCenter()
{
var clean = Rectangle(0, 0, 10, 10);
clean.Codes.AddRange(Circle(3, 5, 2).Codes);
clean.Codes.AddRange(Circle(7.5, 5, 0.3).Codes);
clean.Codes.AddRange(MakeCircleProgram(3, 5, 2).Codes);
clean.Codes.AddRange(MakeCircleProgram(7.5, 5, 0.3).Codes);
var target = Snapshot(clean);
var execution = Read(new ContourCuttingStrategy { Parameters = Parameters(1) }.Apply(clean, Start).Program);
@@ -350,14 +351,6 @@ public class PierceZoneTests
private static Program Rectangle(double x1, double y1, double x2, double y2) =>
LeadPathValidationTests.Rectangle(x1, y1, x2, y2);
private static Program Circle(double x, double y, double radius)
{
var p = new Program();
p.MoveTo(x + radius, y);
p.ArcTo(x + radius, y, x, y, RotationType.CCW);
return p;
}
private static Program ExternalLead()
{
var p = new Program();
@@ -3,6 +3,7 @@ using OpenNest.CNC.CuttingPlanning;
using OpenNest.Diagnostics;
using OpenNest.Engine.CuttingPlanning;
using OpenNest.Geometry;
using static OpenNest.Tests.TestHelpers;
namespace OpenNest.Tests.CuttingPlanning;
@@ -111,7 +112,8 @@ public class SafestRouteFallbackTests
{
var plate = new Nest().CreatePlate();
plate.Size = new Size(60, 60);
plate.Parts.Add(new Part(new Drawing("bar", Rectangle(1, 50)), new Vector(25, 2)));
plate.Parts.Add(new Part(
new Drawing("bar", MakeClockwiseRectProgram(1, 50)), new Vector(25, 2)));
plate.Parts.Add(new Part(new Drawing("left", ExplicitContourTests.Square(false)), new Vector(5, 20)));
var right = new Part(new Drawing("right", ExplicitContourTests.Square(false)), new Vector(36, 20));
if (lockRight)
@@ -119,15 +121,4 @@ public class SafestRouteFallbackTests
plate.Parts.Add(right);
return plate;
}
private static Program Rectangle(double width, double height)
{
var program = new Program();
program.MoveTo(0, 0);
program.LineTo(0, height);
program.LineTo(width, height);
program.LineTo(width, 0);
program.LineTo(0, 0);
return program;
}
}
@@ -1,6 +1,7 @@
using OpenNest.CNC;
using OpenNest.Diagnostics;
using OpenNest.Geometry;
using static OpenNest.Tests.TestHelpers;
namespace OpenNest.Tests.Diagnostics;
@@ -96,19 +97,8 @@ public class OverlapAutoCheckSchedulerTests
private static Plate PlateWithParts()
{
var plate = new Plate();
plate.Parts.Add(Rectangle());
plate.Parts.Add(Rectangle());
plate.Parts.Add(MakeSquarePart("same name"));
plate.Parts.Add(MakeSquarePart("same name"));
return plate;
}
private static Part Rectangle()
{
var program = new Program(Mode.Absolute);
program.Codes.Add(new RapidMove(0, 0));
program.Codes.Add(new LinearMove(4, 0));
program.Codes.Add(new LinearMove(4, 4));
program.Codes.Add(new LinearMove(0, 4));
program.Codes.Add(new LinearMove(0, 0));
return new Part(new Drawing("same name", program));
}
}
@@ -2,6 +2,7 @@ using System.Globalization;
using OpenNest.CNC;
using OpenNest.Diagnostics;
using OpenNest.Geometry;
using static OpenNest.Tests.TestHelpers;
namespace OpenNest.Tests.Diagnostics;
@@ -10,7 +11,7 @@ public class OverlapHoverPagesTests
[Fact]
public void CrowdedCoincidentPairsRemainAccessibleInOrderWithinEveryPageBudget()
{
var parts = Enumerable.Range(1, 12).Select(i => Rectangle($"part-{i}")).ToArray();
var parts = Enumerable.Range(1, 12).Select(i => MakeSquarePart($"part-{i}")).ToArray();
var report = PlateOverlapAnalyzer.Analyze(parts);
var pairs = OverlapPairPresentation.HitTest(report.Pairs.Reverse(), p => p, new Vector(2, 2), 96);
var text = string.Join("\n\n", pairs.Select(p => OverlapPairPresentation.Details(p, Units.Millimeters, CultureInfo.InvariantCulture)));
@@ -35,7 +36,8 @@ public class OverlapHoverPagesTests
public void OneDetailWithLongUnbrokenNamesPagesWithoutDroppingText(int rows, int width)
{
var name = string.Concat(Enumerable.Repeat("veryLongName😀e\u0301", 30));
var pair = Assert.Single(PlateOverlapAnalyzer.Analyze(new[] { Rectangle(name), Rectangle("tail") }).Pairs);
var pair = Assert.Single(PlateOverlapAnalyzer.Analyze(
new[] { MakeSquarePart(name), MakeSquarePart("tail") }).Pairs);
var text = OverlapPairPresentation.Details(pair, Units.Inches, CultureInfo.InvariantCulture);
var pages = OverlapHoverPages.Create(text, 1, rows, width, s => new StringInfo(s).LengthInTextElements);
@@ -116,15 +118,4 @@ public class OverlapHoverPagesTests
pages.MovePage(1);
}
}
private static Part Rectangle(string name)
{
var program = new Program(Mode.Absolute);
program.Codes.Add(new RapidMove(0, 0));
program.Codes.Add(new LinearMove(4, 0));
program.Codes.Add(new LinearMove(4, 4));
program.Codes.Add(new LinearMove(0, 4));
program.Codes.Add(new LinearMove(0, 0));
return new Part(new Drawing(name, program));
}
}
@@ -2,6 +2,7 @@ using System.Globalization;
using OpenNest.CNC;
using OpenNest.Diagnostics;
using OpenNest.Geometry;
using static OpenNest.Tests.TestHelpers;
namespace OpenNest.Tests.Diagnostics;
@@ -12,7 +13,10 @@ public class OverlapPairPresentationTests
[InlineData(Units.Millimeters, "mm")]
public void DetailsUseCapturedNamesSequenceIdsAndSuppliedUnits(Units units, string symbol)
{
var parts = new[] { Rectangle("cutoff"), Rectangle("left"), Rectangle("right") };
var parts = new[]
{
MakeSquarePart("cutoff"), MakeSquarePart("left"), MakeSquarePart("right"),
};
parts[0].BaseDrawing.IsCutOff = true;
var snapshot = PlateOverlapAnalyzer.Capture(parts);
parts[1].BaseDrawing.Name = "changed";
@@ -41,7 +45,8 @@ public class OverlapPairPresentationTests
{
Assert.Equal(6 * factor, OverlapPairPresentation.MarkerHalfSize(dpi));
Assert.Equal(10 * factor, OverlapPairPresentation.HitRadius(dpi));
var pairs = PlateOverlapAnalyzer.Analyze(new[] { Rectangle("a"), Rectangle("b") }).Pairs;
var pairs = PlateOverlapAnalyzer.Analyze(
new[] { MakeSquarePart("a"), MakeSquarePart("b") }).Pairs;
foreach (var zoom in new[] { 0.5, 20.0, 500.0 })
{
Vector ToScreen(Vector world) => new(100 + world.X * zoom, 200 - world.Y * zoom);
@@ -59,20 +64,10 @@ public class OverlapPairPresentationTests
[Fact]
public void CoincidentMarkersReturnEveryPairInIdOrderNotFragmentOrder()
{
var pairs = PlateOverlapAnalyzer.Analyze(new[] { Rectangle("a"), Rectangle("b"), Rectangle("c") }).Pairs;
var pairs = PlateOverlapAnalyzer.Analyze(
new[] { MakeSquarePart("a"), MakeSquarePart("b"), MakeSquarePart("c") }).Pairs;
var hits = OverlapPairPresentation.HitTest(pairs.Reverse(), p => p, new Vector(2, 2), 96);
Assert.Equal(new[] { "1/2", "1/3", "2/3" }, hits.Select(OverlapPairPresentation.Label));
Assert.All(hits, p => Assert.Single(p.Regions)); // One island per coincident pair.
}
private static Part Rectangle(string name)
{
var program = new Program(Mode.Absolute);
program.Codes.Add(new RapidMove(0, 0));
program.Codes.Add(new LinearMove(4, 0));
program.Codes.Add(new LinearMove(4, 4));
program.Codes.Add(new LinearMove(0, 4));
program.Codes.Add(new LinearMove(0, 0));
return new Part(new Drawing(name, program));
}
}
@@ -1,6 +1,7 @@
using OpenNest.CNC;
using OpenNest.Diagnostics;
using OpenNest.Geometry;
using static OpenNest.Tests.TestHelpers;
namespace OpenNest.Tests.Diagnostics;
@@ -51,10 +52,10 @@ public class OverlapReportStateTests
case "rotate": part.Rotate(0.01); break;
case "reorder": (plate.Parts[0], plate.Parts[1]) = (plate.Parts[1], plate.Parts[0]); break;
case "replace-part": plate.Parts[0] = part.CloneAtOffset(new Vector()); break;
case "replace-drawing": plate.Parts[0] = Rectangle(); break;
case "replace-drawing": plate.Parts[0] = MakeSquarePart("same name"); break;
case "placed-program": part.Update(); break;
case "clean-program": part.BaseDrawing.Program = (Program)part.BaseDrawing.Program.Clone(); break;
case "add": plate.Parts.Add(Rectangle()); break;
case "add": plate.Parts.Add(MakeSquarePart("same name")); break;
case "remove": plate.Parts.RemoveAt(0); break;
case "plate":
var replacement = new Plate();
@@ -178,7 +179,7 @@ public class OverlapReportStateTests
public void CutoffSlotsRemainPartOfTheOrderedIdentityStamp()
{
var plate = PlateWithParts();
var cutoff = Rectangle();
var cutoff = MakeSquarePart("same name");
cutoff.BaseDrawing.IsCutOff = true;
plate.Parts.Insert(0, cutoff);
var state = new OverlapReportState();
@@ -269,7 +270,7 @@ public class OverlapReportStateTests
case "placed-program": part.Update(); break;
case "clean-program": part.BaseDrawing.Program = (Program)part.BaseDrawing.Program.Clone(); break;
case "cutoff": part.BaseDrawing.IsCutOff = true; break;
case "replace": plate.Parts[0] = Rectangle(); break;
case "replace": plate.Parts[0] = MakeSquarePart("same name"); break;
case "remove": plate.Parts.RemoveAt(0); break;
case "reorder": (plate.Parts[0], plate.Parts[1]) = (plate.Parts[1], plate.Parts[0]); break;
}
@@ -332,7 +333,7 @@ public class OverlapReportStateTests
var state = new OverlapReportState();
Assert.True(state.TryPublish(state.Begin(plate), plate, Analyze(plate)));
var display = state.DisplayPairs;
plate.Parts.Add(Rectangle());
plate.Parts.Add(MakeSquarePart("same name"));
state.Invalidate(plate); // Same entry point as the controller's collection event.
Assert.Null(state.Report);
Assert.Equal(OverlapCheckStatus.Stale, state.Status);
@@ -345,8 +346,8 @@ public class OverlapReportStateTests
private static Plate PlateWithTwoPairs()
{
var plate = PlateWithParts();
var a = Rectangle();
var b = Rectangle();
var a = MakeSquarePart("same name");
var b = MakeSquarePart("same name");
a.Offset(20, 0);
b.Offset(20, 0);
plate.Parts.Add(a);
@@ -359,19 +360,8 @@ public class OverlapReportStateTests
private static Plate PlateWithParts()
{
var plate = new Plate();
plate.Parts.Add(Rectangle());
plate.Parts.Add(Rectangle());
plate.Parts.Add(MakeSquarePart("same name"));
plate.Parts.Add(MakeSquarePart("same name"));
return plate;
}
private static Part Rectangle()
{
var program = new Program(Mode.Absolute);
program.Codes.Add(new RapidMove(0, 0));
program.Codes.Add(new LinearMove(4, 0));
program.Codes.Add(new LinearMove(4, 4));
program.Codes.Add(new LinearMove(0, 4));
program.Codes.Add(new LinearMove(0, 0));
return new Part(new Drawing("same name", program));
}
}
@@ -2,6 +2,7 @@ using OpenNest.CNC;
using OpenNest.Diagnostics;
using OpenNest.Engine.Jobs;
using OpenNest.Geometry;
using static OpenNest.Tests.TestHelpers;
namespace OpenNest.Tests.Diagnostics;
@@ -66,8 +67,8 @@ public class PartSpacingReviewTests
public void PartInsideCutout_IsHeldToSpacingFromTheHoleEdge(double insert, bool warns)
{
var frame = new Program();
frame.Codes.AddRange(SquareProgram(0, 0, 10).Codes);
frame.Codes.AddRange(SquareProgram(2, 2, 6).Codes);
frame.Codes.AddRange(MakeSquareProgram(0, 0, 10).Codes);
frame.Codes.AddRange(MakeSquareProgram(2, 2, 6).Codes);
var report = Analyze(Spacing, new Part(new Drawing("frame", frame)), Square(insert, insert, 2));
Assert.Equal(warns, Find(report, PostVerificationKind.PartSpacing).Length == 1);
@@ -189,18 +190,7 @@ public class PartSpacingReviewTests
}
private static Part Square(double x, double y, double size, string name = "square") =>
new(new Drawing(name, SquareProgram(0, 0, size)), new Vector(x, y));
private static Program SquareProgram(double x, double y, double size)
{
var program = new Program();
program.MoveTo(x, y);
program.LineTo(x + size, y);
program.LineTo(x + size, y + size);
program.LineTo(x, y + size);
program.LineTo(x, y);
return program;
}
new(new Drawing(name, MakeSquareProgram(0, 0, size)), new Vector(x, y));
private static Program Rectangle(double width, double height)
{
@@ -4,6 +4,7 @@ using OpenNest.CNC.CuttingStrategy;
using OpenNest.Diagnostics;
using OpenNest.Geometry;
using OpenNest.IO;
using static OpenNest.Tests.TestHelpers;
namespace OpenNest.Tests.Diagnostics;
@@ -50,8 +51,8 @@ public class PostVerificationAnalyzerTests
[Fact]
public void Overlap_SubtractsHoles()
{
var program = Square(0, 0, 10);
program.Codes.AddRange(Square(2, 2, 6).Codes);
var program = MakeSquareProgram(0, 0, 10);
program.Codes.AddRange(MakeSquareProgram(2, 2, 6).Codes);
var frame = Part(program);
var report = Analyze(frame, Rectangle(3, 3, 1));
Assert.Empty(Find(report, PostVerificationKind.Overlap));
@@ -91,8 +92,8 @@ public class PostVerificationAnalyzerTests
[Fact]
public void MissingLeadIn_PartialApplySingleLeavesOtherContourUncovered()
{
var program = Square(0, 0, 10);
program.Codes.AddRange(Square(2, 2, 2).Codes);
var program = MakeSquareProgram(0, 0, 10);
program.Codes.AddRange(MakeSquareProgram(2, 2, 2).Codes);
var part = Part(program);
part.ApplySingleLeadIn(Leads(), Vector.Zero,
new Line(Vector.Zero, new Vector(10, 0)), ContourType.External);
@@ -377,7 +378,7 @@ public class PostVerificationAnalyzerTests
public void Rapid_NonzeroInitialLeadRapidIsNotAppliedTwice(Mode mode)
{
var part = Rectangle(100, 50, 4);
var placed = Square(0, 0, 4);
var placed = MakeSquareProgram(0, 0, 4);
placed.Codes.Insert(0, new RapidMove(-2, 0));
placed.Codes[1] = new LinearMove(0, 0) { Layer = LayerType.Leadin };
placed.MoveTo(-2, 2);
@@ -408,7 +409,7 @@ public class PostVerificationAnalyzerTests
var placed = new Program();
placed.MoveTo(-2, 2);
placed.MoveTo(6, 2);
placed.Codes.AddRange(Square(0, 0, 4).Codes);
placed.Codes.AddRange(MakeSquareProgram(0, 0, 4).Codes);
part.RestoreLeadInProgram(placed, false);
Assert.Empty(Find(Analyze(part), PostVerificationKind.RapidCrossing));
}
@@ -472,7 +473,7 @@ public class PostVerificationAnalyzerTests
public void Rapid_ResumedContourBecomesObstacleOnlyAfterItsLastCut()
{
var part = Rectangle(0, 0, 4);
var placed = Square(0, 0, 4);
var placed = MakeSquareProgram(0, 0, 4);
placed.Codes.Insert(3, new RapidMove(4, 4)); // Pause, with no uncut gap.
placed.MoveTo(-2, 2);
placed.MoveTo(6, 2);
@@ -486,7 +487,7 @@ public class PostVerificationAnalyzerTests
public void Rapid_InternalGapDoesNotBecomeAnObstacle(bool staleFlag)
{
var part = Rectangle(0, 0, 4);
var placed = Square(0, 0, 4);
var placed = MakeSquareProgram(0, 0, 4);
placed.Codes.Insert(3, new RapidMove(3.75, 4)); // Leave a gap on the top edge.
placed.MoveTo(-2, 2);
placed.MoveTo(6, 2);
@@ -568,7 +569,7 @@ public class PostVerificationAnalyzerTests
public void Rapid_OpenCutsAcrossAClosedContourAreIncomplete()
{
var part = Rectangle(0, 0, 10);
var placed = Square(0, 0, 10);
var placed = MakeSquareProgram(0, 0, 10);
// Two cuts from edge to edge release the strip between them.
placed.MoveTo(0, 3);
placed.LineTo(10, 3);
@@ -597,7 +598,7 @@ public class PostVerificationAnalyzerTests
{
var part = Rectangle(100, 50, 20);
var sub = CircleWithLead();
var program = Square(0, 0, 20);
var program = MakeSquareProgram(0, 0, 20);
program.Codes.Insert(0, new SubProgramCall { Program = sub, Offset = new Vector(5, 5) });
program.Codes.Insert(1, new SubProgramCall { Program = sub, Offset = new Vector(15, 5) });
program.Mode = Mode.Incremental;
@@ -647,25 +648,14 @@ public class PostVerificationAnalyzerTests
private static Part Part(Program program) => new(new Drawing("fixture", program));
private static Part Rectangle(double x, double y, double size) =>
new(new Drawing("rectangle", Square(0, 0, size)), new Vector(x, y));
private static Program Square(double x, double y, double size)
{
var program = new Program();
program.MoveTo(x, y);
program.LineTo(x + size, y);
program.LineTo(x + size, y + size);
program.LineTo(x, y + size);
program.LineTo(x, y);
return program;
}
new(new Drawing("rectangle", MakeSquareProgram(0, 0, size)), new Vector(x, y));
// A 10x10 part whose 4x4 cutout is cut in two pieces, leaving 0.2 tabs on its left and
// right edges; leads enter and leave each piece from inside the slug.
private static Part TabbedCutout(bool leads)
{
var clean = Square(3, 3, 4);
clean.Codes.AddRange(Square(0, 0, 10).Codes);
var clean = MakeSquareProgram(3, 3, 4);
clean.Codes.AddRange(MakeSquareProgram(0, 0, 10).Codes);
var part = new Part(new Drawing("tabbed", clean));
var program = new Program();
if (leads)
@@ -691,7 +681,7 @@ public class PostVerificationAnalyzerTests
program.LineTo(3, 4.9);
if (leads)
program.Codes.Add(new LinearMove(3.2, 4.7) { Layer = LayerType.Leadout });
program.Codes.AddRange(Square(0, 0, 10).Codes);
program.Codes.AddRange(MakeSquareProgram(0, 0, 10).Codes);
part.RestoreLeadInProgram(program, false);
return part;
}
+5 -15
View File
@@ -5,6 +5,7 @@ using OpenNest.Tests.BestFit;
using Xunit.Abstractions;
using OpenNest.Engine.Fill;
using OpenNest.Geometry;
using static OpenNest.Tests.TestHelpers;
namespace OpenNest.Tests.Fill;
@@ -223,7 +224,7 @@ public class FillExtentsTests
[InlineData(15, 10)] // One rectangle fits, the pair does not.
public void Fill_NoFit_MatchesFrozenLegacy(double length, double width)
{
var drawing = MakeRect(10, 8);
var drawing = MakeRectDrawing(10, 8);
var before = Snapshot(drawing);
var area = new Box(3, 5, length, width);
var expected = new LegacyFillExtents(area, 0.5).Fill(drawing);
@@ -433,7 +434,7 @@ public class FillExtentsTests
internal static Drawing MakeFixture(string shape)
{
if (shape == "rectangle")
return MakeRect(10, 8);
return MakeRectDrawing(10, 8);
if (shape == "triangle")
return MakeRightTriangle(10, 8);
var program = new Program();
@@ -525,17 +526,6 @@ public class FillExtentsTests
return new Drawing("triangle", pgm);
}
private static Drawing MakeRect(double w, double h)
{
var pgm = new Program();
pgm.Codes.Add(new RapidMove(new Vector(0, 0)));
pgm.Codes.Add(new LinearMove(new Vector(w, 0)));
pgm.Codes.Add(new LinearMove(new Vector(w, h)));
pgm.Codes.Add(new LinearMove(new Vector(0, h)));
pgm.Codes.Add(new LinearMove(new Vector(0, 0)));
return new Drawing("rect", pgm);
}
[Fact]
public void Fill_Triangle_ReturnsPartsWithinWorkArea()
{
@@ -566,7 +556,7 @@ public class FillExtentsTests
{
var workArea = new Box(0, 0, 5, 5);
var filler = new FillExtents(workArea, 0.5);
var drawing = MakeRect(10, 10);
var drawing = MakeRectDrawing(10, 10);
var parts = filler.Fill(drawing);
@@ -631,7 +621,7 @@ public class FillExtentsTests
{
var workArea = new Box(0, 0, 120, 60);
var filler = new FillExtents(workArea, 0.5);
var drawing = MakeRect(15, 10);
var drawing = MakeRectDrawing(15, 10);
var parts = filler.Fill(drawing);
@@ -17,35 +17,11 @@ public class PairOverlapDiagnosticTests
/// Creates a 5x3.31 rectangle with rounded corners on the top-right and bottom-right
/// (radius 0.5), similar to "4526 A14 PT13".
/// </summary>
private static Drawing MakeRoundedRect(double w = 5.0, double h = 3.31, double r = 0.5)
{
var pgm = new Program();
pgm.Codes.Add(new RapidMove(new Vector(0, 0)));
// Bottom edge
pgm.Codes.Add(new LinearMove(new Vector(w - r, 0)));
// Bottom-right rounded corner
pgm.Codes.Add(new ArcMove(new Vector(w, r), new Vector(w - r, r), RotationType.CW));
// Right edge
pgm.Codes.Add(new LinearMove(new Vector(w, h - r)));
// Top-right rounded corner
pgm.Codes.Add(new ArcMove(new Vector(w - r, h), new Vector(w - r, h - r), RotationType.CW));
// Top edge
pgm.Codes.Add(new LinearMove(new Vector(0, h)));
// Left edge back to start
pgm.Codes.Add(new LinearMove(new Vector(0, 0)));
return new Drawing("rounded-rect", pgm);
}
private static Drawing MakeRoundedRect(double w = 5.0, double h = 3.31, double r = 0.5) =>
TestHelpers.MakeRoundedRectDrawing(w, h, r);
private static Drawing MakeSimpleRect(double w = 5.0, double h = 3.31)
{
var pgm = new Program();
pgm.Codes.Add(new RapidMove(new Vector(0, 0)));
pgm.Codes.Add(new LinearMove(new Vector(w, 0)));
pgm.Codes.Add(new LinearMove(new Vector(w, h)));
pgm.Codes.Add(new LinearMove(new Vector(0, h)));
pgm.Codes.Add(new LinearMove(new Vector(0, 0)));
return new Drawing("rect", pgm);
}
private static Drawing MakeSimpleRect(double w = 5.0, double h = 3.31) =>
TestHelpers.MakeRectDrawing(w, h);
[Theory]
[InlineData(0)] // 0 degrees
+82
View File
@@ -98,6 +98,88 @@ internal static class TestHelpers
return new Drawing("square", pgm);
}
/// <summary>A full circle of the given radius around (x, y), starting at its right.</summary>
public static Program MakeCircleProgram(double x, double y, double radius)
{
var p = new Program();
p.MoveTo(x + radius, y);
p.ArcTo(x + radius, y, x, y, RotationType.CCW);
return p;
}
/// <summary>A counterclockwise size x size square, lower-left corner at (x, y).</summary>
public static Program MakeSquareProgram(double x, double y, double size)
{
var program = new Program();
program.MoveTo(x, y);
program.LineTo(x + size, y);
program.LineTo(x + size, y + size);
program.LineTo(x, y + size);
program.LineTo(x, y);
return program;
}
/// <summary>A width x height rectangle from the origin, clockwise: up first, then +X.</summary>
public static Program MakeClockwiseRectProgram(double width, double height)
{
var program = new Program();
program.MoveTo(0, 0);
program.LineTo(0, height);
program.LineTo(width, height);
program.LineTo(width, 0);
program.LineTo(0, 0);
return program;
}
/// <summary>A closed contour through the vertices in order, back to the first one.</summary>
public static Program MakeClosedProgram(IEnumerable<Vector> vertices)
{
var p = new Program();
p.MoveTo(vertices.First().X, vertices.First().Y);
foreach (var v in vertices.Skip(1).Append(vertices.First()))
p.LineTo(v.X, v.Y);
return p;
}
/// <summary>
/// Appends a clockwise width x height rectangle that starts and ends at the midpoint of its
/// right edge, where <paramref name="program"/> must already be.
/// </summary>
public static void TraceRectangleFromRightMidpoint(Program program, double width, double height)
{
program.LineTo(width, 0);
program.LineTo(0, 0);
program.LineTo(0, height);
program.LineTo(width, height);
program.LineTo(width, height / 2);
}
/// <summary>A part of a 4 x 4 absolute-mode square drawing with the given name.</summary>
public static Part MakeSquarePart(string name)
{
var program = new Program(Mode.Absolute);
program.Codes.Add(new RapidMove(0, 0));
program.Codes.Add(new LinearMove(4, 0));
program.Codes.Add(new LinearMove(4, 4));
program.Codes.Add(new LinearMove(0, 4));
program.Codes.Add(new LinearMove(0, 0));
return new Part(new Drawing(name, program));
}
/// <summary>A w x h outline whose two right-hand corners are arcs of radius r.</summary>
public static Drawing MakeRoundedRectDrawing(double w, double h, double r)
{
var pgm = new Program();
pgm.Codes.Add(new RapidMove(new Vector(0, 0)));
pgm.Codes.Add(new LinearMove(new Vector(w - r, 0)));
pgm.Codes.Add(new ArcMove(new Vector(w, r), new Vector(w - r, r), RotationType.CW));
pgm.Codes.Add(new LinearMove(new Vector(w, h - r)));
pgm.Codes.Add(new ArcMove(new Vector(w - r, h), new Vector(w - r, h - r), RotationType.CW));
pgm.Codes.Add(new LinearMove(new Vector(0, h)));
pgm.Codes.Add(new LinearMove(new Vector(0, 0)));
return new Drawing("rounded-rect", pgm);
}
public static Drawing MakeFrameDrawing(double w = 20, double h = 6, double t = 0.5)
{
// Thin-walled hollow frame: outer CW, inner cutout CCW (CNC convention).