| # Diagnostics log |
|
|
| Every failure in this suite that took more than one attempt to understand, with the *measurement* |
| that settled it. Written down because the diagnosis is worth more than the fix: almost all of |
| these look like tuning problems and are not. |
|
|
| The recurring lesson: **when a grasp or a joint does not behave, measure the asset offline |
| (trimesh on the source mesh, or the URDF's own numbers) before touching a parameter.** |
|
|
| --- |
|
|
| ## Asset geometry |
|
|
| ### The mesh origin is often at the bbox EDGE, not the centre |
| `object_pos()` returns the rigid body's origin. For several RoboTwin meshes the geometry is not |
| centred on it, so the grasp lands beside the object. Symptom: `gap 0.0 cm` (jaws fully closed on |
| nothing) or `object did not rise` β never anything that points at geometry. |
|
|
| | asset | offset from origin | how it was found | |
| |---|---|---| |
| | `083_brush` | **+3.7 cm in y** (its Y runs 0..0.46, not β0.23..0.23) | trimesh: centroid of the handle slice | |
| | `091_kettle` | **+4.7 cm in y** | trimesh: per-slice centroid along the tall axis | |
|
|
| Fix: measure the centroid of the part you intend to grip and pass `grasp_offset`. |
|
|
| ### Slice the mesh to find the graspable feature |
| Both the brush and the kettle were being gripped in the wrong place entirely. Slicing the mesh |
| along its long axis and printing each slice's cross-section shows where the thin part is: |
|
|
| * `083_brush` β 0β40 % of its length is a **1.0 cm handle**, 40β100 % is the 2.7β3.6 cm head. |
| * `091_kettle` β top 25 % is a **1.5 Γ 3.5 cm neck**; the rest is a smooth 7 cm shell. |
|
|
| (The kettle's neck stalls the jaws but slips out under load β the body grip works once the y |
| offset is corrected. A stall is not a grip.) |
|
|
| ### The bbox is pose-independent β `object_size()` ignores yaw |
| `handover_basket` sets `yaw=90` so the basket's short axis faces the jaws, but `auto_jaw()` reads |
| the **authored** bbox and so chose the wrong closing axis. Anything that yaws an object must |
| state its jaw axis and its rotated half-extent explicitly. |
| |
| ### RoboTwin GLBs are Y-up β and so are the SAPIEN articulations |
| Long known for the rigid meshes (`rpy: (90,0,0)`). It is **also** true of the articulated assets, |
| which was not handled and broke every one of them: |
| |
| * `036_cabinet` β prismatic axis `(0,0,1)` with drawers stacked along **Y** β the drawers were |
| being asked to slide straight **up**. |
| * `044_microwave` β hinge axis `(0,-1,0)` β a microwave door hinging about a **horizontal** axis. |
| * `060_kitchenpot` β lid slides along **Y**, i.e. vertically. Consistent. |
|
|
| `place_articulation` now applies `roll=90` by default, mapping asset `(x,y,z)` β sim `(x,-z,y)`. |
|
|
| ### Seat articulations from `bounding_box.json`, never from the USD |
| `ComputeWorldBound` on an articulation root returns an **empty range**, whose min is `-3.4e38`. |
| Using it as a seat height threw every fixture to negative infinity. The shipped |
| `bounding_box.json Γ scale` is deterministic and correct. |
|
|
| --- |
|
|
| ## Asset conversion |
|
|
| ### A hyphen in a mesh filename silently produces an empty USD |
| SAPIEN names collision meshes `original-7.obj`. Hyphens are illegal in USD prim paths. Isaac's |
| URDF importer half-handles it: renames the prim to `original_7`, then still tries to create |
| `</colliders/base/original-7>`, fails to parse, dies with `Used null prim` β **writes a 492-byte |
| stub and returns normally**. The breakage only surfaces much later as |
| `Failed to find an articulation ... ArticulationRootAPI`. |
|
|
| Fix: `scripts/sanitize_urdf_asset.py`, plus a payload-size check in the converter that FAILS. |
|
|
| ### Renaming `.mtl` files without rewriting `mtllib` inside the `.obj` |
| An OBJ names its material library **inside the file**. Renaming the `.mtl` on disk leaves every |
| mesh material-less and the whole asset imports as featureless grey β the microwave rendered as a |
| plain white cube. The sanitizer now rewrites `mtllib` / `usemtl` / `map_*` lines too. |
|
|
| ### `UrdfConverterCfg.collider_type` defaults to `convex_hull` |
| That collapses a whole microwave into one box, swallowing the door, and the door's collider then |
| interpenetrates the body's so the hinge cannot move. Forced to `convex_decomposition`. |
|
|
| ### A carried vessel cannot have an interior |
| PhysX will not simulate a hollow triangle mesh as a **dynamic** body, so anything liftable is |
| convex and solid inside. Beads placed "in" the kettle were pushed onto the table before the |
| episode started (measured at `z=0.459`, i.e. table height, every run). Static containers can be |
| hollow β convert them with `--collision none --suffix _mesh`. |
|
|
| Consequence: `pour_kettle` is scored on the **pour** (vessel over the target, tipped past the |
| angle its contents would leave it), not on contents landing. |
|
|
| --- |
|
|
| ## The checking code lying |
|
|
| Three false positives, all of which reported SUCCESS for episodes that did nothing. These were |
| worse than the task failures, because they hide them. |
|
|
| | check | why it lied | fix | |
| |---|---|---| |
| | `count_in_region` | no height bound β a bead still inside the carried vessel counted as poured, so an episode whose **grasp failed** reported SUCCESS | added `below_top`, plus a per-object position print | |
| | `joint_moved` | baselined at build time, so the fixture settling under gravity counted as progress β `switch_toggle` "passed" on 0.44 rad of falling and 0.008 rad of robot work | `joint_driven`: the delta the solver measured across the action itself | |
| | `joint_driven` (first version) | still passed a joint that had blown to **β7.99 rad** against a `[β2.339, 0.754]` limit | also require the final value to be inside the joint's limits | |
|
|
| ### `ast.parse` does not catch duplicate keyword arguments |
| My syntax check passed a call with `grip_tilt=` written twice; it only failed at run time. Use |
| `compile()`. |
|
|
| ### Verifying only with `--no-randomize` hides pose bugs |
| `lid_food` placed the lid at the class constant `self.pot_xy` while the pot itself was jittered β |
| so with randomization on, the lid landed beside the pot. Invisible for as long as every check run |
| used nominal poses. Always derive from the value the builder **returned**. |
|
|
| --- |
|
|
| ## Robot workspace |
|
|
| Shoulders are at `(-0.2, +0.2)` and `(-0.2, -0.2)`; the wrist parks at `[0.078, -0.19]`. |
|
|
| * **Comfortable reach is ~0.25 m; ~0.34 m still works; 0.38β0.40 m stalls.** Several "grasp |
| failed" bugs were only this β `sweep_debris` (0.38 m), `cupboard_store` (0.40 m), |
| `pour_kettle` (0.39 m). |
| * **Nearest arm is not the best arm.** `sort_cubes` chose the right arm for a pad 9 cm from its |
| own shoulder; folded up that far it could not servo (0.54 m tracking error). Score on |
| `|reach β 0.25|` instead. |
| * **Each arm can only push away from its own shoulder.** To push in βy the hand must stand on the |
| block's +y side, which the right arm (at y=β0.2) can only do by reaching over the block β |
| the approach swipes it off the table. `push_relay` is an L-shaped route so each arm pushes its |
| own natural direction. |
| * **The home pose sits inside the only reachable placement band.** This is why `cupboard_store` |
| is parked: moved in, the unit's walls block the arm before it can reach the object; moved out, |
| the shelf is past reach. |
|
|
| ## Motion |
|
|
| * **The jaw opens 9.4 cm and clips above ~7.8 cm.** `check_graspable()` warns. |
| * **Push: the loop must be parameterised by CONTROL STEPS, not distance.** Sizing it as |
| `span/advance` gave 25 iterations = 25 sim steps for a 22 cm push, which reads exactly like |
| "the block will not slide". |
| * **Push penetration is capped at 10 mm.** 17 mm into a 25 mm half-block launched it 1.8 m. |
| * **Re-aim the pusher every step from the live pose.** A line fixed at t=0 has no authority to |
| steer a block that has skidded (13 cm off in x). |
| * **Grip height for anything resting on a fixture must come from physics** (`live_z=True`), not |
| from the table. A lid on a 7.5 cm pot rim was grasped 7.5 cm too low. |
| * **`env.render()` needs warm-up.** A single render straight after `build()` returns black β all |
| 29 tiles of the first overview sheet were black. Step and render repeatedly, keep the last. |
|
|
| ## Still open |
|
|
| * `rope_straighten` β the rope never leaves its off-scene spawn. It is the only **floating-base** |
| articulation (the fixed-base ones all move). Try `write_root_state_to_sim()`. Separately, the |
| chain interleaves massless spacer links, so body index 13 is **not** `seg13` β use |
| `scene.link_index()`. |
| * `microwave_door`, `drawer_open`, `drawer_store`, `laptop_close`, `switch_toggle` β all load, |
| seat and report their joints correctly now; the remaining problem is the grip pose against a |
| flat, handle-less panel, and (for the switch) that the button's link frame **is** the hinge, so |
| pressing there has zero moment arm. |
| * `handover_basket` β both hands grip, but the basket is empty by the time the second hand |
| arrives. 0.062 scale (8.8 cm short axis) is the largest this basket can be and still be |
| graspable at all. |
| * `ring_post`, `push_relay`, `pour_cup`, `cupboard_store` β see the notes in each task file. |
|
|