55
66from __future__ import annotations
77
8+ import warnings
89from collections .abc import Callable , Sequence
910from typing import Any
1011
1112import torch
1213import warp as wp
13- from newton import ModelBuilder , solvers
14+ from newton import GeoType , ModelBuilder , ShapeFlags , solvers
1415
15- from pxr import Usd
16+ from pxr import Usd , UsdPhysics
1617
1718from isaaclab .cloner .cloner_utils import replace_path_prefix
1819from isaaclab .sim .utils .newton_model_utils import replace_newton_builder_shape_colors
1920
21+ # USD ``physics:approximation`` token (lower case) -> Newton remeshing method.
22+ # Mirrors Newton's own importer mapping; ``none`` keeps the raw trimesh.
23+ _APPROXIMATION_TO_REMESHING_METHOD = {
24+ "convexdecomposition" : "coacd" ,
25+ "convexhull" : "convex_hull" ,
26+ "boundingsphere" : "bounding_sphere" ,
27+ "boundingcube" : "bounding_box" ,
28+ "meshsimplification" : "quadratic" ,
29+ }
30+
31+
32+ def _authored_collision_approximations (stage : Usd .Stage ) -> dict [str , str ]:
33+ """Prim path -> authored ``physics:approximation`` token (lower case).
34+
35+ SDF collision prims are excluded: the attribute has no meaning on a shape with
36+ ``NewtonSDFCollisionAPI`` applied (matching Newton's importer semantics).
37+ """
38+ authored : dict [str , str ] = {}
39+ for prim in stage .Traverse ():
40+ attr = UsdPhysics .MeshCollisionAPI (prim ).GetApproximationAttr ()
41+ if attr and attr .HasAuthoredValue () and "NewtonSDFCollisionAPI" not in prim .GetAppliedSchemas ():
42+ authored [prim .GetPath ().pathString ] = str (attr .Get ()).lower ()
43+ return authored
44+
45+
46+ def _apply_authored_approximations (builder : ModelBuilder , path_shape_map : dict , authored : dict [str , str ]) -> set [int ]:
47+ """Remesh authored collision shapes (visual shapes preserved); return their indices."""
48+ authored_shape_indices : set [int ] = set ()
49+ for path , mode in authored .items ():
50+ index = path_shape_map .get (path )
51+ if index is None :
52+ continue
53+ authored_shape_indices .add (index )
54+ method = _APPROXIMATION_TO_REMESHING_METHOD .get (mode )
55+ if method is not None :
56+ builder .approximate_meshes (method , shape_indices = [index ], keep_visual_shapes = True )
57+ return authored_shape_indices
58+
59+
60+ def _unauthored_collision_mesh_shapes (builder : ModelBuilder , authored_shape_indices : set [int ]) -> list [int ]:
61+ """Colliding mesh shapes not covered by an authored ``physics:approximation``."""
62+ return [
63+ index
64+ for index , shape_type in enumerate (builder .shape_type )
65+ if shape_type == GeoType .MESH
66+ and (builder .shape_flags [index ] & ShapeFlags .COLLIDE_SHAPES )
67+ and index not in authored_shape_indices
68+ ]
69+
2070
2171def build_source_builders (
2272 stage : Usd .Stage ,
@@ -27,27 +77,76 @@ def build_source_builders(
2777 ignore_paths : Sequence [str ] | None = None ,
2878 simplify_meshes : bool = True ,
2979) -> dict [str , ModelBuilder ]:
30- """Build one Newton builder for each clone source prim path."""
31- builders : dict [str , ModelBuilder ] = {}
32- for source in sources :
33- builder = create_builder ()
34- solvers .SolverMuJoCo .register_custom_attributes (builder )
35- solvers .SolverKamino .register_custom_attributes (builder )
36- builder .add_usd (
37- stage ,
38- root_path = source ,
39- load_visual_shapes = True ,
40- skip_mesh_approximation = True ,
41- schema_resolvers = schema_resolvers ,
42- ignore_paths = ignore_paths ,
80+ """Build one Newton builder for each clone source prim path.
81+
82+ USD-authored ``physics:approximation`` modes are honored (applied after import so
83+ visual shapes are preserved for visualization/rendering). Exception: when the
84+ honored modes leave multiple sources with differing shape-type sequences (e.g.
85+ heterogeneous asset variants), every mesh falls back to the uniform convex-hull
86+ treatment, because :class:`SolverMuJoCo` requires homogeneous worlds.
87+ """
88+ authored = _authored_collision_approximations (stage )
89+ builders = {
90+ source : _build_source_builder (
91+ stage , source , create_builder , schema_resolvers , ignore_paths , simplify_meshes , authored
4392 )
44- if simplify_meshes :
45- builder .approximate_meshes ("convex_hull" , keep_visual_shapes = True )
46- replace_newton_builder_shape_colors (builder , stage )
47- builders [source ] = builder
93+ for source in sources
94+ }
95+
96+ if authored and len (builders ) > 1 :
97+ shape_sequences = {tuple (int (t ) for t in b .shape_type ) for b in builders .values ()}
98+ if len (shape_sequences ) > 1 :
99+ warnings .warn (
100+ "Clone sources have differing collision shape sequences after honoring authored"
101+ " physics:approximation modes, which SolverMuJoCo's homogeneous-worlds requirement"
102+ " does not support. Falling back to uniform convex-hull approximation for all"
103+ " collision meshes." ,
104+ stacklevel = 2 ,
105+ )
106+ builders = {
107+ source : _build_source_builder (
108+ stage , source , create_builder , schema_resolvers , ignore_paths , simplify_meshes , {}
109+ )
110+ for source in sources
111+ }
48112 return builders
49113
50114
115+ def _build_source_builder (
116+ stage : Usd .Stage ,
117+ source : str ,
118+ create_builder : Callable [[], ModelBuilder ],
119+ schema_resolvers : Sequence [Any ],
120+ ignore_paths : Sequence [str ] | None ,
121+ simplify_meshes : bool ,
122+ authored : dict [str , str ],
123+ ) -> ModelBuilder :
124+ """Build one source builder; an empty ``authored`` map restores hull-everything."""
125+ builder = create_builder ()
126+ solvers .SolverMuJoCo .register_custom_attributes (builder )
127+ solvers .SolverKamino .register_custom_attributes (builder )
128+ import_result = builder .add_usd (
129+ stage ,
130+ root_path = source ,
131+ load_visual_shapes = True ,
132+ skip_mesh_approximation = True ,
133+ schema_resolvers = schema_resolvers ,
134+ ignore_paths = ignore_paths ,
135+ )
136+ if authored :
137+ authored_shape_indices = _apply_authored_approximations (builder , import_result ["path_shape_map" ], authored )
138+ if simplify_meshes :
139+ builder .approximate_meshes (
140+ "convex_hull" ,
141+ shape_indices = _unauthored_collision_mesh_shapes (builder , authored_shape_indices ),
142+ keep_visual_shapes = True ,
143+ )
144+ elif simplify_meshes :
145+ builder .approximate_meshes ("convex_hull" , keep_visual_shapes = True )
146+ replace_newton_builder_shape_colors (builder , stage )
147+ return builder
148+
149+
51150def replicate_builder_mapping (
52151 builder : ModelBuilder ,
53152 sources : Sequence [str ],
0 commit comments