Import 3D XML in Blender or convert it with another tool
Import Citygml Model


Textures applied to Zuidplein model.

I've merged all the scripts into 1 for convenience, would envisage splitting them up if made into an addon.

import bpy

from mathutils import Vector

##### Refactor to own file citygml.py
class Texture:
    def __init__(self, id=None, path="", map=""):
        def unflatten(coords):
            return [Vector(float(x) for x in coords[i:i+2]) for i in range(0, len(coords), 2)]        
        self.id = id
        self.path = path
        self.map = unflatten(map.split())

class PolyGon:
    def __init__(self, id=None, pts=""):
        def unflatten(coords):
            return [Vector(float(x) for x in coords[i:i+3]) for i in range(0, len(coords), 3)]
        self.id = id
        self.verts = unflatten(pts.split())

class Building:
    def __init__(self, id="", name="", surfaces={}, textures={}):
        self.id = id
        self.name = name
        self.surfaces = {s.id: s for s in surfaces}
        self.textures = {t.id: t for t in textures}

class CityModel:

    def __init__(self, name="", upper_corner="", lower_corner=""):
        self.lower_corner = Vector(float(x) for x in lower_corner.split())
        self.upper_corner = Vector(float(x) for x in upper_corner.split())
        self.name = name
        self.buildings = {}
        self.images = {}

    def add_building(self, building):
        self.buildings[building.id] = building

    def build(self, scene, filepath, scale):
        import bmesh
        import os

        for k, b in self.buildings.items():
            keep_verts = []
            bm = bmesh.new()
            bm.verts.ensure_lookup_table()
            mesh = bpy.data.meshes.new(b.id)

            for sid, s in b.surfaces.items():
                verts = [bm.verts.new(scale * (v - self.lower_corner)) for v in s.verts]
                face = bm.faces.new(verts)
                texture = b.textures.get("#%s" % s.id)

                if texture:                    
                    uv_layer = bm.loops.layers.uv.verify()
                    tex = bm.faces.layers.tex.verify()
                    bm.faces.ensure_lookup_table()
                    for i,l in enumerate(bm.faces[-1].loops):
                        uv = l[uv_layer].uv
                        (uv.x, uv.y) = texture.map[i]

                    path = os.path.join(filepath, texture.path).replace("\\", "/")
                    image = bpy.data.images.get(os.path.basename(path))
                    if not image:
                        image = bpy.data.images.load(path)
                        image.use_fake_user = True  

                    bm.faces[-1][tex].image = image  
                    dubs = bmesh.ops.find_doubles(bm,
                              verts=verts,
                              dist=0.001)['targetmap']
                    if len(dubs.keys()) &gt; 2:
                        keep_verts.extend(dubs.values())
            verts = list(set(bm.verts) - set(keep_verts))
            bmesh.ops.remove_doubles(bm, verts=verts, dist=0.001)
            #bmesh.ops.automerge(bm, verts=bm.verts, dist=0.000001)
            bm.to_mesh(mesh)
            building = bpy.data.objects.new("building", mesh)
            scene.objects.link(building)
            bm.free()

