diff --git a/assets/licenses.txt b/assets/licenses.txt index 4d68109..bb39b4a 100644 --- a/assets/licenses.txt +++ b/assets/licenses.txt @@ -1360,6 +1360,42 @@ THE SOFTWARE. ----------- +The following npm package may be included in this product: + + - js-base64@3.7.7 + +This package contains the following license: + +Copyright (c) 2014, Dan Kogai +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of {{{project}}} nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------- + The following npm package may be included in this product: - estree-walker@2.0.2 diff --git a/frontend/App.vue b/frontend/App.vue index fc3e384..d7bab44 100644 --- a/frontend/App.vue +++ b/frontend/App.vue @@ -11,9 +11,9 @@ import {NetworkManager, NetworkUpdateEvent, NetworkUpdateEventModel} from "./mis import {SceneMgr} from "./misc/scene"; import {Document} from "@gltf-transform/core"; import type ModelViewerWrapperT from "./viewer/ModelViewerWrapper.vue"; -import {mdiPlus} from '@mdi/js' +import {mdiCube, mdiPlus, mdiScriptTextPlay} from '@mdi/js' +// @ts-expect-error import SvgIcon from '@jamescoyle/vue-icon'; -import {toBuffer} from "./misc/gltf.ts"; // NOTE: The ModelViewer library is big (THREE.js), so we split it and import it asynchronously const ModelViewerWrapper = defineAsyncComponent({ @@ -22,7 +22,7 @@ const ModelViewerWrapper = defineAsyncComponent({ delay: 0, }); -let openSidebarsByDefault: Ref = ref(window.innerWidth > 1200); +let openSidebarsByDefault: Ref = ref(window.innerWidth > window.innerHeight); const sceneUrl = ref("") const viewer: Ref | null> = ref(null); @@ -81,6 +81,7 @@ let networkMgr = new NetworkManager(); networkMgr.addEventListener('update-early', (e) => viewer.value?.onProgress((e as CustomEvent>).detail.length * 0.01)); networkMgr.addEventListener('update', (e) => onModelUpdateRequest(e as NetworkUpdateEvent)); +let preloadingModels = ref>([]); (async () => { // Start loading all configured models ASAP let sett = await settings(); if (sett.preload.length > 0) { @@ -91,17 +92,16 @@ networkMgr.addEventListener('update', (e) => onModelUpdateRequest(e as NetworkUp } }); for (let model of sett.preload) { - await networkMgr.load(model); + preloadingModels.value.push(model); + let removeFromPreloadingModels = () => { + preloadingModels.value = preloadingModels.value.filter((m) => m !== model); + }; + networkMgr.load(model).then(removeFromPreloadingModels).catch((e) => { + removeFromPreloadingModels() + console.error("Error preloading model", model, e); + }); } - } else { // Skip to interface without models (useful for playground mode) - console.debug("Showing empty gltf document to load the interface without models."); - // FIXME: Empty document breaks the playground-loaded models (using preload seems to fix this, maybe __helpers issue?) - let emptyDoc = new Document(); - emptyDoc.createScene(); - let buffer = await toBuffer(emptyDoc); - let blob = new Blob([buffer], {type: 'model/gltf-binary'}); - sceneUrl.value = URL.createObjectURL(blob); - } + } // else No preloaded models (useful for playground mode) })(); async function loadModelManual() { @@ -115,7 +115,28 @@ async function loadModelManual() { - + + +
+ No model loaded + +   Open playground... + + +   Load demo model... + + +   Load model manually... + + + Still trying to load the following: + + {{ model }}, + + + +
diff --git a/frontend/misc/IfNotSmallBuild.vue b/frontend/misc/IfNotSmallBuild.vue index c2f9be4..ce13a66 100644 --- a/frontend/misc/IfNotSmallBuild.vue +++ b/frontend/misc/IfNotSmallBuild.vue @@ -1,6 +1,7 @@ @@ -165,35 +206,59 @@ setupPyodide().then(() => { - Playground + Playground - - + + + + + Opacity of the editor (0 = hidden, 1 = fully visible) + - + + + + + Save current state to a snapshot for fast startup (WIP) + + + + + + Load snapshot for fast startup (WIP) + + Reset Pyodide worker (this forgets all previous state and will + take a little while) + - + + + Run code + Share link that auto-runs the code + Close (Pyodide remains loaded)
-
+
@@ -212,6 +277,10 @@ setupPyodide().then(() => { background-color: #00000000; /* Transparent background */ } +.v-toolbar.popup > * { + overflow-x: auto; +} + .popup-card-text { background-color: #1e1e1e; /* Matches the Monaco editor background */ opacity: v-bind(opacity); diff --git a/frontend/tools/Selection.vue b/frontend/tools/Selection.vue index 73f6386..3a22359 100644 --- a/frontend/tools/Selection.vue +++ b/frontend/tools/Selection.vue @@ -1,6 +1,7 @@ @@ -474,8 +479,8 @@ window.addEventListener('keydown', (event) => { variant="underlined"/> - - (H)ighlight the next clicked element in the models list + + (O)pen the next clicked element in the models list diff --git a/frontend/tools/Tools.vue b/frontend/tools/Tools.vue index a76b554..d56798e 100644 --- a/frontend/tools/Tools.vue +++ b/frontend/tools/Tools.vue @@ -13,7 +13,17 @@ import { import OrientationGizmo from "./OrientationGizmo.vue"; import type {PerspectiveCamera} from "three/src/cameras/PerspectiveCamera.js"; import {OrthographicCamera} from "three/src/cameras/OrthographicCamera.js"; -import {mdiClose, mdiCrosshairsGps, mdiDownload, mdiGithub, mdiLicense, mdiProjector, mdiScriptTextPlay} from '@mdi/js' +import { + mdiClose, + mdiCrosshairsGps, + mdiDownload, + mdiGithub, + mdiLicense, + mdiLightbulb, + mdiProjector, + mdiScriptTextPlay +} from '@mdi/js' +// @ts-expect-error import SvgIcon from '@jamescoyle/vue-icon'; import type {ModelViewerElement} from '@google/model-viewer'; import Loading from "../misc/Loading.vue"; @@ -51,7 +61,7 @@ const sett = ref(null); const showPlaygroundDialog = ref(false); (async () => { sett.value = await settings(); - showPlaygroundDialog.value = sett.value.code != ""; + showPlaygroundDialog.value = sett.value.pg_code != ""; })(); let selection: Ref> = ref([]); @@ -130,10 +140,14 @@ function removeObjectSelections(objName: string) { selectionComp.value?.updateDistances(); } -defineExpose({removeObjectSelections}); +defineExpose({removeObjectSelections, openPlayground: () => showPlaygroundDialog.value = true}); // Add keyboard shortcuts -window.addEventListener('keydown', (event) => { +document.addEventListener('keydown', (event) => { + if ((event.target as any)?.tagName && ((event.target as any).tagName === 'INPUT' || (event.target as any).tagName === 'TEXTAREA')) { + // Ignore key events when an input is focused, except for text inputs + return; + } if (event.key === 'p') toggleProjection(); else if (event.key === 'c') centerCamera(); else if (event.key === 'd') downloadSceneGlb(); @@ -155,6 +169,13 @@ window.addEventListener('keydown', (event) => { Re(c)enter Camera + + To rotate the light hold shift and drag the mouse or use two fingers
+ Note that this breaks slightly clipping planes for now... (restart to fix)
+ + + +
Selection ({{ selectionFaceCount() }}F {{ selectionEdgeCount() }}E {{ selectionVertexCount() }}V)
{ diff --git a/frontend/viewer/lighting.ts b/frontend/viewer/lighting.ts index 4d1cbe8..f82a903 100644 --- a/frontend/viewer/lighting.ts +++ b/frontend/viewer/lighting.ts @@ -2,12 +2,13 @@ import {ModelViewerElement} from '@google/model-viewer'; import {$scene} from "@google/model-viewer/lib/model-viewer-base"; import {settings} from "../misc/settings.ts"; +export let currentSceneRotation = 0; // radians, 0 is the default rotation + export async function setupLighting(modelViewer: ModelViewerElement) { modelViewer[$scene].environmentIntensity = (await settings()).environmentIntensity; // Code is mostly copied from the example at: https://modelviewer.dev/examples/stagingandcameras/#turnSkybox let lastX: number; let panning = false; - let skyboxAngle = 0; let radiansPerPixel: number; const startPan = () => { @@ -20,11 +21,11 @@ export async function setupLighting(modelViewer: ModelViewerElement) { const updatePan = (thisX: number) => { const delta = (thisX - lastX) * radiansPerPixel; lastX = thisX; - skyboxAngle += delta; + currentSceneRotation += delta; const orbit = modelViewer.getCameraOrbit(); orbit.theta += delta; modelViewer.cameraOrbit = orbit.toString(); - modelViewer.resetTurntableRotation(skyboxAngle); + modelViewer.resetTurntableRotation(currentSceneRotation); modelViewer.jumpCameraToGoal(); }