fix(assembly): classify datum plane references in Distance joints
Some checks failed
Build and Test / build (pull_request) Has been cancelled

When a Distance joint references a datum plane (XY_Plane, XZ_Plane,
YZ_Plane), getDistanceType() failed to recognize it because datum
plane sub-names yield an empty element type. This caused the fallback
to DistanceType::Other → BaseJointKind::Planar, which adds spurious
parallel-normal residuals that overconstrain the system.

For example, three vertex-to-datum-plane Distance joints produced
10 residuals (3×Planar) with 6 mutually contradictory orientation
constraints, causing the solver to find garbage least-squares
solutions.

Add early detection of App::Plane datum objects before the main
geometry classification chain. Datum planes are now correctly mapped:
- Vertex + DatumPlane → PointPlane → PointInPlane (1 residual)
- Edge + DatumPlane → LinePlane → LineInPlane
- Face/DatumPlane + DatumPlane → PlanePlane → Planar
This commit is contained in:
forbes
2026-02-21 22:04:18 -06:00
parent cf2fc82eac
commit b4835e1b05
4 changed files with 156 additions and 9 deletions

View File

@@ -54,10 +54,56 @@
namespace PartApp = Part;
FC_LOG_LEVEL_INIT("Assembly", true, true, true)
// ======================================= Utils ======================================
namespace Assembly
{
const char* distanceTypeName(DistanceType dt)
{
switch (dt) {
case DistanceType::PointPoint: return "PointPoint";
case DistanceType::LineLine: return "LineLine";
case DistanceType::LineCircle: return "LineCircle";
case DistanceType::CircleCircle: return "CircleCircle";
case DistanceType::PlanePlane: return "PlanePlane";
case DistanceType::PlaneCylinder: return "PlaneCylinder";
case DistanceType::PlaneSphere: return "PlaneSphere";
case DistanceType::PlaneCone: return "PlaneCone";
case DistanceType::PlaneTorus: return "PlaneTorus";
case DistanceType::CylinderCylinder: return "CylinderCylinder";
case DistanceType::CylinderSphere: return "CylinderSphere";
case DistanceType::CylinderCone: return "CylinderCone";
case DistanceType::CylinderTorus: return "CylinderTorus";
case DistanceType::ConeCone: return "ConeCone";
case DistanceType::ConeTorus: return "ConeTorus";
case DistanceType::ConeSphere: return "ConeSphere";
case DistanceType::TorusTorus: return "TorusTorus";
case DistanceType::TorusSphere: return "TorusSphere";
case DistanceType::SphereSphere: return "SphereSphere";
case DistanceType::PointPlane: return "PointPlane";
case DistanceType::PointCylinder: return "PointCylinder";
case DistanceType::PointSphere: return "PointSphere";
case DistanceType::PointCone: return "PointCone";
case DistanceType::PointTorus: return "PointTorus";
case DistanceType::LinePlane: return "LinePlane";
case DistanceType::LineCylinder: return "LineCylinder";
case DistanceType::LineSphere: return "LineSphere";
case DistanceType::LineCone: return "LineCone";
case DistanceType::LineTorus: return "LineTorus";
case DistanceType::CurvePlane: return "CurvePlane";
case DistanceType::CurveCylinder: return "CurveCylinder";
case DistanceType::CurveSphere: return "CurveSphere";
case DistanceType::CurveCone: return "CurveCone";
case DistanceType::CurveTorus: return "CurveTorus";
case DistanceType::PointLine: return "PointLine";
case DistanceType::PointCurve: return "PointCurve";
case DistanceType::Other: return "Other";
}
return "Unknown";
}
void swapJCS(const App::DocumentObject* joint)
{
if (!joint) {
@@ -173,6 +219,8 @@ DistanceType getDistanceType(App::DocumentObject* joint)
if (datum1 || datum2) {
if (datum1 && datum2) {
FC_LOG("Assembly : getDistanceType('" << joint->getFullName()
<< "') — datum+datum → PlanePlane");
return DistanceType::PlanePlane;
}
@@ -193,17 +241,22 @@ DistanceType getDistanceType(App::DocumentObject* joint)
if (datum1) {
swapJCS(joint); // move datum from Ref1 → Ref2
}
if (otherType == "Vertex") {
return DistanceType::PointPlane;
}
return DistanceType::LinePlane;
DistanceType result = (otherType == "Vertex")
? DistanceType::PointPlane : DistanceType::LinePlane;
FC_LOG("Assembly : getDistanceType('" << joint->getFullName()
<< "') — datum+" << otherType << ""
<< distanceTypeName(result)
<< (datum1 ? " (swapped)" : ""));
return result;
}
// Face + datum or unknown + datum → PlanePlane
// Datum on Reference1 for consistency with Face+Face path.
if (!datum1) {
swapJCS(joint); // move datum from Ref2 → Ref1
}
// Face + datum or unknown + datum → PlanePlane.
// No swap needed: PlanarConstraint is symmetric (uses both
// z_i and z_j), and preserving the original Reference order
// keeps the initial Placement values consistent so the solver
// stays in the correct orientation branch.
FC_LOG("Assembly : getDistanceType('" << joint->getFullName()
<< "') — datum+" << otherType << " → PlanePlane");
return DistanceType::PlanePlane;
}