mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 22:24:17 +01:00
basic fixes for the server
This commit is contained in:
@@ -43,13 +43,16 @@ export class NetworkManager extends EventTarget {
|
||||
ws.onmessage = (event) => {
|
||||
let data = JSON.parse(event.data);
|
||||
console.debug("WebSocket message", data);
|
||||
this.foundModel(data.name, data.hash, data.url);
|
||||
let urlObj = new URL(url);
|
||||
urlObj.protocol = urlObj.protocol === "ws:" ? "http:" : "https:";
|
||||
urlObj.searchParams.set("api_object", data.name);
|
||||
this.foundModel(data.name, data.hash, urlObj.toString());
|
||||
};
|
||||
ws.onerror = (event) => {
|
||||
console.error("WebSocket error", event);
|
||||
}
|
||||
ws.onclose = () => {
|
||||
//console.trace("WebSocket closed, reconnecting very soon");
|
||||
console.debug("WebSocket closed, reconnecting very soon");
|
||||
setTimeout(() => this.monitorWebSocket(url), settings.checkServerEveryMs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
export const settings = {
|
||||
preloadModels: [
|
||||
// @ts-ignore
|
||||
new URL('../../assets/fox.glb', import.meta.url).href,
|
||||
// new URL('../../assets/fox.glb', import.meta.url).href,
|
||||
// @ts-ignore
|
||||
new URL('../../assets/logo.glb', import.meta.url).href,
|
||||
// new URL('../../assets/logo.glb', import.meta.url).href,
|
||||
// Websocket URLs automatically listen for new models from the python backend
|
||||
// "ws://localhost:32323/"
|
||||
"ws://192.168.1.132:32323/"
|
||||
],
|
||||
displayLoadingEveryMs: 1000, /* How often to display partially loaded models */
|
||||
checkServerEveryMs: 100, /* How often to check for a new server */
|
||||
|
||||
@@ -4,7 +4,6 @@ import os
|
||||
|
||||
from OCP.TopoDS import TopoDS_Shape
|
||||
from build123d import *
|
||||
from build123d import Shape
|
||||
|
||||
|
||||
def build_logo() -> TopoDS_Shape:
|
||||
@@ -21,7 +20,6 @@ def build_logo() -> TopoDS_Shape:
|
||||
|
||||
return logo_obj.part.wrapped
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from threading import Thread
|
||||
from typing import Optional, Dict, Union
|
||||
|
||||
@@ -12,7 +12,7 @@ import aiohttp_cors
|
||||
from OCP.TopoDS import TopoDS_Shape
|
||||
from aiohttp import web
|
||||
from build123d import Shape, Axis
|
||||
from dataclasses_json import dataclass_json, config
|
||||
from dataclasses_json import dataclass_json
|
||||
|
||||
from mylogger import logger
|
||||
from pubsub import BufferedPubSub
|
||||
@@ -31,11 +31,24 @@ class UpdatesApiData:
|
||||
"""Name of the object. Should be unique unless you want to overwrite the previous object"""
|
||||
hash: str
|
||||
"""Hash of the object, to detect changes without rebuilding the object"""
|
||||
obj: Optional[TopoDS_Shape] = field(default=None, metadata=config(exclude=lambda obj: True))
|
||||
|
||||
|
||||
class UpdatesApiFullData(UpdatesApiData):
|
||||
obj: Optional[TopoDS_Shape]
|
||||
"""The OCCT object, if any (not serialized)"""
|
||||
kwargs: Optional[Dict[str, any]] = field(default=None, metadata=config(exclude=lambda obj: True))
|
||||
kwargs: Optional[Dict[str, any]]
|
||||
"""The show_object options, if any (not serialized)"""
|
||||
|
||||
def __init__(self, name: str, hash: str, obj: Optional[TopoDS_Shape] = None,
|
||||
kwargs: Optional[Dict[str, any]] = None):
|
||||
self.name = name
|
||||
self.hash = hash
|
||||
self.obj = obj
|
||||
self.kwargs = kwargs
|
||||
|
||||
def to_json(self) -> str:
|
||||
return super().to_json()
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
async def _index_handler(request: web.Request) -> web.Response:
|
||||
@@ -47,7 +60,7 @@ class Server:
|
||||
runner: web.AppRunner
|
||||
thread: Optional[Thread] = None
|
||||
do_shutdown = asyncio.Event()
|
||||
show_events = BufferedPubSub[UpdatesApiData]()
|
||||
show_events = BufferedPubSub[UpdatesApiFullData]()
|
||||
object_events: Dict[str, BufferedPubSub[bytes]] = {}
|
||||
object_events_lock = asyncio.Lock()
|
||||
|
||||
@@ -134,6 +147,7 @@ class Server:
|
||||
subscription = self.show_events.subscribe()
|
||||
try:
|
||||
async for data in subscription:
|
||||
logger.debug('Sending info about %s to %s: %s', data.name, request.remote, data)
|
||||
# noinspection PyUnresolvedReferences
|
||||
await ws.send_str(data.to_json())
|
||||
finally:
|
||||
@@ -141,13 +155,15 @@ class Server:
|
||||
|
||||
# Start sending updates to the client automatically
|
||||
send_task = asyncio.create_task(_send_api_updates())
|
||||
receive_task = asyncio.create_task(ws.receive())
|
||||
try:
|
||||
logger.debug('Client connected: %s', request.remote)
|
||||
# Wait for the client to close the connection (or send a message)
|
||||
await ws.receive()
|
||||
finally:
|
||||
done, pending = await asyncio.wait([send_task, receive_task], return_when=asyncio.FIRST_COMPLETED)
|
||||
# Make sure to stop sending updates to the client and close the connection
|
||||
send_task.cancel()
|
||||
for task in pending:
|
||||
task.cancel()
|
||||
finally:
|
||||
await ws.close()
|
||||
logger.debug('Client disconnected: %s', request.remote)
|
||||
|
||||
@@ -159,7 +175,7 @@ class Server:
|
||||
kwargs=None):
|
||||
name = name or f'object_{self.obj_counter}'
|
||||
self.obj_counter += 1
|
||||
precomputed_info = UpdatesApiData(name=name, hash=hash, obj=obj, kwargs=kwargs or {})
|
||||
precomputed_info = UpdatesApiFullData(name=name, hash=hash, obj=obj, kwargs=kwargs or {})
|
||||
self.show_events.publish_nowait(precomputed_info)
|
||||
logger.info('show_object(%s, %s) took %.3f seconds', name, hash, time.time() - start)
|
||||
return precomputed_info
|
||||
@@ -198,6 +214,7 @@ class Server:
|
||||
# Build123D & CadQuery
|
||||
while 'wrapped' in dir(obj) and not isinstance(obj, TopoDS_Shape):
|
||||
obj = obj.wrapped
|
||||
# TODO: Support locations (drawn as axes)
|
||||
if not isinstance(obj, TopoDS_Shape):
|
||||
raise ValueError(f'Cannot show object of type {type(obj)} (submit issue?)')
|
||||
|
||||
@@ -208,22 +225,13 @@ class Server:
|
||||
|
||||
async def _api_object(self, request: web.Request) -> web.Response:
|
||||
"""Returns the object file with the matching name, building it if necessary."""
|
||||
|
||||
# Export the object (or fail if not found)
|
||||
exported_glb = await self.export(request.match_info['name'])
|
||||
response = web.Response()
|
||||
try:
|
||||
# Create a new stream response with custom content type and headers
|
||||
response.content_type = 'model/gltf-binary'
|
||||
response.headers['Content-Disposition'] = f'attachment; filename="{request.match_info["name"]}.glb"'
|
||||
await response.prepare(request)
|
||||
|
||||
# Stream the export data to the response
|
||||
response.body = exported_glb
|
||||
finally:
|
||||
# Close the response (if not an error)
|
||||
if response.prepared:
|
||||
await response.write_eof()
|
||||
# Wrap the GLB in a response and return it
|
||||
response = web.Response(body=exported_glb)
|
||||
response.content_type = 'model/gltf-binary'
|
||||
response.headers['Content-Disposition'] = f'attachment; filename="{request.match_info["name"]}.glb"'
|
||||
return response
|
||||
|
||||
async def export(self, name: str) -> bytes:
|
||||
@@ -238,6 +246,7 @@ class Server:
|
||||
async for data in subscription:
|
||||
if data.name == name:
|
||||
obj = data.obj
|
||||
kwargs = data.kwargs
|
||||
found = True # Required because obj could be None
|
||||
break
|
||||
finally:
|
||||
|
||||
Reference in New Issue
Block a user