mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-10 19:04:13 -04:00
fix(cutting): certify complete selected contour programs
This commit is contained in:
1 parent
9f7580a486
commit
e8d974a2db
8 files changed
+538
-47
No files matched your search
@@ -94,8 +94,27 @@ public sealed class FixedProgramPlacement
|
||||
public bool IsRegenerated => ContourChoices.Count != 0;
|
||||
/// <summary>Returns an independent deep copy; never an alias to captured/proposed code.</summary>
|
||||
public Program CopyProgram() => program == null ? null : OwnedProgramCopy.Copy(program);
|
||||
internal FixedProgramPlacement Propose(Program proposed, OwnedExecution execution, IReadOnlyList<ContourChoice> choices) =>
|
||||
new(SourcePart, SourceOrdinal, Location, Rotation, LeadInsLocked, execution, OwnedProgramCopy.Copy(proposed), Prepared, Material, choices);
|
||||
internal SelectedContourProgram SelectedProgram { get; private init; }
|
||||
|
||||
internal FixedProgramPlacement Propose(Program proposed, OwnedExecution execution, IReadOnlyList<ContourChoice> choices,
|
||||
System.Threading.CancellationToken token = default)
|
||||
{
|
||||
SelectedContourProgram selected = null;
|
||||
try
|
||||
{
|
||||
// The expected program is constructed from owned choices/settings, NEVER
|
||||
// from proposed or its cached execution. Counterfeit payloads remain subject
|
||||
// to independent final checks, including callers of this internal test seam.
|
||||
selected = Prepared?.CaptureSelectedProgram(choices, Location, token);
|
||||
}
|
||||
catch (Exception ex) when (ex is ArgumentException or NotSupportedException or InvalidOperationException or ArithmeticException)
|
||||
{
|
||||
// Invalid metadata is a refused proposal, not an exception from final replay.
|
||||
}
|
||||
return new(SourcePart, SourceOrdinal, Location, Rotation, LeadInsLocked, execution,
|
||||
OwnedProgramCopy.Copy(proposed, token), Prepared, Material, choices)
|
||||
{ SelectedProgram = selected };
|
||||
}
|
||||
|
||||
public Part SourcePart { get; }
|
||||
public int SourceOrdinal { get; }
|
||||
|
||||
@@ -201,18 +201,15 @@ public static class CuttingPlanService
|
||||
if (!ReferenceEquals(source, proposal))
|
||||
return new(CuttingPlanStatus.InvalidInput, expansions: expansions);
|
||||
}
|
||||
else if (proposal.ContourChoices.Count != source.Prepared.Count
|
||||
|| proposal.ContourChoices.Any(c => !ReferenceEquals(c.Owner, source.Prepared)
|
||||
|| c.ContourOrdinal < 0 || c.ContourOrdinal >= source.Prepared.Count)
|
||||
|| proposal.ContourChoices.Select(c => c.ContourOrdinal).Distinct().Count() != source.Prepared.Count
|
||||
|| proposal.ContourChoices[^1].ContourOrdinal != source.Prepared.PerimeterOrdinal)
|
||||
else if (proposal.SelectedProgram == null
|
||||
|| !proposal.SelectedProgram.Matches(source.Prepared, proposal.ContourChoices))
|
||||
return new(CuttingPlanStatus.InvalidInput, expansions: expansions);
|
||||
OwnedExecution execution;
|
||||
bool complete;
|
||||
try
|
||||
{
|
||||
execution = ExecutionMotionReader.Read(proposal.CopyProgram(), proposal.Location, position, token);
|
||||
if (source.Prepared != null && !HasCompleteContourAccounting(execution, source.Material, token))
|
||||
return new(CuttingPlanStatus.InvalidInput, expansions: expansions);
|
||||
complete = ContourProgramVerifier.Verify(execution, source.Material, proposal.SelectedProgram, token);
|
||||
}
|
||||
catch (Exception ex) when (ex is ArgumentException or NotSupportedException)
|
||||
{
|
||||
@@ -225,6 +222,11 @@ public static class CuttingPlanService
|
||||
if (!lead.IsComplete || !lead.IsClear)
|
||||
findings.Add(JointCuttingPlanSearch.Finding(source,
|
||||
lead.IsComplete ? null : PostVerificationKind.Incomplete, lead.Reason));
|
||||
if (!complete && findings.Count == 0)
|
||||
return new(CuttingPlanStatus.InvalidInput,
|
||||
findings: [JointCuttingPlanSearch.Finding(source, null,
|
||||
"Selected program does not preserve complete directed contours, certified tabs or selected entry/style geometry.")],
|
||||
expansions: expansions);
|
||||
distance += execution.RapidDistanceFrom(position);
|
||||
position = execution.DeparturePoint;
|
||||
}
|
||||
@@ -237,42 +239,6 @@ public static class CuttingPlanService
|
||||
rapidDistance: distance, independentlyReplayed: true);
|
||||
}
|
||||
|
||||
private static bool HasCompleteContourAccounting(OwnedExecution execution, LeadMaterialSnapshot material, CancellationToken token)
|
||||
{
|
||||
var visited = new HashSet<int>();
|
||||
var current = -1;
|
||||
var budget = 1000000;
|
||||
foreach (var move in execution.Motions)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (move.Rapid || move.Layer is not (LayerType.Cut or LayerType.Display))
|
||||
{
|
||||
current = -1;
|
||||
continue;
|
||||
}
|
||||
var ring = -1;
|
||||
for (var i = 0; i < material.Rings.Count; i++)
|
||||
if (material.Rings[i].Any(c => Matches(c, move)))
|
||||
{
|
||||
ring = i;
|
||||
break;
|
||||
}
|
||||
if (ring < 0 || current >= 0 && ring != current || current < 0 && !visited.Add(ring))
|
||||
return false;
|
||||
current = ring;
|
||||
}
|
||||
return visited.Count == material.Rings.Count;
|
||||
|
||||
bool Matches(PostVerificationGeometry.Curve curve, ExecutionMotion move)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (--budget < 0) throw new NotSupportedException("Replay contour accounting exceeds the native query limit.");
|
||||
return curve.SameSupport(move.Curve) && curve.Contains(move.Curve.Start)
|
||||
&& curve.Contains(move.End) && curve.Contains(move.Curve.Midpoint)
|
||||
&& move.Length <= curve.Length + PostVerificationGeometry.Epsilon;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool SameBits(double source, double proposed) =>
|
||||
BitConverter.DoubleToInt64Bits(source) == BitConverter.DoubleToInt64Bits(proposed);
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ internal static class JointCuttingPlanSearch
|
||||
if (!Check(source, execution, arrival, checker)) continue;
|
||||
var distance = execution.RapidDistanceFrom(arrival);
|
||||
var next = prefix.Length == prepared.Count
|
||||
? new Node([.. node.Order, source.Propose(program, execution, prefix)], execution.DeparturePoint, checker, null)
|
||||
? new Node([.. node.Order, source.Propose(program, execution, prefix, token)], execution.DeparturePoint, checker, null)
|
||||
: new Node(node.Order, execution.DeparturePoint, checker,
|
||||
new(source, prefix, arrival, before, distance));
|
||||
yield return new(next, distance - (node.Active?.Distance ?? 0), source.SourceOrdinal, contour, entry);
|
||||
|
||||
Reference in new issue
Block a user