implement remaining keyboard shortcuts and improve line update interactions with other tools

This commit is contained in:
Yeicor
2024-03-01 20:20:25 +01:00
parent f1776d8436
commit e42ccd795e
3 changed files with 48 additions and 28 deletions

View File

@@ -2,6 +2,7 @@
import {onMounted, onUpdated, ref} from "vue"; import {onMounted, onUpdated, ref} from "vue";
import type {ModelScene} from "@google/model-viewer/lib/three-components/ModelScene"; import type {ModelScene} from "@google/model-viewer/lib/three-components/ModelScene";
import * as OrientationGizmoRaw from "three-orientation-gizmo/src/OrientationGizmo"; import * as OrientationGizmoRaw from "three-orientation-gizmo/src/OrientationGizmo";
import type {ModelViewerElement} from '@google/model-viewer';
// Optimized minimal dependencies from three // Optimized minimal dependencies from three
import {Vector3} from "three/src/math/Vector3.js"; import {Vector3} from "three/src/math/Vector3.js";
@@ -9,7 +10,7 @@ import {Matrix4} from "three/src/math/Matrix4.js";
globalThis.THREE = {Vector3, Matrix4} as any // HACK: Required for the gizmo to work globalThis.THREE = {Vector3, Matrix4} as any // HACK: Required for the gizmo to work
const props = defineProps<{ scene: ModelScene }>(); const props = defineProps<{ elem: ModelViewerElement | null, scene: ModelScene }>();
function createGizmo(expectedParent: HTMLElement, scene: ModelScene): HTMLElement { function createGizmo(expectedParent: HTMLElement, scene: ModelScene): HTMLElement {
// noinspection SpellCheckingInspection // noinspection SpellCheckingInspection
@@ -43,6 +44,8 @@ function createGizmo(expectedParent: HTMLElement, scene: ModelScene): HTMLElemen
(scene as any).__perspectiveCamera.lookAt(lookAt); (scene as any).__perspectiveCamera.lookAt(lookAt);
} }
scene.queueRender(); scene.queueRender();
requestIdleCallback(() => props.elem?.dispatchEvent(
new CustomEvent('camera-change', {detail: {source: 'none'}})))
} }
return gizmo; return gizmo;
} }

View File

