mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-05 13:32:13 -04:00
fix(cutting): create plate-scoped requests through a factory
A CuttingPlanRequest constructor overload taking a Plate made the existing detached call new CuttingPlanRequest(null) ambiguous (CS0121). Plate scope is now requested with CuttingPlanRequest.ForPlate, and the result summary describes dependencies and Apply as they now behave.
This commit is contained in:
1 parent
e43a7c99d0
commit
1d8c5fe274
4 files changed
+50
-41
No files matched your search
@@ -11,7 +11,8 @@ namespace OpenNest.Engine.CuttingPlanning;
|
||||
|
||||
/// <summary>
|
||||
/// Caller-side direct-XY input. Regeneration requires explicit confirmed parameters.
|
||||
/// Keep sources/settings stable during Capture. This is not an Apply request.
|
||||
/// Keep sources/settings stable during Capture. Only plate-scoped requests (<see cref="ForPlate"/>)
|
||||
/// record the state that CuttingPlanService.Apply checks; a detached part list cannot be applied.
|
||||
/// </summary>
|
||||
public sealed class CuttingPlanRequest
|
||||
{
|
||||
@@ -30,19 +31,18 @@ public sealed class CuttingPlanRequest
|
||||
|
||||
/// <summary>
|
||||
/// Plate scope: plans the plate's current parts and captures its exact state, so a Ready
|
||||
/// result can later be applied through <see cref="CuttingPlanService.Apply"/>.
|
||||
/// result can later be applied through CuttingPlanService.Apply. A factory rather than a
|
||||
/// constructor overload, so <c>new CuttingPlanRequest(null)</c> stays unambiguous.
|
||||
/// </summary>
|
||||
public CuttingPlanRequest(Plate plate, Vector startPoint = default, int expansionBudget = 20000,
|
||||
public static CuttingPlanRequest ForPlate(Plate plate, Vector startPoint = default, int expansionBudget = 20000,
|
||||
CuttingParameters confirmedParameters = null, IEnumerable<Part> eligibleParts = null,
|
||||
bool preservePartOrder = false, int maxEntries = 16)
|
||||
: this(plate?.Parts, startPoint, expansionBudget, confirmedParameters, eligibleParts,
|
||||
bool preservePartOrder = false, int maxEntries = 16) =>
|
||||
new(plate?.Parts, startPoint, expansionBudget, confirmedParameters, eligibleParts,
|
||||
preservePartOrder, maxEntries)
|
||||
{
|
||||
Plate = plate;
|
||||
}
|
||||
{ Plate = plate };
|
||||
|
||||
/// <summary>The plate scope, or null for a detached part list that cannot be applied.</summary>
|
||||
public Plate Plate { get; }
|
||||
public Plate Plate { get; private init; }
|
||||
public CuttingParameters ConfirmedParameters { get; }
|
||||
public IReadOnlyList<Part> EligibleParts { get; }
|
||||
public bool PreservePartOrder { get; }
|
||||
@@ -167,8 +167,9 @@ public sealed record CuttingPlanFinding(int? SourceOrdinal, Part SourcePart,
|
||||
int? OtherSourceOrdinal, Part OtherSourcePart, PostVerificationKind? Kind, string Message);
|
||||
|
||||
/// <summary>
|
||||
/// A replayed direct-XY proposal, optionally with regenerated programs. Not physical
|
||||
/// safety, posting consent, dependency readiness or an atomic Apply payload. Failures contain no proposals.
|
||||
/// A replayed direct-XY proposal, optionally with regenerated programs, that respects the
|
||||
/// captured cutoff/containment prerequisites. Not physical safety or posting consent. Apply
|
||||
/// installs it only for a plate-scoped request whose plate is unchanged. Failures contain no proposals.
|
||||
/// </summary>
|
||||
public sealed class CuttingPlanResult
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ public class CuttingDependencyTests
|
||||
{
|
||||
var parameters = ExplicitContourTests.Parameters();
|
||||
var (_, plate, p, q, cut) = CutOffPlate(parameters, orphan: false);
|
||||
var request = new CuttingPlanRequest(plate, new Vector(11, 12.5),
|
||||
var request = CuttingPlanRequest.ForPlate(plate, new Vector(11, 12.5),
|
||||
confirmedParameters: regenerate ? parameters : null);
|
||||
var snapshot = CuttingPlanService.Capture(request);
|
||||
Assert.Equal(new[] { 2 }, snapshot.Dependencies.PrerequisitesOf(0));
|
||||
@@ -43,7 +43,7 @@ public class CuttingDependencyTests
|
||||
public void Apply_CutOffReclassifiedAfterPlanning_IsStale()
|
||||
{
|
||||
var (_, plate, p, q, cut) = CutOffPlate(ExplicitContourTests.Parameters(), orphan: false);
|
||||
var result = CuttingPlanService.Plan(new CuttingPlanRequest(plate, new Vector(11, 12.5)));
|
||||
var result = CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(plate, new Vector(11, 12.5)));
|
||||
Assert.Equal(CuttingPlanStatus.Ready, result.Status);
|
||||
cut.BaseDrawing.IsCutOff = false;
|
||||
|
||||
@@ -57,7 +57,7 @@ public class CuttingDependencyTests
|
||||
public void Plan_OrphanedCutOff_PrecedesEveryPart()
|
||||
{
|
||||
var (_, plate, p, q, cut) = CutOffPlate(ExplicitContourTests.Parameters(), orphan: true);
|
||||
var snapshot = CuttingPlanService.Capture(new CuttingPlanRequest(plate, new Vector(11, 12.5)));
|
||||
var snapshot = CuttingPlanService.Capture(CuttingPlanRequest.ForPlate(plate, new Vector(11, 12.5)));
|
||||
Assert.Equal(new[] { 2 }, snapshot.Dependencies.PrerequisitesOf(0));
|
||||
Assert.Equal(new[] { 2 }, snapshot.Dependencies.PrerequisitesOf(1));
|
||||
|
||||
@@ -72,7 +72,7 @@ public class CuttingDependencyTests
|
||||
{
|
||||
var (_, plate, p, _, cut) = CutOffPlate(ExplicitContourTests.Parameters(), orphan: false);
|
||||
|
||||
var result = CuttingPlanService.Plan(new CuttingPlanRequest(plate, new Vector(11, 12.5), preservePartOrder: true));
|
||||
var result = CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(plate, new Vector(11, 12.5), preservePartOrder: true));
|
||||
|
||||
Assert.Equal(CuttingPlanStatus.ConstraintConflict, result.Status);
|
||||
Assert.Empty(result.ProposedOrder);
|
||||
@@ -89,7 +89,7 @@ public class CuttingDependencyTests
|
||||
{
|
||||
var parameters = ExplicitContourTests.Parameters();
|
||||
var (_, plate, _, _, _) = CutOffPlate(parameters, orphan: false);
|
||||
var snapshot = CuttingPlanService.Capture(new CuttingPlanRequest(plate, new Vector(11, 12.5),
|
||||
var snapshot = CuttingPlanService.Capture(CuttingPlanRequest.ForPlate(plate, new Vector(11, 12.5),
|
||||
confirmedParameters: regenerate ? parameters : null));
|
||||
var ready = CuttingPlanService.Plan(snapshot);
|
||||
Assert.Equal(CuttingPlanStatus.Ready, ready.Status);
|
||||
@@ -120,7 +120,7 @@ public class CuttingDependencyTests
|
||||
public void Plan_PartInsideAHostCutout_IsCutBeforeTheHost()
|
||||
{
|
||||
var (plate, host, inner) = NestedPlate(Rectangle(2.7, 2.7, 0.6, 0.6));
|
||||
var snapshot = CuttingPlanService.Capture(new CuttingPlanRequest(plate));
|
||||
var snapshot = CuttingPlanService.Capture(CuttingPlanRequest.ForPlate(plate));
|
||||
Assert.Equal(new[] { 1 }, snapshot.Dependencies.PrerequisitesOf(0));
|
||||
Assert.Empty(snapshot.Dependencies.PrerequisitesOf(1));
|
||||
|
||||
@@ -129,7 +129,7 @@ public class CuttingDependencyTests
|
||||
Assert.True(result.Status == CuttingPlanStatus.Ready, Describe(result));
|
||||
Assert.Equal(new[] { inner, host }, result.ProposedOrder.Select(o => o.SourcePart));
|
||||
|
||||
var preserved = CuttingPlanService.Plan(new CuttingPlanRequest(plate, preservePartOrder: true));
|
||||
var preserved = CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(plate, preservePartOrder: true));
|
||||
Assert.Equal(CuttingPlanStatus.ConstraintConflict, preserved.Status);
|
||||
var finding = Assert.Single(preserved.Findings);
|
||||
Assert.Same(host, finding.SourcePart);
|
||||
@@ -143,7 +143,7 @@ public class CuttingDependencyTests
|
||||
// Same insert as above plus a remote rapid and scribe mark: material is unchanged, so
|
||||
// the inner-before-host prerequisite must not depend on whole-program bounds.
|
||||
var (plate, host, inner) = NestedPlate(Marked(Rectangle(2.7, 2.7, 0.6, 0.6)));
|
||||
var snapshot = CuttingPlanService.Capture(new CuttingPlanRequest(plate));
|
||||
var snapshot = CuttingPlanService.Capture(CuttingPlanRequest.ForPlate(plate));
|
||||
Assert.Null(snapshot.Findings.FirstOrDefault()?.Message);
|
||||
Assert.True(inner.BoundingBox.Left < host.BoundingBox.Left);
|
||||
|
||||
@@ -158,7 +158,7 @@ public class CuttingDependencyTests
|
||||
{
|
||||
var (plate, host, inner) = NestedPlate(Rectangle(3.6, 2.8, 0.8, 0.4));
|
||||
|
||||
var result = CuttingPlanService.Plan(new CuttingPlanRequest(plate));
|
||||
var result = CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(plate));
|
||||
|
||||
Assert.Equal(CuttingPlanStatus.UnsupportedGeometry, result.Status);
|
||||
var finding = Assert.Single(result.Findings);
|
||||
@@ -177,7 +177,7 @@ public class CuttingDependencyTests
|
||||
plate.Parts.Add(host);
|
||||
plate.Parts.Add(inner);
|
||||
|
||||
var snapshot = CuttingPlanService.Capture(new CuttingPlanRequest(plate));
|
||||
var snapshot = CuttingPlanService.Capture(CuttingPlanRequest.ForPlate(plate));
|
||||
|
||||
Assert.Null(snapshot.Findings.FirstOrDefault()?.Message);
|
||||
Assert.Empty(snapshot.Dependencies.PrerequisitesOf(0));
|
||||
|
||||
@@ -21,7 +21,7 @@ public class CuttingPlanCommitTests
|
||||
Assert.Contains(PostVerificationAnalyzer.Analyze(nest, Vector.Zero).Findings,
|
||||
f => f.Kind == PostVerificationKind.RapidCrossing);
|
||||
|
||||
var result = CuttingPlanService.Plan(new CuttingPlanRequest(plate));
|
||||
var result = CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(plate));
|
||||
Assert.Equal(CuttingPlanStatus.Ready, result.Status);
|
||||
Assert.Equal(parts, plate.Parts); // Planning never mutates.
|
||||
var commit = CuttingPlanService.Apply([result]);
|
||||
@@ -53,7 +53,7 @@ public class CuttingPlanCommitTests
|
||||
// capture neither stales the plan nor leaks into what is installed.
|
||||
var parameters = ExplicitContourTests.Parameters();
|
||||
var length = ((LineLeadIn)parameters.ExternalLeadIn).Length;
|
||||
var result = CuttingPlanService.Plan(new CuttingPlanRequest(plate, confirmedParameters: parameters));
|
||||
var result = CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(plate, confirmedParameters: parameters));
|
||||
Assert.Equal(CuttingPlanStatus.Ready, result.Status);
|
||||
var proposal = Assert.Single(result.ProposedOrder);
|
||||
Assert.True(proposal.IsRegenerated);
|
||||
@@ -104,7 +104,7 @@ public class CuttingPlanCommitTests
|
||||
var cutOff = new CutOff(new Vector(30, 0), CutOffAxis.Vertical);
|
||||
plate.CutOffs.Add(cutOff);
|
||||
plate.CuttingParameters = new CuttingParameters();
|
||||
var result = CuttingPlanService.Plan(new CuttingPlanRequest(plate));
|
||||
var result = CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(plate));
|
||||
Assert.Equal(CuttingPlanStatus.Ready, result.Status);
|
||||
switch (change)
|
||||
{
|
||||
@@ -149,7 +149,7 @@ public class CuttingPlanCommitTests
|
||||
public void Apply_MalformedLiveProgram_IsStaleInsteadOfThrowing()
|
||||
{
|
||||
var (_, plate, parts) = FixedPlate();
|
||||
var result = CuttingPlanService.Plan(new CuttingPlanRequest(plate));
|
||||
var result = CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(plate));
|
||||
Assert.Equal(CuttingPlanStatus.Ready, result.Status);
|
||||
parts[1].Program.Codes = null!;
|
||||
var events = Watch(plate);
|
||||
@@ -194,8 +194,8 @@ public class CuttingPlanCommitTests
|
||||
fixedPlate.Parts.Add(shared);
|
||||
var results = new[]
|
||||
{
|
||||
CuttingPlanService.Plan(new CuttingPlanRequest(regenerated, confirmedParameters: parameters)),
|
||||
CuttingPlanService.Plan(new CuttingPlanRequest(fixedPlate))
|
||||
CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(regenerated, confirmedParameters: parameters)),
|
||||
CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(fixedPlate))
|
||||
};
|
||||
Assert.All(results, r => Assert.Equal(CuttingPlanStatus.Ready, r.Status));
|
||||
Assert.True(results[0].ProposedOrder[0].IsRegenerated);
|
||||
@@ -229,8 +229,8 @@ public class CuttingPlanCommitTests
|
||||
var (_, second, secondParts) = FixedPlate();
|
||||
var results = new[]
|
||||
{
|
||||
CuttingPlanService.Plan(new CuttingPlanRequest(first)),
|
||||
CuttingPlanService.Plan(new CuttingPlanRequest(second))
|
||||
CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(first)),
|
||||
CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(second))
|
||||
};
|
||||
secondParts[0].LeadInsLocked = true;
|
||||
var firstState = PlateCuttingState.Capture(first);
|
||||
@@ -253,9 +253,9 @@ public class CuttingPlanCommitTests
|
||||
var (_, third, thirdPart, thirdParameters) = RegeneratedPlate(Vector.Zero);
|
||||
var results = new[]
|
||||
{
|
||||
CuttingPlanService.Plan(new CuttingPlanRequest(first)),
|
||||
CuttingPlanService.Plan(new CuttingPlanRequest(second, confirmedParameters: parameters)),
|
||||
CuttingPlanService.Plan(new CuttingPlanRequest(third, confirmedParameters: thirdParameters))
|
||||
CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(first)),
|
||||
CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(second, confirmedParameters: parameters)),
|
||||
CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(third, confirmedParameters: thirdParameters))
|
||||
};
|
||||
Assert.All(results, r => Assert.Equal(CuttingPlanStatus.Ready, r.Status));
|
||||
var states = new[] { first, second, third }.Select(p => PlateCuttingState.Capture(p)).ToArray();
|
||||
@@ -287,7 +287,7 @@ public class CuttingPlanCommitTests
|
||||
public void Apply_CancelledBeforeCommit_ChangesNothing()
|
||||
{
|
||||
var (_, plate, parts) = FixedPlate();
|
||||
var result = CuttingPlanService.Plan(new CuttingPlanRequest(plate));
|
||||
var result = CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(plate));
|
||||
var state = PlateCuttingState.Capture(plate);
|
||||
var events = Watch(plate);
|
||||
using var cancel = new CancellationTokenSource();
|
||||
@@ -308,8 +308,8 @@ public class CuttingPlanCommitTests
|
||||
var (_, second, secondParts) = FixedPlate();
|
||||
var results = new[]
|
||||
{
|
||||
CuttingPlanService.Plan(new CuttingPlanRequest(first)),
|
||||
CuttingPlanService.Plan(new CuttingPlanRequest(second))
|
||||
CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(first)),
|
||||
CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(second))
|
||||
};
|
||||
var failure = new InvalidOperationException("view refresh");
|
||||
var seen = new List<string>();
|
||||
@@ -339,8 +339,8 @@ public class CuttingPlanCommitTests
|
||||
CuttingPlanResult[] results = fault switch
|
||||
{
|
||||
"detached" => [CuttingPlanService.Plan(new CuttingPlanRequest(parts))],
|
||||
"not-ready" => [CuttingPlanService.Plan(new CuttingPlanRequest(plate, preservePartOrder: true))],
|
||||
"duplicate" => Enumerable.Repeat(CuttingPlanService.Plan(new CuttingPlanRequest(plate)), 2).ToArray(),
|
||||
"not-ready" => [CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(plate, preservePartOrder: true))],
|
||||
"duplicate" => Enumerable.Repeat(CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(plate)), 2).ToArray(),
|
||||
_ => []
|
||||
};
|
||||
if (fault == "detached")
|
||||
@@ -356,6 +356,14 @@ public class CuttingPlanCommitTests
|
||||
Assert.Equal((0, 0, 0), events());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Request_NullDetachedListStaysUnambiguousAndInvalid()
|
||||
{
|
||||
// A plate overload of the constructor made this existing call ambiguous (CS0121).
|
||||
Assert.Equal(CuttingPlanStatus.InvalidInput, CuttingPlanService.Plan(new CuttingPlanRequest(null)).Status);
|
||||
Assert.Equal(CuttingPlanStatus.InvalidInput, CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(null)).Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Apply_AllPlatesWithEmptySentinel_KeepsSentinelAndPlateList()
|
||||
{
|
||||
@@ -370,7 +378,7 @@ public class CuttingPlanCommitTests
|
||||
var listChanges = 0;
|
||||
manager.PlateListChanged += (_, _) => listChanges++;
|
||||
var sentinelEvents = Watch(sentinel);
|
||||
var results = nest.Plates.ToArray().Select(p => CuttingPlanService.Plan(new CuttingPlanRequest(p))).ToArray();
|
||||
var results = nest.Plates.ToArray().Select(p => CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(p))).ToArray();
|
||||
Assert.All(results, r => Assert.Equal(CuttingPlanStatus.Ready, r.Status));
|
||||
Assert.Empty(results[1].ProposedOrder);
|
||||
|
||||
|
||||
@@ -15,15 +15,15 @@ programs and settings are stable. Pass that snapshot to `Plan` on a worker;
|
||||
defaults to `Vector.Zero`, not a discovered controller position.
|
||||
|
||||
```csharp
|
||||
var request = new CuttingPlanRequest(plate, startPoint: start,
|
||||
var request = CuttingPlanRequest.ForPlate(plate, startPoint: start,
|
||||
confirmedParameters: parameters, expansionBudget: 20000,
|
||||
maxEntries: 16, preservePartOrder: false);
|
||||
var snapshot = CuttingPlanService.Capture(request, cancellationToken);
|
||||
var result = CuttingPlanService.Plan(snapshot, cancellationToken);
|
||||
```
|
||||
|
||||
- A plate-scoped request plans the plate's current parts and records its exact
|
||||
state for `Apply`. A detached part list (`new CuttingPlanRequest(parts, ...)`)
|
||||
- A plate-scoped request (`CuttingPlanRequest.ForPlate`) plans the plate's current
|
||||
parts and records its exact state for `Apply`. A detached part list (`new CuttingPlanRequest(parts, ...)`)
|
||||
plans the same way but can never be applied. An empty plate is a Ready no-op.
|
||||
- Omitting `confirmedParameters` preserves the original fixed-program contract:
|
||||
locked and unlocked programs stay fixed; only whole-part order may change.
|
||||
|
||||
Reference in new issue
Block a user