add a couple of useful tools

This commit is contained in:
Yeicor
2024-02-18 19:10:08 +01:00
parent 47f7d09385
commit 0f46d06463
4 changed files with 69 additions and 23 deletions

View File

@@ -55,7 +55,7 @@ for (let model of settings.preloadModels) {
<template #toolbar> <template #toolbar>
<v-toolbar-title>Tools</v-toolbar-title> <v-toolbar-title>Tools</v-toolbar-title>
</template> </template>
<tools :scene-mgr-data="refSData"/> <tools :ref-s-data="refSData"/>
</sidebar> </sidebar>
</v-layout> </v-layout>

View File

@@ -15,7 +15,7 @@ export async function mergePartial(glb: Uint8Array, name: string, document: Docu
let newDoc = await io.readBinary(glb); let newDoc = await io.readBinary(glb);
// noinspection TypeScriptValidateJSTypes // noinspection TypeScriptValidateJSTypes
// await newDoc.transform(dropByName(name), setNames(name)); await newDoc.transform(dropByName(name), setNames(name));
let merged = document.merge(newDoc); let merged = document.merge(newDoc);

View File

@@ -3,15 +3,16 @@ import {VBtn} from "vuetify/lib/components";
import {ref} from "vue"; import {ref} from "vue";
import OrientationGizmo from "./OrientationGizmo.vue"; import OrientationGizmo from "./OrientationGizmo.vue";
import {OrthographicCamera} from "three/src/cameras/OrthographicCamera"; import {OrthographicCamera} from "three/src/cameras/OrthographicCamera";
import {mdiCrosshairsGps, mdiProjector} from '@mdi/js' import {mdiCrosshairsGps, mdiCursorDefaultClick, mdiDownload, mdiProjector} from '@mdi/js'
import SvgIcon from '@jamescoyle/vue-icon'; import SvgIcon from '@jamescoyle/vue-icon';
import {SceneManagerData} from "../misc/scene"; import type {ModelViewerElement, RGBA} from '@google/model-viewer';
import type {ModelViewerElement} from '@google/model-viewer'; import type {Material} from '@google/model-viewer/lib/features/scene-graph/material.js';
import {SceneMgrRefData} from "../misc/scene";
let props = defineProps<{ sceneMgrData: SceneManagerData }>(); let props = defineProps<{ refSData: SceneMgrRefData }>();
function syncOrthoCamera(force: boolean) { function syncOrthoCamera(force: boolean) {
let scene = props.sceneMgrData.viewerScene; let scene = props.refSData.viewerScene;
if (!scene) return; if (!scene) return;
let perspectiveCam = (scene as any).__perspectiveCamera; let perspectiveCam = (scene as any).__perspectiveCamera;
if (force || perspectiveCam && scene.camera != perspectiveCam) { if (force || perspectiveCam && scene.camera != perspectiveCam) {
@@ -28,7 +29,7 @@ function syncOrthoCamera(force: boolean) {
let toggleProjectionText = ref('PERSP'); // Default to perspective camera let toggleProjectionText = ref('PERSP'); // Default to perspective camera
function toggleProjection() { function toggleProjection() {
let scene = props.sceneMgrData.viewerScene; let scene = props.refSData.viewerScene;
if (!scene) return; if (!scene) return;
let prevCam = scene.camera; let prevCam = scene.camera;
let wasPerspectiveCamera = prevCam.isPerspectiveCamera; let wasPerspectiveCamera = prevCam.isPerspectiveCamera;
@@ -44,32 +45,76 @@ function toggleProjection() {
} }
function centerCamera() { function centerCamera() {
let viewer: ModelViewerElement = props.sceneMgrData.viewer; let viewer: ModelViewerElement = props.refSData.viewer;
if (!viewer) return; if (!viewer) return;
viewer.updateFraming(); viewer.updateFraming();
} }
let selectionEnabled = ref(false);
let prevHighlightedMaterial: Material | null = null;
let hasListener = false;
let selectionListener = (event: MouseEvent) => {
if (!selectionEnabled.value) return;
let viewer: ModelViewerElement = props.refSData.viewer;
const material = viewer.materialFromPoint(event.clientX, event.clientY);
if (material !== null && prevHighlightedMaterial?.index === material.index) return
if (prevHighlightedMaterial) {
prevHighlightedMaterial.pbrMetallicRoughness.setBaseColorFactor(
(prevHighlightedMaterial as any).__prevBaseColorFactor);
}
if (!material) {
prevHighlightedMaterial = null;
return;
}
(material as any).__prevBaseColorFactor = [...material.pbrMetallicRoughness.baseColorFactor];
material.pbrMetallicRoughness.setBaseColorFactor([1, 0, 0, 1] as RGBA);
prevHighlightedMaterial = material;
};
function toggleSelection() {
let viewer: ModelViewerElement = props.refSData.viewer;
if (!viewer) return;
selectionEnabled.value = !selectionEnabled.value;
if (selectionEnabled.value) {
if (!hasListener) {
viewer.addEventListener('mousemove', selectionListener);
hasListener = true;
}
} else {
if (prevHighlightedMaterial) {
prevHighlightedMaterial.pbrMetallicRoughness.setBaseColorFactor(
(prevHighlightedMaterial as any).__prevBaseColorFactor);
prevHighlightedMaterial = null;
}
}
}
async function downloadSceneGlb() {
let viewer = props.refSData.viewer;
if (!viewer) return;
const glTF = await viewer.exportScene();
const file = new File([glTF], "export.glb");
const link = document.createElement("a");
link.download = file.name;
link.href = URL.createObjectURL(file);
link.click();
}
</script> </script>
<template> <template>
<orientation-gizmo :scene="props.sceneMgrData.viewerScene" v-if="props.sceneMgrData.viewerScene !== null"/> <orientation-gizmo :scene="props.refSData.viewerScene" v-if="props.refSData.viewerScene !== null"/>
<v-btn icon="" @click="toggleProjection"><span class="icon-detail">{{ toggleProjectionText }}</span> <v-btn icon="" @click="toggleProjection"><span class="icon-detail">{{ toggleProjectionText }}</span>
<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">
<svg-icon type="mdi" :path="mdiCrosshairsGps"/> <svg-icon type="mdi" :path="mdiCrosshairsGps"/>
</v-btn> </v-btn>
<v-btn icon="" @click="centerCamera"> <v-btn icon="" @click="toggleSelection" :variant="selectionEnabled ? 'tonal' : 'elevated'">
<svg-icon type="mdi" :path="mdiCrosshairsGps"/> <svg-icon type="mdi" :path="mdiCursorDefaultClick"/>
</v-btn> </v-btn>
<v-btn icon="" @click="centerCamera"> <v-btn icon="" @click="downloadSceneGlb">
<svg-icon type="mdi" :path="mdiCrosshairsGps"/> <svg-icon type="mdi" :path="mdiDownload"/>
</v-btn>
<v-btn icon="" @click="centerCamera">
<svg-icon type="mdi" :path="mdiCrosshairsGps"/>
</v-btn>
<v-btn icon="" @click="centerCamera">
<svg-icon type="mdi" :path="mdiCrosshairsGps"/>
</v-btn> </v-btn>
</template> </template>

View File

@@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import {settings} from "../misc/settings"; import {settings} from "../misc/settings";
import {ModelViewerElement} from '@google/model-viewer'; import {ModelViewerElement, RGBA} from '@google/model-viewer';
import {onMounted, ref} from "vue"; import {onMounted, ref} from "vue";
import {$scene} from "@google/model-viewer/lib/model-viewer-base"; import {$scene} from "@google/model-viewer/lib/model-viewer-base";
import type {ModelScene} from "@google/model-viewer/lib/three-components/ModelScene"; import type {ModelScene} from "@google/model-viewer/lib/three-components/ModelScene";
export type ModelViewerInfo = { viewer: ModelViewerElement, scene: ModelScene }; export type ModelViewerInfo = { viewer: ModelViewerElement, scene: ModelScene };
let _ = ModelViewerElement; // HACK: Needed to avoid tree shaking ModelViewerElement.modelCacheSize = 0; // Also needed to avoid tree shaking
const emit = defineEmits<{ const emit = defineEmits<{
load: [info: ModelViewerInfo] load: [info: ModelViewerInfo]
@@ -33,7 +33,8 @@ onMounted(() => {
<template> <template>
<model-viewer ref="viewer" <model-viewer ref="viewer"
style="width: 100%; height: 100%" :src="props.src" alt="The 3D model(s)" camera-controls camera-orbit="30deg 75deg auto" style="width: 100%; height: 100%" :src="props.src" alt="The 3D model(s)" camera-controls
camera-orbit="30deg 75deg auto"
max-camera-orbit="Infinity 180deg auto" min-camera-orbit="-Infinity 0deg auto" disable-tap max-camera-orbit="Infinity 180deg auto" min-camera-orbit="-Infinity 0deg auto" disable-tap
:exposure="settings.exposure" :shadow-intensity="settings.shadowIntensity" interaction-prompt="none" :exposure="settings.exposure" :shadow-intensity="settings.shadowIntensity" interaction-prompt="none"
:autoplay="settings.autoplay" :ar="settings.arModes.length > 0" :ar-modes="settings.arModes" :autoplay="settings.autoplay" :ar="settings.arModes.length > 0" :ar-modes="settings.arModes"