from lxml import etree import collada_types as types from functools import partial with open("cube_material.DAE") as f: tree = etree.parse(f) xml_namespace = "http://www.collada.org/2005/11/COLLADASchema" def tag(s): return f"{{{xml_namespace}}}{s}" def lookup_add(lookup, name, value): if name is None: return assert name not in lookup lookup[name] = value def parse_init_from(lookup, root): return types.InitFrom(root.text.strip()) def parse_surface_type(s): if s == "UNTYPED": return types.FxSurfaceType.UNTYPED if s == "1D": return types.FxSurfaceType._1D if s == "2D": return types.FxSurfaceType._2D if s == "3D": return types.FxSurfaceType._3D if s == "CUBE": return types.FxSurfaceType.CUBE if s == "DEPTH": return types.FxSurfaceType.DEPTH if s == "RECT": return types.FxSurfaceType.RECT assert False, s def parse_surface(lookup, root): type = parse_surface_type(root.attrib["type"]) init_from = None for child in root.getchildren(): if child.tag == tag("init_from"): assert init_from is None init_from = parse_init_from(lookup, child) surface = types.Surface(type, init_from) return surface def parse_source_fx(lookup, root): return types.SourceFX(root.text.strip()) def parse_sampler2d(lookup, root): source_fx = None for child in root.getchildren(): if child.tag == tag("source"): assert source_fx is None source_fx = parse_source_fx(lookup, child) assert source_fx is not None sampler2d = types.Sampler2D(source_fx) return sampler2d def parse_newparam(lookup, sid_lookup, root): sid = root.attrib["sid"] parameter_type = None for child in root.getchildren(): if child.tag == tag("surface"): assert parameter_type is None parameter_type = parse_surface(lookup, child) if child.tag == tag("sampler2D"): assert parameter_type is None parameter_type = parse_sampler2d(lookup, child) assert parameter_type is not None newparam = types.Newparam(sid, parameter_type) lookup_add(sid_lookup, sid, newparam) return newparam def parse_fields(lookup, root, *, cls, fields): field_kv = {} for child in root.getchildren(): for tag_name, parser in fields: if child.tag == tag(tag_name): assert tag_name not in field_kv grandchildren = child.getchildren() assert len(grandchildren) == 1 field_kv[tag_name] = parser(lookup, grandchildren[0]) for tag_name, _ in fields: if tag_name not in field_kv: field_kv[tag_name] = None return cls(**field_kv) def parse_color_or_texture(lookup, root): if root.tag == tag("color"): return types.Color(tuple(map(float, root.text.strip().split()))) if root.tag == tag("texture"): return types.Texture(root.attrib["texture"], root.attrib["texcoord"]) assert False, (root, root.tag) def parse_float(lookup, root): assert root.tag == tag("float") return types.Float(float(root.text.strip())) blinn_phong_fields = [ ("emission", parse_color_or_texture), ("ambient", parse_color_or_texture), ("diffuse", parse_color_or_texture), ("specular", parse_color_or_texture), ("shininess", parse_float), ("reflective", parse_color_or_texture), ("reflectivity", parse_float), ("transparent", parse_color_or_texture), ("transparency", parse_float), ("index_of_refraction", parse_float), ] lambert_fields = [ ("emission", parse_color_or_texture), ("ambient", parse_color_or_texture), ("diffuse", parse_color_or_texture), ("reflective", parse_color_or_texture), ("reflectivity", parse_float), ("transparent", parse_color_or_texture), ("transparency", parse_float), ("index_of_refraction", parse_float), ] constant_fields = [ ("emission", parse_color_or_texture), ("reflective", parse_color_or_texture), ("reflectivity", parse_float), ("transparent", parse_color_or_texture), ("transparency", parse_float), ("index_of_refraction", parse_float), ] parse_blinn = partial(parse_fields, cls=types.Blinn, fields=blinn_phong_fields) parse_phong = partial(parse_fields, cls=types.Phong, fields=blinn_phong_fields) parse_lambert = partial(parse_fields, cls=types.Lambert, fields=lambert_fields) parse_constant = partial(parse_fields, cls=types.Constant, fields=constant_fields) def parse_technique_fx(lookup, sid_lookup, root): id = root.attrib.get("id") sid = root.attrib["sid"] shader = None for child in root.getchildren(): if child.tag == tag("blinn"): assert shader is None shader = parse_blinn(lookup, child) if child.tag == tag("constant"): assert shader is None shader = parse_constant(lookup, child) if child.tag == tag("lambert"): assert shader is None shader = parse_lambert(lookup, child) if child.tag == tag("phong"): assert shader is None shader = parse_phong(lookup, child) assert shader is not None technique = types.TechniqueFX(id, sid, shader) lookup_add(lookup, id, technique) lookup_add(sid_lookup, sid, technique) return technique def parse_profile_common(lookup, root): newparam = [] technique_fx = None sid_lookup = {} for child in root.getchildren(): if child.tag == tag("newparam"): newparam.append(parse_newparam(lookup, sid_lookup, child)) if child.tag == tag("technique"): assert technique_fx is None technique_fx = parse_technique_fx(lookup, sid_lookup, child) assert technique_fx is not None return types.ProfileCommon(newparam, technique_fx, sid_lookup) def parse_effect(lookup, root): id = root.attrib["id"] name = root.attrib.get("name") profile_common = [] for child in root.getchildren(): if child.tag == tag("profile_COMMON"): profile_common.append(parse_profile_common(lookup, child)) effect = types.Effect(id, name, profile_common) lookup_add(lookup, id, effect) return effect def parse_library_effects(lookup, root): id = root.attrib.get("id") name = root.attrib.get("name") effects = [] for child in root.getchildren(): if child.tag == tag("effect"): effects.append(parse_effect(lookup, child)) library_effects = types.LibraryEffects(id, name, effects) lookup_add(lookup, id, library_effects) return library_effects def parse_instance_effect(lookup, sid_lookup, root): sid = root.attrib.get("sid") name = root.attrib.get("name") url = root.attrib["url"] instance_effect = types.InstanceEffect(sid, name, url) lookup_add(sid_lookup, sid, instance_effect) return instance_effect def parse_material(lookup, root): sid_lookup = {} id = root.attrib.get("id") name = root.attrib.get("name") instance_effect = None for child in root.getchildren(): if child.tag == tag("instance_effect"): assert instance_effect is None instance_effect = parse_instance_effect(lookup, sid_lookup, child) assert instance_effect is not None material = types.Material(id, name, instance_effect, sid_lookup) lookup_add(lookup, id, material) return material def parse_library_materials(lookup, root): id = root.attrib.get("id") name = root.attrib.get("name") materials = [] for child in root.getchildren(): if child.tag == tag("material"): materials.append(parse_material(lookup, child)) library_materials = types.LibraryMaterials(id, name, materials) lookup_add(lookup, id, library_materials) return library_materials def parse_input_unshared(lookup, root): semantic = root.attrib["semantic"] source = root.attrib["source"] return types.InputUnshared(semantic, source) def parse_vertices(lookup, root): id = root.attrib["id"] name = root.attrib.get("name") input = [] for child in root.getchildren(): if child.tag == tag("input"): input.append(parse_input_unshared(lookup, child)) assert len(input) >= 1 vertices = types.Vertices(id, name, input) lookup_add(lookup, id, vertices) return vertices def parse_float_array(lookup, root): count = int(root.attrib["count"]) id = root.attrib.get("id") name = root.attrib.get("name") digits = int(root.attrib.get("digits", 6)) magnitude = int(root.attrib.get("digits", 38)) assert len(root.getchildren()) == 0 floats = [float(s) for s in root.text.strip().split()] float_array = types.FloatArray(count, id, name, digits, magnitude, floats) lookup_add(lookup, id, float_array) return float_array def parse_param(lookup, sid_lookup, root): name = root.attrib.get("name") sid = root.attrib.get("sid") type = root.attrib["type"] semantic = root.attrib.get("semantic") param = types.Param(name, sid, type, semantic) lookup_add(sid_lookup, sid, param) return param def parse_accessor(lookup, root): count = int(root.attrib["count"]) offset = int(root.attrib.get("offset", 0)) source = root.attrib["source"] stride = int(root.attrib.get("stride", 1)) sid_lookup = {} params = [] for child in root.getchildren(): if child.tag == tag("param"): params.append(parse_param(lookup, sid_lookup, child)) return types.Accessor(count, offset, source, stride, params, sid_lookup) def parse_technique_common_source_core(lookup, root): accessor = None for child in root.getchildren(): if child.tag == tag("accessor"): assert accessor is None accessor = parse_accessor(lookup, child) assert accessor is not None return types.TechniqueCommon_SourceCore(accessor) def parse_source_core(lookup, root): id = root.attrib["id"] name = root.attrib.get("name") array_element = None # 0 or 1 technique_common = None # 0 or 1 for child in root.getchildren(): if child.tag == tag("float_array"): assert array_element is None array_element = parse_float_array(lookup, child) if child.tag == tag("technique_common"): assert technique_common is None technique_common = parse_technique_common_source_core(lookup, child) source_core = types.SourceCore(id, name, array_element, technique_common) lookup_add(lookup, id, source_core) return source_core def parse_input_shared(lookup, root): offset = int(root.attrib["offset"]) semantic = root.attrib["semantic"] source = root.attrib["source"] set = int(root.attrib["set"]) if "set" in root.attrib else None return types.InputShared(offset, semantic, source, set) def parse_p(lookup, root): assert len(root.getchildren()) == 0 return [int(i) for i in root.text.strip().split()] def parse_triangles(lookup, root): name = root.attrib.get("name") count = int(root.attrib["count"]) material = root.attrib.get("material") inputs = [] p = None for child in root.getchildren(): if child.tag == tag("input"): inputs.append(parse_input_shared(lookup, child)) if child.tag == tag("p"): assert p is None p = parse_p(lookup, child) return types.Triangles(name, count, material, inputs, p) def parse_mesh(lookup, root): sources = [] vertices = None primitive_elements = [] for child in root.getchildren(): if child.tag == tag("source"): sources.append(parse_source_core(lookup, child)) if child.tag == tag("vertices"): assert vertices is None vertices = parse_vertices(lookup, child) if child.tag == tag("triangles"): primitive_elements.append(parse_triangles(lookup, child)) assert vertices is not None assert len(sources) >= 1 mesh = types.Mesh(sources, vertices, primitive_elements) return mesh def parse_geometry(lookup, root): id = root.attrib.get("id") name = root.attrib.get("name") geometric_element = None for child in root.getchildren(): if child.tag == tag("mesh"): assert geometric_element is None geometric_element = parse_mesh(lookup, child) assert geometric_element is not None geometry = types.Geometry(id, name, geometric_element) lookup_add(lookup, id, geometry) return geometry def parse_library_geometries(lookup, root): id = root.attrib.get("id") name = root.attrib.get("name") geometries = [] for child in root.getchildren(): if child.tag == tag("geometry"): geometries.append(parse_geometry(lookup, child)) assert len(geometries) >= 1 library_geometries = types.LibraryGeometries(id, name, geometries) lookup_add(lookup, id, library_geometries) return library_geometries def parse_light_color(lookup, root, *, cls): color = None for child in root.getchildren(): if child.tag == tag("color"): assert color is None color = tuple([float(i) for i in child.text.strip().split()]) assert color is not None return cls(color) parse_ambient = partial(parse_light_color, cls=types.Ambient) parse_directional = partial(parse_light_color, cls=types.Directional) parse_point = partial(parse_light_color, cls=types.Point) parse_spot = partial(parse_light_color, cls=types.Spot) def parse_technique_common_light(lookup, root): light = None for child in root.getchildren(): if child.tag == tag("ambient"): assert light is None light = parse_ambient(lookup, child) if child.tag == tag("directional"): assert light is None light = parse_directional(lookup, child) if child.tag == tag("point"): assert light is None light = parse_point(lookup, child) if child.tag == tag("spot"): assert light is None light = parse_spot(lookup, child) assert light is not None return types.TechniqueCommon_Light(light) def parse_light(lookup, root): id = root.attrib.get("id") name = root.attrib.get("name") technique_common = None for child in root.getchildren(): if child.tag == tag("technique_common"): assert technique_common is None technique_common = parse_technique_common_light(lookup, child) assert technique_common is not None light = types.Light(id, name, technique_common) lookup_add(lookup, id, light) return light def parse_library_lights(lookup, root): id = root.attrib.get("id") name = root.attrib.get("name") lights = [] for child in root.getchildren(): if child.tag == tag("light"): lights.append(parse_light(lookup, child)) assert len(lights) >= 1 library_lights = types.LibraryLights(id, name, lights) lookup_add(lookup, id, library_lights) return library_lights def parse_init_from(lookup, root): assert len(root.getchildren()) == 0 return types.InitFrom(root.text.strip()) def parse_image(lookup, root): id = root.attrib.get("id") name = root.attrib.get("name") format = root.attrib.get("format") height = root.attrib.get("height") width = root.attrib.get("width") depth = root.attrib.get("depth") image_source = None for child in root.getchildren(): if child.tag == tag("init_from"): assert image_source is None image_source = parse_init_from(lookup, child) assert image_source is not None image = types.Image(id, name, format, height, width, depth, image_source) lookup_add(lookup, id, image) return image def parse_library_images(lookup, root): id = root.attrib.get("id") name = root.attrib.get("name") images = [] for child in root.getchildren(): if child.tag == tag("image"): images.append(parse_image(lookup, child)) assert len(images) >= 1 library_images = types.LibraryImages(id, name, images) lookup_add(lookup, id, library_images) return library_images def parse_node_type(s): if s == "JOINT": return types.NodeType.JOINT if s == "NODE": return types.NodeType.NODE assert False, s def parse_lookat(lookup, sid_lookup, root): sid = root.attrib.get("sid") assert len(root.getchildren()) == 0 values = [float(i) for i in root.text.strip().split()] assert len(values) == 9 eye = tuple(values[0:3]) at = tuple(values[3:6]) up = tuple(values[6:9]) lookat = types.Lookat(sid, eye, at, up) lookup_add(sid_lookup, sid, lookat) return lookat def parse_matrix(lookup, sid_lookup, root): sid = root.attrib.get("sid") assert len(root.getchildren()) == 0 values = [float(i) for i in root.text.strip().split()] assert len(values) == 16 r0 = tuple(values[0:4]) r1 = tuple(values[4:8]) r2 = tuple(values[8:12]) r3 = tuple(values[12:16]) matrix = types.Matrix(sid, tuple([r0, r1, r2, r3])) lookup_add(sid_lookup, sid, matrix) return matrix def parse_simple_transform(lookup, sid_lookup, root, *, cls, count): sid = root.attrib.get("sid") assert len(root.getchildren()) == 0 values = [float(i) for i in root.text.strip().split()] assert len(values) == count inst = cls(sid, values) lookup_add(sid_lookup, sid, inst) return inst parse_rotate = partial(parse_simple_transform, cls=types.Rotate, count=4) parse_scale = partial(parse_simple_transform, cls=types.Scale, count=3) parse_skew = partial(parse_simple_transform, cls=types.Skew, count=7) parse_translate = partial(parse_simple_transform, cls=types.Translate, count=3) def parse_bind_vertex_input(lookup, root): semantic = root.attrib["semantic"] input_semantic = root.attrib["input_semantic"] input_set = root.attrib.get("input_set") return types.BindVertexInput(semantic, input_semantic, input_set) def parse_instance_material(lookup, sid_lookup, root): sid = root.attrib.get("sid") name = root.attrib.get("name") target = root.attrib["target"] symbol = root.attrib["symbol"] bind_vertex_inputs = [] for child in root.getchildren(): if child.tag == tag("bind_vertex_input"): bind_vertex_inputs.append(parse_bind_vertex_input(lookup, child)) inst = types.InstanceMaterial(sid, name, target, symbol, bind_vertex_inputs) lookup_add(sid_lookup, sid, inst) return inst def parse_technique_common_bind_material(lookup, root): materials = [] sid_lookup = {} for child in root.getchildren(): if child.tag == tag("instance_material"): materials.append(parse_instance_material(lookup, sid_lookup, child)) assert len(materials) >= 1 return types.TechniqueCommon_BindMaterial(materials, sid_lookup) def parse_bind_material(lookup, root): technique_common = None for child in root.getchildren(): if child.tag == tag("technique_common"): assert technique_common is None technique_common = parse_technique_common_bind_material(lookup, child) assert technique_common is not None return types.BindMaterial(technique_common) def parse_instance_geometry(lookup, sid_lookup, root): sid = root.attrib.get("sid") name = root.attrib.get("name") url = root.attrib["url"] bind_material = None # optional for child in root.getchildren(): if child.tag == tag("bind_material"): assert bind_material is None bind_material = parse_bind_material(lookup, child) # bind_material may be none instance_geometry = types.InstanceGeometry(sid, name, url, bind_material) lookup_add(sid_lookup, sid, instance_geometry) return instance_geometry def parse_instance_light(lookup, sid_lookup, root): sid = root.attrib.get("sid") name = root.attrib.get("name") url = root.attrib["url"] instance_light = types.InstanceLight(sid, name, url) lookup_add(sid_lookup, sid, instance_light) return instance_light def parse_node(lookup, sid_lookup, root): id = root.attrib.get("id") name = root.attrib.get("name") sid = root.attrib.get("sid") type = root.attrib["type"] if "type" in root.attrib else types.NodeType.NODE layer = root.attrib.get("layer", "").strip().split() transformation_elements = [] instance_geometries = [] instance_lights = [] nodes = [] child_sid_lookup = {} for child in root.getchildren(): if child.tag == tag("lookat"): transformation_elements.append(parse_lookat(lookup, child_sid_lookup, child)) if child.tag == tag("matrix"): transformation_elements.append(parse_matrix(lookup, child_sid_lookup, child)) if child.tag == tag("rotate"): transformation_elements.append(parse_rotate(lookup, child_sid_lookup, child)) if child.tag == tag("scale"): transformation_elements.append(parse_scale(lookup, child_sid_lookup, child)) if child.tag == tag("skew"): transformation_elements.append(parse_skew(lookup, child_sid_lookup, child)) if child.tag == tag("translate"): transformation_elements.append(parse_translate(lookup, child_sid_lookup, child)) if child.tag == tag("instance_geometry"): instance_geometries.append(parse_instance_geometry(lookup, child_sid_lookup, child)) if child.tag == tag("instance_light"): instance_lights.append(parse_instance_light(lookup, child_sid_lookup, child)) if child.tag == tag("node"): nodes.append(parse_node(lookup, child_sid_lookup, child)) node = types.Node(id, name, sid, type, layer, transformation_elements, instance_geometries, instance_lights, nodes, child_sid_lookup) lookup_add(lookup, id, node) lookup_add(sid_lookup, sid, node) return node def parse_visual_scene(lookup, root): id = root.attrib.get("id") name = root.attrib.get("name") sid_lookup = {} nodes = [] for child in root.getchildren(): if child.tag == tag("node"): nodes.append(parse_node(lookup, sid_lookup, child)) assert len(nodes) >= 1 visual_scenes = types.VisualScene(id, name, nodes, sid_lookup) lookup_add(lookup, id, visual_scenes) return visual_scenes def parse_library_visual_scenes(lookup, root): id = root.attrib.get("id") name = root.attrib.get("name") visual_scenes = [] for child in root.getchildren(): if child.tag == tag("visual_scene"): visual_scenes.append(parse_visual_scene(lookup, child)) assert len(visual_scenes) >= 1 library_visual_scenes = types.LibraryVisualScenes(id, name, visual_scenes) lookup_add(lookup, id, library_visual_scenes) return library_visual_scenes def parse_instance_visual_scene(lookup, sid_lookup, root): sid = root.attrib.get("sid") name = root.attrib.get("name") url = root.attrib["url"] instance_visual_scene = types.InstanceVisualScene(sid, name, url) lookup_add(sid_lookup, sid, instance_visual_scene) return instance_visual_scene def parse_scene(lookup, root): instance_visual_scene = None sid_lookup = {} for child in root.getchildren(): if child.tag == tag("instance_visual_scene"): instance_visual_scene = parse_instance_visual_scene(lookup, sid_lookup, child) # instance_visual_scene may be none return types.Scene(instance_visual_scene, sid_lookup) def parse_collada(tree): root = tree.getroot() assert root.tag == tag("COLLADA") collada = types.Collada() lookup = {} for child in root.getchildren(): if child.tag == tag("library_effects"): collada.library_effects.append(parse_library_effects(lookup, child)) if child.tag == tag("library_materials"): collada.library_materials.append(parse_library_materials(lookup, child)) if child.tag == tag("library_geometries"): collada.library_geometries.append(parse_library_geometries(lookup, child)) if child.tag == tag("library_lights"): collada.library_lights.append(parse_library_lights(lookup, child)) if child.tag == tag("library_images"): collada.library_images.append(parse_library_images(lookup, child)) if child.tag == tag("library_visual_scenes"): collada.library_visual_scenes.append(parse_library_visual_scenes(lookup, child)) if child.tag == tag("scene"): collada.scenes.append(parse_scene(lookup, child)) return collada collada = parse_collada(tree) from prettyprinter import pprint, install_extras install_extras(include=["dataclasses"]) pprint(collada, width=120)