I would like to import CityGML 1.0 or 2.0 into Blender. I got the CityGML of Berlin from https://www.citygml.org/3dcities/ and id should be with textures.
I found this script in a other question from 2015 (Import 3D XML in Blender or convert it with another tool). But the Script don't work for Blender 2.79 and i tried multiple Versions of .GML files.
Script:
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()) > 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')
I get this error when trying to import a GML-File:
#Importing citygml city
name: urn:ogc:def:crs,crs:EPSG:31467
importing 30 city objects (#:buildings, X:non-building)
Traceback (most recent call last):
File "/Text", line 199, in execute
File "/Text", line 141, in read_some_data2
AttributeError: 'NoneType' object has no attribute 'text'
Has anyone an idea how i can make this script work for Blender 2.79?