fix(assembly): classify datum planes from all class hierarchies in Distance joints
Some checks failed
Build and Test / build (pull_request) Has been cancelled

The datum plane detection in getDistanceType() only checked for
App::Plane (origin planes). This missed two other class hierarchies:

  - PartDesign::Plane (inherits Part::Datum, NOT App::Plane)
  - Part::Plane primitive referenced bare (no Face element)

Both produce empty element types (sub-name ends with ".") but failed
the isDerivedFrom<App::Plane>() check, falling through to
DistanceType::Other and the Planar fallback. This caused incorrect
constraint geometry, leading to conflicting/unsatisfiable constraints
and solver failures.

Add shape-based isDatumPlane/Line/Point helpers that cover all three
hierarchies by inspecting the actual OCCT geometry rather than relying
on class identity alone. Extend getDistanceType() to use these helpers
for all datum-vs-datum and datum-vs-element combinations.

Adds TestDatumClassification.py with tests for PartDesign::Plane,
Part::Plane (bare ref), and cross-hierarchy datum combinations.
This commit is contained in:
forbes
2026-02-22 18:55:39 -06:00
parent a6a5db11f8
commit 14ee8c673f

View File

@@ -325,21 +325,28 @@ DistanceType getDistanceType(App::DocumentObject* joint)
auto* obj1 = getLinkedObjFromRef(joint, "Reference1");
auto* obj2 = getLinkedObjFromRef(joint, "Reference2");
// Datum objects 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.
// Datum objects referenced bare have empty element types (sub-name
// ends with "."). PartDesign datums referenced through a body can
// also produce non-standard element types like "Plane" (from a
// sub-name such as "Body.DatumPlane.Plane" — Part::Datum::getSubObject
// ignores the trailing element, but splitSubName still extracts it).
//
// Detect these before the main geometry chain, which only handles
// the standard Face/Edge/Vertex element types.
//
// isDatumPlane/Line/Point cover all three independent hierarchies:
// - App::Plane / App::Line / App::Point (origin datums)
// - Part::Datum subclasses (PartDesign datums)
// - Part::Feature with single-face shape (Part::Plane primitive, bare ref)
const bool datumPlane1 = type1.empty() && isDatumPlane(obj1);
const bool datumPlane2 = type2.empty() && isDatumPlane(obj2);
const bool datumLine1 = type1.empty() && !datumPlane1 && isDatumLine(obj1);
const bool datumLine2 = type2.empty() && !datumPlane2 && isDatumLine(obj2);
const bool datumPoint1 = type1.empty() && !datumPlane1 && !datumLine1 && isDatumPoint(obj1);
const bool datumPoint2 = type2.empty() && !datumPlane2 && !datumLine2 && isDatumPoint(obj2);
auto isNonGeomElement = [](const std::string& t) {
return t != "Face" && t != "Edge" && t != "Vertex";
};
const bool datumPlane1 = isNonGeomElement(type1) && isDatumPlane(obj1);
const bool datumPlane2 = isNonGeomElement(type2) && isDatumPlane(obj2);
const bool datumLine1 = isNonGeomElement(type1) && !datumPlane1 && isDatumLine(obj1);
const bool datumLine2 = isNonGeomElement(type2) && !datumPlane2 && isDatumLine(obj2);
const bool datumPoint1 = isNonGeomElement(type1) && !datumPlane1 && !datumLine1 && isDatumPoint(obj1);
const bool datumPoint2 = isNonGeomElement(type2) && !datumPlane2 && !datumLine2 && isDatumPoint(obj2);
const bool datum1 = datumPlane1 || datumLine1 || datumPoint1;
const bool datum2 = datumPlane2 || datumLine2 || datumPoint2;