mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-04 16:01:35 -05:00
style: use one spelling across the codebase
The repo mixed variants such as center/centre and color/colour, so a name could be spelled either way and searches missed half the hits. Standardize on the US spelling, matching .NET's own APIs (Color, Center). Wire values owned by other systems keep their spelling: the material ledger's "Cancelled" hold status and GitHub's "cancelled" job status. No behavior change beyond the spelling of a few status messages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2a128c5a11
commit
4d55e11318
230 files changed
+864
-864
No files matched your search
@@ -66,7 +66,7 @@ Read the relevant contract before changing its behavior:
|
||||
- [Nest file format](docs/nest-file-format.md)
|
||||
- [Irregular stock](docs/irregular-stock.md): outlined sheets, what honors the outline and what refuses it
|
||||
- [Directional slides](docs/geometry/directional-slides.md) and [pair-spacing limits](docs/geometry/pair-spacing.md)
|
||||
- [Lead-in placement](docs/geometry/lead-in-placement.md) (including the planner's eroded pierce for cutouts too small for their lead-in and the stop at a feature's centre line) and [pierce zones](docs/geometry/pierce-zone.md): opt-in `PierceDiameter` clearance, the hole pole fallback and the planner's perimeter scrap fallback
|
||||
- [Lead-in placement](docs/geometry/lead-in-placement.md) (including the planner's eroded pierce for cutouts too small for their lead-in and the stop at a feature's center line) and [pierce zones](docs/geometry/pierce-zone.md): opt-in `PierceDiameter` clearance, the hole pole fallback and the planner's perimeter scrap fallback
|
||||
- [Breakoffs](docs/breakoffs.md): tabbed scrap; `Break`-layer open cuts that are cut but are not material
|
||||
- [Tabbed cutouts](docs/tabbed-cutouts.md): holes cut in pieces around `Tab`-layer spans that are material but never cut; nesting reads material through `NestingMaterial.ForNesting`, so nothing nests in them
|
||||
- [Support tabs](docs/support-tabs.md): the nest's support grid, the center-of-gravity check, and Plan Cutting's tab placement for parts the grid cannot hold level
|
||||
|
||||
@@ -35,7 +35,7 @@ static class NestConsole
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Console.Error.WriteLine("Nesting cancelled; nothing saved.");
|
||||
Console.Error.WriteLine("Nesting canceled; nothing saved.");
|
||||
return 2;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace OpenNest.Breakoffs
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves and applies <see cref="Breakoff"/>s: the joining line becomes open cuts on the
|
||||
/// break layer between tabs, each travelling with the scrap on its left so the cutting
|
||||
/// break layer between tabs, each traveling with the scrap on its left so the cutting
|
||||
/// strategies pierce and lead in from the scrap.
|
||||
/// </summary>
|
||||
public static class BreakoffBuilder
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace OpenNest.Breakoffs
|
||||
|
||||
/// <summary>
|
||||
/// <paramref name="center"/> for tab <paramref name="index"/> limited so the tab stays on the
|
||||
/// joining line and keeps a gap from its neighbours.
|
||||
/// joining line and keeps a gap from its neighbors.
|
||||
/// </summary>
|
||||
public double ClampTabCenter(int index, double center)
|
||||
{
|
||||
|
||||
@@ -53,7 +53,7 @@ internal enum AutomaticEntryKind
|
||||
|
||||
/// <summary>
|
||||
/// Which automatic entries a perimeter tabbed for support offers (see
|
||||
/// <see cref="PreparedContours.WithSupportTab"/>); every other contour offers its whole catalogue.
|
||||
/// <see cref="PreparedContours.WithSupportTab"/>); every other contour offers its whole catalog.
|
||||
/// </summary>
|
||||
internal enum SupportEntries
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ using OpenNest.Geometry;
|
||||
namespace OpenNest.CNC.CuttingPlanning;
|
||||
|
||||
/// <summary>
|
||||
/// Pure deterministic ordering of the automatic entry catalogue toward the next cut:
|
||||
/// Pure deterministic ordering of the automatic entry catalog toward the next cut:
|
||||
/// circles rank by outgoing distance; other contours use facing sides, tier and travel,
|
||||
/// then a stable geometric key. It adds no
|
||||
/// candidates, mutates nothing, runs no lead checks and applies no cap — feasibility
|
||||
@@ -62,12 +62,12 @@ internal static class ContourEntryRanking
|
||||
if (p.Y > maxY) maxY = p.Y;
|
||||
}
|
||||
|
||||
// Facing sides from the target relative to the centre, matching the source plan:
|
||||
// horizontal right when the target is right of centre else left; vertical likewise.
|
||||
var centreX = minX + (maxX - minX) * 0.5;
|
||||
var centreY = minY + (maxY - minY) * 0.5;
|
||||
var facingRight = target != null && target.Value.X > centreX;
|
||||
var facingTop = target != null && target.Value.Y > centreY;
|
||||
// Facing sides from the target relative to the center, matching the source plan:
|
||||
// horizontal right when the target is right of center else left; vertical likewise.
|
||||
var centerX = minX + (maxX - minX) * 0.5;
|
||||
var centerY = minY + (maxY - minY) * 0.5;
|
||||
var facingRight = target != null && target.Value.X > centerX;
|
||||
var facingTop = target != null && target.Value.Y > centerY;
|
||||
|
||||
return candidates
|
||||
.Select(c => (Candidate: c, Score: Score(c, minX, minY, maxX, maxY, facingRight, facingTop, target, arrival)))
|
||||
|
||||
@@ -62,7 +62,7 @@ public enum CuttingCommitStatus
|
||||
Applied,
|
||||
Stale,
|
||||
InvalidInput,
|
||||
Cancelled,
|
||||
Canceled,
|
||||
Failed
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ internal static class CuttingPlanCommit
|
||||
Action<Plate, Part> beforeInstall)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return new(CuttingCommitStatus.Cancelled, "Cancelled before commit.");
|
||||
return new(CuttingCommitStatus.Canceled, "Canceled before commit.");
|
||||
var scope = plans?.ToArray();
|
||||
if (scope == null || scope.Length == 0 || scope.Any(p => p?.Expected == null || p.Order == null))
|
||||
return Invalid(null, "A nonempty set of captured plate plans is required.");
|
||||
@@ -126,7 +126,7 @@ internal static class CuttingPlanCommit
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return new(CuttingCommitStatus.Cancelled, "Cancelled before commit.");
|
||||
return new(CuttingCommitStatus.Canceled, "Canceled before commit.");
|
||||
}
|
||||
if (difference != null)
|
||||
return new(CuttingCommitStatus.Stale, difference, plan.Expected.Plate);
|
||||
@@ -200,7 +200,7 @@ internal static class CuttingPlanCommit
|
||||
|
||||
// Last cancellation point. The boundary below has no await, search or geometry work.
|
||||
if (token.IsCancellationRequested)
|
||||
return new(CuttingCommitStatus.Cancelled, "Cancelled before commit.");
|
||||
return new(CuttingCommitStatus.Canceled, "Canceled before commit.");
|
||||
|
||||
var undo = new List<(Plate Plate, Part[] Order, (Part Part, PartCuttingState State)[] States,
|
||||
(CutOff CutOff, bool FarEdgeFirst, Program Program)[] CutOffs)>();
|
||||
|
||||
@@ -79,7 +79,7 @@ internal static class IncrementalClosureDrift
|
||||
var endPoint = last.EndPoint + correction;
|
||||
if (last is ArcMove arc)
|
||||
{
|
||||
// Keep the arc's centre (relative to its start) equidistant from its moved end.
|
||||
// Keep the arc's center (relative to its start) equidistant from its moved end.
|
||||
var chord = endPoint.DistanceTo(new Vector());
|
||||
if (chord <= Tolerance.Epsilon)
|
||||
return false;
|
||||
|
||||
@@ -180,7 +180,7 @@ public sealed class PreparedContours
|
||||
/// <summary>False when the outside is a display perimeter that is never cut: all contours are holes.</summary>
|
||||
public bool HasCutPerimeter => hasCutPerimeter;
|
||||
|
||||
/// <summary>Resolved pins this preparation could not honour: a tab on an internal contour.</summary>
|
||||
/// <summary>Resolved pins this preparation could not honor: a tab on an internal contour.</summary>
|
||||
public IReadOnlyList<CutPinResolution> RejectedPins { get; } = Array.Empty<CutPinResolution>();
|
||||
|
||||
/// <summary>True when a drawing pin fixes this contour's entry, leads and tab.</summary>
|
||||
@@ -512,7 +512,7 @@ public sealed class PreparedContours
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The uncapped preferred automatic start catalogue for one contour, in preference then
|
||||
/// The uncapped preferred automatic start catalog for one contour, in preference then
|
||||
/// contour-travel order: convex corners of the contour's own winding, then straight-edge
|
||||
/// midpoints, then tangent line/arc joints. Reflex and cusp vertices, collinear
|
||||
/// line/line splits, circles and interior points never appear; each geometric point is
|
||||
@@ -528,12 +528,12 @@ public sealed class PreparedContours
|
||||
if (pinned[contourOrdinal] is { } pin)
|
||||
return new[] { new ContourEntryCandidate(pin, AutomaticEntryKind.Pinned) };
|
||||
if (IsSingleCircle(shape))
|
||||
throw new ArgumentException("Circles have no preferred corners or joints; use the fallback catalogue.");
|
||||
throw new ArgumentException("Circles have no preferred corners or joints; use the fallback catalog.");
|
||||
return SupportFiltered(contourOrdinal, PreferredCandidates(shape, contourOrdinal, token), SupportEntries.Steadying);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The complete uncapped automatic start catalogue: the preferred points of
|
||||
/// The complete uncapped automatic start catalog: the preferred points of
|
||||
/// <see cref="AutomaticEntryCandidates"/> followed by tier-3 fallbacks — native arc
|
||||
/// midpoints, near-convex-corner points on straight edges, the eight compass points of a
|
||||
/// whole circle, and the exact target-facing closest point toward
|
||||
@@ -541,7 +541,7 @@ public sealed class PreparedContours
|
||||
/// A pure-circle contour therefore yields compass points instead of refusing. Every
|
||||
/// fallback passes the same reflex/cusp exclusion and geometric duplicate merge as the
|
||||
/// preferred tier; fallbacks never replace a preferred point at the same geometry. A
|
||||
/// pinned contour's catalogue is its pinned entry alone: nothing may relocate it.
|
||||
/// pinned contour's catalog is its pinned entry alone: nothing may relocate it.
|
||||
/// </summary>
|
||||
internal IReadOnlyList<ContourEntryCandidate> AutomaticEntryCandidatesWithFallbacks(
|
||||
int contourOrdinal,
|
||||
@@ -562,7 +562,7 @@ public sealed class PreparedContours
|
||||
|
||||
/// <summary>
|
||||
/// Whether this preparation tabs its perimeter for support (see <see cref="WithSupportTab"/>):
|
||||
/// its automatic catalogue then keeps to steadying points unless asked for a fallback.
|
||||
/// its automatic catalog then keeps to steadying points unless asked for a fallback.
|
||||
/// </summary>
|
||||
internal bool HasSupportTab => support != null && !IsPinned(PerimeterOrdinal);
|
||||
|
||||
@@ -603,7 +603,7 @@ public sealed class PreparedContours
|
||||
|
||||
/// <summary>Preferred tier: convex corners, straight midpoints, tangent joints.</summary>
|
||||
private List<ContourEntryCandidate> PreferredCandidates(Shape shape, int contourOrdinal, CancellationToken token)
|
||||
=> CataloguePoints(shape, token)
|
||||
=> CatalogPoints(shape, token)
|
||||
.Select(p => new ContourEntryCandidate(Entry(contourOrdinal, p.EntityOrdinal, p.Point), p.Kind, p.CornerReach))
|
||||
.ToList();
|
||||
|
||||
@@ -649,7 +649,7 @@ public sealed class PreparedContours
|
||||
// endpoint and so never lands on the reflex/cusp vertex at the far end.
|
||||
if (lead > 0 && !(count == 1 && shape.Entities[0] is Circle))
|
||||
{
|
||||
foreach (var corner in CataloguePoints(shape, token)
|
||||
foreach (var corner in CatalogPoints(shape, token)
|
||||
.Where(p => p.Kind == AutomaticEntryKind.ConvexCorner))
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
@@ -763,13 +763,13 @@ public sealed class PreparedContours
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The preferred catalogue points for one contour: for each entity, its convex corner
|
||||
/// The preferred catalog points for one contour: for each entity, its convex corner
|
||||
/// (end vertex, classified from the contour's own winding) and its straight-edge midpoint,
|
||||
/// plus tangent line/arc joints. The joints of a corner fillet (see <see cref="FilletReach"/>)
|
||||
/// are that corner's convex corners. Reflex, cusp and collinear-split vertices contribute
|
||||
/// nothing; a single whole circle yields no points at all.
|
||||
/// </summary>
|
||||
private List<(int EntityOrdinal, Vector Point, AutomaticEntryKind Kind, double CornerReach)> CataloguePoints(
|
||||
private List<(int EntityOrdinal, Vector Point, AutomaticEntryKind Kind, double CornerReach)> CatalogPoints(
|
||||
Shape shape, CancellationToken token)
|
||||
{
|
||||
var found = new List<(int, Vector, AutomaticEntryKind, double)>();
|
||||
@@ -787,7 +787,7 @@ public sealed class PreparedContours
|
||||
token.ThrowIfCancellationRequested();
|
||||
var entity = shape.Entities[i];
|
||||
|
||||
// Vertex reached by travelling along entity i (its end point), reported under
|
||||
// Vertex reached by traveling along entity i (its end point), reported under
|
||||
// entity i. The closed contour guarantees every vertex appears exactly once
|
||||
// this way; each is classified from the contour's own winding.
|
||||
var vertex = End(entity);
|
||||
@@ -965,20 +965,20 @@ public sealed class PreparedContours
|
||||
? throw new ArgumentException("Foreign contour ordinal.") : shapes[ordinal];
|
||||
|
||||
/// <summary>
|
||||
/// Representative POINTS per contour ordinal (bounding-box centre) for hole routing
|
||||
/// Representative POINTS per contour ordinal (bounding-box center) for hole routing
|
||||
/// only — the S10 <c>CuttingHoleOrder</c> proxy. The perimeter has no entry; it is the
|
||||
/// route's fixed endpoint, not a stop. These are ordering proxies, never cut points.
|
||||
/// </summary>
|
||||
internal IReadOnlyList<Vector?> HoleCentres(CancellationToken token = default)
|
||||
internal IReadOnlyList<Vector?> HoleCenters(CancellationToken token = default)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
var centres = new Vector?[Count];
|
||||
var centers = new Vector?[Count];
|
||||
for (var contour = 0; contour < PerimeterOrdinal; contour++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
centres[contour] = GetShape(contour).BoundingBox.Center;
|
||||
centers[contour] = GetShape(contour).BoundingBox.Center;
|
||||
}
|
||||
return centres;
|
||||
return centers;
|
||||
}
|
||||
|
||||
/// <summary>The bounding box of contour <paramref name="contourOrdinal"/>, local. A routing hint only.</summary>
|
||||
@@ -1000,7 +1000,7 @@ public sealed class PreparedContours
|
||||
/// which treat a point within <see cref="OpenNest.Math.Tolerance.Epsilon"/> of an endpoint as that endpoint and
|
||||
/// start the contour there. An entry that close to a vertex is that vertex, so the lead-in ends
|
||||
/// where the contour starts: otherwise the span between them (5e-8 in on imported arcs whose
|
||||
/// stored centre sits beside the chord's midpoint) is never cut.
|
||||
/// stored center sits beside the chord's midpoint) is never cut.
|
||||
/// </summary>
|
||||
private static Vector SnapToVertex(Shape shape, int entityOrdinal, Vector point)
|
||||
{
|
||||
|
||||
@@ -343,7 +343,7 @@ public sealed class ReleasedContourState
|
||||
/// contour with its own leads already encloses whatever it releases and counts as one node.
|
||||
/// Open cuts may touch any cut only at their own ends, and must form no loop through those
|
||||
/// nodes or each other: a forest of open paths separates no region of the sheet. Kerf and
|
||||
/// tab strength are not modelled, exactly as for a single open contour.
|
||||
/// tab strength are not modeled, exactly as for a single open contour.
|
||||
/// </summary>
|
||||
private const double JointTolerance = 1e-5;
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ internal static class SeamOvercut
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>The angle travelled around <paramref name="center"/> from one point to another, in [0, 2pi).</summary>
|
||||
/// <summary>The angle traveled around <paramref name="center"/> from one point to another, in [0, 2pi).</summary>
|
||||
private static double Travel(Vector center, Vector from, Vector to, bool clockwise)
|
||||
{
|
||||
var start = System.Math.Atan2(from.Y - center.Y, from.X - center.X);
|
||||
|
||||
@@ -315,7 +315,7 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
EmitContour(result, shape, choice.Point, shape.Entities[choice.EntityOrdinal],
|
||||
choice.ContourOrdinal == perimeterOrdinal ? ContourType.External : null,
|
||||
exactCirclePrograms: true, scrapPierce: choice.Pierce,
|
||||
stopAtCentreLine: contourParameters?[choice.ContourOrdinal] == null);
|
||||
stopAtCenterLine: contourParameters?[choice.ContourOrdinal] == null);
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -442,7 +442,7 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
ContourType? forceType = null,
|
||||
bool exactCirclePrograms = false,
|
||||
Vector? scrapPierce = null,
|
||||
bool stopAtCentreLine = false
|
||||
bool stopAtCenterLine = false
|
||||
)
|
||||
{
|
||||
var contourType = forceType ?? DetectContourType(shape);
|
||||
@@ -474,8 +474,8 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
leadIn = StraightLeadIn(point, holePierce, out normal);
|
||||
else
|
||||
{
|
||||
if (stopAtCentreLine)
|
||||
leadIn = StopAtCentreLine(shape, point, leadIn, normal, winding);
|
||||
if (stopAtCenterLine)
|
||||
leadIn = StopAtCenterLine(shape, point, leadIn, normal, winding);
|
||||
leadIn = ClampLeadInForCircle(leadIn, circle, point, normal,
|
||||
Parameters.GetEffectivePierceClearance());
|
||||
leadIn = ResolvePierceZone(shape, point, leadIn, normal, Parameters.GetPierceZoneRadius(), winding);
|
||||
@@ -529,8 +529,8 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
Parameters.GetEffectivePierceClearance(), out leadInNormal);
|
||||
if (contourType == ContourType.Internal)
|
||||
{
|
||||
if (stopAtCentreLine)
|
||||
leadIn = StopAtCentreLine(shape, point, leadIn, leadInNormal, turn);
|
||||
if (stopAtCenterLine)
|
||||
leadIn = StopAtCenterLine(shape, point, leadIn, leadInNormal, turn);
|
||||
leadIn = ResolvePierceZone(shape, point, leadIn, leadInNormal, Parameters.GetPierceZoneRadius(), turn);
|
||||
}
|
||||
}
|
||||
@@ -1067,10 +1067,10 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
/// Keeps a hole's lead-in pierce <paramref name="pierceRadius"/> clear of the hole wall.
|
||||
/// A lead-in whose pierce already keeps it, or that pierces on the contour itself, is
|
||||
/// returned as given. Otherwise the pierce moves straight in from the wall to the hole's
|
||||
/// centre line, or the lead's own length in when that comes first (see
|
||||
/// <see cref="StopAtCentreLine"/>). A lead that cannot keep the zone there moves onto the
|
||||
/// center line, or the lead's own length in when that comes first (see
|
||||
/// <see cref="StopAtCenterLine"/>). A lead that cannot keep the zone there moves onto the
|
||||
/// line from <paramref name="point"/> toward the nearest point that keeps it (see
|
||||
/// <see cref="ErodedPierce"/>) or else the hole's pole of inaccessibility (the centre of a
|
||||
/// <see cref="ErodedPierce"/>) or else the hole's pole of inaccessibility (the center of a
|
||||
/// round hole), as close to the contour as the zone allows. Either way the lead becomes
|
||||
/// a straight line from there, generated with <paramref name="normal"/>. A hole too small
|
||||
/// for the zone is pierced at the pole itself, its best clearance; the lead validator and
|
||||
@@ -1094,7 +1094,7 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
if (Clear(configured))
|
||||
return leadIn;
|
||||
|
||||
var depth = CentreLineDepth(hole, point, normal, configured.DistanceTo(point));
|
||||
var depth = CenterLineDepth(hole, point, normal, configured.DistanceTo(point));
|
||||
if (depth > Math.Tolerance.Epsilon && Clear(point + Inward(normal) * depth))
|
||||
return new LineLeadIn { Length = depth, ApproachAngle = 90 };
|
||||
|
||||
@@ -1104,7 +1104,7 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
return leadIn;
|
||||
|
||||
// The pole of a letter can be a stroke away: head for the stroke beside the entry, on
|
||||
// its centre line when the lead reaches that far.
|
||||
// its center line when the lead reaches that far.
|
||||
var reach = configured.DistanceTo(point);
|
||||
var toward = pole;
|
||||
if (circle == null && ErodedPierce.Find(hole, System.Math.Max(pierceRadius, reach), new[] { point }, 1)
|
||||
@@ -1148,14 +1148,14 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
|
||||
/// <summary>
|
||||
/// Keeps a cutout's lead-in out of the cutout's far half. A lead longer than the cutout is
|
||||
/// deep at its entry, measured straight in from the wall to the centre line where the walls
|
||||
/// meet as the cutout erodes, reaches past the centre line or runs along a narrow stroke, as
|
||||
/// deep at its entry, measured straight in from the wall to the center line where the walls
|
||||
/// meet as the cutout erodes, reaches past the center line or runs along a narrow stroke, as
|
||||
/// does one whose own line gets closer to a wall again. It becomes a straight one, generated
|
||||
/// with <paramref name="normal"/>, from that centre line (the centre of a round hole) or
|
||||
/// with <paramref name="normal"/>, from that center line (the center of a round hole) or
|
||||
/// from the lead's own length in when that comes first. Any other lead keeps its shape, and
|
||||
/// one that leaves a cutout other than a round hole is left for the lead check to refuse.
|
||||
/// </summary>
|
||||
public static LeadIn StopAtCentreLine(Shape hole, Vector point, LeadIn leadIn, double normal, RotationType turn)
|
||||
public static LeadIn StopAtCenterLine(Shape hole, Vector point, LeadIn leadIn, double normal, RotationType turn)
|
||||
{
|
||||
if (leadIn is NoLeadIn || !hole.IsClosed())
|
||||
return leadIn;
|
||||
@@ -1172,42 +1172,42 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
if (inside && Clearance(hole, pierce) >= along.X * inward.X + along.Y * inward.Y - Math.Tolerance.Epsilon)
|
||||
return leadIn;
|
||||
|
||||
var depth = CentreLineDepth(hole, point, normal, length);
|
||||
var depth = CenterLineDepth(hole, point, normal, length);
|
||||
if (depth <= Math.Tolerance.Epsilon
|
||||
|| inside && depth >= length - Math.Tolerance.Epsilon && !PeaksBeforeItsEnd(hole, point, along))
|
||||
return leadIn;
|
||||
if (circle == null)
|
||||
{
|
||||
var polygon = hole.ToPolygonWithTolerance(Math.Tolerance.ChainTolerance);
|
||||
for (var i = 1; i <= CentreLineSteps; i++)
|
||||
if (!polygon.ContainsPoint(point + along * ((double)i / CentreLineSteps)))
|
||||
for (var i = 1; i <= CenterLineSteps; i++)
|
||||
if (!polygon.ContainsPoint(point + along * ((double)i / CenterLineSteps)))
|
||||
return leadIn;
|
||||
}
|
||||
return new LineLeadIn { Length = depth, ApproachAngle = 90 };
|
||||
}
|
||||
|
||||
// How far a straight line in from the wall runs, up to maxDepth, before it reaches the
|
||||
// centre line: the distance to the wall grows at a steady rate until a second wall is as
|
||||
// center line: the distance to the wall grows at a steady rate until a second wall is as
|
||||
// near, then levels off along a stroke or falls.
|
||||
private static double CentreLineDepth(Shape hole, Vector point, double normal, double maxDepth)
|
||||
private static double CenterLineDepth(Shape hole, Vector point, double normal, double maxDepth)
|
||||
{
|
||||
if (hole.Entities.Count == 1 && hole.Entities[0] is Circle circle)
|
||||
return System.Math.Min(maxDepth, circle.Radius);
|
||||
|
||||
var inward = Inward(normal);
|
||||
var step = maxDepth / CentreLineSteps;
|
||||
var step = maxDepth / CenterLineSteps;
|
||||
var rate = Clearance(hole, point + inward * step) / step;
|
||||
// A corner's curved lead keeps an edge's normal, which runs along the other edge.
|
||||
if (rate < MinimumCentreLineRate)
|
||||
if (rate < MinimumCenterLineRate)
|
||||
return maxDepth;
|
||||
bool Rising(double depth) => Clearance(hole, point + inward * (depth + step / CentreLineSteps))
|
||||
- Clearance(hole, point + inward * depth) >= rate / 2 * (step / CentreLineSteps);
|
||||
for (var i = 1; i < CentreLineSteps; i++)
|
||||
bool Rising(double depth) => Clearance(hole, point + inward * (depth + step / CenterLineSteps))
|
||||
- Clearance(hole, point + inward * depth) >= rate / 2 * (step / CenterLineSteps);
|
||||
for (var i = 1; i < CenterLineSteps; i++)
|
||||
{
|
||||
if (Rising(step * i))
|
||||
continue;
|
||||
double low = step * (i - 1), high = step * i;
|
||||
for (var refine = 0; refine < CentreLineRefinements; refine++)
|
||||
for (var refine = 0; refine < CenterLineRefinements; refine++)
|
||||
{
|
||||
var middle = (low + high) / 2;
|
||||
if (Rising(middle))
|
||||
@@ -1223,9 +1223,9 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
private static bool PeaksBeforeItsEnd(Shape hole, Vector point, Vector along)
|
||||
{
|
||||
var peak = 0.0;
|
||||
for (var i = 1; i <= CentreLineSteps; i++)
|
||||
for (var i = 1; i <= CenterLineSteps; i++)
|
||||
{
|
||||
var clearance = Clearance(hole, point + along * ((double)i / CentreLineSteps));
|
||||
var clearance = Clearance(hole, point + along * ((double)i / CenterLineSteps));
|
||||
if (clearance < peak - Math.Tolerance.Epsilon)
|
||||
return true;
|
||||
peak = System.Math.Max(peak, clearance);
|
||||
@@ -1250,10 +1250,10 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
// Bounds the pole search on long, thin holes; the result is checked before use.
|
||||
private const int PoleSearchCells = 4096;
|
||||
|
||||
// Samples along a lead when looking for the centre line, then the steps narrowing in on it.
|
||||
private const int CentreLineSteps = 64;
|
||||
private const int CentreLineRefinements = 16;
|
||||
private const double MinimumCentreLineRate = 0.01;
|
||||
// Samples along a lead when looking for the center line, then the steps narrowing in on it.
|
||||
private const int CenterLineSteps = 64;
|
||||
private const int CenterLineRefinements = 16;
|
||||
private const double MinimumCenterLineRate = 0.01;
|
||||
|
||||
private LeadIn SelectLeadIn(ContourType contourType)
|
||||
{
|
||||
@@ -1451,7 +1451,7 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Where a break cut (travelling with its scrap on the left) is entered: the end with more
|
||||
/// Where a break cut (traveling with its scrap on the left) is entered: the end with more
|
||||
/// scrap before <paramref name="contours"/>, the unit direction into the scrap there, and
|
||||
/// whether that is the cut's far end.
|
||||
/// </summary>
|
||||
|
||||
@@ -20,9 +20,9 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
/// The pierce search for cutouts too small for their configured lead-in (text, slots, small
|
||||
/// holes): erode the cutout inward, as a laser kerf offset would, and pierce in what is left.
|
||||
/// The erosion goes as deep as the caller asks when the cutout has room for it, else almost
|
||||
/// to its pole of inaccessibility; a round hole is pierced at its centre. Each target, an entry the planner ranked, gets the
|
||||
/// to its pole of inaccessibility; a round hole is pierced at its center. Each target, an entry the planner ranked, gets the
|
||||
/// nearest point of the deepest erosion that still reaches the wall beside it, stepping down to
|
||||
/// half that depth: a letter stroke is pierced on its own centre line next to the entry, and
|
||||
/// half that depth: a letter stroke is pierced on its own center line next to the entry, and
|
||||
/// only a target with no stroke beside it falls back to the deepest place. The lead then runs
|
||||
/// straight from the pierce to the wall.
|
||||
/// </summary>
|
||||
@@ -30,7 +30,7 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// The share of a cutout's inscribed radius the erosion keeps when the full clearance does
|
||||
/// not fit: deep enough to stay near the centre line, with room for every place the
|
||||
/// not fit: deep enough to stay near the center line, with room for every place the
|
||||
/// cutout is about as wide as its widest.
|
||||
/// </summary>
|
||||
public const double DepthFraction = 0.9;
|
||||
@@ -86,7 +86,7 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
return new(targets.Take(max).Select(_ => pole).ToArray(), inscribed);
|
||||
|
||||
// Each target gets the deepest level that reaches the wall beside it: a stroke
|
||||
// narrower than the cutout's widest place is still pierced on its own centre line, near
|
||||
// narrower than the cutout's widest place is still pierced on its own center line, near
|
||||
// the entry, rather than only at the widest place.
|
||||
var pierces = new List<Vector>();
|
||||
var shallowest = depth;
|
||||
|
||||
@@ -253,7 +253,7 @@ namespace OpenNest
|
||||
/// True when <paramref name="part"/>, the placed part of <paramref name="definition"/>, cuts
|
||||
/// one continuous straight line on the definition's axis across the whole sheet, so it
|
||||
/// splits the sheet in two. Anything else (gaps around parts, limits short of an edge, a
|
||||
/// line on the sheet edge, a stale or unrecognised program) does not separate the sheet.
|
||||
/// line on the sheet edge, a stale or unrecognized program) does not separate the sheet.
|
||||
/// </summary>
|
||||
internal static bool SeparatesSheet(Part part, CutOff definition, Box sheet)
|
||||
{
|
||||
@@ -347,7 +347,7 @@ namespace OpenNest
|
||||
}
|
||||
|
||||
// An outlined sheet is cut only where the line crosses material: each crossing is a
|
||||
// span of its own, started at the material's edge and overtravelled past the far
|
||||
// span of its own, started at the material's edge and overtraveled past the far
|
||||
// one, within the user's limits.
|
||||
var spans = plate.Outline == null
|
||||
? new List<(double Start, double End)> { (lineStart, lineEnd) }
|
||||
|
||||
@@ -61,8 +61,8 @@ namespace OpenNest
|
||||
/// <summary>
|
||||
/// Trims and extends cut-offs against the cut-offs that cross them, by changing only their
|
||||
/// start and end limits and their gaps (see docs/automatic-scrap-cutoffs.md, "Trim and
|
||||
/// extend"). A trim stops on the crossing line's centre, inside its kerf; trimming a piece
|
||||
/// between two crossing lines leaves a gap from one centre to the other. An extend closes the
|
||||
/// extend"). A trim stops on the crossing line's center, inside its kerf; trimming a piece
|
||||
/// between two crossing lines leaves a gap from one center to the other. An extend closes the
|
||||
/// gap at the picked end, or runs the line's end to the next crossing line beyond it, or to
|
||||
/// the sheet edge, which clears the limit.
|
||||
/// </summary>
|
||||
|
||||
@@ -48,13 +48,13 @@ internal static class OverlapMarker
|
||||
point = default;
|
||||
var minY = outline.Min(vertex => vertex.Y);
|
||||
var maxY = outline.Max(vertex => vertex.Y);
|
||||
var centre = (minY + maxY) / 2;
|
||||
var center = (minY + maxY) / 2;
|
||||
double below = minY, above = maxY;
|
||||
foreach (var vertex in outline.Concat(holes.SelectMany(hole => hole)))
|
||||
{
|
||||
if (vertex.Y <= centre && vertex.Y > below)
|
||||
if (vertex.Y <= center && vertex.Y > below)
|
||||
below = vertex.Y;
|
||||
else if (vertex.Y > centre && vertex.Y < above)
|
||||
else if (vertex.Y > center && vertex.Y < above)
|
||||
above = vertex.Y;
|
||||
}
|
||||
var y = (below + above) / 2;
|
||||
|
||||
@@ -348,10 +348,10 @@ public static class PlateOverlapAnalyzer
|
||||
new Vector(zone.Right, zone.Top), new Vector(zone.Left, zone.Top),
|
||||
new Vector(zone.Left, zone.Bottom), // Regions repeat their closing vertex exactly.
|
||||
};
|
||||
var centre = new Vector((zone.Left + zone.Right) / 2, (zone.Bottom + zone.Top) / 2);
|
||||
var center = new Vector((zone.Left + zone.Right) / 2, (zone.Bottom + zone.Top) / 2);
|
||||
return new PlateOverlapPair(part.Input.Id, ClampFindingSlot.IdFor(clamp.Ordinal), part.Input.Name,
|
||||
$"Clamp '{clamp.Id}'", new List<PlateOverlapRegion> { new(corners, zone.Length * zone.Width) }, centre,
|
||||
centre);
|
||||
$"Clamp '{clamp.Id}'", new List<PlateOverlapRegion> { new(corners, zone.Length * zone.Width) }, center,
|
||||
center);
|
||||
}
|
||||
|
||||
private static Polygon RectanglePolygon(Box box, Vector offset)
|
||||
|
||||
@@ -95,7 +95,7 @@ public sealed class PlateOverlapPair
|
||||
/// its pole of inaccessibility, or an interior scan-line point when a thin island defeats
|
||||
/// the bounded pole search. Markers, hit testing and inspection rings use this point.
|
||||
/// A tolerance-free tiny-clamp finding (a clamp narrower than the chord tolerance) keeps
|
||||
/// its conservative whole-clamp region, so its marker is the clamp's centre, within half
|
||||
/// its conservative whole-clamp region, so its marker is the clamp's center, within half
|
||||
/// the clamp's size of the material. Returned by value.
|
||||
/// </summary>
|
||||
public Vector Marker { get; }
|
||||
|
||||
@@ -49,7 +49,7 @@ internal static class PostVerificationGeometry
|
||||
/// <summary>
|
||||
/// The relative rounding allowance of native line/circle intersections. Their discriminant
|
||||
/// b^2 - 4ac cancels: its absolute error stays below about 30 units of roundoff times
|
||||
/// |d1|^2 (|d2|^2 + r^2) (d1 the line, d2 from the circle's centre to the line's start), so a
|
||||
/// |d1|^2 (|d2|^2 + r^2) (d1 the line, d2 from the circle's center to the line's start), so a
|
||||
/// spurious root can lie at most about 1e-7 times the span of both extents beyond the true
|
||||
/// distance between line and circle (about 2e-9 is seen in practice). A long lead beside a
|
||||
/// small circle needs the full check; a short lead beside a nearby part does not.
|
||||
@@ -473,11 +473,11 @@ internal static class PostVerificationGeometry
|
||||
point.X - Center.Value.X)) <= System.Math.Abs(Sweep) + Epsilon / Radius
|
||||
|| point.DistanceTo(Start) <= Epsilon || point.DistanceTo(End) <= Epsilon;
|
||||
|
||||
// How far past r^2 a squared distance from the centre still counts as touching the arc.
|
||||
// How far past r^2 a squared distance from the center still counts as touching the arc.
|
||||
// For small arcs this band reaches well beyond the radius (sqrt(1e-8) = 1e-4 as r -> 0).
|
||||
private double ContactSlack => Epsilon * System.Math.Max(1, Radius * 2);
|
||||
|
||||
// The largest distance from the centre that ContactAfterStart can count as contact.
|
||||
// The largest distance from the center that ContactAfterStart can count as contact.
|
||||
private double ContactReach => System.Math.Sqrt(Radius * Radius + ContactSlack);
|
||||
|
||||
internal bool ContactAfterStart(Vector origin, Vector direction, double length,
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace OpenNest.Geometry
|
||||
/// <summary>
|
||||
/// Polygon overlap test with hole subtraction, used by engine and validation per-pair
|
||||
/// checks. It was written as the reference for a GPU kernel; that port is on hold in
|
||||
/// favour of Clipper2, starting with the material-overlap diagnostic. Engine callers
|
||||
/// favor of Clipper2, starting with the material-overlap diagnostic. Engine callers
|
||||
/// stay on this kernel until a Clipper2 near-contact differential and the layout gate
|
||||
/// pass.
|
||||
/// <para>
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace OpenNest.Geometry
|
||||
/// <summary>
|
||||
/// Aligns <paramref name="newProgram"/> onto <paramref name="oldProgram"/>.
|
||||
/// Both programs are treated as read-only. Throws
|
||||
/// <see cref="OperationCanceledException"/> only when cancelled before any
|
||||
/// <see cref="OperationCanceledException"/> only when canceled before any
|
||||
/// measurable work; work limits reached mid-run are reported as reasons on
|
||||
/// the result instead.
|
||||
/// </summary>
|
||||
|
||||
@@ -69,7 +69,7 @@ public static class KeepOutOverlap
|
||||
/// <summary>
|
||||
/// True when the material (perimeter minus holes) shares positive area with the open
|
||||
/// rectangle: some boundary edge runs through its interior, or its interior lies wholly
|
||||
/// inside the material (tested at its centre).
|
||||
/// inside the material (tested at its center).
|
||||
/// </summary>
|
||||
public static bool MaterialEnters(Polygon perimeter, IReadOnlyList<Polygon> holes, Box box)
|
||||
{
|
||||
@@ -80,12 +80,12 @@ public static class KeepOutOverlap
|
||||
if (RingEnters(hole, box))
|
||||
return true;
|
||||
|
||||
var centre = new Vector((box.Left + box.Right) / 2, (box.Bottom + box.Top) / 2);
|
||||
if (!Contains(perimeter, centre))
|
||||
var center = new Vector((box.Left + box.Right) / 2, (box.Bottom + box.Top) / 2);
|
||||
if (!Contains(perimeter, center))
|
||||
return false;
|
||||
if (holes != null)
|
||||
foreach (var hole in holes)
|
||||
if (Contains(hole, centre))
|
||||
if (Contains(hole, center))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ public static class MaximalRectangles
|
||||
/// <remarks>
|
||||
/// The grid has a line through every vertex coordinate plus <paramref name="divisions"/>
|
||||
/// evenly spaced lines per axis. A cell is free only when no edge passes through its
|
||||
/// interior and its centre is inside the region, so results are exact for regions whose
|
||||
/// interior and its center is inside the region, so results are exact for regions whose
|
||||
/// edges are all horizontal or vertical. Slanted and curved edges are followed as a
|
||||
/// staircase: results stay inside, but can fall short of the true maximum by up to about
|
||||
/// one cell on each side. Rotate the region to search other rectangle angles.
|
||||
@@ -64,9 +64,9 @@ public static class MaximalRectangles
|
||||
var crossings = new List<double>();
|
||||
for (var r = 0; r < rows; r++)
|
||||
{
|
||||
// Even-odd scan along the row's centre line. No vertex lies on it, and an edge that
|
||||
// Even-odd scan along the row's center line. No vertex lies on it, and an edge that
|
||||
// meets it strictly inside a cell has already marked that cell crossed, so each
|
||||
// uncrossed cell is on the same side as its centre.
|
||||
// uncrossed cell is on the same side as its center.
|
||||
var y = (ys[r] + ys[r + 1]) / 2;
|
||||
crossings.Clear();
|
||||
foreach (var path in paths)
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace OpenNest.Geometry
|
||||
/// <summary>
|
||||
/// Bounded search for callers that cannot afford refinement along a long thin shape:
|
||||
/// stops after about <paramref name="maxCells"/> distance evaluations and returns the best
|
||||
/// cell centre so far. When the starting grid would exceed a quarter of the budget it is
|
||||
/// cell center so far. When the starting grid would exceed a quarter of the budget it is
|
||||
/// coarsened so the whole shape is sampled first. The result can lie outside a shape
|
||||
/// thinner than the remaining refinement, so callers must check it.
|
||||
/// </summary>
|
||||
@@ -157,7 +157,7 @@ namespace OpenNest.Geometry
|
||||
|
||||
halfCell = cell.HalfSize / 2;
|
||||
|
||||
// Far from the origin a half-step may no longer move the centre; such a cell
|
||||
// Far from the origin a half-step may no longer move the center; such a cell
|
||||
// cannot be split, and its identical children would multiply without end.
|
||||
if (cell.X - halfCell == cell.X || cell.X + halfCell == cell.X
|
||||
|| cell.Y - halfCell == cell.Y || cell.Y + halfCell == cell.Y)
|
||||
|
||||
@@ -37,8 +37,8 @@ namespace OpenNest.Geometry
|
||||
internal const double IncidenceTolerance = 1e-7;
|
||||
|
||||
// Drawing data fixes arc centers to finite precision, so a tangent joint's arc end
|
||||
// can sit a little past its neighbour's end. A contact this close to a joint takes
|
||||
// the joint's tangents; otherwise the neighbour reads as a second boundary there.
|
||||
// can sit a little past its neighbor's end. A contact this close to a joint takes
|
||||
// the joint's tangents; otherwise the neighbor reads as a second boundary there.
|
||||
internal const double JointTolerance = 1e-6;
|
||||
|
||||
// Loops are recovered with ShapeBuilder's chaining tolerance: a source contour that
|
||||
|
||||
@@ -360,19 +360,19 @@ namespace OpenNest.Support
|
||||
yield break;
|
||||
|
||||
var step = perimeter / TabSamples;
|
||||
var travelled = 0.0;
|
||||
var traveled = 0.0;
|
||||
var next = step;
|
||||
for (var i = 0; i < outline.Count; i++)
|
||||
{
|
||||
var a = outline[i];
|
||||
var b = outline[(i + 1) % outline.Count];
|
||||
var length = a.DistanceTo(b);
|
||||
while (length > 0 && next < travelled + length)
|
||||
while (length > 0 && next < traveled + length)
|
||||
{
|
||||
yield return a + (b - a) * ((next - travelled) / length);
|
||||
yield return a + (b - a) * ((next - traveled) / length);
|
||||
next += step;
|
||||
}
|
||||
travelled += length;
|
||||
traveled += length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace OpenNest.TabbedCutouts
|
||||
public double Length { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// One open cut per span of the contour between tabs, in contour order, travelling
|
||||
/// One open cut per span of the contour between tabs, in contour order, traveling
|
||||
/// counterclockwise (the slug on the left).
|
||||
/// </summary>
|
||||
public IReadOnlyList<IReadOnlyList<Entity>> Pieces { get; internal set; }
|
||||
@@ -65,7 +65,7 @@ namespace OpenNest.TabbedCutouts
|
||||
|
||||
/// <summary>
|
||||
/// <paramref name="center"/> for tab <paramref name="index"/> limited so the tab keeps a
|
||||
/// gap from its neighbours around the contour and does not pass the contour's start
|
||||
/// gap from its neighbors around the contour and does not pass the contour's start
|
||||
/// (which would reorder the tabs).
|
||||
/// </summary>
|
||||
public double ClampTabCenter(int index, double center)
|
||||
|
||||
@@ -17,7 +17,7 @@ public interface IBrowsePage
|
||||
/// <summary>
|
||||
/// Browse state shared by the paged server lists: the requested search, sort and page, and the
|
||||
/// last page applied. Every change issues one bounded server query. A response that a later
|
||||
/// request superseded is discarded (and the earlier request is cancelled), so an older result
|
||||
/// request superseded is discarded (and the earlier request is canceled), so an older result
|
||||
/// can never replace a newer one. Call from one thread (the UI thread).
|
||||
/// </summary>
|
||||
public abstract class BrowseSession<TPage, TSort> : IDisposable
|
||||
@@ -130,7 +130,7 @@ public abstract class BrowseSession<TPage, TSort> : IDisposable
|
||||
private async Task<bool> RunAsync()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
// Supersede before cancelling: a request that completes synchronously when cancelled
|
||||
// Supersede before canceling: a request that completes synchronously when canceled
|
||||
// must already see itself as superseded. Each source is released by its own request.
|
||||
var previous = _pending;
|
||||
using var cancellation = new CancellationTokenSource();
|
||||
|
||||
@@ -50,7 +50,7 @@ public sealed class LedgerHoldSummary
|
||||
|
||||
public int OpenHolds { get; }
|
||||
|
||||
/// <summary>Holds in any other status: released, closed, cancelled, exception or unknown.</summary>
|
||||
/// <summary>Holds in any other status: released, closed, canceled, exception or unknown.</summary>
|
||||
public int ClosedHolds { get; }
|
||||
|
||||
/// <summary>Sheets without an open hold.</summary>
|
||||
|
||||
@@ -26,13 +26,13 @@ public static class LedgerHoldStatuses
|
||||
public const string AwaitingCloseout = "AwaitingCloseout";
|
||||
public const string ClosedConsumed = "ClosedConsumed";
|
||||
public const string ClosedReturned = "ClosedReturned";
|
||||
public const string Cancelled = "Cancelled";
|
||||
public const string Canceled = "Cancelled";
|
||||
public const string Exception = "Exception";
|
||||
|
||||
/// <summary>
|
||||
/// True for holds that still keep a sheet for the nest and need closing out: the statuses ML
|
||||
/// counts against free stock. Released means the hold was let go, not sent to cutting, so it
|
||||
/// is not open; nor are closed, cancelled, exception and unknown statuses.
|
||||
/// is not open; nor are closed, canceled, exception and unknown statuses.
|
||||
/// </summary>
|
||||
public static bool IsOpen(string? status) =>
|
||||
string.Equals(status, Proposed, StringComparison.OrdinalIgnoreCase)
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace OpenNest.Data.MaterialLedger;
|
||||
|
||||
/// <summary>
|
||||
/// Gives the plates Auto Nest committed on ledger stock their item codes. Runs only after the
|
||||
/// proposal was validated, kept and committed, so a rejected or cancelled one never changes
|
||||
/// proposal was validated, kept and committed, so a rejected or canceled one never changes
|
||||
/// codes. Codes are hand-off data for MaterialLedger holds, not inventory accounting.
|
||||
/// </summary>
|
||||
public static class LedgerStockAssignment
|
||||
|
||||
@@ -49,7 +49,7 @@ public sealed class NestDetailsSession : IDisposable
|
||||
public async Task<bool> LoadAsync(Guid id)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
// Supersede before cancelling: a load that completes synchronously when cancelled
|
||||
// Supersede before canceling: a load that completes synchronously when canceled
|
||||
// must already see itself as superseded. Each source is released by its own load.
|
||||
var cancellation = Supersede();
|
||||
var generation = _generation;
|
||||
|
||||
@@ -148,7 +148,7 @@ public sealed class SetReleaseResult
|
||||
/// matches its record, units the new nest does not use, an insert refusal) creates
|
||||
/// nothing, and so does a server too old to record drawings released on their own, which is
|
||||
/// checked first. Uploads then run in order; the first failure stops them, and cancellation is
|
||||
/// honoured between uploads, never during one. Nests already uploaded stay as ordinary
|
||||
/// honored between uploads, never during one. Nests already uploaded stay as ordinary
|
||||
/// nests and are listed in the result; the release is posted only when every chosen group
|
||||
/// was created. Nothing is rolled back.
|
||||
/// </para>
|
||||
|
||||
@@ -38,7 +38,7 @@ public class ChangeScopeTests
|
||||
|
||||
using (ChangeScope.Nested(root))
|
||||
{
|
||||
// Left incomplete, as when the nested edit throws or is cancelled.
|
||||
// Left incomplete, as when the nested edit throws or is canceled.
|
||||
}
|
||||
|
||||
root.MarkChanged();
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace OpenNest.Undo
|
||||
/// cut-offs, and the drawings with their required quantities. Two equal fingerprints do not
|
||||
/// prove two nests equal; a differing one means the nest changed since it was taken. Objects
|
||||
/// count by reference, so compare fingerprints of one live nest, never across a restore.
|
||||
/// Derived values (nested counts, colours, the modified date) are left out.
|
||||
/// Derived values (nested counts, colors, the modified date) are left out.
|
||||
/// </summary>
|
||||
internal static class NestFingerprint
|
||||
{
|
||||
|
||||
@@ -143,7 +143,7 @@ public class ClampZonePushTests
|
||||
var upper = AddSquare(plate, 4, 5, 2);
|
||||
var lower = AddSquare(plate, 4, 3.1, 2);
|
||||
var result = Expander.Expand(new List<Part> { upper, lower }, plate);
|
||||
Assert.False(result.Cancelled);
|
||||
Assert.False(result.Canceled);
|
||||
Assert.True(lower.BoundingBox.Bottom >= 3 - 1e-6,
|
||||
$"Achieved={result.AchievedSpacing}, lower at {lower.Location.Y}");
|
||||
}
|
||||
@@ -159,7 +159,7 @@ public class ClampZonePushTests
|
||||
|
||||
var result = Expander.Expand(new List<Part> { upper, lower }, plate);
|
||||
|
||||
Assert.False(result.Cancelled);
|
||||
Assert.False(result.Canceled);
|
||||
AssertClear(plate, upper, lower);
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ public class ClampZonePushTests
|
||||
|
||||
var result = Expander.Expand(new List<Part> { straddler, clear }, plate);
|
||||
|
||||
Assert.False(result.Cancelled);
|
||||
Assert.False(result.Canceled);
|
||||
// The clear member must not be driven into any clamp while its partner is stuck.
|
||||
AssertClear(plate, clear);
|
||||
}
|
||||
@@ -189,7 +189,7 @@ public class ClampZonePushTests
|
||||
|
||||
var result = Expander.Expand(new List<Part> { a, b, c }, plate);
|
||||
|
||||
Assert.False(result.Cancelled);
|
||||
Assert.False(result.Canceled);
|
||||
// Whatever the achieved spacing or reported violations, no clear member may
|
||||
// end inside a clamp keep-out.
|
||||
AssertClear(plate, a, b, c);
|
||||
|
||||
@@ -56,7 +56,7 @@ public class ExpanderSpecRegressionTests
|
||||
var result = Expander.Separate(new List<Part> { a, b }, plate, spacing: 3);
|
||||
Assert.False(result.Converged);
|
||||
Assert.True(result.Positions[1].X + 2 <= 12 + 1e-6,
|
||||
$"native disk crossed third wall: centre={result.Positions[1].X:R}");
|
||||
$"native disk crossed third wall: center={result.Positions[1].X:R}");
|
||||
Assert.Equal(9, b.Location.X);
|
||||
Assert.Equal(12, wall.Location.X);
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ public class ExpanderTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Expand_CancelledBeforeRun_LeavesEverythingInPlace()
|
||||
public void Expand_CanceledBeforeRun_LeavesEverythingInPlace()
|
||||
{
|
||||
var plate = MakePlate(24, 24);
|
||||
var a = AddSquare(plate, 6, 10);
|
||||
@@ -187,7 +187,7 @@ public class ExpanderTests
|
||||
token: cts.Token
|
||||
);
|
||||
|
||||
Assert.True(result.Cancelled);
|
||||
Assert.True(result.Canceled);
|
||||
Assert.Equal(6, a.Location.X, 6);
|
||||
Assert.Equal(14, b.Location.X, 6);
|
||||
}
|
||||
@@ -529,7 +529,7 @@ public class ExpanderTests
|
||||
|
||||
var result = Expander.Expand(new List<Part> { insert, other }, plate,
|
||||
new Expander.Options { MaxSpacing = 1 });
|
||||
Assert.False(result.Cancelled);
|
||||
Assert.False(result.Canceled);
|
||||
Assert.True(result.AchievedSpacing >= 0.99);
|
||||
Assert.InRange(insert.Location.X, 6 - 1e-4, 12 + 1e-4);
|
||||
Assert.False(MateriallyOverlaps(insert, host));
|
||||
@@ -610,7 +610,7 @@ public class ExpanderTests
|
||||
}, cts.Token);
|
||||
|
||||
Assert.True(seen, $"checkpoint {cancelAt} was not reached");
|
||||
Assert.True(result.Cancelled);
|
||||
Assert.True(result.Canceled);
|
||||
foreach (var state in before)
|
||||
{
|
||||
Assert.Same(state.Program, state.Part.Program);
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace OpenNest.Engine.Tests.Jobs;
|
||||
/// <summary>
|
||||
/// Slice 6 task 6.4: regressions for the independent Slice 3b reviews of last-resort clamp
|
||||
/// removal (D8/D9, C10-C13). Geometry: a 23"-long, 3"-wide sheet with 1.5 x 0.625 clamps
|
||||
/// centred at X = 6 and X = 21 (clamp-1 spans X [5.25, 6.75], clamp-2 [20.25, 21.75]); a
|
||||
/// centerd at X = 6 and X = 21 (clamp-1 spans X [5.25, 6.75], clamp-2 [20.25, 21.75]); a
|
||||
/// 3"-tall part always crosses the clamp band, a 14"-wide part fits only with one clamp
|
||||
/// removed, a 22"-wide part only with both removed.
|
||||
/// </summary>
|
||||
@@ -359,10 +359,10 @@ public class ClampRemovalRepairTests
|
||||
Assert.Equal(8, outcome.Result.Plates.Count);
|
||||
Assert.All(outcome.Result.Plates, sheet => Assert.Empty(sheet.Stock.ClampZones));
|
||||
}
|
||||
// PM 3df03af2 (C11/D8 "the fewest clamps"): the minimal set need not be neighbours. The
|
||||
// PM 3df03af2 (C11/D8 "the fewest clamps"): the minimal set need not be neighbors. The
|
||||
// notch over the middle clamp lets it stay while the two outer clamps come off.
|
||||
[Fact]
|
||||
public void FewestClampsNeedNotBeNeighbours()
|
||||
public void FewestClampsNeedNotBeNeighbors()
|
||||
{
|
||||
var program = new OpenNest.CNC.Program();
|
||||
program.MoveTo(0, 0);
|
||||
|
||||
@@ -38,7 +38,7 @@ public class CutoutLatticeFillTests
|
||||
|
||||
[Theory]
|
||||
[InlineData(7.0)] // diagonal 9.90: wider than the cutout less its wall spacing
|
||||
[InlineData(6.75)] // corners 4.773 from the centre; the usable radius is 4.75
|
||||
[InlineData(6.75)] // corners 4.773 from the center; the usable radius is 4.75
|
||||
public void InsertThatCannotClearTheCutoutWallIsNeverPlaced(double side)
|
||||
{
|
||||
Assert.Empty(CutoutLatticeFill.Fill(Ring(), 0, Squares(side, 4), 4, Spacing));
|
||||
|
||||
@@ -146,9 +146,9 @@ public class CutoutNfpProposalTests
|
||||
{
|
||||
var frame = JobBuilder.Part("frame", TestShapes.Ring(20, 10), 1);
|
||||
var insert = JobBuilder.Rectangle("insert", 6, 6, 1);
|
||||
using var cancelled = new CancellationTokenSource();
|
||||
cancelled.Cancel();
|
||||
using var canceled = new CancellationTokenSource();
|
||||
canceled.Cancel();
|
||||
Assert.Throws<OperationCanceledException>(() => CutoutNfpProposal.Find(frame, 0, insert, Spacing,
|
||||
cancelled.Token));
|
||||
canceled.Token));
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ public class NestJobRunnerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreCancelledEmptyJobThrows()
|
||||
public void PreCanceledEmptyJobThrows()
|
||||
{
|
||||
using var cancellation = new CancellationTokenSource();
|
||||
cancellation.Cancel();
|
||||
|
||||
@@ -183,7 +183,7 @@ public class NestJobValidationTests
|
||||
public void EtchMarksAreLeftOutOfNestingGeometry()
|
||||
{
|
||||
// The etch tick sticks 0.5 past the right edge. As material it would be open geometry
|
||||
// leaving the part and would overlap the neighbour placed 0.2 away; as a mark it is ignored.
|
||||
// leaving the part and would overlap the neighbor placed 0.2 away; as a mark it is ignored.
|
||||
var job = new NestJob(
|
||||
new[] { new NestJobPart("part", PartGeometrySnapshot.FromProgram(RectangleWithEtch(LayerType.Scribe)), 2) },
|
||||
new[] { new NestPlateStock("stock", new Size(30, 30), 1) }
|
||||
|
||||
@@ -143,7 +143,7 @@ public class NestPipelineCommitTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CancelledCommitDoesNotCreateAnyPlate()
|
||||
public void CanceledCommitDoesNotCreateAnyPlate()
|
||||
{
|
||||
var drawing = new Drawing("part", TestDrawingFactory.Rectangle());
|
||||
var nest = new Nest();
|
||||
|
||||
@@ -298,7 +298,7 @@ public class RepeatedPlateGroupingTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CancelledGroupingAndCommitLeaveInputsUntouched()
|
||||
public void CanceledGroupingAndCommitLeaveInputsUntouched()
|
||||
{
|
||||
using var source = new CancellationTokenSource();
|
||||
source.Cancel();
|
||||
|
||||
@@ -201,7 +201,7 @@ public class TinyClampZoneTests
|
||||
|
||||
private static Program DiskProgram()
|
||||
{
|
||||
// Radius 2 centred at (10, 10), two absolute CCW half circles.
|
||||
// Radius 2 centerd at (10, 10), two absolute CCW half circles.
|
||||
var program = new Program();
|
||||
program.MoveTo(12, 10);
|
||||
program.ArcTo(8, 10, 10, 10, RotationType.CCW);
|
||||
@@ -273,8 +273,8 @@ public class TinyClampZoneTests
|
||||
var arc = new ShapeProfile(OpenNest.Converters.ConvertProgram.ToGeometry(program))
|
||||
.Perimeter.Entities.OfType<Arc>().Single();
|
||||
var native = arc.ToPoints(arc.SegmentsForTolerance(Tiny / 4)).First(v => v.X > 11.9 && v.Y < 19.5);
|
||||
var centre = new Vector(10, 20);
|
||||
var outward = centre + (native - centre) / 2 * (2 + 6e-7);
|
||||
var center = new Vector(10, 20);
|
||||
var outward = center + (native - center) / 2 * (2 + 6e-7);
|
||||
return (program, new Box(outward.X - Tiny, outward.Y, Tiny, Tiny));
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ public class FlatBarNestingEngineTests
|
||||
public void MitredEndsInterlockByTurningAlternateParts()
|
||||
{
|
||||
// 45-degree mitres: a 23-long box, but turned copies nest 20 apart. Five fit on a
|
||||
// 105 bar only when every other part is turned and slid into its neighbour.
|
||||
// 105 bar only when every other part is turned and slid into its neighbor.
|
||||
var trapezoid = Part("mitre", Polyline((0, 0), (23, 0), (20, 3), (3, 3)), 5, RotationPolicy.Automatic);
|
||||
var job = Job([trapezoid], [Stock("bar", 3, 105, spacing: 0.25)]);
|
||||
|
||||
|
||||
@@ -133,13 +133,13 @@ public class IrregularBlockTests
|
||||
var type = PartCatalog.Build(job)[0];
|
||||
var drawing = new Drawing("box", program);
|
||||
using var cancellation = new CancellationTokenSource();
|
||||
var members = new CancellingMembers(Enumerable.Range(0, 3)
|
||||
var members = new CancelingMembers(Enumerable.Range(0, 3)
|
||||
.Select(i => new OpenNest.Part(drawing) { Location = new Vector(i * 3, 0) }).ToArray(), cancellation);
|
||||
Assert.Throws<OperationCanceledException>(() => BlockCatalog.Resolve(type, members,
|
||||
type.Orientations.ToList(), 0.25, cancellation.Token));
|
||||
}
|
||||
|
||||
private sealed class CancellingMembers(OpenNest.Part[] parts, CancellationTokenSource cancellation)
|
||||
private sealed class CancelingMembers(OpenNest.Part[] parts, CancellationTokenSource cancellation)
|
||||
: IReadOnlyList<OpenNest.Part>
|
||||
{
|
||||
public int Count => parts.Length;
|
||||
|
||||
@@ -184,7 +184,7 @@ public class IrregularClampZoneTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CancelledClampPackingDoesNotReturnAnEmptySuccess()
|
||||
public void CanceledClampPackingDoesNotReturnAnEmptySuccess()
|
||||
{
|
||||
var stock = Stock("sheet", 4, 5, clampZones: [new("rail", new Box(0, 0, 5, 1))]);
|
||||
var job = Job([Rectangle("part", 2, 2, 1)], [stock]);
|
||||
|
||||
@@ -38,7 +38,7 @@ public class IrregularExactContactTests
|
||||
[InlineData(8.5, 3.501)]
|
||||
[InlineData(8.501, 3.5)]
|
||||
[InlineData(8.52, 3.52)] // Generator smoke stock: a looser control, not exact-contact acceptance.
|
||||
public void LargerNeighbouringStockStillPlacesThePart(double width, double length)
|
||||
public void LargerNeighboringStockStillPlacesThePart(double width, double length)
|
||||
{
|
||||
var job = RectangleJob(width, length, 1, RotationPolicy.Automatic);
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ public class PlateFillServiceTests
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(StrategiesData))]
|
||||
public void FillItem_ReturnsNoParts_WhenTokenIsAlreadyCancelled(string strategy)
|
||||
public void FillItem_ReturnsNoParts_WhenTokenIsAlreadyCanceled(string strategy)
|
||||
{
|
||||
// Quantity > 2 keeps the request off the qty 1-2 fast path so the cancellation
|
||||
// check inside the strategy pipeline is actually reached.
|
||||
|
||||
@@ -243,7 +243,7 @@ public class AreaPackerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pack_CancelledTokenStillReturnsFirstValidLayout()
|
||||
public void Pack_CanceledTokenStillReturnsFirstValidLayout()
|
||||
{
|
||||
var area = new Box(0, 0, 30, 20);
|
||||
var items = new List<NestItem> { Item("a", 6, 4, 5), Item("b", 4, 3, 4) };
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace OpenNest.Engine.BestFit
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Like <see cref="GetOrCompute(Drawing, double, double, double)"/>, but cancelling stops
|
||||
/// Like <see cref="GetOrCompute(Drawing, double, double, double)"/>, but canceling stops
|
||||
/// the wait, not the computation: it finishes in the background and caches its result.
|
||||
/// Callers must pass a drawing nothing else mutates, such as a canonical copy.
|
||||
/// </summary>
|
||||
|
||||
@@ -46,10 +46,10 @@ internal static class BestEffortCuttingPlan
|
||||
var choices = new List<ContourChoice>();
|
||||
var approach = position - source.Location;
|
||||
// Source order is not a route: tour the holes from the arrival (the same
|
||||
// nearest-neighbour + 2-opt proposal the search uses) so the rapids between
|
||||
// nearest-neighbor + 2-opt proposal the search uses) so the rapids between
|
||||
// them do not double back across each other. The perimeter is still last.
|
||||
var holes = Enumerable.Range(0, prepared.PerimeterOrdinal).ToArray();
|
||||
var route = CuttingHoleOrder.Plan(holes, CuttingHoleOrder.Centres(prepared, approach, token),
|
||||
var route = CuttingHoleOrder.Plan(holes, CuttingHoleOrder.Centers(prepared, approach, token),
|
||||
approach, null, token);
|
||||
foreach (var contour in prepared.HasCutPerimeter ? route.Append(prepared.PerimeterOrdinal) : route)
|
||||
{
|
||||
@@ -92,7 +92,7 @@ internal static class BestEffortCuttingPlan
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return new(CuttingPlanStatus.Cancelled);
|
||||
return new(CuttingPlanStatus.Canceled);
|
||||
}
|
||||
catch (Exception exception) when (exception is ArgumentException or NotSupportedException
|
||||
or InvalidOperationException or ArithmeticException)
|
||||
|
||||
@@ -16,7 +16,7 @@ internal enum ContourSelectionShortfall
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// The finite catalogue was fully evaluated and the selection is all there is. With an
|
||||
/// The finite catalog was fully evaluated and the selection is all there is. With an
|
||||
/// empty selection this is exactly the sentence "no tested lead-in fits on part N,
|
||||
/// contour M" — and ONLY here may that sentence be spoken.
|
||||
/// </summary>
|
||||
@@ -31,7 +31,7 @@ internal enum ContourSelectionShortfall
|
||||
|
||||
/// <summary>
|
||||
/// Outcome of one bounded selection: selected choices in global rank order, how many
|
||||
/// distinct candidates were evaluated, which ones (in catalogue order, for cost tests),
|
||||
/// distinct candidates were evaluated, which ones (in catalog order, for cost tests),
|
||||
/// which evaluated uncertain (never refused — the caller's full check stays the authority
|
||||
/// on those), and why the selection stopped.
|
||||
/// </summary>
|
||||
@@ -45,7 +45,7 @@ internal sealed record ContourSelectionResult(
|
||||
|
||||
/// <summary>
|
||||
/// Bounded, lazy selection of lead-feasible entry candidates for ONE contour. Greedy global
|
||||
/// rank order until the cap (default 16) fills or the finite catalogue ends — rejected and
|
||||
/// rank order until the cap (default 16) fills or the finite catalog ends — rejected and
|
||||
/// incomplete candidates never consume a slot. When the cap can afford it (4+), one
|
||||
/// corrective scan makes sure every side of the candidate bounding rectangle that HAS a
|
||||
/// feasible candidate is represented, replacing the worst selected candidate only when
|
||||
@@ -65,7 +65,7 @@ internal static class ContourEntrySelection
|
||||
|
||||
/// <summary>
|
||||
/// Selects up to <paramref name="maxEntries"/> feasible choices from one contour's
|
||||
/// globally ranked catalogue. <paramref name="evaluate"/> is the S07 adapter verdict,
|
||||
/// globally ranked catalog. <paramref name="evaluate"/> is the S07 adapter verdict,
|
||||
/// called lazily at most once per distinct candidate.
|
||||
/// </summary>
|
||||
internal static ContourSelectionResult Select(
|
||||
@@ -111,7 +111,7 @@ internal static class ContourEntrySelection
|
||||
return known;
|
||||
}
|
||||
|
||||
// Phase 1: greedy global order until the cap fills or the catalogue ends.
|
||||
// Phase 1: greedy global order until the cap fills or the catalog ends.
|
||||
for (; index < candidates.Count && selected.Count < maxEntries; index++)
|
||||
{
|
||||
var verdict = Verdict(candidates[index]);
|
||||
@@ -128,7 +128,7 @@ internal static class ContourEntrySelection
|
||||
selected.Add(candidates[index]);
|
||||
}
|
||||
|
||||
// Phase 2: reconsider the globally ranked catalogue for each missing side, reusing
|
||||
// Phase 2: reconsider the globally ranked catalog for each missing side, reusing
|
||||
// verdicts passed while chasing earlier sides and evaluating the tail lazily.
|
||||
// A clear candidate on that side is appended when
|
||||
// a slot remains, otherwise it replaces the worst selected candidate whose removal
|
||||
@@ -182,10 +182,10 @@ internal static class ContourEntrySelection
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unreachable: phase 1 ends at cap or catalogue end and phase 2 chases every
|
||||
// Unreachable: phase 1 ends at cap or catalog end and phase 2 chases every
|
||||
// side to the end; a cancellation throws before this point.
|
||||
shortfall = ContourSelectionShortfall.Incomplete;
|
||||
reason = "Selection stopped before the catalogue ended.";
|
||||
reason = "Selection stopped before the catalog ended.";
|
||||
}
|
||||
|
||||
var ordered = selected
|
||||
|
||||
@@ -10,16 +10,16 @@ namespace OpenNest.Engine.CuttingPlanning;
|
||||
/// <summary>
|
||||
/// Proposes a serpentine whole-part order for large plates whose parts sit in aligned rows and
|
||||
/// columns: each row in turn, alternating direction, starting from the corner nearest the tool.
|
||||
/// Part centres are binned into rows by Y and columns by X within a quarter of the smallest part
|
||||
/// extent; a plate is a grid when every gap between neighbouring rows and columns is wider than
|
||||
/// Part centers are binned into rows by Y and columns by X within a quarter of the smallest part
|
||||
/// extent; a plate is a grid when every gap between neighboring rows and columns is wider than
|
||||
/// that, no two parts share a cell and the cells are at least a quarter occupied (interleaved
|
||||
/// rows fill half). Rows run along whichever axis gives the shorter route. It replaces the
|
||||
/// travelling-salesman improvement, whose passes grow with the square of the part count, and,
|
||||
/// traveling-salesman improvement, whose passes grow with the square of the part count, and,
|
||||
/// like that order, is only a proposal: the forward search still checks every rapid and lead.
|
||||
/// </summary>
|
||||
internal static class CuttingGridOrder
|
||||
{
|
||||
/// <summary>Smaller plates keep the travelling-salesman order, which is fast enough there.</summary>
|
||||
/// <summary>Smaller plates keep the traveling-salesman order, which is fast enough there.</summary>
|
||||
internal const int MinimumParts = 200;
|
||||
|
||||
/// <summary>The bin tolerance as a fraction of the smallest part extent on that axis.</summary>
|
||||
@@ -36,10 +36,10 @@ internal static class CuttingGridOrder
|
||||
var count = extents.Count;
|
||||
if (count < MinimumParts)
|
||||
return null;
|
||||
var centres = extents.Select(box => box.Center).ToArray();
|
||||
var rows = Bins(centres.Select(c => c.Y).ToArray(),
|
||||
var centers = extents.Select(box => box.Center).ToArray();
|
||||
var rows = Bins(centers.Select(c => c.Y).ToArray(),
|
||||
BinFraction * extents.Min(box => box.Top - box.Bottom), token);
|
||||
var columns = Bins(centres.Select(c => c.X).ToArray(),
|
||||
var columns = Bins(centers.Select(c => c.X).ToArray(),
|
||||
BinFraction * extents.Min(box => box.Right - box.Left), token);
|
||||
if (rows == null || columns == null)
|
||||
return null;
|
||||
@@ -60,7 +60,7 @@ internal static class CuttingGridOrder
|
||||
foreach (var reverseFirst in new[] { false, true })
|
||||
{
|
||||
var order = Serpentine(lines, along, reverseLines, reverseFirst);
|
||||
var length = Length(order, centres, start);
|
||||
var length = Length(order, centers, start);
|
||||
if (length < bestLength)
|
||||
{
|
||||
best = order;
|
||||
@@ -116,11 +116,11 @@ internal static class CuttingGridOrder
|
||||
return order.ToArray();
|
||||
}
|
||||
|
||||
private static double Length(int[] order, Vector[] centres, Vector start)
|
||||
private static double Length(int[] order, Vector[] centers, Vector start)
|
||||
{
|
||||
var length = start.DistanceTo(centres[order[0]]);
|
||||
var length = start.DistanceTo(centers[order[0]]);
|
||||
for (var i = 1; i < order.Length; i++)
|
||||
length += centres[order[i - 1]].DistanceTo(centres[order[i]]);
|
||||
length += centers[order[i - 1]].DistanceTo(centers[order[i]]);
|
||||
return length;
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,11 @@ namespace OpenNest.Engine.CuttingPlanning;
|
||||
|
||||
/// <summary>
|
||||
/// Proposes the ORDER in which a part's remaining holes are cut when the perimeter entry is
|
||||
/// already chosen: an open path from the tool's arrival through every hole centre and ON to
|
||||
/// the selected perimeter entry — the terminal edge is real rapid travel and is optimised,
|
||||
/// already chosen: an open path from the tool's arrival through every hole center and ON to
|
||||
/// the selected perimeter entry — the terminal edge is real rapid travel and is optimized,
|
||||
/// not scored after the fact. It reuses <see cref="CuttingPartOrder"/>'s bounded nearest-
|
||||
/// neighbour + 2-opt/Or-opt machinery (no exhaustive permutations) with an optional fixed
|
||||
/// endpoint, so whole-part routing without an endpoint keeps byte-identical behaviour.
|
||||
/// neighbor + 2-opt/Or-opt machinery (no exhaustive permutations) with an optional fixed
|
||||
/// endpoint, so whole-part routing without an endpoint keeps byte-identical behavior.
|
||||
/// This is a proposal only: the search still certifies every rapid and emitted lead along
|
||||
/// it, exactly as it does for part orders.
|
||||
/// </summary>
|
||||
@@ -22,41 +22,41 @@ internal static class CuttingHoleOrder
|
||||
/// <summary>
|
||||
/// One representative point per hole ordinal of <paramref name="prepared"/>, in its LOCAL
|
||||
/// coordinates. The tool arriving inside a cutout from a part nested there says nothing
|
||||
/// useful about that cutout's centre: it is routed from where the tool is instead, before
|
||||
/// useful about that cutout's center: it is routed from where the tool is instead, before
|
||||
/// a later hop to it has to cross the other parts nested in it.
|
||||
/// </summary>
|
||||
internal static Vector[] Centres(PreparedContours prepared, Vector localArrival, CancellationToken token)
|
||||
internal static Vector[] Centers(PreparedContours prepared, Vector localArrival, CancellationToken token)
|
||||
{
|
||||
var centres = prepared.HoleCentres(token).Select(c => c ?? Vector.Zero).ToArray();
|
||||
var centers = prepared.HoleCenters(token).Select(c => c ?? Vector.Zero).ToArray();
|
||||
for (var hole = 0; hole < prepared.PerimeterOrdinal; hole++)
|
||||
if (prepared.HoleContains(hole, localArrival))
|
||||
centres[hole] = localArrival;
|
||||
return centres;
|
||||
centers[hole] = localArrival;
|
||||
return centers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Orders <paramref name="holes"/> (stable ordinals, each appearing exactly once in the
|
||||
/// result, never the perimeter) to minimize arrival -> holes -> <paramref
|
||||
/// name="perimeterEntry"/> distance. <paramref name="centresByOrdinal"/> supplies one
|
||||
/// name="perimeterEntry"/> distance. <paramref name="centersByOrdinal"/> supplies one
|
||||
/// representative point per hole ordinal. Empty holes give an empty order. Without a
|
||||
/// perimeter entry the path is open: it ends at whichever hole keeps it shortest.
|
||||
/// </summary>
|
||||
internal static IReadOnlyList<int> Plan(IReadOnlyList<int> holes,
|
||||
IReadOnlyList<Vector> centresByOrdinal, Vector arrival, Vector? perimeterEntry,
|
||||
IReadOnlyList<Vector> centersByOrdinal, Vector arrival, Vector? perimeterEntry,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
if (holes == null)
|
||||
throw new ArgumentException("Hole ordinals are required.", nameof(holes));
|
||||
if (centresByOrdinal == null)
|
||||
throw new ArgumentException("Hole centres are required.", nameof(centresByOrdinal));
|
||||
if (centersByOrdinal == null)
|
||||
throw new ArgumentException("Hole centers are required.", nameof(centersByOrdinal));
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (holes.Count == 0)
|
||||
return Array.Empty<int>();
|
||||
|
||||
var none = Array.Empty<int>();
|
||||
var prerequisites = holes.Select(_ => (IReadOnlyCollection<int>)none).ToList();
|
||||
var centres = holes.Select(hole => centresByOrdinal[hole]).ToList();
|
||||
var route = CuttingPartOrder.Plan(centres, arrival, prerequisites, token, perimeterEntry);
|
||||
var centers = holes.Select(hole => centersByOrdinal[hole]).ToList();
|
||||
var route = CuttingPartOrder.Plan(centers, arrival, prerequisites, token, perimeterEntry);
|
||||
return route.Select(index => holes[index]).ToArray();
|
||||
}
|
||||
}
|
||||
@@ -10,17 +10,17 @@ namespace OpenNest.Engine.CuttingPlanning;
|
||||
/// Proposes a whole-part order for plates with parts nested inside other parts' cutouts. A host
|
||||
/// is cut after everything inside it, so the tool reaches it from whichever inner part was cut
|
||||
/// last; from the middle of a crowded cutout every straight approach crosses a finished
|
||||
/// neighbour. Each host's inner parts are therefore toured as one run that ends beside the
|
||||
/// neighbor. Each host's inner parts are therefore toured as one run that ends beside the
|
||||
/// host's own contours, immediately before the host. Plates without nested parts get exactly
|
||||
/// <see cref="CuttingPartOrder"/>'s order. Like that order, this is only a proposal.
|
||||
/// </summary>
|
||||
internal static class CuttingNestedOrder
|
||||
{
|
||||
/// <param name="Centres">One representative point per part ordinal.</param>
|
||||
/// <param name="Centers">One representative point per part ordinal.</param>
|
||||
/// <param name="Prerequisites">Ordinals that must come before each part, learned rules included.</param>
|
||||
/// <param name="Inside">Per part ordinal, the parts nested inside its cutouts at any depth.</param>
|
||||
/// <param name="HostEntry">The point on a host's contours nearest a tool position.</param>
|
||||
internal sealed record Context(IReadOnlyList<Vector> Centres,
|
||||
internal sealed record Context(IReadOnlyList<Vector> Centers,
|
||||
IReadOnlyList<IReadOnlyCollection<int>> Prerequisites, IReadOnlyList<IReadOnlyCollection<int>> Inside,
|
||||
Func<int, Vector, Vector> HostEntry, CancellationToken Token);
|
||||
|
||||
@@ -44,7 +44,7 @@ internal static class CuttingNestedOrder
|
||||
rootOf[inner] = outer;
|
||||
var roots = parts.Where(p => !rootOf.ContainsKey(p)).ToArray();
|
||||
// A rule between parts of two groups orders the groups.
|
||||
var groupRules = context.Centres.Select(_ => new HashSet<int>()).ToArray();
|
||||
var groupRules = context.Centers.Select(_ => new HashSet<int>()).ToArray();
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var group = rootOf.GetValueOrDefault(part, part);
|
||||
@@ -63,13 +63,13 @@ internal static class CuttingNestedOrder
|
||||
int[] rootOrder;
|
||||
try
|
||||
{
|
||||
rootOrder = CuttingPartOrder.Plan(roots, context.Centres, start, hostsLast, context.Token, endpoint);
|
||||
rootOrder = CuttingPartOrder.Plan(roots, context.Centers, start, hostsLast, context.Token, endpoint);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
try
|
||||
{
|
||||
rootOrder = CuttingPartOrder.Plan(roots, context.Centres, start, groupRules, context.Token, endpoint);
|
||||
rootOrder = CuttingPartOrder.Plan(roots, context.Centers, start, groupRules, context.Token, endpoint);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
@@ -84,7 +84,7 @@ internal static class CuttingNestedOrder
|
||||
if (inner.Length != 0)
|
||||
order.AddRange(Plan(inner, position, context, context.HostEntry(root, position)));
|
||||
order.Add(root);
|
||||
position = context.Centres[root];
|
||||
position = context.Centers[root];
|
||||
}
|
||||
var placed = new HashSet<int>();
|
||||
foreach (var part in order)
|
||||
@@ -95,6 +95,6 @@ internal static class CuttingNestedOrder
|
||||
}
|
||||
return order.ToArray();
|
||||
|
||||
int[] Flat() => CuttingPartOrder.Plan(parts, context.Centres, start, context.Prerequisites, context.Token, endpoint);
|
||||
int[] Flat() => CuttingPartOrder.Plan(parts, context.Centers, start, context.Prerequisites, context.Token, endpoint);
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ using OpenNest.Geometry;
|
||||
namespace OpenNest.Engine.CuttingPlanning;
|
||||
|
||||
/// <summary>
|
||||
/// Proposes a whole-part visiting order as an open travelling-salesman path from the start point
|
||||
/// over part centres: nearest neighbour, then 2-opt and Or-opt improvement, never placing a part
|
||||
/// Proposes a whole-part visiting order as an open traveling-salesman path from the start point
|
||||
/// over part centers: nearest neighbor, then 2-opt and Or-opt improvement, never placing a part
|
||||
/// before one of its prerequisites. It is only a proposal; the forward search still checks every
|
||||
/// rapid and lead along it.
|
||||
/// </summary>
|
||||
@@ -20,24 +20,24 @@ internal static class CuttingPartOrder
|
||||
internal const int MaxPasses = 50;
|
||||
|
||||
/// <param name="parts">The part ordinals to order; prerequisites outside this set count as done.</param>
|
||||
/// <param name="centres">One representative point per part ordinal.</param>
|
||||
/// <param name="centers">One representative point per part ordinal.</param>
|
||||
/// <param name="start">The modeled tool position before the first of these parts.</param>
|
||||
/// <param name="prerequisites">Ordinals that must come before each part; must be acyclic.</param>
|
||||
/// <param name="endpoint">Optional fixed final position after the last of these parts.</param>
|
||||
internal static int[] Plan(IReadOnlyList<int> parts, IReadOnlyList<Vector> centres, Vector start,
|
||||
internal static int[] Plan(IReadOnlyList<int> parts, IReadOnlyList<Vector> centers, Vector start,
|
||||
IReadOnlyList<IReadOnlyCollection<int>> prerequisites, CancellationToken token, Vector? endpoint = null)
|
||||
{
|
||||
var local = new Dictionary<int, int>();
|
||||
for (var i = 0; i < parts.Count; i++)
|
||||
local.Add(parts[i], i);
|
||||
var localCentres = parts.Select(p => centres[p]).ToArray();
|
||||
var localCenters = parts.Select(p => centers[p]).ToArray();
|
||||
var localPrerequisites = parts
|
||||
.Select(p => (IReadOnlyCollection<int>)prerequisites[p].Where(local.ContainsKey).Select(q => local[q]).ToArray())
|
||||
.ToArray();
|
||||
return Plan(localCentres, start, localPrerequisites, token, endpoint).Select(i => parts[i]).ToArray();
|
||||
return Plan(localCenters, start, localPrerequisites, token, endpoint).Select(i => parts[i]).ToArray();
|
||||
}
|
||||
|
||||
/// <param name="centres">One representative point per part ordinal.</param>
|
||||
/// <param name="centers">One representative point per part ordinal.</param>
|
||||
/// <param name="start">The modeled tool position before the first part.</param>
|
||||
/// <param name="prerequisites">Ordinals that must come before each part; must be acyclic.</param>
|
||||
/// <param name="endpoint">
|
||||
@@ -45,27 +45,27 @@ internal static class CuttingPartOrder
|
||||
/// terminal edge). The terminal edge joins EVERY improvement delta, not just final
|
||||
/// scoring. Null keeps the previous whole-part open-route semantics exactly.
|
||||
/// </param>
|
||||
internal static int[] Plan(IReadOnlyList<Vector> centres, Vector start,
|
||||
internal static int[] Plan(IReadOnlyList<Vector> centers, Vector start,
|
||||
IReadOnlyList<IReadOnlyCollection<int>> prerequisites, CancellationToken token, Vector? endpoint = null)
|
||||
{
|
||||
var count = centres.Count;
|
||||
var order = NearestNeighbour(centres, start, prerequisites, token);
|
||||
var count = centers.Count;
|
||||
var order = NearestNeighbor(centers, start, prerequisites, token);
|
||||
var position = new int[count];
|
||||
for (var pass = 0; pass < MaxPasses; pass++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
var improved = TwoOpt(order, centres, start, prerequisites, position, token, endpoint);
|
||||
improved |= OrOpt(order, centres, start, prerequisites, position, token, endpoint);
|
||||
var improved = TwoOpt(order, centers, start, prerequisites, position, token, endpoint);
|
||||
improved |= OrOpt(order, centers, start, prerequisites, position, token, endpoint);
|
||||
if (!improved)
|
||||
break;
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
private static int[] NearestNeighbour(IReadOnlyList<Vector> centres, Vector start,
|
||||
private static int[] NearestNeighbor(IReadOnlyList<Vector> centers, Vector start,
|
||||
IReadOnlyList<IReadOnlyCollection<int>> prerequisites, CancellationToken token)
|
||||
{
|
||||
var count = centres.Count;
|
||||
var count = centers.Count;
|
||||
var placed = new bool[count];
|
||||
var order = new int[count];
|
||||
var current = start;
|
||||
@@ -78,7 +78,7 @@ internal static class CuttingPartOrder
|
||||
{
|
||||
if (placed[part] || prerequisites[part].Any(p => !placed[p]))
|
||||
continue;
|
||||
var distance = current.DistanceTo(centres[part]);
|
||||
var distance = current.DistanceTo(centers[part]);
|
||||
if (distance < bestDistance - Epsilon)
|
||||
{
|
||||
best = part;
|
||||
@@ -89,30 +89,30 @@ internal static class CuttingPartOrder
|
||||
throw new InvalidOperationException("Part prerequisites form a cycle.");
|
||||
placed[best] = true;
|
||||
order[step] = best;
|
||||
current = centres[best];
|
||||
current = centers[best];
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
// Reverses order[i..j] when that shortens the open path and keeps every prerequisite earlier.
|
||||
private static bool TwoOpt(int[] order, IReadOnlyList<Vector> centres, Vector start,
|
||||
private static bool TwoOpt(int[] order, IReadOnlyList<Vector> centers, Vector start,
|
||||
IReadOnlyList<IReadOnlyCollection<int>> prerequisites, int[] position, CancellationToken token,
|
||||
Vector? endpoint = null)
|
||||
{
|
||||
var improved = false;
|
||||
var count = order.Length;
|
||||
// The terminal edge belongs to every delta: reversing the route's tail swaps which
|
||||
// endpoint-side centre faces the fixed final position.
|
||||
// endpoint-side center faces the fixed final position.
|
||||
double Tail(Vector from) => endpoint is { } e ? from.DistanceTo(e) : 0;
|
||||
for (var i = 0; i < count - 1; i++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
for (var j = i + 1; j < count; j++)
|
||||
{
|
||||
var before = Point(i - 1).DistanceTo(centres[order[i]])
|
||||
+ (j + 1 < count ? centres[order[j]].DistanceTo(centres[order[j + 1]]) : Tail(centres[order[j]]));
|
||||
var after = Point(i - 1).DistanceTo(centres[order[j]])
|
||||
+ (j + 1 < count ? centres[order[i]].DistanceTo(centres[order[j + 1]]) : Tail(centres[order[i]]));
|
||||
var before = Point(i - 1).DistanceTo(centers[order[i]])
|
||||
+ (j + 1 < count ? centers[order[j]].DistanceTo(centers[order[j + 1]]) : Tail(centers[order[j]]));
|
||||
var after = Point(i - 1).DistanceTo(centers[order[j]])
|
||||
+ (j + 1 < count ? centers[order[i]].DistanceTo(centers[order[j + 1]]) : Tail(centers[order[i]]));
|
||||
if (after >= before - Epsilon || !CanReverse(order, i, j, prerequisites, position))
|
||||
continue;
|
||||
Array.Reverse(order, i, j - i + 1);
|
||||
@@ -121,7 +121,7 @@ internal static class CuttingPartOrder
|
||||
}
|
||||
return improved;
|
||||
|
||||
Vector Point(int index) => index < 0 ? start : centres[order[index]];
|
||||
Vector Point(int index) => index < 0 ? start : centers[order[index]];
|
||||
}
|
||||
|
||||
// A reversal breaks a prerequisite only when both parts lie inside the reversed span.
|
||||
@@ -138,7 +138,7 @@ internal static class CuttingPartOrder
|
||||
}
|
||||
|
||||
// Moves a run of one to three parts to a later or earlier gap when that shortens the path.
|
||||
private static bool OrOpt(int[] order, IReadOnlyList<Vector> centres, Vector start,
|
||||
private static bool OrOpt(int[] order, IReadOnlyList<Vector> centers, Vector start,
|
||||
IReadOnlyList<IReadOnlyCollection<int>> prerequisites, int[] position, CancellationToken token,
|
||||
Vector? endpoint = null)
|
||||
{
|
||||
@@ -172,12 +172,12 @@ internal static class CuttingPartOrder
|
||||
// end — the fixed endpoint when one was supplied, so the terminal edge is in every delta).
|
||||
double Gap(int a, int b)
|
||||
{
|
||||
var from = a < 0 ? start : centres[order[a]];
|
||||
var from = a < 0 ? start : centers[order[a]];
|
||||
if (b >= count)
|
||||
return endpoint is { } e ? from.DistanceTo(e) : 0;
|
||||
if (b < 0)
|
||||
return 0;
|
||||
return from.DistanceTo(centres[order[b]]);
|
||||
return from.DistanceTo(centers[order[b]]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@ public sealed class CuttingPlanBatch
|
||||
/// Checks and plans every plate from its captured snapshots. Safe on a worker: live plates are
|
||||
/// not read. Overlapping material blocks a plate whatever its route. A free search that ends
|
||||
/// without a complete plan within its budget is retried with the current order; the proposal
|
||||
/// reports that it kept the order. Incomplete geometry also gets a separately labelled,
|
||||
/// reports that it kept the order. Incomplete geometry also gets a separately labeled,
|
||||
/// unverified fallback when supported closed contours can still be emitted.
|
||||
/// </summary>
|
||||
public CuttingPlanProposal Plan(IProgress<CuttingPlanProgress> progress = null,
|
||||
@@ -177,7 +177,7 @@ public sealed class CuttingPlanBatch
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
plans[index] = new(entry.Plate, entry.Number, new(CuttingPlanStatus.Cancelled), null, null);
|
||||
plans[index] = new(entry.Plate, entry.Number, new(CuttingPlanStatus.Canceled), null, null);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ public sealed class CuttingPlanBatch
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return (new(CuttingPlanStatus.Cancelled), true);
|
||||
return (new(CuttingPlanStatus.Canceled), true);
|
||||
}
|
||||
return best;
|
||||
}
|
||||
@@ -265,7 +265,7 @@ public sealed class CuttingPlanBatch
|
||||
// A replayed proposal whose only warnings are fixed emissions passed every strict
|
||||
// check; fixed emissions never steer the search, so the full pass cannot improve it.
|
||||
if (first.IndependentlyReplayed && first.Status is CuttingPlanStatus.Ready or CuttingPlanStatus.BestEffort
|
||||
|| first.Status == CuttingPlanStatus.Cancelled)
|
||||
|| first.Status == CuttingPlanStatus.Canceled)
|
||||
return first;
|
||||
}
|
||||
return CuttingPlanService.Plan(snapshot, token);
|
||||
@@ -277,7 +277,7 @@ public sealed class CuttingPlanBatch
|
||||
return strict;
|
||||
var fallback = BestEffortCuttingPlan.Plan(entry.Fallback, token);
|
||||
if (fallback.Status != CuttingPlanStatus.BestEffort)
|
||||
return fallback.Status == CuttingPlanStatus.Cancelled ? fallback : strict;
|
||||
return fallback.Status == CuttingPlanStatus.Canceled ? fallback : strict;
|
||||
return new(CuttingPlanStatus.BestEffort, fallback.ProposedOrder,
|
||||
strict.Findings.Concat(fallback.Findings).Distinct(), rapidDistance: fallback.RapidDistance)
|
||||
{ Snapshot = entry.Fallback };
|
||||
@@ -310,7 +310,7 @@ public sealed class CuttingPlanPlateResult
|
||||
/// <summary>The free-order attempt that ran out of budget before the current order was kept; else null.</summary>
|
||||
public CuttingPlanResult ReorderAttempt { get; }
|
||||
|
||||
/// <summary>The clean-material overlap check (part ids are current part positions); null if cancelled first.</summary>
|
||||
/// <summary>The clean-material overlap check (part ids are current part positions); null if canceled first.</summary>
|
||||
public PlateOverlapReport Overlap { get; }
|
||||
|
||||
public bool KeptCurrentOrder => ReorderAttempt != null;
|
||||
@@ -331,7 +331,7 @@ public sealed class CuttingPlanPlateResult
|
||||
/// </summary>
|
||||
public bool SearchLimitReached => !CanApplyWithWarnings && !IsRouteReady
|
||||
&& (Result.Status == CuttingPlanStatus.NoSolutionWithinBudget || KeptCurrentOrder
|
||||
&& Result.Status is not (CuttingPlanStatus.Cancelled or CuttingPlanStatus.UnsupportedGeometry
|
||||
&& Result.Status is not (CuttingPlanStatus.Canceled or CuttingPlanStatus.UnsupportedGeometry
|
||||
or CuttingPlanStatus.InvalidInput));
|
||||
|
||||
/// <summary>
|
||||
@@ -374,13 +374,13 @@ public sealed class CuttingPlanProposal
|
||||
|
||||
public IReadOnlyList<CuttingPlanPlateResult> Plates { get; }
|
||||
|
||||
public bool IsCancelled => Plates.Any(p => p.Result.Status == CuttingPlanStatus.Cancelled);
|
||||
public bool IsCanceled => Plates.Any(p => p.Result.Status == CuttingPlanStatus.Canceled);
|
||||
|
||||
public bool CanApply => Plates.Count > 0 && !IsCancelled && ownedParameters != null
|
||||
public bool CanApply => Plates.Count > 0 && !IsCanceled && ownedParameters != null
|
||||
&& Plates.All(p => p.IsReady);
|
||||
|
||||
/// <summary>All plates have usable output, possibly requiring explicit warning acceptance.</summary>
|
||||
public bool CanApplyWithWarnings => Plates.Count > 0 && !IsCancelled && ownedParameters != null
|
||||
public bool CanApplyWithWarnings => Plates.Count > 0 && !IsCanceled && ownedParameters != null
|
||||
&& Plates.All(p => p.CanApplyWithWarnings);
|
||||
|
||||
public bool RequiresWarningAcceptance => CanApplyWithWarnings && !CanApply;
|
||||
@@ -483,8 +483,8 @@ public sealed class CuttingPlanProposal
|
||||
{
|
||||
var lines = new List<string>();
|
||||
var blocked = Plates.Count(p => !p.CanApplyWithWarnings);
|
||||
if (IsCancelled)
|
||||
lines.Add("Planning was cancelled. Nothing has changed.");
|
||||
if (IsCanceled)
|
||||
lines.Add("Planning was canceled. Nothing has changed.");
|
||||
else if (CanApply)
|
||||
lines.Add($"Ready to apply to {Count(Plates.Count, "plate")}: "
|
||||
+ $"{Count(Plates.Count(p => p.OrderChanged), "plate")} with a new part order, "
|
||||
@@ -557,7 +557,7 @@ public sealed class CuttingPlanProposal
|
||||
yield break;
|
||||
}
|
||||
|
||||
var missingLead = !plate.IsRouteReady && result.Status != CuttingPlanStatus.Cancelled
|
||||
var missingLead = !plate.IsRouteReady && result.Status != CuttingPlanStatus.Canceled
|
||||
&& result.Findings.Any(f => f.Kind == PostVerificationKind.MissingLeadIn);
|
||||
yield return heading + (plate.IsRouteReady
|
||||
? "blocked: parts overlap or could not be checked for overlap."
|
||||
@@ -613,7 +613,7 @@ public sealed class CuttingPlanProposal
|
||||
CuttingPlanStatus.UnsupportedGeometry => "blocked: unsupported geometry or an incomplete check.",
|
||||
CuttingPlanStatus.InvalidInput => "blocked: invalid input.",
|
||||
CuttingPlanStatus.NoSolutionWithinBudget => "no complete plan was found within the search limit.",
|
||||
CuttingPlanStatus.Cancelled => "cancelled.",
|
||||
CuttingPlanStatus.Canceled => "canceled.",
|
||||
_ => status.ToString(),
|
||||
};
|
||||
|
||||
|
||||
@@ -295,7 +295,7 @@ public enum CuttingPlanStatus
|
||||
UnsupportedGeometry,
|
||||
InvalidInput,
|
||||
NoSolutionWithinBudget,
|
||||
Cancelled,
|
||||
Canceled,
|
||||
/// <summary>Usable owned programs, but geometric safety checks did not pass. Never Ready.</summary>
|
||||
BestEffort
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ public static class CuttingPlanService
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return Failure(CuttingPlanStatus.Cancelled, "Capture cancelled.");
|
||||
return Failure(CuttingPlanStatus.Canceled, "Capture canceled.");
|
||||
}
|
||||
catch (CuttingDependencyException exception)
|
||||
{
|
||||
@@ -260,7 +260,7 @@ public static class CuttingPlanService
|
||||
private static CuttingPlanResult PlanCaptured(CuttingPlanSnapshot snapshot, CancellationToken token)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return new(CuttingPlanStatus.Cancelled);
|
||||
return new(CuttingPlanStatus.Canceled);
|
||||
if (snapshot == null)
|
||||
return new(CuttingPlanStatus.InvalidInput);
|
||||
if (snapshot.Failure is { } failure)
|
||||
@@ -300,7 +300,7 @@ public static class CuttingPlanService
|
||||
if (snapshot.Regeneration)
|
||||
{
|
||||
var joint = JointCuttingPlanSearch.Run(snapshot, token);
|
||||
// Capture warnings (drawing pins that could not be honoured) stay visible on a refusal.
|
||||
// Capture warnings (drawing pins that could not be honored) stay visible on a refusal.
|
||||
if (joint.Status != CuttingPlanStatus.Ready)
|
||||
return new(joint.Status, findings: snapshot.Findings.Concat(joint.Findings), expansions: joint.Expansions);
|
||||
return ReplayPrograms(snapshot, joint.Order, joint.Expansions, token);
|
||||
@@ -312,7 +312,7 @@ public static class CuttingPlanService
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return new(CuttingPlanStatus.Cancelled);
|
||||
return new(CuttingPlanStatus.Canceled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,7 +407,7 @@ public static class CuttingPlanService
|
||||
var distance = 0.0;
|
||||
var findings = new List<CuttingPlanFinding>();
|
||||
// Violations inside fixed emissions: accepted as given, reported, never refused (D1a).
|
||||
// Capture adds the drawing pins it could not honour; their contours were planned normally.
|
||||
// Capture adds the drawing pins it could not honor; their contours were planned normally.
|
||||
var warnings = new List<CuttingPlanFinding>(snapshot.Findings);
|
||||
// Rapids that raise the head over cut parts are listed, never refused.
|
||||
var raises = new List<CuttingPlanFinding>();
|
||||
|
||||
@@ -68,7 +68,7 @@ internal static class JointCuttingPlanSearch
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return new(CuttingPlanStatus.Cancelled, [], [], walk.Expansions, walk.LeadPrechecks);
|
||||
return new(CuttingPlanStatus.Canceled, [], [], walk.Expansions, walk.LeadPrechecks);
|
||||
}
|
||||
return Run(snapshot, token, new Walk(snapshot, token, [], walk.Expansions, walk.LeadPrechecks), []);
|
||||
}
|
||||
@@ -107,11 +107,11 @@ internal static class JointCuttingPlanSearch
|
||||
return kept.Order != null ? walk.Ready(kept.Order) : walk.Exhausted();
|
||||
}
|
||||
|
||||
var centres = snapshot.Placements.Select(Centre).ToArray();
|
||||
var centers = snapshot.Placements.Select(Center).ToArray();
|
||||
var prerequisites = Enumerable.Range(0, count)
|
||||
.Select(i => new HashSet<int>(snapshot.Dependencies.PrerequisitesOf(i).Except(excluded))).ToArray();
|
||||
|
||||
var nesting = NestedOrder(snapshot, centres, prerequisites, token);
|
||||
var nesting = NestedOrder(snapshot, centers, prerequisites, token);
|
||||
// A large aligned grid with no required order is toured row by row; anything else,
|
||||
// and every replan after a learned rule, gets the general order.
|
||||
var extents = snapshot.Placements.Select(CutExtent).ToArray();
|
||||
@@ -166,7 +166,7 @@ internal static class JointCuttingPlanSearch
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return new(CuttingPlanStatus.Cancelled, [], [], walk.Expansions, walk.LeadPrechecks);
|
||||
return new(CuttingPlanStatus.Canceled, [], [], walk.Expansions, walk.LeadPrechecks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,19 +191,19 @@ internal static class JointCuttingPlanSearch
|
||||
}
|
||||
|
||||
// Captured containment (never learned rules) and each host's placed cut contours.
|
||||
private static CuttingNestedOrder.Context NestedOrder(CuttingPlanSnapshot snapshot, Vector[] centres,
|
||||
private static CuttingNestedOrder.Context NestedOrder(CuttingPlanSnapshot snapshot, Vector[] centers,
|
||||
HashSet<int>[] prerequisites, CancellationToken token)
|
||||
{
|
||||
var inside = snapshot.Placements.Select((placement, host) => placement.IsCutOff ? []
|
||||
: snapshot.Dependencies.PrerequisitesOf(host).Where(p => !snapshot.Placements[p].IsCutOff).ToArray()).ToArray();
|
||||
var contours = new Dictionary<int, Entity[]>();
|
||||
return new(centres, prerequisites, inside, (host, position) =>
|
||||
return new(centers, prerequisites, inside, (host, position) =>
|
||||
{
|
||||
if (!contours.TryGetValue(host, out var entities))
|
||||
contours[host] = entities = snapshot.Placements[host].Execution.Motions
|
||||
.Where(m => !m.Rapid && m.Layer is LayerType.Cut or LayerType.Display && m.Curve != null)
|
||||
.Select(m => m.Curve.ToEntity()).ToArray();
|
||||
return entities.Length == 0 ? centres[host]
|
||||
return entities.Length == 0 ? centers[host]
|
||||
: entities.Select(e => e.ClosestPointTo(position)).MinBy(point => point.DistanceTo(position));
|
||||
}, token);
|
||||
}
|
||||
@@ -263,8 +263,8 @@ internal static class JointCuttingPlanSearch
|
||||
return false;
|
||||
}
|
||||
|
||||
// The centre of the part's placed cut material, for ordering only.
|
||||
private static Vector Centre(FixedProgramPlacement placement) =>
|
||||
// The center of the part's placed cut material, for ordering only.
|
||||
private static Vector Center(FixedProgramPlacement placement) =>
|
||||
CutExtent(placement)?.Center ?? placement.Execution.DeparturePoint;
|
||||
|
||||
// The extent of the part's placed cut material, or null when it has none.
|
||||
@@ -398,7 +398,7 @@ internal static class JointCuttingPlanSearch
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Ready, stalled, cancelled and budget-exhausted searches can all leave
|
||||
// Ready, stalled, canceled and budget-exhausted searches can all leave
|
||||
// suspended siblings. Release their captured prefixes on every exit.
|
||||
foreach (var frame in stack)
|
||||
frame.Children?.Dispose();
|
||||
@@ -419,7 +419,7 @@ internal static class JointCuttingPlanSearch
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The NEXT cut's centre that the outside entry should face, or null for the last
|
||||
/// The NEXT cut's center that the outside entry should face, or null for the last
|
||||
/// part. Supplied order: the next not-yet-finished part in that order (the sequence
|
||||
/// is re-read after every learned-order replan). Sequence-free fallback: nearest
|
||||
/// dependency-ready remaining part once the current part counts as finished, stable
|
||||
@@ -428,7 +428,7 @@ internal static class JointCuttingPlanSearch
|
||||
/// should face, so a kept order with segments gets the entries planned without them.
|
||||
/// Global coordinates.
|
||||
/// </summary>
|
||||
private Vector? LookAheadCentre(Node node, int[] sequence, FixedProgramPlacement source) =>
|
||||
private Vector? LookAheadCenter(Node node, int[] sequence, FixedProgramPlacement source) =>
|
||||
NextCut(node, sequence, source) is { } next ? Facing(source, next) : null;
|
||||
|
||||
private FixedProgramPlacement NextCut(Node node, int[] sequence, FixedProgramPlacement source)
|
||||
@@ -439,7 +439,7 @@ internal static class JointCuttingPlanSearch
|
||||
for (var i = node.Order.Length + 1; i < sequence.Length; i++)
|
||||
if (!finished.Contains(sequence[i]) && !IsSegment(snapshot.Placements[sequence[i]]))
|
||||
return snapshot.Placements[sequence[i]];
|
||||
var from = Centre(source);
|
||||
var from = Center(source);
|
||||
FixedProgramPlacement best = null;
|
||||
var bestDistance = double.PositiveInfinity;
|
||||
foreach (var candidate in snapshot.Placements
|
||||
@@ -447,7 +447,7 @@ internal static class JointCuttingPlanSearch
|
||||
&& snapshot.Dependencies.IsReady(p.SourceOrdinal, finished))
|
||||
.OrderBy(p => p.SourceOrdinal))
|
||||
{
|
||||
var distance = Centre(candidate).DistanceTo(from);
|
||||
var distance = Center(candidate).DistanceTo(from);
|
||||
if (distance < bestDistance - 1e-9)
|
||||
{
|
||||
bestDistance = distance;
|
||||
@@ -472,26 +472,26 @@ internal static class JointCuttingPlanSearch
|
||||
var cuts = next.Execution.Motions
|
||||
.Where(m => !m.Rapid && m.Layer is LayerType.Cut or LayerType.Display && m.Curve != null)
|
||||
.Select(m => m.Curve.ToEntity().BoundingBox).ToList();
|
||||
if (cuts.Count == 0 || facing != Centre(next))
|
||||
if (cuts.Count == 0 || facing != Center(next))
|
||||
return [facing];
|
||||
var box = cuts.GetBoundingBox();
|
||||
return [facing, new(box.Left, box.Bottom), new(box.Right, box.Bottom), new(box.Left, box.Top), new(box.Right, box.Top)];
|
||||
}
|
||||
|
||||
// The next cut as seen from the current part. A host's centre lies in the middle of
|
||||
// The next cut as seen from the current part. A host's center lies in the middle of
|
||||
// its cutout, among the parts nested there; a part inside it leaves toward the
|
||||
// nearest of the host's own contours instead.
|
||||
private Vector Facing(FixedProgramPlacement source, FixedProgramPlacement next)
|
||||
{
|
||||
if (source.IsCutOff || next.IsCutOff
|
||||
|| !snapshot.Dependencies.PrerequisitesOf(next.SourceOrdinal).Contains(source.SourceOrdinal))
|
||||
return Centre(next);
|
||||
return Center(next);
|
||||
if (!hostContours.TryGetValue(next.SourceOrdinal, out var contours))
|
||||
hostContours[next.SourceOrdinal] = contours = next.Execution.Motions
|
||||
.Where(m => !m.Rapid && m.Layer is LayerType.Cut or LayerType.Display && m.Curve != null)
|
||||
.Select(m => m.Curve.ToEntity()).ToArray();
|
||||
var from = Centre(source);
|
||||
return contours.Length == 0 ? Centre(next)
|
||||
var from = Center(source);
|
||||
return contours.Length == 0 ? Center(next)
|
||||
: contours.Select(c => c.ClosestPointTo(from)).MinBy(point => point.DistanceTo(from));
|
||||
}
|
||||
|
||||
@@ -506,7 +506,7 @@ internal static class JointCuttingPlanSearch
|
||||
// source. Expand already yields its contour/entry rank order, so emit and
|
||||
// validate a sibling only when DFS reaches it. Free whole-part selection
|
||||
// still needs every source's distances before it can rank them. Recovery
|
||||
// instead visits dependency-ready sources lazily by their material centres.
|
||||
// instead visits dependency-ready sources lazily by their material centers.
|
||||
if (sequence != null || node.Active != null || contourRecovery)
|
||||
return Expand(node, sequence, attempt, preferredOnly, contourRecovery);
|
||||
var edges = Expand(node, sequence, attempt, preferredOnly, contourRecovery).ToList();
|
||||
@@ -542,7 +542,7 @@ internal static class JointCuttingPlanSearch
|
||||
? [snapshot.Placements[sequence[node.Order.Length]]] : [];
|
||||
}
|
||||
if (contourRecovery)
|
||||
sources = sources.OrderBy(p => Centre(p).DistanceTo(node.Position)).ThenBy(p => p.SourceOrdinal);
|
||||
sources = sources.OrderBy(p => Center(p).DistanceTo(node.Position)).ThenBy(p => p.SourceOrdinal);
|
||||
foreach (var source in sources)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
@@ -587,7 +587,7 @@ internal static class JointCuttingPlanSearch
|
||||
var state = new ActivePart(source, [], node.Position, node.Checker, 0, node,
|
||||
perimeters[index]);
|
||||
yield return new(new(node.Order, node.Position, node.Checker, state, null),
|
||||
Centre(source).DistanceTo(node.Position), source.SourceOrdinal, -1, index, index);
|
||||
Center(source).DistanceTo(node.Position), source.SourceOrdinal, -1, index, index);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -699,7 +699,7 @@ internal static class JointCuttingPlanSearch
|
||||
source.Prepared.HasCutPerimeter || source.Material is { IsComplete: true, HasDisplayPerimeter: true };
|
||||
|
||||
// Recovery only: solve directed contour transitions rather than assuming
|
||||
// that a centre-to-centre hole tour can cross the already released material.
|
||||
// that a center-to-center hole tour can cross the already released material.
|
||||
// One complete checked chain per outside endpoint; original DFS remains fallback.
|
||||
// arrivalMayCross (safest route only): the rapid onto the part may cross cut contours;
|
||||
// every other motion of the part stays strict.
|
||||
@@ -719,12 +719,12 @@ internal static class JointCuttingPlanSearch
|
||||
Attempt attempt, bool arrivalMayCross, List<Edge> heldBack)
|
||||
{
|
||||
var prepared = source.Prepared;
|
||||
var target = LookAheadCentre(node, sequence, source) - source.Location;
|
||||
var target = LookAheadCenter(node, sequence, source) - source.Location;
|
||||
ContourChoice[] perimeters;
|
||||
if (prepared.HasCutPerimeter)
|
||||
{
|
||||
// Placement origins are authored program coordinates, not a downstream cut.
|
||||
// Use the same material-centre look-ahead as the retained entry search.
|
||||
// Use the same material-center look-ahead as the retained entry search.
|
||||
var ranked = prepared.AutomaticEntryCandidatesWithFallbacks(prepared.PerimeterOrdinal, token: token)
|
||||
.OrderBy(c => c.Choice.Point.DistanceTo(target ?? node.RoutePosition - source.Location)).ToArray();
|
||||
perimeters = ranked.Where(c => Evaluate(source, c).Status == ContourFeasibilityStatus.Clear)
|
||||
@@ -742,12 +742,12 @@ internal static class JointCuttingPlanSearch
|
||||
ContourChoice[][] Holes() => holes ??= Enumerable.Range(0, prepared.PerimeterOrdinal)
|
||||
.Select(c =>
|
||||
{
|
||||
var catalogue = prepared.AutomaticEntryCandidatesWithFallbacks(c, token: token);
|
||||
var clear = catalogue.Where(e => Evaluate(source, e).Status == ContourFeasibilityStatus.Clear).ToArray();
|
||||
var catalog = prepared.AutomaticEntryCandidatesWithFallbacks(c, token: token);
|
||||
var clear = catalog.Where(e => Evaluate(source, e).Status == ContourFeasibilityStatus.Clear).ToArray();
|
||||
// No configured lead-in fits the cutout: pierce where eroding it leaves room.
|
||||
// A widened try adds those pierces to a cutout that refused some of its leads.
|
||||
if (clear.Length == 0 || widened && clear.Length < catalogue.Count)
|
||||
clear = [.. clear, .. PreferredContourEntries.ErodedPierces(prepared, c, catalogue, snapshot.MaxEntries, token)
|
||||
if (clear.Length == 0 || widened && clear.Length < catalog.Count)
|
||||
clear = [.. clear, .. PreferredContourEntries.ErodedPierces(prepared, c, catalog, snapshot.MaxEntries, token)
|
||||
.Where(e => Evaluate(source, e).Status == ContourFeasibilityStatus.Clear)];
|
||||
return clear.Select(e => e.Choice).ToArray();
|
||||
}).ToArray();
|
||||
@@ -778,7 +778,7 @@ internal static class JointCuttingPlanSearch
|
||||
}
|
||||
PostVerificationGeometry.Curve[] CutCurves(ContourChoice choice) => Motion(choice).Motions
|
||||
.Where(m => !m.Rapid && m.Layer is LayerType.Cut or LayerType.Display).Select(m => m.Curve).ToArray();
|
||||
var centres = prepared.HoleCentres(token);
|
||||
var centers = prepared.HoleCenters(token);
|
||||
var bounds = Enumerable.Range(0, prepared.PerimeterOrdinal).Select(prepared.ContourBounds).ToArray();
|
||||
var sweepEntries = new Dictionary<(int Hole, int Next, Vector Direction), ContourChoice[]>();
|
||||
// A hole's sweep entries: at its far side, its far corners and facing the next hole, each with the
|
||||
@@ -791,10 +791,10 @@ internal static class JointCuttingPlanSearch
|
||||
var reach = box.Width + box.Length + 1;
|
||||
var toward = next < 0 ? direction : Unit(bounds[next].Center - box.Center, direction);
|
||||
var across = new Vector(-direction.Y, direction.X) * reach;
|
||||
var catalogue = new[] { direction * reach, toward * reach, direction * reach + across, direction * reach - across }
|
||||
var catalog = new[] { direction * reach, toward * reach, direction * reach + across, direction * reach - across }
|
||||
.Select(offset => prepared.ClosestEntry(hole, box.Center + offset)).Distinct()
|
||||
.Select(c => new ContourEntryCandidate(c, AutomaticEntryKind.TargetFacing)).ToArray();
|
||||
var extra = catalogue.Concat(PreferredContourEntries.ErodedPierces(prepared, hole, catalogue, catalogue.Length, token))
|
||||
var extra = catalog.Concat(PreferredContourEntries.ErodedPierces(prepared, hole, catalog, catalog.Length, token))
|
||||
.Where(e => Evaluate(source, e).Status == ContourFeasibilityStatus.Clear).Select(e => e.Choice);
|
||||
// Stencil pieces overlap along the direction: leave toward the next piece.
|
||||
var stacked = next >= 0 && Near(bounds[next], direction) < Far(box, direction) - OpenNest.Math.Tolerance.Epsilon;
|
||||
@@ -1084,8 +1084,8 @@ internal static class JointCuttingPlanSearch
|
||||
{
|
||||
var onward = double.PositiveInfinity;
|
||||
foreach (var other in remaining)
|
||||
if (other != hole && centres[other] is { } centre)
|
||||
onward = System.Math.Min(onward, departure.DistanceTo(centre + source.Location));
|
||||
if (other != hole && centers[other] is { } center)
|
||||
onward = System.Math.Min(onward, departure.DistanceTo(center + source.Location));
|
||||
if (!double.IsPositiveInfinity(onward))
|
||||
return onward;
|
||||
// The last hole: toward the outside entry, or with none, toward the next part.
|
||||
@@ -1136,9 +1136,9 @@ internal static class JointCuttingPlanSearch
|
||||
|
||||
/// <summary>
|
||||
/// The S03-S08 pipeline for one outside contour, in prepared LOCAL coordinates
|
||||
/// converted exactly once: rank the (fallback-complemented) catalogue toward the
|
||||
/// converted exactly once: rank the (fallback-complemented) catalog toward the
|
||||
/// next cut, lazily filter through the shared validator adapter, cap at MaxEntries
|
||||
/// with side coverage. Empty ONLY when the finite catalogue was fully evaluated and
|
||||
/// with side coverage. Empty ONLY when the finite catalog was fully evaluated and
|
||||
/// nothing fits — then the honest part/contour finding is recorded once.
|
||||
/// </summary>
|
||||
private IReadOnlyList<ContourChoice> AutomaticEntries(Node node, int[] sequence,
|
||||
@@ -1147,7 +1147,7 @@ internal static class JointCuttingPlanSearch
|
||||
var prepared = source.Prepared!;
|
||||
var arrival = node.RoutePosition - source.Location;
|
||||
// One global->local conversion of target and arrival; geometry is already rotated.
|
||||
Vector? local = LookAheadCentre(node, sequence, source) is { } target
|
||||
Vector? local = LookAheadCenter(node, sequence, source) is { } target
|
||||
? target - source.Location : null;
|
||||
return SelectEntries(source, contour, local, arrival, SightTargets(node, sequence, source));
|
||||
}
|
||||
@@ -1303,7 +1303,7 @@ internal static class JointCuttingPlanSearch
|
||||
};
|
||||
}
|
||||
|
||||
// One contour's automatic catalogue ranked toward the next cut, before lead checks. A
|
||||
// One contour's automatic catalog ranked toward the next cut, before lead checks. A
|
||||
// support fallback leads with the entries whose tab steadies the part most.
|
||||
private IReadOnlyList<ContourEntryCandidate> RankedEntries(FixedProgramPlacement source, int contour,
|
||||
Vector? target, Vector arrival, Vector[] sightTargets, SupportEntries supportEntries)
|
||||
@@ -1311,9 +1311,9 @@ internal static class JointCuttingPlanSearch
|
||||
// Last contour has no downstream target, but still needs its exact native
|
||||
// closest-arrival fallback. Keep target null in the ranker: arrival is not
|
||||
// a next cut and must not acquire facing-side priority.
|
||||
var catalogue = source.Prepared.AutomaticEntryCandidatesWithFallbacks(contour, target ?? arrival, token,
|
||||
var catalog = source.Prepared.AutomaticEntryCandidatesWithFallbacks(contour, target ?? arrival, token,
|
||||
supportEntries);
|
||||
var ordered = catalogue.RankTowardNextCut(target, arrival);
|
||||
var ordered = catalog.RankTowardNextCut(target, arrival);
|
||||
// With no lead-out the tool leaves the part from its outside entry. From inside a
|
||||
// slot or pocket the straight way to the next cut runs back over the part just
|
||||
// cut, so entries with the clearest way out lead. Order only: every entry stays.
|
||||
@@ -1340,17 +1340,17 @@ internal static class JointCuttingPlanSearch
|
||||
var prepared = source.Prepared;
|
||||
var holes = Enumerable.Range(0, prepared.PerimeterOrdinal).ToArray();
|
||||
var localArrival = arrival - source.Location;
|
||||
var centres = CuttingHoleOrder.Centres(prepared, localArrival, token);
|
||||
var centers = CuttingHoleOrder.Centers(prepared, localArrival, token);
|
||||
var endpoint = perimeter.Point;
|
||||
var entries = new Dictionary<int, ContourChoice>();
|
||||
var pierces = holes.ToDictionary(h => h, h => centres[h]);
|
||||
var pierces = holes.ToDictionary(h => h, h => centers[h]);
|
||||
var route = holes;
|
||||
try
|
||||
{
|
||||
CountExpansion(source);
|
||||
endpoint = PreferredContourEntries.Pierce(prepared, perimeter, token);
|
||||
route = CuttingHoleOrder.Plan(holes, centres, localArrival, endpoint, token).ToArray();
|
||||
var proposal = PreferredContourEntries.TryPlan(prepared, perimeter, route, centres,
|
||||
route = CuttingHoleOrder.Plan(holes, centers, localArrival, endpoint, token).ToArray();
|
||||
var proposal = PreferredContourEntries.TryPlan(prepared, perimeter, route, centers,
|
||||
localArrival, candidate => Evaluate(source, candidate), token);
|
||||
if (proposal.IsPreferred)
|
||||
foreach (var choice in proposal.HoleChoices)
|
||||
@@ -1476,7 +1476,7 @@ internal static class JointCuttingPlanSearch
|
||||
var ready = (snapshot.PreservePartOrder ? [snapshot.Placements[node.Order.Length]]
|
||||
: snapshot.Placements.Where(p => !finished.Contains(p.SourceOrdinal)
|
||||
&& snapshot.Dependencies.IsReady(p.SourceOrdinal, finished)))
|
||||
.OrderBy(p => Centre(p).DistanceTo(node.Position)).ThenBy(p => p.SourceOrdinal).ToArray();
|
||||
.OrderBy(p => Center(p).DistanceTo(node.Position)).ThenBy(p => p.SourceOrdinal).ToArray();
|
||||
// Nearest first, the first part with a route that passes every check wins. When
|
||||
// none of the nearby parts has one, the least unsafe of them is cut.
|
||||
SafestStep best = null;
|
||||
@@ -1532,9 +1532,9 @@ internal static class JointCuttingPlanSearch
|
||||
// the fewest violations to the part's route, nearest along the tour on a tie.
|
||||
var approach = arrival - source.Location;
|
||||
var holes = Enumerable.Range(0, prepared.PerimeterOrdinal).ToArray();
|
||||
var centres = CuttingHoleOrder.Centres(prepared, approach, token);
|
||||
var remaining = CuttingHoleOrder.Plan(holes, centres, approach, null, token).ToList();
|
||||
var onward = LookAheadCentre(node, KeptSequence, source) - source.Location;
|
||||
var centers = CuttingHoleOrder.Centers(prepared, approach, token);
|
||||
var remaining = CuttingHoleOrder.Plan(holes, centers, approach, null, token).ToList();
|
||||
var onward = LookAheadCenter(node, KeptSequence, source) - source.Location;
|
||||
var choices = new List<ContourChoice>();
|
||||
Program program = null;
|
||||
OwnedExecution execution = null;
|
||||
@@ -1551,7 +1551,7 @@ internal static class JointCuttingPlanSearch
|
||||
// Leave each hole toward the next one along the tour, so its exit rapid does
|
||||
// not run back across it; the perimeter faces the next part.
|
||||
var next = remaining.FirstOrDefault(h => h != contour, -1);
|
||||
var entries = SelectEntries(source, contour, next >= 0 ? centres[next] : onward, local);
|
||||
var entries = SelectEntries(source, contour, next >= 0 ? centers[next] : onward, local);
|
||||
if (entries.Count == 0)
|
||||
entries = [prepared.ClosestEntry(contour, local)];
|
||||
foreach (var entry in entries)
|
||||
|
||||
@@ -36,7 +36,7 @@ internal static class PreferredContourEntries
|
||||
/// Resolves one preferred entry per hole on <paramref name="holeRoute"/> (cut order,
|
||||
/// from <see cref="CuttingHoleOrder"/>), ending at <paramref name="perimeterChoice"/>.
|
||||
/// Arrival proxies avoid circularity: the actual (local) part arrival for the first
|
||||
/// hole, otherwise the previous hole's centre. Facing target (the downstream actual
|
||||
/// hole, otherwise the previous hole's center. Facing target (the downstream actual
|
||||
/// pierce) and tier still rank first. <paramref name="evaluate"/> is the S07 adapter
|
||||
/// verdict; candidates it refuses or leaves uncertain are not preferred.
|
||||
/// </summary>
|
||||
@@ -44,7 +44,7 @@ internal static class PreferredContourEntries
|
||||
PreparedContours prepared,
|
||||
ContourChoice perimeterChoice,
|
||||
IReadOnlyList<int> holeRoute,
|
||||
IReadOnlyList<Vector> centresByOrdinal,
|
||||
IReadOnlyList<Vector> centersByOrdinal,
|
||||
Vector arrival,
|
||||
Func<ContourEntryCandidate, ContourFeasibilityVerdict> evaluate,
|
||||
CancellationToken token = default)
|
||||
@@ -53,8 +53,8 @@ internal static class PreferredContourEntries
|
||||
throw new ArgumentException("Prepared contours are required.", nameof(prepared));
|
||||
if (perimeterChoice == null || !ReferenceEquals(perimeterChoice.Owner, prepared))
|
||||
throw new ArgumentException("The perimeter choice must belong to this preparation.", nameof(perimeterChoice));
|
||||
if (holeRoute == null || centresByOrdinal == null || evaluate == null)
|
||||
throw new ArgumentException("Hole route, centres and evaluation are required.");
|
||||
if (holeRoute == null || centersByOrdinal == null || evaluate == null)
|
||||
throw new ArgumentException("Hole route, centers and evaluation are required.");
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (holeRoute.Count == 0)
|
||||
return new(Array.Empty<ContourChoice>(), ContourSelectionShortfall.Exhausted, null, null);
|
||||
@@ -67,11 +67,11 @@ internal static class PreferredContourEntries
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
var contour = holeRoute[index];
|
||||
var arrivalProxy = index == 0 ? arrival : centresByOrdinal[holeRoute[index - 1]];
|
||||
// The S04 merged catalogue: preferred kinds when present, fallback kinds
|
||||
var arrivalProxy = index == 0 ? arrival : centersByOrdinal[holeRoute[index - 1]];
|
||||
// The S04 merged catalog: preferred kinds when present, fallback kinds
|
||||
// (compass points, arc midpoints, ...) otherwise — pure circles included.
|
||||
var catalogue = prepared.AutomaticEntryCandidatesWithFallbacks(contour, target, token);
|
||||
var ranked = catalogue.RankTowardNextCut(target, arrivalProxy).DepartingClear(prepared, target, token);
|
||||
var catalog = prepared.AutomaticEntryCandidatesWithFallbacks(contour, target, token);
|
||||
var ranked = catalog.RankTowardNextCut(target, arrivalProxy).DepartingClear(prepared, target, token);
|
||||
var selection = ContourEntrySelection.Select(ranked, evaluate, 1, token);
|
||||
if (selection.Choices.Count == 0
|
||||
&& selection.Shortfall is ContourSelectionShortfall.Exhausted or ContourSelectionShortfall.Incomplete)
|
||||
|
||||
@@ -66,8 +66,8 @@ namespace OpenNest.Engine.Fill
|
||||
/// </summary>
|
||||
public double AchievedSpacing;
|
||||
|
||||
/// <summary>True when the run was cancelled; no positions were changed.</summary>
|
||||
public bool Cancelled;
|
||||
/// <summary>True when the run was canceled; no positions were changed.</summary>
|
||||
public bool Canceled;
|
||||
|
||||
/// <summary>Pairs still below the attempted spacing and invalid
|
||||
/// work-area poses, including unresolved negative clearances when no
|
||||
@@ -136,7 +136,7 @@ namespace OpenNest.Engine.Fill
|
||||
}
|
||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||
{
|
||||
result.Cancelled = true;
|
||||
result.Canceled = true;
|
||||
return result;
|
||||
}
|
||||
var entry = context.Positions(); // all parts; movers are [0, count)
|
||||
@@ -148,14 +148,14 @@ namespace OpenNest.Engine.Fill
|
||||
}
|
||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||
{
|
||||
result.Cancelled = true;
|
||||
result.Canceled = true;
|
||||
return result;
|
||||
}
|
||||
result.AchievedSpacing = System.Math.Max(0, sMin);
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
result.Cancelled = true;
|
||||
result.Canceled = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -192,14 +192,14 @@ namespace OpenNest.Engine.Fill
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
result.Cancelled = true;
|
||||
result.Canceled = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
var probe = Probe(s);
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
result.Cancelled = true;
|
||||
result.Canceled = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ namespace OpenNest.Engine.Fill
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
result.Cancelled = true;
|
||||
result.Canceled = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ namespace OpenNest.Engine.Fill
|
||||
var midProbe = Probe(mid);
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
result.Cancelled = true;
|
||||
result.Canceled = true;
|
||||
return result;
|
||||
}
|
||||
if (midProbe.Converged)
|
||||
@@ -253,7 +253,7 @@ namespace OpenNest.Engine.Fill
|
||||
observer?.Invoke(Checkpoint.BeforeApply);
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
result.Cancelled = true;
|
||||
result.Canceled = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -270,12 +270,12 @@ namespace OpenNest.Engine.Fill
|
||||
}
|
||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||
{
|
||||
result.Cancelled = true;
|
||||
result.Canceled = true;
|
||||
return result;
|
||||
}
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
result.Cancelled = true;
|
||||
result.Canceled = true;
|
||||
return result;
|
||||
}
|
||||
Apply(selected, entry, positions);
|
||||
@@ -1090,7 +1090,7 @@ namespace OpenNest.Engine.Fill
|
||||
if (TryNativeDisk(moving, out var movingCenter, out var movingRadius) &&
|
||||
TryNativeDisk(stationary, out var fixedCenter, out var fixedRadius))
|
||||
{
|
||||
// Full circles are convex. The relative centre distance
|
||||
// Full circles are convex. The relative center distance
|
||||
// must increase from the start; its ray intersects the
|
||||
// summed-radius disk at most once in forward travel.
|
||||
var relative = movingCenter + movePos - fixedCenter - fixedPos;
|
||||
|
||||
@@ -228,7 +228,7 @@ namespace OpenNest.Engine.Fill
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Debug.WriteLine("[PairFiller] Cancelled mid-phase, using results so far");
|
||||
Debug.WriteLine("[PairFiller] Canceled mid-phase, using results so far");
|
||||
}
|
||||
|
||||
Debug.WriteLine($"[PairFiller] Best pair result: {best?.Count ?? 0} parts");
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace OpenNest.Engine.Fill
|
||||
/// <summary>
|
||||
/// <paramref name="boxObstacles"/> are kept-out rectangles (e.g. inflated clamp
|
||||
/// zones) treated like occupied space: they are excluded from the remnant areas
|
||||
/// every fill attempt sees. Null keeps today's behaviour exactly.
|
||||
/// every fill attempt sees. Null keeps today's behavior exactly.
|
||||
/// </summary>
|
||||
public RemnantFiller(Box workArea, double spacing, IEnumerable<Box> boxObstacles = null)
|
||||
{
|
||||
|
||||
@@ -245,7 +245,7 @@ internal static class ClampRemovalRecovery
|
||||
if (!CanRemoveClamps(stock))
|
||||
continue;
|
||||
token.ThrowIfCancellationRequested();
|
||||
// Placement-frame X is signed from the origin, so |centre X| is the distance
|
||||
// Placement-frame X is signed from the origin, so |center X| is the distance
|
||||
// along the edge in every quadrant.
|
||||
var zones = stock.ClampZones
|
||||
.OrderBy(z => System.Math.Abs(z.Bounds.X + z.Bounds.Length / 2))
|
||||
@@ -272,7 +272,7 @@ internal static class ClampRemovalRecovery
|
||||
searches.Add((stock, zones, fitting, mandatory));
|
||||
}
|
||||
|
||||
// Then the fewest clamps overall (any set, not only neighbours: a notch can leave a
|
||||
// Then the fewest clamps overall (any set, not only neighbors: a notch can leave a
|
||||
// middle clamp in place), size by size across stocks in offered order; within a stock
|
||||
// the optional additions nearest the origin first.
|
||||
var maxSize = searches.Count == 0 ? 0 : searches.Max(c => c.Zones.Count);
|
||||
|
||||
@@ -35,7 +35,7 @@ internal static class InteractiveClamps
|
||||
|
||||
/// <summary>
|
||||
/// Placement keep-outs inflated by the full <see cref="Plate.ClampClearance"/> - the same
|
||||
/// convention the Rectangles lane blocks and engines honour, so fill and pack produce
|
||||
/// convention the Rectangles lane blocks and engines honor, so fill and pack produce
|
||||
/// placements with at least that clearance from the raw zone.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<Box> ClearanceRectangles(Plate plate) =>
|
||||
|
||||
@@ -26,7 +26,7 @@ internal static class NestJobPlacementValidator
|
||||
throw new InvalidOperationException("The plate nester returned a null candidate.");
|
||||
var counts = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
var placed = new List<ShapeTopology>();
|
||||
// Spacing outlines per placement, prepared only once a neighbour comes near.
|
||||
// Spacing outlines per placement, prepared only once a neighbor comes near.
|
||||
var poses = new List<(JobPartGeometry Geometry, NestJobPlacement Pose)>();
|
||||
var spaced = new List<NestLayoutCheck.PreparedPlacement>();
|
||||
var geometries = new Dictionary<string, JobPartGeometry>(StringComparer.Ordinal);
|
||||
|
||||
@@ -539,7 +539,7 @@ public static class NestLayoutCheck
|
||||
/// material, with holes subtracted on both sides - so a small part
|
||||
/// nested inside another part's cutout (part-in-part) is legal as long as
|
||||
/// it clears the cutout's edge by the spacing. Pairs are pruned with an
|
||||
/// X-sorted sweep over bounding boxes so only neighbours reach the
|
||||
/// X-sorted sweep over bounding boxes so only neighbors reach the
|
||||
/// polygon clipper. A pair the outlines flag at a positive spacing is
|
||||
/// reported only when <see cref="NativeContours"/> confirm it.
|
||||
/// </summary>
|
||||
|
||||
@@ -660,7 +660,7 @@ internal class PlateFiller
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Debug.WriteLine("[RunPipeline] Cancelled, returning current best");
|
||||
Debug.WriteLine("[RunPipeline] Canceled, returning current best");
|
||||
if (context.ProvisionalLeads(context.CurrentBest))
|
||||
{
|
||||
context.CurrentBest = context.Provisional;
|
||||
|
||||
@@ -77,7 +77,7 @@ public sealed class DefaultNestingEngine : INestingEngine
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// A cancelled solve is rethrown by the token checks around each candidate.
|
||||
// A canceled solve is rethrown by the token checks around each candidate.
|
||||
firstFailure ??= ExceptionDispatchInfo.Capture(ex);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ internal sealed record BarType(
|
||||
/// validation-aware bounds as the Rectangles lane so box contact passes the layout check.
|
||||
/// Unlike Rectangles, all rotations are kept: a bar accepts any rotation whose cross extent
|
||||
/// fits its width, and the compaction pass may prefer a different rotation of the same length
|
||||
/// (a part turned 180 degrees so its mitred end nests against its neighbour).
|
||||
/// (a part turned 180 degrees so its mitred end nests against its neighbor).
|
||||
/// </summary>
|
||||
internal static class BarCatalog
|
||||
{
|
||||
|
||||
@@ -175,18 +175,18 @@ internal sealed class BarLayout
|
||||
boxStart = segment.Start;
|
||||
|
||||
(BarPart Part, Part Shape)? best = null;
|
||||
var neighbours = laid.Skip(first).ToList();
|
||||
var slide = neighbours.Count > 0 && !(Boxy(type, bar) && neighbours.All(n => Boxy(n.Part.Type, bar)));
|
||||
var neighbors = laid.Skip(first).ToList();
|
||||
var slide = neighbors.Count > 0 && !(Boxy(type, bar) && neighbors.All(n => Boxy(n.Part.Type, bar)));
|
||||
foreach (var orientation in bar.Fits[type.Index].Take(slide ? MaxSlideOrientations : 1))
|
||||
{
|
||||
var start = boxStart;
|
||||
var shape = Shape(bar, type, orientation, start);
|
||||
if (slide)
|
||||
{
|
||||
var travel = Slide(bar, shape, neighbours, segment, start);
|
||||
var travel = Slide(bar, shape, neighbors, segment, start);
|
||||
if (travel > 0)
|
||||
{
|
||||
start = Confirm(bar, type, orientation, start, travel, neighbours);
|
||||
start = Confirm(bar, type, orientation, start, travel, neighbors);
|
||||
shape = Shape(bar, type, orientation, start);
|
||||
}
|
||||
}
|
||||
@@ -200,7 +200,7 @@ internal sealed class BarLayout
|
||||
}
|
||||
|
||||
/// <summary>How far the part can slide back before its offset outline meets a laid part.</summary>
|
||||
private static double Slide(BarStock bar, Part shape, List<(BarPart Part, Part Shape)> neighbours,
|
||||
private static double Slide(BarStock bar, Part shape, List<(BarPart Part, Part Shape)> neighbors,
|
||||
BarSegment segment, double start)
|
||||
{
|
||||
var room = start - segment.Start;
|
||||
@@ -208,7 +208,7 @@ internal sealed class BarLayout
|
||||
return 0;
|
||||
var direction = bar.AlongX ? new Vector(-1, 0) : new Vector(0, -1);
|
||||
var huge = new Box(-1e9, -1e9, 2e9, 2e9);
|
||||
var obstacles = neighbours.Select(n => n.Shape).ToList();
|
||||
var obstacles = neighbors.Select(n => n.Shape).ToList();
|
||||
var travel = Compactor.Push(new List<Part> { shape }, obstacles, huge, bar.Spacing, direction);
|
||||
return System.Math.Clamp(travel, 0, room);
|
||||
}
|
||||
@@ -218,7 +218,7 @@ internal sealed class BarLayout
|
||||
/// laid part the new one can reach; falls back to the box-to-box start.
|
||||
/// </summary>
|
||||
private double Confirm(BarStock bar, BarType type, BarOrientation orientation, double boxStart, double travel,
|
||||
List<(BarPart Part, Part Shape)> neighbours)
|
||||
List<(BarPart Part, Part Shape)> neighbors)
|
||||
{
|
||||
if (type.Geometry == null)
|
||||
return boxStart;
|
||||
@@ -229,7 +229,7 @@ internal sealed class BarLayout
|
||||
break;
|
||||
var start = boxStart - slid;
|
||||
var pose = Placement(bar, type, orientation, start);
|
||||
var clear = neighbours
|
||||
var clear = neighbors
|
||||
.Where(n => n.Part.AxisEnd + bar.Spacing + 1e-6 > start)
|
||||
.All(n => n.Part.Type.Geometry != null && NestLayoutCheck.Clears(
|
||||
type.Geometry, pose, n.Part.Type.Geometry,
|
||||
|
||||
@@ -52,7 +52,7 @@ internal sealed record BarPlan(IReadOnlyList<PackedBar> Bars, IReadOnlyList<BarI
|
||||
/// <summary>
|
||||
/// One-dimensional cut-list packer, ported from CutList.Core (the saw cut-list optimizer).
|
||||
/// Lengths are "inflated": every item carries one part spacing and every segment's capacity
|
||||
/// gets one spacing back, so a run of items needs spacing between neighbours but none after the
|
||||
/// gets one spacing back, so a run of items needs spacing between neighbors but none after the
|
||||
/// last, exactly like a saw kerf in CutList's <c>Bin</c>.
|
||||
///
|
||||
/// Small jobs (at most <see cref="ExhaustiveItemLimit"/> items, one free run per bar, unlimited
|
||||
|
||||
@@ -168,7 +168,7 @@ public sealed class FlatBarNestingEngine : INestingEngine
|
||||
|
||||
/// <summary>
|
||||
/// Prices the demand as one stock's plan, using its packed envelopes: the cheaper of the
|
||||
/// cut-list plan and repeated best fills, so the estimate does not favour bars that leave
|
||||
/// cut-list plan and repeated best fills, so the estimate does not favor bars that leave
|
||||
/// first-fit-decreasing an awkward remainder.
|
||||
/// </summary>
|
||||
private static Estimate EstimatePlan(NestJob job, BarStock bar, IReadOnlyList<BarType> types, BarLayout layout,
|
||||
|
||||
@@ -44,7 +44,7 @@ internal static class SheetPacker
|
||||
/// <summary>
|
||||
/// Packing size along one axis: a box that exceeds the sheet by no more than
|
||||
/// <see cref="OverhangAllowance"/> packs as exactly the sheet's size. It then spans the whole
|
||||
/// axis, so it has no neighbour there and the overhang lands only past the work-area edge.
|
||||
/// axis, so it has no neighbor there and the overhang lands only past the work-area edge.
|
||||
/// </summary>
|
||||
public static double PackSize(double size, double sheet) =>
|
||||
size > sheet && size <= sheet + OverhangAllowance ? sheet : size;
|
||||
|
||||
@@ -31,8 +31,8 @@ internal static class AreaPacker
|
||||
|
||||
/// <summary>
|
||||
/// Returns the packed parts. Items and the area grow by <paramref name="spacing"/> on their
|
||||
/// right/top sides, so neighbouring parts are one spacing apart and the last may touch the
|
||||
/// area's edge. A cancelled token stops between candidates and keeps the best layout so far.
|
||||
/// right/top sides, so neighboring parts are one spacing apart and the last may touch the
|
||||
/// area's edge. A canceled token stops between candidates and keeps the best layout so far.
|
||||
/// </summary>
|
||||
/// <param name="workArea">Plate work area, when known. Only matching right/top boundaries
|
||||
/// receive the existing work-area overhang allowance; internal areas remain strict.</param>
|
||||
|
||||
@@ -73,7 +73,7 @@ internal sealed class MaxRectsSheet
|
||||
foreach (var f in free)
|
||||
{
|
||||
// Clamp only at the bin's allowed plate boundaries. Internal free edges still
|
||||
// use Eps, and real sizes are retained there to preserve neighbour spacing.
|
||||
// use Eps, and real sizes are retained there to preserve neighbor spacing.
|
||||
var packW = w > f.W && w <= f.W + rightSlack && System.Math.Abs(f.Right - Width) <= Eps
|
||||
? f.W : w;
|
||||
var packH = h > f.H && h <= f.H + topSlack && System.Math.Abs(f.Top - Height) <= Eps
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace OpenNest.Engine.Sequencing
|
||||
return (0, double.PositiveInfinity);
|
||||
|
||||
var best = (Along: 0.0, Distance: point.DistanceTo(path[0]));
|
||||
var travelled = 0.0;
|
||||
var traveled = 0.0;
|
||||
for (var i = 1; i < path.Count; i++)
|
||||
{
|
||||
var start = path[i - 1];
|
||||
@@ -94,8 +94,8 @@ namespace OpenNest.Engine.Sequencing
|
||||
var nearest = new Vector(start.X + dx * t, start.Y + dy * t);
|
||||
var distance = point.DistanceTo(nearest);
|
||||
if (distance < best.Distance - Tolerance.Epsilon)
|
||||
best = (travelled + length * t, distance);
|
||||
travelled += length;
|
||||
best = (traveled + length * t, distance);
|
||||
traveled += length;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace OpenNest.Engine.Strategies
|
||||
/// <summary>
|
||||
/// A cheap layout shown before the slow strategies run, so it can be accepted early. It
|
||||
/// never becomes <see cref="CurrentBest"/> while strategies run, so they pick the same
|
||||
/// winner as without it; the pipeline returns it only when cancelled while it still leads.
|
||||
/// winner as without it; the pipeline returns it only when canceled while it still leads.
|
||||
/// </summary>
|
||||
public List<Part> Provisional { get; set; }
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ public class McpEngineHarnessProcessTests(McpEngineHarnessChildFixture fixture)
|
||||
descendant = McpEngineHarnessChildFixture.GetProcess(root, "child");
|
||||
await cancellation.CancelAsync();
|
||||
var result = await run.WaitAsync(Watchdog);
|
||||
Assert.Contains("cancelled", result, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("canceled", result, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain("Cleanup failed", result);
|
||||
await parent.WaitForExitAsync().WaitAsync(Watchdog);
|
||||
await descendant.WaitForExitAsync().WaitAsync(Watchdog);
|
||||
@@ -142,12 +142,12 @@ public class McpEngineHarnessProcessTests(McpEngineHarnessChildFixture fixture)
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AlreadyCancelled_DoesNotStartChild()
|
||||
public async Task AlreadyCanceled_DoesNotStartChild()
|
||||
{
|
||||
var root = fixture.NewRun();
|
||||
var result = await fixture.Tool().TestEngine(root,
|
||||
cancellationToken: new CancellationToken(true)).WaitAsync(Watchdog);
|
||||
Assert.Contains("cancelled", result, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("canceled", result, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.False(File.Exists(root + ".parent"));
|
||||
}
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ public class NestingPipelineTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Console_CancelledSolveDoesNotSave()
|
||||
public void Console_CanceledSolveDoesNotSave()
|
||||
{
|
||||
var input = Path.Combine(directory, "input.nest");
|
||||
var output = Path.Combine(directory, "output.nest");
|
||||
@@ -412,7 +412,7 @@ public class NestingPipelineTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Console_CancelledOrdinaryFillDoesNotOverwriteOutput()
|
||||
public void Console_CanceledOrdinaryFillDoesNotOverwriteOutput()
|
||||
{
|
||||
var input = Path.Combine(directory, "input.nest");
|
||||
var output = Path.Combine(directory, "output.nest");
|
||||
|
||||
@@ -204,7 +204,7 @@ namespace OpenNest.IO
|
||||
return root.ToJsonString(NestFormat.JsonOptions);
|
||||
}
|
||||
|
||||
// StreamReader honours the byte-order mark the nest writer emits.
|
||||
// StreamReader honors the byte-order mark the nest writer emits.
|
||||
private static string ReadText(ZipArchiveEntry entry)
|
||||
{
|
||||
using var stream = entry.Open();
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace OpenNest.IO
|
||||
/// <summary>
|
||||
/// Reads the copy back as a new nest. Each drawing of <paramref name="live"/> whose saved
|
||||
/// content equals a drawing in the copy is used in its place, so caches keyed by the
|
||||
/// drawing (best fits, fill results), its colour and its id survive; a drawing whose
|
||||
/// drawing (best fits, fill results), its color and its id survive; a drawing whose
|
||||
/// content changed comes from the copy. Reused drawings' nested counts then follow the
|
||||
/// returned nest, which replaces <paramref name="live"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -139,8 +139,8 @@ namespace OpenNest.Mcp.Tools
|
||||
+ "acceptWarnings=true to apply it. Nothing changed.";
|
||||
|
||||
var commit = proposal.Apply(acceptWarnings, cancellationToken);
|
||||
if (commit.Status == CuttingCommitStatus.Cancelled)
|
||||
return "Apply cancelled. Nothing changed.";
|
||||
if (commit.Status == CuttingCommitStatus.Canceled)
|
||||
return "Apply canceled. Nothing changed.";
|
||||
|
||||
// Applied or refused, a proposal is used at most once.
|
||||
_session.CuttingProposal = null;
|
||||
|
||||
@@ -480,7 +480,7 @@ namespace OpenNest.Mcp.Tools
|
||||
}
|
||||
// Reserve physical copies separately from display-layout targets. Do this
|
||||
// only after mapping every layout, so a repeat cannot steal a residual's
|
||||
// target. A rejected/cancelled proposal reserves nothing and deletes nothing.
|
||||
// target. A rejected/canceled proposal reserves nothing and deletes nothing.
|
||||
var reservations = new List<List<Plate>>();
|
||||
foreach (var proposed in result.Plates)
|
||||
{
|
||||
|
||||
@@ -123,7 +123,7 @@ namespace OpenNest.Mcp.Tools
|
||||
var error = $"Error running test harness: {ex.Message}";
|
||||
if (ex is OperationCanceledException)
|
||||
{
|
||||
var reason = cancellationToken.IsCancellationRequested ? "cancelled" : "timed out";
|
||||
var reason = cancellationToken.IsCancellationRequested ? "canceled" : "timed out";
|
||||
error = $"Error running test harness: {reason}.";
|
||||
}
|
||||
if (started)
|
||||
|
||||
@@ -152,7 +152,7 @@ public class BenchmarkScoringTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Run_OverlappingNeighbours_AreStillCaughtBySweep()
|
||||
public void Run_OverlappingNeighbors_AreStillCaughtBySweep()
|
||||
{
|
||||
var job = Job(0.5, (Rect(10, 5), 3));
|
||||
|
||||
@@ -245,7 +245,7 @@ public class BenchmarkScoringTests
|
||||
return new Drawing("rect", pgm);
|
||||
}
|
||||
|
||||
/// <summary>20x20 square with a 10x10 cutout centred in it.</summary>
|
||||
/// <summary>20x20 square with a 10x10 cutout centerd in it.</summary>
|
||||
private static Drawing Frame()
|
||||
{
|
||||
var pgm = new OpenNest.CNC.Program();
|
||||
|
||||
@@ -238,7 +238,7 @@ namespace OpenNest.Tests.Benchmark
|
||||
/// material, with holes subtracted on both sides - so a small part
|
||||
/// nested inside another part's cutout (part-in-part) is legal as long as
|
||||
/// it clears the cutout's edge by the spacing. Pairs are pruned with an
|
||||
/// X-sorted sweep over bounding boxes so only neighbours reach the
|
||||
/// X-sorted sweep over bounding boxes so only neighbors reach the
|
||||
/// polygon clipper.
|
||||
/// </summary>
|
||||
private static void ValidateSpacing(
|
||||
|
||||
@@ -27,7 +27,7 @@ public class NestLayoutCheckTimingTests
|
||||
var job = new NestJob(new[] { part }, new[] { stock });
|
||||
var builder = new NestJobResultBuilder(job);
|
||||
// Dense bounding-box candidates exercise the pair gate; both overlapping and
|
||||
// disjoint diagonal neighbours occur. Violations are deliberate and compared.
|
||||
// disjoint diagonal neighbors occur. Violations are deliberate and compared.
|
||||
builder.AddSheet(stock, Enumerable.Range(0, count)
|
||||
.Select(i => (part.Id, 2 + (i % 10) * 1.5, 2 + (i / 10) * 1.5, 0.0)));
|
||||
var result = builder.Build(NestJobStopReason.Completed);
|
||||
|
||||
@@ -54,7 +54,7 @@ public class BestFitCacheTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetOrCompute_Cancelled_StopsWaitingAndCachesBackgroundResult()
|
||||
public async Task GetOrCompute_Canceled_StopsWaitingAndCachesBackgroundResult()
|
||||
{
|
||||
var drawing = MakeRotatedTShape();
|
||||
var canonical = CanonicalFrame.AsCanonicalCopy(drawing);
|
||||
|
||||
@@ -45,7 +45,7 @@ public class BreakoffBuilderTests
|
||||
private static Breakoff On(params Entity[] join) => new() { EdgeIds = join.Select(e => e.Id).ToList() };
|
||||
|
||||
[Fact]
|
||||
public void Layout_CutsTheJoiningLineBetweenTabs_TravellingWithTheScrapOnTheLeft()
|
||||
public void Layout_CutsTheJoiningLineBetweenTabs_TravelingWithTheScrapOnTheLeft()
|
||||
{
|
||||
var (entities, join) = Chord();
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ public class ProgramSubProgramTransformTests
|
||||
{
|
||||
private const double QuarterTurn = System.Math.PI / 2;
|
||||
|
||||
/// <summary>Incremental hole sub-program: a lead-in from the hole centre to
|
||||
/// <summary>Incremental hole sub-program: a lead-in from the hole center to
|
||||
/// (1, 0), then a full radius-1 circle.</summary>
|
||||
private static Program MakeHoleSub()
|
||||
{
|
||||
@@ -58,7 +58,7 @@ public class ProgramSubProgramTransformTests
|
||||
pgm.Rotate(QuarterTurn);
|
||||
|
||||
// (5, 5) -> (-5, 5) and (15, 5) -> (-5, 15); each lead-in ends one unit along
|
||||
// the rotated +X axis, i.e. at +Y from its centre.
|
||||
// the rotated +X axis, i.e. at +Y from its center.
|
||||
var ends = LeadInEnds(pgm);
|
||||
Assert.Equal(2, ends.Count);
|
||||
AssertPoint(-5, 6, ends[0]);
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace OpenNest.Tests.CincinnatiCIFiber;
|
||||
/// <summary>
|
||||
/// Output-contract tests for the Cincinnati CI Fiber post, checked against the
|
||||
/// structure of a Cincinnati machine sample NC: header block,
|
||||
/// restart jump, N-labelled contour blocks with /L macro lines, G41
|
||||
/// restart jump, N-labeled contour blocks with /L macro lines, G41
|
||||
/// selection, trimmed coordinate format and the M50/M30/% tail.
|
||||
/// </summary>
|
||||
public class CIFiberPostProcessorTests
|
||||
@@ -44,7 +44,7 @@ public class CIFiberPostProcessorTests
|
||||
|
||||
var pgm = new Program(Mode.Absolute);
|
||||
|
||||
// Hole sub-program: CCW full circle r=0.5 centred on the call offset,
|
||||
// Hole sub-program: CCW full circle r=0.5 centerd on the call offset,
|
||||
// linear lead-in from below, left absolute then flipped to
|
||||
// incremental — exactly what ContourCuttingStrategy emits for a hole.
|
||||
var hole = new Program(Mode.Absolute);
|
||||
@@ -125,7 +125,7 @@ public class CIFiberPostProcessorTests
|
||||
Assert.Equal("G0X6Y6.4", lines[21]); // pierce = part(1,2) + hole(5,5) + (0,-0.6)
|
||||
Assert.Equal("/L \"L2\"", lines[22]);
|
||||
Assert.Equal("G41", lines[23]);
|
||||
Assert.Equal("G1X6Y6.5", lines[24]); // contour start = hole centre + (0,-0.5)
|
||||
Assert.Equal("G1X6Y6.5", lines[24]); // contour start = hole center + (0,-0.5)
|
||||
Assert.Equal("/L \"L6\"", lines[25]);
|
||||
Assert.Equal("G3X6Y6.5I0J0.5", lines[26]); // I/J incremental from arc start
|
||||
Assert.Equal("/L \"ZHSOFF\"", lines[27]);
|
||||
@@ -221,7 +221,7 @@ public class CIFiberPostProcessorTests
|
||||
var radial = new Vector(
|
||||
-double.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture),
|
||||
-double.Parse(m.Groups[2].Value, CultureInfo.InvariantCulture)
|
||||
); // centre -> start
|
||||
); // center -> start
|
||||
travel = cut.StartsWith("G3")
|
||||
? new Vector(-radial.Y, radial.X)
|
||||
: new Vector(radial.Y, -radial.X);
|
||||
@@ -265,8 +265,8 @@ public class CIFiberPostProcessorTests
|
||||
Assert.Equal("G0X-6.4Y6", lines[21]);
|
||||
// Contour start local (0,-0.5) -> (0.5,0) = (-6.5,6).
|
||||
Assert.Equal("G1X-6.5Y6", lines[24]);
|
||||
// Full CCW circle, centre local (0,0.5) from the start -> (-0.5,0):
|
||||
// centre at (-7,6), so I=-0.5 J=0.
|
||||
// Full CCW circle, center local (0,0.5) from the start -> (-0.5,0):
|
||||
// center at (-7,6), so I=-0.5 J=0.
|
||||
Assert.Equal("G3X-6.5Y6I-0.5J0", lines[26]);
|
||||
// Perimeter corner local (0,10) -> (-10,0): pierce (-2,1)+(0.5,0)...
|
||||
// pierce local (0,-0.5)->(0.5,0) = (-1.5,1); corner (-10,1)... check
|
||||
|
||||
@@ -57,7 +57,7 @@ public class CIFiberSampleRegressionTests
|
||||
// OpenNest's own short perpendicular linear leads; circles take the
|
||||
// internal lead. TF5200 13.2.4.1 requires a LINEAR
|
||||
// first motion block after G41/G42, so holes must have linear
|
||||
// leads. (The sample itself pierces holes at their centre and
|
||||
// leads. (The sample itself pierces holes at their center and
|
||||
// uses 0.125 in perimeter leads; see the saved-lead-in test.)
|
||||
InternalLeadIn = new LineLeadIn { Length = 0.075, ApproachAngle = 90 },
|
||||
ExternalLeadIn = new LineLeadIn { Length = 0.075, ApproachAngle = 90 },
|
||||
|
||||
@@ -485,7 +485,7 @@ public class ClampRailTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RailClampAtHonoursTheToleranceAndNeedsARail()
|
||||
public void RailClampAtHonorsTheToleranceAndNeedsARail()
|
||||
{
|
||||
var plate = Sheet(48, 120);
|
||||
Assert.Null(plate.RailClampAt(new Vector(36, 0.3))); // no rail
|
||||
|
||||
@@ -9,7 +9,7 @@ using OpenNest.Shapes;
|
||||
namespace OpenNest.Tests.CutOffs;
|
||||
|
||||
/// <summary>
|
||||
/// Trimming a piece between two crossing lines leaves a gap from one crossing centre to the other,
|
||||
/// Trimming a piece between two crossing lines leaves a gap from one crossing center to the other,
|
||||
/// and the line splits into segments as it does around a part. Extend closes a gap; every consumer
|
||||
/// of a cut-off's limits treats a gap as uncut.
|
||||
/// </summary>
|
||||
@@ -43,7 +43,7 @@ public class CutOffGapTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Trim_MiddlePiece_LeavesAGapBetweenTheCrossingCentres()
|
||||
public void Trim_MiddlePiece_LeavesAGapBetweenTheCrossingCenters()
|
||||
{
|
||||
var (plate, horizontal) = Gapped();
|
||||
|
||||
|
||||
@@ -66,8 +66,8 @@ public class CutOffRoundoffTests
|
||||
Assert.Empty(AutomaticCutOffPlanner.Create(loadedPlate, options, settings).Definitions);
|
||||
}
|
||||
|
||||
// A pie slice whose arc ends 2d further from its centre than it starts. ConvertProgram
|
||||
// only refits centres beyond Tolerance.Epsilon, so it rebuilds the start point ~2d past
|
||||
// A pie slice whose arc ends 2d further from its center than it starts. ConvertProgram
|
||||
// only refits centers beyond Tolerance.Epsilon, so it rebuilds the start point ~2d past
|
||||
// the cached bounds, as rotated imported arcs do at d = 2.5e-11.
|
||||
private static Plate MakeSlicePlate(double d)
|
||||
{
|
||||
|
||||
Loaded 100 of 230 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user