@@ -185,6 +185,26 @@ function toggleShowBoundingBox() {
let viewerFound = false let viewerFound = false
let firstLoad = true; let firstLoad = true;
let cameraChangeWaiting = false;
let cameraChangeLast = 0
let onCameraChange = () => {
// Avoid updates while dragging (slow operation)
cameraChangeLast = performance.now();
if (cameraChangeWaiting) return;
cameraChangeWaiting = true;
let waitingHandler: () => void;
waitingHandler = () => {
// Ignore also inertia
if (performance.now() - cameraChangeLast > 250) {
updateBoundingBox();
cameraChangeWaiting = false;
} else {
// If the camera is still moving, wait a bit more
setTimeout(waitingHandler, 100);
}
};
setTimeout(waitingHandler, 100); // Wait for the camera to stop moving
};
watch(() => props.viewer, (viewer) => { watch(() => props.viewer, (viewer) => {
if (!viewer) return; if (!viewer) return;
if (viewerFound) return; if (viewerFound) return;
@@ -204,26 +224,7 @@ watch(() => props.viewer, (viewer) => {
updateBoundingBox(); updateBoundingBox();
} }
}); });
let isWaiting = false; elem.addEventListener('camera-change', onCameraChange);
let lastCameraChange = 0
elem.addEventListener('camera-change', () => {
// Avoid updates while dragging (slow operation)
lastCameraChange = performance.now();
if (isWaiting) return;
isWaiting = true;
let waitingHandler: () => void;
waitingHandler = () => {
// Ignore also inertia
if (performance.now() - lastCameraChange > 250) {
updateBoundingBox();
isWaiting = false;
} else {
// If the camera is still moving, wait a bit more
setTimeout(waitingHandler, 100);
}
};
setTimeout(waitingHandler, 100); // Wait for the camera to stop moving
});
}); });
}); });
@@ -360,6 +361,8 @@ function updateDistances() {
return; return;
} }
defineExpose({onCameraChange})
// Add keyboard shortcuts // Add keyboard shortcuts
window.addEventListener('keydown', (event) => { window.addEventListener('keydown', (event) => {
if (event.key === 's') { if (event.key === 's') {

View File

@@ -27,6 +27,7 @@ const SelectionComponent = defineAsyncComponent({
loadingComponent: () => "Loading...", loadingComponent: () => "Loading...",
delay: 0, delay: 0,
}); });
let selectionComp = ref<InstanceType<typeof SelectionComponent> | null>(null);
const LicensesDialogContent = defineAsyncComponent({ const LicensesDialogContent = defineAsyncComponent({
loader: () => import("./LicensesDialogContent.vue"), loader: () => import("./LicensesDialogContent.vue"),
@@ -69,13 +70,16 @@ function toggleProjection() {
if (wasPerspectiveCamera) { if (wasPerspectiveCamera) {
(scene as any).__perspectiveCamera = prevCam; // Save the default perspective camera (scene as any).__perspectiveCamera = prevCam; // Save the default perspective camera
// This hack also needs to sync the camera position and target // This hack also needs to sync the camera position and target
requestAnimationFrame(() => syncOrthoCamera(true)); syncOrthoCamera(true);
} else { } else {
// Restore the default perspective camera // Restore the default perspective camera
scene.camera = (scene as any).__perspectiveCamera; scene.camera = (scene as any).__perspectiveCamera;
props.viewer.scene.queueRender() // Force rerender of model-viewer props.viewer.scene.queueRender() // Force rerender of model-viewer
} }
toggleProjectionText.value = wasPerspectiveCamera ? 'ORTHO' : 'PERSP'; toggleProjectionText.value = wasPerspectiveCamera ? 'ORTHO' : 'PERSP';
// The camera change may take a few frames to take effect, dispatch the event after a delay
requestIdleCallback(() => props.viewer?.elem?.dispatchEvent(
new CustomEvent('camera-change', {detail: {source: 'none'}})))
} }
async function centerCamera() { async function centerCamera() {
@@ -98,30 +102,40 @@ async function downloadSceneGlb() {
async function openGithub() { async function openGithub() {
window.open('https://github.com/yeicor-3d/yet-another-cad-viewer', '_blank') window.open('https://github.com/yeicor-3d/yet-another-cad-viewer', '_blank')
} }
// Add keyboard shortcuts
window.addEventListener('keydown', (event) => {
if (event.key === 'p') toggleProjection();
else if (event.key === 'c') centerCamera();
else if (event.key === 'd') downloadSceneGlb();
else if (event.key === 'g') openGithub();
});
</script> </script>
<template> <template>
<orientation-gizmo :scene="props.viewer.scene" v-if="props.viewer?.scene"/> <orientation-gizmo :scene="props.viewer.scene" :elem="props.viewer?.elem" v-if="props.viewer?.scene"/>
<v-divider/> <v-divider/>
<h5>Camera</h5> <h5>Camera</h5>
<v-btn icon @click="toggleProjection"><span class="icon-detail">{{ toggleProjectionText }}</span> <v-btn icon @click="toggleProjection"><span class="icon-detail">{{ toggleProjectionText }}</span>
<v-tooltip activator="parent">Toggle Projection<br/>(currently <v-tooltip activator="parent">Toggle (P)rojection<br/>(currently
{{ toggleProjectionText === 'PERSP' ? 'perspective' : 'orthographic' }}) {{ toggleProjectionText === 'PERSP' ? 'perspective' : 'orthographic' }})
</v-tooltip> </v-tooltip>
<svg-icon type="mdi" :path="mdiProjector"></svg-icon> <svg-icon type="mdi" :path="mdiProjector"></svg-icon>
</v-btn> </v-btn>
<v-btn icon @click="centerCamera"> <v-btn icon @click="centerCamera">
<v-tooltip activator="parent">Recenter Camera</v-tooltip> <v-tooltip activator="parent">Re(c)enter Camera</v-tooltip>
<svg-icon type="mdi" :path="mdiCrosshairsGps"/> <svg-icon type="mdi" :path="mdiCrosshairsGps"/>
</v-btn> </v-btn>
<v-divider/> <v-divider/>
<h5>Selection ({{ selectionFaceCount() }}F {{ selectionEdgeCount() }}E {{ selectionVertexCount() }}V)</h5> <h5>Selection ({{ selectionFaceCount() }}F {{ selectionEdgeCount() }}E {{ selectionVertexCount() }}V)</h5>
<selection-component :viewer="props.viewer" v-model="selection" @findModel="(name) => emit('findModel', name)"/> <selection-component :ref="selectionComp" :viewer="props.viewer" v-model="selection"
@findModel="(name) => emit('findModel', name)"/>
<v-divider/> <v-divider/>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<h5>Extras</h5> <h5>Extras</h5>
<v-btn icon @click="downloadSceneGlb"> <v-btn icon @click="downloadSceneGlb">
<v-tooltip activator="parent">Download Scene</v-tooltip> <v-tooltip activator="parent">(D)ownload Scene</v-tooltip>
<svg-icon type="mdi" :path="mdiDownload"/> <svg-icon type="mdi" :path="mdiDownload"/>
</v-btn> </v-btn>
<v-dialog id="licenses-dialog" fullscreen> <v-dialog id="licenses-dialog" fullscreen>
@@ -148,7 +162,7 @@ async function openGithub() {
</template> </template>
</v-dialog> </v-dialog>
<v-btn icon @click="openGithub"> <v-btn icon @click="openGithub">
<v-tooltip activator="parent">Open GitHub</v-tooltip> <v-tooltip activator="parent">Open (G)itHub</v-tooltip>
<svg-icon type="mdi" :path="mdiGithub"/> <svg-icon type="mdi" :path="mdiGithub"/>
</v-btn> </v-btn>
<div ref="statsHolder"></div> <div ref="statsHolder"></div>