optionally group geometries in engine to beautify openscad export
This commit is contained in:
parent
1c91c9a6db
commit
13f4f5164b
7 changed files with 67 additions and 32 deletions
|
@ -65,8 +65,11 @@ class RenderEngine(ABC):
|
|||
return (*(i/255 for i in color[:3]), color[3] if alpha is None else alpha)
|
||||
raise ValueError('invalid color string!')
|
||||
|
||||
def add_group(self, group):
|
||||
pass
|
||||
|
||||
def add_geometry(self, geometry, fill: Optional[FillAttribs] = None, stroke: Optional[StrokeAttribs] = None,
|
||||
altitude=None, height=None, shape_cache_key=None):
|
||||
altitude=None, height=None, shape_cache_key=None, category=None):
|
||||
# draw a shapely geometry with a given style
|
||||
# altitude is the absolute altitude of the upper bound of the element
|
||||
# height is the height of the element
|
||||
|
@ -77,7 +80,7 @@ class RenderEngine(ABC):
|
|||
return
|
||||
|
||||
self._add_geometry(geometry=geometry, fill=fill, stroke=stroke,
|
||||
altitude=altitude, height=height, shape_cache_key=shape_cache_key)
|
||||
altitude=altitude, height=height, shape_cache_key=shape_cache_key, category=category)
|
||||
|
||||
@abstractmethod
|
||||
def _add_geometry(self, geometry, fill: Optional[FillAttribs], stroke: Optional[StrokeAttribs],
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from collections import OrderedDict
|
||||
from itertools import chain
|
||||
from typing import Optional
|
||||
|
||||
|
@ -14,7 +15,9 @@ class Base3DEngine(RenderEngine):
|
|||
def __init__(self, *args, center=True, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.vertices = []
|
||||
self._current_group = None
|
||||
self.groups = OrderedDict()
|
||||
self.vertices = OrderedDict()
|
||||
|
||||
self.np_scale = np.array((self.scale, self.scale, self.scale))
|
||||
self.np_offset = np.array((-self.minx * self.scale, -self.miny * self.scale, 0))
|
||||
|
@ -23,9 +26,17 @@ class Base3DEngine(RenderEngine):
|
|||
(self.miny - self.maxy) * self.scale / 2,
|
||||
0))
|
||||
|
||||
def _add_geometry(self, geometry, fill: Optional[FillAttribs], stroke: Optional[StrokeAttribs], **kwargs):
|
||||
def add_group(self, group):
|
||||
self._current_group = group
|
||||
self.groups.setdefault(group, [])
|
||||
|
||||
def _add_geometry(self, geometry, fill: Optional[FillAttribs], stroke: Optional[StrokeAttribs], category=None,
|
||||
**kwargs):
|
||||
if fill is not None:
|
||||
self.vertices.append(self._place_geometry(geometry))
|
||||
key = '%s_%s' % (self._current_group, category)
|
||||
if key not in self.vertices:
|
||||
self.groups[self._current_group].append(key)
|
||||
self.vertices.setdefault(key, []).append(self._place_geometry(geometry))
|
||||
|
||||
@staticmethod
|
||||
def _append_to_vertices(vertices, append=None):
|
||||
|
|
|
@ -156,6 +156,8 @@ class OpenGLEngine(Base3DEngine):
|
|||
self.gl_scale = (scale_x, -scale_y, scale_z)
|
||||
self.gl_offset = (-self.minx * scale_x - 1, self.maxy * scale_y - 1, 0)
|
||||
|
||||
self.vertices = []
|
||||
|
||||
def _add_geometry(self, geometry, fill: Optional[FillAttribs], stroke: Optional[StrokeAttribs], **kwargs):
|
||||
if fill is not None:
|
||||
self.vertices.append(self._place_geometry(geometry, self.color_to_rgb(fill.color), offset=False))
|
||||
|
|
|
@ -8,21 +8,34 @@ from c3nav.mapdata.render.engines.base3d import Base3DEngine
|
|||
class OpenSCADEngine(Base3DEngine):
|
||||
filetype = 'scad'
|
||||
|
||||
def render(self) -> bytes:
|
||||
facets = np.vstack(self.vertices)
|
||||
result = b''
|
||||
for facets in self.vertices:
|
||||
vertices = tuple(set(tuple(vertex) for vertex in facets.reshape((-1, 3))))
|
||||
lookup = {vertex: i for i, vertex in enumerate(vertices)}
|
||||
def _create_polyhedron(self, name, vertices):
|
||||
facets = np.vstack(vertices)
|
||||
vertices = tuple(set(tuple(vertex) for vertex in facets.reshape((-1, 3))))
|
||||
lookup = {vertex: i for i, vertex in enumerate(vertices)}
|
||||
|
||||
result += (b'polyhedron(\n' +
|
||||
b' points = [\n' +
|
||||
b'\n'.join((b' [%.3f, %.3f, %.3f],' % tuple(vertex)) for vertex in vertices) + b'\n' +
|
||||
b' ],\n' +
|
||||
b' faces = [\n' +
|
||||
b'\n'.join((b' [%d, %d, %d],' % (lookup[tuple(a)], lookup[tuple(b)], lookup[tuple(c)]))
|
||||
for a, b, c in facets) + b'\n' +
|
||||
b' ],\n' +
|
||||
b' convexity = 10\n' +
|
||||
b');\n')
|
||||
return (b'module ' + name.encode() + b'() {\n' +
|
||||
b' polyhedron(\n' +
|
||||
b' points = [\n' +
|
||||
b'\n'.join((b' [%.3f, %.3f, %.3f],' % tuple(vertex)) for vertex in vertices) + b'\n' +
|
||||
b' ],\n' +
|
||||
b' faces = [\n' +
|
||||
b'\n'.join((b' [%d, %d, %d],' % (lookup[tuple(a)], lookup[tuple(b)], lookup[tuple(c)]))
|
||||
for a, b, c in facets) + b'\n' +
|
||||
b' ],\n' +
|
||||
b' convexity = 10\n' +
|
||||
b' );\n'
|
||||
b'}\n')
|
||||
|
||||
def render(self) -> bytes:
|
||||
result = (b'scale([100, 100, 100]) c3nav_export();\n\n' +
|
||||
b'module c3nav_export() {\n' +
|
||||
b'\n'.join((b' %s();' % group.encode()) for group in self.groups.keys()) + b'\n' +
|
||||
b'}\n\n')
|
||||
for group, subgroups in self.groups.items():
|
||||
result += (b'module ' + group.encode() + b'() {\n' +
|
||||
b'\n'.join((b' %s();' % subgroup.encode()) for subgroup in subgroups) + b'\n' +
|
||||
b'}\n')
|
||||
result += b'\n'
|
||||
for group, vertices in self.vertices.items():
|
||||
result += self._create_polyhedron(group, vertices)
|
||||
return result
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from itertools import chain
|
||||
|
||||
import numpy as np
|
||||
|
||||
from c3nav.mapdata.render.engines import register_engine
|
||||
|
@ -20,7 +22,7 @@ class STLEngine(Base3DEngine):
|
|||
return self.facet_template % tuple(facet.flatten())
|
||||
|
||||
def render(self) -> bytes:
|
||||
facets = np.vstack(self.vertices)
|
||||
facets = np.vstack(chain(*self.vertices.values()))
|
||||
facets = np.hstack((np.cross(facets[:, 1]-facets[:, 0], facets[:, 2]-facets[:, 1]).reshape((-1, 1, 3)),
|
||||
facets))
|
||||
return (b'solid c3nav_export\n' +
|
||||
|
|
|
@ -215,7 +215,7 @@ class SVGEngine(RenderEngine):
|
|||
self.altitudes[new_altitude] = new_geometry
|
||||
|
||||
def _add_geometry(self, geometry, fill: Optional[FillAttribs], stroke: Optional[StrokeAttribs],
|
||||
altitude=None, height=None, shape_cache_key=None):
|
||||
altitude=None, height=None, shape_cache_key=None, category=None):
|
||||
geometry = self.buffered_bbox.intersection(geometry.geom)
|
||||
|
||||
if geometry.is_empty:
|
||||
|
|
|
@ -90,6 +90,8 @@ class MapRenderer:
|
|||
if not bbox.intersects(geoms.affected_area):
|
||||
continue
|
||||
|
||||
engine.add_group('level_%s' % geoms.pk)
|
||||
|
||||
# hide indoor and outdoor rooms if their access restriction was not unlocked
|
||||
add_walls = hybrid_union(tuple(area for access_restriction, area in geoms.restricted_spaces_indoors.items()
|
||||
if access_restriction not in unlocked_access_restrictions))
|
||||
|
@ -99,17 +101,17 @@ class MapRenderer:
|
|||
).union(add_walls)
|
||||
|
||||
if not self.full_levels and engine.is_3d:
|
||||
engine.add_geometry(geoms.walls_base, fill=FillAttribs('#aaaaaa'))
|
||||
engine.add_geometry(geoms.walls_base, fill=FillAttribs('#aaaaaa'), category='walls')
|
||||
if min_altitude < geoms.min_altitude:
|
||||
engine.add_geometry(geoms.walls_bottom.fit(scale=geoms.min_altitude-min_altitude,
|
||||
offset=min_altitude),
|
||||
fill=FillAttribs('#aaaaaa'))
|
||||
fill=FillAttribs('#aaaaaa'), category='walls')
|
||||
for altitudearea in geoms.altitudeareas:
|
||||
bottom = altitudearea.altitude - int(0.7 * 1000)
|
||||
scale = (bottom - min_altitude) / int(0.7 * 1000)
|
||||
offset = min_altitude - bottom * scale
|
||||
engine.add_geometry(altitudearea.geometry.fit(scale=scale, offset=offset).filter(top=False),
|
||||
fill=FillAttribs('#aaaaaa'))
|
||||
fill=FillAttribs('#aaaaaa'), category='ground')
|
||||
|
||||
# render altitude areas in default ground color and add ground colors to each one afterwards
|
||||
# shadows are directly calculated and added by the engine
|
||||
|
@ -117,14 +119,15 @@ class MapRenderer:
|
|||
geometry = altitudearea.geometry.difference(crop_areas)
|
||||
if not self.full_levels and engine.is_3d:
|
||||
geometry = geometry.filter(bottom=False)
|
||||
engine.add_geometry(geometry, altitude=altitudearea.altitude, fill=FillAttribs('#eeeeee'))
|
||||
engine.add_geometry(geometry, altitude=altitudearea.altitude, fill=FillAttribs('#eeeeee'),
|
||||
category='ground')
|
||||
|
||||
for color, areas in altitudearea.colors.items():
|
||||
# only select ground colors if their access restriction is unlocked
|
||||
areas = tuple(area for access_restriction, area in areas.items()
|
||||
if access_restriction in unlocked_access_restrictions)
|
||||
if areas:
|
||||
engine.add_geometry(hybrid_union(areas), fill=FillAttribs(color))
|
||||
engine.add_geometry(hybrid_union(areas), fill=FillAttribs(color), category='ground')
|
||||
|
||||
# add walls, stroke_px makes sure that all walls are at least 1px thick on all zoom levels,
|
||||
walls = None
|
||||
|
@ -133,16 +136,17 @@ class MapRenderer:
|
|||
|
||||
if walls is not None:
|
||||
engine.add_geometry(walls.filter(bottom=(self.full_levels or not engine.is_3d)),
|
||||
height=geoms.default_height, fill=FillAttribs('#aaaaaa'))
|
||||
height=geoms.default_height, fill=FillAttribs('#aaaaaa'), category='walls')
|
||||
|
||||
if geoms.walls_extended and self.full_levels and engine.is_3d:
|
||||
engine.add_geometry(geoms.walls_extended, height=geoms.default_height, fill=FillAttribs('#aaaaaa'))
|
||||
engine.add_geometry(geoms.walls_extended, height=geoms.default_height, fill=FillAttribs('#aaaaaa'),
|
||||
category='walls')
|
||||
|
||||
if not geoms.doors.is_empty:
|
||||
engine.add_geometry(geoms.doors.difference(add_walls), fill=FillAttribs('#ffffff'),
|
||||
stroke=StrokeAttribs('#ffffff', 0.05, min_px=0.2))
|
||||
stroke=StrokeAttribs('#ffffff', 0.05, min_px=0.2), category='doors')
|
||||
|
||||
if walls is not None:
|
||||
engine.add_geometry(walls, stroke=StrokeAttribs('#666666', 0.05, min_px=0.2))
|
||||
engine.add_geometry(walls, stroke=StrokeAttribs('#666666', 0.05, min_px=0.2), category='walls')
|
||||
|
||||
return engine
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue