fix(assembly): treat a joint origin as a pivot, not a translation - #686
fix(assembly): treat a joint origin as a pivot, not a translation#686w1ne wants to merge 1 commit into
Conversation
Parts are modeled in assembly coordinates, so a joint's `origin` is a PIVOT POINT. forwardKinematics composed T(o) . M with no T(-o), so the origin leaked in as a bare translation and every joint with a non-origin pivot displaced its child by the pivot vector — visible at pose 0, where the child must sit exactly where it was modeled. A hinge with pivot [5,20,16] posed at elbow=0 exported its arm at [10,35,28]..[60,45,36] instead of the modeled [5,15,12]..[55,25,20]. The Y figure alone is decisive: the joint spins about Y, and a Y rotation cannot change a Y coordinate, yet y moved 15..25 -> 35..45. Conjugate the motion by the origin — T(o) . M . T(-o) — for all four joint kinds. This is the convention the mate solver already uses; see composeChildTransform() in src/modeling/mates/solver.ts, which conjugates the joint frame by the parent and child connector origins the same way. Transform.rotationAroundPivot() in se3.ts was already the correct primitive. Every pre-existing `.revolute(...)` test used `origin: [0, 0, 0]`, where T(o) is identity and the defect is invisible, and the one FK test asserted only `toBeDefined()`. The new tests all use a non-zero pivot and assert positions: all 7 fail without this change.
|
Closing — the premise of this PR is wrong.
Both tests pass on What I actually hit is a convention collision, not a math error:
I modeled parts in place ( So the real defect is that mixing the two conventions is undetectable and unhinted. Nothing in Apologies for the noise on CI. |
The defect
forwardKinematicscomposedT(o) . Mwith noT(-o), so a joint'soriginleaked in as a bare translation instead of acting as a pivot. Every joint with a non-origin pivot displaced its child by the pivot vector — visible at pose 0, where the child must sit exactly where it was modeled.Measured against production (
POST api.kernelcad.com/__kernelcad/export?format=stl, binary STL parsed locally). Hinge with pivot[5,20,16], posed atelbow: 0:The Y figure alone is decisive: the joint spins about Y, and a rotation about Y cannot change a Y coordinate — yet y moved 15..25 → 35..45. Controls confirm it: pivot
[5,0,16]makes Y correct and leaves X/Z wrong by 5 and 16; pivot[0,0,0]makes all three correct.Knock-on effects: the interference validator and
swept-collisionscore displaced solids, so a hinge that collides at 30° swept clean across 0–90°.The fix
Conjugate the motion by the origin —
T(o) . M . T(-o)— for all four joint kinds.This is the convention the mate solver already uses: see
composeChildTransform()insrc/modeling/mates/solver.ts, which conjugates the joint frame by the parent and child connector origins the same way.Transform.rotationAroundPivot()inse3.tswas already the correct primitive; FK just never called it. A mate-built hinge atelbow: 0returns the modeled position today — only the joint-primitive path was wrong.Why it shipped
Every pre-existing
.revolute(...)test usedorigin: [0, 0, 0], whereT(o)is identity and the defect is invisible. The one FK test assertedexpect(solved.transform('link')).toBeDefined()— it never checked a position.Verification
src/modeling/capture/forwardKinematics.test.ts— 7 new tests, all with a non-zero pivot, asserting actual transformed points. All 7 fail without this change, with exactly the displacements measured above (expected 35 to be close to 15,expected 10 to be close to 5).src/modeling/capture,src/modeling/mates,src/kinematic— 43 files / 428 tests pass, no regressions.Note for release
The export API and every MCP tool run the engine vendored into
kernelCAD-serverat 0.13.0 (web is 0.15.0). This fix does not reach customers until that vendored dist is rebuilt and bumped.