"""Convert an ARTICULATED RoboTwin object (SAPIEN-style URDF) to USD. Most RoboTwin categories ship a single fused GLB, but some also ship articulated variants under numbered sub-directories with a `mobility.urdf` and a `semantics.txt`. `060_kitchenpot/100015`, for instance, is `link_0 = lid` on a PRISMATIC joint plus `link_1 = pot_body` -- i.e. a pot whose lid genuinely lifts off, which the fused GLB cannot do. ROBOTWIN_SRC=... ROBOTWIN_USD=... \ python scripts/robotwin_urdf_convert.py --headless --jobs 060_kitchenpot/100015 """ import argparse, os, sys from isaaclab.app import AppLauncher p = argparse.ArgumentParser() p.add_argument("--jobs", default="060_kitchenpot/100015", help="comma-separated / dirs holding a mobility.urdf") p.add_argument("--fix_base", action="store_true", help="pin the base in place (a static fixture)") AppLauncher.add_app_launcher_args(p) a = p.parse_args(); a.headless = True app = AppLauncher(a).app from isaaclab.sim.converters import UrdfConverter, UrdfConverterCfg SRC = os.environ.get("ROBOTWIN_SRC", "/home/yu/internship_yu/robotwin_object/objects") OUT = os.environ.get("ROBOTWIN_USD", "/home/yu/internship_yu/robotwin_usd") os.makedirs(OUT, exist_ok=True) for job in [j for j in a.jobs.split(",") if j]: cat, inst = job.split("/") urdf = f"{SRC}/{cat}/{inst}/mobility.urdf" if not os.path.exists(urdf): print(f"[urdf] MISSING {urdf}", flush=True); continue sem = f"{SRC}/{cat}/{inst}/semantics.txt" if os.path.exists(sem): print(f"[urdf] {cat}/{inst} parts: {open(sem).read().strip().splitlines()}", flush=True) # `fix_base` and the joint drive's PD gains are required fields in this Isaac Lab version -- # a lid on a prismatic joint needs a soft drive so the arm can pull it open rather than # fighting a stiff actuator. cfg = UrdfConverterCfg( asset_path=urdf, usd_dir=f"{OUT}/{cat}", usd_file_name=f"{inst}.usd", force_usd_conversion=True, fix_base=a.fix_base, # THE default here is "convex_hull", and it is wrong for every one of these fixtures: it # collapses a whole microwave into a single box, swallowing the door, and the door's # collider then interpenetrates the body's so the hinge cannot move at all. It also makes # the asset RENDER as a featureless cube. Decomposition keeps the parts apart. collider_type="convex_decomposition", joint_drive=UrdfConverterCfg.JointDriveCfg( target_type="none", gains=UrdfConverterCfg.JointDriveCfg.PDGainsCfg(stiffness=0.0, damping=2.0), ), ) try: conv = UrdfConverter(cfg) # The importer swallows its own failures: a hyphen in a mesh filename makes it die with # "Used null prim", write a 492-byte stub, and return normally. Size-check the result so # a broken conversion is caught here instead of as a missing ArticulationRootAPI later. size = os.path.getsize(conv.usd_path) if os.path.exists(conv.usd_path) else 0 payload = sum(os.path.getsize(os.path.join(r, f)) for r, _d, fs in os.walk(os.path.dirname(conv.usd_path)) for f in fs) if payload < 12_000: print(f"[urdf] FAIL {cat}/{inst}: importer produced an EMPTY asset " f"({size} B stub, {payload} B total). Almost always a USD-illegal character in " f"a mesh/link name -- run scripts/sanitize_urdf_asset.py {cat}/{inst} first.", flush=True) else: print(f"[urdf] OK {cat}/{inst} -> {conv.usd_path} ({payload//1024} KB)", flush=True) except Exception as e: print(f"[urdf] FAIL {cat}/{inst}: {type(e).__name__}: {e}", flush=True) app.close(); print("URDF_CONVERT_DONE", flush=True)