mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 22:24:17 +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 Tools from "./tools/Tools.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 {NetworkManager, NetworkUpdateEvent} from "./misc/network";
|
||||
import {SceneMgr} from "./misc/scene";
|
||||
import {Document} from "@gltf-transform/core";
|
||||
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
|
||||
const ModelViewerWrapper = defineAsyncComponent({
|
||||
@@ -42,6 +44,11 @@ networkMgr.addEventListener('update', onModelLoadRequest);
|
||||
for (let model of settings.preloadModels) {
|
||||
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>
|
||||
|
||||
<template>
|
||||
@@ -57,6 +64,11 @@ for (let model of settings.preloadModels) {
|
||||
<template #toolbar>
|
||||
<v-toolbar-title>Models</v-toolbar-title>
|
||||
</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"/>
|
||||
</sidebar>
|
||||
|
||||
|
||||
@@ -15,9 +15,12 @@ export async function mergePartial(url: string, name: string, document: Document
|
||||
// Load the new document
|
||||
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
|
||||
await newDoc.transform(dropByName(name), setNames(name));
|
||||
await newDoc.transform(setNames(name));
|
||||
|
||||
// Merge the new document into the current one
|
||||
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) */
|
||||
function setNames(name: string): Transform {
|
||||
return (doc: Document, _: any) => {
|
||||
return (doc: Document) => {
|
||||
// Do this automatically for all elements changing any name
|
||||
for (let elem of doc.getGraph().listEdges().map(e => e.getChild())) {
|
||||
if (!elem.getExtras()) elem.setExtras({});
|
||||
elem.getExtras()[extrasNameKey] = name;
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
|
||||
/** Ensures that all elements with the given name are removed from the document */
|
||||
export function dropByName(name: string): Transform {
|
||||
return (doc: Document, _: any) => {
|
||||
function dropByName(name: string): Transform {
|
||||
return (doc: Document) => {
|
||||
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()[extrasNameKey]?.toString() ?? "") == name) {
|
||||
|
||||
@@ -49,7 +49,7 @@ export class NetworkManager extends EventTarget {
|
||||
console.error("WebSocket error", event);
|
||||
}
|
||||
ws.onclose = () => {
|
||||
console.trace("WebSocket closed, reconnecting very soon");
|
||||
//console.trace("WebSocket closed, reconnecting very soon");
|
||||
setTimeout(() => this.monitorWebSocket(url), settings.checkServerEveryMs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ import {
|
||||
import {extrasNameKey} from "../misc/gltf";
|
||||
import {Document, Mesh} from "@gltf-transform/core";
|
||||
import {watch} from "vue";
|
||||
|
||||
import type ModelViewerWrapper from "../viewer/ModelViewerWrapper.vue";
|
||||
import {mdiDelete, mdiRectangle, mdiRectangleOutline, mdiVectorRectangle} from '@mdi/js'
|
||||
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 emit = defineEmits<{ remove: [] }>()
|
||||
@@ -32,9 +32,10 @@ let hasListener = false;
|
||||
function onEnabledFeaturesChange(newEnabledFeatures: Array<number>) {
|
||||
//console.log('Enabled features may have changed', newEnabledFeatures)
|
||||
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
|
||||
props.viewer.elem.addEventListener('load', () => onEnabledFeaturesChange(enabledFeatures.value));
|
||||
elem.addEventListener('load', () => onEnabledFeaturesChange(enabledFeatures.value));
|
||||
hasListener = true;
|
||||
}
|
||||
// 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,
|
||||
faces: bool = True,
|
||||
edges: bool = True,
|
||||
vertices: bool = False,
|
||||
vertices: bool = True,
|
||||
) -> GLTF2:
|
||||
"""Tessellate a whole shape into a list of triangle vertices and a list of triangle indices."""
|
||||
mgr = GLTFMgr()
|
||||
@@ -40,16 +40,17 @@ def tessellate(
|
||||
_tessellate_face(mgr, face.wrapped, tolerance, angular_tolerance)
|
||||
if 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:
|
||||
for vertex in face.vertices():
|
||||
vertex_to_faces[vertex.wrapped] = vertex_to_faces.get(vertex.wrapped, []) + [face.wrapped]
|
||||
if 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:
|
||||
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user