diff --git a/poetry.lock b/poetry.lock index 64a7338..ec29cf7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -96,6 +96,20 @@ yarl = ">=1.0,<2.0" [package.extras] speedups = ["Brotli", "aiodns", "brotlicffi"] +[[package]] +name = "aiohttp-cors" +version = "0.7.0" +description = "CORS support for aiohttp" +optional = false +python-versions = "*" +files = [ + {file = "aiohttp-cors-0.7.0.tar.gz", hash = "sha256:4d39c6d7100fd9764ed1caf8cebf0eb01bf5e3f24e2e073fda6234bc48b19f5d"}, + {file = "aiohttp_cors-0.7.0-py3-none-any.whl", hash = "sha256:0451ba59fdf6909d0e2cd21e4c0a43752bc0703d33fc78ae94d9d9321710193e"}, +] + +[package.dependencies] +aiohttp = ">=1.1" + [[package]] name = "aiohttp-devtools" version = "1.1.2" @@ -1597,4 +1611,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "950e4ebfaa15d8cc389403d0f931a63b2c2685aa1afbf3624e5806152acd6c83" +content-hash = "92566c9fc24a286eea553f28b583fe6de53d7ecb595be508a0ce4ae9ca9f58c6" diff --git a/pyproject.toml b/pyproject.toml index b973245..0dc51a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ ocp-tessellate = "^2.0.6" # Web aiohttp = "^3.9.3" +aiohttp-cors = "^0.7.0" aiohttp-devtools = "^1.1.2" # Misc diff --git a/src/App.vue b/src/App.vue index 45efa33..23cb975 100644 --- a/src/App.vue +++ b/src/App.vue @@ -3,13 +3,14 @@ import {defineAsyncComponent, ref, Ref} from "vue"; import Sidebar from "./misc/Sidebar.vue"; import Loading from "./misc/Loading.vue"; -import ModelViewerOverlay from "./viewer/ModelViewerOverlay.vue"; import Tools from "./tools/Tools.vue"; import Models from "./models/Models.vue"; -import {VLayout, VMain, VToolbarTitle, VTooltip, VBtn} from "vuetify/lib/components"; +import {VLayout, VMain, VToolbarTitle} from "vuetify/lib/components"; import {settings} from "./misc/settings"; import {NetworkManager, NetworkUpdateEvent} from "./misc/network"; import {SceneMgr} from "./misc/scene"; +import {Document} from "@gltf-transform/core"; +import type ModelViewerWrapperT from "./viewer/ModelViewerWrapper.vue"; // NOTE: The ModelViewer library is big (THREE.js), so we split it and import it asynchronously const ModelViewerWrapper = defineAsyncComponent({ @@ -20,12 +21,14 @@ const ModelViewerWrapper = defineAsyncComponent({ let openSidebarsByDefault: Ref = ref(window.innerWidth > 1200); -let [refSData, sData] = SceneMgr.newData(); +let sceneUrl = ref("") +let viewer: Ref | null> = ref(null); +let document = new Document(); // Set up the load model event listener let networkMgr = new NetworkManager(); -networkMgr.addEventListener('update', (model: NetworkUpdateEvent) => { - SceneMgr.loadModel(refSData, sData, model.name, model.url); +networkMgr.addEventListener('update', async (model: NetworkUpdateEvent) => { + document = await SceneMgr.loadModel(sceneUrl, document, model.name, model.url); }); // Start loading all configured models ASAP for (let model of settings.preloadModels) { @@ -38,8 +41,7 @@ for (let model of settings.preloadModels) { - - + @@ -47,15 +49,15 @@ for (let model of settings.preloadModels) { - + - + - + diff --git a/src/models/glb/merge.ts b/src/misc/gltf.ts similarity index 91% rename from src/models/glb/merge.ts rename to src/misc/gltf.ts index 05915c2..1723838 100644 --- a/src/models/glb/merge.ts +++ b/src/misc/gltf.ts @@ -4,15 +4,15 @@ import {unpartition} from "@gltf-transform/functions"; let io = new WebIO(); /** - * Given the bytes of a GLB file and a parsed GLTF document, it parses and merges the GLB into the document. + * Loads a GLB model from a URL and adds it to the document or replaces it if the names match. * * It can replace previous models in the document if the provided name matches the name of a previous model. * * Remember to call mergeFinalize after all models have been merged (slower required operations). */ -export async function mergePartial(glb: Uint8Array, name: string, document: Document): Promise { +export async function mergePartial(url: string, name: string, document: Document): Promise { // Load the new document - let newDoc = await io.readBinary(glb); + let newDoc = await io.read(url); // Remove any previous model with the same name and ensure consistent names // noinspection TypeScriptValidateJSTypes diff --git a/src/misc/network.ts b/src/misc/network.ts index 2351df6..0d1cc46 100644 --- a/src/misc/network.ts +++ b/src/misc/network.ts @@ -16,7 +16,7 @@ export class NetworkManager extends EventTarget { private knownObjectHashes: { [name: string]: string } = {}; /** - * Tries to load a new model (.glb or .glbs) from the given URL. + * Tries to load a new model (.glb) from the given URL. * * If the URL uses the websocket protocol (ws:// or wss://), the server will be continuously monitored for changes. * In this case, it will only trigger updates if the name or hash of any model changes. @@ -56,7 +56,7 @@ export class NetworkManager extends EventTarget { private foundModel(name: string, hash: string, url: string) { let prevHash = this.knownObjectHashes[name]; - if (hash !== prevHash) { + if (!hash || hash !== prevHash) { this.knownObjectHashes[name] = hash; this.dispatchEvent(new NetworkUpdateEvent(name, url)); } diff --git a/src/misc/scene.ts b/src/misc/scene.ts index 5d61b0d..b251922 100644 --- a/src/misc/scene.ts +++ b/src/misc/scene.ts @@ -1,76 +1,37 @@ import type {ModelViewerElement} from '@google/model-viewer'; import type {ModelScene} from "@google/model-viewer/lib/three-components/ModelScene"; -import {Ref, ref} from 'vue'; +import {Ref} from 'vue'; import {Document} from '@gltf-transform/core'; -import {ModelViewerInfo} from "./viewer/ModelViewerWrapper.vue"; -import {mergeFinalize, mergePartial, toBuffer} from "../models/glb/merge"; - -export type SceneMgrRefData = { - /** When updated, forces the viewer to load a new model replacing the current one */ - viewerSrc: string | null - - /** The model viewer HTML element with all APIs */ - viewer: ModelViewerElement | null - /** The (hidden) scene of the model viewer */ - viewerScene: ModelScene | null -} - -export type SceneMgrData = { - /** The currently shown document, which must match the viewerSrc. */ - document: Document -} +import {mergeFinalize, mergePartial, toBuffer} from "./gltf"; /** This class helps manage SceneManagerData. All methods are static to support reactivity... */ export class SceneMgr { - private constructor() { - } - - /** Creates a new SceneManagerData object */ - static newData(): [Ref, SceneMgrData] { - let refData: any = ref({ - viewerSrc: null, - viewer: null, - viewerScene: null, - }); - let data = { - document: new Document() - }; - // newVar.value.document.createScene("scene"); - // this.showCurrentDoc(newVar.value).then(r => console.log("Initial empty model loaded")); - return [refData, data]; - } - - /** Loads a GLB/GLBS model from a URL and adds it to the viewer or replaces it if the names match */ - static async loadModel(refData: Ref, data: SceneMgrData, name: string, url: string) { + /** Loads a GLB model from a URL and adds it to the viewer or replaces it if the names match */ + static async loadModel(sceneUrl: Ref, document: Document, name: string, url: string): Promise { let loadStart = performance.now(); - // Connect to the URL of the model - let response = await fetch(url); - if (!response.ok) throw new Error("Failed to fetch model: " + response.statusText); - // Start merging into the current document, replacing or adding as needed - let glb = new Uint8Array(await response.arrayBuffer()); - data.document = await mergePartial(glb, name, data.document); + document = await mergePartial(url, name, document); // Display the final fully loaded model - await this.showCurrentDoc(refData, data); + document = await this.showCurrentDoc(sceneUrl, document); console.log("Model", name, "loaded in", performance.now() - loadStart, "ms"); + return document; } /** Serializes the current document into a GLB and updates the viewerSrc */ - private static async showCurrentDoc(refData: Ref, data: SceneMgrData) { - data.document = await mergeFinalize(data.document); - let buffer = await toBuffer(data.document); - let blob = new Blob([buffer], {type: 'model/gltf-binary'}); - console.log("Showing current doc", data.document, "as", Array.from(buffer)); - refData.value.viewerSrc = URL.createObjectURL(blob); - } + private static async showCurrentDoc(sceneUrl: Ref, document: Document): Promise { + // Make sure the document is fully loaded and ready to be shown + document = await mergeFinalize(document); - /** Should be called any model finishes loading successfully (after a viewerSrc update) */ - static onload(data: SceneMgrRefData, info: typeof ModelViewerInfo) { - console.log("ModelViewer loaded", info); - data.viewer = info.viewer; - data.viewerScene = info.scene; + // Serialize the document into a GLB and update the viewerSrc + let buffer = await toBuffer(document); + let blob = new Blob([buffer], {type: 'model/gltf-binary'}); + //console.log("Showing current doc", document, "as", Array.from(buffer)); + sceneUrl.value = URL.createObjectURL(blob); + + // Return the updated document + return document; } } \ No newline at end of file diff --git a/src/misc/settings.ts b/src/misc/settings.ts index 72e8c14..023a769 100644 --- a/src/misc/settings.ts +++ b/src/misc/settings.ts @@ -6,7 +6,7 @@ export const settings = { // @ts-ignore new URL('../../assets/logo.glb', import.meta.url).href, // Websocket URLs automatically listen for new models from the python backend - //"ws://localhost:8080/" + // "ws://localhost:32323/" ], displayLoadingEveryMs: 1000, /* How often to display partially loaded models */ checkServerEveryMs: 100, /* How often to check for a new server */ diff --git a/src/models/Models.vue b/src/models/Models.vue index 93157db..7a73c63 100644 --- a/src/models/Models.vue +++ b/src/models/Models.vue @@ -1,9 +1,14 @@