mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 19:01:02 +00:00
Mate connectors: derive a face-only frame's X from the face, not from world
datum_frame took a FaceAndDirection frame's Z from the face normal, which follows the body, but its X from coordsys_x_hint, a world constant, whenever no explicit edge reference was set. Spinning a body about its own face normal therefore left the frame bit-identical: the connector could not encode that rotation at all, so Fastened and Slider mates claimed to fix an orientation the frame could not see. X now comes from the face's own first usable edge, which rotates with the body. The hint survives only as a last resort, for faces that offer no in-plane direction — a full circular edge has coincident endpoints, and a seam projects to nothing in-plane. The new test spins a box 90 degrees about its top-face normal and asserts the frame's X turned with it. Reverting just the X_tent derivation and rerunning makes it fail with "1.0 is within 0.000001 of 0.0" — cos(angle) between the before and after X is exactly 1, i.e. the frame did not move — and that is the only failure in 2019 assertions, so the test discriminates this defect and nothing else. Note for anyone replaying an older document: a face-only connector's frame can now differ from what that recipe produced before, so a mate built on one may place its body differently. Nothing in the suite or the golden v3 fixture changed, but the semantics did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f13f2876f6
commit
1726e93760
@@ -1681,8 +1681,29 @@ CadDocument::DatumCoordSys CadDocument::datum_frame(const std::vector<CadBody>&
|
||||
if (fc.IsNull()) { ds.error = "face not found"; break; }
|
||||
ds.origin = GeometryEngine::face_centroid_world(fc);
|
||||
Vec3d Z = GeometryEngine::face_normal_world(fc);
|
||||
// Tentative X: edge direction if available, else the hint or a fallback.
|
||||
Vec3d X_tent = have_edge ? edge_dir : f.coordsys_x_hint;
|
||||
// Tentative X: an explicit edge reference wins. Failing that, derive X from the
|
||||
// face's OWN first usable edge, so the frame rotates with the body. Reading it from
|
||||
// coordsys_x_hint — a world constant — meant a face-only connector could not encode
|
||||
// spin about its own normal: Z followed the body, X did not, so Fastened and Slider
|
||||
// claimed to fix an orientation the frame could not see. The hint survives only as a
|
||||
// last resort, for faces that offer no usable direction (a full circular edge has
|
||||
// coincident endpoints, and a cylinder's seam projects to nothing in-plane).
|
||||
Vec3d X_tent = Vec3d::Zero();
|
||||
if (have_edge) {
|
||||
X_tent = edge_dir;
|
||||
} else {
|
||||
for (const TopoDS_Edge& fe : GeometryEngine::edges_of_face(fc)) {
|
||||
auto pts = GeometryEngine::sample_edge_world(fe);
|
||||
if (pts.size() < 2) continue;
|
||||
Vec3d d = pts.back() - pts.front();
|
||||
if (d.squaredNorm() < 1e-18) continue; // closed edge: endpoints coincide
|
||||
Vec3d in_plane = d - Z * Z.dot(d); // drop any out-of-plane component
|
||||
if (in_plane.squaredNorm() < 1e-18) continue;
|
||||
X_tent = in_plane;
|
||||
break;
|
||||
}
|
||||
if (X_tent.squaredNorm() < 1e-18) X_tent = f.coordsys_x_hint;
|
||||
}
|
||||
if (X_tent.squaredNorm() < 1e-18) { ds.error = "zero-length direction"; break; }
|
||||
X_tent.normalize();
|
||||
// Gram-Schmidt: ensure orthonormal, right-handed frame.
|
||||
|
||||
Reference in New Issue
Block a user