From b76f35a4a5007b6de15cd183d131e6774829db7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Tue, 14 Nov 2017 20:53:04 +0100 Subject: [PATCH] wavefront obj export --- .../mapdata/management/commands/rendermap.py | 13 +++- src/c3nav/mapdata/render/engines/__init__.py | 1 + src/c3nav/mapdata/render/engines/base.py | 2 +- src/c3nav/mapdata/render/engines/opengl.py | 2 +- src/c3nav/mapdata/render/engines/openscad.py | 2 +- src/c3nav/mapdata/render/engines/stl.py | 2 +- src/c3nav/mapdata/render/engines/svg.py | 2 +- src/c3nav/mapdata/render/engines/wavefront.py | 63 +++++++++++++++++++ 8 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 src/c3nav/mapdata/render/engines/wavefront.py diff --git a/src/c3nav/mapdata/management/commands/rendermap.py b/src/c3nav/mapdata/management/commands/rendermap.py index fa1dec78..ddd772af 100644 --- a/src/c3nav/mapdata/management/commands/rendermap.py +++ b/src/c3nav/mapdata/management/commands/rendermap.py @@ -67,10 +67,19 @@ class Command(BaseCommand): renderer = MapRenderer(level.pk, minx, miny, maxx, maxy, access_permissions=options['permissions'], full_levels=options['full_levels']) - stl = renderer.render(get_engine(options['filetype']), center=not options['no_center']) - data = stl.render() filename = os.path.join(settings.RENDER_ROOT, 'level_%s_%s.%s' % (level.short_label, renderer.access_cache_key.replace('_', '-'), options['filetype'])) + + render = renderer.render(get_engine(options['filetype']), center=not options['no_center']) + data = render.render(filename) + if isinstance(data, tuple): + other_data = data[1:] + data = data[0] + else: + other_data = () + open(filename, 'wb').write(data) + for filename, data in other_data: + open(filename, 'wb').write(data) diff --git a/src/c3nav/mapdata/render/engines/__init__.py b/src/c3nav/mapdata/render/engines/__init__.py index c8d982f4..227fdadc 100644 --- a/src/c3nav/mapdata/render/engines/__init__.py +++ b/src/c3nav/mapdata/render/engines/__init__.py @@ -3,6 +3,7 @@ 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.openscad import OpenSCADEngine # noqa +from c3nav.mapdata.render.engines.wavefront import WavefrontEngine # noqa from c3nav.mapdata.render.engines.stl import STLEngine # noqa from c3nav.mapdata.render.engines.svg import SVGEngine # noqa diff --git a/src/c3nav/mapdata/render/engines/base.py b/src/c3nav/mapdata/render/engines/base.py index 94b04181..2625403c 100644 --- a/src/c3nav/mapdata/render/engines/base.py +++ b/src/c3nav/mapdata/render/engines/base.py @@ -51,7 +51,7 @@ class RenderEngine(ABC): self.background_rgb = tuple(int(background[i:i + 2], 16)/255 for i in range(1, 6, 2)) @abstractmethod - def render(self) -> bytes: + def render(self, filename=None) -> bytes: # render the image to png. pass diff --git a/src/c3nav/mapdata/render/engines/opengl.py b/src/c3nav/mapdata/render/engines/opengl.py index 2f4efcb1..722e53e9 100644 --- a/src/c3nav/mapdata/render/engines/opengl.py +++ b/src/c3nav/mapdata/render/engines/opengl.py @@ -196,7 +196,7 @@ class OpenGLEngine(Base3DEngine): worker = OpenGLWorker() - def render(self) -> bytes: + def render(self, filename=None) -> bytes: return self.worker.render(self.width, self.height, self.gl_scale, self.gl_offset, self.background_rgb, np.vstack(self.vertices).astype(np.float32).tobytes() if self.vertices else b'') diff --git a/src/c3nav/mapdata/render/engines/openscad.py b/src/c3nav/mapdata/render/engines/openscad.py index 63263b07..07b77cd9 100644 --- a/src/c3nav/mapdata/render/engines/openscad.py +++ b/src/c3nav/mapdata/render/engines/openscad.py @@ -28,7 +28,7 @@ class OpenSCADEngine(Base3DEngine): b' convexity = 10\n' + b' );') - def render(self) -> bytes: + def render(self, filename=None) -> bytes: result = (b'c3nav_export();\n\n' + b'module c3nav_export() {\n' + b'\n'.join((b' %s();' % group.replace('-', 'minus').encode()) diff --git a/src/c3nav/mapdata/render/engines/stl.py b/src/c3nav/mapdata/render/engines/stl.py index 114ec9cf..ef61abd4 100644 --- a/src/c3nav/mapdata/render/engines/stl.py +++ b/src/c3nav/mapdata/render/engines/stl.py @@ -21,7 +21,7 @@ class STLEngine(Base3DEngine): def _create_facet(self, facet) -> bytes: return self.facet_template % tuple(facet.flatten()) - def render(self) -> bytes: + def render(self, filename=None) -> bytes: facets = np.vstack(chain(*(chain(*v.values()) for v in self.vertices.values()))) facets = np.hstack((np.cross(facets[:, 1]-facets[:, 0], facets[:, 2]-facets[:, 1]).reshape((-1, 1, 3)), facets)) diff --git a/src/c3nav/mapdata/render/engines/svg.py b/src/c3nav/mapdata/render/engines/svg.py index e387a400..64b20d86 100644 --- a/src/c3nav/mapdata/render/engines/svg.py +++ b/src/c3nav/mapdata/render/engines/svg.py @@ -83,7 +83,7 @@ class SVGEngine(RenderEngine): result += '' return result - def render(self): + def render(self, filename=None): # render the image to png. returns bytes if f is None, otherwise it calls f.write() if self.width == 256 and self.height == 256 and not self.g: diff --git a/src/c3nav/mapdata/render/engines/wavefront.py b/src/c3nav/mapdata/render/engines/wavefront.py new file mode 100644 index 00000000..2b6dd9c2 --- /dev/null +++ b/src/c3nav/mapdata/render/engines/wavefront.py @@ -0,0 +1,63 @@ +import os +from itertools import chain + +import numpy as np + +from c3nav.mapdata.render.engines import register_engine +from c3nav.mapdata.render.engines.base3d import Base3DEngine + + +@register_engine +class WavefrontEngine(Base3DEngine): + filetype = 'obj' + + def _normal_normal(self, normal): + return normal / (np.absolute(normal).max()) + + def render(self, filename=None): + facets = np.vstack(chain(*(chain(*v.values()) for v in self.vertices.values()))) + vertices = tuple(set(tuple(vertex) for vertex in facets.reshape((-1, 3)))) + vertices_lookup = {vertex: i for i, vertex in enumerate(vertices, start=1)} + + normals = np.cross(facets[:, 1] - facets[:, 0], facets[:, 2] - facets[:, 1]).reshape((-1, 3)) + normals = normals / np.amax(np.absolute(normals), axis=1).reshape((-1, 1)) + normals = tuple(set(tuple(normal) for normal in normals)) + normals_lookup = {normal: i for i, normal in enumerate(normals, start=1)} + + materials = b'' + materials_filename = filename + '.mtl' + for name, color in self.colors.items(): + materials += ((b'newmtl %s\n' % name.encode()) + + (b'Ka %.2f %.2f %.2f\n' % color[:3]) + + (b'Kd %.2f %.2f %.2f\n' % color[:3]) + + b'Ks 0.00 0.00 0.00\n' + + (b'd %.2f\n' % color[3]) + + b'illum 2\n') + + result = b'mtllib %s\n' % os.path.split(materials_filename)[-1].encode() + result += b'o c3navExport\n' + result += b''.join((b'v %.3f %.3f %.3f\n' % vertex) for vertex in vertices) + result += b''.join((b'vn %.6f %.6f %.6f\n' % normal) for normal in normals) + + for group, subgroups in self.groups.items(): + result += b'\n# ' + group.encode() + b'\n' + for subgroup in subgroups: + result += b'\n# ' + subgroup.encode() + b'\n' + for i, vertices in enumerate(self.vertices[subgroup].values()): + if not vertices: + continue + for j, facets in enumerate(vertices): + if not facets.size: + continue + normals = np.cross(facets[:, 1] - facets[:, 0], facets[:, 2] - facets[:, 1]).reshape((-1, 3)) + normals = normals / np.amax(np.absolute(normals), axis=1).reshape((-1, 1)) + normals = tuple(normals_lookup[tuple(normal)] for normal in normals) + result += ((b'g %s_%d_%d\n' % (subgroup.encode(), i, j)) + + (b'usemtl %s\n' % subgroup.encode()) + + b's off\n' + + b''.join((b'f %d//%d %d//%d %d//%d\n' % (vertices_lookup[tuple(a)], normals[k], + vertices_lookup[tuple(b)], normals[k], + vertices_lookup[tuple(c)], normals[k],) + for k, (a, b, c) in enumerate(facets))) + ) + return result, (materials_filename, materials)