mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 22:24:17 +01:00
Basic python server working including hot-reload
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
from OCP.TopoDS import TopoDS_Shape
|
||||||
|
|
||||||
|
from server import Server
|
||||||
|
|
||||||
|
server = Server()
|
||||||
|
|
||||||
|
if 'YACV_DISABLE_SERVER' not in os.environ:
|
||||||
|
# Start a new server ASAP to let the polling client connect while still building CAD objects
|
||||||
|
# This is a bit of a hack, but it is seamless to the user. This behavior can be disabled by setting
|
||||||
|
# the environment variable YACV_DISABLE_SERVER to a non-empty value
|
||||||
|
server.start()
|
||||||
|
|
||||||
|
|
||||||
|
def show_object(obj: TopoDS_Shape):
|
||||||
|
"""Show a CAD object in the default server"""
|
||||||
|
server.show_object(obj)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Publish the logo to the server
|
||||||
|
from logo.logo import build_logo
|
||||||
|
|
||||||
|
assert server is not None
|
||||||
|
server.show_object(build_logo())
|
||||||
|
|
||||||
|
time.sleep(60)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from typing import TypeVar, Generic, List, Callable
|
import asyncio
|
||||||
|
from typing import TypeVar, Generic, List, Callable, Tuple
|
||||||
|
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
|
|
||||||
@@ -8,20 +9,32 @@ class EventPublisher(Generic[T]):
|
|||||||
|
|
||||||
_listeners: List[Callable[[T], None]]
|
_listeners: List[Callable[[T], None]]
|
||||||
_buffer: List[T]
|
_buffer: List[T]
|
||||||
|
_lock: asyncio.Lock
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._listeners = []
|
self._listeners = []
|
||||||
self._buffer = []
|
self._buffer = []
|
||||||
|
self._lock = asyncio.Lock()
|
||||||
|
|
||||||
def subscribe(self, listener: Callable[[T], None]):
|
async def subscribe(self, listener: Callable[[T], None]):
|
||||||
self._listeners.append(listener)
|
async with self._lock:
|
||||||
for data in self._buffer:
|
self._listeners.append(listener)
|
||||||
listener(data)
|
for data in self._buffer:
|
||||||
|
listener(data)
|
||||||
|
|
||||||
def unsubscribe(self, listener: Callable[[T], None]):
|
def unsubscribe(self, listener: Callable[[T], None]):
|
||||||
self._listeners.remove(listener)
|
async with self._lock:
|
||||||
|
self._listeners.remove(listener)
|
||||||
|
|
||||||
def emit(self, data: T):
|
def emit(self, data: T):
|
||||||
self._buffer.append(data)
|
async with self._lock:
|
||||||
for listener in self._listeners:
|
self._buffer.append(data)
|
||||||
listener(data)
|
for listener in self._listeners:
|
||||||
|
listener(data)
|
||||||
|
|
||||||
|
def buffer(self) -> Tuple[List[T], asyncio.Lock]:
|
||||||
|
return self._buffer, self._lock
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
async with self._lock:
|
||||||
|
self._buffer.clear()
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
|
from OCP.TopoDS import TopoDS_Shape
|
||||||
from build123d import *
|
from build123d import *
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
from tessellate import tessellate, tessellate_count
|
from tessellate import tessellate, tessellate_count
|
||||||
|
|
||||||
|
|
||||||
def logo() -> Compound:
|
def build_logo() -> TopoDS_Shape:
|
||||||
"""Builds the CAD part of the logo"""
|
"""Builds the CAD part of the logo"""
|
||||||
with BuildPart() as logo_obj:
|
with BuildPart() as logo_obj:
|
||||||
Box(1, 2, 3)
|
Box(1, 2, 3)
|
||||||
return logo_obj.part
|
return logo_obj.part.wrapped
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
obj = logo()
|
obj = build_logo()
|
||||||
|
|
||||||
for update in tqdm(tessellate(obj.wrapped), total=tessellate_count(obj.wrapped)):
|
for update in tqdm(tessellate(obj.wrapped), total=tessellate_count(obj.wrapped)):
|
||||||
# print(update.gltf)
|
# print(update.gltf)
|
||||||
|
|||||||
6
yacv_server/main.py
Normal file
6
yacv_server/main.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from os import system
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Just a reminder that a hot-reloading server can be started with the following command:
|
||||||
|
# Need to disable auto-start to avoid conflicts with the hot-reloading server
|
||||||
|
system('YACV_DISABLE_SERVER=true aiohttp-devtools runserver server.py --port 32323')
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import asyncio
|
||||||
|
import atexit
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
from threading import Thread
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from OCP.TopoDS import TopoDS_Shape
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
FRONTEND_BASE_PATH = os.getenv('FRONTEND_BASE_PATH', '../dist')
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
|
async def _index_handler(request: web.Request) -> web.Response:
|
||||||
|
return web.HTTPTemporaryRedirect(location='index.html')
|
||||||
|
|
||||||
|
|
||||||
|
class Server:
|
||||||
|
app = web.Application()
|
||||||
|
runner: web.AppRunner
|
||||||
|
thread: Optional[Thread] = None
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# --- Routes ---
|
||||||
|
# - API
|
||||||
|
# self.app.router.add_route({'POST','GET'}, '/api/{collection}', api_handler)
|
||||||
|
# - Static files from the frontend
|
||||||
|
self.app.router.add_get('/{path:(.*/|)}', _index_handler) # Any folder -> index.html
|
||||||
|
self.app.router.add_static('/', path=FRONTEND_BASE_PATH, name='static_frontend')
|
||||||
|
# --- Misc ---
|
||||||
|
self.runner = web.AppRunner(self.app)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""Starts the web server in the background"""
|
||||||
|
assert self.thread is None, "Server already started"
|
||||||
|
self.thread = Thread(target=self.run_server, name='yacv_server', daemon=True)
|
||||||
|
signal.signal(signal.SIGINT | signal.SIGTERM, self.stop)
|
||||||
|
atexit.register(self.stop)
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
|
def stop(self, *args):
|
||||||
|
"""Stops the web server"""
|
||||||
|
print('Stopping server...')
|
||||||
|
if self.thread is None:
|
||||||
|
print('Cannot stop server because it is not running')
|
||||||
|
return
|
||||||
|
asyncio.run(self.runner.shutdown())
|
||||||
|
asyncio.run(self.app.cleanup())
|
||||||
|
self.thread = None # FIXME: Not properly cleaned up (join blocks forever)
|
||||||
|
print('Cleanup done')
|
||||||
|
|
||||||
|
def run_server(self):
|
||||||
|
"""Runs the web server"""
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
loop.run_until_complete(self.runner.setup())
|
||||||
|
site = web.TCPSite(self.runner, os.getenv('YACV_HOST', 'localhost'), int(os.getenv('YACV_PORT', 32323)))
|
||||||
|
loop.run_until_complete(site.start())
|
||||||
|
loop.run_forever()
|
||||||
|
|
||||||
|
def show_object(self, obj: TopoDS_Shape):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_app() -> web.Application:
|
||||||
|
"""Required by aiohttp-devtools"""
|
||||||
|
return Server().app
|
||||||
|
|||||||
Reference in New Issue
Block a user