From 6ed1d2a085a2f1be71c532550784b0513a0ef074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Mon, 3 Dec 2018 23:39:28 +0100 Subject: [PATCH] start of new blender render engine --- src/c3nav/mapdata/render/engines/__init__.py | 1 + src/c3nav/mapdata/render/engines/blender.py | 87 ++++++++++++++++++++ src/c3nav/mapdata/render/geometry/level.py | 3 + src/c3nav/mapdata/render/renderer.py | 20 ++--- src/c3nav/mapdata/render/utils.py | 22 +++++ 5 files changed, 120 insertions(+), 13 deletions(-) create mode 100644 src/c3nav/mapdata/render/engines/blender.py create mode 100644 src/c3nav/mapdata/render/utils.py diff --git a/src/c3nav/mapdata/render/engines/__init__.py b/src/c3nav/mapdata/render/engines/__init__.py index 227fdadc..8f5b514e 100644 --- a/src/c3nav/mapdata/render/engines/__init__.py +++ b/src/c3nav/mapdata/render/engines/__init__.py @@ -2,6 +2,7 @@ from django.conf import settings from django.core import checks from c3nav.mapdata.render.engines.base import register_engine, get_engine, get_engine_filetypes # noqa +from c3nav.mapdata.render.engines.blender import BlenderEngine # noqa from c3nav.mapdata.render.engines.openscad import OpenSCADEngine # noqa from c3nav.mapdata.render.engines.wavefront import WavefrontEngine # noqa from c3nav.mapdata.render.engines.stl import STLEngine # noqa diff --git a/src/c3nav/mapdata/render/engines/blender.py b/src/c3nav/mapdata/render/engines/blender.py new file mode 100644 index 00000000..dc779af4 --- /dev/null +++ b/src/c3nav/mapdata/render/engines/blender.py @@ -0,0 +1,87 @@ +import re + +from shapely.ops import unary_union + +from c3nav.mapdata.render.engines import register_engine +from c3nav.mapdata.render.engines.base3d import Base3DEngine +from c3nav.mapdata.render.utils import get_full_levels, get_min_altitude +from c3nav.mapdata.utils.geometry import assert_multipolygon + + +@register_engine +class BlenderEngine(Base3DEngine): + filetype = 'blend.py' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.result = '' + self._add_python(''' + def add_polygon(exterior, interiors, minz, maxz): + add_ring(exterior, minz, maxz) + + def add_ring(coords, minz, maxz): + if coords[0] == coords[-1]: + coords = coords[:-1] + if len(coords) < 3: + raise ValueError('Ring with less than 3 points.') + indices = tuple(range(len(coords))) + mesh = bpy.data.meshes.new(name='Test') + mesh.from_pydata( + tuple((x, y, minz) for x, y in coords), + tuple(zip(indices, indices[1:]+(0, ))), + (indices, ), + ) + obj = bpy.data.objects.new('Test', mesh) + scene = bpy.context.scene + scene.objects.link(obj) + return obj + ''') + + def _clean_python(self, code): + if '\t' in code: + raise ValueError('Tabulators in code') + code = re.sub(r'^( *\n)*', '', code) + whitespaces = re.match('^ *', code) + code = re.sub(r'^%s' % whitespaces.group(0), '', code, flags=re.MULTILINE) + code = re.sub(r'^ +$', '', code, flags=re.MULTILINE) + code = re.sub(r' +$', '', code) + return code + + def _add_python(self, code): + self.result += self._clean_python(code)+'\n' + + def custom_render(self, level_render_data, bbox, access_permissions): + levels = get_full_levels(level_render_data) + min_altitude = get_min_altitude(levels, default=level_render_data.base_altitude) + print(min_altitude) + for level in levels: + print(level) + + for geoms in levels: + # hide indoor and outdoor rooms if their access restriction was not unlocked + restricted_spaces_indoors = unary_union( + tuple(area.geom for access_restriction, area in geoms.restricted_spaces_indoors.items() + if access_restriction not in access_permissions) + ) + restricted_spaces_outdoors = unary_union( + tuple(area.geom for access_restriction, area in geoms.restricted_spaces_outdoors.items() + if access_restriction not in access_permissions) + ) + restricted_spaces = unary_union((restricted_spaces_indoors, restricted_spaces_outdoors)) # noqa + + for altitudearea in geoms.altitudeareas: + self._add_polygon(altitudearea.geometry.geom) + break + + break + + def _add_polygon(self, geometry): + for polygon in assert_multipolygon(geometry): + self._add_python('add_polygon(exterior=%(exterior)r, interiors=%(interiors)r, minz=0, maxz=1)' % { + 'exterior': tuple(polygon.exterior.coords), + 'interiors': tuple(tuple(interior.coords) for interior in polygon.interiors), + }) + continue + + def render(self, filename=None): + return self.result.encode() diff --git a/src/c3nav/mapdata/render/geometry/level.py b/src/c3nav/mapdata/render/geometry/level.py index 6f70c8fd..583a0d83 100644 --- a/src/c3nav/mapdata/render/geometry/level.py +++ b/src/c3nav/mapdata/render/geometry/level.py @@ -53,6 +53,9 @@ class LevelGeometries: self.door_height = None self.min_altitude = None + def __repr__(self): + return '' % (self.short_label, self.pk) + @classmethod def build_for_level(cls, level, altitudeareas_above): geoms = LevelGeometries() diff --git a/src/c3nav/mapdata/render/renderer.py b/src/c3nav/mapdata/render/renderer.py index 3a527cda..697c9b3a 100644 --- a/src/c3nav/mapdata/render/renderer.py +++ b/src/c3nav/mapdata/render/renderer.py @@ -1,5 +1,3 @@ -from itertools import chain - from django.utils.functional import cached_property from shapely import prepared from shapely.geometry import box @@ -8,6 +6,7 @@ from c3nav.mapdata.models import Level from c3nav.mapdata.render.engines.base import FillAttribs, StrokeAttribs from c3nav.mapdata.render.geometry import hybrid_union from c3nav.mapdata.render.renderdata import LevelRenderData +from c3nav.mapdata.render.utils import get_full_levels, get_min_altitude class MapRenderer: @@ -39,21 +38,16 @@ class MapRenderer: engine = engine_cls(self.width, self.height, self.minx, self.miny, float(level_render_data.base_altitude), scale=self.scale, buffer=1, background='#DCDCDC', center=center) + if hasattr(engine, 'custom_render'): + engine.custom_render(level_render_data, bbox, access_permissions) + return engine + if self.full_levels: - levels = tuple(chain(*( - tuple(sublevel for sublevel in LevelRenderData.get(level.pk).levels - if sublevel.pk == level.pk or sublevel.on_top_of_id == level.pk) - for level in level_render_data.levels if level.on_top_of_id is None - ))) + levels = get_full_levels(level_render_data) else: levels = level_render_data.levels - min_altitude = min(chain(*(tuple(area.altitude for area in geoms.altitudeareas) - for geoms in levels)), - default=None) - if min_altitude is None: - min_altitude = min(tuple(geoms.base_altitude for geoms in levels), - default=level_render_data.base_altitude) + min_altitude = get_min_altitude(levels, default=level_render_data.base_altitude) not_full_levels = engine.is_3d # always do non-full-levels until after the first primary level full_levels = self.full_levels and engine.is_3d diff --git a/src/c3nav/mapdata/render/utils.py b/src/c3nav/mapdata/render/utils.py new file mode 100644 index 00000000..653f1e8d --- /dev/null +++ b/src/c3nav/mapdata/render/utils.py @@ -0,0 +1,22 @@ +from itertools import chain + +from c3nav.mapdata.render.renderdata import LevelRenderData + + +def get_min_altitude(levels, default): + min_altitude = min(chain(*(tuple(area.altitude for area in geoms.altitudeareas) + for geoms in levels)), + default=None) + if min_altitude is None: + min_altitude = min(tuple(geoms.base_altitude for geoms in levels), + default=default) + return min_altitude + + +def get_full_levels(level_render_data): + levels = tuple(chain(*( + tuple(sublevel for sublevel in LevelRenderData.get(level.pk).levels + if sublevel.pk == level.pk or sublevel.on_top_of_id == level.pk) + for level in level_render_data.levels if level.on_top_of_id is None + ))) + return levels