From a10b9d9a9fe443bb91792957eb8374e16c83354b Mon Sep 17 00:00:00 2001 From: forbes Date: Sat, 21 Feb 2026 22:04:18 -0600 Subject: [PATCH] fix(assembly): classify datum plane references in Distance joints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/Mod/Assembly/App/AssemblyUtils.cpp | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/Mod/Assembly/App/AssemblyUtils.cpp b/src/Mod/Assembly/App/AssemblyUtils.cpp index 8569af29f4..852dff3799 100644 --- a/src/Mod/Assembly/App/AssemblyUtils.cpp +++ b/src/Mod/Assembly/App/AssemblyUtils.cpp @@ -164,6 +164,39 @@ DistanceType getDistanceType(App::DocumentObject* joint) auto* obj1 = getLinkedObjFromRef(joint, "Reference1"); auto* obj2 = getLinkedObjFromRef(joint, "Reference2"); + // Datum planes (App::Plane) have empty element types because their + // sub-name ends with "." and yields no Face/Edge/Vertex element. + // Detect them here and classify before the main geometry chain, + // which cannot handle the empty element type. + const bool datum1 = type1.empty() && obj1 && obj1->isDerivedFrom(); + const bool datum2 = type2.empty() && obj2 && obj2->isDerivedFrom(); + + if (datum1 || datum2) { + if (datum1 && datum2) { + return DistanceType::PlanePlane; + } + + // One side is a datum plane, the other has a real element type. + // Ensure the datum (plane) side is Reference1 for consistent + // convention (face/plane first), matching the existing swap logic. + const auto& otherType = datum1 ? type2 : type1; + if (!datum1) { + swapJCS(joint); + } + + if (otherType == "Vertex") { + return DistanceType::PointPlane; + } + if (otherType == "Edge") { + return DistanceType::LinePlane; + } + if (otherType == "Face") { + return DistanceType::PlanePlane; + } + // Unknown element + datum plane → best-effort planar + return DistanceType::PlanePlane; + } + if (type1 == "Vertex" && type2 == "Vertex") { return DistanceType::PointPoint; }