Twenty-one points in a camera’s coordinate system on one side. A CAD assembly with hard mechanical stops on the other. This is the arithmetic that makes them agree — and the one row where it currently does not. The vision layer gives you 21 points floating in a normalized coordinate space that has no physical units and a faked depth axis. The mechanical layer gives you fifteen revolute joints, each with a lower and upper bound in radians that came out of a CAD mate.
Nothing connects them. Building that connection is the actual work of this project, and it happens in about forty lines of Python.
flowchart LR A["21 landmarks
(x, y, z) normalized"] --> B["Triplet selection
15 × (p₁, p₂, p₃)"] B --> C["Two vectors per joint
v₁ = p₁ − p₂ · v₂ = p₃ − p₂"] C --> D["Dot product → arccos
θ in radians"] D --> E["Normalize
flexion ∈ [0, 1]"] E --> F["Lerp onto URDF limits
θ_urdf"] F --> G["JointState
names + positions"] Step 1 — pick three points # To measure a joint you need the joint itself and the two bones meeting at it. In landmark terms: the vertex, plus its two neighbours.
Bridging a modern parametric CAD platform and a fifteen-year-old XML standard is where most roboticists lose days. The exporter translates exactly what it sees — so every shortcut taken in CAD becomes a bug in ROS. → Open the assembly on Onshape — it is public, so everything below is checkable against the source.
The assembly: four fingers on blue linkages, the thumb on an orange one, all mounted to a single palm block. onshape-to-robot is a compiler. Assembly in, robot description out. It reads mate names, mate limits and material densities directly from the CAD document and writes them into URDF as joint names, joint limits and inertia tensors.
That is a genuinely good deal — done properly, the physical properties of your robot are generated rather than hand-typed, and they stay correct when the mechanism changes. Done improperly, you spend your evenings editing XML by hand and discovering that your ring finger has no knuckle.
flowchart LR A["Onshape assembly
mates · limits · materials"] --> B["onshape-to-robot
API pull"] B --> C["robot.urdf
links · joints · inertials"] B --> D["assets/*.stl
visual + collision meshes"] C --> E["robot_state_publisher"] C --> F["JOINT_MAPPING
limits transcribed by hand"] D --> G["RViz / Gazebo"] Five rules that move work back into CAD # Each of these exists because its absence cost real time on this project.