Spaces:
Running
Running
Download docs/vendor/reference_fk.py from bacool/arm-playground: direct link, hf CLI and curl.
- Browser
- Download file 3.57 kB
-
https://huggingface.co/spaces/bacool/arm-playground/resolve/main/docs/vendor/reference_fk.py
- Command line
-
hf download hf://spaces/bacool/arm-playground/docs/vendor/reference_fk.py
-
curl -L -o reference_fk.py https://huggingface.co/spaces/bacool/arm-playground/resolve/main/docs/vendor/reference_fk.py
3.57 kB
| #!/usr/bin/env python3 | |
| """Independent forward kinematics for the vendored SO-101 URDF. | |
| This is the cross-check behind the "known poses" in test.mjs. It shares no code with arm3d.js: | |
| it reads the URDF with an XML parser, follows the parent/child links from the base to the | |
| fingertip frame, and multiplies 4x4 homogeneous matrices (axis-angle joint turns). | |
| Run: python3 docs/vendor/reference_fk.py (standard library only) | |
| Prints joint origins, joint axes and the fingertip frame, in millimetres, for the poses listed | |
| in POSES. The numbers in test.mjs were copied from this output on 2026-10-05. | |
| """ | |
| import math | |
| import pathlib | |
| import xml.etree.ElementTree as ET | |
| URDF = pathlib.Path(__file__).with_name("so101_new_calib.urdf") | |
| TIP = "gripper_frame_link" | |
| POSES = { # radians, in chain order: shoulder_pan, shoulder_lift, elbow_flex, wrist_flex, wrist_roll | |
| "zero": [0, 0, 0, 0, 0], | |
| "generic": [0.4, -0.6, 0.9, 0.3, -0.7], | |
| "limits-ish": [-1.5, 1.2, -1.1, 1.3, 2.0], | |
| } | |
| def matmul(a, b): | |
| return [[sum(a[i][k] * b[k][j] for k in range(4)) for j in range(4)] for i in range(4)] | |
| def origin(xyz, rpy): | |
| """URDF origin: translate, then fixed-axis roll (x), pitch (y), yaw (z) -> R = Rz Ry Rx.""" | |
| r, p, y = rpy | |
| cr, sr, cp, sp, cy, sy = math.cos(r), math.sin(r), math.cos(p), math.sin(p), math.cos(y), math.sin(y) | |
| return [ | |
| [cy * cp, cy * sp * sr - sy * cr, cy * sp * cr + sy * sr, xyz[0]], | |
| [sy * cp, sy * sp * sr + cy * cr, sy * sp * cr - cy * sr, xyz[1]], | |
| [-sp, cp * sr, cp * cr, xyz[2]], | |
| [0, 0, 0, 1], | |
| ] | |
| def turn(axis, angle): | |
| """Rodrigues' rotation about a unit axis.""" | |
| x, y, z = axis | |
| c, s, t = math.cos(angle), math.sin(angle), 1 - math.cos(angle) | |
| return [ | |
| [t * x * x + c, t * x * y - s * z, t * x * z + s * y, 0], | |
| [t * x * y + s * z, t * y * y + c, t * y * z - s * x, 0], | |
| [t * x * z - s * y, t * y * z + s * x, t * z * z + c, 0], | |
| [0, 0, 0, 1], | |
| ] | |
| def chain(): | |
| joints = {} | |
| for j in ET.parse(URDF).getroot().findall("joint"): | |
| o = j.find("origin") | |
| joints[j.find("child").get("link")] = { | |
| "name": j.get("name"), "type": j.get("type"), "parent": j.find("parent").get("link"), | |
| "xyz": [float(v) for v in o.get("xyz").split()], "rpy": [float(v) for v in o.get("rpy").split()], | |
| "axis": [float(v) for v in j.find("axis").get("xyz").split()] if j.find("axis") is not None else None, | |
| } | |
| out, link = [], TIP | |
| while link in joints: # walk from the fingertip back to the base | |
| out.append(joints[link]) | |
| link = joints[link]["parent"] | |
| return list(reversed(out)) | |
| def fk(q): | |
| T = [[1 if i == j else 0 for j in range(4)] for i in range(4)] | |
| rows, k = [], 0 | |
| for j in chain(): | |
| T = matmul(T, origin(j["xyz"], j["rpy"])) | |
| pos = [T[i][3] * 1000 for i in range(3)] | |
| if j["type"] == "revolute": | |
| ax = [sum(T[i][c] * j["axis"][c] for c in range(3)) for i in range(3)] | |
| rows.append((j["name"], pos, ax)) | |
| T = matmul(T, turn(j["axis"], q[k])) | |
| k += 1 | |
| else: | |
| rows.append((j["name"], pos, None)) | |
| return rows | |
| if __name__ == "__main__": | |
| print("chain:", " -> ".join(j["name"] for j in chain())) | |
| for name, q in POSES.items(): | |
| print(f"\npose {name}: q = {q}") | |
| for jn, pos, ax in fk(q): | |
| axs = " axis (" + ", ".join(f"{v:+.6f}" for v in ax) + ")" if ax else "" | |
| print(f" {jn:20s} ({pos[0]:10.4f}, {pos[1]:10.4f}, {pos[2]:10.4f}) mm{axs}") | |