Skip to content

Commit 01ddb81

Browse files
authored
Optimized numpy processing when converting STL, OBJ, and DAE (#54)
1 parent 9fbc516 commit 01ddb81

File tree

3 files changed

+52
-48
lines changed

3 files changed

+52
-48
lines changed

urdf_usd_converter/_impl/conversion_collada.py

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,13 @@ def _convert_mesh(
6262
matrix = _multiply_root_matrix(_collada, matrix)
6363

6464
all_face_vertex_counts: list[int] = []
65-
all_face_vertex_indices: list[int] = []
65+
all_face_vertex_indices_list: list[np.ndarray] = []
66+
all_normals_list: list[np.ndarray] = []
6667
all_normals: Vt.Vec3fArray | None = None
67-
all_normal_indices: list[int] = []
68+
all_normal_indices_list: list[np.ndarray] = []
69+
all_uvs_list: list[np.ndarray] = []
6870
all_uvs: Vt.Vec2fArray | None = None
69-
all_uv_indices: list[int] = []
71+
all_uv_indices_list: list[np.ndarray] = []
7072
face_offsets: list[int] = []
7173
face_material_names: list[str] = []
7274
current_normal_offset = 0
@@ -90,15 +92,16 @@ def _convert_mesh(
9092
# vertex indices.
9193
if is_triangle_type:
9294
face_vertex_counts, face_vertex_indices = convert_face_indices_array(primitive.vertex_index)
95+
face_vertex_indices_array = np.array(face_vertex_indices, dtype=np.int32)
9396
else: # Polylist or Polygons
9497
# Use numpy for faster conversion
9598
face_vertex_counts = primitive.vcounts.tolist()
96-
face_vertex_indices = primitive.vertex_index.tolist()
99+
face_vertex_indices_array = primitive.vertex_index
97100
all_face_vertex_counts.extend(face_vertex_counts)
98-
all_face_vertex_indices.extend(face_vertex_indices)
101+
all_face_vertex_indices_list.append(face_vertex_indices_array)
99102

100103
# Remove duplicates and add used vertex indices.
101-
unique_vertex_indices.extend(np.unique(face_vertex_indices))
104+
unique_vertex_indices.extend(np.unique(face_vertex_indices_array))
102105

103106
face_offsets.append(len(face_vertex_counts))
104107

@@ -110,49 +113,56 @@ def _convert_mesh(
110113

111114
# normals.
112115
if hasattr(primitive, "normal") and len(primitive.normal) > 0:
113-
primitive_normals = convert_vec3f_array(primitive.normal)
114-
all_normals = primitive_normals if all_normals is None else Vt.Vec3fArray(list(all_normals) + list(primitive_normals))
116+
primitive_normals = np.array(primitive.normal, dtype=np.float32).reshape(-1, 3)
117+
all_normals_list.append(primitive_normals)
115118
normal_indices = primitive.normal_index
116119

117120
# Optimize flattening operation using numpy when possible
118121
if is_triangle_type:
119122
# Flatten 2D array more efficiently
120123
if isinstance(normal_indices, np.ndarray):
121-
normal_indices = normal_indices.ravel().tolist()
124+
normal_indices = normal_indices.ravel()
122125
else: # Polylist or Polygons
123-
normal_indices = normal_indices.tolist()
126+
pass # normal_indices is already a numpy array
124127

125-
normal_indices = (np.array(normal_indices, dtype=np.int32) + current_normal_offset).tolist()
126-
all_normal_indices.extend(normal_indices)
128+
normal_indices_array = np.array(normal_indices, dtype=np.int32) + current_normal_offset
129+
all_normal_indices_list.append(normal_indices_array)
127130
current_normal_offset += len(primitive_normals)
128131

129132
# uvs.
130133
if hasattr(primitive, "texcoordset") and len(primitive.texcoordset) > 0:
131-
uv_data = convert_vec2f_array(primitive.texcoordset[0])
132-
all_uvs = uv_data if all_uvs is None else Vt.Vec2fArray(list(all_uvs) + list(uv_data))
133-
uv_indices = primitive.texcoord_index if hasattr(primitive, "texcoord_index") else list(range(len(uv_data)))
134-
uv_indices = (np.array(uv_indices, dtype=np.int32) + current_uv_offset).tolist()
135-
all_uv_indices.extend(uv_indices)
134+
uv_data = np.array(primitive.texcoordset[0], dtype=np.float32).reshape(-1, 2)
135+
all_uvs_list.append(uv_data)
136+
uv_indices = primitive.texcoord_index if hasattr(primitive, "texcoord_index") else np.arange(len(uv_data), dtype=np.int32)
137+
uv_indices_array = np.array(uv_indices, dtype=np.int32) + current_uv_offset
138+
all_uv_indices_list.append(uv_indices_array)
136139
current_uv_offset += len(uv_data)
137140

141+
# Concatenate all numpy arrays into single arrays
142+
all_face_vertex_indices = np.concatenate(all_face_vertex_indices_list) if all_face_vertex_indices_list else np.array([], dtype=np.int32)
143+
all_normal_indices = np.concatenate(all_normal_indices_list) if all_normal_indices_list else np.array([], dtype=np.int32)
144+
all_uv_indices = np.concatenate(all_uv_indices_list) if all_uv_indices_list else np.array([], dtype=np.int32)
145+
138146
if len(all_face_vertex_counts) > 0 and len(all_face_vertex_indices) > 0 and all_vertices is not None:
139147
# Remove unused vertices from all_vertices and update the vertex list all_face_vertex_indices.
140148
unique_vertex_indices = np.unique(unique_vertex_indices)
141149

142150
vertices_array = np.array(all_vertices, dtype=np.float32).reshape(-1, 3)
143151
all_vertices = convert_vec3f_array(vertices_array[unique_vertex_indices])
144-
all_face_vertex_indices = np.searchsorted(unique_vertex_indices, all_face_vertex_indices).tolist()
152+
all_face_vertex_indices = Vt.IntArray.FromNumpy(np.searchsorted(unique_vertex_indices, all_face_vertex_indices))
145153

146154
# create a normal primvar data for the geometry.
147155
normals = None
148-
if all_normals and all_normal_indices and len(all_normal_indices) == len(all_face_vertex_indices):
149-
normals = usdex.core.Vec3fPrimvarData(UsdGeom.Tokens.faceVarying, all_normals, Vt.IntArray(all_normal_indices))
156+
all_normals = convert_vec3f_array(np.concatenate(all_normals_list)) if len(all_normals_list) > 0 else None
157+
if all_normals and len(all_normal_indices) > 0 and len(all_normal_indices) == len(all_face_vertex_indices):
158+
normals = usdex.core.Vec3fPrimvarData(UsdGeom.Tokens.faceVarying, all_normals, Vt.IntArray.FromNumpy(all_normal_indices))
150159
normals.index() # re-index the normals to remove duplicates
151160

152161
# create a uv primvar data for the geometry.
153162
uvs = None
154-
if all_uvs and all_uv_indices and len(all_uv_indices) == len(all_face_vertex_indices):
155-
uvs = usdex.core.Vec2fPrimvarData(UsdGeom.Tokens.faceVarying, all_uvs, Vt.IntArray(all_uv_indices))
163+
all_uvs = convert_vec2f_array(np.concatenate(all_uvs_list)) if len(all_uvs_list) > 0 else None
164+
if all_uvs and len(all_uv_indices) > 0 and len(all_uv_indices) == len(all_face_vertex_indices):
165+
uvs = usdex.core.Vec2fPrimvarData(UsdGeom.Tokens.faceVarying, all_uvs, Vt.IntArray.FromNumpy(all_uv_indices))
156166
uvs.index() # re-index the uvs to remove duplicates
157167

158168
# If only one geometry exists within the dae, only one mesh will be placed.
@@ -172,8 +182,8 @@ def _convert_mesh(
172182
_prim,
173183
_safe_name,
174184
faceVertexCounts=Vt.IntArray(all_face_vertex_counts),
175-
faceVertexIndices=Vt.IntArray(all_face_vertex_indices),
176-
points=Vt.Vec3fArray(all_vertices),
185+
faceVertexIndices=all_face_vertex_indices,
186+
points=all_vertices,
177187
normals=normals,
178188
uvs=uvs,
179189
)

urdf_usd_converter/_impl/mesh.py

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -134,16 +134,14 @@ def _convert_single_obj(
134134
points = convert_vec3f_array(np.asarray(vertices, dtype=np.float32).reshape(-1, 3))
135135

136136
normals = None
137-
source_normals = attrib.normals
138-
if len(source_normals) > 0:
139-
normals_data = convert_vec3f_array(np.asarray(source_normals, dtype=np.float32).reshape(-1, 3))
137+
if len(attrib.normals) > 0:
138+
normals_data = convert_vec3f_array(np.asarray(attrib.normals, dtype=np.float32).reshape(-1, 3))
140139
normals = usdex.core.Vec3fPrimvarData(UsdGeom.Tokens.faceVarying, normals_data, Vt.IntArray(obj_mesh.normal_indices()))
141140
normals.index() # re-index the normals to remove duplicates
142141

143142
uvs = None
144-
source_uvs = attrib.texcoords
145-
if len(source_uvs) > 0:
146-
uv_data = convert_vec2f_array(np.asarray(source_uvs, dtype=np.float32).reshape(-1, 2))
143+
if len(attrib.texcoords) > 0:
144+
uv_data = convert_vec2f_array(np.asarray(attrib.texcoords, dtype=np.float32).reshape(-1, 2))
147145
uvs = usdex.core.Vec2fPrimvarData(UsdGeom.Tokens.faceVarying, uv_data, Vt.IntArray(obj_mesh.texcoord_indices()))
148146
uvs.index() # re-index the uvs to remove duplicates
149147

@@ -195,7 +193,7 @@ def convert_obj(prim: Usd.Prim, input_path: pathlib.Path, data: ConversionData)
195193

196194
for shape, name, safe_name in zip(shapes, names, safe_names):
197195
obj_mesh = shape.mesh
198-
face_vertex_counts = obj_mesh.num_face_vertices
196+
face_vertex_counts = Vt.IntArray(obj_mesh.num_face_vertices)
199197
material_ids = obj_mesh.material_ids[0]
200198
material = materials[material_ids] if material_ids >= 0 else None
201199

@@ -211,7 +209,7 @@ def convert_obj(prim: Usd.Prim, input_path: pathlib.Path, data: ConversionData)
211209
points = convert_vec3f_array(np.asarray(points_array, dtype=np.float32).reshape(-1, 3))
212210

213211
# Remap indices using NumPy searchsorted
214-
face_vertex_indices = np.searchsorted(unique_vertex_indices, vertex_indices_in_shape).tolist()
212+
face_vertex_indices = Vt.IntArray.FromNumpy(np.searchsorted(unique_vertex_indices, vertex_indices_in_shape))
215213

216214
# Process normals
217215
normals = None
@@ -220,11 +218,10 @@ def convert_obj(prim: Usd.Prim, input_path: pathlib.Path, data: ConversionData)
220218
unique_normal_indices = np.unique(normal_indices_in_shape)
221219

222220
normals_array = np.array(attrib.normals, dtype=np.float32).reshape(-1, 3)
223-
normals_data_array = normals_array[unique_normal_indices]
224-
normals_data = convert_vec3f_array(np.asarray(normals_data_array, dtype=np.float32).reshape(-1, 3))
221+
normals_data = convert_vec3f_array(normals_array[unique_normal_indices])
225222

226-
remapped_normal_indices = np.searchsorted(unique_normal_indices, normal_indices_in_shape).tolist()
227-
normals = usdex.core.Vec3fPrimvarData(UsdGeom.Tokens.faceVarying, normals_data, Vt.IntArray(remapped_normal_indices))
223+
remapped_normal_indices = Vt.IntArray.FromNumpy(np.searchsorted(unique_normal_indices, normal_indices_in_shape))
224+
normals = usdex.core.Vec3fPrimvarData(UsdGeom.Tokens.faceVarying, normals_data, remapped_normal_indices)
228225
normals.index() # re-index the normals to remove duplicates
229226

230227
# Process UV coordinates
@@ -234,18 +231,17 @@ def convert_obj(prim: Usd.Prim, input_path: pathlib.Path, data: ConversionData)
234231
unique_texcoord_indices = np.unique(texcoord_indices_in_shape)
235232

236233
texcoords_array = np.array(attrib.texcoords, dtype=np.float32).reshape(-1, 2)
237-
uv_data_array = texcoords_array[unique_texcoord_indices]
238-
uv_data = convert_vec2f_array(np.asarray(uv_data_array, dtype=np.float32).reshape(-1, 2))
234+
uv_data = convert_vec2f_array(texcoords_array[unique_texcoord_indices])
239235

240-
remapped_texcoord_indices = np.searchsorted(unique_texcoord_indices, texcoord_indices_in_shape).tolist()
241-
uvs = usdex.core.Vec2fPrimvarData(UsdGeom.Tokens.faceVarying, uv_data, Vt.IntArray(remapped_texcoord_indices))
236+
remapped_texcoord_indices = Vt.IntArray.FromNumpy(np.searchsorted(unique_texcoord_indices, texcoord_indices_in_shape))
237+
uvs = usdex.core.Vec2fPrimvarData(UsdGeom.Tokens.faceVarying, uv_data, remapped_texcoord_indices)
242238
uvs.index() # re-index the uvs to remove duplicates
243239

244240
usd_mesh = usdex.core.definePolyMesh(
245241
prim,
246242
safe_name,
247-
faceVertexCounts=Vt.IntArray(face_vertex_counts),
248-
faceVertexIndices=Vt.IntArray(face_vertex_indices),
243+
faceVertexCounts=face_vertex_counts,
244+
faceVertexIndices=face_vertex_indices,
249245
points=points,
250246
normals=normals,
251247
uvs=uvs,

urdf_usd_converter/_impl/numpy.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,12 @@ def convert_vec2f_array(source: np.ndarray) -> Vt.Vec2fArray:
2020
Raises:
2121
AssertionError: If the second dimension of the input array is not divisible by 2.
2222
"""
23-
num_elements, element_size = source.shape
23+
_, element_size = source.shape
2424
assert element_size % 2 == 0
2525

2626
# Reshape to (total_vectors, 2) and create Vec2f objects in batch
2727
reshaped = source.reshape(-1, 2).astype(np.float32) if element_size != 2 else source
28-
result = [Gf.Vec2f(float(v[0]), float(v[1])) for v in reshaped]
29-
return Vt.Vec2fArray(result)
28+
return Vt.Vec2fArray.FromNumpy(reshaped)
3029

3130

3231
def convert_vec3f_array(source: np.ndarray) -> Vt.Vec3fArray:
@@ -43,16 +42,15 @@ def convert_vec3f_array(source: np.ndarray) -> Vt.Vec3fArray:
4342
Raises:
4443
AssertionError: If the second dimension of the input array is not divisible by 3.
4544
"""
46-
num_elements, element_size = source.shape
45+
_, element_size = source.shape
4746
assert element_size % 3 == 0
4847

4948
# In the case of STL, element_size=9, and it holds the three vertices of a triangle as a one-dimensional array.
5049
# Therefore, we need to reshape the array to (total_vectors, 3) before creating the Vec3f objects.
5150

5251
# Reshape to (total_vectors, 3) and create Vec3f objects in batch
5352
reshaped = source.reshape(-1, 3).astype(np.float32) if element_size != 3 else source
54-
result = [Gf.Vec3f(float(v[0]), float(v[1]), float(v[2])) for v in reshaped]
55-
return Vt.Vec3fArray(result)
53+
return Vt.Vec3fArray.FromNumpy(reshaped)
5654

5755

5856
def convert_face_indices_array(source: np.ndarray) -> tuple[list[int], list[int]]:

0 commit comments

Comments
 (0)