0:\n axis = axis / np.linalg.norm(axis)\n trans_resp, rot_resp = [], []\n for d in TRANS_DIRS:\n if jtype == 'free':\n trans_resp.append(1.0)\n elif jtype == 'slide':\n trans_resp.append(abs(float(np.dot(d, axis)))) # only along axis\n else: # hinge / weld\n trans_resp.append(0.0)\n for d in ROT_DIRS:\n if jtype == 'free':\n rot_resp.append(1.0)\n elif jtype == 'hinge':\n rot_resp.append(abs(float(np.dot(d, axis)))) # only about axis\n else: # slide / weld\n rot_resp.append(0.0)\n return np.array(trans_resp), np.array(rot_resp), axis\n\nprint('probe() ready')","label":"Joint model - how a joint admits motion (given - do not edit)"},{"code":"# Add ONE joint so the leaf rotates about the pivot line (world Y) and NOTHING\n# else can move. Choose type ('hinge'=revolute, 'slide'=prismatic, 'free'=all 6),\n# the axis vector, and the range in degrees.\n#\n# in the full Bench this is inside .\n\njoint = {\n 'type': 'free', # <-- rotation, not translation, not all-6\n 'axis': [0, 0, 0], # <-- pivot runs along world Y\n 'pos': [30, 0, 12], # given: a point on the pivot line\n 'range': [0, 90], # degrees\n}\n\ntr, rot, axis = probe(joint)\nprint('translational response x,y,z :', np.round(tr, 3))\nprint('rotational response x,y,z :', np.round(rot, 3))","label":"YOUR JOB - declare ONE joint (edit this cell only)"},{"code":"# Sim-based DOF probe: count directions that moved, classify kind, check axis & range.\n\ntr, rot, axis = probe(joint)\nn_trans = int(np.sum(tr > MOTION_EPS))\nn_rot = int(np.sum(rot > MOTION_EPS))\nn_free = n_trans + n_rot\nkind = 'rotational' if n_rot >= n_trans else 'translational'\n\n# realized motion axis = the test direction that moved most; axis error vs target\nresp = rot if kind == 'rotational' else tr\nrealized = (ROT_DIRS if kind == 'rotational' else TRANS_DIRS)[int(np.argmax(resp))]\ncos = abs(float(np.dot(realized, TARGET_AXIS)))\naxis_err = float(np.degrees(np.arccos(np.clip(cos, -1, 1))))\n\nlo, hi = joint['range']\nrange_ok = (lo == 0 and hi == 90) # revolute spec: stop at 0 and 90 deg\n\nfails = []\nif n_free == 0:\n fails.append('FAIL: no free DOF detected - the leaf is welded. Add a joint inside the leaf body.')\nelif n_free > 1:\n fails.append(f'FAIL: leaf moves in {n_free} directions - that is a free joint, not a hinge. Use type=\"hinge\" to leave exactly 1 rotational DOF.')\nelif kind != 'rotational':\n fails.append('FAIL: leaf slides instead of rotating. Spec asked for rotation - change type from \"slide\" to \"hinge\".')\nelif axis_err > 1.0:\n fails.append(f'FAIL: hinge axis is {axis_err:.0f} deg off: leaf folds about the wrong axis, but the pivot runs along Y. Set axis=\"0 1 0\".')\nelif not range_ok:\n fails.append(f'FAIL: leaf rotates past 90 deg into the base. Add range=\"0 90\" to stop it.')\n\nprint(f'n_free={n_free} kind={kind} axis_err={axis_err:.1f}deg range=({lo},{hi})')\nif fails:\n print(fails[0])\nelse:\n print('PASS: exactly 1 rotational DOF about Y, range 0-90 enforced - that is a hinge.')","label":"Autograder - submit"}],"intro":"The real Bench probes a MuJoCo-WASM sim for DOF. Here we emulate the kinematic referee in numpy: declare a joint (type, axis, range) and the grader applies a small test wrench in all six directions, measures which produce motion, and reports n_free, the realized axis, and the kind - exactly the sim-based DOF probe, not an XML parse. Edit only the joint declaration cell, gravity off, kinematic only.","key":"design-simulation/joints-and-degrees-of-freedom","kind":"python","title":"Joints & Degrees of Freedom"}">
PYTHON · NUMPY · IN-BROWSER
Joints & Degrees of Freedom
The real Bench probes a MuJoCo-WASM sim for DOF. Here we emulate the kinematic referee in numpy: declare a joint (type, axis, range) and the grader applies a small test wrench in all six directions, measures which produce motion, and reports n_free, the realized axis, and the kind - exactly the sim-based DOF probe, not an XML parse. Edit only the joint declaration cell, gravity off, kinematic only.