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,62 @@ 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 Geometry 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+ # Get a list of face numbers for each material_id from obj_mesh.material_ids.
118+ # If a material does not exist, the material_id for the face will be set to -1.
119+ face_list_by_material = {}
120+ material_ids_array = np .array (obj_mesh .material_ids )
121+ unique_material_ids = np .unique (material_ids_array )
122+ for material_id in unique_material_ids :
123+ face_indices = np .where (material_ids_array == material_id )[0 ]
124+ face_list_by_material [int (material_id )] = face_indices .tolist ()
125+
126+ materials = reader .GetMaterials ()
127+ if len (face_list_by_material ) == 1 :
128+ # If there is only one material. In this case, no subset is created.
129+ material_id = next (iter (face_list_by_material .keys ()))
130+ material_name = materials [material_id ].name if material_id >= 0 else None
131+ if material_name :
132+ store_mesh_material_reference (input_path , mesh .GetPrim ().GetName (), [material_name ], data )
133+ return
134+ else :
135+ stage = mesh .GetPrim ().GetStage ()
136+
137+ # If there are multiple materials. In this case, subsets are created.
138+ material_names = []
139+ for i , (material_id , face_indices ) in enumerate (face_list_by_material .items ()):
140+ material_name = materials [material_id ].name if material_id >= 0 else None
141+ material_names .append (material_name )
142+ subset_name = f"GeomSubset_{ (i + 1 ):03d} "
143+
144+ geom_subset = UsdGeom .Subset .Define (stage , mesh .GetPrim ().GetPath ().AppendChild (subset_name ))
145+ if geom_subset :
146+ geom_subset .GetIndicesAttr ().Set (Vt .IntArray (face_indices ))
147+ geom_subset .GetElementTypeAttr ().Set (UsdGeom .Tokens .face )
148+ geom_subset .GetFamilyNameAttr ().Set (UsdShade .Tokens .materialBind )
149+
150+ # Store the material names for the mesh.
151+ store_mesh_material_reference (input_path , mesh .GetPrim ().GetName (), material_names , data )
152+
153+
98154def _convert_single_obj (
99155 prim : Usd .Prim ,
100156 input_path : pathlib .Path ,
@@ -116,17 +172,10 @@ def _convert_single_obj(
116172 """
117173 shapes = reader .GetShapes ()
118174 attrib = reader .GetAttrib ()
119- materials = reader .GetMaterials ()
120175
121176 # This method only deals with a single mesh, so it only considers the first mesh.
122177 obj_mesh = shapes [0 ].mesh
123178
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-
130179 vertices = attrib .vertices
131180 face_vertex_counts = obj_mesh .num_face_vertices
132181 face_vertex_indices = obj_mesh .vertex_indices ()
@@ -157,10 +206,9 @@ def _convert_single_obj(
157206 if not usd_mesh :
158207 Tf .Warn (f'Failed to convert mesh "{ prim .GetPath ()} " from { input_path } ' )
159208
160- # If the mesh has a material, stores the material name for the mesh .
209+ # Create subsets for the mesh if there are multiple materials .
161210 # 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 )
211+ _mesh_subsets_obj (usd_mesh , input_path , reader , obj_mesh , data )
164212
165213 return usd_mesh
166214
@@ -183,7 +231,6 @@ def convert_obj(prim: Usd.Prim, input_path: pathlib.Path, data: ConversionData)
183231 return _convert_single_obj (prim , input_path , reader , data )
184232
185233 attrib = reader .GetAttrib ()
186- materials = reader .GetMaterials ()
187234
188235 names = []
189236 for shape in shapes :
@@ -193,9 +240,8 @@ def convert_obj(prim: Usd.Prim, input_path: pathlib.Path, data: ConversionData)
193240
194241 for shape , name , safe_name in zip (shapes , names , safe_names ):
195242 obj_mesh = shape .mesh
243+
196244 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
199245
200246 # Get indices directly as arrays
201247 vertex_indices_in_shape = np .array (obj_mesh .vertex_indices (), dtype = np .int32 )
@@ -250,10 +296,9 @@ def convert_obj(prim: Usd.Prim, input_path: pathlib.Path, data: ConversionData)
250296 Tf .Warn (f'Failed to convert mesh "{ prim .GetPath ()} " from { input_path } ' )
251297 return None
252298
253- # If the mesh has a material, stores the material name for the mesh .
299+ # Create subsets for the mesh if there are multiple materials .
254300 # 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 )
301+ _mesh_subsets_obj (usd_mesh , input_path , reader , obj_mesh , data )
257302
258303 if name != safe_name :
259304 usdex .core .setDisplayName (usd_mesh .GetPrim (), name )
0 commit comments