Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
4e7862c
primary impl
alanray-tech Nov 13, 2025
e5d699f
seems successful
alanray-tech Nov 14, 2025
dd257bd
able to load g1
alanray-tech Nov 19, 2025
97d4c8c
[FEATURE] Add USD stage import functionality and related parsing util…
alanray-tech Nov 20, 2025
b85d994
temp
alanray-tech Nov 25, 2025
0a44794
Merge branch 'Genesis-Embodied-AI:main' into main
alanray-tech Nov 25, 2025
8c88436
Merge remote-tracking branch 'origin/main' into dev
alanray-tech Nov 25, 2025
f652a3a
new structure
alanray-tech Nov 30, 2025
8814e38
add free root joint support
alanray-tech Dec 2, 2025
2e47e34
fix free joint base link init transform
alanray-tech Dec 3, 2025
b0727c8
refactor code to better structure
alanray-tech Dec 3, 2025
4d31b02
Merge branch 'main' into dev
alanray-tech Dec 3, 2025
9fc5db0
add missing usd-parser-related import
alanray-tech Dec 3, 2025
74689d2
support usd optional import
alanray-tech Dec 3, 2025
7a44ac2
add missing morphs.Drone
alanray-tech Dec 4, 2025
303f84f
support assets-download/argparser in usd example, clean up deps
alanray-tech Dec 8, 2025
e51f975
add usd import stage example to unit test
alanray-tech Dec 8, 2025
1f4633e
refactor parser
alanray-tech Dec 8, 2025
cabdd49
fix uv_name missing
alanray-tech Dec 8, 2025
7b0c544
fix rotation scaling extract
alanray-tech Dec 9, 2025
0b23d9a
add usd driver api support
alanray-tech Dec 10, 2025
0f5d298
update doc
alanray-tech Dec 10, 2025
606752d
update workflow, install usd for usd unit test
alanray-tech Dec 10, 2025
83675f1
add a simple animator, add default value init for dofs_frictionloss/d…
alanray-tech Dec 10, 2025
54443f0
add morph option
YilingQiao Dec 10, 2025
82b3533
Merge pull request #1 from YilingQiao/yiling/251210_usd_collision_vis…
alanray-tech Dec 11, 2025
7d64715
Merge remote-tracking branch 'pub/main' into dev
alanray-tech Dec 12, 2025
a3fee62
weird target behaviour, need fix
alanray-tech Dec 12, 2025
dbc08ca
Merge branch 'main' into dev
YilingQiao Dec 14, 2025
bb87147
set target
YilingQiao Dec 16, 2025
c7cfa53
update limit
YilingQiao Dec 17, 2025
8f02db1
add target to dofs info
YilingQiao Dec 23, 2025
886dfe6
Merge pull request #2 from YilingQiao/yiling/251216_change_target
alanray-tech Dec 23, 2025
7820f73
Merge remote-tracking branch 'pub/main' into dev
alanray-tech Dec 28, 2025
f9a037f
update pyproject.toml
alanray-tech Dec 29, 2025
e72dff4
merge origin/dev
alanray-tech Dec 29, 2025
28ba6ce
change damping
YilingQiao Dec 29, 2025
2290640
Merge pull request #3 from YilingQiao/yiling/251229_change_damping
alanray-tech Dec 29, 2025
9e24f49
Merge remote-tracking branch 'pub/main' into dev
alanray-tech Dec 30, 2025
0a8611d
fix rigid_solver_decomp missing entity_idx, which crash the rigid sim…
alanray-tech Dec 30, 2025
eba954a
Fixed the import path of .usda
alanray-tech Dec 30, 2025
a47bae5
fix usd_parser, move Entity type import into the function, so that a …
alanray-tech Dec 31, 2025
6a71764
try skip usd related test on ARM machine
alanray-tech Jan 3, 2026
45c5119
Merge branch 'main' into dev
alanray-tech Jan 4, 2026
fd4dda0
make usd import optional
alanray-tech Jan 4, 2026
e973715
Merge remote-tracking branch 'pub/main' into dev
alanray-tech Jan 5, 2026
e587af0
Merge branch 'main' into dev
alanray-tech Jan 6, 2026
ea45d00
Merge branch 'main' into dev
alanray-tech Jan 6, 2026
10a38a3
improve at api and code style level according to the review
alanray-tech Jan 8, 2026
feb0ebe
refactor parsing logic & clean up codes for better readability and pe…
alanray-tech Jan 9, 2026
3a82ffd
improve docstring
alanray-tech Jan 9, 2026
016909b
try fix CI
alanray-tech Jan 9, 2026
179be0e
fix drone test
alanray-tech Jan 9, 2026
9b0fbe2
unify the usd rigidbody and articulation parser to rigid entity parse…
alanray-tech Jan 10, 2026
c443129
remove compute_joint_axis_scaling_factor, because USD take this value…
alanray-tech Jan 10, 2026
dcea807
Merge branch 'main' into dev
alanray-tech Jan 10, 2026
0b54773
try fix NV EULA agreement input
alanray-tech Jan 11, 2026
e817bda
merge
alanray-tech Jan 11, 2026
5bab2c6
add some unit tests, not fully implemented
alanray-tech Jan 12, 2026
778b92b
add PureRigid/Revoluate/Prismatic/Spherical unit tests
alanray-tech Jan 13, 2026
d470b10
Adding OMNI_KIT_ACCEPT_EULA to the workflows that use USD; Improve ex…
alanray-tech Jan 13, 2026
c84b358
Merge branch 'dev' of https://github.com/alanray-tech/Genesis into dev
alanray-tech Jan 13, 2026
46a025c
use float32 compatible tol in test_usd
alanray-tech Jan 13, 2026
ff159d3
expose more options in USD(Morph), align them with MJCF, test passed
alanray-tech Jan 13, 2026
b111c67
clean up code, add better warning when attribute matching fails
alanray-tech Jan 14, 2026
f7bbd6a
Merge remote-tracking branch 'pub/main' into dev
alanray-tech Jan 14, 2026
86c5def
change warning about attribute missing to debug
alanray-tech Jan 14, 2026
131147c
include OMNI_KIT_ACCEPT_EULA and OMNI_KIT_ALLOW_ROOT in SLURM_ENV_VAR…
alanray-tech Jan 14, 2026
6ce56ec
support batched polar (torch and numpy), add filter function in polar…
alanray-tech Jan 14, 2026
38dd302
update tol
alanray-tech Jan 14, 2026
79a25ad
Merge remote-tracking branch 'pub/main' into dev
alanray-tech Jan 15, 2026
4c5f77c
Final cleanup.
duburcqa Jan 15, 2026
a4d64a7
Merge branch 'main' into dev
duburcqa Jan 15, 2026
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
33 changes: 33 additions & 0 deletions examples/usd/import_stage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import genesis as gs

gs.init(backend=gs.cpu)

scene = gs.Scene(
viewer_options=gs.options.ViewerOptions(
camera_pos=(3.5, 0.0, 2.5),
camera_lookat=(0.0, 0.0, 0.5),
camera_fov=40,
enable_interaction=True,
),
rigid_options=gs.options.RigidOptions(
# constraint_solver=gs.constraint_solver.Newton,
gravity=(0, 0, -9.8),
enable_collision=True,
enable_joint_limit=True,
max_collision_pairs=1000,
),
show_viewer=True,
)

AssetRoot = "D:/Assets"

# load a stage from USD file
entities = scene.add_stage(f"{AssetRoot}/Lightwheel_Kitchen001/Kitchen001/Kitchen001.usd")

# Build the scene
scene.build()

# Run the simulation for visualization
while scene.viewer.is_alive():
scene.step()
pass
157 changes: 100 additions & 57 deletions genesis/engine/entities/rigid_entity/rigid_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,20 @@ def _load_model(self):
self._joints = gs.List()
self._equalities = gs.List()

if isinstance(self._morph, gs.morphs.Mesh):
self._load_mesh(self._morph, self._surface)
elif isinstance(self._morph, (gs.morphs.MJCF, gs.morphs.URDF, gs.morphs.Drone)):
if isinstance(self._morph, (gs.morphs.Mesh, gs.morphs.USDRigidBody)):
if isinstance(self._morph, gs.morphs.Mesh):
self._load_mesh(self._morph, self._surface)
else:
self._load_usd_rigid_body(self._morph, self._surface)
elif isinstance(
self._morph,
(
gs.morphs.MJCF,
gs.morphs.URDF,
gs.morphs.Drone,
gs.morphs.USDArticulation,
),
):
self._load_scene(self._morph, self._surface)
elif isinstance(self._morph, gs.morphs.Primitive):
self._load_primitive(self._morph, self._surface)
Expand Down Expand Up @@ -322,6 +333,24 @@ def _load_mesh(self, morph, surface):
surface=surface,
)

