Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion docs/source/overview/sim/sim_articulation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/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. |
Expand Down Expand Up @@ -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)
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The USD import doc example uses drive_props=..., but the actual config field is drive_pros (see ArticulationCfg.drive_pros in embodichain/lab/sim/cfg.py). As written, the example will raise a TypeError for an unexpected keyword argument. Update the docs to use the correct parameter name.

Suggested change
drive_props=JointDrivePropertiesCfg(stiffness=5000, damping=500)
drive_pros=JointDrivePropertiesCfg(stiffness=5000, damping=500)

Copilot uses AI. Check for mistakes.
)
robot = sim.add_articulation(cfg=usd_art_cfg_override)
Comment on lines +70 to +88
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This USD import example uses JointDrivePropertiesCfg but doesn’t import it, and it also assumes ArticulationCfg/sim are already in scope. Add the missing imports (and/or include a minimal complete snippet) so the documentation example works when copied.

Copilot uses AI. Check for mistakes.
```

## Articulation Class

State data is accessed via getter methods that return batched tensors.
Expand Down
26 changes: 26 additions & 0 deletions docs/source/overview/sim/sim_rigid_object.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down Expand Up @@ -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)
```
Comment on lines +81 to +100
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The USD import documentation snippet is missing required imports (RigidObjectCfg, MeshCfg, RigidBodyAttributesCfg) and relies on an existing sim variable, so it won’t run as written when copied. Include the necessary imports (or reference the earlier setup snippet) so the example is executable.

Copilot uses AI. Check for mistakes.

## Rigid Object Class — Common Methods & Attributes

Rigid objects are observed and controlled via single poses and linear/angular velocities. Key APIs include:
Expand Down
12 changes: 12 additions & 0 deletions embodichain/data/assets/obj_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,15 @@ 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)
28 changes: 28 additions & 0 deletions embodichain/data/assets/robot_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,34 @@ def __init__(self, data_root: str = None):
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):
"""Dataset class for the ABB robot.

Expand Down
16 changes: 16 additions & 0 deletions embodichain/lab/sim/cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Only effective for USD files.
"""

def to_dexsim_body_type(self) -> ActorType:
"""Convert the body type to dexsim ActorType."""
if self.body_type == "dynamic":
Expand Down Expand Up @@ -1018,6 +1026,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
class RobotCfg(ArticulationCfg):
Expand Down
84 changes: 49 additions & 35 deletions embodichain/lab/sim/objects/articulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -582,41 +582,55 @@ def __init__(
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()
# 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)

if use_cfg_properties:
# Set articulation configuration in DexSim
set_dexsim_articulation_cfg(entities, self.cfg)

num_entities = len(entities)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These parts must be set and should not be placed inside the if condition. self._set_default_joint_drive() could be placed here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjusted

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()
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:
Expand Down
17 changes: 13 additions & 4 deletions embodichain/lab/sim/objects/rigid_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,18 @@ 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())
# 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())

if device.type == "cuda":
self._world.update(0.001)
Expand Down Expand Up @@ -869,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)
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RigidObject.reset() no longer restores physical attributes because the call to set_attrs(self.cfg.attrs, ...) was commented out. This changes reset semantics for all rigid objects (including non-USD) and can lead to state drift across episodes. If the intent is only to preserve USD-loaded properties, gate this behavior on cfg.use_usd_properties/USD file type rather than disabling it unconditionally.

Suggested change
# self.set_attrs(self.cfg.attrs, env_ids=local_env_ids)
# Reset physical attributes unless we explicitly want to preserve USD-authored properties.
if not getattr(self.cfg, "use_usd_properties", False):
self.set_attrs(self.cfg.attrs, env_ids=local_env_ids)

Copilot uses AI. Check for mistakes.

pos = torch.as_tensor(
self.cfg.init_pos, dtype=torch.float32, device=self.device
Expand Down
58 changes: 52 additions & 6 deletions embodichain/lab/sim/sim_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -1041,9 +1041,32 @@ 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 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)

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}.")
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)

Expand Down Expand Up @@ -1109,9 +1132,32 @@ 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 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
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as add_articulation: the USD import branch uses self._env instead of the single target arena (env_list[0]). In num_envs>=1 this can spawn the robot outside the arena grid and produces behavior inconsistent with URDF loading.

Suggested change
env = self._env
# Use the same arena selection logic as for URDF loading
env = env_list[0]

Copilot uses AI. Check for mistakes.
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}.")
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)

Expand Down
24 changes: 24 additions & 0 deletions embodichain/lab/sim/utility/sim_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,30 @@ def load_mesh_objects_from_cfg(

compute_uv = cfg.shape.compute_uv

is_usd = fpath.endswith((".usd", ".usda", ".usdc"))
if is_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()
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, 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(
Expand Down
Loading