From 9b0a5eed78c0afb0205a106849b89db73539eb8b Mon Sep 17 00:00:00 2001 From: xiemenghong Date: Mon, 2 Mar 2026 18:49:38 +0800 Subject: [PATCH 01/14] usd example --- embodichain/lab/sim/cfg.py | 16 ++ embodichain/lab/sim/objects/articulation.py | 73 ++++----- embodichain/lab/sim/objects/rigid_object.py | 7 +- embodichain/lab/sim/sim_manager.py | 56 ++++++- embodichain/lab/sim/utility/sim_utils.py | 22 +++ scripts/tutorials/sim/import_usd.py | 164 ++++++++++++++++++++ 6 files changed, 293 insertions(+), 45 deletions(-) create mode 100644 scripts/tutorials/sim/import_usd.py diff --git a/embodichain/lab/sim/cfg.py b/embodichain/lab/sim/cfg.py index 440e525c..f857e916 100644 --- a/embodichain/lab/sim/cfg.py +++ b/embodichain/lab/sim/cfg.py @@ -546,6 +546,14 @@ class RigidObjectCfg(ObjectBaseCfg): body_scale: Union[tuple, list] = (1.0, 1.0, 1.0) """Scale of the rigid body in the simulation world frame.""" + use_usd_properties: bool = False + """Whether to use physical properties from USD file instead of config. + + When True: Keep all physical properties (drive, physics attrs, etc.) from USD file. + When False (default): Override USD properties with config values (URDF behavior). + Only effective for USD files, ignored for URDF files. + """ + def to_dexsim_body_type(self) -> ActorType: """Convert the body type to dexsim ActorType.""" if self.body_type == "dynamic": @@ -1017,6 +1025,14 @@ class ArticulationCfg(ObjectBaseCfg): Currently, the uv mapping is computed for each link with projection uv mapping method. """ + + use_usd_properties: bool = False + """Whether to use physical properties from USD file instead of config. + + When True: Keep all physical properties (drive, physics attrs, etc.) from USD file. + When False (default): Override USD properties with config values (URDF behavior). + Only effective for USD files, ignored for URDF files. + """ @configclass diff --git a/embodichain/lab/sim/objects/articulation.py b/embodichain/lab/sim/objects/articulation.py index d6ebe2aa..12347a65 100644 --- a/embodichain/lab/sim/objects/articulation.py +++ b/embodichain/lab/sim/objects/articulation.py @@ -581,42 +581,43 @@ def __init__( self.cfg: ArticulationCfg if self.cfg.init_qpos is None: self.cfg.init_qpos = torch.zeros(self.dof, dtype=torch.float32) - - # Set articulation configuration in DexSim - set_dexsim_articulation_cfg(entities, self.cfg) - - # Init joint drive parameters. - num_entities = len(entities) - dof = self._data.dof - default_cfg = JointDrivePropertiesCfg() - self.default_joint_damping = torch.full( - (num_entities, dof), default_cfg.damping, dtype=torch.float32, device=device - ) - self.default_joint_stiffness = torch.full( - (num_entities, dof), - default_cfg.stiffness, - dtype=torch.float32, - device=device, - ) - self.default_joint_max_effort = torch.full( - (num_entities, dof), - default_cfg.max_effort, - dtype=torch.float32, - device=device, - ) - self.default_joint_max_velocity = torch.full( - (num_entities, dof), - default_cfg.max_velocity, - dtype=torch.float32, - device=device, - ) - self.default_joint_friction = torch.full( - (num_entities, dof), - default_cfg.friction, - dtype=torch.float32, - device=device, - ) - self._set_default_joint_drive() + + if not cfg.use_usd_properties: + # Set articulation configuration in DexSim + set_dexsim_articulation_cfg(entities, self.cfg) + + # Init joint drive parameters. + num_entities = len(entities) + dof = self._data.dof + default_cfg = JointDrivePropertiesCfg() + self.default_joint_damping = torch.full( + (num_entities, dof), default_cfg.damping, dtype=torch.float32, device=device + ) + self.default_joint_stiffness = torch.full( + (num_entities, dof), + default_cfg.stiffness, + dtype=torch.float32, + device=device, + ) + self.default_joint_max_effort = torch.full( + (num_entities, dof), + default_cfg.max_effort, + dtype=torch.float32, + device=device, + ) + self.default_joint_max_velocity = torch.full( + (num_entities, dof), + default_cfg.max_velocity, + dtype=torch.float32, + device=device, + ) + self.default_joint_friction = torch.full( + (num_entities, dof), + default_cfg.friction, + dtype=torch.float32, + device=device, + ) + self._set_default_joint_drive() self.pk_chain = None if self.cfg.build_pk_chain: diff --git a/embodichain/lab/sim/objects/rigid_object.py b/embodichain/lab/sim/objects/rigid_object.py index ac7723f5..46a91b42 100644 --- a/embodichain/lab/sim/objects/rigid_object.py +++ b/embodichain/lab/sim/objects/rigid_object.py @@ -211,9 +211,10 @@ def __init__( self._visual_material: List[VisualMaterialInst] = [None] * len(entities) self.is_shared_visual_material = False - for entity in entities: - entity.set_body_scale(*cfg.body_scale) - entity.set_physical_attr(cfg.attrs.attr()) + if not cfg.use_usd_properties: + for entity in entities: + entity.set_body_scale(*cfg.body_scale) + entity.set_physical_attr(cfg.attrs.attr()) if device.type == "cuda": self._world.update(0.001) diff --git a/embodichain/lab/sim/sim_manager.py b/embodichain/lab/sim/sim_manager.py index eb29fea6..0f5ff9f4 100644 --- a/embodichain/lab/sim/sim_manager.py +++ b/embodichain/lab/sim/sim_manager.py @@ -1041,9 +1041,31 @@ def add_articulation( env_list = [self._env] if len(self._arenas) == 0 else self._arenas obj_list = [] - for env in env_list: - art = env.load_urdf(cfg.fpath) - obj_list.append(art) + is_usd = cfg.fpath.endswith((".usd", ".usda", ".usdc")) + if is_usd: + # TODO: currently not supporting multiple arenas for USD + env= self._env + results = env.import_from_usd_file(cfg.fpath, return_object=True) + print("USD import results:", results) + + articulations_found = [] + for key, value in results.items(): + if isinstance(value, dexsim.engine.Articulation): + articulations_found.append(value) + + if len(articulations_found) == 0: + logger.log_error(f"No articulation found in USD file {cfg.fpath}.") + return None + elif len(articulations_found) > 1: + logger.log_error( + f"Multiple articulations found in USD file {cfg.fpath}. " + ) + elif len(articulations_found) == 1: + obj_list.append(articulations_found[0]) + else: + for env in env_list: + art = env.load_urdf(cfg.fpath) + obj_list.append(art) articulation = Articulation(cfg=cfg, entities=obj_list, device=self.device) @@ -1109,9 +1131,31 @@ def add_robot(self, cfg: RobotCfg) -> Robot | None: env_list = [self._env] if len(self._arenas) == 0 else self._arenas obj_list = [] - for env in env_list: - art = env.load_urdf(cfg.fpath) - obj_list.append(art) + is_usd = cfg.fpath.endswith((".usd", ".usda", ".usdc")) + if is_usd: + # TODO: currently not supporting multiple arenas for USD + env= self._env + results = env.import_from_usd_file(cfg.fpath, return_object=True) + print("USD import results:", results) + + articulations_found = [] + for key, value in results.items(): + if isinstance(value, dexsim.engine.Articulation): + articulations_found.append(value) + + if len(articulations_found) == 0: + logger.log_error(f"No articulation found in USD file {cfg.fpath}.") + return None + elif len(articulations_found) > 1: + logger.log_error( + f"Multiple articulations found in USD file {cfg.fpath}. " + ) + elif len(articulations_found) == 1: + obj_list.append(articulations_found[0]) + else: + for env in env_list: + art = env.load_urdf(cfg.fpath) + obj_list.append(art) robot = Robot(cfg=cfg, entities=obj_list, device=self.device) diff --git a/embodichain/lab/sim/utility/sim_utils.py b/embodichain/lab/sim/utility/sim_utils.py index 523c7fef..b071da28 100644 --- a/embodichain/lab/sim/utility/sim_utils.py +++ b/embodichain/lab/sim/utility/sim_utils.py @@ -219,6 +219,28 @@ def load_mesh_objects_from_cfg( compute_uv = cfg.shape.compute_uv + is_usd = fpath.endswith((".usd", ".usda", ".usdc")) + if is_usd: + # TODO: currently not supporting multiple arenas for USD + _env:dexsim.environment.Env = dexsim.default_world().get_env() + results = _env.import_from_usd_file(fpath,return_object=True) + print(f"import usd result: {results}") + + rigidbodys_found=[] + for key,value in results.items(): + if isinstance(value, dexsim.cuda.pybind.models.MeshObject): + rigidbodys_found.append(value) + if len(rigidbodys_found)==0: + logger.log_error(f"No rigid body found in USD file: {fpath}") + elif len(rigidbodys_found)>1: + logger.log_error(f"Multiple rigid bodies found in USD file: {fpath}.") + elif len(rigidbodys_found)==1: + obj_list.append(rigidbodys_found[0]) + return obj_list + else: + # non-usd file does not support this option, will be ignored if set. + cfg.use_usd_properties=False + for i, env in enumerate(env_list): if max_convex_hull_num > 1: obj = env.load_actor_with_coacd( diff --git a/scripts/tutorials/sim/import_usd.py b/scripts/tutorials/sim/import_usd.py new file mode 100644 index 00000000..a2de891c --- /dev/null +++ b/scripts/tutorials/sim/import_usd.py @@ -0,0 +1,164 @@ +# ---------------------------------------------------------------------------- +# Copyright (c) 2021-2025 DexForce Technology Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ---------------------------------------------------------------------------- + +""" +This script demonstrates how to create a simulation scene using SimulationManager. +It shows the basic setup of simulation context, adding objects, and sensors. +""" + +import argparse +import time + +from embodichain.lab.sim import SimulationManager, SimulationManagerCfg +from embodichain.lab.sim.cfg import RigidBodyAttributesCfg +from embodichain.lab.sim.shapes import CubeCfg, MeshCfg +from embodichain.lab.sim.objects import RigidObject, RigidObjectCfg,ArticulationCfg,Articulation +from dexsim.utility.path import get_resources_data_path + + +def main(): + """Main function to create and run the simulation scene.""" + + # Parse command line arguments + parser = argparse.ArgumentParser( + description="Create a simulation scene with SimulationManager" + ) + parser.add_argument( + "--headless", + action="store_true", + default=False, + help="Run simulation in headless mode", + ) + parser.add_argument( + "--device", type=str, default="cpu", help="Simulation device (cuda or cpu)" + ) + parser.add_argument( + "--enable_rt", + action="store_true", + default=True, + help="Enable ray tracing for better visuals", + ) + args = parser.parse_args() + + # Configure the simulation + sim_cfg = SimulationManagerCfg( + width=1920, + height=1080, + headless=True, + physics_dt=1.0 / 100.0, # Physics timestep (100 Hz) + sim_device=args.device, + enable_rt=args.enable_rt, # Enable ray tracing for better visuals + num_envs=1, + arena_space=3.0, + ) + + # Create the simulation instance + sim = SimulationManager(sim_cfg) + # Open window when the scene has been set up + if not args.headless: + sim.open_window() + + + cube: RigidObject = sim.add_rigid_object( + cfg=RigidObjectCfg( + uid="cube", + shape=CubeCfg(size=[0.1, 0.1, 0.1]), + body_type="dynamic", + attrs=RigidBodyAttributesCfg( + mass=1.0, + dynamic_friction=0.5, + static_friction=0.5, + restitution=0.1, + ), + init_pos=[0.0, 0.0, 1.0], + ) + ) + + usdpath="/home/xiemh/model/004_sugar_box/004_sugar_box_xmh.usda" + sugar_box: RigidObject = sim.add_rigid_object( + cfg=RigidObjectCfg( + uid="sugar_box", + shape=MeshCfg(fpath=usdpath), + body_type="dynamic", + init_pos=[0.2, 0.2, 1.0], + use_usd_properties=True, + ) + ) + + # Add objects to the scene + h1 :Articulation= sim.add_articulation( + cfg=ArticulationCfg( + uid="h1", + # fpath="/home/xiemh/model/Collected_ur10/ur10.usd", + fpath="/home/xiemh/model/Collected_h1/h1.usda", + build_pk_chain=False, + init_pos=[-0.2, -0.2, 1.0], + use_usd_properties=False, + ) + ) + + print("[INFO]: Scene setup complete!") + print("[INFO]: Press Ctrl+C to stop the simulation") + + # Run the simulation + run_simulation(sim) + + +def run_simulation(sim: SimulationManager): + """Run the simulation loop. + + Args: + sim: The SimulationManager instance to run + """ + + # Initialize GPU physics if using CUDA + if sim.is_use_gpu_physics: + sim.init_gpu_physics() + + step_count = 0 + + try: + last_time = time.time() + last_step = 0 + while True: + # Update physics simulation + sim.update(step=1) + time.sleep(0.03) # Sleep to limit update rate (optional) + step_count += 1 + + # Print FPS every second + if step_count % 100 == 0: + current_time = time.time() + elapsed = current_time - last_time + fps = ( + sim.num_envs * (step_count - last_step) / elapsed + if elapsed > 0 + else 0 + ) + # print(f"[INFO]: Simulation step: {step_count}, FPS: {fps:.2f}") + last_time = current_time + last_step = step_count + + except KeyboardInterrupt: + print("\n[INFO]: Stopping simulation...") + finally: + # Clean up resources + sim.destroy() + print("[INFO]: Simulation terminated successfully") + + +if __name__ == "__main__": + main() From 82f9990eca36cf48c44716f7c958e4ebeb740d28 Mon Sep 17 00:00:00 2001 From: xiemenghong Date: Mon, 2 Mar 2026 18:49:38 +0800 Subject: [PATCH 02/14] code style --- embodichain/lab/sim/cfg.py | 2 +- embodichain/lab/sim/objects/articulation.py | 7 +++-- embodichain/lab/sim/sim_manager.py | 12 ++++----- embodichain/lab/sim/utility/sim_utils.py | 18 ++++++------- scripts/tutorials/sim/import_usd.py | 30 ++++++++++++--------- 5 files changed, 38 insertions(+), 31 deletions(-) diff --git a/embodichain/lab/sim/cfg.py b/embodichain/lab/sim/cfg.py index f857e916..16bc4445 100644 --- a/embodichain/lab/sim/cfg.py +++ b/embodichain/lab/sim/cfg.py @@ -1025,7 +1025,7 @@ class ArticulationCfg(ObjectBaseCfg): Currently, the uv mapping is computed for each link with projection uv mapping method. """ - + use_usd_properties: bool = False """Whether to use physical properties from USD file instead of config. diff --git a/embodichain/lab/sim/objects/articulation.py b/embodichain/lab/sim/objects/articulation.py index 12347a65..dfd446f7 100644 --- a/embodichain/lab/sim/objects/articulation.py +++ b/embodichain/lab/sim/objects/articulation.py @@ -581,7 +581,7 @@ def __init__( self.cfg: ArticulationCfg if self.cfg.init_qpos is None: self.cfg.init_qpos = torch.zeros(self.dof, dtype=torch.float32) - + if not cfg.use_usd_properties: # Set articulation configuration in DexSim set_dexsim_articulation_cfg(entities, self.cfg) @@ -591,7 +591,10 @@ def __init__( dof = self._data.dof default_cfg = JointDrivePropertiesCfg() self.default_joint_damping = torch.full( - (num_entities, dof), default_cfg.damping, dtype=torch.float32, device=device + (num_entities, dof), + default_cfg.damping, + dtype=torch.float32, + device=device, ) self.default_joint_stiffness = torch.full( (num_entities, dof), diff --git a/embodichain/lab/sim/sim_manager.py b/embodichain/lab/sim/sim_manager.py index 0f5ff9f4..6f7c89fd 100644 --- a/embodichain/lab/sim/sim_manager.py +++ b/embodichain/lab/sim/sim_manager.py @@ -1044,7 +1044,7 @@ def add_articulation( is_usd = cfg.fpath.endswith((".usd", ".usda", ".usdc")) if is_usd: # TODO: currently not supporting multiple arenas for USD - env= self._env + env = self._env results = env.import_from_usd_file(cfg.fpath, return_object=True) print("USD import results:", results) @@ -1059,9 +1059,9 @@ def add_articulation( elif len(articulations_found) > 1: logger.log_error( f"Multiple articulations found in USD file {cfg.fpath}. " - ) + ) elif len(articulations_found) == 1: - obj_list.append(articulations_found[0]) + obj_list.append(articulations_found[0]) else: for env in env_list: art = env.load_urdf(cfg.fpath) @@ -1134,7 +1134,7 @@ def add_robot(self, cfg: RobotCfg) -> Robot | None: is_usd = cfg.fpath.endswith((".usd", ".usda", ".usdc")) if is_usd: # TODO: currently not supporting multiple arenas for USD - env= self._env + env = self._env results = env.import_from_usd_file(cfg.fpath, return_object=True) print("USD import results:", results) @@ -1149,9 +1149,9 @@ def add_robot(self, cfg: RobotCfg) -> Robot | None: elif len(articulations_found) > 1: logger.log_error( f"Multiple articulations found in USD file {cfg.fpath}. " - ) + ) elif len(articulations_found) == 1: - obj_list.append(articulations_found[0]) + obj_list.append(articulations_found[0]) else: for env in env_list: art = env.load_urdf(cfg.fpath) diff --git a/embodichain/lab/sim/utility/sim_utils.py b/embodichain/lab/sim/utility/sim_utils.py index b071da28..0f63b482 100644 --- a/embodichain/lab/sim/utility/sim_utils.py +++ b/embodichain/lab/sim/utility/sim_utils.py @@ -221,25 +221,25 @@ def load_mesh_objects_from_cfg( is_usd = fpath.endswith((".usd", ".usda", ".usdc")) if is_usd: - # TODO: currently not supporting multiple arenas for USD - _env:dexsim.environment.Env = dexsim.default_world().get_env() - results = _env.import_from_usd_file(fpath,return_object=True) + # TODO: currently not supporting multiple arenas for USD + _env: dexsim.environment.Env = dexsim.default_world().get_env() + results = _env.import_from_usd_file(fpath, return_object=True) print(f"import usd result: {results}") - rigidbodys_found=[] - for key,value in results.items(): + rigidbodys_found = [] + for key, value in results.items(): if isinstance(value, dexsim.cuda.pybind.models.MeshObject): rigidbodys_found.append(value) - if len(rigidbodys_found)==0: + if len(rigidbodys_found) == 0: logger.log_error(f"No rigid body found in USD file: {fpath}") - elif len(rigidbodys_found)>1: + elif len(rigidbodys_found) > 1: logger.log_error(f"Multiple rigid bodies found in USD file: {fpath}.") - elif len(rigidbodys_found)==1: + elif len(rigidbodys_found) == 1: obj_list.append(rigidbodys_found[0]) return obj_list else: # non-usd file does not support this option, will be ignored if set. - cfg.use_usd_properties=False + cfg.use_usd_properties = False for i, env in enumerate(env_list): if max_convex_hull_num > 1: diff --git a/scripts/tutorials/sim/import_usd.py b/scripts/tutorials/sim/import_usd.py index a2de891c..49e59833 100644 --- a/scripts/tutorials/sim/import_usd.py +++ b/scripts/tutorials/sim/import_usd.py @@ -25,7 +25,12 @@ from embodichain.lab.sim import SimulationManager, SimulationManagerCfg from embodichain.lab.sim.cfg import RigidBodyAttributesCfg from embodichain.lab.sim.shapes import CubeCfg, MeshCfg -from embodichain.lab.sim.objects import RigidObject, RigidObjectCfg,ArticulationCfg,Articulation +from embodichain.lab.sim.objects import ( + RigidObject, + RigidObjectCfg, + ArticulationCfg, + Articulation, +) from dexsim.utility.path import get_resources_data_path @@ -71,7 +76,6 @@ def main(): if not args.headless: sim.open_window() - cube: RigidObject = sim.add_rigid_object( cfg=RigidObjectCfg( uid="cube", @@ -87,28 +91,28 @@ def main(): ) ) - usdpath="/home/xiemh/model/004_sugar_box/004_sugar_box_xmh.usda" + usdpath = "/home/xiemh/model/004_sugar_box/004_sugar_box_xmh.usda" sugar_box: RigidObject = sim.add_rigid_object( cfg=RigidObjectCfg( uid="sugar_box", shape=MeshCfg(fpath=usdpath), - body_type="dynamic", + body_type="dynamic", init_pos=[0.2, 0.2, 1.0], use_usd_properties=True, ) ) # Add objects to the scene - h1 :Articulation= sim.add_articulation( - cfg=ArticulationCfg( - uid="h1", - # fpath="/home/xiemh/model/Collected_ur10/ur10.usd", - fpath="/home/xiemh/model/Collected_h1/h1.usda", - build_pk_chain=False, - init_pos=[-0.2, -0.2, 1.0], - use_usd_properties=False, - ) + h1: Articulation = sim.add_articulation( + cfg=ArticulationCfg( + uid="h1", + # fpath="/home/xiemh/model/Collected_ur10/ur10.usd", + fpath="/home/xiemh/model/Collected_h1/h1.usda", + build_pk_chain=False, + init_pos=[-0.2, -0.2, 1.0], + use_usd_properties=False, ) + ) print("[INFO]: Scene setup complete!") print("[INFO]: Press Ctrl+C to stop the simulation") From ed8c68ea84eeadf7ae0209556b1c6a83dd2e80e8 Mon Sep 17 00:00:00 2001 From: xiemenghong Date: Mon, 2 Mar 2026 18:49:38 +0800 Subject: [PATCH 03/14] use huggingface assets --- embodichain/data/assets/obj_assets.py | 11 +++++++++++ embodichain/data/assets/robot_assets.py | 26 +++++++++++++++++++++++++ scripts/tutorials/sim/import_usd.py | 18 +++++++++-------- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/embodichain/data/assets/obj_assets.py b/embodichain/data/assets/obj_assets.py index 75ccf3d9..442cbd46 100644 --- a/embodichain/data/assets/obj_assets.py +++ b/embodichain/data/assets/obj_assets.py @@ -215,3 +215,14 @@ def __init__(self, data_root: str = None): path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) + +class SugarBox(EmbodiChainDataset): + def __init__(self, data_root: str = None): + data_descriptor = o3d.data.DataDescriptor( + os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, obj_assets, "sugar_box_usd.zip"), + "a1bc5075512cedecd08af4f9c3e8f636", + ) + prefix = "SugarBox" + path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root + + super().__init__(prefix, data_descriptor, path) diff --git a/embodichain/data/assets/robot_assets.py b/embodichain/data/assets/robot_assets.py index c979afbc..0fc9cb58 100644 --- a/embodichain/data/assets/robot_assets.py +++ b/embodichain/data/assets/robot_assets.py @@ -122,6 +122,32 @@ def __init__(self, data_root: str = None): path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) +class UnitreeH1Usd(EmbodiChainDataset): + """Dataset class for the Unitree H1 robot USD version. + + Directory structure: + UnitreeH1Usd/H1_usd + h1.usd + h1.usda + + Example usage: + >>> from embodichain.data.robot_dataset import UnitreeH1Usd + >>> dataset = UnitreeH1Usd() + or + >>> from embodichain.data import get_data_path + >>> print(get_data_path("UnitreeH1Usd/H1_usd/h1.usd")) + >>> print(get_data_path("UnitreeH1Usd/H1_usd/h1.usda")) + """ + + def __init__(self, data_root: str = None): + data_descriptor = o3d.data.DataDescriptor( + os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, robot_assets, "H1_usd.zip"), + "9fc19f8c8b4a49398ec661e6ea9877ee", + ) + prefix = "UnitreeH1Usd" + path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root + + super().__init__(prefix, data_descriptor, path) class ABB(EmbodiChainDataset): diff --git a/scripts/tutorials/sim/import_usd.py b/scripts/tutorials/sim/import_usd.py index 49e59833..3e1d5a3b 100644 --- a/scripts/tutorials/sim/import_usd.py +++ b/scripts/tutorials/sim/import_usd.py @@ -15,8 +15,9 @@ # ---------------------------------------------------------------------------- """ -This script demonstrates how to create a simulation scene using SimulationManager. -It shows the basic setup of simulation context, adding objects, and sensors. +This script demonstrates how to import USD files into the scene. +Currently, it supports importing USD files as rigid objects or articulations. +Multiple arenas are not supported when importing USD files. """ import argparse @@ -31,8 +32,7 @@ ArticulationCfg, Articulation, ) -from dexsim.utility.path import get_resources_data_path - +from embodichain.data import get_data_path def main(): """Main function to create and run the simulation scene.""" @@ -91,11 +91,12 @@ def main(): ) ) - usdpath = "/home/xiemh/model/004_sugar_box/004_sugar_box_xmh.usda" + sugar_box_path = get_data_path("SugarBox/sugar_box_usd/sugar_box.usda") + print(f"Loading USD file from: {sugar_box_path}") sugar_box: RigidObject = sim.add_rigid_object( cfg=RigidObjectCfg( uid="sugar_box", - shape=MeshCfg(fpath=usdpath), + shape=MeshCfg(fpath=sugar_box_path), body_type="dynamic", init_pos=[0.2, 0.2, 1.0], use_usd_properties=True, @@ -103,11 +104,12 @@ def main(): ) # Add objects to the scene + h1_path= get_data_path("UnitreeH1Usd/H1_usd/h1.usd") + print(f"Loading USD file from: {h1_path}") h1: Articulation = sim.add_articulation( cfg=ArticulationCfg( uid="h1", - # fpath="/home/xiemh/model/Collected_ur10/ur10.usd", - fpath="/home/xiemh/model/Collected_h1/h1.usda", + fpath=h1_path, build_pk_chain=False, init_pos=[-0.2, -0.2, 1.0], use_usd_properties=False, From f7792f9173a42caa65eda3d5912bea8a9741099d Mon Sep 17 00:00:00 2001 From: xiemenghong Date: Mon, 2 Mar 2026 18:49:38 +0800 Subject: [PATCH 04/14] update --- embodichain/lab/sim/objects/articulation.py | 12 ++++++++---- embodichain/lab/sim/objects/rigid_object.py | 10 +++++++++- embodichain/lab/sim/sim_manager.py | 2 ++ embodichain/lab/sim/utility/sim_utils.py | 2 ++ 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/embodichain/lab/sim/objects/articulation.py b/embodichain/lab/sim/objects/articulation.py index dfd446f7..30c334c2 100644 --- a/embodichain/lab/sim/objects/articulation.py +++ b/embodichain/lab/sim/objects/articulation.py @@ -582,10 +582,14 @@ def __init__( if self.cfg.init_qpos is None: self.cfg.init_qpos = torch.zeros(self.dof, dtype=torch.float32) - if not cfg.use_usd_properties: - # Set articulation configuration in DexSim - set_dexsim_articulation_cfg(entities, self.cfg) - + # Determine if we should use USD properties or cfg properties. + is_usd_file = cfg.fpath.endswith((".usd", ".usda", ".usdc")) + use_cfg_properties = not (cfg.use_usd_properties and is_usd_file) + + # Set articulation configuration in DexSim + set_dexsim_articulation_cfg(entities, self.cfg) + + if use_cfg_properties: # Init joint drive parameters. num_entities = len(entities) dof = self._data.dof diff --git a/embodichain/lab/sim/objects/rigid_object.py b/embodichain/lab/sim/objects/rigid_object.py index 46a91b42..230e9063 100644 --- a/embodichain/lab/sim/objects/rigid_object.py +++ b/embodichain/lab/sim/objects/rigid_object.py @@ -211,7 +211,15 @@ def __init__( self._visual_material: List[VisualMaterialInst] = [None] * len(entities) self.is_shared_visual_material = False - if not cfg.use_usd_properties: + # Determine if we should use USD properties or cfg properties. + from embodichain.lab.sim.shapes import MeshCfg + is_usd_file = ( + isinstance(cfg.shape, MeshCfg) + and cfg.shape.fpath.endswith((".usd", ".usda", ".usdc")) + ) + use_cfg_properties = not (cfg.use_usd_properties and is_usd_file) + + if use_cfg_properties: for entity in entities: entity.set_body_scale(*cfg.body_scale) entity.set_physical_attr(cfg.attrs.attr()) diff --git a/embodichain/lab/sim/sim_manager.py b/embodichain/lab/sim/sim_manager.py index 6f7c89fd..83202b43 100644 --- a/embodichain/lab/sim/sim_manager.py +++ b/embodichain/lab/sim/sim_manager.py @@ -1044,6 +1044,8 @@ def add_articulation( is_usd = cfg.fpath.endswith((".usd", ".usda", ".usdc")) if is_usd: # TODO: currently not supporting multiple arenas for USD + if len(env_list) > 1: + logger.log_error(f"Currently not supporting multiple arenas for USD.") env = self._env results = env.import_from_usd_file(cfg.fpath, return_object=True) print("USD import results:", results) diff --git a/embodichain/lab/sim/utility/sim_utils.py b/embodichain/lab/sim/utility/sim_utils.py index 0f63b482..fdec76a7 100644 --- a/embodichain/lab/sim/utility/sim_utils.py +++ b/embodichain/lab/sim/utility/sim_utils.py @@ -222,6 +222,8 @@ def load_mesh_objects_from_cfg( is_usd = fpath.endswith((".usd", ".usda", ".usdc")) if is_usd: # TODO: currently not supporting multiple arenas for USD + if len(env_list) > 1: + logger.log_error(f"Currently not supporting multiple arenas for USD.") _env: dexsim.environment.Env = dexsim.default_world().get_env() results = _env.import_from_usd_file(fpath, return_object=True) print(f"import usd result: {results}") From 6ab46764842dec0a4dd90a6abba0bf1a0451208e Mon Sep 17 00:00:00 2001 From: xiemenghong Date: Mon, 2 Mar 2026 18:49:38 +0800 Subject: [PATCH 05/14] style --- embodichain/data/assets/obj_assets.py | 1 + embodichain/data/assets/robot_assets.py | 2 ++ embodichain/lab/sim/objects/articulation.py | 2 +- embodichain/lab/sim/objects/rigid_object.py | 6 +++--- embodichain/lab/sim/utility/sim_utils.py | 4 ++-- scripts/tutorials/sim/import_usd.py | 3 ++- 6 files changed, 11 insertions(+), 7 deletions(-) diff --git a/embodichain/data/assets/obj_assets.py b/embodichain/data/assets/obj_assets.py index 442cbd46..4790a26b 100644 --- a/embodichain/data/assets/obj_assets.py +++ b/embodichain/data/assets/obj_assets.py @@ -216,6 +216,7 @@ def __init__(self, data_root: str = None): super().__init__(prefix, data_descriptor, path) + class SugarBox(EmbodiChainDataset): def __init__(self, data_root: str = None): data_descriptor = o3d.data.DataDescriptor( diff --git a/embodichain/data/assets/robot_assets.py b/embodichain/data/assets/robot_assets.py index 0fc9cb58..a817ffa5 100644 --- a/embodichain/data/assets/robot_assets.py +++ b/embodichain/data/assets/robot_assets.py @@ -122,6 +122,8 @@ def __init__(self, data_root: str = None): path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) + + class UnitreeH1Usd(EmbodiChainDataset): """Dataset class for the Unitree H1 robot USD version. diff --git a/embodichain/lab/sim/objects/articulation.py b/embodichain/lab/sim/objects/articulation.py index 30c334c2..e6ded056 100644 --- a/embodichain/lab/sim/objects/articulation.py +++ b/embodichain/lab/sim/objects/articulation.py @@ -588,7 +588,7 @@ def __init__( # Set articulation configuration in DexSim set_dexsim_articulation_cfg(entities, self.cfg) - + if use_cfg_properties: # Init joint drive parameters. num_entities = len(entities) diff --git a/embodichain/lab/sim/objects/rigid_object.py b/embodichain/lab/sim/objects/rigid_object.py index 230e9063..08eb2712 100644 --- a/embodichain/lab/sim/objects/rigid_object.py +++ b/embodichain/lab/sim/objects/rigid_object.py @@ -213,9 +213,9 @@ def __init__( # Determine if we should use USD properties or cfg properties. from embodichain.lab.sim.shapes import MeshCfg - is_usd_file = ( - isinstance(cfg.shape, MeshCfg) - and cfg.shape.fpath.endswith((".usd", ".usda", ".usdc")) + + is_usd_file = isinstance(cfg.shape, MeshCfg) and cfg.shape.fpath.endswith( + (".usd", ".usda", ".usdc") ) use_cfg_properties = not (cfg.use_usd_properties and is_usd_file) diff --git a/embodichain/lab/sim/utility/sim_utils.py b/embodichain/lab/sim/utility/sim_utils.py index fdec76a7..193ba607 100644 --- a/embodichain/lab/sim/utility/sim_utils.py +++ b/embodichain/lab/sim/utility/sim_utils.py @@ -226,11 +226,11 @@ def load_mesh_objects_from_cfg( logger.log_error(f"Currently not supporting multiple arenas for USD.") _env: dexsim.environment.Env = dexsim.default_world().get_env() results = _env.import_from_usd_file(fpath, return_object=True) - print(f"import usd result: {results}") + # print(f"import usd result: {results}") rigidbodys_found = [] for key, value in results.items(): - if isinstance(value, dexsim.cuda.pybind.models.MeshObject): + if isinstance(value, MeshObject): rigidbodys_found.append(value) if len(rigidbodys_found) == 0: logger.log_error(f"No rigid body found in USD file: {fpath}") diff --git a/scripts/tutorials/sim/import_usd.py b/scripts/tutorials/sim/import_usd.py index 3e1d5a3b..970313ea 100644 --- a/scripts/tutorials/sim/import_usd.py +++ b/scripts/tutorials/sim/import_usd.py @@ -34,6 +34,7 @@ ) from embodichain.data import get_data_path + def main(): """Main function to create and run the simulation scene.""" @@ -104,7 +105,7 @@ def main(): ) # Add objects to the scene - h1_path= get_data_path("UnitreeH1Usd/H1_usd/h1.usd") + h1_path = get_data_path("UnitreeH1Usd/H1_usd/h1.usd") print(f"Loading USD file from: {h1_path}") h1: Articulation = sim.add_articulation( cfg=ArticulationCfg( From feba885938d612a0b24bf651d90bc6e9b1ed5167 Mon Sep 17 00:00:00 2001 From: xiemenghong Date: Mon, 2 Mar 2026 18:49:38 +0800 Subject: [PATCH 06/14] update --- embodichain/lab/sim/sim_manager.py | 12 ++++++------ embodichain/lab/sim/utility/sim_utils.py | 2 +- scripts/tutorials/sim/import_usd.py | 8 +------- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/embodichain/lab/sim/sim_manager.py b/embodichain/lab/sim/sim_manager.py index 83202b43..6ccd13cf 100644 --- a/embodichain/lab/sim/sim_manager.py +++ b/embodichain/lab/sim/sim_manager.py @@ -1043,12 +1043,12 @@ def add_articulation( is_usd = cfg.fpath.endswith((".usd", ".usda", ".usdc")) if is_usd: - # TODO: currently not supporting multiple arenas for USD + # TODO: Currently add checking for num_envs when file is USD. After we support spawn via cloning, we can remove this. if len(env_list) > 1: logger.log_error(f"Currently not supporting multiple arenas for USD.") env = self._env results = env.import_from_usd_file(cfg.fpath, return_object=True) - print("USD import results:", results) + # print("USD import results:", results) articulations_found = [] for key, value in results.items(): @@ -1057,7 +1057,6 @@ def add_articulation( if len(articulations_found) == 0: logger.log_error(f"No articulation found in USD file {cfg.fpath}.") - return None elif len(articulations_found) > 1: logger.log_error( f"Multiple articulations found in USD file {cfg.fpath}. " @@ -1135,10 +1134,12 @@ def add_robot(self, cfg: RobotCfg) -> Robot | None: is_usd = cfg.fpath.endswith((".usd", ".usda", ".usdc")) if is_usd: - # TODO: currently not supporting multiple arenas for USD + # TODO: Currently add checking for num_envs when file is USD. After we support spawn via cloning, we can remove this. + if len(env_list) > 1: + logger.log_error(f"Currently not supporting multiple arenas for USD.") env = self._env results = env.import_from_usd_file(cfg.fpath, return_object=True) - print("USD import results:", results) + # print("USD import results:", results) articulations_found = [] for key, value in results.items(): @@ -1147,7 +1148,6 @@ def add_robot(self, cfg: RobotCfg) -> Robot | None: if len(articulations_found) == 0: logger.log_error(f"No articulation found in USD file {cfg.fpath}.") - return None elif len(articulations_found) > 1: logger.log_error( f"Multiple articulations found in USD file {cfg.fpath}. " diff --git a/embodichain/lab/sim/utility/sim_utils.py b/embodichain/lab/sim/utility/sim_utils.py index 193ba607..15e16edf 100644 --- a/embodichain/lab/sim/utility/sim_utils.py +++ b/embodichain/lab/sim/utility/sim_utils.py @@ -221,7 +221,7 @@ def load_mesh_objects_from_cfg( is_usd = fpath.endswith((".usd", ".usda", ".usdc")) if is_usd: - # TODO: currently not supporting multiple arenas for USD + # TODO: Currently add checking for num_envs when file is USD. After we support spawn via cloning, we can remove this. if len(env_list) > 1: logger.log_error(f"Currently not supporting multiple arenas for USD.") _env: dexsim.environment.Env = dexsim.default_world().get_env() diff --git a/scripts/tutorials/sim/import_usd.py b/scripts/tutorials/sim/import_usd.py index 970313ea..6793c83b 100644 --- a/scripts/tutorials/sim/import_usd.py +++ b/scripts/tutorials/sim/import_usd.py @@ -51,12 +51,6 @@ def main(): parser.add_argument( "--device", type=str, default="cpu", help="Simulation device (cuda or cpu)" ) - parser.add_argument( - "--enable_rt", - action="store_true", - default=True, - help="Enable ray tracing for better visuals", - ) args = parser.parse_args() # Configure the simulation @@ -66,7 +60,7 @@ def main(): headless=True, physics_dt=1.0 / 100.0, # Physics timestep (100 Hz) sim_device=args.device, - enable_rt=args.enable_rt, # Enable ray tracing for better visuals + enable_rt=True, # Enable ray tracing for better visuals num_envs=1, arena_space=3.0, ) From fac439bc6697c9a9fa22aaeda61ad9f4cdc941b0 Mon Sep 17 00:00:00 2001 From: xiemenghong Date: Mon, 2 Mar 2026 18:49:38 +0800 Subject: [PATCH 07/14] update --- embodichain/lab/sim/objects/articulation.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/embodichain/lab/sim/objects/articulation.py b/embodichain/lab/sim/objects/articulation.py index e6ded056..ac75d34e 100644 --- a/embodichain/lab/sim/objects/articulation.py +++ b/embodichain/lab/sim/objects/articulation.py @@ -586,11 +586,10 @@ def __init__( is_usd_file = cfg.fpath.endswith((".usd", ".usda", ".usdc")) use_cfg_properties = not (cfg.use_usd_properties and is_usd_file) - # Set articulation configuration in DexSim - set_dexsim_articulation_cfg(entities, self.cfg) - if use_cfg_properties: - # Init joint drive parameters. + # Set articulation configuration in DexSim + set_dexsim_articulation_cfg(entities, self.cfg) + num_entities = len(entities) dof = self._data.dof default_cfg = JointDrivePropertiesCfg() @@ -625,6 +624,13 @@ def __init__( device=device, ) self._set_default_joint_drive() + else: + # Read current properties from USD-loaded entities + self.default_joint_stiffness = self._data.joint_stiffness.clone() + self.default_joint_damping = self._data.joint_damping.clone() + self.default_joint_friction = self._data.joint_friction.clone() + self.default_joint_max_effort = self._data.qf_limits.clone() + self.default_joint_max_velocity = self._data.qvel_limits.clone() self.pk_chain = None if self.cfg.build_pk_chain: From b172eb93ed1f534ea8c472044f1646d5f18089cd Mon Sep 17 00:00:00 2001 From: xiemenghong Date: Mon, 2 Mar 2026 18:49:38 +0800 Subject: [PATCH 08/14] add usd test --- tests/sim/objects/test_usd.py | 91 +++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 tests/sim/objects/test_usd.py diff --git a/tests/sim/objects/test_usd.py b/tests/sim/objects/test_usd.py new file mode 100644 index 00000000..101afeda --- /dev/null +++ b/tests/sim/objects/test_usd.py @@ -0,0 +1,91 @@ +# ---------------------------------------------------------------------------- +# Copyright (c) 2021-2026 DexForce Technology Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ---------------------------------------------------------------------------- + +import pytest + +from embodichain.lab.sim import ( + SimulationManager, + SimulationManagerCfg, +) +from embodichain.lab.sim.objects import Articulation, RigidObject +from embodichain.lab.sim.cfg import ArticulationCfg, RigidObjectCfg +from embodichain.lab.sim.shapes import MeshCfg +from embodichain.data import get_data_path + +NUM_ARENAS = 1 + + +class BaseUsdTest: + """Shared test logic for CPU and CUDA.""" + + def setup_simulation(self, sim_device): + config = SimulationManagerCfg( + headless=False, sim_device=sim_device, num_envs=NUM_ARENAS, enable_rt=True + ) + self.sim = SimulationManager(config) + + if sim_device == "cuda" and getattr(self.sim, "is_use_gpu_physics", False): + self.sim.init_gpu_physics() + + def test_import_rigid(self): + sugar_box_path = get_data_path("SugarBox/sugar_box_usd/sugar_box.usda") + sugar_box: RigidObject = self.sim.add_rigid_object( + cfg=RigidObjectCfg( + uid="sugar_box", + shape=MeshCfg(fpath=sugar_box_path), + body_type="dynamic", + use_usd_properties=True, + init_pos=[0.0, 0.0, 0.1], + ) + ) + + def test_import_articulation(self): + h1_path = get_data_path("UnitreeH1Usd/H1_usd/h1.usd") + h1: Articulation = self.sim.add_articulation( + cfg=ArticulationCfg( + uid="h1", + fpath=h1_path, + build_pk_chain=False, + use_usd_properties=False, + init_pos=[0.0, 0.0, 1.2], + ) + ) + + def teardown_method(self): + """Clean up resources after each test method.""" + self.sim.destroy() + + +class TestUsdCPU(BaseUsdTest): + def setup_method(self): + self.setup_simulation("cpu") + + +@pytest.mark.skip(reason="Skipping CUDA tests temporarily") +class TestUsdCUDA(BaseUsdTest): + def setup_method(self): + self.setup_simulation("cuda") + + +if __name__ == "__main__": + test = TestUsdCPU() + test.setup_method() + test.test_import_rigid() + test.test_import_articulation() + + from IPython import embed + + embed() From 6aca2a47215dbe25bd787762f2d5c66dba81c60d Mon Sep 17 00:00:00 2001 From: xiemenghong Date: Mon, 2 Mar 2026 18:49:38 +0800 Subject: [PATCH 09/14] update --- embodichain/lab/sim/cfg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embodichain/lab/sim/cfg.py b/embodichain/lab/sim/cfg.py index 16bc4445..4ed0e4f9 100644 --- a/embodichain/lab/sim/cfg.py +++ b/embodichain/lab/sim/cfg.py @@ -550,8 +550,8 @@ class RigidObjectCfg(ObjectBaseCfg): """Whether to use physical properties from USD file instead of config. When True: Keep all physical properties (drive, physics attrs, etc.) from USD file. - When False (default): Override USD properties with config values (URDF behavior). - Only effective for USD files, ignored for URDF files. + When False (default): Override USD properties with config values. + Only effective for USD files. """ def to_dexsim_body_type(self) -> ActorType: From b755cdae7f1b323f2b309506b04f1393db3b4ed1 Mon Sep 17 00:00:00 2001 From: xiemenghong Date: Mon, 2 Mar 2026 18:49:38 +0800 Subject: [PATCH 10/14] update --- scripts/tutorials/sim/import_usd.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/tutorials/sim/import_usd.py b/scripts/tutorials/sim/import_usd.py index 6793c83b..59dfac62 100644 --- a/scripts/tutorials/sim/import_usd.py +++ b/scripts/tutorials/sim/import_usd.py @@ -67,9 +67,6 @@ def main(): # Create the simulation instance sim = SimulationManager(sim_cfg) - # Open window when the scene has been set up - if not args.headless: - sim.open_window() cube: RigidObject = sim.add_rigid_object( cfg=RigidObjectCfg( @@ -111,6 +108,10 @@ def main(): ) ) + # Open window when the scene has been set up + if not args.headless: + sim.open_window() + print("[INFO]: Scene setup complete!") print("[INFO]: Press Ctrl+C to stop the simulation") From 74cb0b4a4ce8915e11a09d31f65bf71a6f2f847e Mon Sep 17 00:00:00 2001 From: xiemenghong Date: Mon, 2 Mar 2026 18:49:38 +0800 Subject: [PATCH 11/14] add docs for usd --- docs/source/overview/sim/sim_articulation.md | 29 +++++++++++++++++++- docs/source/overview/sim/sim_rigid_object.md | 26 ++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/docs/source/overview/sim/sim_articulation.md b/docs/source/overview/sim/sim_articulation.md index 5e4c0669..f687d754 100644 --- a/docs/source/overview/sim/sim_articulation.md +++ b/docs/source/overview/sim/sim_articulation.md @@ -10,10 +10,11 @@ The {class}`~objects.Articulation` class represents the fundamental physics enti Articulations are configured using the {class}`~cfg.ArticulationCfg` dataclass. | Parameter | Type | Default | Description | | :--- | :--- | :--- | :--- | -| `fpath` | `str` | `None` | Path to the asset file (URDF/MJCF). | +| `fpath` | `str` | `None` | Path to the asset file (URDF/MJCF/USD). | | `init_pos` | `tuple` | `(0,0,0)` | Initial root position `(x, y, z)`. | | `init_rot` | `tuple` | `(0,0,0)` | Initial root rotation `(r, p, y)` in degrees. | | `fix_base` | `bool` | `True` | Whether to fix the base of the articulation. | +| `use_usd_properties` | `bool` | `False` | If True, use physical properties from USD file; if False, override with config values. Only effective for usd files. | | `init_qpos` | `List[float]` | `None` | Initial joint positions. | | `body_scale` | `List[float]` | `[1.0, 1.0, 1.0]` | Scaling factors for the articulation links. | | `disable_self_collisions` | `bool` | `True` | Whether to disable self-collisions. | @@ -61,6 +62,32 @@ articulation: Articulation = sim.add_articulation(cfg=art_cfg) # This performs a global reset of the simulation state sim.reset_objects_state() ``` + +### USD Import + +You can import USD files (`.usd`, `.usda`, `.usdc`) as articulations: + +```python +from embodichain.data import get_data_path + +# Import USD with properties from file +usd_art_cfg = ArticulationCfg( + fpath=get_data_path("path/to/robot.usd"), + init_pos=(0, 0, 0.5), + use_usd_properties=True # Keep USD drive/physics properties +) +usd_robot = sim.add_articulation(cfg=usd_art_cfg) + +# Or override USD properties with config (URDF behavior) +usd_art_cfg_override = ArticulationCfg( + fpath=get_data_path("path/to/robot.usd"), + init_pos=(0, 0, 0.5), + use_usd_properties=False, # Use config instead + drive_props=JointDrivePropertiesCfg(stiffness=5000, damping=500) +) +robot = sim.add_articulation(cfg=usd_art_cfg_override) +``` + ## Articulation Class State data is accessed via getter methods that return batched tensors. diff --git a/docs/source/overview/sim/sim_rigid_object.md b/docs/source/overview/sim/sim_rigid_object.md index 6d71465d..5bd4b417 100644 --- a/docs/source/overview/sim/sim_rigid_object.md +++ b/docs/source/overview/sim/sim_rigid_object.md @@ -16,6 +16,7 @@ Configured via the {class}`~cfg.RigidObjectCfg` class. | `attrs` | {class}`~cfg.RigidBodyAttributesCfg` | defaults in code | Physical attributes (mass, damping, friction, restitution, collision offsets, CCD, etc.). | | `init_pos` | `Sequence[float]` | `(0,0,0)` | Initial root position (x, y, z). | | `init_rot` | `Sequence[float]` | `(0,0,0)` (Euler degrees) | Initial root orientation (Euler angles in degrees) or provide `init_local_pose`. | +| `use_usd_properties` | `bool` | `False` | If True, use physical properties from USD file; if False, override with config values. Only effective for usd files. | | `uid` | `str` | `None` | Optional unique identifier for the object; manager will assign one if omitted. | ### Rigid Body Attributes ({class}`~cfg.RigidBodyAttributesCfg`) @@ -73,6 +74,31 @@ sim.update() > Note: `scripts/tutorials/sim/create_scene.py` provides a minimal working example of adding a rigid cube and running the simulation loop. +### USD Import + +You can import USD files (`.usd`, `.usda`, `.usdc`) as rigid objects: + +```python +from embodichain.data import get_data_path + +# Import USD with properties from file +usd_cfg = RigidObjectCfg( + shape=MeshCfg(fpath=get_data_path("path/to/object.usd")), + body_type="dynamic", + use_usd_properties=True # Keep USD properties +) +obj = sim.add_rigid_object(cfg=usd_cfg) + +# Or override USD properties with config +usd_cfg_override = RigidObjectCfg( + shape=MeshCfg(fpath=get_data_path("path/to/object.usd")), + body_type="dynamic", + use_usd_properties=False, # Use config instead + attrs=RigidBodyAttributesCfg(mass=2.0) +) +obj2 = sim.add_rigid_object(cfg=usd_cfg_override) +``` + ## Rigid Object Class — Common Methods & Attributes Rigid objects are observed and controlled via single poses and linear/angular velocities. Key APIs include: From 387aacfb4bd730a175e56dd240f899628c978e03 Mon Sep 17 00:00:00 2001 From: MahooX <72488485+MahooX@users.noreply.github.com> Date: Mon, 2 Mar 2026 18:49:38 +0800 Subject: [PATCH 12/14] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/sim/objects/test_usd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sim/objects/test_usd.py b/tests/sim/objects/test_usd.py index 101afeda..b050bc71 100644 --- a/tests/sim/objects/test_usd.py +++ b/tests/sim/objects/test_usd.py @@ -33,7 +33,7 @@ class BaseUsdTest: def setup_simulation(self, sim_device): config = SimulationManagerCfg( - headless=False, sim_device=sim_device, num_envs=NUM_ARENAS, enable_rt=True + headless=True, sim_device=sim_device, num_envs=NUM_ARENAS, enable_rt=False ) self.sim = SimulationManager(config) From 69e44e468643c972ff20de786a1781d9c4311608 Mon Sep 17 00:00:00 2001 From: xiemenghong Date: Tue, 3 Mar 2026 15:14:26 +0800 Subject: [PATCH 13/14] update --- docs/source/overview/sim/sim_articulation.md | 2 +- tests/sim/objects/test_usd.py | 107 ++++++++++++++++++- 2 files changed, 103 insertions(+), 6 deletions(-) diff --git a/docs/source/overview/sim/sim_articulation.md b/docs/source/overview/sim/sim_articulation.md index f687d754..f35bb64b 100644 --- a/docs/source/overview/sim/sim_articulation.md +++ b/docs/source/overview/sim/sim_articulation.md @@ -10,7 +10,7 @@ The {class}`~objects.Articulation` class represents the fundamental physics enti Articulations are configured using the {class}`~cfg.ArticulationCfg` dataclass. | Parameter | Type | Default | Description | | :--- | :--- | :--- | :--- | -| `fpath` | `str` | `None` | Path to the asset file (URDF/MJCF/USD). | +| `fpath` | `str` | `None` | Path to the asset file (URDF/USD). | | `init_pos` | `tuple` | `(0,0,0)` | Initial root position `(x, y, z)`. | | `init_rot` | `tuple` | `(0,0,0)` | Initial root rotation `(r, p, y)` in degrees. | | `fix_base` | `bool` | `True` | Whether to fix the base of the articulation. | diff --git a/tests/sim/objects/test_usd.py b/tests/sim/objects/test_usd.py index b050bc71..e36be7c7 100644 --- a/tests/sim/objects/test_usd.py +++ b/tests/sim/objects/test_usd.py @@ -15,13 +15,19 @@ # ---------------------------------------------------------------------------- import pytest +import torch from embodichain.lab.sim import ( SimulationManager, SimulationManagerCfg, ) from embodichain.lab.sim.objects import Articulation, RigidObject -from embodichain.lab.sim.cfg import ArticulationCfg, RigidObjectCfg +from embodichain.lab.sim.cfg import ( + ArticulationCfg, + RigidObjectCfg, + JointDrivePropertiesCfg, + RigidBodyAttributesCfg, +) from embodichain.lab.sim.shapes import MeshCfg from embodichain.data import get_data_path @@ -41,18 +47,32 @@ def setup_simulation(self, sim_device): self.sim.init_gpu_physics() def test_import_rigid(self): + default_attr = RigidBodyAttributesCfg() sugar_box_path = get_data_path("SugarBox/sugar_box_usd/sugar_box.usda") sugar_box: RigidObject = self.sim.add_rigid_object( cfg=RigidObjectCfg( uid="sugar_box", shape=MeshCfg(fpath=sugar_box_path), body_type="dynamic", - use_usd_properties=True, + use_usd_properties=False, init_pos=[0.0, 0.0, 0.1], + attrs=default_attr, ) ) + body0 = sugar_box._entities[0].get_physical_body() + print(sugar_box._entities[0].get_physical_attr()) + assert pytest.approx(body0.get_mass()) == default_attr.mass + assert pytest.approx(body0.get_linear_damping()) == default_attr.linear_damping + assert ( + pytest.approx(body0.get_angular_damping()) == default_attr.angular_damping + ) + assert body0.get_solver_iteration_counts() == ( + default_attr.min_position_iters, + default_attr.min_velocity_iters, + ) def test_import_articulation(self): + default_drive = JointDrivePropertiesCfg() h1_path = get_data_path("UnitreeH1Usd/H1_usd/h1.usd") h1: Articulation = self.sim.add_articulation( cfg=ArticulationCfg( @@ -61,9 +81,86 @@ def test_import_articulation(self): build_pk_chain=False, use_usd_properties=False, init_pos=[0.0, 0.0, 1.2], + drive_pros=default_drive, + ) + ) + + stiffness = h1.body_data.joint_stiffness + damping = h1.body_data.joint_damping + max_force = h1.body_data.qf_limits + print(f"All joint stiffness: {stiffness}") + print(f"All joint damping: {damping}") + print(f"All joint max force: {max_force}") + expected_stiffness = default_drive.stiffness + assert torch.allclose( + stiffness, torch.tensor(expected_stiffness, dtype=torch.float32) + ) + expectied_damping = default_drive.damping + assert torch.allclose( + damping, torch.tensor(expectied_damping, dtype=torch.float32) + ) + + def test_usd_properties(self): + """In this test, we set use_usd_properties=True to verify that the USD properties are correctly applied.""" + h1_path = get_data_path("UnitreeH1Usd/H1_usd/h1.usd") + h1: Articulation = self.sim.add_articulation( + cfg=ArticulationCfg( + uid="h1_beta", + fpath=h1_path, + build_pk_chain=False, + use_usd_properties=True, + init_pos=[0.0, 0.0, 1.2], ) ) + stiffness = h1.body_data.joint_stiffness + damping = h1.body_data.joint_damping + max_force = h1.body_data.qf_limits + print(f"All joint stiffness: {stiffness}") + print(f"All joint damping: {damping}") + print(f"All joint max force: {max_force}") + expected_stiffness = 10000000.0 + assert torch.allclose( + stiffness, torch.tensor(expected_stiffness, dtype=torch.float32) + ) + + joint_names = h1.joint_names + print(f"Joint names: {joint_names}") + + target_joint_name = "left_hip_yaw_joint" + if target_joint_name in joint_names: + joint_idx = joint_names.index(target_joint_name) + # check for the first instance + assert torch.isclose( + stiffness[0, joint_idx], torch.tensor(10000000.0, dtype=torch.float32) + ) + assert torch.isclose( + damping[0, joint_idx], torch.tensor(0.0, dtype=torch.float32) + ) + assert torch.isclose( + max_force[0, joint_idx], torch.tensor(200.0, dtype=torch.float32) + ) + + default_attr = RigidBodyAttributesCfg() + sugar_box_path = get_data_path("SugarBox/sugar_box_usd/sugar_box.usda") + sugar_box: RigidObject = self.sim.add_rigid_object( + cfg=RigidObjectCfg( + uid="sugar_box_beta", + shape=MeshCfg(fpath=sugar_box_path), + body_type="dynamic", + use_usd_properties=True, + init_pos=[0.0, 0.0, 0.1], + attrs=default_attr, + ) + ) + # body0=sugar_box._entities[0].get_physical_body() + # print(sugar_box._entities[0].get_physical_attr()) + # assert(body0.get_mass()==0.514) + # assert(body0.get_linear_damping()==0) + # assert(body0.get_angular_damping()==0.05) + # assert(body0.get_solver_iteration_counts()==(4, 1)) + # assert(body0.get_max_angular_velocity()==100) + def teardown_method(self): """Clean up resources after each test method.""" self.sim.destroy() @@ -85,7 +182,7 @@ def setup_method(self): test.setup_method() test.test_import_rigid() test.test_import_articulation() + test.test_usd_properties() - from IPython import embed - - embed() + # from IPython import embed + # embed() From b65eb0ebd789d2a0213600323da4c4182383cc8e Mon Sep 17 00:00:00 2001 From: xiemenghong Date: Tue, 3 Mar 2026 16:04:47 +0800 Subject: [PATCH 14/14] update test. fix rigidbody.reset() --- embodichain/lab/sim/objects/rigid_object.py | 2 +- tests/sim/objects/test_usd.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/embodichain/lab/sim/objects/rigid_object.py b/embodichain/lab/sim/objects/rigid_object.py index 08eb2712..4fec12b7 100644 --- a/embodichain/lab/sim/objects/rigid_object.py +++ b/embodichain/lab/sim/objects/rigid_object.py @@ -878,7 +878,7 @@ def set_visible(self, visible: bool = True) -> None: def reset(self, env_ids: Sequence[int] | None = None) -> None: local_env_ids = self._all_indices if env_ids is None else env_ids num_instances = len(local_env_ids) - self.set_attrs(self.cfg.attrs, env_ids=local_env_ids) + # self.set_attrs(self.cfg.attrs, env_ids=local_env_ids) pos = torch.as_tensor( self.cfg.init_pos, dtype=torch.float32, device=self.device diff --git a/tests/sim/objects/test_usd.py b/tests/sim/objects/test_usd.py index e36be7c7..06258c20 100644 --- a/tests/sim/objects/test_usd.py +++ b/tests/sim/objects/test_usd.py @@ -141,7 +141,6 @@ def test_usd_properties(self): max_force[0, joint_idx], torch.tensor(200.0, dtype=torch.float32) ) - default_attr = RigidBodyAttributesCfg() sugar_box_path = get_data_path("SugarBox/sugar_box_usd/sugar_box.usda") sugar_box: RigidObject = self.sim.add_rigid_object( cfg=RigidObjectCfg( @@ -150,12 +149,12 @@ def test_usd_properties(self): body_type="dynamic", use_usd_properties=True, init_pos=[0.0, 0.0, 0.1], - attrs=default_attr, ) ) - # body0=sugar_box._entities[0].get_physical_body() - # print(sugar_box._entities[0].get_physical_attr()) - # assert(body0.get_mass()==0.514) + body0 = sugar_box._entities[0].get_physical_body() + print(sugar_box._entities[0].get_physical_attr()) + assert pytest.approx(body0.get_mass(), 0.001) == 0.514 + # TODO: nvidia physx attrs in usd currently are not fully suported # assert(body0.get_linear_damping()==0) # assert(body0.get_angular_damping()==0.05) # assert(body0.get_solver_iteration_counts()==(4, 1))