diagnostics: every non-obvious failure and the measurement that settled it
Browse files- DIAGNOSTICS.md +157 -0
DIAGNOSTICS.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Diagnostics log
|
| 2 |
+
|
| 3 |
+
Every failure in this suite that took more than one attempt to understand, with the *measurement*
|
| 4 |
+
that settled it. Written down because the diagnosis is worth more than the fix: almost all of
|
| 5 |
+
these look like tuning problems and are not.
|
| 6 |
+
|
| 7 |
+
The recurring lesson: **when a grasp or a joint does not behave, measure the asset offline
|
| 8 |
+
(trimesh on the source mesh, or the URDF's own numbers) before touching a parameter.**
|
| 9 |
+
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
## Asset geometry
|
| 13 |
+
|
| 14 |
+
### The mesh origin is often at the bbox EDGE, not the centre
|
| 15 |
+
`object_pos()` returns the rigid body's origin. For several RoboTwin meshes the geometry is not
|
| 16 |
+
centred on it, so the grasp lands beside the object. Symptom: `gap 0.0 cm` (jaws fully closed on
|
| 17 |
+
nothing) or `object did not rise` β never anything that points at geometry.
|
| 18 |
+
|
| 19 |
+
| asset | offset from origin | how it was found |
|
| 20 |
+
|---|---|---|
|
| 21 |
+
| `083_brush` | **+3.7 cm in y** (its Y runs 0..0.46, not β0.23..0.23) | trimesh: centroid of the handle slice |
|
| 22 |
+
| `091_kettle` | **+4.7 cm in y** | trimesh: per-slice centroid along the tall axis |
|
| 23 |
+
|
| 24 |
+
Fix: measure the centroid of the part you intend to grip and pass `grasp_offset`.
|
| 25 |
+
|
| 26 |
+
### Slice the mesh to find the graspable feature
|
| 27 |
+
Both the brush and the kettle were being gripped in the wrong place entirely. Slicing the mesh
|
| 28 |
+
along its long axis and printing each slice's cross-section shows where the thin part is:
|
| 29 |
+
|
| 30 |
+
* `083_brush` β 0β40 % of its length is a **1.0 cm handle**, 40β100 % is the 2.7β3.6 cm head.
|
| 31 |
+
* `091_kettle` β top 25 % is a **1.5 Γ 3.5 cm neck**; the rest is a smooth 7 cm shell.
|
| 32 |
+
|
| 33 |
+
(The kettle's neck stalls the jaws but slips out under load β the body grip works once the y
|
| 34 |
+
offset is corrected. A stall is not a grip.)
|
| 35 |
+
|
| 36 |
+
### The bbox is pose-independent β `object_size()` ignores yaw
|
| 37 |
+
`handover_basket` sets `yaw=90` so the basket's short axis faces the jaws, but `auto_jaw()` reads
|
| 38 |
+
the **authored** bbox and so chose the wrong closing axis. Anything that yaws an object must
|
| 39 |
+
state its jaw axis and its rotated half-extent explicitly.
|
| 40 |
+
|
| 41 |
+
### RoboTwin GLBs are Y-up β and so are the SAPIEN articulations
|
| 42 |
+
Long known for the rigid meshes (`rpy: (90,0,0)`). It is **also** true of the articulated assets,
|
| 43 |
+
which was not handled and broke every one of them:
|
| 44 |
+
|
| 45 |
+
* `036_cabinet` β prismatic axis `(0,0,1)` with drawers stacked along **Y** β the drawers were
|
| 46 |
+
being asked to slide straight **up**.
|
| 47 |
+
* `044_microwave` β hinge axis `(0,-1,0)` β a microwave door hinging about a **horizontal** axis.
|
| 48 |
+
* `060_kitchenpot` β lid slides along **Y**, i.e. vertically. Consistent.
|
| 49 |
+
|
| 50 |
+
`place_articulation` now applies `roll=90` by default, mapping asset `(x,y,z)` β sim `(x,-z,y)`.
|
| 51 |
+
|
| 52 |
+
### Seat articulations from `bounding_box.json`, never from the USD
|
| 53 |
+
`ComputeWorldBound` on an articulation root returns an **empty range**, whose min is `-3.4e38`.
|
| 54 |
+
Using it as a seat height threw every fixture to negative infinity. The shipped
|
| 55 |
+
`bounding_box.json Γ scale` is deterministic and correct.
|
| 56 |
+
|
| 57 |
+
---
|
| 58 |
+
|
| 59 |
+
## Asset conversion
|
| 60 |
+
|
| 61 |
+
### A hyphen in a mesh filename silently produces an empty USD
|
| 62 |
+
SAPIEN names collision meshes `original-7.obj`. Hyphens are illegal in USD prim paths. Isaac's
|
| 63 |
+
URDF importer half-handles it: renames the prim to `original_7`, then still tries to create
|
| 64 |
+
`</colliders/base/original-7>`, fails to parse, dies with `Used null prim` β **writes a 492-byte
|
| 65 |
+
stub and returns normally**. The breakage only surfaces much later as
|
| 66 |
+
`Failed to find an articulation ... ArticulationRootAPI`.
|
| 67 |
+
|
| 68 |
+
Fix: `scripts/sanitize_urdf_asset.py`, plus a payload-size check in the converter that FAILS.
|
| 69 |
+
|
| 70 |
+
### Renaming `.mtl` files without rewriting `mtllib` inside the `.obj`
|
| 71 |
+
An OBJ names its material library **inside the file**. Renaming the `.mtl` on disk leaves every
|
| 72 |
+
mesh material-less and the whole asset imports as featureless grey β the microwave rendered as a
|
| 73 |
+
plain white cube. The sanitizer now rewrites `mtllib` / `usemtl` / `map_*` lines too.
|
| 74 |
+
|
| 75 |
+
### `UrdfConverterCfg.collider_type` defaults to `convex_hull`
|
| 76 |
+
That collapses a whole microwave into one box, swallowing the door, and the door's collider then
|
| 77 |
+
interpenetrates the body's so the hinge cannot move. Forced to `convex_decomposition`.
|
| 78 |
+
|
| 79 |
+
### A carried vessel cannot have an interior
|
| 80 |
+
PhysX will not simulate a hollow triangle mesh as a **dynamic** body, so anything liftable is
|
| 81 |
+
convex and solid inside. Beads placed "in" the kettle were pushed onto the table before the
|
| 82 |
+
episode started (measured at `z=0.459`, i.e. table height, every run). Static containers can be
|
| 83 |
+
hollow β convert them with `--collision none --suffix _mesh`.
|
| 84 |
+
|
| 85 |
+
Consequence: `pour_kettle` is scored on the **pour** (vessel over the target, tipped past the
|
| 86 |
+
angle its contents would leave it), not on contents landing.
|
| 87 |
+
|
| 88 |
+
---
|
| 89 |
+
|
| 90 |
+
## The checking code lying
|
| 91 |
+
|
| 92 |
+
Three false positives, all of which reported SUCCESS for episodes that did nothing. These were
|
| 93 |
+
worse than the task failures, because they hide them.
|
| 94 |
+
|
| 95 |
+
| check | why it lied | fix |
|
| 96 |
+
|---|---|---|
|
| 97 |
+
| `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 |
|
| 98 |
+
| `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 |
|
| 99 |
+
| `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 |
|
| 100 |
+
|
| 101 |
+
### `ast.parse` does not catch duplicate keyword arguments
|
| 102 |
+
My syntax check passed a call with `grip_tilt=` written twice; it only failed at run time. Use
|
| 103 |
+
`compile()`.
|
| 104 |
+
|
| 105 |
+
### Verifying only with `--no-randomize` hides pose bugs
|
| 106 |
+
`lid_food` placed the lid at the class constant `self.pot_xy` while the pot itself was jittered β
|
| 107 |
+
so with randomization on, the lid landed beside the pot. Invisible for as long as every check run
|
| 108 |
+
used nominal poses. Always derive from the value the builder **returned**.
|
| 109 |
+
|
| 110 |
+
---
|
| 111 |
+
|
| 112 |
+
## Robot workspace
|
| 113 |
+
|
| 114 |
+
Shoulders are at `(-0.2, +0.2)` and `(-0.2, -0.2)`; the wrist parks at `[0.078, -0.19]`.
|
| 115 |
+
|
| 116 |
+
* **Comfortable reach is ~0.25 m; ~0.34 m still works; 0.38β0.40 m stalls.** Several "grasp
|
| 117 |
+
failed" bugs were only this β `sweep_debris` (0.38 m), `cupboard_store` (0.40 m),
|
| 118 |
+
`pour_kettle` (0.39 m).
|
| 119 |
+
* **Nearest arm is not the best arm.** `sort_cubes` chose the right arm for a pad 9 cm from its
|
| 120 |
+
own shoulder; folded up that far it could not servo (0.54 m tracking error). Score on
|
| 121 |
+
`|reach β 0.25|` instead.
|
| 122 |
+
* **Each arm can only push away from its own shoulder.** To push in βy the hand must stand on the
|
| 123 |
+
block's +y side, which the right arm (at y=β0.2) can only do by reaching over the block β
|
| 124 |
+
the approach swipes it off the table. `push_relay` is an L-shaped route so each arm pushes its
|
| 125 |
+
own natural direction.
|
| 126 |
+
* **The home pose sits inside the only reachable placement band.** This is why `cupboard_store`
|
| 127 |
+
is parked: moved in, the unit's walls block the arm before it can reach the object; moved out,
|
| 128 |
+
the shelf is past reach.
|
| 129 |
+
|
| 130 |
+
## Motion
|
| 131 |
+
|
| 132 |
+
* **The jaw opens 9.4 cm and clips above ~7.8 cm.** `check_graspable()` warns.
|
| 133 |
+
* **Push: the loop must be parameterised by CONTROL STEPS, not distance.** Sizing it as
|
| 134 |
+
`span/advance` gave 25 iterations = 25 sim steps for a 22 cm push, which reads exactly like
|
| 135 |
+
"the block will not slide".
|
| 136 |
+
* **Push penetration is capped at 10 mm.** 17 mm into a 25 mm half-block launched it 1.8 m.
|
| 137 |
+
* **Re-aim the pusher every step from the live pose.** A line fixed at t=0 has no authority to
|
| 138 |
+
steer a block that has skidded (13 cm off in x).
|
| 139 |
+
* **Grip height for anything resting on a fixture must come from physics** (`live_z=True`), not
|
| 140 |
+
from the table. A lid on a 7.5 cm pot rim was grasped 7.5 cm too low.
|
| 141 |
+
* **`env.render()` needs warm-up.** A single render straight after `build()` returns black β all
|
| 142 |
+
29 tiles of the first overview sheet were black. Step and render repeatedly, keep the last.
|
| 143 |
+
|
| 144 |
+
## Still open
|
| 145 |
+
|
| 146 |
+
* `rope_straighten` β the rope never leaves its off-scene spawn. It is the only **floating-base**
|
| 147 |
+
articulation (the fixed-base ones all move). Try `write_root_state_to_sim()`. Separately, the
|
| 148 |
+
chain interleaves massless spacer links, so body index 13 is **not** `seg13` β use
|
| 149 |
+
`scene.link_index()`.
|
| 150 |
+
* `microwave_door`, `drawer_open`, `drawer_store`, `laptop_close`, `switch_toggle` β all load,
|
| 151 |
+
seat and report their joints correctly now; the remaining problem is the grip pose against a
|
| 152 |
+
flat, handle-less panel, and (for the switch) that the button's link frame **is** the hinge, so
|
| 153 |
+
pressing there has zero moment arm.
|
| 154 |
+
* `handover_basket` β both hands grip, but the basket is empty by the time the second hand
|
| 155 |
+
arrives. 0.062 scale (8.8 cm short axis) is the largest this basket can be and still be
|
| 156 |
+
graspable at all.
|
| 157 |
+
* `ring_post`, `push_relay`, `pour_cup`, `cupboard_store` β see the notes in each task file.
|