Skip to content

Commit 9b22057

Browse files
authored
Merge pull request #93 from compas-dev/fix-polylist-parsing
Fix DAE parser to handle polylist-based meshes
2 parents 4162090 + cf4159a commit 9b22057

File tree

2 files changed

+47
-9
lines changed

2 files changed

+47
-9
lines changed

CHANGELOG.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Unreleased
2121

2222
**Fixed**
2323

24+
* Fix DAE parser to handle ``polylist`` meshes
25+
2426
**Deprecated**
2527

2628
0.10.0

src/compas_fab/backends/ros/fileserver_loader.py

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -230,14 +230,17 @@ def _local_mesh_filename(self, url):
230230

231231

232232
def _dae_mesh_importer(filename, precision):
233-
"""This is a very simple implementation of a DAE/Collada parser."""
233+
"""This is a very simple implementation of a DAE/Collada parser.
234+
235+
Collada specification: https://www.khronos.org/files/collada_spec_1_5.pdf
236+
"""
234237
dae = XML.from_file(filename)
235238
meshes = []
236239
visual_scenes = dae.root.find('library_visual_scenes')
237240
materials = dae.root.find('library_materials')
238241
effects = dae.root.find('library_effects')
239242

240-
for geometry in dae.root.findall('.//geometry'):
243+
for geometry in dae.root.findall('library_geometries/geometry'):
241244
mesh_xml = geometry.find('mesh')
242245
mesh_id = geometry.attrib['id']
243246
matrix_node = visual_scenes.find('visual_scene/node/instance_geometry[@url="#{}"]/../matrix'.format(mesh_id))
@@ -254,16 +257,28 @@ def _dae_mesh_importer(filename, precision):
254257
M = M[0:4], M[4:8], M[8:12], M[12:16]
255258
transform = Transformation.from_matrix(M)
256259

257-
for triangle_set in mesh_xml.findall('triangles'):
258-
triangle_set_data = triangle_set.find('p').text.split()
260+
# primitive elements can be any combination of:
261+
# lines, linestrips, polygons, polylist, triangles, trifans, tristrips
262+
# The current implementation only supports triangles and polylist of triangular meshes
263+
primitive_element_sets = []
264+
primitive_element_sets.extend(mesh_xml.findall('triangles'))
265+
primitive_element_sets.extend(mesh_xml.findall('polylist'))
266+
267+
if len(primitive_element_sets) == 0:
268+
raise Exception('No primitive elements found (currently only triangles and polylist are supported)')
269+
270+
for primitive_element_set in primitive_element_sets:
271+
primitive_tag = primitive_element_set.tag
272+
primitive_set_data = primitive_element_set.find('p').text.split()
259273

260274
# Try to retrieve mesh colors
261275
mesh_colors = {}
262276

263277
if materials is not None and effects is not None:
264278
try:
265279
instance_effect = None
266-
material_id = triangle_set.attrib.get('material')
280+
material_id = primitive_element_set.attrib.get('material')
281+
primitive_count = int(primitive_element_set.attrib['count'])
267282

268283
if material_id is not None:
269284
instance_effect = materials.find('material[@id="{}"]/instance_effect'.format(material_id))
@@ -278,16 +293,37 @@ def _dae_mesh_importer(filename, precision):
278293
LOGGER.exception('Exception while loading materials, all materials of mesh file %s will be ignored ', filename)
279294

280295
# Parse vertices
281-
vertices_input = triangle_set.find('input[@semantic="VERTEX"]')
282-
vertices_link = mesh_xml.find('vertices[@id="{}"]/input'.format(vertices_input.attrib['source'][1:]))
296+
all_offsets = sorted([int(i.attrib['offset']) for i in primitive_element_set.findall('input[@offset]')])
297+
if not all_offsets:
298+
raise Exception('Primitive element node does not contain offset information! Primitive tag={}'.format(primitive_tag))
299+
300+
vertices_input = primitive_element_set.find('input[@semantic="VERTEX"]')
301+
vertices_id = vertices_input.attrib['source'][1:]
302+
vertices_link = mesh_xml.find('vertices[@id="{}"]/input'.format(vertices_id))
283303
positions = mesh_xml.find('source[@id="{}"]/float_array'.format(vertices_link.attrib['source'][1:]))
284304
positions = positions.text.split()
285305

286306
vertices = [[float(p) for p in positions[i:i + 3]] for i in range(0, len(positions), 3)]
287307

288308
# Parse faces
289-
faces = [int(f) for f in triangle_set_data[::2]] # Ignore normals (every second item is normal index)
290-
faces = [faces[i:i + 3] for i in range(0, len(faces), 3)]
309+
# Every nth element is a vertex key, we ignore the rest based on the offsets defined
310+
# Usually, every second item is the normal, but there can be other items offset in there (vertex tangents, etc)
311+
skip_step = 1 + all_offsets[-1]
312+
313+
if primitive_tag == 'triangles':
314+
vcount = [3] * primitive_count
315+
elif primitive_tag == 'polylist':
316+
vcount = [int(v) for v in primitive_element_set.find('vcount').text.split()]
317+
318+
if len(vcount) != primitive_count:
319+
raise Exception('Primitive count does not match vertex per face count, vertex input id={}'.format(vertices_id))
320+
321+
fkeys = [int(f) for f in primitive_set_data[::skip_step]]
322+
faces = []
323+
for i in range(primitive_count):
324+
a = i * vcount[i]
325+
b = a + vcount[i]
326+
faces.append(fkeys[a:b])
291327

292328
# Rebuild vertices and faces using the same logic that other importers
293329
# use remapping everything based on a selected precision

0 commit comments

Comments
 (0)