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