def read_some_data2(context, filepath, directory, use_some_setting, scale):
    scene = context.scene
    from xml.etree import ElementTree as ET
    cityxml = ET.parse(filepath)
    citymodelnode = cityxml.getroot()
    print("Importing citygml city")
    namespaces = {
           "citygml":"http://www.opengis.net/citygml/1.0",
           "core":"http://www.opengis.net/citygml/base/1.0" ,
           "tex":"http://www.opengis.net/citygml/textures/1.0",
           "gml":"http://www.opengis.net/gml",
           "bldg":"http://www.opengis.net/citygml/building/1.0", 
           "app":"http://www.opengis.net/citygml/appearance/1.0", 
           "dem":"http://www.opengis.net/citygml/relief/1.0",
           "tran":"http://www.opengis.net/citygml/transportation/1.0",
           "gen":"http://www.opengis.net/citygml/generics/1.0",
           "frn":"http://www.opengis.net/citygml/cityfurniture/1.0", 
           "wtr":"http://www.opengis.net/citygml/waterbody/1.0", 
           "luse":"http://www.opengis.net/citygml/landuse/1.0", 
           "veg":"http://www.opengis.net/citygml/vegetation/1.0", 
           "xAL":"urn:oasis:names:tc:ciq:xsdschema:xAL:2.0",
           }
    '''
    for prefix, uri in namespaces.items():
        ET.register_namespace(prefix, uri)            
    '''
    bbox = cityxml.find("gml:boundedBy", namespaces=namespaces)

    name = bbox.find(".//gml:Envelope", namespaces=namespaces).attrib["srsName"]
    print("name: %s" % name)
    uc = bbox.find(".//gml:upperCorner", namespaces=namespaces).text
    lc = bbox.find(".//gml:lowerCorner", namespaces=namespaces).text

    city = CityModel(name=name, upper_corner=uc, lower_corner=lc)

    cityobjects = cityxml.findall("citygml:cityObjectMember",
                                namespaces=namespaces)

    print("importing %d city objects (#:buildings, X:non-building)" % len(cityobjects))

    for cityobject in cityobjects:

        building = cityobject.find("bldg:Building", namespaces=namespaces)
        if building is None:
            # ugly hack do for now.
            groundsurface = cityobject.find("bldg:GroundSurface", namespaces=namespaces)
            print("X", end="")
            if groundsurface is None:
                continue
            building = groundsurface
        else:

            print("#", end="")
        building_id = building.attrib["{%s}id" % namespaces["gml"]]

        rings = cityobject.findall(".//gml:Polygon//gml:LinearRing", namespaces=namespaces)
        surfaces = []
        for ring in rings:
            ring_id = ring.attrib["{%s}id" % namespaces["gml"]]
            poslist = ring.find("gml:posList", namespaces=namespaces).text
            surfaces.append(PolyGon(id=ring_id, pts=poslist))

        textures = []
        sdms = cityobject.findall(".//app:surfaceDataMember", namespaces=namespaces)
        for sdm in sdms:
            coords = sdm.find(".//app:textureCoordinates", namespaces=namespaces)
            if coords is None:
                continue
            sdm_ring = coords.attrib["ring"]
            path = r"%s" % sdm.find(".//app:imageURI", namespaces=namespaces).text
            textures.append(Texture(id=sdm_ring, map=coords.text, path=path))

        building = Building(id=building_id, surfaces=surfaces, textures=textures)
        city.add_building(building)
    print()
    print("finished importing, building...")
    city.build(scene, directory, scale)
    print("done")
    return {'FINISHED'}    

from bpy_extras.io_utils import ImportHelper
from bpy.props import StringProperty, BoolProperty, FloatProperty
from bpy.types import Operator

class ImportCityGML(Operator, ImportHelper):
    """Import CityGML"""
    bl_idname = "import_dem.city_gml"  # important since its how bpy.ops.import_test.some_data is constructed
    bl_label = "Load City"

    # ImportHelper mixin class uses this
    filename_ext = ".xml"

    filter_glob = StringProperty(
            default="*.xml",
            options={'HIDDEN'},
            )
    directory = StringProperty()
    filepath = StringProperty(name="File Path", description="Filepath used for importing txt files", maxlen= 1024, default= "")

    use_setting = BoolProperty(
            name="Save to Text Editor",
            description="Make a copy of generated script in text editor",
            default=False,
            )
    scale = FloatProperty(
            name="Scale",
            description="Scale the model",
            default=0.05,
            min=0.01,
            max=10.0
            )

    def draw(self, context):
        layout = self.layout
        layout.prop(self, "scale")

    def execute(self, context):
        return read_some_data2(context, self.filepath, self.directory, self.use_setting, self.scale)


# Only needed if you want to add into a dynamic menu
def menu_func_import(self, context):
    self.layout.operator(ImportCityGML.bl_idname, text="Import CityGML")


def register():
    bpy.utils.register_class(ImportCityGML)
    bpy.types.INFO_MT_file_import.append(menu_func_import)


def unregister():
    bpy.utils.unregister_class(ImportCityGML)
    bpy.types.INFO_MT_file_import.remove(menu_func_import)


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.import_dem.city_gml('INVOKE_DEFAULT')


3-28-WITTE-DORP.xml

 

Witt Dorp with textures.

EDIT:

This is a non lxml version and works with the xml.etree version from blender.  The remove doubles code from Removing doubles on imported 3DXML ngon mesh has been added which greatly reduces the amount of verts.  Might post the lxml version on my github page if anyone is interested.