def _load_usd_rigid_body(self, morph, surface):
"""
Load a USD rigid body, similar to _load_mesh but parsing from USD file.
"""
from genesis.utils.usd import parse_usd_rigid_body

# Parse USD rigid body to get l_info, j_infos, and g_infos
l_info, j_infos, g_infos = parse_usd_rigid_body(morph, surface)

# Create link and joint using _add_by_info
link, (joint,) = self._add_by_info(
l_info=l_info,
j_infos=j_infos,
g_infos=g_infos,
morph=morph,
surface=surface,
)

def _load_terrain(self, morph, surface):
vmesh, mesh, self.terrain_hf = tu.parse_terrain(morph, surface)
self.terrain_scale = np.array((morph.horizontal_scale, morph.vertical_scale), dtype=gs.np_float)
Expand Down Expand Up @@ -372,60 +401,54 @@ def _load_terrain(self, morph, surface):
surface=surface,
)

def _load_scene(self, morph, surface):
# Mujoco's unified MJCF+URDF parser is not good enough for now to be used for loading both MJCF and URDF files.
# First, it would happen when loading visual meshes having supported format (i.e. Collada files '.dae').
# Second, it does not take into account URDF 'mimic' joint constraints. However, it does a better job at
# initialized undetermined physics parameters.
if isinstance(morph, gs.morphs.MJCF):
# Mujoco's unified MJCF+URDF parser systematically for MJCF files
l_infos, links_j_infos, links_g_infos, eqs_info = mju.parse_xml(morph, surface)
else:
# Custom "legacy" URDF parser for loading geometries (visual and collision) and equality constraints.
# This is necessary because Mujoco cannot parse visual geometries (meshes) reliably for URDF.
l_infos, links_j_infos, links_g_infos, eqs_info = uu.parse_urdf(morph, surface)

# Mujoco's unified MJCF+URDF parser for only link, joints, and collision geometries properties.
morph_ = copy(morph)
morph_.visualization = False
try:
# Mujoco's unified MJCF+URDF parser for URDF files.
# Note that Mujoco URDF parser completely ignores equality constraints.
l_infos, links_j_infos_mj, links_g_infos_mj, _ = mju.parse_xml(morph_, surface)

# Mujoco is not parsing actuators properties
for j_info_gs in chain.from_iterable(links_j_infos):
for j_info_mj in chain.from_iterable(links_j_infos_mj):
if j_info_mj["name"] == j_info_gs["name"]:
for name in ("dofs_force_range", "dofs_armature", "dofs_kp", "dofs_kv"):
j_info_mj[name] = j_info_gs[name]
links_j_infos = links_j_infos_mj

# Take into account 'world' body if it was added automatically for our legacy URDF parser
if len(links_g_infos_mj) == len(links_g_infos) + 1:
assert not links_g_infos_mj[0]
links_g_infos.insert(0, [])
assert len(links_g_infos_mj) == len(links_g_infos)

# Update collision geometries, ignoring fake" visual geometries returned by Mujoco, (which is using
# collision as visual to avoid loading mesh files), and keeping the true visual geometries provided
# by our custom legacy URDF parser.
# Note that the Kinematic tree ordering is stable between Mujoco and Genesis (Hopefully!).
for link_g_infos, link_g_infos_mj in zip(links_g_infos, links_g_infos_mj):
# Remove collision geometries from our legacy URDF parser
for i_g, g_info in tuple(enumerate(link_g_infos))[::-1]:
is_col = g_info["contype"] or g_info["conaffinity"]
if is_col:
del link_g_infos[i_g]

# Add visual geometries from Mujoco's unified MJCF+URDF parser
for g_info in link_g_infos_mj:
is_col = g_info["contype"] or g_info["conaffinity"]
if is_col:
link_g_infos.append(g_info)
except (ValueError, AssertionError):
gs.logger.info("Falling back to legacy URDF parser. Default values of physics properties may be off.")

