mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-20 14:37:03 +01:00
fixes for lines, positions, distances, bounding boxes and enable tap to move
This commit is contained in:
11
src/App.vue
11
src/App.vue
@@ -1,6 +1,6 @@
|
|||||||
<!--suppress SillyAssignmentJS -->
|
<!--suppress SillyAssignmentJS -->
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {defineAsyncComponent, ref, Ref, shallowRef, provide} from "vue";
|
import {defineAsyncComponent, provide, Ref, ref, shallowRef} from "vue";
|
||||||
import Sidebar from "./misc/Sidebar.vue";
|
import Sidebar from "./misc/Sidebar.vue";
|
||||||
import Loading from "./misc/Loading.vue";
|
import Loading from "./misc/Loading.vue";
|
||||||
import Tools from "./tools/Tools.vue";
|
import Tools from "./tools/Tools.vue";
|
||||||
@@ -28,6 +28,11 @@ 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)
|
let models: Ref<InstanceType<typeof Models> | null> = ref(null)
|
||||||
provide('document', document);
|
provide('document', document);
|
||||||
|
let disableTap = ref(false);
|
||||||
|
let setDisableTap = (val: boolean) => {
|
||||||
|
disableTap.value = val;
|
||||||
|
}
|
||||||
|
provide('disableTap', {disableTap, setDisableTap});
|
||||||
|
|
||||||
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);
|
||||||
@@ -39,10 +44,6 @@ 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);
|
||||||
|
|||||||
@@ -1,20 +1,27 @@
|
|||||||
import {BufferAttribute, InterleavedBufferAttribute, Vector3} from 'three';
|
import {BufferAttribute, InterleavedBufferAttribute, Vector3} from 'three';
|
||||||
import type {MObject3D} from "../tools/Selection.vue";
|
import type {MObject3D} from "../tools/Selection.vue";
|
||||||
|
import type { ModelScene } from '@google/model-viewer/lib/three-components/ModelScene';
|
||||||
|
|
||||||
|
|
||||||
function getCenterAndVertexList(obj: InstanceType<typeof MObject3D>): {
|
function getCenterAndVertexList(obj: InstanceType<typeof MObject3D>, scene: ModelScene): {
|
||||||
center: Vector3,
|
center: Vector3,
|
||||||
vertices: Array<Vector3>
|
vertices: Array<Vector3>
|
||||||
} {
|
} {
|
||||||
obj.updateMatrixWorld();
|
obj.updateMatrixWorld();
|
||||||
let pos: InterleavedBufferAttribute = obj.geometry.getAttribute('position');
|
let pos: InterleavedBufferAttribute = obj.geometry.getAttribute('position');
|
||||||
let ind: BufferAttribute = obj.geometry.index;
|
let ind: BufferAttribute = obj.geometry.index;
|
||||||
|
if (!ind) {
|
||||||
|
ind = new BufferAttribute(new Uint16Array(pos.count), 1);
|
||||||
|
for (let i = 0; i < pos.count; i++) {
|
||||||
|
ind.array[i] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
let center = new Vector3();
|
let center = new Vector3();
|
||||||
let vertices = [];
|
let vertices = [];
|
||||||
for (let i = 0; i < ind.count; i++) {
|
for (let i = 0; i < ind.count; i++) {
|
||||||
let index = ind.array[i];
|
let index = ind.array[i];
|
||||||
let vertex = new Vector3(pos.getX(index), pos.getY(index), pos.getZ(index));
|
let vertex = new Vector3(pos.getX(index), pos.getY(index), pos.getZ(index));
|
||||||
// vertex = obj.localToWorld(vertex); // TODO: Bad locations due to with model viewer?
|
vertex = scene.target.worldToLocal(obj.localToWorld(vertex));
|
||||||
center.add(vertex);
|
center.add(vertex);
|
||||||
vertices.push(vertex);
|
vertices.push(vertex);
|
||||||
}
|
}
|
||||||
@@ -27,15 +34,15 @@ function getCenterAndVertexList(obj: InstanceType<typeof MObject3D>): {
|
|||||||
* Given two THREE.Object3D objects, returns their closest and farthest vertices, and the geometric centers.
|
* Given two THREE.Object3D objects, returns their closest and farthest vertices, and the geometric centers.
|
||||||
* All of them are approximated and should not be used for precise calculations.
|
* All of them are approximated and should not be used for precise calculations.
|
||||||
*/
|
*/
|
||||||
export function distances(a: InstanceType<typeof MObject3D>, b: InstanceType<typeof MObject3D>): {
|
export function distances(a: InstanceType<typeof MObject3D>, b: InstanceType<typeof MObject3D>, scene: ModelScene): {
|
||||||
min: Array<Vector3>,
|
min: Array<Vector3>,
|
||||||
center: Array<Vector3>,
|
center: Array<Vector3>,
|
||||||
max: Array<Vector3>
|
max: Array<Vector3>
|
||||||
} {
|
} {
|
||||||
// Simplify this problem (approximate) by using the distance between each of their vertices.
|
// Simplify this problem (approximate) by using the distance between each of their vertices.
|
||||||
// Find the center of each object.
|
// Find the center of each object.
|
||||||
let {center: aCenter, vertices: aVertices} = getCenterAndVertexList(a);
|
let {center: aCenter, vertices: aVertices} = getCenterAndVertexList(a, scene);
|
||||||
let {center: bCenter, vertices: bVertices} = getCenterAndVertexList(b);
|
let {center: bCenter, vertices: bVertices} = getCenterAndVertexList(b, scene);
|
||||||
|
|
||||||
// Find the closest and farthest vertices.
|
// Find the closest and farthest vertices.
|
||||||
// TODO: Compute actual min and max distances between the two objects.
|
// TODO: Compute actual min and max distances between the two objects.
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export type MObject3D = Object3D & {
|
|||||||
|
|
||||||
let props = defineProps<{ viewer: typeof ModelViewerWrapperT | null }>();
|
let props = defineProps<{ viewer: typeof ModelViewerWrapperT | null }>();
|
||||||
let emit = defineEmits<{ findModel: [string] }>();
|
let emit = defineEmits<{ findModel: [string] }>();
|
||||||
|
let {disableTap, setDisableTap} = inject('disableTap');
|
||||||
let selectionEnabled = ref(false);
|
let selectionEnabled = ref(false);
|
||||||
let selected = 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 highlightNextSelection = ref([false, false]); // Second is whether selection was enabled before
|
||||||
@@ -56,7 +57,7 @@ let selectionListener = (event: MouseEvent) => {
|
|||||||
if (selectFilter.value === 'Any') {
|
if (selectFilter.value === 'Any') {
|
||||||
raycaster.params.Line.threshold = 0.2;
|
raycaster.params.Line.threshold = 0.2;
|
||||||
raycaster.params.Points.threshold = 0.8;
|
raycaster.params.Points.threshold = 0.8;
|
||||||
} else if(selectFilter.value === 'Edges') {
|
} else if (selectFilter.value === 'Edges') {
|
||||||
raycaster.params.Line.threshold = 0.8;
|
raycaster.params.Line.threshold = 0.8;
|
||||||
raycaster.params.Points.threshold = 0.0;
|
raycaster.params.Points.threshold = 0.0;
|
||||||
} else if (selectFilter.value === 'Vertices') {
|
} else if (selectFilter.value === 'Vertices') {
|
||||||
@@ -160,14 +161,7 @@ function toggleSelection() {
|
|||||||
let viewer: ModelViewerElement = props.viewer?.elem;
|
let viewer: ModelViewerElement = props.viewer?.elem;
|
||||||
if (!viewer) return;
|
if (!viewer) return;
|
||||||
selectionEnabled.value = !selectionEnabled.value;
|
selectionEnabled.value = !selectionEnabled.value;
|
||||||
if (selectionEnabled.value) {
|
setDisableTap(selectionEnabled.value);
|
||||||
for (let material of selected.value) {
|
|
||||||
select(material);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
deselectAll(false);
|
|
||||||
}
|
|
||||||
props.viewer.scene?.queueRender() // Force rerender of model-viewer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleHighlightNextSelection() {
|
function toggleHighlightNextSelection() {
|
||||||
@@ -186,15 +180,16 @@ function toggleHighlightNextSelection() {
|
|||||||
|
|
||||||
function toggleShowBoundingBox() {
|
function toggleShowBoundingBox() {
|
||||||
showBoundingBox.value = !showBoundingBox.value;
|
showBoundingBox.value = !showBoundingBox.value;
|
||||||
|
if (!firstLoad /*bug?*/) updateBoundingBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
let viewerFound = false
|
let viewerFound = false
|
||||||
|
let firstLoad = true;
|
||||||
watch(() => props.viewer, (viewer) => {
|
watch(() => props.viewer, (viewer) => {
|
||||||
if (!viewer) return;
|
if (!viewer) return;
|
||||||
if (viewerFound) return;
|
if (viewerFound) return;
|
||||||
viewerFound = true;
|
viewerFound = true;
|
||||||
let hasListeners = false;
|
let hasListeners = false;
|
||||||
let firstLoad = true;
|
|
||||||
// props.viewer.elem may not yet be available, so we need to wait for it
|
// props.viewer.elem may not yet be available, so we need to wait for it
|
||||||
viewer.onElemReady((elem) => {
|
viewer.onElemReady((elem) => {
|
||||||
if (hasListeners) return;
|
if (hasListeners) return;
|
||||||
@@ -219,12 +214,11 @@ watch(() => props.viewer, (viewer) => {
|
|||||||
let waitingHandler: () => void;
|
let waitingHandler: () => void;
|
||||||
waitingHandler = () => {
|
waitingHandler = () => {
|
||||||
// Ignore also inertia
|
// Ignore also inertia
|
||||||
let stillMoving = performance.now() - lastCameraChange < 100;
|
if (performance.now() - lastCameraChange > 250) {
|
||||||
if (!stillMoving) {
|
|
||||||
updateBoundingBox();
|
updateBoundingBox();
|
||||||
isWaiting = false;
|
isWaiting = false;
|
||||||
} else {
|
} else {
|
||||||
// If the mouse is still down, wait a little more
|
// If the camera is still moving, wait a bit more
|
||||||
setTimeout(waitingHandler, 100);
|
setTimeout(waitingHandler, 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -333,6 +327,7 @@ function updateDistances() {
|
|||||||
|
|
||||||
// Set up the line cache (for delta updates)
|
// Set up the line cache (for delta updates)
|
||||||
let distanceLinesToRemove = Object.keys(distanceLines);
|
let distanceLinesToRemove = Object.keys(distanceLines);
|
||||||
|
|
||||||
function ensureLine(from: Vector3, to: Vector3, text: string, color: string) {
|
function ensureLine(from: Vector3, to: Vector3, text: string, color: string) {
|
||||||
console.log('ensureLine', from, to, text, color)
|
console.log('ensureLine', from, to, text, color)
|
||||||
let lineCacheKey = JSON.stringify([from, to]);
|
let lineCacheKey = JSON.stringify([from, to]);
|
||||||
@@ -351,7 +346,7 @@ function updateDistances() {
|
|||||||
// Add lines (if not already added)
|
// Add lines (if not already added)
|
||||||
let objA = selected.value[0].object;
|
let objA = selected.value[0].object;
|
||||||
let objB = selected.value[1].object;
|
let objB = selected.value[1].object;
|
||||||
let {min, center, max} = distances(objA, objB);
|
let {min, center, max} = distances(objA, objB, props.viewer?.scene);
|
||||||
ensureLine(max[0], max[1], max[1].distanceTo(max[0]).toFixed(1) + "mm", "orange");
|
ensureLine(max[0], max[1], max[1].distanceTo(max[0]).toFixed(1) + "mm", "orange");
|
||||||
ensureLine(center[0], center[1], center[1].distanceTo(center[0]).toFixed(1) + "mm", "green");
|
ensureLine(center[0], center[1], center[1].distanceTo(center[0]).toFixed(1) + "mm", "green");
|
||||||
ensureLine(min[0], min[1], min[1].distanceTo(min[0]).toFixed(1) + "mm", "cyan");
|
ensureLine(min[0], min[1], min[1].distanceTo(min[0]).toFixed(1) + "mm", "cyan");
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {settings} from "../misc/settings";
|
import {settings} from "../misc/settings";
|
||||||
import {onMounted} from "vue";
|
import {onMounted, inject, Ref} from "vue";
|
||||||
import {$scene, $renderer} from "@google/model-viewer/lib/model-viewer-base";
|
import {$scene, $renderer} from "@google/model-viewer/lib/model-viewer-base";
|
||||||
import Loading from "../misc/Loading.vue";
|
import Loading from "../misc/Loading.vue";
|
||||||
import {ref, watch} from "vue";
|
import {ref, watch} from "vue";
|
||||||
@@ -87,7 +87,7 @@ function removeLine3D(id: number) {
|
|||||||
scene.value.removeHotspot(new Hotspot({name: 'line' + id + '_end'}));
|
scene.value.removeHotspot(new Hotspot({name: 'line' + id + '_end'}));
|
||||||
lines.value[id].endHotspot.parentElement.remove()
|
lines.value[id].endHotspot.parentElement.remove()
|
||||||
delete lines.value[id];
|
delete lines.value[id];
|
||||||
lines.value = {...lines.value}; // TODO: Trigger reactivity not working...
|
scene.value.queueRender() // Needed to update the hotspots
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCameraChange() {
|
function onCameraChange() {
|
||||||
@@ -128,14 +128,25 @@ function onElemReady(callback: (elem: ModelViewerElement) => void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function entries(lines: { [id: number]: Line3DData }): [string, Line3DData][] {
|
||||||
|
return Object.entries(lines);
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({elem, onElemReady, scene, renderer, addLine3D, removeLine3D});
|
defineExpose({elem, onElemReady, scene, renderer, addLine3D, removeLine3D});
|
||||||
|
|
||||||
|
let {disableTap} = inject('disableTap');
|
||||||
|
watch(disableTap, (value) => {
|
||||||
|
// Rerender not auto triggered? This works anyway...
|
||||||
|
if (value) elem.value?.setAttribute('disable-tap', '');
|
||||||
|
else elem.value?.removeAttribute('disable-tap');
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- The main 3D model viewer -->
|
<!-- The main 3D model viewer -->
|
||||||
<model-viewer ref="elem" style="width: 100%; height: 100%" :src="props.src" alt="The 3D model(s)" camera-controls
|
<model-viewer ref="elem" 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"
|
camera-orbit="30deg 75deg auto" max-camera-orbit="Infinity 180deg auto"
|
||||||
min-camera-orbit="-Infinity 0deg 5%" disable-tap :exposure="settings.exposure"
|
min-camera-orbit="-Infinity 0deg 5%" :disable-tap="disableTap.value" :exposure="settings.exposure"
|
||||||
:shadow-intensity="settings.shadowIntensity" interaction-prompt="none" :autoplay="settings.autoplay"
|
:shadow-intensity="settings.shadowIntensity" interaction-prompt="none" :autoplay="settings.autoplay"
|
||||||
:ar="settings.arModes.length > 0" :ar-modes="settings.arModes" :skybox-image="settings.background"
|
:ar="settings.arModes.length > 0" :ar-modes="settings.arModes" :skybox-image="settings.background"
|
||||||
:environment-image="settings.background">
|
:environment-image="settings.background">
|
||||||
@@ -146,7 +157,7 @@ defineExpose({elem, onElemReady, scene, renderer, addLine3D, removeLine3D});
|
|||||||
<!-- The SVG overlay for fake 3D lines attached to the model -->
|
<!-- The SVG overlay for fake 3D lines attached to the model -->
|
||||||
<div class="overlay-svg-wrapper">
|
<div class="overlay-svg-wrapper">
|
||||||
<svg ref="svg" class="overlay-svg" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
<svg ref="svg" class="overlay-svg" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g v-for="[lineId, line] in Object.entries(lines)" :key="lineId">
|
<g v-for="[lineId, line] in entries(lines)" :key="lineId">
|
||||||
<line :x1="line.start2D[0]" :y1="line.start2D[1]" :x2="line.end2D[0]"
|
<line :x1="line.start2D[0]" :y1="line.start2D[1]" :x2="line.end2D[0]"
|
||||||
:y2="line.end2D[1]" v-bind="line.lineAttrs"/>
|
:y2="line.end2D[1]" v-bind="line.lineAttrs"/>
|
||||||
<rect :x="(line.start2D[0] + line.end2D[0]) / 2 - line.centerTextSize[0]/2 - 4"
|
<rect :x="(line.start2D[0] + line.end2D[0]) / 2 - line.centerTextSize[0]/2 - 4"
|
||||||
|
|||||||
Reference in New Issue
Block a user