fix(cutting): bind replay proposals to exact captured pose bits

This commit is contained in:
aj committed 2026-10-04 21:56:49 -04:00
1 parent fb7ef6fd0d
commit 9f7580a486
2 files changed
+53 -2

No files matched your search

@@ -190,8 +190,9 @@ public static class CuttingPlanService
{
token.ThrowIfCancellationRequested();
var source = snapshot.Placements[proposal.SourceOrdinal];
if (!ReferenceEquals(source.SourcePart, proposal.SourcePart) || source.Location != proposal.Location
|| source.Rotation != proposal.Rotation || source.LeadInsLocked != proposal.LeadInsLocked
if (!ReferenceEquals(source.SourcePart, proposal.SourcePart)
|| !SameBits(source.Location.X, proposal.Location.X) || !SameBits(source.Location.Y, proposal.Location.Y)
|| !SameBits(source.Rotation, proposal.Rotation) || source.LeadInsLocked != proposal.LeadInsLocked
|| !ReferenceEquals(source.Prepared, proposal.Prepared) || !ReferenceEquals(source.Material, proposal.Material)
|| snapshot.PreservePartOrder && proposal.SourceOrdinal != order.TakeWhile(p => !ReferenceEquals(p, proposal)).Count())
return new(CuttingPlanStatus.InvalidInput, expansions: expansions);
@@ -272,6 +273,9 @@ public static class CuttingPlanService
}
}
private static bool SameBits(double source, double proposed) =>
BitConverter.DoubleToInt64Bits(source) == BitConverter.DoubleToInt64Bits(proposed);
private static IEnumerable<CuttingPlanFinding> Map(CuttingPlanSnapshot snapshot,
IEnumerable<PostVerificationFinding> findings) => findings.Select(finding =>
{
@@ -0,0 +1,47 @@
using OpenNest.CNC.CuttingPlanning;
using OpenNest.Engine.CuttingPlanning;
using OpenNest.Geometry;
namespace OpenNest.Tests.CuttingPlanning;
public class ReplayPoseBindingTests
{
[Theory]
[InlineData("near-x")]
[InlineData("near-y")]
[InlineData("zero-x")]
[InlineData("zero-y")]
[InlineData("zero-rotation")]
[InlineData("matching-bits")]
public void ExactReplay_BindsEveryPoseScalarByBits(string fault)
{
var part = new Part(new Drawing("pose", LeadPathValidationTests.Rectangle(0, 0, 10, 10)));
var snapshot = CuttingPlanService.Capture(new CuttingPlanRequest([part], new Vector(-2, 5),
confirmedParameters: ExplicitContourTests.Parameters()));
var ready = CuttingPlanService.Plan(snapshot);
Assert.Equal(CuttingPlanStatus.Ready, ready.Status);
var source = Assert.Single(snapshot.Placements);
var proposal = Assert.Single(ready.ProposedOrder);
var x = source.Location.X; var y = source.Location.Y; var rotation = source.Rotation;
var negativeZero = BitConverter.Int64BitsToDouble(long.MinValue);
if (fault == "near-x") x += 1e-10;
if (fault == "near-y") y += 1e-10;
if (fault == "zero-x") x = negativeZero;
if (fault == "zero-y") y = negativeZero;
if (fault == "zero-rotation") rotation = negativeZero;
// These deliberately remain equal under the old geometric/numeric operators.
Assert.True(source.Location == new Vector(x, y));
Assert.True(source.Rotation == rotation);
var sameBits = BitConverter.DoubleToInt64Bits(source.Location.X) == BitConverter.DoubleToInt64Bits(x)
&& BitConverter.DoubleToInt64Bits(source.Location.Y) == BitConverter.DoubleToInt64Bits(y)
&& BitConverter.DoubleToInt64Bits(source.Rotation) == BitConverter.DoubleToInt64Bits(rotation);
Assert.Equal(fault == "matching-bits", sameBits);
var selected = new FixedProgramPlacement(source.SourcePart, source.SourceOrdinal, new Vector(x, y), rotation,
source.LeadInsLocked, proposal.Execution, proposal.CopyProgram(), source.Prepared, source.Material, proposal.ContourChoices);
var replay = CuttingPlanService.ReplayPrograms(snapshot, [selected], 0, default);
Assert.Equal(sameBits ? CuttingPlanStatus.Ready : CuttingPlanStatus.InvalidInput, replay.Status);
Assert.Equal(sameBits, replay.IndependentlyReplayed);
if (sameBits) Assert.Single(replay.ProposedOrder);
else Assert.Empty(replay.ProposedOrder);
}
}