def _collect_urdf_articulation_info(self, morph, surface):
# Custom "legacy" URDF parser for loading geometries (visual and collision) and equality constraints.
# This is necessary because Mujoco cannot parse visual geometries (meshes) reliably for URDF.
l_infos, links_j_infos, links_g_infos, eqs_info = uu.parse_urdf(morph, surface)
# Mujoco's unified MJCF+URDF parser for only link, joints, and collision geometries properties.
morph_ = copy(morph)
morph_.visualization = False
try:
# Mujoco's unified MJCF+URDF parser for URDF files.
# Note that Mujoco URDF parser completely ignores equality constraints.
l_infos, links_j_infos_mj, links_g_infos_mj, _ = mju.parse_xml(morph_, surface)

# Mujoco is not parsing actuators properties
for j_info_gs in chain.from_iterable(links_j_infos):
for j_info_mj in chain.from_iterable(links_j_infos_mj):
if j_info_mj["name"] == j_info_gs["name"]:
for name in ("dofs_force_range", "dofs_armature", "dofs_kp", "dofs_kv"):
j_info_mj[name] = j_info_gs[name]
links_j_infos = links_j_infos_mj

# Take into account 'world' body if it was added automatically for our legacy URDF parser
if len(links_g_infos_mj) == len(links_g_infos) + 1:
assert not links_g_infos_mj[0]
links_g_infos.insert(0, [])
assert len(links_g_infos_mj) == len(links_g_infos)

# Update collision geometries, ignoring fake" visual geometries returned by Mujoco, (which is using
# collision as visual to avoid loading mesh files), and keeping the true visual geometries provided
# by our custom legacy URDF parser.
# Note that the Kinematic tree ordering is stable between Mujoco and Genesis (Hopefully!).
for link_g_infos, link_g_infos_mj in zip(links_g_infos, links_g_infos_mj):
# Remove collision geometries from our legacy URDF parser
for i_g, g_info in tuple(enumerate(link_g_infos))[::-1]:
is_col = g_info["contype"] or g_info["conaffinity"]
if is_col:
del link_g_infos[i_g]

# Add visual geometries from Mujoco's unified MJCF+URDF parser
for g_info in link_g_infos_mj:
is_col = g_info["contype"] or g_info["conaffinity"]
if is_col:
link_g_infos.append(g_info)
except (ValueError, AssertionError):
gs.logger.info("Falling back to legacy URDF parser. Default values of physics properties may be off.")

return l_infos, links_j_infos, links_g_infos, eqs_info

def _build_up_articulation(self, l_infos, links_j_infos, links_g_infos, eqs_info, morph, surface):
# Add free floating joint at root if necessary
if (
(isinstance(morph, gs.morphs.Drone) or (isinstance(morph, gs.morphs.URDF) and not morph.fixed))
Expand Down Expand Up @@ -591,6 +614,26 @@ def _load_scene(self, morph, surface):
sol_params=eq_info["sol_params"],
)

def _load_scene(self, morph, surface):
# Mujoco's unified MJCF+URDF parser is not good enough for now to be used for loading both MJCF and URDF files.
# First, it would happen when loading visual meshes having supported format (i.e. Collada files '.dae').
# Second, it does not take into account URDF 'mimic' joint constraints. However, it does a better job at
# initialized undetermined physics parameters.
if isinstance(morph, gs.morphs.MJCF):
# Mujoco's unified MJCF+URDF parser systematically for MJCF files
l_infos, links_j_infos, links_g_infos, eqs_info = mju.parse_xml(morph, surface)
elif isinstance(morph, (gs.morphs.URDF, gs.morphs.Drone)):
l_infos, links_j_infos, links_g_infos, eqs_info = self._collect_urdf_articulation_info(morph, surface)
elif isinstance(morph, gs.morphs.USDArticulation):
from genesis.utils.usd import parse_usd_articulation

l_infos, links_j_infos, links_g_infos, eqs_info = parse_usd_articulation(morph, surface)
else:
gs.raise_exception(f"Unsupported morph type: {type(morph)}")

if len(l_infos) > 0:
self._build_up_articulation(l_infos, links_j_infos, links_g_infos, eqs_info, morph, surface)

