From fb32a9ce31d6b5f78cbcdc900f500b632d78962e Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Tue, 4 Aug 2026 18:14:27 +0200 Subject: [PATCH] Handle non-surface Collada geometry --- CHANGELOG.md | 1 + src/compas_robots/resources/mesh_importer.py | 34 ++++++++--- .../surfaces_lines_and_empty_geometry.dae | 61 +++++++++++++++++++ tests/test_resources.py | 11 ++++ 4 files changed, 97 insertions(+), 10 deletions(-) create mode 100644 tests/fixtures/surfaces_lines_and_empty_geometry.dae diff --git a/CHANGELOG.md b/CHANGELOG.md index b6c027c58..ac998c2bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed * `Joint._create` no longer rotates `current_axis` once more every time it runs. It transformed the axis in place instead of recomputing it from `axis`, so re-initializing the transformation tree of a model that has joints (e.g. after its structure changed) left the joint axes wrong — 90 degrees out per extra call for a joint whose origin is rotated by 90 degrees. Models built or loaded in one pass were unaffected, which is why this went unnoticed. +* The COLLADA mesh importer now skips empty and non-surface geometry instead of aborting files that otherwise contain valid meshes, and reads colors from Lambert, Blinn, and constant shaders in addition to Phong. ### Removed diff --git a/src/compas_robots/resources/mesh_importer.py b/src/compas_robots/resources/mesh_importer.py index d3bc9d019..7fda5debc 100644 --- a/src/compas_robots/resources/mesh_importer.py +++ b/src/compas_robots/resources/mesh_importer.py @@ -95,15 +95,18 @@ def tag(name): M = M[0:4], M[4:8], M[8:12], M[12:16] transform = Transformation.from_matrix(M) - # primitive elements can be any combination of: - # lines, linestrips, polygons, polylist, triangles, trifans, tristrips - # The current implementation only supports triangles and polylist of triangular meshes + # Primitive elements can be any combination of: + # lines, linestrips, polygons, polylist, triangles, trifans, tristrips. + # The current implementation only supports triangles and polylist surface meshes. + # Other primitives and empty geometry nodes are ignored because this importer returns + # face-based Mesh objects. Raise only if the complete file has no supported surfaces. primitive_element_sets = [] primitive_element_sets.extend(mesh_xml.findall(tag("triangles"))) primitive_element_sets.extend(mesh_xml.findall(tag("polylist"))) if len(primitive_element_sets) == 0: - raise Exception("No primitive elements found (currently only triangles and polylist are supported)") + LOGGER.debug("Skipping Collada geometry %s because it contains no supported surface primitives", mesh_id) + continue for primitive_element_set in primitive_element_sets: primitive_tag = _xml_local_name(primitive_element_set.tag) @@ -124,12 +127,20 @@ def tag(name): if instance_effect is not None: instance_effect_id = instance_effect.attrib["url"][1:] effect = effects.find('{}[@id="{}"]'.format(tag("effect"), instance_effect_id)) - phong = effect.find("{}/{}/{}".format(tag("profile_COMMON"), tag("technique"), tag("phong"))) - colors = phong.findall(".//{}".format(tag("color"))) - for color_node in colors: - rgba = [float(i) for i in color_node.text.split()] - if "sid" in color_node.attrib: - mesh_colors["mesh_color.{}".format(color_node.attrib["sid"])] = rgba + technique = effect.find("{}/{}".format(tag("profile_COMMON"), tag("technique"))) + shader = None + + for shader_name in ("phong", "lambert", "blinn", "constant"): + shader = technique.find(tag(shader_name)) + if shader is not None: + break + + if shader is not None: + colors = shader.findall(".//{}".format(tag("color"))) + for color_node in colors: + rgba = [float(i) for i in color_node.text.split()] + if "sid" in color_node.attrib: + mesh_colors["mesh_color.{}".format(color_node.attrib["sid"])] = rgba except Exception: LOGGER.exception("Exception while loading materials, all materials of mesh file %s will be ignored ", filename) @@ -191,4 +202,7 @@ def tag(name): meshes.append(mesh) + if not meshes: + raise Exception("No supported surface primitives found (currently only triangles and polylist are supported)") + return meshes diff --git a/tests/fixtures/surfaces_lines_and_empty_geometry.dae b/tests/fixtures/surfaces_lines_and_empty_geometry.dae new file mode 100644 index 000000000..19a29c20e --- /dev/null +++ b/tests/fixtures/surfaces_lines_and_empty_geometry.dae @@ -0,0 +1,61 @@ + + + + + + + + + 1 0 0 1 + + + + + + + + + + + + + + + + 0 0 0 1 0 0 0 1 0 + + + + + + +

0 1 2

+
+
+
+ + + + 0 0 0 1 0 0 + + + + + + +

0 1

+
+
+
+ + + + + + + + + + +
+
diff --git a/tests/test_resources.py b/tests/test_resources.py index bce5bc561..db7c71c4e 100644 --- a/tests/test_resources.py +++ b/tests/test_resources.py @@ -23,3 +23,14 @@ def test_mesh_import_namespaced_collada(): assert meshes[0].number_of_faces() == 2598 assert meshes[1].number_of_vertices() == 1158 assert meshes[1].number_of_faces() == 2240 + + +def test_mesh_import_collada_ignores_non_surface_geometry_and_loads_lambert_material(): + filename = os.path.join(os.path.dirname(__file__), "fixtures", "surfaces_lines_and_empty_geometry.dae") + + meshes = mesh_import(filename, filename) + + assert len(meshes) == 1 + assert meshes[0].number_of_vertices() == 3 + assert meshes[0].number_of_faces() == 1 + assert meshes[0].attributes["mesh_color.diffuse"] == [1.0, 0.0, 0.0, 1.0]