fix(assembly): use short-arc angle in validateNewPlacements (#338)
All checks were successful
Build and Test / build (pull_request) Successful in 30m5s

Rotation::evaluateVector() computes angle = 2*acos(w) which gives
values in [0, 2*pi].  When the relative quaternion has w < 0 (opposite
hemisphere), the angle exceeds pi even though q and -q represent the
same rotation.  This caused the validator to report ~350 degree 'flips'
and reject valid solver output.

Fix: map the angle to [0, pi] before comparing against the 91-degree
threshold.  This is the short-arc equivalent — the minimum rotation
angle between two orientations regardless of quaternion sign convention.
This commit is contained in:
forbes
2026-02-27 10:38:37 -06:00
parent 07a51aefb1
commit d174ef7a8d

View File

@@ -659,19 +659,26 @@ bool AssemblyObject::validateNewPlacements()
// Get the relative rotation: how much did the part rotate? // Get the relative rotation: how much did the part rotate?
Base::Rotation relativeRot = newRot * oldRot.inverse(); Base::Rotation relativeRot = newRot * oldRot.inverse();
// Get the angle of this rotation // Ensure we measure the short-arc angle. Rotation stores
// angle = 2*acos(w) which gives [0, 2*pi]. When the
// quaternion w component is negative (opposite hemisphere),
// the angle exceeds pi even though q and -q represent the
// same rotation. Map to [0, pi] for a correct comparison.
Base::Vector3d axis; Base::Vector3d axis;
double angle; double angle;
relativeRot.getRawValue(axis, angle); relativeRot.getRawValue(axis, angle);
if (angle > M_PI) {
angle = 2.0 * M_PI - angle;
}
// If the part rotated more than 90 degrees, consider it a flip // If the part rotated more than 90 degrees, consider it a flip
// Use 91 degrees to allow for small numerical errors // Use 91 degrees to allow for small numerical errors
constexpr double maxAngle = 91.0 * M_PI / 180.0; constexpr double maxAngle = 91.0 * M_PI / 180.0;
if (std::abs(angle) > maxAngle) { if (angle > maxAngle) {
Base::Console().warning( Base::Console().warning(
"Assembly : Ignoring bad solve, part (%s) flipped orientation (%.1f degrees).\n", "Assembly : Ignoring bad solve, part (%s) flipped orientation (%.1f degrees).\n",
obj->getFullLabel(), obj->getFullLabel(),
std::abs(angle) * 180.0 / M_PI angle * 180.0 / M_PI
); );
return false; return false;
} }