diff --git a/assets/logo.glb b/assets/logo_build/base.glb similarity index 100% rename from assets/logo.glb rename to assets/logo_build/base.glb diff --git a/assets/logo_build/img.jpg.glb b/assets/logo_build/img.jpg.glb new file mode 100644 index 0000000..62948c9 Binary files /dev/null and b/assets/logo_build/img.jpg.glb differ diff --git a/assets/logo_build/location.glb b/assets/logo_build/location.glb new file mode 100644 index 0000000..8757f20 Binary files /dev/null and b/assets/logo_build/location.glb differ diff --git a/src/misc/settings.ts b/src/misc/settings.ts index 737b918..32a5790 100644 --- a/src/misc/settings.ts +++ b/src/misc/settings.ts @@ -2,11 +2,15 @@ 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_build/base.glb', import.meta.url).href, + // @ts-ignore + new URL('../../assets/logo_build/location.glb', import.meta.url).href, + // @ts-ignore + new URL('../../assets/logo_build/img.jpg.glb', import.meta.url).href, // Websocket URLs automatically listen for new models from the python backend - "ws://192.168.1.132: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 */ diff --git a/yacv_server/__init__.py b/yacv_server/__init__.py index 7037103..9ce7175 100644 --- a/yacv_server/__init__.py +++ b/yacv_server/__init__.py @@ -27,13 +27,9 @@ def _get_app() -> web.Application: """Required by aiohttp-devtools""" logging.basicConfig(level=logging.DEBUG) from logo import build_logo - from build123d import Axis - logo = build_logo() + logo, img_location, img_path = build_logo() server.show_cad(logo, 'Logo') - img_location = logo.faces().group_by(Axis.X)[0].face().center_location # Avoid overlapping: - img_location.position = Vector(img_location.position.X - 1e-2, img_location.position.Y, img_location.position.Z) server.show_cad(img_location, 'Location') - img_path = os.path.join(os.path.dirname(__file__), '..', 'assets', 'img.jpg') server.show_image(img_path, img_location, 20) return server.app diff --git a/yacv_server/logo.py b/yacv_server/logo.py index f79a605..5e9870e 100644 --- a/yacv_server/logo.py +++ b/yacv_server/logo.py @@ -1,11 +1,14 @@ import asyncio import logging import os +from typing import Tuple from build123d import * +ASSETS_DIR = os.getenv('ASSETS_DIR', os.path.join(os.path.dirname(__file__), '..', 'assets')) -def build_logo(text: bool = True) -> Part: + +def build_logo(text: bool = True) -> Tuple[Part, Location, str]: """Builds the CAD part of the logo""" with BuildPart(Plane.XY.offset(50)) as logo_obj: Box(22, 40, 30) @@ -18,29 +21,38 @@ def build_logo(text: bool = True) -> Part: Text('Yet Another\nCAD Viewer', 7, font_path='/usr/share/fonts/TTF/OpenSans-Regular.ttf') extrude(amount=1) - return logo_obj.part + logo_img_location = logo_obj.faces().group_by(Axis.X)[0].face().center_location # Avoid overlapping: + logo_img_location.position = Vector(logo_img_location.position.X - 4e-2, logo_img_location.position.Y, + logo_img_location.position.Z) + logo_img_path = os.path.join(ASSETS_DIR, 'img.jpg') + return logo_obj.part, logo_img_location, logo_img_path if __name__ == "__main__": logging.basicConfig(level=logging.DEBUG) - # Start an offline "server" to export the CAD part of the logo in a way compatible with the frontend + # Start an offline server to export the CAD part of the logo in a way compatible with the frontend + # If this is not set, the server will auto-start on import and show_* calls will provide live updates os.environ['YACV_DISABLE_SERVER'] = '1' - from yacv_server import show_object, server - - ASSETS_DIR = os.getenv('ASSETS_DIR', os.path.join(os.path.dirname(__file__), '..', 'assets')) + from yacv_server import show, show_image # Add the CAD part of the logo to the server - obj = build_logo() - # DEBUG: Shape(obj).export_stl(os.path.join(ASSETS_DIR, 'logo.stl')) - show_object(obj, 'logo') - - # Save the complete logo to a single GLB file - with open(os.path.join(ASSETS_DIR, 'logo.glb'), 'wb') as f: - async def writer(): - f.write(await server.export('logo')) + logo, img_location, img_path = build_logo() + show(logo, 'base') + show(img_location, 'location') + show_image(img_path, img_location, 20) - asyncio.run(writer()) + async def exporter(): + # We need access to the actual server object for advanced features like exporting to file + from yacv_server import server + for name in server.shown_object_names(): + print(f'Exporting {name} to GLB...') + with open(os.path.join(ASSETS_DIR, 'logo_build', f'{name}.glb'), 'wb') as f: + f.write(await server.export(name)) - print('Logo saved to', os.path.join(ASSETS_DIR, 'logo.glb')) + + # Save the complete logo to multiple GLB files (async required) + asyncio.run(exporter()) + + print('Logo saved!') diff --git a/yacv_server/pubsub.py b/yacv_server/pubsub.py index 24c77e4..bf478ea 100644 --- a/yacv_server/pubsub.py +++ b/yacv_server/pubsub.py @@ -58,3 +58,7 @@ class BufferedPubSub(Generic[T]): yield v finally: # When aclose() is called await self._unsubscribe(q) + + def buffer(self) -> List[T]: + """Returns a shallow copy of the list of buffered events""" + return self._buffer[:] \ No newline at end of file diff --git a/yacv_server/server.py b/yacv_server/server.py index b90e9cf..23727fe 100644 --- a/yacv_server/server.py +++ b/yacv_server/server.py @@ -243,23 +243,25 @@ class Server: response.headers['Content-Disposition'] = f'attachment; filename="{request.match_info["name"]}.glb"' return response + def shown_object_names(self) -> list[str]: + """Returns the names of all objects that have been shown""" + return list([obj.name for obj in self.show_events.buffer()]) + async def export(self, name: str) -> bytes: """Export the given previously-shown object to a single GLB file, building it if necessary.""" start = time.time() + # Check that the object to build exists and grab it if it does found = False obj: Optional[TopoDS_Shape] = None kwargs: Optional[Dict[str, any]] = None - subscription = self.show_events.subscribe(include_future=False) - try: - 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: - await subscription.aclose() + subscription = self.show_events.buffer() + for data in subscription: + if data.name == name: + obj = data.obj + kwargs = data.kwargs + found = True # Required because obj could be None + break if not found: raise web.HTTPNotFound(text=f'No object named {name} was previously shown')