mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2026-02-03 20:08:57 +01:00
cleaning up
This commit is contained in:
77
src/tools/OrientationGizmo.vue
Normal file
77
src/tools/OrientationGizmo.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<script setup lang="ts">
|
||||
import {onMounted, onUpdated, ref} from "vue";
|
||||
import type {ModelScene} from "@google/model-viewer/lib/three-components/ModelScene";
|
||||
import * as OrientationGizmoRaw from "three-orientation-gizmo/src/OrientationGizmo";
|
||||
|
||||
// Optimized minimal dependencies from three to avoid more async imports
|
||||
import {Vector3} from "three/src/math/Vector3.js";
|
||||
import {Matrix4} from "three/src/math/Matrix4.js";
|
||||
|
||||
globalThis.THREE = {Vector3, Matrix4} as any // HACK: Required for the gizmo to work
|
||||
|
||||
const props = defineProps({
|
||||
scene: Object
|
||||
});
|
||||
|
||||
function createGizmo(expectedParent: HTMLElement, scene: ModelScene): HTMLElement {
|
||||
// noinspection SpellCheckingInspection
|
||||
let gizmo = new OrientationGizmoRaw(scene.camera, {
|
||||
size: expectedParent.clientWidth,
|
||||
bubbleSizePrimary: expectedParent.clientWidth / 12,
|
||||
bubbleSizeSeconday: expectedParent.clientWidth / 14,
|
||||
fontSize: (expectedParent.clientWidth / 10) + "px"
|
||||
});
|
||||
// HACK: Swap axes to match A-Frame
|
||||
for (let swap of [["y", "-z"], ["z", "-y"], ["z", "-z"]]) {
|
||||
let indexA = gizmo.bubbles.findIndex((bubble) => bubble.axis == swap[0])
|
||||
let indexB = gizmo.bubbles.findIndex((bubble) => bubble.axis == swap[1])
|
||||
let dirA = gizmo.bubbles[indexA].direction.clone();
|
||||
let dirB = gizmo.bubbles[indexB].direction.clone();
|
||||
gizmo.bubbles[indexA].direction.copy(dirB);
|
||||
gizmo.bubbles[indexB].direction.copy(dirA);
|
||||
}
|
||||
// Append and listen for events
|
||||
gizmo.onAxisSelected = (axis: { direction: { x: any; y: any; z: any; }; }) => {
|
||||
let lookFrom = scene.getCamera().position.clone();
|
||||
let lookAt = scene.getTarget().clone().add(scene.target.position);
|
||||
let magnitude = lookFrom.clone().sub(lookAt).length()
|
||||
let direction = new Vector3(axis.direction.x, axis.direction.y, axis.direction.z);
|
||||
let newLookFrom = lookAt.clone().add(direction.clone().multiplyScalar(magnitude));
|
||||
//console.log("New camera position", newLookFrom)
|
||||
scene.getCamera().position.copy(newLookFrom);
|
||||
scene.getCamera().lookAt(lookAt);
|
||||
if ((scene as any).__perspectiveCamera) { // HACK: Make the hacky ortho also work
|
||||
(scene as any).__perspectiveCamera.position.copy(newLookFrom);
|
||||
(scene as any).__perspectiveCamera.lookAt(lookAt);
|
||||
}
|
||||
scene.queueRender();
|
||||
}
|
||||
return gizmo;
|
||||
}
|
||||
|
||||
// Mount, unmount and listen for scene changes
|
||||
let container = ref<HTMLElement | null>(null);
|
||||
|
||||
let gizmo: HTMLElement & { update: () => void }
|
||||
|
||||
function updateGizmo() {
|
||||
if (gizmo.isConnected) {
|
||||
gizmo.update();
|
||||
requestIdleCallback(updateGizmo);
|
||||
}
|
||||
}
|
||||
|
||||
let reinstall = () => {
|
||||
if (gizmo) container.value.removeChild(gizmo);
|
||||
gizmo = createGizmo(container.value, props.scene as ModelScene) as typeof gizmo;
|
||||
container.value.appendChild(gizmo);
|
||||
requestIdleCallback(updateGizmo); // Low priority updates
|
||||
}
|
||||
onMounted(reinstall)
|
||||
onUpdated(reinstall);
|
||||
// onUnmounted is not needed because the gizmo is removed when the container is removed
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="container" class="orientation-gizmo"/>
|
||||
</template>
|
||||
69
src/tools/Tools.vue
Normal file
69
src/tools/Tools.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<script setup lang="ts">
|
||||
import {VBtn, VIcon} from "vuetify/lib/components";
|
||||
import {ref} from "vue";
|
||||
import OrientationGizmo from "./OrientationGizmo.vue";
|
||||
import type {ModelScene} from "@google/model-viewer/lib/three-components/ModelScene";
|
||||
import {OrthographicCamera} from "three";
|
||||
|
||||
const props = defineProps({
|
||||
modelViewerInfo: Object
|
||||
});
|
||||
|
||||
|
||||
function syncOrthoCamera(force: boolean) {
|
||||
if (!props.modelViewerInfo) return;
|
||||
let scene: ModelScene = props.modelViewerInfo.scene
|
||||
let perspectiveCam = (scene as any).__perspectiveCamera;
|
||||
if (force || perspectiveCam && scene.camera != perspectiveCam) {
|
||||
// Get zoom level from perspective camera
|
||||
let dist = scene.getTarget().distanceToSquared(perspectiveCam.position);
|
||||
let w = scene.aspect * dist ** 1.1 / 4000;
|
||||
let h = dist ** 1.1 / 4000;
|
||||
(scene as any).camera = new OrthographicCamera(-w, w, h, -h, perspectiveCam.near, perspectiveCam.far);
|
||||
scene.camera.position.copy(perspectiveCam.position);
|
||||
scene.camera.lookAt(scene.getTarget().clone().add(scene.target.position));
|
||||
requestAnimationFrame(() => syncOrthoCamera(false));
|
||||
}
|
||||
}
|
||||
|
||||
let toggleProjectionText = ref('PERSP'); // Default to perspective camera
|
||||
function toggleProjection() {
|
||||
if (!props.modelViewerInfo) return;
|
||||
let scene: ModelScene = props.modelViewerInfo.scene
|
||||
let prevCam = scene.camera;
|
||||
let wasPerspectiveCamera = prevCam.isPerspectiveCamera;
|
||||
if (wasPerspectiveCamera) {
|
||||
(scene as any).__perspectiveCamera = prevCam; // Save the default perspective camera
|
||||
// This hack also needs to sync the camera position and target
|
||||
requestAnimationFrame(() => syncOrthoCamera(true));
|
||||
} else {
|
||||
// Restore the default perspective camera
|
||||
scene.camera = (scene as any).__perspectiveCamera;
|
||||
}
|
||||
toggleProjectionText.value = wasPerspectiveCamera ? 'ORTHO' : 'PERSP';
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<orientation-gizmo v-if="props.modelViewerInfo" :scene="props.modelViewerInfo.scene"/>
|
||||
<v-btn icon="mdi-projector" @click="toggleProjection"><span class="icon-detail">{{ toggleProjectionText }}</span>
|
||||
<v-icon icon="mdi-projector"></v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.icon-detail {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
font-size: xx-small;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.icon-detail + .v-icon {
|
||||
position: relative;
|
||||
top: 5px;
|
||||
}
|
||||
</style>
|
||||
45
src/tools/settings.ts
Normal file
45
src/tools/settings.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
// These are the default values for the settings, which are overridden below
|
||||
export const settings = {
|
||||
// @ts-ignore
|
||||
preloadModels: [new URL('../../assets/fox.glb', import.meta.url).href, new URL('../../assets/logo.glbs', import.meta.url).href, "ws://localhost:8080"],
|
||||
// ModelViewer settings
|
||||
autoplay: true,
|
||||
arModes: 'webxr scene-viewer quick-look',
|
||||
exposure: 1,
|
||||
shadowIntensity: 0,
|
||||
background: '',
|
||||
}
|
||||
|
||||
const firstTimeNames = []; // Needed for array values, which clear the array when overridden
|
||||
function parseSetting(name: string, value: string): any {
|
||||
let arrayElem = name.endsWith(".0")
|
||||
if (arrayElem) name = name.slice(0, -2);
|
||||
let prevValue = settings[name];
|
||||
if (prevValue === undefined) throw new Error(`Unknown setting: ${name}`);
|
||||
if (Array.isArray(prevValue) && !arrayElem) {
|
||||
let toExtend = []
|
||||
if (!firstTimeNames.includes(name)) {
|
||||
firstTimeNames.push(name);
|
||||
} else {
|
||||
toExtend = prevValue;
|
||||
}
|
||||
toExtend.push(parseSetting(name + ".0", value));
|
||||
return toExtend;
|
||||
}
|
||||
switch (typeof prevValue) {
|
||||
case 'boolean':
|
||||
return value === 'true';
|
||||
case 'number':
|
||||
return Number(value);
|
||||
case 'string':
|
||||
return value;
|
||||
default:
|
||||
throw new Error(`Unknown setting type: ${typeof prevValue}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-override any settings from the URL
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.forEach((value, key) => {
|
||||
if (key in settings) settings[key] = parseSetting(key, value);
|
||||
})
|
||||
Reference in New Issue
Block a user