mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 22:24:17 +01:00
add the ability to expand models by clicking on them
This commit is contained in:
@@ -26,6 +26,7 @@ let openSidebarsByDefault: Ref<boolean> = ref(window.innerWidth > 1200);
|
|||||||
let sceneUrl = ref("")
|
let sceneUrl = ref("")
|
||||||
let viewer: Ref<InstanceType<typeof ModelViewerWrapperT> | null> = ref(null);
|
let viewer: Ref<InstanceType<typeof ModelViewerWrapperT> | null> = ref(null);
|
||||||
let document = shallowRef(new Document());
|
let document = shallowRef(new Document());
|
||||||
|
let models: Ref<InstanceType<typeof Models> | null> = ref(null)
|
||||||
|
|
||||||
async function onModelLoadRequest(model: NetworkUpdateEvent) {
|
async function onModelLoadRequest(model: NetworkUpdateEvent) {
|
||||||
await SceneMgr.loadModel(sceneUrl, document, model.name, model.url);
|
await SceneMgr.loadModel(sceneUrl, document, model.name, model.url);
|
||||||
@@ -37,6 +38,10 @@ function onModelRemoveRequest(name: string) {
|
|||||||
document.value = document.value.clone(); // Force update from this component!
|
document.value = document.value.clone(); // Force update from this component!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onFindModel(name: string) {
|
||||||
|
Models.value.findModel(name);
|
||||||
|
}
|
||||||
|
|
||||||
// Set up the load model event listener
|
// Set up the load model event listener
|
||||||
let networkMgr = new NetworkManager();
|
let networkMgr = new NetworkManager();
|
||||||
networkMgr.addEventListener('update', onModelLoadRequest);
|
networkMgr.addEventListener('update', onModelLoadRequest);
|
||||||
@@ -69,7 +74,7 @@ async function loadModelManual() {
|
|||||||
<svg-icon type="mdi" :path="mdiPlus"/>
|
<svg-icon type="mdi" :path="mdiPlus"/>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<models :viewer="viewer" :document="document" @remove="onModelRemoveRequest"/>
|
<models ref="models" :viewer="viewer" :document="document" @remove="onModelRemoveRequest"/>
|
||||||
</sidebar>
|
</sidebar>
|
||||||
|
|
||||||
<!-- The right collapsible sidebar has the list of tools -->
|
<!-- The right collapsible sidebar has the list of tools -->
|
||||||
@@ -77,7 +82,7 @@ async function loadModelManual() {
|
|||||||
<template #toolbar>
|
<template #toolbar>
|
||||||
<v-toolbar-title>Tools</v-toolbar-title>
|
<v-toolbar-title>Tools</v-toolbar-title>
|
||||||
</template>
|
</template>
|
||||||
<tools :viewer="viewer"/>
|
<tools :viewer="viewer" @findModel="(name) => models.findModel(name)"/>
|
||||||
</sidebar>
|
</sidebar>
|
||||||
|
|
||||||
</v-layout>
|
</v-layout>
|
||||||
|
|||||||
@@ -25,17 +25,22 @@ const emit = defineEmits<{ remove: [] }>()
|
|||||||
|
|
||||||
let modelName = props.meshes[0].getExtras()[extrasNameKey] // + " blah blah blah blah blag blah blah blah"
|
let modelName = props.meshes[0].getExtras()[extrasNameKey] // + " blah blah blah blah blag blah blah blah"
|
||||||
|
|
||||||
|
// Reactive properties
|
||||||
|
const enabledFeatures = defineModel<Array<number>>("enabledFeatures", {default: [0, 1, 2]});
|
||||||
|
const opacity = defineModel<number>("opacity", {default: 1});
|
||||||
|
// TODO: Clipping planes (+ stencil!)
|
||||||
|
|
||||||
|
// Count the number of faces, edges and vertices
|
||||||
let faceCount = props.meshes.map((m) => m.listPrimitives().filter(p => p.getMode() === WebGL2RenderingContext.TRIANGLES).length).reduce((a, b) => a + b, 0)
|
let faceCount = props.meshes.map((m) => m.listPrimitives().filter(p => p.getMode() === WebGL2RenderingContext.TRIANGLES).length).reduce((a, b) => a + b, 0)
|
||||||
let edgeCount = props.meshes.map((m) => m.listPrimitives().filter(p => p.getMode() in [WebGL2RenderingContext.LINE_STRIP, WebGL2RenderingContext.LINES]).length).reduce((a, b) => a + b, 0)
|
let edgeCount = props.meshes.map((m) => m.listPrimitives().filter(p => p.getMode() in [WebGL2RenderingContext.LINE_STRIP, WebGL2RenderingContext.LINES]).length).reduce((a, b) => a + b, 0)
|
||||||
let vertexCount = props.meshes.map((m) => m.listPrimitives().filter(p => p.getMode() === WebGL2RenderingContext.POINTS).length).reduce((a, b) => a + b, 0)
|
let vertexCount = props.meshes.map((m) => m.listPrimitives().filter(p => p.getMode() === WebGL2RenderingContext.POINTS).length).reduce((a, b) => a + b, 0)
|
||||||
|
|
||||||
const enabledFeatures = defineModel<Array<number>>("enabledFeatures", {default: [0, 1, 2]});
|
// Set initial defaults for the enabled features
|
||||||
const opacity = defineModel<number>("opacity", {default: 1});
|
|
||||||
|
|
||||||
if (faceCount === 0) enabledFeatures.value = enabledFeatures.value.filter((f) => f !== 0)
|
if (faceCount === 0) enabledFeatures.value = enabledFeatures.value.filter((f) => f !== 0)
|
||||||
if (edgeCount === 0) enabledFeatures.value = enabledFeatures.value.filter((f) => f !== 1)
|
if (edgeCount === 0) enabledFeatures.value = enabledFeatures.value.filter((f) => f !== 1)
|
||||||
if (vertexCount === 0) enabledFeatures.value = enabledFeatures.value.filter((f) => f !== 2)
|
if (vertexCount === 0) enabledFeatures.value = enabledFeatures.value.filter((f) => f !== 2)
|
||||||
|
|
||||||
|
// Listeners for changes in the properties (or viewer reloads)
|
||||||
function onEnabledFeaturesChange(newEnabledFeatures: Array<number>) {
|
function onEnabledFeaturesChange(newEnabledFeatures: Array<number>) {
|
||||||
//console.log('Enabled features may have changed', newEnabledFeatures)
|
//console.log('Enabled features may have changed', newEnabledFeatures)
|
||||||
let scene = props.viewer?.scene;
|
let scene = props.viewer?.scene;
|
||||||
@@ -106,7 +111,6 @@ function onModelLoad() {
|
|||||||
scene.queueRender()
|
scene.queueRender()
|
||||||
|
|
||||||
// Furthermore...
|
// Furthermore...
|
||||||
|
|
||||||
// Enabled features may have been reset after a reload
|
// Enabled features may have been reset after a reload
|
||||||
onEnabledFeaturesChange(enabledFeatures.value)
|
onEnabledFeaturesChange(enabledFeatures.value)
|
||||||
// Opacity may have been reset after a reload
|
// Opacity may have been reset after a reload
|
||||||
@@ -124,7 +128,7 @@ if (props.viewer.elem) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-expansion-panel>
|
<v-expansion-panel :value="modelName">
|
||||||
<v-expansion-panel-title expand-icon="hide-this-icon" collapse-icon="hide-this-icon">
|
<v-expansion-panel-title expand-icon="hide-this-icon" collapse-icon="hide-this-icon">
|
||||||
<v-btn-toggle v-model="enabledFeatures" multiple @click.stop color="surface-light">
|
<v-btn-toggle v-model="enabledFeatures" multiple @click.stop color="surface-light">
|
||||||
<v-btn icon>
|
<v-btn icon>
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ import Loading from "../misc/Loading.vue";
|
|||||||
import {Document, Mesh} from "@gltf-transform/core";
|
import {Document, Mesh} from "@gltf-transform/core";
|
||||||
import {extrasNameKey} from "../misc/gltf";
|
import {extrasNameKey} from "../misc/gltf";
|
||||||
import Model from "./Model.vue";
|
import Model from "./Model.vue";
|
||||||
|
import {ref} from "vue";
|
||||||
|
|
||||||
const props = defineProps<{ viewer: InstanceType<typeof ModelViewerWrapper> | null, document: Document }>();
|
const props = defineProps<{ viewer: InstanceType<typeof ModelViewerWrapper> | null, document: Document }>();
|
||||||
const emit = defineEmits<{ remove: [string] }>()
|
const emit = defineEmits<{ remove: [string] }>()
|
||||||
|
|
||||||
|
let expandedNames = ref<Array<string>>([]);
|
||||||
|
|
||||||
function meshesList(document: Document): Array<Array<Mesh>> {
|
function meshesList(document: Document): Array<Array<Mesh>> {
|
||||||
// Grouped by shared name
|
// Grouped by shared name
|
||||||
return document.getRoot().listMeshes().reduce((acc, mesh) => {
|
return document.getRoot().listMeshes().reduce((acc, mesh) => {
|
||||||
@@ -30,11 +33,19 @@ function meshName(mesh: Mesh) {
|
|||||||
function onRemove(mesh: Mesh) {
|
function onRemove(mesh: Mesh) {
|
||||||
emit('remove', meshName(mesh))
|
emit('remove', meshName(mesh))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findModel(name: string) {
|
||||||
|
console.log('Find model', name);
|
||||||
|
if (!expandedNames.value.includes(name)) expandedNames.value.push(name);
|
||||||
|
console.log('Expanded', expandedNames.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({findModel})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Loading v-if="!props.document"/>
|
<Loading v-if="!props.document"/>
|
||||||
<v-expansion-panels v-else v-for="meshes in meshesList(props.document)" :key="meshName(meshes[0])">
|
<v-expansion-panels v-else v-for="meshes in meshesList(props.document)" :key="meshName(meshes[0])" v-model="expandedNames" multiple>
|
||||||
<model :meshes="meshes" :viewer="props.viewer" :document="props.document" @remove="onRemove(meshes[0])"/>
|
<model :meshes="meshes" :viewer="props.viewer" :document="props.document" @remove="onRemove(meshes[0])"/>
|
||||||
</v-expansion-panels>
|
</v-expansion-panels>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import {VBtn, VSelect, VTooltip} from "vuetify/lib/components";
|
|||||||
import SvgIcon from '@jamescoyle/vue-icon/lib/svg-icon.vue';
|
import SvgIcon from '@jamescoyle/vue-icon/lib/svg-icon.vue';
|
||||||
import type {ModelViewerElement} from '@google/model-viewer';
|
import type {ModelViewerElement} from '@google/model-viewer';
|
||||||
import type {ModelScene} from "@google/model-viewer/lib/three-components/ModelScene";
|
import type {ModelScene} from "@google/model-viewer/lib/three-components/ModelScene";
|
||||||
import {mdiCursorDefaultClick} from '@mdi/js';
|
import {mdiCubeOutline, mdiCursorDefaultClick, mdiFeatureSearch, mdiRuler} from '@mdi/js';
|
||||||
import type {Intersection, Material, Object3D} from "three";
|
import type {Intersection, Material, Object3D} from "three";
|
||||||
import {Raycaster} from "three";
|
import {Raycaster} from "three";
|
||||||
import type ModelViewerWrapperT from "./ModelViewerWrapper.vue";
|
import type ModelViewerWrapperT from "./ModelViewerWrapper.vue";
|
||||||
|
import {extrasNameKey} from "../misc/gltf";
|
||||||
|
|
||||||
export type MObject3D = Object3D & {
|
export type MObject3D = Object3D & {
|
||||||
userData: { noHit?: boolean },
|
userData: { noHit?: boolean },
|
||||||
@@ -15,12 +16,16 @@ export type MObject3D = Object3D & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let props = defineProps<{ viewer: typeof ModelViewerWrapperT | null }>();
|
let props = defineProps<{ viewer: typeof ModelViewerWrapperT | null }>();
|
||||||
|
let emit = defineEmits<{ findModel: [string] }>();
|
||||||
let selectionEnabled = ref(false);
|
let selectionEnabled = ref(false);
|
||||||
let selectedMaterials = defineModel<Array<Intersection<MObject3D>>>({default: []});
|
let selected = defineModel<Array<Intersection<MObject3D>>>({default: []});
|
||||||
|
let highlightNextSelection = ref([false, false]); // Second is whether selection was enabled before
|
||||||
|
let showBoundingBox = ref<Boolean>(false);
|
||||||
|
|
||||||
let hasListener = false;
|
let hasListener = false;
|
||||||
let mouseDownAt: [number, number] | null = null;
|
let mouseDownAt: [number, number] | null = null;
|
||||||
let selectFilter = ref('Any');
|
let selectFilter = ref('Any');
|
||||||
const ray_caster = new Raycaster();
|
const raycaster = new Raycaster();
|
||||||
|
|
||||||
let selectionMoveListener = (event: MouseEvent) => {
|
let selectionMoveListener = (event: MouseEvent) => {
|
||||||
if (!selectionEnabled.value) return;
|
if (!selectionEnabled.value) return;
|
||||||
@@ -44,15 +49,15 @@ let selectionListener = (event: MouseEvent) => {
|
|||||||
// NOTE: Need to access internal as the API has issues with small faces surrounded by edges
|
// NOTE: Need to access internal as the API has issues with small faces surrounded by edges
|
||||||
const ndcCoords = scene.getNDC(event.clientX, event.clientY);
|
const ndcCoords = scene.getNDC(event.clientX, event.clientY);
|
||||||
//const hit = scene.hitFromPoint(ndcCoords) as Intersection<MObject3D> | undefined;
|
//const hit = scene.hitFromPoint(ndcCoords) as Intersection<MObject3D> | undefined;
|
||||||
ray_caster.setFromCamera(ndcCoords, (scene as any).camera);
|
raycaster.setFromCamera(ndcCoords, (scene as any).camera);
|
||||||
if ((scene as any).camera.isOrthographicCamera) {
|
if ((scene as any).camera.isOrthographicCamera) {
|
||||||
// Need to fix the ray direction for ortho camera
|
// Need to fix the ray direction for ortho camera
|
||||||
// FIXME: Still buggy (but less so :)
|
// FIXME: Still buggy (but less so :)
|
||||||
ray_caster.ray.direction.copy(
|
raycaster.ray.direction.copy(
|
||||||
scene.getTarget().clone().add(scene.target.position).sub((scene as any).camera.position).normalize());
|
scene.getTarget().clone().add(scene.target.position).sub((scene as any).camera.position).normalize());
|
||||||
}
|
}
|
||||||
// console.log('NDC', ndcCoords, 'Camera', (scene as any).camera, 'Ray', ray_caster.ray);
|
// console.log('NDC', ndcCoords, 'Camera', (scene as any).camera, 'Ray', ray_caster.ray);
|
||||||
const hits = ray_caster.intersectObject(scene, true);
|
const hits = raycaster.intersectObject(scene, true);
|
||||||
let hit = hits.find((hit) => {
|
let hit = hits.find((hit) => {
|
||||||
const kind = hit.object.type
|
const kind = hit.object.type
|
||||||
const kindOk = (selectFilter.value === 'Any') ||
|
const kindOk = (selectFilter.value === 'Any') ||
|
||||||
@@ -62,24 +67,31 @@ let selectionListener = (event: MouseEvent) => {
|
|||||||
return hit.object.visible && !hit.object.userData.noHit && kindOk;
|
return hit.object.visible && !hit.object.userData.noHit && kindOk;
|
||||||
}) as Intersection<MObject3D> | undefined;
|
}) as Intersection<MObject3D> | undefined;
|
||||||
console.log('Hit', hit)
|
console.log('Hit', hit)
|
||||||
|
if (!highlightNextSelection.value[0]) {
|
||||||
if (!hit) {
|
if (!hit) {
|
||||||
deselectAll();
|
deselectAll();
|
||||||
} else {
|
} else {
|
||||||
// Toggle selection
|
// Toggle selection
|
||||||
const wasSelected = selectedMaterials.value.find((m) => m.object.name === hit.object.name) !== undefined;
|
const wasSelected = selected.value.find((m) => m.object.name === hit.object.name) !== undefined;
|
||||||
if (wasSelected) {
|
if (wasSelected) {
|
||||||
deselect(hit)
|
deselect(hit)
|
||||||
} else {
|
} else {
|
||||||
select(hit)
|
select(hit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise, highlight the model that owns the hit
|
||||||
|
emit('findModel', hit.object.userData[extrasNameKey])
|
||||||
|
// And reset the selection mode
|
||||||
|
toggleHighlightNextSelection()
|
||||||
|
}
|
||||||
scene.queueRender() // Force rerender of model-viewer
|
scene.queueRender() // Force rerender of model-viewer
|
||||||
};
|
}
|
||||||
|
|
||||||
function select(hit: Intersection<MObject3D>) {
|
function select(hit: Intersection<MObject3D>) {
|
||||||
console.log('Selecting', hit.object.name)
|
console.log('Selecting', hit.object.name)
|
||||||
if (selectedMaterials.value.find((m) => m.object.name === hit.object.name) === undefined) {
|
if (selected.value.find((m) => m.object.name === hit.object.name) === undefined) {
|
||||||
selectedMaterials.value.push(hit);
|
selected.value.push(hit);
|
||||||
}
|
}
|
||||||
hit.object.material.__prevBaseColorFactor = [
|
hit.object.material.__prevBaseColorFactor = [
|
||||||
hit.object.material.color.r,
|
hit.object.material.color.r,
|
||||||
@@ -95,8 +107,8 @@ function deselect(hit: Intersection<MObject3D>, alsoRemove = true) {
|
|||||||
console.log('Deselecting', hit.object.name)
|
console.log('Deselecting', hit.object.name)
|
||||||
if (alsoRemove) {
|
if (alsoRemove) {
|
||||||
// Remove the matching object from the selection
|
// Remove the matching object from the selection
|
||||||
let toRemove = selectedMaterials.value.findIndex((m) => m.object.name === hit.object.name);
|
let toRemove = selected.value.findIndex((m) => m.object.name === hit.object.name);
|
||||||
selectedMaterials.value.splice(toRemove, 1);
|
selected.value.splice(toRemove, 1);
|
||||||
}
|
}
|
||||||
hit.object.material.color.r = hit.object.material.__prevBaseColorFactor[0]
|
hit.object.material.color.r = hit.object.material.__prevBaseColorFactor[0]
|
||||||
hit.object.material.color.g = hit.object.material.__prevBaseColorFactor[1]
|
hit.object.material.color.g = hit.object.material.__prevBaseColorFactor[1]
|
||||||
@@ -106,7 +118,7 @@ function deselect(hit: Intersection<MObject3D>, alsoRemove = true) {
|
|||||||
|
|
||||||
function deselectAll(alsoRemove = true) {
|
function deselectAll(alsoRemove = true) {
|
||||||
// Clear selection (shallow copy to avoid modifying the array while iterating)
|
// Clear selection (shallow copy to avoid modifying the array while iterating)
|
||||||
let toClear = selectedMaterials.value.slice();
|
let toClear = selected.value.slice();
|
||||||
for (let material of toClear) {
|
for (let material of toClear) {
|
||||||
deselect(material, alsoRemove);
|
deselect(material, alsoRemove);
|
||||||
}
|
}
|
||||||
@@ -122,7 +134,7 @@ function toggleSelection() {
|
|||||||
viewer.addEventListener('mousedown', selectionMoveListener); // Avoid clicking when dragging
|
viewer.addEventListener('mousedown', selectionMoveListener); // Avoid clicking when dragging
|
||||||
hasListener = true;
|
hasListener = true;
|
||||||
}
|
}
|
||||||
for (let material of selectedMaterials.value) {
|
for (let material of selected.value) {
|
||||||
select(material);
|
select(material);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -130,23 +142,69 @@ function toggleSelection() {
|
|||||||
}
|
}
|
||||||
props.viewer.scene.queueRender() // Force rerender of model-viewer
|
props.viewer.scene.queueRender() // Force rerender of model-viewer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleHighlightNextSelection() {
|
||||||
|
highlightNextSelection.value = [
|
||||||
|
!highlightNextSelection.value[0],
|
||||||
|
highlightNextSelection.value[0] ? highlightNextSelection.value[1] : selectionEnabled.value
|
||||||
|
];
|
||||||
|
if (highlightNextSelection.value[0]) {
|
||||||
|
// Reuse selection code to identify the model
|
||||||
|
if (!selectionEnabled.value) toggleSelection()
|
||||||
|
} else {
|
||||||
|
if (selectionEnabled.value !== highlightNextSelection.value[1]) toggleSelection()
|
||||||
|
highlightNextSelection.value = [false, false];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleShowBoundingBox() {
|
||||||
|
// let scene: ModelScene = props.viewer?.scene;
|
||||||
|
// if (!scene) return;
|
||||||
|
showBoundingBox.value = !showBoundingBox.value;
|
||||||
|
// scene.model?.traverse((child) => {
|
||||||
|
// if (child.userData[extrasNameKey] === modelName) {
|
||||||
|
// if (child.type === 'BoxHelper') {
|
||||||
|
// child.visible = showBoundingBox.value;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// scene.queueRender() // Force rerender of model-viewer
|
||||||
|
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="select-parent">
|
<div class="select-parent">
|
||||||
<v-btn icon @click="toggleSelection" :color="selectionEnabled ? 'surface-light' : ''">
|
<v-btn icon @click="toggleSelection" :color="selectionEnabled ? 'surface-light' : ''">
|
||||||
<v-tooltip activator="parent">{{ selectionEnabled ? 'Disable Selection Mode' : 'Enable Selection Mode' }}
|
<v-tooltip activator="parent">{{ selectionEnabled ? 'Disable selection mode' : 'Enable selection mode' }}
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiCursorDefaultClick"/>
|
<svg-icon type="mdi" :path="mdiCursorDefaultClick"/>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-tooltip :text="'Select Only ' + selectFilter" :open-on-click="false">
|
<v-tooltip :text="'Select only ' + selectFilter.toString().toLocaleLowerCase()" :open-on-click="false">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<!-- TODO: Keyboard shortcuts for fast selection (& other tools) -->
|
<!-- TODO: Keyboard shortcuts for fast selection (& other tools) -->
|
||||||
<v-select v-bind="props" class="select-only" variant="underlined" :items="['Any', 'Faces', 'Edges', 'Vertices']"
|
<v-select v-bind="props" class="select-only" variant="underlined"
|
||||||
|
:items="['Any', 'Faces', 'Edges', 'Vertices']"
|
||||||
v-model="selectFilter"/>
|
v-model="selectFilter"/>
|
||||||
</template>
|
</template>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
<v-btn icon @click="toggleHighlightNextSelection" :color="highlightNextSelection[0] ? 'surface-light' : ''">
|
||||||
|
<v-tooltip activator="parent">Highlight the next clicked element in the models list</v-tooltip>
|
||||||
|
<svg-icon type="mdi" :path="mdiFeatureSearch"/>
|
||||||
|
</v-btn>
|
||||||
|
<!-- TODO: Show BB -->
|
||||||
|
<v-btn icon disabled @click="toggleShowBoundingBox" :color="showBoundingBox ? 'surface-light' : ''">
|
||||||
|
<v-tooltip activator="parent">{{ showBoundingBox ? 'Hide selection bounds' : 'Show selection bounds' }}
|
||||||
|
</v-tooltip>
|
||||||
|
<svg-icon type="mdi" :path="mdiCubeOutline"/>
|
||||||
|
</v-btn>
|
||||||
|
<!-- TODO: Show distances of selections (min/center/max distance) -->
|
||||||
|
<v-btn icon disabled @click="toggleShowBoundingBox" :color="showBoundingBox ? 'surface-light' : ''">
|
||||||
|
<v-tooltip activator="parent">{{ showBoundingBox ? 'Hide selection dimensions' : 'Show selection dimensions' }}
|
||||||
|
</v-tooltip>
|
||||||
|
<svg-icon type="mdi" :path="mdiRuler"/>
|
||||||
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ const LicensesDialogContent = defineAsyncComponent({
|
|||||||
|
|
||||||
|
|
||||||
let props = defineProps<{ viewer: InstanceType<typeof ModelViewerWrapper> | null }>();
|
let props = defineProps<{ viewer: InstanceType<typeof ModelViewerWrapper> | null }>();
|
||||||
|
const emit = defineEmits<{ findModel: [string] }>()
|
||||||
|
|
||||||
let selection: Ref<Array<Intersection<typeof MObject3D>>> = ref([]);
|
let selection: Ref<Array<Intersection<typeof MObject3D>>> = ref([]);
|
||||||
let selectionFaceCount = () => selection.value.filter((s) => s.object.type == "Mesh" || s.object.type == "SkinnedMesh").length
|
let selectionFaceCount = () => selection.value.filter((s) => s.object.type == "Mesh" || s.object.type == "SkinnedMesh").length
|
||||||
@@ -115,7 +116,7 @@ async function openGithub() {
|
|||||||
</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"/>
|
<selection-component :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>
|
||||||
|
|||||||
Reference in New Issue
Block a user