Skip to content

Commit 7a1a1f2

Browse files
authored
Fixed the DAE file conversion errors (#60)
* dae: Fixed an issue where materials would sometimes not bind dae: Fixed an issue where the UV array could sometimes not be acquired correctly. dae: Fixed an issue when Opaque Mode is set to 'RGB_ZERO'. * dae: Ignore broken references * dae: If there are no duplicate material names in dae, use the material name for identification, otherwise it will use material ID.
1 parent f196d63 commit 7a1a1f2

File tree

7 files changed

+258
-17
lines changed

7 files changed

+258
-17
lines changed

tests/data/assets/box_transparent_material.dae

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
<transparent opaque="RGB_ZERO">
1919
<color sid="alpha">0.2 0.3 0.1 1</color>
2020
</transparent>
21+
<transparency>
22+
<float sid="transparency">1.0</float>
23+
</transparency>
2124
<index_of_refraction>
2225
<float sid="ior">1.45</float>
2326
</index_of_refraction>
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
3+
<asset>
4+
<unit name="meter" meter="1"/>
5+
<up_axis>Z_UP</up_axis>
6+
</asset>
7+
<library_effects>
8+
<effect id="Material_red-effect">
9+
<profile_COMMON>
10+
<technique sid="common">
11+
<lambert>
12+
<emission>
13+
<color sid="emission">0 0 0 1</color>
14+
</emission>
15+
<diffuse>
16+
<color sid="diffuse">1 0 0 1</color>
17+
</diffuse>
18+
<index_of_refraction>
19+
<float sid="ior">1.45</float>
20+
</index_of_refraction>
21+
</lambert>
22+
</technique>
23+
</profile_COMMON>
24+
</effect>
25+
<effect id="Material_green-effect">
26+
<profile_COMMON>
27+
<technique sid="common">
28+
<lambert>
29+
<emission>
30+
<color sid="emission">0 0 0 1</color>
31+
</emission>
32+
<diffuse>
33+
<color sid="diffuse">0 1 0 1</color>
34+
</diffuse>
35+
<index_of_refraction>
36+
<float sid="ior">1.5</float>
37+
</index_of_refraction>
38+
</lambert>
39+
</technique>
40+
</profile_COMMON>
41+
</effect>
42+
</library_effects>
43+
<library_images/>
44+
<library_materials>
45+
<material id="Material_red-material" name="Material_same">
46+
<instance_effect url="#Material_red-effect"/>
47+
</material>
48+
<material id="Material_green-material" name="Material_same">
49+
<instance_effect url="#Material_green-effect"/>
50+
</material>
51+
</library_materials>
52+
<library_geometries>
53+
<geometry id="Cube-mesh" name="Cube">
54+
<mesh>
55+
<source id="Cube-mesh-positions">
56+
<float_array id="Cube-mesh-positions-array" count="24">1 1 1 1 1 -1 1 -1 1 1 -1 -1 -1 1 1 -1 1 -1 -1 -1 1 -1 -1 -1</float_array>
57+
<technique_common>
58+
<accessor source="#Cube-mesh-positions-array" count="8" stride="3">
59+
<param name="X" type="float"/>
60+
<param name="Y" type="float"/>
61+
<param name="Z" type="float"/>
62+
</accessor>
63+
</technique_common>
64+
</source>
65+
<source id="Cube-mesh-normals">
66+
<float_array id="Cube-mesh-normals-array" count="36">0 0 1 0 -1 0 -1 0 0 0 0 -1 1 0 0 0 1 0 0 0 1 0 -1 0 -1 0 0 0 0 -1 1 0 0 0 1 0</float_array>
67+
<technique_common>
68+
<accessor source="#Cube-mesh-normals-array" count="12" stride="3">
69+
<param name="X" type="float"/>
70+
<param name="Y" type="float"/>
71+
<param name="Z" type="float"/>
72+
</accessor>
73+
</technique_common>
74+
</source>
75+
<source id="Cube-mesh-map-0">
76+
<float_array id="Cube-mesh-map-0-array" count="72">0.875 0.5 0.625 0.75 0.625 0.5 0.625 0.75 0.375 1 0.375 0.75 0.625 0 0.375 0.25 0.375 0 0.375 0.5 0.125 0.75 0.125 0.5 0.625 0.5 0.375 0.75 0.375 0.5 0.625 0.25 0.375 0.5 0.375 0.25 0.875 0.5 0.875 0.75 0.625 0.75 0.625 0.75 0.625 1 0.375 1 0.625 0 0.625 0.25 0.375 0.25 0.375 0.5 0.375 0.75 0.125 0.75 0.625 0.5 0.625 0.75 0.375 0.75 0.625 0.25 0.625 0.5 0.375 0.5</float_array>
77+
<technique_common>
78+
<accessor source="#Cube-mesh-map-0-array" count="36" stride="2">
79+
<param name="S" type="float"/>
80+
<param name="T" type="float"/>
81+
</accessor>
82+
</technique_common>
83+
</source>
84+
<vertices id="Cube-mesh-vertices">
85+
<input semantic="POSITION" source="#Cube-mesh-positions"/>
86+
</vertices>
87+
<triangles material="Material_red-material" count="6">
88+
<input semantic="VERTEX" source="#Cube-mesh-vertices" offset="0"/>
89+
<input semantic="NORMAL" source="#Cube-mesh-normals" offset="1"/>
90+
<input semantic="TEXCOORD" source="#Cube-mesh-map-0" offset="2" set="0"/>
91+
<p>2 1 3 7 1 4 3 1 5 6 2 6 5 2 7 7 2 8 1 3 9 7 3 10 5 3 11 2 7 21 6 7 22 7 7 23 6 8 24 4 8 25 5 8 26 1 9 27 3 9 28 7 9 29</p>
92+
</triangles>
93+
<triangles material="Material_green-material" count="6">
94+
<input semantic="VERTEX" source="#Cube-mesh-vertices" offset="0"/>
95+
<input semantic="NORMAL" source="#Cube-mesh-normals" offset="1"/>
96+
<input semantic="TEXCOORD" source="#Cube-mesh-map-0" offset="2" set="0"/>
97+
<p>4 0 0 2 0 1 0 0 2 0 4 12 3 4 13 1 4 14 4 5 15 1 5 16 5 5 17 4 6 18 6 6 19 2 6 20 0 10 30 2 10 31 3 10 32 4 11 33 0 11 34 1 11 35</p>
98+
</triangles>
99+
</mesh>
100+
</geometry>
101+
</library_geometries>
102+
<library_visual_scenes>
103+
<visual_scene id="Scene" name="Scene">
104+
<node id="Cube" name="Cube" type="NODE">
105+
<matrix sid="transform">0.5 0 0 0 0 0.5 0 0 0 0 0.5 0 0 0 0 1</matrix>
106+
<instance_geometry url="#Cube-mesh" name="Cube">
107+
<bind_material>
108+
<technique_common>
109+
<instance_material symbol="Material_red-material" target="#Material_red-material">
110+
<bind_vertex_input semantic="UVMap" input_semantic="TEXCOORD" input_set="0"/>
111+
</instance_material>
112+
<instance_material symbol="Material_green-material" target="#Material_green-material">
113+
<bind_vertex_input semantic="UVMap" input_semantic="TEXCOORD" input_set="0"/>
114+
</instance_material>
115+
</technique_common>
116+
</bind_material>
117+
</instance_geometry>
118+
</node>
119+
</visual_scene>
120+
</library_visual_scenes>
121+
<scene>
122+
<instance_visual_scene url="#Scene"/>
123+
</scene>
124+
</COLLADA>

tests/data/test_displayname.urdf

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@
3535
</geometry>
3636
</visual>
3737
</link>
38+
<link name="link-box_two_materials_same_names">
39+
<visual>
40+
<geometry>
41+
<!-- With the same material name (IDs differ) -->
42+
<mesh filename="assets/box_two_materials_same_names.dae" scale="0.5 0.5 0.5"/>
43+
</geometry>
44+
</visual>
45+
</link>
3846

3947
<joint name="joint:root" type="fixed">
4048
<origin rpy="0 0 0" xyz="1 0 0"/>
@@ -51,5 +59,9 @@
5159
<parent link="link-box2"/>
5260
<child link="link-mesh_dae"/>
5361
</joint>
54-
62+
<joint name="box_two_materials_same_names" type="fixed">
63+
<origin rpy="0 0 0" xyz="0 3 0"/>
64+
<parent link="link-box2"/>
65+
<child link="link-box_two_materials_same_names"/>
66+
</joint>
5567
</robot>

tests/testAssetStructure.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,28 @@ def test_display_name(self):
103103
self.assertTrue(material_red_prim.IsA(UsdShade.Material))
104104
self.assertEqual(usdex.core.getDisplayName(material_red_prim), "material:red")
105105

106+
material_red_prim = material_scope_prim.GetChild("red_mat")
107+
self.assertTrue(material_red_prim.IsValid())
108+
self.assertTrue(material_red_prim.IsA(UsdShade.Material))
109+
110+
material_blue_prim = material_scope_prim.GetChild("blue_mat")
111+
self.assertTrue(material_blue_prim.IsValid())
112+
self.assertTrue(material_blue_prim.IsA(UsdShade.Material))
113+
114+
material_green_prim = material_scope_prim.GetChild("green_mat")
115+
self.assertTrue(material_green_prim.IsValid())
116+
self.assertTrue(material_green_prim.IsA(UsdShade.Material))
117+
118+
material_red_prim = material_scope_prim.GetChild("tn__Material_redmaterial_wT")
119+
self.assertTrue(material_red_prim.IsValid())
120+
self.assertTrue(material_red_prim.IsA(UsdShade.Material))
121+
self.assertEqual(usdex.core.getDisplayName(material_red_prim), "Material_same")
122+
123+
material_green_prim = material_scope_prim.GetChild("tn__Material_greenmaterial_vW0")
124+
self.assertTrue(material_green_prim.IsValid())
125+
self.assertTrue(material_green_prim.IsA(UsdShade.Material))
126+
self.assertEqual(usdex.core.getDisplayName(material_green_prim), "Material_same")
127+
106128
def test_interface_layer(self):
107129
input_path = "tests/data/simple_box.urdf"
108130
robot_name = pathlib.Path(input_path).stem

urdf_usd_converter/_impl/conversion_collada.py

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@
88
from pxr import Gf, Tf, Usd, UsdGeom, UsdShade, Vt
99

1010
from .data import ConversionData
11-
from .material import store_dae_material_data, store_mesh_material_reference
11+
from .material import store_dae_material_data, store_mesh_material_reference, use_material_id
1212
from .numpy import convert_face_indices_array, convert_matrix4d, convert_vec2f_array, convert_vec3f_array
1313

1414
__all__ = ["convert_collada"]
1515

1616

1717
def convert_collada(prim: Usd.Prim, input_path: pathlib.Path, data: ConversionData) -> Usd.Prim | None:
1818
try:
19-
_collada = collada.Collada(str(input_path))
19+
# Ignore broken references (e.g., missing lights, cameras) to allow parsing incomplete DAE files
20+
_collada = collada.Collada(str(input_path), ignore=[collada.DaeError])
2021

2122
# Store the material data from the DAE file.
2223
store_dae_material_data(input_path, _collada, data)
@@ -50,6 +51,7 @@ def _convert_mesh(
5051
prim: Usd.Prim,
5152
name: str,
5253
geometry: collada.geometry.Geometry,
54+
node_materials: list[collada.scene.MaterialNode] | None,
5355
matrix: np.ndarray,
5456
data: ConversionData,
5557
) -> Usd.Prim:
@@ -78,6 +80,12 @@ def _convert_mesh(
7880
all_vertices = geometry.primitives[0].vertex if hasattr(geometry.primitives[0], "vertex") else None
7981
unique_vertex_indices = []
8082

83+
# Whether to use material IDs for material identification.
84+
# If True, the material ID is used as the identifier.
85+
# If False, the material name is used as the identifier.
86+
dae_file_path = pathlib.Path(_collada.filename)
87+
_use_material_id = use_material_id(dae_file_path, data)
88+
8189
for primitive in geometry.primitives:
8290
primitive_type = type(primitive).__name__
8391

@@ -105,10 +113,20 @@ def _convert_mesh(
105113

106114
face_offsets.append(len(face_vertex_counts))
107115

108-
# Get the material name and store it temporarily.
116+
# Get the material_id from the primitive.
117+
# If node_materials exists, we need to re-search for the material ID using 'primitive.material'.
118+
material_id = (
119+
next((material.target.id for material in node_materials if material.symbol == primitive.material), primitive.material)
120+
if node_materials
121+
else primitive.material
122+
)
123+
124+
# Retrieve and store the material name or material ID.
109125
# For primitives, the material ID is retrieved.
110-
# The material name that matches the material ID is retrieved from the material list in _collada.materials.
111-
material_name = next((material.name for material in _collada.materials if material.id == primitive.material), None)
126+
# The material name or ID that matches the 'material_id' is retrieved from the material list in _collada.materials.
127+
material_name = next(
128+
(material.id if _use_material_id else material.name for material in _collada.materials if material.id == material_id), None
129+
)
112130
face_material_names.append(material_name)
113131

114132
# normals.
@@ -133,7 +151,21 @@ def _convert_mesh(
133151
if hasattr(primitive, "texcoordset") and len(primitive.texcoordset) > 0:
134152
uv_data = np.array(primitive.texcoordset[0], dtype=np.float32).reshape(-1, 2)
135153
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)
154+
155+
uv_indices = (
156+
primitive.texcoord_indexset[0]
157+
if hasattr(primitive, "texcoord_indexset") and len(primitive.texcoord_indexset) > 0
158+
else np.arange(len(uv_data), dtype=np.int32)
159+
)
160+
161+
# Flatten the UV indices array if needed (same as normal_index processing)
162+
if is_triangle_type:
163+
# Flatten 2D array more efficiently
164+
if isinstance(uv_indices, np.ndarray) and uv_indices.ndim > 1:
165+
uv_indices = uv_indices.ravel()
166+
else: # Polylist or Polygons
167+
pass # uv_indices is already a 1D numpy array
168+
137169
uv_indices_array = np.array(uv_indices, dtype=np.int32) + current_uv_offset
138170
all_uv_indices_list.append(uv_indices_array)
139171
current_uv_offset += len(uv_data)
@@ -210,11 +242,10 @@ def _convert_mesh(
210242
geom_subset.GetFamilyNameAttr().Set(UsdShade.Tokens.materialBind)
211243
subset_offset += face_offset
212244

213-
# Stores the material names referenced by geometry. Each primitive can have its own material.
245+
# Stores the material names or IDs referenced by geometry. Each primitive can have its own material.
214246
# These will be allocated per single mesh or GeomSubset in USD.
215247
# Material binding is done on the Material layer, so no binding is done at this stage.
216248
if len(face_material_names) > 0:
217-
dae_file_path = pathlib.Path(_collada.filename)
218249
store_mesh_material_reference(dae_file_path, usd_mesh.GetPrim().GetName(), face_material_names, data)
219250

220251
# Convert the matrix to a Gf.Matrix4d.
@@ -253,7 +284,7 @@ def _traverse_scene(
253284
# Converts geometry to usd meshes.
254285
# If the geometry has no primitives, skip the conversion.
255286
# The name of the mesh to be created will be the geometry name in DAE.
256-
_convert_mesh(_collada, prim, node.geometry.name, node.geometry, matrix, data)
287+
_convert_mesh(_collada, prim, node.geometry.name, node.geometry, node.materials, matrix, data)
257288

258289
if hasattr(node, "children") and node.children:
259290
for child in node.children:

0 commit comments

Comments
 (0)