def _build(self):
for link in self._links:
link._build()
Expand Down
13 changes: 6 additions & 7 deletions genesis/engine/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,13 +452,12 @@ def add_entity(
return entity

@gs.assert_unbuilt
def link_entities(
self,
parent_entity: "Entity",
child_entity: "Entity",
parent_link_name="",
child_link_name="",
):
def add_stage(self, file: str):
from ..utils.usd import import_from_usd
return import_from_usd(self, file)

@gs.assert_unbuilt
def link_entities(self, parent_entity: "Entity", child_entity: "Entity", parent_link_name="", child_link_name=""):
"""
links two entities to act as single entity.

Expand Down
1 change: 0 additions & 1 deletion genesis/engine/solvers/rigid/constraint_solver_decomp.py
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,6 @@ def add_joint_limit_constraints(
pos_delta_min = rigid_global_info.qpos[i_q, i_b] - dofs_info.limit[I_d][0]
pos_delta_max = dofs_info.limit[I_d][1] - rigid_global_info.qpos[i_q, i_b]
pos_delta = min(pos_delta_min, pos_delta_max)

if pos_delta < 0:
jac = (pos_delta_min < pos_delta_max) * 2 - 1
jac_qvel = jac * dofs_state.vel[i_d, i_b]
Expand Down
60 changes: 59 additions & 1 deletion genesis/options/morphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"""

import os
from typing import Any, List, Optional, Sequence, Tuple, Union
from typing import Any, List, Optional, Sequence, Tuple, Union, ClassVar

import numpy as np

Expand Down Expand Up @@ -1283,3 +1283,61 @@ def default_params(self):
@property
def subterrain_params(self):
return self._subterrain_parameters

class USDArticulation(FileMorph):
"""
Morph loaded from a USD Prim inside a USD file.

This morph only supports Prim with APISchema:

- PhysicsRigidBodyAPI
- PhysicsArticulationRootAPI

Parameters
----------
file : str
The path to the file.

prim_path : str, optional
The path to the USD prim inside the USD file. If not specified, the default prim will be used.

parser_ctx : UsdParserContext, optional, for better performance when parsing large USD files
"""
file: str
prim_path: str = None
parser_ctx: Any = None
def __init__(self, **data):
super().__init__(**data)
for USD_FORMAT in USD_FORMATS:
if self.is_format(USD_FORMAT):
break
else:
gs.raise_exception(f"Expected one of `{USD_FORMATS}` extensions for USD file: {self.file}")


class USDRigidBody(FileMorph):
"""
Morph loaded from a USD Prim with RigidBodyAPI (but not ArticulationRootAPI) inside a USD file.

This morph supports single rigid bodies (not articulated structures).

Parameters
----------
file : str
The path to the file.

prim_path : str, optional
The path to the USD prim inside the USD file. If not specified, the first prim with RigidBodyAPI will be used.

parser_ctx : UsdParserContext, optional, for better performance when parsing large USD files
"""
file: str
prim_path: str = None
parser_ctx: Any = None
def __init__(self, **data):
super().__init__(**data)
for USD_FORMAT in USD_FORMATS:
if self.is_format(USD_FORMAT):
break
else:
gs.raise_exception(f"Expected one of `{USD_FORMATS}` extensions for USD file: {self.file}")
2 changes: 1 addition & 1 deletion genesis/utils/urdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def _order_links(l_infos, j_infos, links_g_infos=None):
def parse_urdf(morph, surface):
if isinstance(morph.file, (str, Path)):
path = os.path.join(get_assets_dir(), morph.file)
robot = urdfpy.URDF.load(path)
robot:urdfpy.URDF = urdfpy.URDF.load(path)
else:
robot = morph.file

Expand Down
16 changes: 16 additions & 0 deletions genesis/utils/usd/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
USD Parser System for Genesis

This package provides an extendable USD parser system with:
- UsdParserUtils: Utility functions for transforms, mesh conversion, etc.
- UsdParserContext: Context for tracking materials, articulations, and rigid bodies
- UsdRenderingMaterialParser: Parser for rendering materials
- UsdArticulationParser: Parser for articulations
- UsdRigidBodyParser: Parser for rigid bodies
- UsdParser: Main parser entrance with import_from_stage function
"""

from .usd_parser import UsdParser
from .usd_parser import import_from_usd
from .usd_articulation_parser import parse_usd_articulation
from .usd_rigid_body_parser import parse_usd_rigid_body
Loading
Loading