mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-21 23:14:27 +01:00
implement remaining keyboard shortcuts and improve line update interactions with other tools
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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') {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user