[verified] add benchmark baseline and rotation fixes
This commit is contained in:
@@ -20,7 +20,8 @@ public static class DrawingJobMapper
|
||||
: RotationPolicy.FromLegacy(
|
||||
constraints.StepAngle,
|
||||
constraints.StartAngle,
|
||||
constraints.EndAngle
|
||||
constraints.EndAngle,
|
||||
constraints.Allow180Equivalent
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -34,7 +35,12 @@ public static class DrawingJobMapper
|
||||
PartGeometrySnapshot.FromProgram(item.Drawing.Program),
|
||||
item.Quantity,
|
||||
item.Priority,
|
||||
RotationPolicy.FromLegacy(item.StepAngle, item.RotationStart, item.RotationEnd)
|
||||
RotationPolicy.FromLegacy(
|
||||
item.StepAngle,
|
||||
item.RotationStart,
|
||||
item.RotationEnd,
|
||||
item.Drawing.Constraints?.Allow180Equivalent ?? false
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -89,6 +95,7 @@ public static class DrawingJobMapper
|
||||
StepAngle = LegacyStep(part.Rotation),
|
||||
StartAngle = part.Rotation.Start,
|
||||
EndAngle = part.Rotation.End,
|
||||
Allow180Equivalent = part.Rotation.Allow180Equivalent,
|
||||
};
|
||||
return drawing;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using OpenNest.Engine.Fill;
|
||||
using OpenNest.Engine.Jobs.Adapters;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
using OpenNest.Engine.Jobs.Adapters;
|
||||
namespace OpenNest.Engine.Jobs.Placement;
|
||||
|
||||
/// <summary>Constrained-order linear fills in conservative rectangular free regions.
|
||||
@@ -105,6 +105,8 @@ internal sealed class OrderedPlateNester : IPlateNester
|
||||
if (policy.Kind == RotationPolicyKind.Fixed)
|
||||
{
|
||||
yield return policy.Start;
|
||||
if (policy.Allow180Equivalent)
|
||||
yield return policy.Start + System.Math.PI;
|
||||
yield break;
|
||||
}
|
||||
// A bounded deterministic search, not a proof that an unplaced part cannot fit.
|
||||
@@ -125,6 +127,8 @@ internal sealed class OrderedPlateNester : IPlateNester
|
||||
if (angle > policy.End + 1e-9)
|
||||
yield break;
|
||||
yield return angle;
|
||||
if (policy.Allow180Equivalent)
|
||||
yield return angle + System.Math.PI;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,13 @@ public enum RotationPolicyKind
|
||||
/// <summary>Immutable rotation constraints, in radians about the geometry origin.</summary>
|
||||
public sealed class RotationPolicy
|
||||
{
|
||||
private RotationPolicy(RotationPolicyKind kind, double start, double end, double step)
|
||||
private RotationPolicy(
|
||||
RotationPolicyKind kind,
|
||||
double start,
|
||||
double end,
|
||||
double step,
|
||||
bool allow180Equivalent
|
||||
)
|
||||
{
|
||||
if (!double.IsFinite(start) || !double.IsFinite(end) || !double.IsFinite(step))
|
||||
throw new ArgumentException("Angles must be finite.");
|
||||
@@ -20,33 +26,53 @@ public sealed class RotationPolicy
|
||||
Start = start;
|
||||
End = end;
|
||||
Step = step;
|
||||
Allow180Equivalent = allow180Equivalent;
|
||||
}
|
||||
|
||||
public RotationPolicyKind Kind { get; }
|
||||
public double Start { get; }
|
||||
public double End { get; }
|
||||
public double Step { get; }
|
||||
public static RotationPolicy Automatic { get; } = new(RotationPolicyKind.Automatic, 0, 0, 0);
|
||||
|
||||
public static RotationPolicy Fixed(double angle) =>
|
||||
new(RotationPolicyKind.Fixed, angle, angle, 0);
|
||||
/// <summary>Whether an orientation 180° from an allowed angle is also legal.</summary>
|
||||
public bool Allow180Equivalent { get; }
|
||||
public static RotationPolicy Automatic { get; } =
|
||||
new(RotationPolicyKind.Automatic, 0, 0, 0, false);
|
||||
|
||||
public static RotationPolicy BoundedSweep(double start, double end, double step)
|
||||
public static RotationPolicy Fixed(double angle, bool allow180Equivalent = false) =>
|
||||
new(RotationPolicyKind.Fixed, angle, angle, 0, allow180Equivalent);
|
||||
|
||||
public static RotationPolicy BoundedSweep(
|
||||
double start,
|
||||
double end,
|
||||
double step,
|
||||
bool allow180Equivalent = false
|
||||
)
|
||||
{
|
||||
if (step <= 0 || end < start)
|
||||
throw new ArgumentException("Sweep needs a positive step and ordered bounds.");
|
||||
return new RotationPolicy(RotationPolicyKind.BoundedSweep, start, end, step);
|
||||
return new RotationPolicy(
|
||||
RotationPolicyKind.BoundedSweep,
|
||||
start,
|
||||
end,
|
||||
step,
|
||||
allow180Equivalent
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Preserves the legacy zero-step automatic sentinel; zero never means locked rotation.</summary>
|
||||
public static RotationPolicy FromLegacy(
|
||||
double stepAngle,
|
||||
double rotationStart,
|
||||
double rotationEnd
|
||||
) => stepAngle == 0 ? Automatic : BoundedSweep(rotationStart, rotationEnd, stepAngle);
|
||||
double rotationEnd,
|
||||
bool allow180Equivalent = false
|
||||
) =>
|
||||
stepAngle == 0
|
||||
? Automatic
|
||||
: BoundedSweep(rotationStart, rotationEnd, stepAngle, allow180Equivalent);
|
||||
|
||||
/// <summary>True when a placement rotation (radians) satisfies this policy: anything for
|
||||
/// Automatic, the fixed angle modulo a full turn, or an exact step inside a bounded sweep.</summary>
|
||||
/// <summary>True when a placement rotation satisfies this policy. Fixed and bounded
|
||||
/// policies compare orientations modulo full turns; an allowed 180° equivalent is included.</summary>
|
||||
public bool Allows(double rotation)
|
||||
{
|
||||
const double epsilon = 0.0000001;
|
||||
@@ -55,14 +81,33 @@ public sealed class RotationPolicy
|
||||
if (Kind == RotationPolicyKind.Automatic)
|
||||
return true;
|
||||
if (Kind == RotationPolicyKind.Fixed)
|
||||
return AnglesEqual(rotation, Start, epsilon)
|
||||
|| (Allow180Equivalent && AnglesEqual(rotation, Start + System.Math.PI, epsilon));
|
||||
return SweepAllows(rotation, epsilon)
|
||||
|| (Allow180Equivalent && SweepAllows(rotation - System.Math.PI, epsilon));
|
||||
}
|
||||
|
||||
private bool SweepAllows(double rotation, double epsilon)
|
||||
{
|
||||
var fullTurn = System.Math.PI * 2;
|
||||
var firstTurn = System.Math.Ceiling((Start - epsilon - rotation) / fullTurn);
|
||||
var lastTurn = System.Math.Floor((End + epsilon - rotation) / fullTurn);
|
||||
for (var turns = firstTurn; turns <= lastTurn; turns++)
|
||||
{
|
||||
var delta = (rotation - Start) % (System.Math.PI * 2);
|
||||
return System.Math.Abs(delta) <= epsilon
|
||||
|| System.Math.Abs(System.Math.Abs(delta) - System.Math.PI * 2) <= epsilon;
|
||||
var equivalent = rotation + turns * fullTurn;
|
||||
if (equivalent < Start - epsilon || equivalent > End + epsilon)
|
||||
continue;
|
||||
var steps = (equivalent - Start) / Step;
|
||||
if (System.Math.Abs(steps - System.Math.Round(steps)) <= epsilon)
|
||||
return true;
|
||||
}
|
||||
if (rotation < Start - epsilon || rotation > End + epsilon)
|
||||
return false;
|
||||
var steps = (rotation - Start) / Step;
|
||||
return System.Math.Abs(steps - System.Math.Round(steps)) <= epsilon;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool AnglesEqual(double left, double right, double epsilon)
|
||||
{
|
||||
var delta = (left - right) % (System.Math.PI * 2);
|
||||
return System.Math.Abs(delta) <= epsilon
|
||||
|| System.Math.Abs(System.Math.Abs(delta) - System.Math.PI * 2) <= epsilon;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user