66import stl
77import tinyobjloader
88import usdex .core
9- from pxr import Tf , Usd , UsdGeom , Vt
9+ from pxr import Tf , Usd , UsdGeom , UsdShade , Vt
1010
1111from .conversion_collada import convert_collada
1212from .data import ConversionData , Tokens
@@ -95,6 +95,63 @@ def convert_stl(prim: Usd.Prim, input_path: pathlib.Path, data: ConversionData)
9595 return usd_mesh
9696
9797
98+ def _mesh_subsets_obj (
99+ mesh : UsdGeom .Mesh ,
100+ input_path : pathlib .Path ,
101+ reader : tinyobjloader .ObjReader ,
102+ obj_mesh : tinyobjloader .mesh_t ,
103+ data : ConversionData ,
104+ ):
105+ """
106+ Create subsets for the mesh if there are multiple materials.
107+ It also stores the names of the materials assigned to the mesh.
108+ Material binding is done on the Material layer, so no binding is done at this stage.
109+
110+ Args:
111+ mesh: The USD mesh.
112+ input_path: The path to the OBJ file.
113+ reader: The tinyobjloader reader.
114+ obj_mesh: The tinyobjloader mesh.
115+ data: The conversion data.
116+ """
117+ materials = reader .GetMaterials ()
118+
119+ # Get a list of face numbers for each material_id from obj_mesh.material_ids.
120+ # If a material does not exist, the material_id for the face will be set to -1.
121+ face_list_by_material = {}
122+ material_ids_array = np .array (obj_mesh .material_ids )
123+ unique_material_ids = np .unique (material_ids_array )
124+
125+ if len (unique_material_ids ) == 1 :
126+ # If there is only one material. In this case, no subset is created.
127+ material_id = int (unique_material_ids [0 ])
128+ material_name = materials [material_id ].name if material_id >= 0 else None
129+ if material_name :
130+ store_mesh_material_reference (input_path , mesh .GetPrim ().GetName (), [material_name ], data )
131+ return
132+
133+ for material_id in unique_material_ids :
134+ face_indices = np .where (material_ids_array == material_id )[0 ]
135+ face_list_by_material [int (material_id )] = Vt .IntArray .FromNumpy (face_indices )
136+
137+ stage = mesh .GetPrim ().GetStage ()
138+
139+ # If there are multiple materials. In this case, subsets are created.
140+ material_names = []
141+ for i , (material_id , face_indices ) in enumerate (face_list_by_material .items ()):
142+ material_name = materials [material_id ].name if material_id >= 0 else None
143+ material_names .append (material_name )
144+ subset_name = f"GeomSubset_{ (i + 1 ):03d} "
145+
146+ geom_subset = UsdGeom .Subset .Define (stage , mesh .GetPrim ().GetPath ().AppendChild (subset_name ))
147+ geom_subset .GetIndicesAttr ().Set (face_indices )
148+ geom_subset .GetElementTypeAttr ().Set (UsdGeom .Tokens .face )
149+ geom_subset .GetFamilyNameAttr ().Set (UsdShade .Tokens .materialBind )
150+
151+ # Store the material names for the mesh.
152+ store_mesh_material_reference (input_path , mesh .GetPrim ().GetName (), material_names , data )
153+
154+
98155def _convert_single_obj (
99156 prim : Usd .Prim ,
100157 input_path : pathlib .Path ,
@@ -116,17 +173,10 @@ def _convert_single_obj(
116173 """
117174 shapes = reader .GetShapes ()
118175 attrib = reader .GetAttrib ()
119- materials = reader .GetMaterials ()
120176
121177 # This method only deals with a single mesh, so it only considers the first mesh.
122178 obj_mesh = shapes [0 ].mesh
123179
124- # Material references are identified by the ID assigned to each face of the mesh.
125- # This will be a common id for each mesh, so we'll take the first one.
126- material_id = obj_mesh .material_ids [0 ]
127-
128- material_name = materials [material_id ].name if material_id >= 0 else None
129-
130180 vertices = attrib .vertices
131181 face_vertex_counts = obj_mesh .num_face_vertices
132182 face_vertex_indices = obj_mesh .vertex_indices ()
@@ -157,10 +207,9 @@ def _convert_single_obj(
157207 if not usd_mesh :
158208 Tf .Warn (f'Failed to convert mesh "{ prim .GetPath ()} " from { input_path } ' )
159209
160- # If the mesh has a material, stores the material name for the mesh .
210+ # Create subsets for the mesh if there are multiple materials .
161211 # Material binding is done on the Geometry layer, so no binding is done at this stage.
162- if material_name :
163- store_mesh_material_reference (input_path , usd_mesh .GetPrim ().GetName (), [material_name ], data )
212+ _mesh_subsets_obj (usd_mesh , input_path , reader , obj_mesh , data )
164213
165214 return usd_mesh
166215
@@ -183,7 +232,6 @@ def convert_obj(prim: Usd.Prim, input_path: pathlib.Path, data: ConversionData)
183232 return _convert_single_obj (prim , input_path , reader , data )
184233
185234 attrib = reader .GetAttrib ()
186- materials = reader .GetMaterials ()
187235
188236 names = []
189237 for shape in shapes :
@@ -193,9 +241,8 @@ def convert_obj(prim: Usd.Prim, input_path: pathlib.Path, data: ConversionData)
193241
194242 for shape , name , safe_name in zip (shapes , names , safe_names ):
195243 obj_mesh = shape .mesh
244+
196245 face_vertex_counts = Vt .IntArray (obj_mesh .num_face_vertices )
197- material_ids = obj_mesh .material_ids [0 ]
198- material = materials [material_ids ] if material_ids >= 0 else None
199246
200247 # Get indices directly as arrays
201248 vertex_indices_in_shape = np .array (obj_mesh .vertex_indices (), dtype = np .int32 )
@@ -250,10 +297,9 @@ def convert_obj(prim: Usd.Prim, input_path: pathlib.Path, data: ConversionData)
250297 Tf .Warn (f'Failed to convert mesh "{ prim .GetPath ()} " from { input_path } ' )
251298 return None
252299
253- # If the mesh has a material, stores the material name for the mesh .
300+ # Create subsets for the mesh if there are multiple materials .
254301 # Material binding is done on the Geometry layer, so no binding is done at this stage.
255- if material and material .name :
256- store_mesh_material_reference (input_path , usd_mesh .GetPrim ().GetName (), [material .name ], data )
302+ _mesh_subsets_obj (usd_mesh , input_path , reader , obj_mesh , data )
257303
258304 if name != safe_name :
259305 usdex .core .setDisplayName (usd_mesh .GetPrim (), name )
0 commit comments