mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-20 06:27:04 +01:00
better model management
This commit is contained in:
14
src/App.vue
14
src/App.vue
@@ -5,12 +5,14 @@ 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";
|
||||||
import Models from "./models/Models.vue";
|
import Models from "./models/Models.vue";
|
||||||
import {VLayout, VMain, VToolbarTitle} from "vuetify/lib/components";
|
import {VBtn, VLayout, VMain, VToolbarTitle} from "vuetify/lib/components";
|
||||||
import {settings} from "./misc/settings";
|
import {settings} from "./misc/settings";
|
||||||
import {NetworkManager, NetworkUpdateEvent} from "./misc/network";
|
import {NetworkManager, NetworkUpdateEvent} from "./misc/network";
|
||||||
import {SceneMgr} from "./misc/scene";
|
import {SceneMgr} from "./misc/scene";
|
||||||
import {Document} from "@gltf-transform/core";
|
import {Document} from "@gltf-transform/core";
|
||||||
import type ModelViewerWrapperT from "./viewer/ModelViewerWrapper.vue";
|
import type ModelViewerWrapperT from "./viewer/ModelViewerWrapper.vue";
|
||||||
|
import {mdiPlus} from '@mdi/js'
|
||||||
|
import SvgIcon from '@jamescoyle/vue-icon';
|
||||||
|
|
||||||
// NOTE: The ModelViewer library is big (THREE.js), so we split it and import it asynchronously
|
// NOTE: The ModelViewer library is big (THREE.js), so we split it and import it asynchronously
|
||||||
const ModelViewerWrapper = defineAsyncComponent({
|
const ModelViewerWrapper = defineAsyncComponent({
|
||||||
@@ -42,6 +44,11 @@ networkMgr.addEventListener('update', onModelLoadRequest);
|
|||||||
for (let model of settings.preloadModels) {
|
for (let model of settings.preloadModels) {
|
||||||
networkMgr.load(model);
|
networkMgr.load(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadModelManual() {
|
||||||
|
const modelUrl = prompt("For an improved experience in viewing CAD/GLTF models with automatic updates, it's recommended to use the official yacv_server Python package. This ensures seamless serving of models and automatic updates.\n\nOtherwise, enter the URL of the model to load:");
|
||||||
|
if (modelUrl) await networkMgr.load(modelUrl);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -57,6 +64,11 @@ for (let model of settings.preloadModels) {
|
|||||||
<template #toolbar>
|
<template #toolbar>
|
||||||
<v-toolbar-title>Models</v-toolbar-title>
|
<v-toolbar-title>Models</v-toolbar-title>
|
||||||
</template>
|
</template>
|
||||||
|
<template #toolbar-items>
|
||||||
|
<v-btn icon="" @click="loadModelManual">
|
||||||
|
<svg-icon type="mdi" :path="mdiPlus"/>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
<models :viewer="viewer" :document="document" @remove="onModelRemoveRequest"/>
|
<models :viewer="viewer" :document="document" @remove="onModelRemoveRequest"/>
|
||||||
</sidebar>
|
</sidebar>
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,12 @@ export async function mergePartial(url: string, name: string, document: Document
|
|||||||
// Load the new document
|
// Load the new document
|
||||||
let newDoc = await io.read(url);
|
let newDoc = await io.read(url);
|
||||||
|
|
||||||
// Remove any previous model with the same name and ensure consistent names
|
// Remove any previous model with the same name
|
||||||
|
await document.transform(dropByName(name));
|
||||||
|
|
||||||
|
// Ensure consistent names
|
||||||
// noinspection TypeScriptValidateJSTypes
|
// noinspection TypeScriptValidateJSTypes
|
||||||
await newDoc.transform(dropByName(name), setNames(name));
|
await newDoc.transform(setNames(name));
|
||||||
|
|
||||||
// Merge the new document into the current one
|
// Merge the new document into the current one
|
||||||
return document.merge(newDoc);
|
return document.merge(newDoc);
|
||||||
@@ -38,18 +41,19 @@ export async function removeModel(name: string, document: Document): Promise<Doc
|
|||||||
|
|
||||||
/** Given a parsed GLTF document and a name, it forces the names of all elements to be identified by the name (or derivatives) */
|
/** Given a parsed GLTF document and a name, it forces the names of all elements to be identified by the name (or derivatives) */
|
||||||
function setNames(name: string): Transform {
|
function setNames(name: string): Transform {
|
||||||
return (doc: Document, _: any) => {
|
return (doc: Document) => {
|
||||||
// Do this automatically for all elements changing any name
|
// Do this automatically for all elements changing any name
|
||||||
for (let elem of doc.getGraph().listEdges().map(e => e.getChild())) {
|
for (let elem of doc.getGraph().listEdges().map(e => e.getChild())) {
|
||||||
if (!elem.getExtras()) elem.setExtras({});
|
if (!elem.getExtras()) elem.setExtras({});
|
||||||
elem.getExtras()[extrasNameKey] = name;
|
elem.getExtras()[extrasNameKey] = name;
|
||||||
}
|
}
|
||||||
|
return doc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Ensures that all elements with the given name are removed from the document */
|
/** Ensures that all elements with the given name are removed from the document */
|
||||||
export function dropByName(name: string): Transform {
|
function dropByName(name: string): Transform {
|
||||||
return (doc: Document, _: any) => {
|
return (doc: Document) => {
|
||||||
for (let elem of doc.getGraph().listEdges().map(e => e.getChild())) {
|
for (let elem of doc.getGraph().listEdges().map(e => e.getChild())) {
|
||||||
if (elem.getExtras() == null || elem instanceof Scene || elem instanceof Buffer) continue;
|
if (elem.getExtras() == null || elem instanceof Scene || elem instanceof Buffer) continue;
|
||||||
if ((elem.getExtras()[extrasNameKey]?.toString() ?? "") == name) {
|
if ((elem.getExtras()[extrasNameKey]?.toString() ?? "") == name) {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export class NetworkManager extends EventTarget {
|
|||||||
console.error("WebSocket error", event);
|
console.error("WebSocket error", event);
|
||||||
}
|
}
|
||||||
ws.onclose = () => {
|
ws.onclose = () => {
|
||||||
console.trace("WebSocket closed, reconnecting very soon");
|
//console.trace("WebSocket closed, reconnecting very soon");
|
||||||
setTimeout(() => this.monitorWebSocket(url), settings.checkServerEveryMs);
|
setTimeout(() => this.monitorWebSocket(url), settings.checkServerEveryMs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import {
|
|||||||
import {extrasNameKey} from "../misc/gltf";
|
import {extrasNameKey} from "../misc/gltf";
|
||||||
import {Document, Mesh} from "@gltf-transform/core";
|
import {Document, Mesh} from "@gltf-transform/core";
|
||||||
import {watch} from "vue";
|
import {watch} from "vue";
|
||||||
|
|
||||||
import type ModelViewerWrapper from "../viewer/ModelViewerWrapper.vue";
|
import type ModelViewerWrapper from "../viewer/ModelViewerWrapper.vue";
|
||||||
import {mdiDelete, mdiRectangle, mdiRectangleOutline, mdiVectorRectangle} from '@mdi/js'
|
import {mdiDelete, mdiRectangle, mdiRectangleOutline, mdiVectorRectangle} from '@mdi/js'
|
||||||
import SvgIcon from '@jamescoyle/vue-icon/lib/svg-icon.vue';
|
import SvgIcon from '@jamescoyle/vue-icon/lib/svg-icon.vue';
|
||||||
|
import type {ModelViewerElement, RGBA} from '@google/model-viewer';
|
||||||
|
|
||||||
const props = defineProps<{ mesh: Mesh, viewer: InstanceType<typeof ModelViewerWrapper> | null, document: Document }>();
|
const props = defineProps<{ mesh: Mesh, viewer: InstanceType<typeof ModelViewerWrapper> | null, document: Document }>();
|
||||||
const emit = defineEmits<{ remove: [] }>()
|
const emit = defineEmits<{ remove: [] }>()
|
||||||
@@ -32,9 +32,10 @@ let hasListener = false;
|
|||||||
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;
|
||||||
if (!scene || !scene._model) return;
|
let elem = props.viewer?.elem;
|
||||||
|
if (!scene || !scene._model || !elem) return;
|
||||||
if (!hasListener) { // Make sure we listen for reloads and re-apply enabled features
|
if (!hasListener) { // Make sure we listen for reloads and re-apply enabled features
|
||||||
props.viewer.elem.addEventListener('load', () => onEnabledFeaturesChange(enabledFeatures.value));
|
elem.addEventListener('load', () => onEnabledFeaturesChange(enabledFeatures.value));
|
||||||
hasListener = true;
|
hasListener = true;
|
||||||
}
|
}
|
||||||
// Iterate all primitives of the mesh and set their visibility based on the enabled features
|
// Iterate all primitives of the mesh and set their visibility based on the enabled features
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ def tessellate(
|
|||||||
angular_tolerance: float = 0.1,
|
angular_tolerance: float = 0.1,
|
||||||
faces: bool = True,
|
faces: bool = True,
|
||||||
edges: bool = True,
|
edges: bool = True,
|
||||||
vertices: bool = False,
|
vertices: bool = True,
|
||||||
) -> GLTF2:
|
) -> GLTF2:
|
||||||
"""Tessellate a whole shape into a list of triangle vertices and a list of triangle indices."""
|
"""Tessellate a whole shape into a list of triangle vertices and a list of triangle indices."""
|
||||||
mgr = GLTFMgr()
|
mgr = GLTFMgr()
|
||||||
@@ -40,16 +40,17 @@ def tessellate(
|
|||||||
_tessellate_face(mgr, face.wrapped, tolerance, angular_tolerance)
|
_tessellate_face(mgr, face.wrapped, tolerance, angular_tolerance)
|
||||||
if edges:
|
if edges:
|
||||||
for edge in face.edges():
|
for edge in face.edges():
|
||||||
edge_to_faces[edge] = edge_to_faces.get(edge, []) + [face.wrapped]
|
edge_to_faces[edge.wrapped] = edge_to_faces.get(edge.wrapped, []) + [face.wrapped]
|
||||||
if vertices:
|
if vertices:
|
||||||
for vertex in face.vertices():
|
for vertex in face.vertices():
|
||||||
vertex_to_faces[vertex.wrapped] = vertex_to_faces.get(vertex.wrapped, []) + [face.wrapped]
|
vertex_to_faces[vertex.wrapped] = vertex_to_faces.get(vertex.wrapped, []) + [face.wrapped]
|
||||||
if edges:
|
if edges:
|
||||||
for edge in shape.edges():
|
for edge in shape.edges():
|
||||||
_tessellate_edge(mgr, edge.wrapped, edge_to_faces[edge], angular_tolerance, angular_tolerance)
|
_tessellate_edge(mgr, edge.wrapped, edge_to_faces.get(edge.wrapped, []), angular_tolerance,
|
||||||
|
angular_tolerance)
|
||||||
if vertices:
|
if vertices:
|
||||||
for vertex in shape.vertices():
|
for vertex in shape.vertices():
|
||||||
_tessellate_vertex(mgr, vertex_to_faces[vertex], vertex.wrapped)
|
_tessellate_vertex(mgr, vertex.wrapped, vertex_to_faces.get(vertex.wrapped, []))
|
||||||
|
|
||||||
return mgr.gltf
|
return mgr.gltf
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user