| """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 |
| 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: |
| |
| 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 |
| 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): |
| env.step(zeros_act()) |
| w = OBJ.data.root_pos_w[0].cpu().numpy()-origin |
| qs = OBJ.data.root_quat_w[0].cpu().numpy() |
| |
| 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) |
|
|