mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-20 06:27:04 +01:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b6d3f6266 | ||
|
|
255ae72ed2 | ||
|
|
77dd9fb43e | ||
|
|
5dc2ae2f8d | ||
|
|
58440723bd | ||
|
|
bfdd656316 | ||
|
|
7408823c02 | ||
|
|
856ffbc4c5 | ||
|
|
d0f8463bbf | ||
|
|
162d3e22a2 | ||
|
|
4b06559ab8 | ||
|
|
9afa2e5786 | ||
|
|
7196fb2f32 | ||
|
|
8ec60faa04 | ||
|
|
13bbdd5956 |
@@ -2225,13 +2225,13 @@ THE SOFTWARE.
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- three@0.162.0
|
||||
- three@0.160.1
|
||||
|
||||
This package contains the following license and notice below:
|
||||
|
||||
The MIT License
|
||||
|
||||
Copyright © 2010-2024 three.js authors
|
||||
Copyright © 2010-2023 three.js authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -33,6 +33,10 @@ const setDisableTap = (val: boolean) => disableTap.value = val;
|
||||
provide('disableTap', {disableTap, setDisableTap});
|
||||
|
||||
async function onModelUpdateRequest(event: NetworkUpdateEvent) {
|
||||
// Trigger progress bar as soon as possible (also triggered earlier for each raw notification)
|
||||
if (viewer.value && event.models.length > 0) {
|
||||
viewer.value.onProgress(0.10);
|
||||
}
|
||||
// Load/unload a new batch of models to optimize rendering time
|
||||
console.log("Received model update request", event.models);
|
||||
let shutdownRequestIndex = event.models.findIndex((model) => model.isRemove == null);
|
||||
@@ -70,6 +74,8 @@ async function onModelRemoveRequest(name: string) {
|
||||
|
||||
// Set up the load model event listener
|
||||
let networkMgr = new NetworkManager();
|
||||
networkMgr.addEventListener('update-early',
|
||||
(e) => viewer.value?.onProgress((e as CustomEvent<Array<any>>).detail.length * 0.01));
|
||||
networkMgr.addEventListener('update', (e) => onModelUpdateRequest(e as NetworkUpdateEvent));
|
||||
// Start loading all configured models ASAP
|
||||
for (let model of settings.preload) {
|
||||
|
||||
@@ -21,7 +21,32 @@ export async function mergePartial(url: string, name: string, document: Document
|
||||
networkFinished();
|
||||
|
||||
// Load the new document
|
||||
let newDoc = await io.readBinary(new Uint8Array(buffer));
|
||||
let newDoc = null;
|
||||
let alreadyTried: { [name: string]: boolean } = {}
|
||||
while (newDoc == null) { // Retry adding extensions as required until the document is loaded
|
||||
try { // Try to load fast if no extensions are used
|
||||
newDoc = await io.readBinary(new Uint8Array(buffer));
|
||||
} catch (e) { // Fallback to wait for download and register big extensions
|
||||
if (e instanceof Error && e.message.toLowerCase().includes("khr_draco_mesh_compression")) {
|
||||
if (alreadyTried["draco"]) throw e; else alreadyTried["draco"] = true;
|
||||
// WARNING: Draco decompression on web is really slow for non-trivial models! (it should work?)
|
||||
let {KHRDracoMeshCompression} = await import("@gltf-transform/extensions")
|
||||
let dracoDecoderWeb = await import("three/examples/jsm/libs/draco/draco_decoder.js");
|
||||
let dracoEncoderWeb = await import("three/examples/jsm/libs/draco/draco_encoder.js");
|
||||
io.registerExtensions([KHRDracoMeshCompression])
|
||||
.registerDependencies({
|
||||
'draco3d.decoder': await dracoDecoderWeb.default({}),
|
||||
'draco3d.encoder': await dracoEncoderWeb.default({})
|
||||
});
|
||||
} else if (e instanceof Error && e.message.toLowerCase().includes("ext_texture_webp")) {
|
||||
if (alreadyTried["webp"]) throw e; else alreadyTried["webp"] = true;
|
||||
let {EXTTextureWebP} = await import("@gltf-transform/extensions")
|
||||
io.registerExtensions([EXTTextureWebP]);
|
||||
} else { // TODO: Add more extensions as required
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any previous model with the same name
|
||||
await document.transform(dropByName(name));
|
||||
|
||||
@@ -93,28 +93,47 @@ export class NetworkManager extends EventTarget {
|
||||
|
||||
private foundModel(name: string, hash: string | null, url: string, isRemove: boolean | null, disconnect: () => void = () => {
|
||||
}) {
|
||||
let prevHash = this.knownObjectHashes[name];
|
||||
// console.debug("Found model", name, "with hash", hash, "and previous hash", prevHash);
|
||||
if (!hash || hash !== prevHash || isRemove) {
|
||||
// Update known hashes
|
||||
if (isRemove == false) {
|
||||
this.knownObjectHashes[name] = hash;
|
||||
} else if (isRemove == true) {
|
||||
if (!(name in this.knownObjectHashes)) return; // Nothing to remove...
|
||||
delete this.knownObjectHashes[name];
|
||||
// Also update buffered updates if the model is removed
|
||||
this.bufferedUpdates = this.bufferedUpdates.filter(m => m.name !== name);
|
||||
}
|
||||
let newModel = new NetworkUpdateEventModel(name, url, hash, isRemove);
|
||||
this.bufferedUpdates.push(newModel);
|
||||
// console.debug("Found model", name, "with hash", hash, "at", url, "isRemove", isRemove);
|
||||
|
||||
// Optimization: try to batch updates automatically for faster rendering
|
||||
if (this.batchTimeout !== null) clearTimeout(this.batchTimeout);
|
||||
this.batchTimeout = setTimeout(() => {
|
||||
this.dispatchEvent(new NetworkUpdateEvent(this.bufferedUpdates, disconnect));
|
||||
this.bufferedUpdates = [];
|
||||
}, batchTimeout);
|
||||
}
|
||||
// We only care about the latest update per model name
|
||||
this.bufferedUpdates = this.bufferedUpdates.filter(m => m.name !== name);
|
||||
|
||||
// Add the new model to the list of updates and dispatch the early update
|
||||
let upd = new NetworkUpdateEventModel(name, url, hash, isRemove);
|
||||
this.bufferedUpdates.push(upd);
|
||||
this.dispatchEvent(new CustomEvent("update-early", {detail: this.bufferedUpdates}));
|
||||
|
||||
// Optimization: try to batch updates automatically for faster rendering
|
||||
if (this.batchTimeout !== null) clearTimeout(this.batchTimeout);
|
||||
this.batchTimeout = setTimeout(() => {
|
||||
// Update known hashes for minimal updates
|
||||
for (let model of this.bufferedUpdates) {
|
||||
if (model.isRemove == false && model.hash && model.hash === this.knownObjectHashes[model.name]) {
|
||||
// Delete this useless update
|
||||
let foundFirst = false;
|
||||
this.bufferedUpdates = this.bufferedUpdates.filter(m => {
|
||||
if (m === model) {
|
||||
if (!foundFirst) { // Remove only first full match
|
||||
foundFirst = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
})
|
||||
} else {
|
||||
// Keep this update and update the last known hash
|
||||
if (model.isRemove == true) {
|
||||
if (model.name in this.knownObjectHashes) delete this.knownObjectHashes[model.name];
|
||||
} else if (model.isRemove == false) {
|
||||
this.knownObjectHashes[model.name] = model.hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch the event to actually update the models
|
||||
this.dispatchEvent(new NetworkUpdateEvent(this.bufferedUpdates, disconnect));
|
||||
this.bufferedUpdates = [];
|
||||
}, batchTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,23 +13,25 @@ export class SceneMgr {
|
||||
let loadStart = performance.now();
|
||||
let loadNetworkEnd: number;
|
||||
|
||||
// Start merging into the current document, replacing or adding as needed
|
||||
document = await mergePartial(url, name, document, () => loadNetworkEnd = performance.now());
|
||||
try {
|
||||
// Start merging into the current document, replacing or adding as needed
|
||||
document = await mergePartial(url, name, document, () => loadNetworkEnd = performance.now());
|
||||
|
||||
console.log("Model", name, "loaded in", performance.now() - loadNetworkEnd!, "ms after",
|
||||
loadNetworkEnd! - loadStart, "ms of transferring data (maybe building the object on the server)");
|
||||
console.log("Model", name, "loaded in", performance.now() - loadNetworkEnd!, "ms after",
|
||||
loadNetworkEnd! - loadStart, "ms of transferring data (maybe building the object on the server)");
|
||||
} finally {
|
||||
if (updateHelpers) {
|
||||
// Reload the helpers to fit the new model
|
||||
await this.reloadHelpers(sceneUrl, document, reloadScene);
|
||||
reloadScene = false;
|
||||
}
|
||||
|
||||
if (updateHelpers) {
|
||||
// Reload the helpers to fit the new model
|
||||
await this.reloadHelpers(sceneUrl, document, reloadScene);
|
||||
reloadScene = false;
|
||||
}
|
||||
|
||||
if (reloadScene) {
|
||||
// Display the final fully loaded model
|
||||
let displayStart = performance.now();
|
||||
document = await this.showCurrentDoc(sceneUrl, document);
|
||||
console.log("Scene displayed in", performance.now() - displayStart, "ms");
|
||||
if (reloadScene) {
|
||||
// Display the final fully loaded model
|
||||
let displayStart = performance.now();
|
||||
document = await this.showCurrentDoc(sceneUrl, document);
|
||||
console.log("Scene displayed in", performance.now() - displayStart, "ms");
|
||||
}
|
||||
}
|
||||
|
||||
return document;
|
||||
@@ -97,7 +99,7 @@ export class SceneMgr {
|
||||
// Serialize the document into a GLB and update the viewerSrc
|
||||
let buffer = await toBuffer(document);
|
||||
let blob = new Blob([buffer], {type: 'model/gltf-binary'});
|
||||
console.debug("Showing current doc", document, "as", Array.from(buffer));
|
||||
console.debug("Showing current doc", document, "with", buffer.length, "total bytes");
|
||||
sceneUrl.value = URL.createObjectURL(blob);
|
||||
|
||||
return document;
|
||||
|
||||
@@ -43,10 +43,11 @@ const emit = defineEmits<{ remove: [] }>()
|
||||
|
||||
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});
|
||||
const wireframe = ref(false);
|
||||
// Count the number of faces, edges and vertices
|
||||
let faceCount = ref(-1);
|
||||
let edgeCount = ref(-1);
|
||||
let vertexCount = ref(-1);
|
||||
|
||||
// Clipping planes are handled in y-up space (swapped on interface, Z inverted later)
|
||||
const clipPlaneX = ref(1);
|
||||
const clipPlaneSwappedX = ref(false);
|
||||
@@ -56,24 +57,10 @@ const clipPlaneZ = ref(1);
|
||||
const clipPlaneSwappedZ = ref(false);
|
||||
const edgeWidth = ref(settings.edgeWidth);
|
||||
|
||||
// Count the number of faces, edges and vertices
|
||||
let faceCount = props.meshes
|
||||
.flatMap((m) => m.listPrimitives().filter(p => p.getMode() === WebGL2RenderingContext.TRIANGLES))
|
||||
.map(p => (p.getExtras()?.face_triangles_end as any)?.length ?? 1)
|
||||
.reduce((a, b) => a + b, 0)
|
||||
let edgeCount = props.meshes
|
||||
.flatMap((m) => m.listPrimitives().filter(p => p.getMode() in [WebGL2RenderingContext.LINE_STRIP, WebGL2RenderingContext.LINES]))
|
||||
.map(p => (p.getExtras()?.edge_points_end as any)?.length ?? 0)
|
||||
.reduce((a, b) => a + b, 0)
|
||||
let vertexCount = props.meshes
|
||||
.flatMap((m) => m.listPrimitives().filter(p => p.getMode() === WebGL2RenderingContext.POINTS))
|
||||
.map(p => (p.getAttribute("POSITION")?.getCount() ?? 0))
|
||||
.reduce((a, b) => a + b, 0)
|
||||
|
||||
// Set initial defaults for the enabled features
|
||||
if (faceCount === 0) enabledFeatures.value = enabledFeatures.value.filter((f) => f !== 0)
|
||||
if (edgeCount === 0) enabledFeatures.value = enabledFeatures.value.filter((f) => f !== 1)
|
||||
if (vertexCount === 0) enabledFeatures.value = enabledFeatures.value.filter((f) => f !== 2)
|
||||
// Misc properties
|
||||
const enabledFeatures = defineModel<Array<number>>("enabledFeatures", {default: [0, 1, 2]});
|
||||
const opacity = defineModel<number>("opacity", {default: 1});
|
||||
const wireframe = ref(false);
|
||||
|
||||
// Listeners for changes in the properties (or viewer reloads)
|
||||
function onEnabledFeaturesChange(newEnabledFeatures: Array<number>) {
|
||||
@@ -81,9 +68,6 @@ function onEnabledFeaturesChange(newEnabledFeatures: Array<number>) {
|
||||
let scene = props.viewer?.scene;
|
||||
let sceneModel = (scene as any)?._model;
|
||||
if (!scene || !sceneModel) return;
|
||||
// Iterate all primitives of the mesh and set their visibility based on the enabled features
|
||||
// Use the scene graph instead of the document to avoid reloading the same model, at the cost
|
||||
// of not actually removing the primitives from the scene graph
|
||||
sceneModel.traverse((child: MObject3D) => {
|
||||
if (child.userData[extrasNameKey] === modelName) {
|
||||
let childIsFace = child.type == 'Mesh' || child.type == 'SkinnedMesh'
|
||||
@@ -107,10 +91,6 @@ function onOpacityChange(newOpacity: number) {
|
||||
let scene = props.viewer?.scene;
|
||||
let sceneModel = (scene as any)?._model;
|
||||
if (!scene || !sceneModel) return;
|
||||
// Iterate all primitives of the mesh and set their opacity based on the enabled features
|
||||
// Use the scene graph instead of the document to avoid reloading the same model, at the cost
|
||||
// of not actually removing the primitives from the scene graph
|
||||
// console.log('Opacity may have changed', newOpacity)
|
||||
sceneModel.traverse((child: MObject3D) => {
|
||||
if (child.userData[extrasNameKey] === modelName) {
|
||||
if (child.material && child.material.opacity !== newOpacity) {
|
||||
@@ -129,10 +109,6 @@ function onWireframeChange(newWireframe: boolean) {
|
||||
let scene = props.viewer?.scene;
|
||||
let sceneModel = (scene as any)?._model;
|
||||
if (!scene || !sceneModel) return;
|
||||
// Iterate all primitives of the mesh and set their wireframe based on the enabled features
|
||||
// Use the scene graph instead of the document to avoid reloading the same model, at the cost
|
||||
// of not actually removing the primitives from the scene graph
|
||||
// console.log('Wireframe may have changed', newWireframe)
|
||||
sceneModel.traverse((child: MObject3D) => {
|
||||
if (child.userData[extrasNameKey] === modelName) {
|
||||
if (child.material && child.material.wireframe !== newWireframe) {
|
||||
@@ -268,9 +244,8 @@ function onModelLoad() {
|
||||
let scene = props.viewer?.scene;
|
||||
let sceneModel = (scene as any)?._model;
|
||||
if (!scene || !sceneModel) return;
|
||||
// Iterate all primitives of the mesh and set their visibility based on the enabled features
|
||||
// Use the scene graph instead of the document to avoid reloading the same model, at the cost
|
||||
// of not actually removing the primitives from the scene graph
|
||||
|
||||
// Add darkened back faces for all face objects to improve cutting planes
|
||||
let childrenToAdd: Array<MObject3D> = [];
|
||||
sceneModel.traverse((child: MObject3D) => {
|
||||
if (child.userData[extrasNameKey] === modelName) {
|
||||
@@ -303,6 +278,28 @@ function onModelLoad() {
|
||||
});
|
||||
childrenToAdd.forEach((child: MObject3D) => sceneModel.add(child));
|
||||
|
||||
// Count the number of faces, edges and vertices
|
||||
faceCount.value = props.meshes
|
||||
.flatMap((m) => m.listPrimitives().filter(p => p.getMode() === WebGL2RenderingContext.TRIANGLES))
|
||||
.map(p => (p.getExtras()?.face_triangles_end as any)?.length ?? 1)
|
||||
.reduce((a, b) => a + b, 0)
|
||||
edgeCount.value = props.meshes
|
||||
.flatMap((m) => m.listPrimitives().filter(p => p.getMode() in [WebGL2RenderingContext.LINE_STRIP, WebGL2RenderingContext.LINES]))
|
||||
.map(p => (p.getExtras()?.edge_points_end as any)?.length ?? 0)
|
||||
.reduce((a, b) => a + b, 0)
|
||||
vertexCount.value = props.meshes
|
||||
.flatMap((m) => m.listPrimitives().filter(p => p.getMode() === WebGL2RenderingContext.POINTS))
|
||||
.map(p => (p.getAttribute("POSITION")?.getCount() ?? 0))
|
||||
.reduce((a, b) => a + b, 0)
|
||||
|
||||
// Set the enabled features to all provided features
|
||||
if (faceCount.value === 0) enabledFeatures.value = enabledFeatures.value.filter((f) => f !== 0)
|
||||
else if (!enabledFeatures.value.includes(0)) enabledFeatures.value.push(0)
|
||||
if (edgeCount.value === 0) enabledFeatures.value = enabledFeatures.value.filter((f) => f !== 1)
|
||||
else if (!enabledFeatures.value.includes(1)) enabledFeatures.value.push(1)
|
||||
if (vertexCount.value === 0) enabledFeatures.value = enabledFeatures.value.filter((f) => f !== 2)
|
||||
else if (!enabledFeatures.value.includes(2)) enabledFeatures.value.push(2)
|
||||
|
||||
// Furthermore...
|
||||
// Enabled features may have been reset after a reload
|
||||
onEnabledFeaturesChange(enabledFeatures.value)
|
||||
|
||||
@@ -36,9 +36,7 @@ function onRemove(mesh: 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})
|
||||
|
||||
2
frontend/shims.d.ts
vendored
2
frontend/shims.d.ts
vendored
@@ -1,3 +1,5 @@
|
||||
// Avoids typescript error when importing some files
|
||||
declare module '@jamescoyle/vue-icon'
|
||||
declare module 'three-orientation-gizmo/src/OrientationGizmo'
|
||||
declare module 'three/examples/jsm/libs/draco/draco_decoder.js'
|
||||
declare module 'three/examples/jsm/libs/draco/draco_encoder.js'
|
||||
@@ -8,8 +8,8 @@ import type {ModelScene} from "@google/model-viewer/lib/three-components/ModelSc
|
||||
import {Hotspot} from "@google/model-viewer/lib/three-components/Hotspot";
|
||||
import type {Renderer} from "@google/model-viewer/lib/three-components/Renderer";
|
||||
import type {Vector3} from "three";
|
||||
import { computeBoundsTree, disposeBoundsTree, acceleratedRaycast } from 'three-mesh-bvh';
|
||||
import {BufferGeometry, Mesh} from "three";
|
||||
import {acceleratedRaycast, computeBoundsTree, disposeBoundsTree} from 'three-mesh-bvh';
|
||||
|
||||
ModelViewerElement.modelCacheSize = 0; // Also needed to avoid tree shaking
|
||||
//@ts-ignore
|
||||
@@ -41,8 +41,31 @@ onMounted(() => {
|
||||
emit('load')
|
||||
});
|
||||
elem.value.addEventListener('camera-change', onCameraChange);
|
||||
elem.value.addEventListener('progress', (ev) => onProgress((ev as any).detail.totalProgress));
|
||||
});
|
||||
|
||||
// Handles loading the events for <model-viewer>'s slotted progress bar
|
||||
const progressBar = ref<HTMLElement | null>(null);
|
||||
const updateBar = ref<HTMLElement | null>(null);
|
||||
let onProgressHideTimeout: number | null = null;
|
||||
const onProgress = (totalProgress: number) => {
|
||||
if (!progressBar.value || !updateBar.value) return;
|
||||
// Update the progress bar and ensure it's visible
|
||||
progressBar.value.style.display = 'block';
|
||||
progressBar.value.style.opacity = '1'; // Fade in
|
||||
updateBar.value.style.width = `${totalProgress * 100}%`;
|
||||
// Auto-hide smoothly when no progress is made for a while
|
||||
if (onProgressHideTimeout) clearTimeout(onProgressHideTimeout);
|
||||
onProgressHideTimeout = setTimeout(() => {
|
||||
if (!progressBar.value) return;
|
||||
progressBar.value.style.opacity = '0'; // Fade out
|
||||
setTimeout(() => {
|
||||
if (!progressBar.value) return;
|
||||
progressBar.value.style.display = 'none'; // Actually hide
|
||||
}, 300); // 0.3s fade out
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
class Line3DData {
|
||||
startHotspot: HTMLElement = document.body
|
||||
endHotspot: HTMLElement = document.body
|
||||
@@ -137,7 +160,7 @@ 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, onProgress});
|
||||
|
||||
let {disableTap} = inject<{ disableTap: Ref<boolean> }>('disableTap')!!;
|
||||
watch(disableTap, (value) => {
|
||||
@@ -155,7 +178,8 @@ watch(disableTap, (value) => {
|
||||
: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">
|
||||
<slot></slot> <!-- Controls, annotations, etc. -->
|
||||
<slot></slot>
|
||||
<!-- Display some information during initial load -->
|
||||
<div class="annotation initial-load-banner">
|
||||
Trying to load models from...
|
||||
<v-list v-for="src in settings.preload" :key="src">
|
||||
@@ -163,6 +187,11 @@ watch(disableTap, (value) => {
|
||||
</v-list>
|
||||
<!-- Too much idle CPU usage: <loading></loading> -->
|
||||
</div>
|
||||
|
||||
<!-- Customize the progress bar -->
|
||||
<div class="progress-bar" slot="progress-bar" ref="progressBar">
|
||||
<div class="update-bar" ref="updateBar"/>
|
||||
</div>
|
||||
</model-viewer>
|
||||
|
||||
<!-- The SVG overlay for fake 3D lines attached to the model -->
|
||||
@@ -223,4 +252,30 @@ watch(disableTap, (value) => {
|
||||
.initial-load-banner .v-list-item {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
width: 100%;
|
||||
height: 10%;
|
||||
max-height: 2%;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
transform: translate3d(-50%, 0%, 0);
|
||||
border-radius: 25px;
|
||||
box-shadow: 0 3px 10px 3px rgba(0, 0, 0, 0.5), 0 0 5px 1px rgba(0, 0, 0, 0.6);
|
||||
border: 1px solid rgba(255, 255, 255, 0.9);
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.update-bar {
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
width: 0;
|
||||
height: 100%;
|
||||
border-radius: 25px;
|
||||
float: left;
|
||||
transition: width 0.3s;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "yet-another-cad-viewer",
|
||||
"version": "0.8.1",
|
||||
"version": "0.8.5",
|
||||
"description": "",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
@@ -15,7 +15,8 @@
|
||||
"update-licenses": "generate-license-file --input package.json --output assets/licenses.txt --overwrite"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gltf-transform/core": "^3.10.0",
|
||||
"@gltf-transform/core": "^3.10.1",
|
||||
"@gltf-transform/extensions": "^3.10.1",
|
||||
"@gltf-transform/functions": "^3.10.1",
|
||||
"@google/model-viewer": "^3.4.0",
|
||||
"@jamescoyle/vue-icon": "^0.1.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "yacv-server"
|
||||
version = "0.8.1"
|
||||
version = "0.8.5"
|
||||
description = "Yet Another CAD Viewer (server)"
|
||||
authors = ["Yeicor <4929005+Yeicor@users.noreply.github.com>"]
|
||||
license = "MIT"
|
||||
|
||||
@@ -28,7 +28,7 @@ export default defineConfig({
|
||||
build: {
|
||||
assetsDir: '.',
|
||||
cssCodeSplit: false, // Small enough to inline
|
||||
chunkSizeWarningLimit: 550, // Three.js is huge
|
||||
chunkSizeWarningLimit: 550, // Three.js is big. Draco is even bigger but not likely to be used.
|
||||
},
|
||||
define: {
|
||||
__APP_NAME__: JSON.stringify(name),
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
Utilities to work with CAD objects
|
||||
"""
|
||||
import hashlib
|
||||
from typing import Optional, Union, List, Tuple
|
||||
from typing import Optional, Union, Tuple
|
||||
|
||||
from OCP.TopLoc import TopLoc_Location
|
||||
from OCP.TopoDS import TopoDS_Shape
|
||||
from build123d import Compound, Shape
|
||||
|
||||
from yacv_server.gltf import GLTFMgr
|
||||
|
||||
@@ -13,7 +14,7 @@ CADCoreLike = Union[TopoDS_Shape, TopLoc_Location] # Faces, Edges, Vertices and
|
||||
CADLike = Union[CADCoreLike, any] # build123d and cadquery types
|
||||
|
||||
|
||||
def get_shape(obj: CADLike, error: bool = True) -> Optional[CADCoreLike]:
|
||||
def get_shape(obj: CADLike, error: bool = True, in_iter: bool = False) -> Optional[CADCoreLike]:
|
||||
""" Get the shape of a CAD-like object """
|
||||
|
||||
# Try to grab a shape if a different type of object was passed
|
||||
@@ -40,22 +41,33 @@ def get_shape(obj: CADLike, error: bool = True) -> Optional[CADCoreLike]:
|
||||
if isinstance(obj, TopoDS_Shape):
|
||||
return obj
|
||||
|
||||
# Handle iterables like Build123d ShapeList by extracting all sub-shapes and making a compound
|
||||
if not in_iter:
|
||||
try:
|
||||
obj_iter = iter(obj)
|
||||
# print(obj, ' -> ', obj_iter)
|
||||
shapes_raw = [get_shape(sub_obj, error=False, in_iter=True) for sub_obj in obj_iter]
|
||||
shapes_bd = [Shape(shape) for shape in shapes_raw if shape is not None]
|
||||
return get_shape(Compound(shapes_bd), error)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
if error:
|
||||
raise ValueError(f'Cannot show object of type {type(obj)} (submit issue?)')
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def grab_all_cad() -> List[Tuple[str, CADCoreLike]]:
|
||||
def grab_all_cad() -> set[Tuple[str, CADCoreLike]]:
|
||||
""" Grab all shapes by inspecting the stack """
|
||||
import inspect
|
||||
stack = inspect.stack()
|
||||
shapes = []
|
||||
shapes = set()
|
||||
for frame in stack:
|
||||
for key, value in frame.frame.f_locals.items():
|
||||
shape = get_shape(value, error=False)
|
||||
if shape:
|
||||
shapes.append((key, shape))
|
||||
if shape and shape not in shapes:
|
||||
shapes.add((key, shape))
|
||||
return shapes
|
||||
|
||||
|
||||
|
||||
@@ -194,7 +194,7 @@ class YACV:
|
||||
|
||||
def show_cad_all(self, **kwargs):
|
||||
"""Publishes all CAD objects in the current scope to the server"""
|
||||
all_cad = grab_all_cad()
|
||||
all_cad = list(grab_all_cad()) # List for reproducible iteration order
|
||||
self.show(*[cad for _, cad in all_cad], names=[name for name, _ in all_cad], **kwargs)
|
||||
|
||||
def remove(self, name: str):
|
||||
@@ -325,7 +325,7 @@ _find_var_name_count = 0
|
||||
def _find_var_name(obj: any, avoid_levels: int = 2) -> str:
|
||||
"""A hacky way to get a stable name for an object that may change over time"""
|
||||
global _find_var_name_count
|
||||
obj_shape = get_shape(obj)
|
||||
obj_shape = get_shape(obj, error=False) or obj
|
||||
for frame in inspect.stack()[avoid_levels:]:
|
||||
for key, value in frame.frame.f_locals.items():
|
||||
if get_shape(value, error=False) is obj_shape:
|
||||
|
||||
@@ -390,7 +390,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc"
|
||||
integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==
|
||||
|
||||
"@gltf-transform/core@^3.10.0", "@gltf-transform/core@^3.10.1":
|
||||
"@gltf-transform/core@^3.10.1":
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@gltf-transform/core/-/core-3.10.1.tgz#d99c060b499482ed2c3304466405bf4c10939831"
|
||||
integrity sha512-50OYemknGNxjBmiOM6iJp04JAu0bl9jvXJfN/gFt9QdJO02cPDcoXlTfSPJG6TVWDcfl0xPlsx1vybcbPVGFcQ==
|
||||
|
||||
Reference in New Issue
Block a user