Download scripts/yam_orient_probe.py from yqi19/mp_yam_code: direct link, hf CLI and curl.
- Browser
- Download file 4.91 kB
-
https://huggingface.co/yqi19/mp_yam_code/resolve/main/scripts/yam_orient_probe.py
- Command line
-
hf download hf://yqi19/mp_yam_code/scripts/yam_orient_probe.py
-
curl -L -o yam_orient_probe.py https://huggingface.co/yqi19/mp_yam_code/resolve/main/scripts/yam_orient_probe.py
4.91 kB
| """Find the rotation that makes a RoboTwin asset sit the right way up. | |
| RoboTwin GLBs are not authored Z-up, so a cup spawns upside-down and a basket spawns on its | |
| side. Rather than guess one rotation per asset, this drops the object at several candidate | |
| orientations, lets each settle, and RENDERS a labelled contact-sheet so the correct one can be | |
| picked by eye -- plus the settled bbox height, which is the objective tell (an upright cup is | |
| taller than it is wide; a tipped one is not). | |
| ROBOTWIN_USD=... YAM_RW_OBJECTS="rw_cupA=021_cup/base0.usd:0.075:0.08" \ | |
| python scripts/yam_orient_probe.py --headless --obj rw_cupA --out outputs/probe_cup.png | |
| """ | |
| import argparse, sys, os | |
| from isaaclab.app import AppLauncher | |
| p = argparse.ArgumentParser() | |
| p.add_argument("--obj", required=True) | |
| p.add_argument("--xy", default="0.00,0.10") | |
| p.add_argument("--rpys", default="0,0,0;180,0,0;90,0,0;-90,0,0;0,90,0;0,180,0", | |
| help="';'-separated roll,pitch,yaw candidates in degrees") | |
| p.add_argument("--out", default="outputs/orient_probe.png") | |
| AppLauncher.add_app_launcher_args(p) | |
| args = p.parse_args(); args.headless = True; args.enable_cameras = True | |
| app = AppLauncher(args).app | |
| import numpy as np, torch, gymnasium as gym | |
| from PIL import Image, ImageDraw | |
| REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
| sys.path.insert(0, os.path.join(REPO, "source")); sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |
| import bimanual.tasks.manager_based.yam # noqa | |
| from isaaclab_tasks.utils import parse_env_cfg | |
| TASK = "Template-YAM-Play-v0"; dev = "cuda:0" | |
| cfg = parse_env_cfg(TASK, device=dev, num_envs=1) | |
| cfg.episode_length_s = 1.0e6 | |
| try: | |
| cfg.terminations.time_out = None | |
| except Exception: | |
| pass | |
| try: | |
| # close-in view of the object spot, so the contact sheet actually shows the orientation | |
| cfg.viewer.eye = (0.55, -0.35, 0.85); cfg.viewer.lookat = (0.0, 0.10, 0.50) | |
| cfg.viewer.resolution = (640, 480) | |
| except Exception as e: | |
| print("viewer cfg:", e) | |
| env = gym.make(TASK, cfg=cfg, render_mode="rgb_array"); u = env.unwrapped; env.reset() | |
| if args.obj not in u.scene.rigid_objects: | |
| raise SystemExit(f"[probe] {args.obj!r} not in scene; register it with YAM_RW_OBJECTS") | |
| OBJ = u.scene.rigid_objects[args.obj] | |
| origin = u.scene.env_origins[0].cpu().numpy() | |
| TABLE_TOP = 0.45 | |
| XY = [float(v) for v in args.xy.split(",")] | |
| import omni.usd | |
| from pxr import UsdGeom, Usd | |
| stage = omni.usd.get_context().get_stage() | |
| bbc = UsdGeom.BBoxCache(Usd.TimeCode.Default(), [UsdGeom.Tokens.default_, UsdGeom.Tokens.render]) | |
| rng = bbc.ComputeWorldBound(stage.GetPrimAtPath(OBJ.root_physx_view.prim_paths[0])).ComputeAlignedRange() | |
| ext = np.array(rng.GetMax())-np.array(rng.GetMin()) | |
| print(f"[probe] {args.obj} authored extents={np.round(ext,4)}", flush=True) | |
| def quat(r, pi, y): | |
| r, pi, y = np.radians([r, pi, y]) | |
| cr, sr = np.cos(r/2), np.sin(r/2); cp, sp = np.cos(pi/2), np.sin(pi/2); cy, sy = np.cos(y/2), np.sin(y/2) | |
| return np.array([cr*cp*cy+sr*sp*sy, sr*cp*cy-cr*sp*sy, cr*sp*cy+sr*cp*sy, cr*cp*sy-sr*sp*cy]) | |
| def zeros_act(): | |
| return torch.zeros((1, env.action_space.shape[1]), dtype=torch.float32, device=dev) | |
| tiles = [] | |
| for spec in [s for s in args.rpys.split(";") if s]: | |
| r, pi, y = [float(v) for v in spec.split(",")] | |
| q = quat(r, pi, y) | |
| seat = TABLE_TOP+float(max(ext))/2.0+0.01 # drop from just above, whatever axis is up | |
| OBJ.write_root_pose_to_sim(torch.tensor(np.concatenate([origin+np.array([XY[0], XY[1], seat]), q]), | |
| dtype=torch.float32, device=dev).view(1, 7)) | |
| OBJ.write_root_velocity_to_sim(torch.zeros((1, 6), device=dev)) | |
| for _ in range(120): # let it settle onto the table | |
| env.step(zeros_act()) | |
| w = OBJ.data.root_pos_w[0].cpu().numpy()-origin | |
| qs = OBJ.data.root_quat_w[0].cpu().numpy() | |
| # world-space height of the object above the table = the objective "is it upright" signal | |
| height = float(w[2]-TABLE_TOP)*2 | |
| img = env.render() | |
| im = Image.fromarray(np.asarray(img)[..., :3].copy()) if img is not None else Image.new("RGB", (640, 480)) | |
| d = ImageDraw.Draw(im) | |
| txt = f"rpy=({r:.0f},{pi:.0f},{y:.0f}) settled z={w[2]:.3f} (~{height:.3f} tall)" | |
| d.rectangle([0, 0, 430, 22], fill=(0, 0, 0)); d.text((5, 4), txt, fill=(255, 235, 60)) | |
| tiles.append(im) | |
| print(f"[probe] rpy=({r:.0f},{pi:.0f},{y:.0f}) settled pos=({w[0]:.3f},{w[1]:.3f},{w[2]:.3f}) " | |
| f"quat={np.round(qs,3)}", flush=True) | |
| cols = 3 | |
| rows = (len(tiles)+cols-1)//cols | |
| W, H = tiles[0].size | |
| sheet = Image.new("RGB", (W*cols, H*rows)) | |
| for i, im in enumerate(tiles): | |
| sheet.paste(im, (W*(i % cols), H*(i//cols))) | |
| os.makedirs(os.path.dirname(args.out), exist_ok=True) | |
| sheet.save(args.out) | |
| print(f"[probe] contact sheet -> {args.out}", flush=True) | |
| env.close(); app.close(); print("ORIENT_PROBE_OK", flush=True) | |