mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 22:24:17 +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 -->
|
||||
<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 Loading from "./misc/Loading.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 models: Ref<InstanceType<typeof Models> | null> = ref(null)
|
||||
provide('document', document);
|
||||
let disableTap = ref(false);
|
||||
let setDisableTap = (val: boolean) => {
|
||||
disableTap.value = val;
|
||||
}
|
||||
provide('disableTap', {disableTap, setDisableTap});
|
||||
|
||||
async function onModelLoadRequest(model: NetworkUpdateEvent) {
|
||||
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!
|
||||
}
|
||||
|
||||
function onFindModel(name: string) {
|
||||
Models.value.findModel(name);
|
||||
}
|
||||
|
||||
// Set up the load model event listener
|
||||
let networkMgr = new NetworkManager();
|
||||
networkMgr.addEventListener('update', onModelLoadRequest);
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
import {BufferAttribute, InterleavedBufferAttribute, Vector3} from 'three';
|
||||
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,
|
||||
vertices: Array<Vector3>
|
||||
} {
|
||||
obj.updateMatrixWorld();
|
||||
let pos: InterleavedBufferAttribute = obj.geometry.getAttribute('position');
|
||||
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 vertices = [];
|
||||
for (let i = 0; i < ind.count; i++) {
|
||||
let index = ind.array[i];
|
||||
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);
|
||||
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.
|
||||
* 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>,
|
||||
center: Array<Vector3>,
|
||||
max: Array<Vector3>
|
||||
} {
|
||||
// Simplify this problem (approximate) by using the distance between each of their vertices.
|
||||
// Find the center of each object.
|
||||
let {center: aCenter, vertices: aVertices} = getCenterAndVertexList(a);
|
||||
let {center: bCenter, vertices: bVertices} = getCenterAndVertexList(b);
|
||||
let {center: aCenter, vertices: aVertices} = getCenterAndVertexList(a, scene);
|
||||
let {center: bCenter, vertices: bVertices} = getCenterAndVertexList(b, scene);
|
||||
|
||||
// Find the closest and farthest vertices.
|
||||
// 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 emit = defineEmits<{ findModel: [string] }>();
|
||||
let {disableTap, setDisableTap} = inject('disableTap');
|
||||
let selectionEnabled = ref(false);
|
||||
let selected = defineModel<Array<Intersection<MObject3D>>>({default: []});
|
||||
let highlightNextSelection = ref([false, false]); // Second is whether selection was enabled before
|
||||
@@ -56,7 +57,7 @@ let selectionListener = (event: MouseEvent) => {
|
||||
if (selectFilter.value === 'Any') {
|
||||
raycaster.params.Line.threshold = 0.2;
|
||||
raycaster.params.Points.threshold = 0.8;
|
||||
} else if(selectFilter.value === 'Edges') {
|
||||
} else if (selectFilter.value === 'Edges') {
|
||||
raycaster.params.Line.threshold = 0.8;
|
||||
raycaster.params.Points.threshold = 0.0;
|
||||
} else if (selectFilter.value === 'Vertices') {
|
||||
@@ -160,14 +161,7 @@ function toggleSelection() {
|
||||
let viewer: ModelViewerElement = props.viewer?.elem;
|
||||
if (!viewer) return;
|
||||
selectionEnabled.value = !selectionEnabled.value;
|
||||
if (selectionEnabled.value) {
|
||||
for (let material of selected.value) {
|
||||
select(material);
|
||||
}
|
||||
} else {
|
||||
deselectAll(false);
|
||||
}
|
||||
props.viewer.scene?.queueRender() // Force rerender of model-viewer
|
||||
setDisableTap(selectionEnabled.value);
|
||||
}
|
||||
|
||||
function toggleHighlightNextSelection() {
|
||||
@@ -186,15 +180,16 @@ function toggleHighlightNextSelection() {
|
||||
|
||||
function toggleShowBoundingBox() {
|
||||
showBoundingBox.value = !showBoundingBox.value;
|
||||
if (!firstLoad /*bug?*/) updateBoundingBox();
|
||||
}
|
||||
|
||||
let viewerFound = false
|
||||
let firstLoad = true;
|
||||
watch(() => props.viewer, (viewer) => {
|
||||
if (!viewer) return;
|
||||
if (viewerFound) return;
|
||||
viewerFound = true;
|
||||
let hasListeners = false;
|
||||
let firstLoad = true;
|
||||
// props.viewer.elem may not yet be available, so we need to wait for it
|
||||
viewer.onElemReady((elem) => {
|
||||
if (hasListeners) return;
|
||||
@@ -219,12 +214,11 @@ watch(() => props.viewer, (viewer) => {
|
||||
let waitingHandler: () => void;
|
||||
waitingHandler = () => {
|
||||
// Ignore also inertia
|
||||
let stillMoving = performance.now() - lastCameraChange < 100;
|
||||
if (!stillMoving) {
|
||||
if (performance.now() - lastCameraChange > 250) {
|
||||
updateBoundingBox();
|
||||
isWaiting = false;
|
||||
} else {
|
||||
// If the mouse is still down, wait a little more
|
||||
// If the camera is still moving, wait a bit more
|
||||
setTimeout(waitingHandler, 100);
|
||||
}
|
||||
};
|
||||
@@ -333,6 +327,7 @@ function updateDistances() {
|
||||
|
||||
// Set up the line cache (for delta updates)
|
||||
let distanceLinesToRemove = Object.keys(distanceLines);
|
||||
|
||||
function ensureLine(from: Vector3, to: Vector3, text: string, color: string) {
|
||||
console.log('ensureLine', from, to, text, color)
|
||||
let lineCacheKey = JSON.stringify([from, to]);
|
||||
@@ -351,7 +346,7 @@ function updateDistances() {
|
||||
// Add lines (if not already added)
|
||||
let objA = selected.value[0].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(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");
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
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 Loading from "../misc/Loading.vue";
|
||||
import {ref, watch} from "vue";
|
||||
@@ -87,7 +87,7 @@ function removeLine3D(id: number) {
|
||||
scene.value.removeHotspot(new Hotspot({name: 'line' + id + '_end'}));
|
||||
lines.value[id].endHotspot.parentElement.remove()
|
||||
delete lines.value[id];
|
||||
lines.value = {...lines.value}; // TODO: Trigger reactivity not working...
|
||||
scene.value.queueRender() // Needed to update the hotspots
|
||||
}
|
||||
|
||||
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});
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<!-- The main 3D model viewer -->
|
||||
<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"
|
||||
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"
|
||||
:ar="settings.arModes.length > 0" :ar-modes="settings.arModes" :skybox-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 -->
|
||||
<div class="overlay-svg-wrapper">
|
||||
<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]"
|
||||
:y2="line.end2D[1]" v-bind="line.lineAttrs"/>
|
||||
<rect :x="(line.start2D[0] + line.end2D[0]) / 2 - line.centerTextSize[0]/2 - 4"
|
||||
|
||||
Reference in New Issue
Block a user