mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 22:24:17 +01:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b6d3f6266 | ||
|
|
255ae72ed2 | ||
|
|
77dd9fb43e | ||
|
|
5dc2ae2f8d | ||
|
|
58440723bd | ||
|
|
bfdd656316 | ||
|
|
7408823c02 | ||
|
|
856ffbc4c5 |
@@ -93,7 +93,7 @@ export class NetworkManager extends EventTarget {
|
|||||||
|
|
||||||
private foundModel(name: string, hash: string | null, url: string, isRemove: boolean | null, disconnect: () => void = () => {
|
private foundModel(name: string, hash: string | null, url: string, isRemove: boolean | null, disconnect: () => void = () => {
|
||||||
}) {
|
}) {
|
||||||
console.debug("Found model", name, "with hash", hash, "at", url, "isRemove", isRemove);
|
// console.debug("Found model", name, "with hash", hash, "at", url, "isRemove", isRemove);
|
||||||
|
|
||||||
// We only care about the latest update per model name
|
// We only care about the latest update per model name
|
||||||
this.bufferedUpdates = this.bufferedUpdates.filter(m => m.name !== name);
|
this.bufferedUpdates = this.bufferedUpdates.filter(m => m.name !== name);
|
||||||
|
|||||||
@@ -13,23 +13,25 @@ export class SceneMgr {
|
|||||||
let loadStart = performance.now();
|
let loadStart = performance.now();
|
||||||
let loadNetworkEnd: number;
|
let loadNetworkEnd: number;
|
||||||
|
|
||||||
// Start merging into the current document, replacing or adding as needed
|
try {
|
||||||
document = await mergePartial(url, name, document, () => loadNetworkEnd = performance.now());
|
// 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",
|
console.log("Model", name, "loaded in", performance.now() - loadNetworkEnd!, "ms after",
|
||||||
loadNetworkEnd! - loadStart, "ms of transferring data (maybe building the object on the server)");
|
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) {
|
if (reloadScene) {
|
||||||
// Reload the helpers to fit the new model
|
// Display the final fully loaded model
|
||||||
await this.reloadHelpers(sceneUrl, document, reloadScene);
|
let displayStart = performance.now();
|
||||||
reloadScene = false;
|
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;
|
return document;
|
||||||
@@ -97,7 +99,7 @@ export class SceneMgr {
|
|||||||
// Serialize the document into a GLB and update the viewerSrc
|
// Serialize the document into a GLB and update the viewerSrc
|
||||||
let buffer = await toBuffer(document);
|
let buffer = await toBuffer(document);
|
||||||
let blob = new Blob([buffer], {type: 'model/gltf-binary'});
|
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);
|
sceneUrl.value = URL.createObjectURL(blob);
|
||||||
|
|
||||||
return document;
|
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"
|
let modelName = props.meshes[0].getExtras()[extrasNameKey] // + " blah blah blah blah blag blah blah blah"
|
||||||
|
|
||||||
// Reactive properties
|
// Count the number of faces, edges and vertices
|
||||||
const enabledFeatures = defineModel<Array<number>>("enabledFeatures", {default: [0, 1, 2]});
|
let faceCount = ref(-1);
|
||||||
const opacity = defineModel<number>("opacity", {default: 1});
|
let edgeCount = ref(-1);
|
||||||
const wireframe = ref(false);
|
let vertexCount = ref(-1);
|
||||||
|
|
||||||
// Clipping planes are handled in y-up space (swapped on interface, Z inverted later)
|
// Clipping planes are handled in y-up space (swapped on interface, Z inverted later)
|
||||||
const clipPlaneX = ref(1);
|
const clipPlaneX = ref(1);
|
||||||
const clipPlaneSwappedX = ref(false);
|
const clipPlaneSwappedX = ref(false);
|
||||||
@@ -56,24 +57,10 @@ const clipPlaneZ = ref(1);
|
|||||||
const clipPlaneSwappedZ = ref(false);
|
const clipPlaneSwappedZ = ref(false);
|
||||||
const edgeWidth = ref(settings.edgeWidth);
|
const edgeWidth = ref(settings.edgeWidth);
|
||||||
|
|
||||||
// Count the number of faces, edges and vertices
|
// Misc properties
|
||||||
let faceCount = props.meshes
|
const enabledFeatures = defineModel<Array<number>>("enabledFeatures", {default: [0, 1, 2]});
|
||||||
.flatMap((m) => m.listPrimitives().filter(p => p.getMode() === WebGL2RenderingContext.TRIANGLES))
|
const opacity = defineModel<number>("opacity", {default: 1});
|
||||||
.map(p => (p.getExtras()?.face_triangles_end as any)?.length ?? 1)
|
const wireframe = ref(false);
|
||||||
.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)
|
|
||||||
|
|
||||||
// Listeners for changes in the properties (or viewer reloads)
|
// Listeners for changes in the properties (or viewer reloads)
|
||||||
function onEnabledFeaturesChange(newEnabledFeatures: Array<number>) {
|
function onEnabledFeaturesChange(newEnabledFeatures: Array<number>) {
|
||||||
@@ -81,9 +68,6 @@ function onEnabledFeaturesChange(newEnabledFeatures: Array<number>) {
|
|||||||
let scene = props.viewer?.scene;
|
let scene = props.viewer?.scene;
|
||||||
let sceneModel = (scene as any)?._model;
|
let sceneModel = (scene as any)?._model;
|
||||||
if (!scene || !sceneModel) return;
|
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) => {
|
sceneModel.traverse((child: MObject3D) => {
|
||||||
if (child.userData[extrasNameKey] === modelName) {
|
if (child.userData[extrasNameKey] === modelName) {
|
||||||
let childIsFace = child.type == 'Mesh' || child.type == 'SkinnedMesh'
|
let childIsFace = child.type == 'Mesh' || child.type == 'SkinnedMesh'
|
||||||
@@ -107,10 +91,6 @@ function onOpacityChange(newOpacity: number) {
|
|||||||
let scene = props.viewer?.scene;
|
let scene = props.viewer?.scene;
|
||||||
let sceneModel = (scene as any)?._model;
|
let sceneModel = (scene as any)?._model;
|
||||||
if (!scene || !sceneModel) return;
|
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) => {
|
sceneModel.traverse((child: MObject3D) => {
|
||||||
if (child.userData[extrasNameKey] === modelName) {
|
if (child.userData[extrasNameKey] === modelName) {
|
||||||
if (child.material && child.material.opacity !== newOpacity) {
|
if (child.material && child.material.opacity !== newOpacity) {
|
||||||
@@ -129,10 +109,6 @@ function onWireframeChange(newWireframe: boolean) {
|
|||||||
let scene = props.viewer?.scene;
|
let scene = props.viewer?.scene;
|
||||||
let sceneModel = (scene as any)?._model;
|
let sceneModel = (scene as any)?._model;
|
||||||
if (!scene || !sceneModel) return;
|
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) => {
|
sceneModel.traverse((child: MObject3D) => {
|
||||||
if (child.userData[extrasNameKey] === modelName) {
|
if (child.userData[extrasNameKey] === modelName) {
|
||||||
if (child.material && child.material.wireframe !== newWireframe) {
|
if (child.material && child.material.wireframe !== newWireframe) {
|
||||||
@@ -268,9 +244,8 @@ function onModelLoad() {
|
|||||||
let scene = props.viewer?.scene;
|
let scene = props.viewer?.scene;
|
||||||
let sceneModel = (scene as any)?._model;
|
let sceneModel = (scene as any)?._model;
|
||||||
if (!scene || !sceneModel) return;
|
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
|
// Add darkened back faces for all face objects to improve cutting planes
|
||||||
// of not actually removing the primitives from the scene graph
|
|
||||||
let childrenToAdd: Array<MObject3D> = [];
|
let childrenToAdd: Array<MObject3D> = [];
|
||||||
sceneModel.traverse((child: MObject3D) => {
|
sceneModel.traverse((child: MObject3D) => {
|
||||||
if (child.userData[extrasNameKey] === modelName) {
|
if (child.userData[extrasNameKey] === modelName) {
|
||||||
@@ -303,6 +278,28 @@ function onModelLoad() {
|
|||||||
});
|
});
|
||||||
childrenToAdd.forEach((child: MObject3D) => sceneModel.add(child));
|
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...
|
// Furthermore...
|
||||||
// Enabled features may have been reset after a reload
|
// Enabled features may have been reset after a reload
|
||||||
onEnabledFeaturesChange(enabledFeatures.value)
|
onEnabledFeaturesChange(enabledFeatures.value)
|
||||||
|
|||||||
@@ -36,9 +36,7 @@ function onRemove(mesh: Mesh) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function findModel(name: string) {
|
function findModel(name: string) {
|
||||||
console.log('Find model', name);
|
|
||||||
if (!expandedNames.value.includes(name)) expandedNames.value.push(name);
|
if (!expandedNames.value.includes(name)) expandedNames.value.push(name);
|
||||||
console.log('Expanded', expandedNames.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({findModel})
|
defineExpose({findModel})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yet-another-cad-viewer",
|
"name": "yet-another-cad-viewer",
|
||||||
"version": "0.8.3",
|
"version": "0.8.5",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "yacv-server"
|
name = "yacv-server"
|
||||||
version = "0.8.3"
|
version = "0.8.5"
|
||||||
description = "Yet Another CAD Viewer (server)"
|
description = "Yet Another CAD Viewer (server)"
|
||||||
authors = ["Yeicor <4929005+Yeicor@users.noreply.github.com>"]
|
authors = ["Yeicor <4929005+Yeicor@users.noreply.github.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
Utilities to work with CAD objects
|
Utilities to work with CAD objects
|
||||||
"""
|
"""
|
||||||
import hashlib
|
import hashlib
|
||||||
from typing import Optional, Union, List, Tuple
|
from typing import Optional, Union, Tuple
|
||||||
|
|
||||||
from OCP.TopLoc import TopLoc_Location
|
from OCP.TopLoc import TopLoc_Location
|
||||||
from OCP.TopoDS import TopoDS_Shape
|
from OCP.TopoDS import TopoDS_Shape
|
||||||
|
from build123d import Compound, Shape
|
||||||
|
|
||||||
from yacv_server.gltf import GLTFMgr
|
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
|
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 """
|
""" Get the shape of a CAD-like object """
|
||||||
|
|
||||||
# Try to grab a shape if a different type of object was passed
|
# Try to grab a shape if a different type of object was passed
|
||||||
@@ -40,6 +41,17 @@ def get_shape(obj: CADLike, error: bool = True) -> Optional[CADCoreLike]:
|
|||||||
if isinstance(obj, TopoDS_Shape):
|
if isinstance(obj, TopoDS_Shape):
|
||||||
return obj
|
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:
|
if error:
|
||||||
raise ValueError(f'Cannot show object of type {type(obj)} (submit issue?)')
|
raise ValueError(f'Cannot show object of type {type(obj)} (submit issue?)')
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user