start of new blender render engine
This commit is contained in:
parent
d90c9b8a10
commit
6ed1d2a085
5 changed files with 120 additions and 13 deletions
|
@ -2,6 +2,7 @@ from django.conf import settings
|
||||||
from django.core import checks
|
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.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.openscad import OpenSCADEngine # noqa
|
||||||
from c3nav.mapdata.render.engines.wavefront import WavefrontEngine # noqa
|
from c3nav.mapdata.render.engines.wavefront import WavefrontEngine # noqa
|
||||||
from c3nav.mapdata.render.engines.stl import STLEngine # noqa
|
from c3nav.mapdata.render.engines.stl import STLEngine # noqa
|
||||||
|
|
87
src/c3nav/mapdata/render/engines/blender.py
Normal file
87
src/c3nav/mapdata/render/engines/blender.py
Normal file
|
@ -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()
|
|
@ -53,6 +53,9 @@ class LevelGeometries:
|
||||||
self.door_height = None
|
self.door_height = None
|
||||||
self.min_altitude = None
|
self.min_altitude = None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<LevelGeometries for Level %s (#%d)>' % (self.short_label, self.pk)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def build_for_level(cls, level, altitudeareas_above):
|
def build_for_level(cls, level, altitudeareas_above):
|
||||||
geoms = LevelGeometries()
|
geoms = LevelGeometries()
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from itertools import chain
|
|
||||||
|
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from shapely import prepared
|
from shapely import prepared
|
||||||
from shapely.geometry import box
|
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.engines.base import FillAttribs, StrokeAttribs
|
||||||
from c3nav.mapdata.render.geometry import hybrid_union
|
from c3nav.mapdata.render.geometry import hybrid_union
|
||||||
from c3nav.mapdata.render.renderdata import LevelRenderData
|
from c3nav.mapdata.render.renderdata import LevelRenderData
|
||||||
|
from c3nav.mapdata.render.utils import get_full_levels, get_min_altitude
|
||||||
|
|
||||||
|
|
||||||
class MapRenderer:
|
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),
|
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)
|
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:
|
if self.full_levels:
|
||||||
levels = tuple(chain(*(
|
levels = get_full_levels(level_render_data)
|
||||||
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
|
|
||||||
)))
|
|
||||||
else:
|
else:
|
||||||
levels = level_render_data.levels
|
levels = level_render_data.levels
|
||||||
|
|
||||||
min_altitude = min(chain(*(tuple(area.altitude for area in geoms.altitudeareas)
|
min_altitude = get_min_altitude(levels, default=level_render_data.base_altitude)
|
||||||
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)
|
|
||||||
|
|
||||||
not_full_levels = engine.is_3d # always do non-full-levels until after the first primary level
|
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
|
full_levels = self.full_levels and engine.is_3d
|
||||||
|
|
22
src/c3nav/mapdata/render/utils.py
Normal file
22
src/c3nav/mapdata/render/utils.py
Normal file
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue