cadquery-freecad-module/ThirdParty/cqparts/display/web.py
2018-12-29 06:10:03 -05:00

193 lines
6.6 KiB
Python

import os
import sys
import inspect
import tempfile
import shutil
import jinja2
import time
# web content
if sys.version_info[0] >= 3:
# python 3.x
import http.server as SimpleHTTPServer
import socketserver as SocketServer
else:
# python 2.x
import SimpleHTTPServer
import SocketServer
import threading
import webbrowser
import logging
log = logging.getLogger(__name__)
from ..utils import working_dir
from .environment import map_environment, DisplayEnvironment
# Get this file's location
_this_path = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
# Set template web-content directory
# note: can be changed prior to calling web_display()
#
# >>> from cqparts.display import web
# >>> web.TEMPLATE_CONTENT_DIR = './my/alternative/template'
# >>> web.web_display(some_thing)
#
# This would typically be used for testing, or development purposes.
TEMPLATE_CONTENT_DIR = os.path.join(_this_path, 'web-template')
SocketServer.TCPServer.allow_reuse_address = True # stops crash on re-use of port
@map_environment(
# named 'cmdline'?
# This is a fallback display environment if no other method is available.
# Therefore it's assumed that the environment that's been detected is a
# no-frills command line.
name='cmdline',
order=99,
condition=lambda: True, # fallback
)
class WebDisplayEnv(DisplayEnvironment):
"""
Display given component in a browser window
This display exports the model, then exposes a http service on *localhost*
for a browser to use.
The http service does not know when the browser window has been closed, so
it will continue to serve the model's data until the user halts the
process with a :class:`KeyboardInterrupt` (by pressing ``Ctrl+C``)
When run, you should see output similar to::
>>> from cqparts.display import WebDisplayEnv
>>> from cqparts_misc.basic.primatives import Cube
>>> WebDisplayEnv().display(Cube())
press [ctrl+c] to stop server
127.0.0.1 - - [27/Dec/2017 16:06:37] "GET / HTTP/1.1" 200 -
Created new window in existing browser session.
127.0.0.1 - - [27/Dec/2017 16:06:39] "GET /model/out.gltf HTTP/1.1" 200 -
127.0.0.1 - - [27/Dec/2017 16:06:39] "GET /model/out.bin HTTP/1.1" 200 -
A new browser window should appear with a render that looks like:
.. image:: /_static/img/web_display.cube.png
Then, when you press ``Ctrl+C``, you should see::
^C[server shutdown successfully]
and any further request on the opened browser window will return
an errorcode 404 (file not found), because the http service has stopped.
"""
def display_callback(self, component, port=9041, autorotate=False):
"""
:param component: the component to render
:type component: :class:`Component <cqparts.Component>`
:param port: port to expose http service on
:type port: :class:`int`
:param autorotate: if ``True``, rendered component will rotate
as if on a turntable.
:type autorotate: :class:`bool`
"""
# Verify Parameter(s)
from .. import Component
if not isinstance(component, Component):
raise TypeError("given component must be a %r, not a %r" % (
Component, type(component)
))
# Create temporary file to host files
temp_dir = tempfile.mkdtemp()
host_dir = os.path.join(temp_dir, 'html')
print("host temp folder: %s" % host_dir)
# Copy template content to temporary location
shutil.copytree(TEMPLATE_CONTENT_DIR, host_dir)
# Export model
exporter = component.exporter('gltf')
exporter(
filename=os.path.join(host_dir, 'model', 'out.gltf'),
embed=False,
)
# Modify templated content
# index.html
with open(os.path.join(host_dir, 'index.html'), 'r') as fh:
index_template = jinja2.Template(fh.read())
with open(os.path.join(host_dir, 'index.html'), 'w') as fh:
# camera location & target
cam_t = [
(((a + b) / 2.0) / 1000) # midpoint (unit: meters)
for (a, b) in zip(exporter.scene_min, exporter.scene_max)
]
cam_p = [
(((b - a) * 1.0) / 1000) + t # max point * 200% (unit: meters)
for (a, b, t) in zip(exporter.scene_min, exporter.scene_max, cam_t)
]
# write
xzy = lambda a: (a[0], a[2], -a[1]) # x,z,y coordinates (not x,y,z)
fh.write(index_template.render(
model_filename='model/out.gltf',
autorotate = str(autorotate).lower(),
camera_target=','.join("%g" % (val) for val in xzy(cam_t)),
camera_pos=','.join("%g" % (val) for val in xzy(cam_p)),
))
try:
# Start web-service (loop forever)
server = SocketServer.ThreadingTCPServer(
server_address=("0.0.0.0", port),
RequestHandlerClass=SimpleHTTPServer.SimpleHTTPRequestHandler,
)
server_addr = "http://%s:%i/" % server.server_address
def thread_target():
with working_dir(host_dir):
server.serve_forever()
print("serving: %s" % server_addr)
sys.stdout.flush()
server_thread = threading.Thread(target=thread_target)
server_thread.daemon = True
server_thread.start()
# Open in browser
print("opening in browser: %s" % server_addr)
sys.stdout.flush()
webbrowser.open(server_addr)
# workaround for https://github.com/dcowden/cadquery/issues/211
import signal
def _handler_sigint(signum, frame):
raise KeyboardInterrupt()
signal.signal(signal.SIGINT, _handler_sigint)
print("press [ctrl+c] to stop server")
sys.stdout.flush()
while True: # wait for Ctrl+C
time.sleep(1)
except KeyboardInterrupt:
log.info("\n[keyboard interrupt]")
sys.stdout.flush()
finally:
# Stop web-service
server.shutdown()
server.server_close()
server_thread.join()
print("[server shutdown successfully]")
# Delete temporary content
if os.path.exists(os.path.join(host_dir, 'cqparts-display.txt')):
# just making sure we're deleting the right folder
shutil.rmtree(temp_dir)