mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-20 06:27:04 +01:00
Compare commits
168 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22ea0617e2 | ||
|
|
09c0994a34 | ||
|
|
0939e25da2 | ||
|
|
712e0a06e6 | ||
|
|
e73f745800 | ||
|
|
773ea797a1 | ||
|
|
aae20aeedf | ||
|
|
a3004e59fb | ||
|
|
5e76193f43 | ||
|
|
50a2627b55 | ||
|
|
cbddacb7b8 | ||
|
|
887e71b7b2 | ||
|
|
7c5bd2aea0 | ||
|
|
b9c3b34416 | ||
|
|
b2f9880fd0 | ||
|
|
4b0fdd3459 | ||
|
|
503501df4b | ||
|
|
62fa310ec7 | ||
|
|
83cc12e59d | ||
|
|
016db36c50 | ||
|
|
583c244769 | ||
|
|
79919041f2 | ||
|
|
263671b4a9 | ||
|
|
e3fc8f1e74 | ||
|
|
7b63f574a1 | ||
|
|
56929ca136 | ||
|
|
dda5abfc94 | ||
|
|
3f9971a171 | ||
|
|
f4326c5779 | ||
|
|
96e73c0e7f | ||
|
|
2f1d1d51b3 | ||
|
|
fb3027fe66 | ||
|
|
d2b1e04e88 | ||
|
|
8c75f02c57 | ||
|
|
68317b32e4 | ||
|
|
49f91383a0 | ||
|
|
15b0636df9 | ||
|
|
e880a9f53a | ||
|
|
0ab8126e18 | ||
|
|
d9da038a06 | ||
|
|
186ca9d5bc | ||
|
|
739ce7f6c3 | ||
|
|
ba42fcfbd4 | ||
|
|
a0144a5432 | ||
|
|
66fd788398 | ||
|
|
9d2166dbe6 | ||
|
|
66396dfaee | ||
|
|
c539538d74 | ||
|
|
7d0ea24e23 | ||
|
|
c7ec469796 | ||
|
|
0c78964316 | ||
|
|
04c18e5198 | ||
|
|
25f0c04915 | ||
|
|
723efa232e | ||
|
|
49d154eeb0 | ||
|
|
5892c454d3 | ||
|
|
08d89836da | ||
|
|
e5e9493b35 | ||
|
|
775f5d3700 | ||
|
|
1ae0e508a8 | ||
|
|
2c5d73e957 | ||
|
|
6e0afef6bf | ||
|
|
4884030bd6 | ||
|
|
64ce65a568 | ||
|
|
3982706365 | ||
|
|
e71ebd75b3 | ||
|
|
c013916299 | ||
|
|
3787625230 | ||
|
|
ff712be60a | ||
|
|
ab0c1b1482 | ||
|
|
32246a890e | ||
|
|
72afb4b439 | ||
|
|
1b2312f0e5 | ||
|
|
87c6b12365 | ||
|
|
3b4982bb24 | ||
|
|
673814e1dd | ||
|
|
4b59ef2d1a | ||
|
|
cdec62f5b4 | ||
|
|
48ef6d2470 | ||
|
|
74fcc1b6b6 | ||
|
|
36da97e8c8 | ||
|
|
6a8ca13de3 | ||
|
|
60de0833b5 | ||
|
|
263a8e0c14 | ||
|
|
4d825ed763 | ||
|
|
5e3841186b | ||
|
|
18f22407c9 | ||
|
|
14817b6505 | ||
|
|
82b5e305d9 | ||
|
|
da94809cd2 | ||
|
|
933ec17293 | ||
|
|
dc91e4b497 | ||
|
|
9e740db791 | ||
|
|
c26cb78cc4 | ||
|
|
24c892c061 | ||
|
|
ec63c9efdc | ||
|
|
6f90e3c448 | ||
|
|
2205901752 | ||
|
|
ad5bb1b8a3 | ||
|
|
8ad0ffa147 | ||
|
|
96dbda0374 | ||
|
|
90c03ea99a | ||
|
|
50a7efa4ac | ||
|
|
dd6154955c | ||
|
|
d3847aae12 | ||
|
|
c74b695907 | ||
|
|
c720a8c03e | ||
|
|
a05c19c25e | ||
|
|
9437ccd987 | ||
|
|
20300ca2d9 | ||
|
|
42d0e0e158 | ||
|
|
4cd3e0ebfb | ||
|
|
83d34e5f21 | ||
|
|
94e7878c86 | ||
|
|
56b8dbadf7 | ||
|
|
a54873314e | ||
|
|
c7d672875e | ||
|
|
9d261f1718 | ||
|
|
db0f139cf8 | ||
|
|
3b394d5325 | ||
|
|
1d4e9efc5c | ||
|
|
097b17386b | ||
|
|
c65b871404 | ||
|
|
64a2c9e630 | ||
|
|
62aa53be27 | ||
|
|
6c9cba552e | ||
|
|
624fcb6fcf | ||
|
|
835d72d6dc | ||
|
|
4c0a752b3b | ||
|
|
f6b6039c5f | ||
|
|
ad7caae417 | ||
|
|
c96b910f5e | ||
|
|
8c5dc4fefd | ||
|
|
fd824f611f | ||
|
|
66fd1d88c7 | ||
|
|
3539918b12 | ||
|
|
a385ff9cb5 | ||
|
|
a80cfffcda | ||
|
|
4f0da2bee1 | ||
|
|
62808e3e52 | ||
|
|
2f901f78c1 | ||
|
|
9fa15f1a49 | ||
|
|
01431bc46c | ||
|
|
073f9086e0 | ||
|
|
36f9b09bd7 | ||
|
|
135023c950 | ||
|
|
dcc206ec78 | ||
|
|
e8a23f2ef1 | ||
|
|
35f8c047df | ||
|
|
0b670896f4 | ||
|
|
d5a9f6a1f5 | ||
|
|
258bd831b3 | ||
|
|
507f5141df | ||
|
|
31bd324d0f | ||
|
|
a1bd159a84 | ||
|
|
b472f1adfb | ||
|
|
9fd6a81d98 | ||
|
|
14d51a4cb0 | ||
|
|
456b593ad0 | ||
|
|
9af50dccd6 | ||
|
|
21e85b1ce6 | ||
|
|
1f30c2fd0a | ||
|
|
ba05a8072b | ||
|
|
27f540be23 | ||
|
|
e8c0f683c5 | ||
|
|
345636e478 | ||
|
|
9a0fb03526 | ||
|
|
2037621afc |
1120
assets/licenses.txt
1120
assets/licenses.txt
File diff suppressed because it is too large
Load Diff
@@ -3,13 +3,12 @@
|
|||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Download the contents of this folder.
|
1. Download the contents of this folder.
|
||||||
2. Assuming you have a recent version of Python installed, install the required packages:
|
2. Assuming you have a recent version of Python 3 installed, install the required packages:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
|
. venv/bin/activate # Execute this line every time you change the terminal
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
# Do this every time you change the terminal:
|
|
||||||
. venv/bin/activate
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from build123d import * # Also works with cadquery objects!
|
from build123d import * # Also works with cadquery objects!
|
||||||
|
from build123d import Compound
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
@@ -13,11 +14,16 @@ from yacv_server import show, export_all # Check out other exported methods for
|
|||||||
# Create a simple object
|
# Create a simple object
|
||||||
with BuildPart() as example:
|
with BuildPart() as example:
|
||||||
Box(10, 10, 5)
|
Box(10, 10, 5)
|
||||||
Cylinder(3, 5, mode=Mode.SUBTRACT)
|
Cylinder(4, 5, mode=Mode.SUBTRACT)
|
||||||
|
|
||||||
|
# Custom colors (optional)
|
||||||
|
example.color = (0.1, 0.3, 0.1, 1) # RGBA
|
||||||
|
to_highlight = example.edges().group_by(Axis.Z)[-1]
|
||||||
|
example_hl = Compound(to_highlight).translate((0, 0, 1e-3)) # To avoid z-fighting
|
||||||
|
example_hl.color = (1, 1, .0, 1)
|
||||||
|
|
||||||
# Show it in the frontend with hot-reloading
|
# Show it in the frontend with hot-reloading
|
||||||
show(example)
|
show(example, example_hl)
|
||||||
|
|
||||||
|
|
||||||
# %%
|
# %%
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<!--suppress SillyAssignmentJS -->
|
<!--suppress SillyAssignmentJS -->
|
||||||
<script setup lang="ts">
|
<script lang="ts" setup>
|
||||||
import {defineAsyncComponent, provide, type Ref, ref, shallowRef, triggerRef} from "vue";
|
import {defineAsyncComponent, provide, type Ref, ref, shallowRef, triggerRef, watch} from "vue";
|
||||||
import Sidebar from "./misc/Sidebar.vue";
|
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";
|
||||||
@@ -83,6 +83,12 @@ networkMgr.addEventListener('update', (e) => onModelUpdateRequest(e as NetworkUp
|
|||||||
for (let model of settings.preload) {
|
for (let model of settings.preload) {
|
||||||
networkMgr.load(model);
|
networkMgr.load(model);
|
||||||
}
|
}
|
||||||
|
watch(viewer, (newViewer) => {
|
||||||
|
if (newViewer) {
|
||||||
|
newViewer.setPosterText('<tspan x="50%" dy="1.2em">Trying to load' +
|
||||||
|
' models from:</tspan>' + settings.preload.map((url) => '<tspan x="50%" dy="1.2em">- ' + url + '</tspan>').join(""));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function loadModelManual() {
|
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:");
|
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:");
|
||||||
@@ -99,20 +105,20 @@ async function loadModelManual() {
|
|||||||
</v-main>
|
</v-main>
|
||||||
|
|
||||||
<!-- The left collapsible sidebar has the list of models -->
|
<!-- The left collapsible sidebar has the list of models -->
|
||||||
<sidebar :opened-init="openSidebarsByDefault" side="left" :width="300">
|
<sidebar :opened-init="openSidebarsByDefault" :width="300" side="left">
|
||||||
<template #toolbar>
|
<template #toolbar>
|
||||||
<v-toolbar-title>Models</v-toolbar-title>
|
<v-toolbar-title>Models</v-toolbar-title>
|
||||||
</template>
|
</template>
|
||||||
<template #toolbar-items>
|
<template #toolbar-items>
|
||||||
<v-btn icon="" @click="loadModelManual">
|
<v-btn icon="" @click="loadModelManual">
|
||||||
<svg-icon type="mdi" :path="mdiPlus"/>
|
<svg-icon :path="mdiPlus" type="mdi"/>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<models ref="models" :viewer="viewer" @remove="onModelRemoveRequest"/>
|
<models ref="models" :viewer="viewer" @remove="onModelRemoveRequest"/>
|
||||||
</sidebar>
|
</sidebar>
|
||||||
|
|
||||||
<!-- The right collapsible sidebar has the list of tools -->
|
<!-- The right collapsible sidebar has the list of tools -->
|
||||||
<sidebar :opened-init="openSidebarsByDefault" side="right" :width="48 * 3 /* buttons */ + 1 /* border? */">
|
<sidebar :opened-init="openSidebarsByDefault" :width="48 * 3 /* buttons */ + 1 /* border? */" side="right">
|
||||||
<template #toolbar>
|
<template #toolbar>
|
||||||
<v-toolbar-title>Tools</v-toolbar-title>
|
<v-toolbar-title>Tools</v-toolbar-title>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script lang="ts" setup>
|
||||||
import {VContainer, VRow, VCol, VProgressCircular} from "vuetify/lib/components/index.mjs";
|
import {VCol, VContainer, VProgressCircular, VRow} from "vuetify/lib/components/index.mjs";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script lang="ts" setup>
|
||||||
import {ref} from "vue";
|
import {ref} from "vue";
|
||||||
import {VBtn, VNavigationDrawer, VToolbar, VToolbarItems} from "vuetify/lib/components/index.mjs";
|
import {VBtn, VNavigationDrawer, VToolbar, VToolbarItems} from "vuetify/lib/components/index.mjs";
|
||||||
import {mdiChevronLeft, mdiChevronRight, mdiClose} from '@mdi/js'
|
import {mdiChevronLeft, mdiChevronRight, mdiClose} from '@mdi/js'
|
||||||
@@ -16,22 +16,22 @@ const openIcon = props.side === 'left' ? mdiChevronRight : mdiChevronLeft;
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-btn icon @click="opened = !opened" class="open-button" :class="side">
|
<v-btn :class="side" class="open-button" icon @click="opened = !opened">
|
||||||
<svg-icon type="mdi" :path="openIcon"/>
|
<svg-icon :path="openIcon" type="mdi"/>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-navigation-drawer v-model="opened" permanent :location="side" :width="props.width">
|
<v-navigation-drawer v-model="opened" :location="side" :width="props.width" permanent>
|
||||||
<v-toolbar density="compact">
|
<v-toolbar density="compact">
|
||||||
<v-toolbar-items v-if="side == 'right'">
|
<v-toolbar-items v-if="side == 'right'">
|
||||||
<slot name="toolbar-items"></slot>
|
<slot name="toolbar-items"></slot>
|
||||||
<v-btn icon @click="opened = !opened">
|
<v-btn icon @click="opened = !opened">
|
||||||
<svg-icon type="mdi" :path="mdiClose"/>
|
<svg-icon :path="mdiClose" type="mdi"/>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-toolbar-items>
|
</v-toolbar-items>
|
||||||
<slot name="toolbar"></slot>
|
<slot name="toolbar"></slot>
|
||||||
<v-toolbar-items v-if="side == 'left'">
|
<v-toolbar-items v-if="side == 'left'">
|
||||||
<slot name="toolbar-items"></slot>
|
<slot name="toolbar-items"></slot>
|
||||||
<v-btn icon @click="opened = !opened">
|
<v-btn icon @click="opened = !opened">
|
||||||
<svg-icon type="mdi" :path="mdiClose"/>
|
<svg-icon :path="mdiClose" type="mdi"/>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-toolbar-items>
|
</v-toolbar-items>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {Buffer, Document, Scene, type Transform, WebIO} from "@gltf-transform/core";
|
import {Buffer, Document, Scene, type Transform, WebIO} from "@gltf-transform/core";
|
||||||
import {unpartition} from "@gltf-transform/functions";
|
import {unpartition, mergeDocuments} from "@gltf-transform/functions";
|
||||||
|
|
||||||
let io = new WebIO();
|
let io = new WebIO();
|
||||||
export let extrasNameKey = "__yacv_name";
|
export let extrasNameKey = "__yacv_name";
|
||||||
@@ -56,7 +56,8 @@ export async function mergePartial(url: string, name: string, document: Document
|
|||||||
await newDoc.transform(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);
|
mergeDocuments(document, newDoc);
|
||||||
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function mergeFinalize(document: Document): Promise<Document> {
|
export async function mergeFinalize(document: Document): Promise<Document> {
|
||||||
|
|||||||
@@ -37,19 +37,6 @@ export class SceneMgr {
|
|||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async reloadHelpers(sceneUrl: Ref<string>, document: Document, reloadScene: boolean): Promise<Document> {
|
|
||||||
let bb = SceneMgr.getBoundingBox(document);
|
|
||||||
if (!bb) return document;
|
|
||||||
|
|
||||||
// Create the helper axes and grid box
|
|
||||||
let helpersDoc = new Document();
|
|
||||||
let transform = (new Matrix4()).makeTranslation(bb.getCenter(new Vector3()));
|
|
||||||
newAxes(helpersDoc, bb.getSize(new Vector3()).multiplyScalar(0.5), transform);
|
|
||||||
newGridBox(helpersDoc, bb.getSize(new Vector3()), transform);
|
|
||||||
let helpersUrl = URL.createObjectURL(new Blob([await toBuffer(helpersDoc)]));
|
|
||||||
return await SceneMgr.loadModel(sceneUrl, document, extrasNameValueHelpers, helpersUrl, false, reloadScene);
|
|
||||||
}
|
|
||||||
|
|
||||||
static getBoundingBox(document: Document): Box3 | null {
|
static getBoundingBox(document: Document): Box3 | null {
|
||||||
if (document.getRoot().listNodes().length === 0) return null;
|
if (document.getRoot().listNodes().length === 0) return null;
|
||||||
// Get bounding box of the model and use it to set the size of the helpers
|
// Get bounding box of the model and use it to set the size of the helpers
|
||||||
@@ -91,6 +78,19 @@ export class SceneMgr {
|
|||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async reloadHelpers(sceneUrl: Ref<string>, document: Document, reloadScene: boolean): Promise<Document> {
|
||||||
|
let bb = SceneMgr.getBoundingBox(document);
|
||||||
|
if (!bb) return document;
|
||||||
|
|
||||||
|
// Create the helper axes and grid box
|
||||||
|
let helpersDoc = new Document();
|
||||||
|
let transform = (new Matrix4()).makeTranslation(bb.getCenter(new Vector3()));
|
||||||
|
newAxes(helpersDoc, bb.getSize(new Vector3()).multiplyScalar(0.5), transform);
|
||||||
|
newGridBox(helpersDoc, bb.getSize(new Vector3()), transform);
|
||||||
|
let helpersUrl = URL.createObjectURL(new Blob([await toBuffer(helpersDoc)]));
|
||||||
|
return await SceneMgr.loadModel(sceneUrl, document, extrasNameValueHelpers, helpersUrl, false, reloadScene);
|
||||||
|
}
|
||||||
|
|
||||||
/** Serializes the current document into a GLB and updates the viewerSrc */
|
/** Serializes the current document into a GLB and updates the viewerSrc */
|
||||||
private static async showCurrentDoc(sceneUrl: Ref<string>, document: Document): Promise<Document> {
|
private static async showCurrentDoc(sceneUrl: Ref<string>, document: Document): Promise<Document> {
|
||||||
// Make sure the document is fully loaded and ready to be shown
|
// Make sure the document is fully loaded and ready to be shown
|
||||||
|
|||||||
@@ -18,8 +18,11 @@ export const settings = {
|
|||||||
monitorEveryMs: 100,
|
monitorEveryMs: 100,
|
||||||
monitorOpenTimeoutMs: 1000,
|
monitorOpenTimeoutMs: 1000,
|
||||||
// ModelViewer settings
|
// ModelViewer settings
|
||||||
autoplay: true,
|
autoplay: true, // Global animation toggle
|
||||||
arModes: 'webxr scene-viewer quick-look',
|
arModes: 'webxr scene-viewer quick-look',
|
||||||
|
zoomSensitivity: 0.25,
|
||||||
|
orbitSensitivity: 1,
|
||||||
|
panSensitivity: 1,
|
||||||
exposure: 1,
|
exposure: 1,
|
||||||
shadowIntensity: 0,
|
shadowIntensity: 0,
|
||||||
background: '',
|
background: '',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script lang="ts" setup>
|
||||||
import {
|
import {
|
||||||
VBtn,
|
VBtn,
|
||||||
VBtnToggle,
|
VBtnToggle,
|
||||||
@@ -33,7 +33,7 @@ import {Plane} from "three/src/math/Plane.js";
|
|||||||
import {Vector3} from "three/src/math/Vector3.js";
|
import {Vector3} from "three/src/math/Vector3.js";
|
||||||
import type {MObject3D} from "../tools/Selection.vue";
|
import type {MObject3D} from "../tools/Selection.vue";
|
||||||
import {toLineSegments} from "../misc/lines.js";
|
import {toLineSegments} from "../misc/lines.js";
|
||||||
import {settings} from "../misc/settings.js";
|
import {settings} from "../misc/settings.js"
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
meshes: Array<Mesh>,
|
meshes: Array<Mesh>,
|
||||||
@@ -178,8 +178,6 @@ watch(clipPlaneZ, onClipPlanesChange);
|
|||||||
watch(clipPlaneSwappedX, onClipPlanesChange);
|
watch(clipPlaneSwappedX, onClipPlanesChange);
|
||||||
watch(clipPlaneSwappedY, onClipPlanesChange);
|
watch(clipPlaneSwappedY, onClipPlanesChange);
|
||||||
watch(clipPlaneSwappedZ, onClipPlanesChange);
|
watch(clipPlaneSwappedZ, onClipPlanesChange);
|
||||||
// Clip planes are also affected by the camera position, so we need to listen to camera changes
|
|
||||||
props.viewer!!.onElemReady((elem) => elem.addEventListener('camera-change', onClipPlanesChange))
|
|
||||||
|
|
||||||
let edgeWidthChangeCleanup = [] as Array<() => void>;
|
let edgeWidthChangeCleanup = [] as Array<() => void>;
|
||||||
|
|
||||||
@@ -318,95 +316,101 @@ function onModelLoad() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// props.viewer.elem may not yet be available, so we need to wait for it
|
// props.viewer.elem may not yet be available, so we need to wait for it
|
||||||
props.viewer!!.onElemReady((elem) => elem.addEventListener('load', onModelLoad))
|
const onViewerReady = (viewer: InstanceType<typeof ModelViewerWrapper>) => {
|
||||||
|
viewer?.onElemReady((elem: HTMLElement) => {
|
||||||
|
elem.addEventListener('before-render', onModelLoad);
|
||||||
|
elem.addEventListener('camera-change', onClipPlanesChange);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (props.viewer) onViewerReady(props.viewer); else watch((() => props.viewer) as any, onViewerReady);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-expansion-panel :value="modelName">
|
<v-expansion-panel :value="modelName">
|
||||||
<v-expansion-panel-title expand-icon="hide-this-icon" collapse-icon="hide-this-icon">
|
<v-expansion-panel-title collapse-icon="hide-this-icon" expand-icon="hide-this-icon">
|
||||||
<v-btn-toggle v-model="enabledFeatures" multiple @click.stop color="surface-light">
|
<v-btn-toggle v-model="enabledFeatures" color="surface-light" multiple @click.stop>
|
||||||
<v-btn icon>
|
<v-btn icon>
|
||||||
<v-tooltip activator="parent">Toggle Faces ({{ faceCount }})</v-tooltip>
|
<v-tooltip activator="parent">Toggle Faces ({{ faceCount }})</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiRectangle" :rotate="90"></svg-icon>
|
<svg-icon :path="mdiRectangle" :rotate="90" type="mdi"></svg-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn icon>
|
<v-btn icon>
|
||||||
<v-tooltip activator="parent">Toggle Edges ({{ edgeCount }})</v-tooltip>
|
<v-tooltip activator="parent">Toggle Edges ({{ edgeCount }})</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiRectangleOutline" :rotate="90"></svg-icon>
|
<svg-icon :path="mdiRectangleOutline" :rotate="90" type="mdi"></svg-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn icon>
|
<v-btn icon>
|
||||||
<v-tooltip activator="parent">Toggle Vertices ({{ vertexCount }})</v-tooltip>
|
<v-tooltip activator="parent">Toggle Vertices ({{ vertexCount }})</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiVectorRectangle" :rotate="90"></svg-icon>
|
<svg-icon :path="mdiVectorRectangle" :rotate="90" type="mdi"></svg-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-btn-toggle>
|
</v-btn-toggle>
|
||||||
<div class="model-name">{{ modelName }}</div>
|
<div class="model-name">{{ modelName }}</div>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-btn icon @click.stop="emit('remove')">
|
<v-btn icon @click.stop="emit('remove')">
|
||||||
<v-tooltip activator="parent">Remove</v-tooltip>
|
<v-tooltip activator="parent">Remove</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiDelete"></svg-icon>
|
<svg-icon :path="mdiDelete" type="mdi"></svg-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-expansion-panel-title>
|
</v-expansion-panel-title>
|
||||||
<v-expansion-panel-text>
|
<v-expansion-panel-text>
|
||||||
<v-slider v-model="opacity" hide-details min="0" max="1" :step="0.1">
|
<v-slider v-model="opacity" :step="0.1" hide-details max="1" min="0">
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<v-tooltip activator="parent">Change opacity</v-tooltip>
|
<v-tooltip activator="parent">Change opacity</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiCircleOpacity"></svg-icon>
|
<svg-icon :path="mdiCircleOpacity" type="mdi"></svg-icon>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<v-tooltip activator="parent">Wireframe</v-tooltip>
|
<v-tooltip activator="parent">Wireframe</v-tooltip>
|
||||||
<v-checkbox-btn trueIcon="mdi-triangle-outline" falseIcon="mdi-triangle" v-model="wireframe"></v-checkbox-btn>
|
<v-checkbox-btn v-model="wireframe" falseIcon="mdi-triangle" trueIcon="mdi-triangle-outline"></v-checkbox-btn>
|
||||||
</template>
|
</template>
|
||||||
</v-slider>
|
</v-slider>
|
||||||
<v-slider v-if="edgeCount > 0 || vertexCount > 0" v-model="edgeWidth" hide-details min="0" max="1">
|
<v-slider v-if="edgeCount > 0 || vertexCount > 0" v-model="edgeWidth" hide-details max="1" min="0">
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<v-tooltip activator="parent">Edge and vertex sizes</v-tooltip>
|
<v-tooltip activator="parent">Edge and vertex sizes</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiVectorLine"></svg-icon>
|
<svg-icon :path="mdiVectorLine" type="mdi"></svg-icon>
|
||||||
</template>
|
</template>
|
||||||
</v-slider>
|
</v-slider>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-slider v-model="clipPlaneX" hide-details min="0" max="1">
|
<v-slider v-model="clipPlaneX" hide-details max="1" min="0">
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<v-tooltip activator="parent">Clip plane X</v-tooltip>
|
<v-tooltip activator="parent">Clip plane X</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiCube" :rotate="120"></svg-icon>
|
<svg-icon :path="mdiCube" :rotate="120" type="mdi"></svg-icon>
|
||||||
X
|
X
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<v-tooltip activator="parent">Swap clip plane X</v-tooltip>
|
<v-tooltip activator="parent">Swap clip plane X</v-tooltip>
|
||||||
<v-checkbox-btn trueIcon="mdi-checkbox-marked-outline" falseIcon="mdi-checkbox-blank-outline"
|
<v-checkbox-btn v-model="clipPlaneSwappedX" falseIcon="mdi-checkbox-blank-outline"
|
||||||
v-model="clipPlaneSwappedX">
|
trueIcon="mdi-checkbox-marked-outline">
|
||||||
<template v-slot:label>
|
<template v-slot:label>
|
||||||
<svg-icon type="mdi" :path="mdiSwapHorizontal"></svg-icon>
|
<svg-icon :path="mdiSwapHorizontal" type="mdi"></svg-icon>
|
||||||
</template>
|
</template>
|
||||||
</v-checkbox-btn>
|
</v-checkbox-btn>
|
||||||
</template>
|
</template>
|
||||||
</v-slider>
|
</v-slider>
|
||||||
<v-slider v-model="clipPlaneZ" hide-details min="0" max="1">
|
<v-slider v-model="clipPlaneZ" hide-details max="1" min="0">
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<v-tooltip activator="parent">Clip plane Y</v-tooltip>
|
<v-tooltip activator="parent">Clip plane Y</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiCube" :rotate="-120"></svg-icon>
|
<svg-icon :path="mdiCube" :rotate="-120" type="mdi"></svg-icon>
|
||||||
Y
|
Y
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<v-tooltip activator="parent">Swap clip plane Y</v-tooltip>
|
<v-tooltip activator="parent">Swap clip plane Y</v-tooltip>
|
||||||
<v-checkbox-btn trueIcon="mdi-checkbox-marked-outline" falseIcon="mdi-checkbox-blank-outline"
|
<v-checkbox-btn v-model="clipPlaneSwappedZ" falseIcon="mdi-checkbox-blank-outline"
|
||||||
v-model="clipPlaneSwappedZ">
|
trueIcon="mdi-checkbox-marked-outline">
|
||||||
<template v-slot:label>
|
<template v-slot:label>
|
||||||
<svg-icon type="mdi" :path="mdiSwapHorizontal"></svg-icon>
|
<svg-icon :path="mdiSwapHorizontal" type="mdi"></svg-icon>
|
||||||
</template>
|
</template>
|
||||||
</v-checkbox-btn>
|
</v-checkbox-btn>
|
||||||
</template>
|
</template>
|
||||||
</v-slider>
|
</v-slider>
|
||||||
<v-slider v-model="clipPlaneY" hide-details min="0" max="1">
|
<v-slider v-model="clipPlaneY" hide-details max="1" min="0">
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<v-tooltip activator="parent">Clip plane Z</v-tooltip>
|
<v-tooltip activator="parent">Clip plane Z</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiCube"></svg-icon>
|
<svg-icon :path="mdiCube" type="mdi"></svg-icon>
|
||||||
Z
|
Z
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<v-tooltip activator="parent">Swap clip plane Z</v-tooltip>
|
<v-tooltip activator="parent">Swap clip plane Z</v-tooltip>
|
||||||
<v-checkbox-btn trueIcon="mdi-checkbox-marked-outline" falseIcon="mdi-checkbox-blank-outline"
|
<v-checkbox-btn v-model="clipPlaneSwappedY" falseIcon="mdi-checkbox-blank-outline"
|
||||||
v-model="clipPlaneSwappedY">
|
trueIcon="mdi-checkbox-marked-outline">
|
||||||
<template v-slot:label>
|
<template v-slot:label>
|
||||||
<svg-icon type="mdi" :path="mdiSwapHorizontal"></svg-icon>
|
<svg-icon :path="mdiSwapHorizontal" type="mdi"></svg-icon>
|
||||||
</template>
|
</template>
|
||||||
</v-checkbox-btn>
|
</v-checkbox-btn>
|
||||||
</template>
|
</template>
|
||||||
@@ -440,11 +444,12 @@ props.viewer!!.onElemReady((elem) => elem.addEventListener('load', onModelLoad))
|
|||||||
}
|
}
|
||||||
|
|
||||||
.model-name {
|
.model-name {
|
||||||
width: 130px;
|
width: 179px;
|
||||||
min-height: 1.15em; /* HACK: Avoid eating the bottom of the text when using 1 line */
|
font-size: 110%;
|
||||||
max-height: 2em;
|
overflow-x: clip;
|
||||||
|
overflow-y: visible; /* HACK: bottom of text is lost otherwise (due to buggy -webkit-box bounds?) */
|
||||||
|
word-wrap: break-word;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 2; /* https://caniuse.com/?search=line-clamp */
|
-webkit-line-clamp: 2; /* https://caniuse.com/?search=line-clamp */
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script lang="ts" setup>
|
||||||
import {VExpansionPanels} from "vuetify/lib/components/index.mjs";
|
import {VExpansionPanels} from "vuetify/lib/components/index.mjs";
|
||||||
import type ModelViewerWrapper from "../viewer/ModelViewerWrapper.vue";
|
import type ModelViewerWrapper from "../viewer/ModelViewerWrapper.vue";
|
||||||
import {Document, Mesh} from "@gltf-transform/core";
|
import {Document, Mesh} from "@gltf-transform/core";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script lang="ts" setup>
|
||||||
// License text for all dependencies, only downloaded when/if needed
|
// License text for all dependencies, only downloaded when/if needed
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import licenseText from "../../assets/licenses.txt?raw";
|
import licenseText from "../../assets/licenses.txt?raw";
|
||||||
|
|||||||
@@ -1,27 +1,29 @@
|
|||||||
<script setup lang="ts">
|
<script lang="ts" setup>
|
||||||
import {onMounted, onUpdated, ref} from "vue";
|
import {onMounted, onUpdated, ref} from "vue";
|
||||||
import type {ModelScene} from "@google/model-viewer/lib/three-components/ModelScene";
|
import type {ModelScene} from "@google/model-viewer/lib/three-components/ModelScene";
|
||||||
import * as OrientationGizmoRaw from "three-orientation-gizmo/src/OrientationGizmo";
|
import * as OrientationGizmoRaw from "three-orientation-gizmo/src/OrientationGizmo";
|
||||||
import type {ModelViewerElement} from '@google/model-viewer';
|
|
||||||
|
|
||||||
// Optimized minimal dependencies from three
|
// Optimized minimal dependencies from three
|
||||||
import {Vector3} from "three/src/math/Vector3.js";
|
import {Vector3} from "three/src/math/Vector3.js";
|
||||||
import {Matrix4} from "three/src/math/Matrix4.js";
|
import {Matrix4} from "three/src/math/Matrix4.js";
|
||||||
|
import type ModelViewerWrapper from "../viewer/ModelViewerWrapper.vue";
|
||||||
|
|
||||||
(globalThis as any).THREE = {Vector3, Matrix4} as any // HACK: Required for the gizmo to work
|
(globalThis as any).THREE = {Vector3, Matrix4} as any // HACK: Required for the gizmo to work
|
||||||
|
|
||||||
const OrientationGizmo = OrientationGizmoRaw.default;
|
const props = defineProps<{ viewer: InstanceType<typeof ModelViewerWrapper> }>();
|
||||||
|
|
||||||
const props = defineProps<{ elem: ModelViewerElement | null, scene: ModelScene }>();
|
|
||||||
|
|
||||||
function createGizmo(expectedParent: HTMLElement, scene: ModelScene): HTMLElement {
|
function createGizmo(expectedParent: HTMLElement, scene: ModelScene): HTMLElement {
|
||||||
// noinspection SpellCheckingInspection
|
// noinspection SpellCheckingInspection
|
||||||
let gizmo = new OrientationGizmoRaw.default(scene.camera, {
|
let gizmo = new OrientationGizmoRaw.default(scene.camera, {
|
||||||
size: expectedParent.clientWidth,
|
size: expectedParent.clientWidth,
|
||||||
bubbleSizePrimary: expectedParent.clientWidth / 12,
|
bubbleSizePrimary: expectedParent.clientWidth / 12,
|
||||||
bubbleSizeSeconday: expectedParent.clientWidth / 14,
|
bubbleSizeSeconday: expectedParent.clientWidth / 12,
|
||||||
fontSize: (expectedParent.clientWidth / 10) + "px"
|
fontSize: (expectedParent.clientWidth / 10) + "px",
|
||||||
});
|
});
|
||||||
|
// Make sure all bubbles are labeled
|
||||||
|
for (let bubble of gizmo.bubbles) {
|
||||||
|
bubble.label = bubble.axis.toUpperCase();
|
||||||
|
}
|
||||||
// HACK: Swap axes to fake the CAD orientation
|
// HACK: Swap axes to fake the CAD orientation
|
||||||
for (let swap of [["y", "-z"], ["z", "-y"], ["z", "-z"]]) {
|
for (let swap of [["y", "-z"], ["z", "-y"], ["z", "-z"]]) {
|
||||||
let indexA = gizmo.bubbles.findIndex((bubble: any) => bubble.axis == swap[0])
|
let indexA = gizmo.bubbles.findIndex((bubble: any) => bubble.axis == swap[0])
|
||||||
@@ -33,21 +35,26 @@ function createGizmo(expectedParent: HTMLElement, scene: ModelScene): HTMLElemen
|
|||||||
}
|
}
|
||||||
// Append and listen for events
|
// Append and listen for events
|
||||||
gizmo.onAxisSelected = (axis: { direction: { x: any; y: any; z: any; }; }) => {
|
gizmo.onAxisSelected = (axis: { direction: { x: any; y: any; z: any; }; }) => {
|
||||||
let lookFrom = scene.getCamera().position.clone();
|
if (!props.viewer.elem || !props.viewer.controls) return;
|
||||||
let lookAt = scene.getTarget().clone().add(scene.target.position);
|
// Animate the controls to the new wanted angle
|
||||||
let magnitude = lookFrom.clone().sub(lookAt).length()
|
const controls = props.viewer.controls;
|
||||||
let direction = new Vector3(axis.direction.x, axis.direction.y, axis.direction.z);
|
const {theta: curTheta/*, phi: curPhi*/} = (controls as any).goalSpherical;
|
||||||
let newLookFrom = lookAt.clone().add(direction.clone().multiplyScalar(magnitude));
|
let wantedTheta = NaN;
|
||||||
//console.log("New camera position", newLookFrom)
|
let wantedPhi = NaN;
|
||||||
scene.getCamera().position.copy(newLookFrom);
|
let attempt = 0
|
||||||
scene.getCamera().lookAt(lookAt);
|
while ((attempt == 0 || curTheta == wantedTheta) && attempt < 2) {
|
||||||
if ((scene as any).__perspectiveCamera) { // HACK: Make the hacky ortho also work
|
if (attempt > 0) { // Flip the camera if the user clicks on the same axis
|
||||||
(scene as any).__perspectiveCamera.position.copy(newLookFrom);
|
axis.direction.x = -axis.direction.x;
|
||||||
(scene as any).__perspectiveCamera.lookAt(lookAt);
|
axis.direction.y = -axis.direction.y;
|
||||||
|
axis.direction.z = -axis.direction.z;
|
||||||
}
|
}
|
||||||
|
wantedTheta = Math.atan2(axis.direction.x, axis.direction.z);
|
||||||
|
wantedPhi = Math.asin(-axis.direction.y) + Math.PI / 2;
|
||||||
|
attempt++;
|
||||||
|
}
|
||||||
|
controls.setOrbit(wantedTheta, wantedPhi);
|
||||||
|
props.viewer.elem?.dispatchEvent(new CustomEvent('camera-change', {detail: {source: 'none'}}))
|
||||||
scene.queueRender();
|
scene.queueRender();
|
||||||
requestIdleCallback(() => props.elem?.dispatchEvent(
|
|
||||||
new CustomEvent('camera-change', {detail: {source: 'none'}})), {timeout: 100})
|
|
||||||
}
|
}
|
||||||
return gizmo;
|
return gizmo;
|
||||||
}
|
}
|
||||||
@@ -67,7 +74,7 @@ function updateGizmo() {
|
|||||||
let reinstall = () => {
|
let reinstall = () => {
|
||||||
if (!container.value) return;
|
if (!container.value) return;
|
||||||
if (gizmo) container.value.removeChild(gizmo);
|
if (gizmo) container.value.removeChild(gizmo);
|
||||||
gizmo = createGizmo(container.value, props.scene as ModelScene) as typeof gizmo;
|
gizmo = createGizmo(container.value, props.viewer.scene!! as any) as typeof gizmo;
|
||||||
container.value.appendChild(gizmo);
|
container.value.appendChild(gizmo);
|
||||||
requestIdleCallback(updateGizmo, {timeout: 250}); // Low priority updates
|
requestIdleCallback(updateGizmo, {timeout: 250}); // Low priority updates
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script lang="ts" setup>
|
||||||
import {defineModel, inject, ref, type ShallowRef, watch} from "vue";
|
import {defineModel, inject, ref, type ShallowRef, watch} from "vue";
|
||||||
import {VBtn, VSelect, VTooltip} from "vuetify/lib/components/index.mjs";
|
import {VBtn, VSelect, VTooltip} from "vuetify/lib/components/index.mjs";
|
||||||
import SvgIcon from '@jamescoyle/vue-icon';
|
import SvgIcon from '@jamescoyle/vue-icon';
|
||||||
@@ -258,7 +258,7 @@ let onViewerReady = (viewer: typeof ModelViewerWrapperT) => {
|
|||||||
hasListeners = true;
|
hasListeners = true;
|
||||||
elem.addEventListener('mousedown', mouseDownListener); // Avoid clicking when dragging
|
elem.addEventListener('mousedown', mouseDownListener); // Avoid clicking when dragging
|
||||||
elem.addEventListener('mouseup', mouseUpListener);
|
elem.addEventListener('mouseup', mouseUpListener);
|
||||||
elem.addEventListener('load', () => {
|
elem.addEventListener('before-render', () => {
|
||||||
// After a reload of the scene, we need to recover object references and highlight them again
|
// After a reload of the scene, we need to recover object references and highlight them again
|
||||||
for (let sel of selected.value) {
|
for (let sel of selected.value) {
|
||||||
let scene = props.viewer?.scene;
|
let scene = props.viewer?.scene;
|
||||||
@@ -462,52 +462,41 @@ window.addEventListener('keydown', (event) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="select-parent">
|
<v-btn :color="selectionEnabled ? 'surface-light' : ''" icon @click="toggleSelection">
|
||||||
<v-btn icon @click="toggleSelection" :color="selectionEnabled ? 'surface-light' : ''">
|
|
||||||
<v-tooltip activator="parent">{{ selectionEnabled ? 'Disable (s)election mode' : 'Enable (s)election mode' }}
|
<v-tooltip activator="parent">{{ selectionEnabled ? 'Disable (s)election mode' : 'Enable (s)election mode' }}
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiCursorDefaultClick"/>
|
<svg-icon :path="mdiCursorDefaultClick" type="mdi"/>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-tooltip :text="'Select only ' + selectFilter.toString().toLocaleLowerCase()" :open-on-click="false">
|
<v-tooltip :open-on-click="false" :text="'Select only ' + selectFilter.toString().toLocaleLowerCase()">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-select v-bind="props" class="select-only" variant="underlined"
|
<v-select v-model="selectFilter" :items="['Any (S)', '(F)aces', '(E)dges', '(V)ertices']" class="select-only"
|
||||||
:items="['Any (S)', '(F)aces', '(E)dges', '(V)ertices']"
|
v-bind="props"
|
||||||
v-model="selectFilter"/>
|
variant="underlined"/>
|
||||||
</template>
|
</template>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</div>
|
<v-btn :color="highlightNextSelection[0] ? 'surface-light' : ''" icon @click="toggleHighlightNextSelection">
|
||||||
<v-btn icon @click="toggleHighlightNextSelection" :color="highlightNextSelection[0] ? 'surface-light' : ''">
|
|
||||||
<v-tooltip activator="parent">(H)ighlight the next clicked element in the models list</v-tooltip>
|
<v-tooltip activator="parent">(H)ighlight the next clicked element in the models list</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiFeatureSearch"/>
|
<svg-icon :path="mdiFeatureSearch" type="mdi"/>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn icon @click="toggleShowBoundingBox" :color="showBoundingBox ? 'surface-light' : ''">
|
<v-btn :color="showBoundingBox ? 'surface-light' : ''" icon @click="toggleShowBoundingBox">
|
||||||
<v-tooltip activator="parent">{{ showBoundingBox ? 'Hide selection (b)ounds' : 'Show selection (b)ounds' }}
|
<v-tooltip activator="parent">{{ showBoundingBox ? 'Hide selection (b)ounds' : 'Show selection (b)ounds' }}
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiCubeOutline"/>
|
<svg-icon :path="mdiCubeOutline" type="mdi"/>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn icon @click="toggleShowDistances" :color="showDistances ? 'surface-light' : ''">
|
<v-btn :color="showDistances ? 'surface-light' : ''" icon @click="toggleShowDistances">
|
||||||
<v-tooltip activator="parent">
|
<v-tooltip activator="parent">
|
||||||
{{ showDistances ? 'Hide selection (d)istances' : 'Show (d)istances (when a pair of features is selected)' }}
|
{{ showDistances ? 'Hide selection (d)istances' : 'Show (d)istances (when a pair of features is selected)' }}
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiRuler"/>
|
<svg-icon :path="mdiRuler" type="mdi"/>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* Very hacky styling... */
|
|
||||||
.select-parent {
|
|
||||||
height: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-parent .v-btn {
|
|
||||||
position: relative;
|
|
||||||
top: -20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-only {
|
.select-only {
|
||||||
display: inline-block;
|
float: right;
|
||||||
width: calc(100% - 48px);
|
height: 36px;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -12px;
|
top: -12px;
|
||||||
|
width: calc(100% - 48px);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script lang="ts" setup>
|
||||||
import {
|
import {
|
||||||
VBtn,
|
VBtn,
|
||||||
VCard,
|
VCard,
|
||||||
@@ -16,7 +16,6 @@ import {OrthographicCamera} from "three/src/cameras/OrthographicCamera.js";
|
|||||||
import {mdiClose, mdiCrosshairsGps, mdiDownload, mdiGithub, mdiLicense, mdiProjector} from '@mdi/js'
|
import {mdiClose, mdiCrosshairsGps, mdiDownload, mdiGithub, mdiLicense, mdiProjector} from '@mdi/js'
|
||||||
import SvgIcon from '@jamescoyle/vue-icon';
|
import SvgIcon from '@jamescoyle/vue-icon';
|
||||||
import type {ModelViewerElement} from '@google/model-viewer';
|
import type {ModelViewerElement} from '@google/model-viewer';
|
||||||
import type {MObject3D} from "./Selection.vue";
|
|
||||||
import Loading from "../misc/Loading.vue";
|
import Loading from "../misc/Loading.vue";
|
||||||
import type ModelViewerWrapper from "../viewer/ModelViewerWrapper.vue";
|
import type ModelViewerWrapper from "../viewer/ModelViewerWrapper.vue";
|
||||||
import {defineAsyncComponent, ref, type Ref} from "vue";
|
import {defineAsyncComponent, ref, type Ref} from "vue";
|
||||||
@@ -57,14 +56,14 @@ function syncOrthoCamera(force: boolean) {
|
|||||||
let h = perspectiveWidthAtCenter / scene.aspect;
|
let h = perspectiveWidthAtCenter / scene.aspect;
|
||||||
(scene as any).camera = new OrthographicCamera(-w, w, h, -h, perspectiveCam.near, perspectiveCam.far);
|
(scene as any).camera = new OrthographicCamera(-w, w, h, -h, perspectiveCam.near, perspectiveCam.far);
|
||||||
scene.camera.position.copy(perspectiveCam.position);
|
scene.camera.position.copy(perspectiveCam.position);
|
||||||
scene.camera.lookAt(lookAtCenter);
|
scene.camera.rotation.copy(perspectiveCam.rotation);
|
||||||
if (force) scene.queueRender() // Force rerender of model-viewer
|
if (force) scene.queueRender() // Force rerender of model-viewer
|
||||||
requestAnimationFrame(() => syncOrthoCamera(false));
|
requestAnimationFrame(() => syncOrthoCamera(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let toggleProjectionText = ref('PERSP'); // Default to perspective camera
|
let toggleProjectionText = ref('PERSP'); // Default to perspective camera
|
||||||
function toggleProjection() {
|
async function toggleProjection() {
|
||||||
let scene = props.viewer?.scene;
|
let scene = props.viewer?.scene;
|
||||||
if (!scene) return;
|
if (!scene) return;
|
||||||
let prevCam = scene.camera;
|
let prevCam = scene.camera;
|
||||||
@@ -79,16 +78,16 @@ function toggleProjection() {
|
|||||||
scene.queueRender() // Force rerender of model-viewer
|
scene.queueRender() // Force rerender of model-viewer
|
||||||
}
|
}
|
||||||
toggleProjectionText.value = wasPerspectiveCamera ? 'ORTHO' : 'PERSP';
|
toggleProjectionText.value = wasPerspectiveCamera ? 'ORTHO' : 'PERSP';
|
||||||
// The camera change may take a few frames to take effect, dispatch the event after a delay
|
// The camera change may take a frame to take effect, dispatch the event after a delay
|
||||||
requestIdleCallback(() => props.viewer?.elem?.dispatchEvent(
|
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||||
new CustomEvent('camera-change', {detail: {source: 'none'}})), {timeout: 100})
|
props.viewer?.elem?.dispatchEvent(new CustomEvent('camera-change', {detail: {source: 'none'}}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function centerCamera() {
|
async function centerCamera() {
|
||||||
let viewerEl: ModelViewerElement | null | undefined = props.viewer?.elem;
|
let viewerEl: ModelViewerElement | null | undefined = props.viewer?.elem;
|
||||||
if (!viewerEl) return;
|
if (!viewerEl) return;
|
||||||
await viewerEl.updateFraming();
|
props.viewer?.scene?.setTarget(0, 0, 0); // Center the target
|
||||||
viewerEl.zoom(3);
|
viewerEl.zoom(-1000000); // Max zoom out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -127,35 +126,35 @@ window.addEventListener('keydown', (event) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<orientation-gizmo :scene="props.viewer.scene as any" :elem="props.viewer.elem" v-if="props.viewer?.scene"/>
|
<orientation-gizmo v-if="props.viewer?.scene" :viewer="props.viewer"/>
|
||||||
<v-divider/>
|
<v-divider/>
|
||||||
<h5>Camera</h5>
|
<h5>Camera</h5>
|
||||||
<v-btn icon @click="toggleProjection"><span class="icon-detail">{{ toggleProjectionText }}</span>
|
<v-btn icon @click="toggleProjection"><span class="icon-detail">{{ toggleProjectionText }}</span>
|
||||||
<v-tooltip activator="parent">Toggle (P)rojection<br/>(currently
|
<v-tooltip activator="parent">Toggle (P)rojection<br/>(currently
|
||||||
{{ toggleProjectionText === 'PERSP' ? 'perspective' : 'orthographic' }})
|
{{ toggleProjectionText === 'PERSP' ? 'perspective' : 'orthographic' }})
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiProjector"></svg-icon>
|
<svg-icon :path="mdiProjector" type="mdi"></svg-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn icon @click="centerCamera">
|
<v-btn icon @click="centerCamera">
|
||||||
<v-tooltip activator="parent">Re(c)enter Camera</v-tooltip>
|
<v-tooltip activator="parent">Re(c)enter Camera</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiCrosshairsGps"/>
|
<svg-icon :path="mdiCrosshairsGps" type="mdi"/>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-divider/>
|
<v-divider/>
|
||||||
<h5>Selection ({{ selectionFaceCount() }}F {{ selectionEdgeCount() }}E {{ selectionVertexCount() }}V)</h5>
|
<h5>Selection ({{ selectionFaceCount() }}F {{ selectionEdgeCount() }}E {{ selectionVertexCount() }}V)</h5>
|
||||||
<selection-component ref="selectionComp" :viewer="props.viewer as any" v-model="selection"
|
<selection-component ref="selectionComp" v-model="selection" :viewer="props.viewer as any"
|
||||||
@findModel="(name) => emit('findModel', name)"/>
|
@findModel="(name: string) => emit('findModel', name)"/>
|
||||||
<v-divider/>
|
<v-divider/>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<h5>Extras</h5>
|
<h5>Extras</h5>
|
||||||
<v-btn icon @click="downloadSceneGlb">
|
<v-btn icon @click="downloadSceneGlb">
|
||||||
<v-tooltip activator="parent">(D)ownload Scene</v-tooltip>
|
<v-tooltip activator="parent">(D)ownload Scene</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiDownload"/>
|
<svg-icon :path="mdiDownload" type="mdi"/>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-dialog id="licenses-dialog" fullscreen>
|
<v-dialog id="licenses-dialog" fullscreen>
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn icon v-bind="props">
|
<v-btn icon v-bind="props">
|
||||||
<v-tooltip activator="parent">Show Licenses</v-tooltip>
|
<v-tooltip activator="parent">Show Licenses</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiLicense"/>
|
<svg-icon :path="mdiLicense" type="mdi"/>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:default="{ isActive }">
|
<template v-slot:default="{ isActive }">
|
||||||
@@ -165,7 +164,7 @@ window.addEventListener('keydown', (event) => {
|
|||||||
<v-spacer>
|
<v-spacer>
|
||||||
</v-spacer>
|
</v-spacer>
|
||||||
<v-btn icon @click="isActive.value = false">
|
<v-btn icon @click="isActive.value = false">
|
||||||
<svg-icon type="mdi" :path="mdiClose"/>
|
<svg-icon :path="mdiClose" type="mdi"/>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
@@ -176,7 +175,7 @@ window.addEventListener('keydown', (event) => {
|
|||||||
</v-dialog>
|
</v-dialog>
|
||||||
<v-btn icon @click="openGithub">
|
<v-btn icon @click="openGithub">
|
||||||
<v-tooltip activator="parent">Open (G)itHub</v-tooltip>
|
<v-tooltip activator="parent">Open (G)itHub</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiGithub"/>
|
<svg-icon :path="mdiGithub" type="mdi"/>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<div ref="statsHolder"></div>
|
<div ref="statsHolder"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script lang="ts" setup>
|
||||||
import {settings} from "../misc/settings";
|
import {settings} from "../misc/settings";
|
||||||
import {inject, onMounted, type Ref, ref, watch} from "vue";
|
import {inject, onMounted, type Ref, ref, watch} from "vue";
|
||||||
import {VList, VListItem} from "vuetify/lib/components/index.mjs";
|
|
||||||
import {$renderer, $scene} from "@google/model-viewer/lib/model-viewer-base";
|
import {$renderer, $scene} from "@google/model-viewer/lib/model-viewer-base";
|
||||||
|
import {$controls} from '@google/model-viewer/lib/features/controls.js';
|
||||||
|
import {type SmoothControls} from '@google/model-viewer/lib/three-components/SmoothControls';
|
||||||
import {ModelViewerElement} from '@google/model-viewer';
|
import {ModelViewerElement} from '@google/model-viewer';
|
||||||
import type {ModelScene} from "@google/model-viewer/lib/three-components/ModelScene";
|
import type {ModelScene} from "@google/model-viewer/lib/three-components/ModelScene";
|
||||||
import {Hotspot} from "@google/model-viewer/lib/three-components/Hotspot";
|
import {Hotspot} from "@google/model-viewer/lib/three-components/Hotspot";
|
||||||
@@ -19,31 +20,63 @@ BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
|
|||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
Mesh.prototype.raycast = acceleratedRaycast;
|
Mesh.prototype.raycast = acceleratedRaycast;
|
||||||
|
|
||||||
const emit = defineEmits<{ load: [] }>()
|
|
||||||
|
|
||||||
const props = defineProps<{ src: string }>();
|
const props = defineProps<{ src: string }>();
|
||||||
|
|
||||||
const elem = ref<ModelViewerElement | null>(null);
|
const elem = ref<ModelViewerElement | null>(null);
|
||||||
const scene = ref<ModelScene | null>(null);
|
const scene = ref<ModelScene | null>(null);
|
||||||
const renderer = ref<Renderer | null>(null);
|
const renderer = ref<Renderer | null>(null);
|
||||||
|
const controls = ref<SmoothControls | null>(null);
|
||||||
|
|
||||||
|
|
||||||
|
let lastCameraTargetPosition: Vector3 | undefined = undefined;
|
||||||
|
let lastCameraZoom: number | undefined = undefined;
|
||||||
|
let lastCameraUrl = props.src.toString();
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!elem.value) return;
|
if (!elem.value) return;
|
||||||
elem.value.addEventListener('load', async () => {
|
elem.value.addEventListener('before-render', () => {
|
||||||
if (!elem.value) return;
|
if (!elem.value) return;
|
||||||
// Delete the initial load banner
|
// Extract internals of model-viewer in order to hack unsupported features
|
||||||
let banner = elem.value.querySelector('.initial-load-banner');
|
|
||||||
if (banner) banner.remove();
|
|
||||||
// Set the scene and renderer
|
|
||||||
scene.value = elem.value[$scene] as ModelScene;
|
scene.value = elem.value[$scene] as ModelScene;
|
||||||
renderer.value = elem.value[$renderer] as Renderer;
|
renderer.value = elem.value[$renderer] as Renderer;
|
||||||
// Emit the load event
|
controls.value = (elem.value as any)[$controls] as SmoothControls;
|
||||||
emit('load')
|
// Recover the camera position if it was set before
|
||||||
|
if (lastCameraTargetPosition) {
|
||||||
|
// console.log("RESTORING camera position?", lastCameraTargetPosition);
|
||||||
|
scene.value.setTarget(-lastCameraTargetPosition.x, -lastCameraTargetPosition.y, -lastCameraTargetPosition.z);
|
||||||
|
scene.value.jumpToGoal(); // Avoid move animation
|
||||||
|
}
|
||||||
|
(async () => {
|
||||||
|
let tries = 0
|
||||||
|
while (tries++ < 25) {
|
||||||
|
if (!lastCameraZoom || !elem.value?.getCameraOrbit()?.radius) break;
|
||||||
|
let change = lastCameraZoom - elem.value.getCameraOrbit().radius;
|
||||||
|
//console.log("Zooming to", lastCameraZoom, "from", elem.value.getCameraOrbit().radius, "change", change);
|
||||||
|
if (Math.abs(change) < 0.001) break;
|
||||||
|
elem.value.zoom(-Math.sign(change) * (Math.pow(Math.abs(change) + 1, 0.9) - 1)); // Arbitrary, experimental
|
||||||
|
elem.value.jumpCameraToGoal();
|
||||||
|
await elem.value.updateComplete;
|
||||||
|
}
|
||||||
|
//console.log("Ready to save!")
|
||||||
|
lastCameraUrl = props.src.toString();
|
||||||
|
})();
|
||||||
});
|
});
|
||||||
elem.value.addEventListener('camera-change', onCameraChange);
|
elem.value.addEventListener('camera-change', onCameraChange);
|
||||||
elem.value.addEventListener('progress', (ev) => onProgress((ev as any).detail.totalProgress));
|
elem.value.addEventListener('progress', (ev) => onProgress((ev as any).detail.totalProgress));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function onCameraChange() {
|
||||||
|
// Remember the camera position to keep it in case of scene changes
|
||||||
|
if (scene.value && props.src.toString() == lastCameraUrl) { // Don't overwrite with initial unwanted positions
|
||||||
|
lastCameraTargetPosition = scene.value.target.position.clone();
|
||||||
|
lastCameraZoom = elem.value?.getCameraOrbit()?.radius;
|
||||||
|
//console.log("Saving camera?", lastCameraTargetPosition, lastCameraZoom);
|
||||||
|
}
|
||||||
|
// Also need to update the SVG overlay
|
||||||
|
for (let lineId in lines.value) {
|
||||||
|
onCameraChangeLine(lineId as any);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handles loading the events for <model-viewer>'s slotted progress bar
|
// Handles loading the events for <model-viewer>'s slotted progress bar
|
||||||
const progressBar = ref<HTMLElement | null>(null);
|
const progressBar = ref<HTMLElement | null>(null);
|
||||||
const updateBar = ref<HTMLElement | null>(null);
|
const updateBar = ref<HTMLElement | null>(null);
|
||||||
@@ -66,6 +99,17 @@ const onProgress = (totalProgress: number) => {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const poster = ref<string>("")
|
||||||
|
const setPosterText = (newText: string) => {
|
||||||
|
poster.value = "data:image/svg+xml;charset=utf-8;base64," + btoa(
|
||||||
|
'<svg width="800" height="600" xmlns="http://www.w3.org/2000/svg" fill="gray">' +
|
||||||
|
'<text x="50%" y="0%" dominant-baseline="middle" text-anchor="middle" font-size="48px">' +
|
||||||
|
newText +
|
||||||
|
'</text>' +
|
||||||
|
'</svg>')
|
||||||
|
}
|
||||||
|
setPosterText("Loading...")
|
||||||
|
|
||||||
class Line3DData {
|
class Line3DData {
|
||||||
startHotspot: HTMLElement = document.body
|
startHotspot: HTMLElement = document.body
|
||||||
endHotspot: HTMLElement = document.body
|
endHotspot: HTMLElement = document.body
|
||||||
@@ -118,13 +162,6 @@ function removeLine3D(id: number): boolean {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCameraChange() {
|
|
||||||
// Need to update the SVG overlay
|
|
||||||
for (let lineId in lines.value) {
|
|
||||||
onCameraChangeLine(lineId as any);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let svg = ref<SVGElement | null>(null);
|
let svg = ref<SVGElement | null>(null);
|
||||||
|
|
||||||
function onCameraChangeLine(lineId: number) {
|
function onCameraChangeLine(lineId: number) {
|
||||||
@@ -160,54 +197,47 @@ function entries(lines: { [id: number]: Line3DData }): [string, Line3DData][] {
|
|||||||
return Object.entries(lines);
|
return Object.entries(lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({elem, onElemReady, scene, renderer, addLine3D, removeLine3D, onProgress});
|
defineExpose({elem, onElemReady, scene, renderer, controls, addLine3D, removeLine3D, onProgress, setPosterText});
|
||||||
|
|
||||||
let {disableTap} = inject<{ disableTap: Ref<boolean> }>('disableTap')!!;
|
let {disableTap} = inject<{ disableTap: Ref<boolean> }>('disableTap')!!;
|
||||||
watch(disableTap, (value) => {
|
watch(disableTap, (newDisableTap) => {
|
||||||
// Rerender not auto triggered? This works anyway...
|
if (elem.value) elem.value.disableTap = newDisableTap;
|
||||||
if (value) elem.value?.setAttribute('disable-tap', '');
|
|
||||||
else elem.value?.removeAttribute('disable-tap');
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- The main 3D model viewer -->
|
<!-- The main 3D model viewer -->
|
||||||
<model-viewer ref="elem" style="width: 100%; height: 100%" :src="props.src" alt="The 3D model(s)" camera-controls
|
<model-viewer ref="elem" :ar="settings.arModes.length > 0" :ar-modes="settings.arModes" :autoplay="settings.autoplay"
|
||||||
camera-orbit="30deg 75deg auto" max-camera-orbit="Infinity 180deg auto"
|
:environment-image="settings.background" :exposure="settings.exposure"
|
||||||
min-camera-orbit="-Infinity 0deg 5%" :disable-tap="disableTap" :exposure="settings.exposure"
|
:orbit-sensitivity="settings.orbitSensitivity" :pan-sensitivity="settings.panSensitivity"
|
||||||
:shadow-intensity="settings.shadowIntensity" interaction-prompt="none" :autoplay="settings.autoplay"
|
:poster="poster" :shadow-intensity="settings.shadowIntensity" :skybox-image="settings.background"
|
||||||
:ar="settings.arModes.length > 0" :ar-modes="settings.arModes" :skybox-image="settings.background"
|
:src="props.src" :zoom-sensitivity="settings.zoomSensitivity" alt="The 3D model(s)" camera-controls
|
||||||
:environment-image="settings.background">
|
camera-orbit="30deg 75deg auto" interaction-prompt="none" max-camera-orbit="Infinity 180deg auto"
|
||||||
|
min-camera-orbit="-Infinity 0deg 5%" style="width: 100%; height: 100%">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<!-- Display some information during initial load -->
|
<!-- Add a progress bar to the top of the model viewer -->
|
||||||
<div class="annotation initial-load-banner">
|
<div ref="progressBar" slot="progress-bar" class="progress-bar">
|
||||||
Trying to load models from...
|
<div ref="updateBar" class="update-bar"/>
|
||||||
<v-list v-for="src in settings.preload" :key="src">
|
|
||||||
<v-list-item>{{ src }}</v-list-item>
|
|
||||||
</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>
|
</div>
|
||||||
</model-viewer>
|
</model-viewer>
|
||||||
|
|
||||||
<!-- The SVG overlay for fake 3D lines attached to the model -->
|
<!-- The SVG overlay for fake 3D lines attached to the model -->
|
||||||
<div class="overlay-svg-wrapper">
|
<div class="overlay-svg-wrapper">
|
||||||
<svg ref="svg" class="overlay-svg" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
<svg ref="svg" class="overlay-svg" height="100%" width="100%" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g v-for="[lineId, line] in 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]"
|
<line :x1="line.start2D[0]" :x2="line.end2D[0]" :y1="line.start2D[1]"
|
||||||
:y2="line.end2D[1]" v-bind="line.lineAttrs"/>
|
:y2="line.end2D[1]" v-bind="line.lineAttrs"/>
|
||||||
<g v-if="line.centerText !== undefined">
|
<g v-if="line.centerText !== undefined">
|
||||||
<rect :x="(line.start2D[0] + line.end2D[0]) / 2 - line.centerTextSize[0]/2 - 4"
|
<rect v-if="line.centerText"
|
||||||
:y="(line.start2D[1] + line.end2D[1]) / 2 - line.centerTextSize[1]/2 - 2"
|
:height="line.centerTextSize[1] + 4"
|
||||||
:width="line.centerTextSize[0] + 8" :height="line.centerTextSize[1] + 4"
|
:width="line.centerTextSize[0] + 8"
|
||||||
fill="gray" fill-opacity="0.75" rx="4" ry="4" stroke="black" v-if="line.centerText"/>
|
:x="(line.start2D[0] + line.end2D[0]) / 2 - line.centerTextSize[0]/2 - 4"
|
||||||
<text :x="(line.start2D[0] + line.end2D[0]) / 2" :y="(line.start2D[1] + line.end2D[1]) / 2"
|
:y="(line.start2D[1] + line.end2D[1]) / 2 - line.centerTextSize[1]/2 - 2" fill="gray"
|
||||||
text-anchor="middle" dominant-baseline="middle" font-size="16" fill="black"
|
fill-opacity="0.75" rx="4" ry="4" stroke="black"/>
|
||||||
:class="'line' + lineId + '_text'" v-if="line.centerText">
|
<text v-if="line.centerText" :class="'line' + lineId + '_text'"
|
||||||
|
:x="(line.start2D[0] + line.end2D[0]) / 2" :y="(line.start2D[1] + line.end2D[1]) / 2"
|
||||||
|
dominant-baseline="middle" fill="black"
|
||||||
|
font-size="16" text-anchor="middle">
|
||||||
{{ line.centerText }}
|
{{ line.centerText }}
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
@@ -242,17 +272,6 @@ watch(disableTap, (value) => {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.initial-load-banner {
|
|
||||||
width: 300px;
|
|
||||||
margin: auto;
|
|
||||||
margin-top: 3em;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.initial-load-banner .v-list-item {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar {
|
.progress-bar {
|
||||||
display: block;
|
display: block;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|||||||
38
package.json
38
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yet-another-cad-viewer",
|
"name": "yet-another-cad-viewer",
|
||||||
"version": "0.8.6",
|
"version": "0.9.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -15,33 +15,33 @@
|
|||||||
"update-licenses": "generate-license-file --input package.json --output assets/licenses.txt --overwrite"
|
"update-licenses": "generate-license-file --input package.json --output assets/licenses.txt --overwrite"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gltf-transform/core": "^3.10.1",
|
"@gltf-transform/core": "^4.0.4",
|
||||||
"@gltf-transform/extensions": "^3.10.1",
|
"@gltf-transform/extensions": "^4.0.10",
|
||||||
"@gltf-transform/functions": "^3.10.1",
|
"@gltf-transform/functions": "^4.0.10",
|
||||||
"@google/model-viewer": "^3.4.0",
|
"@google/model-viewer": "^3.5.0",
|
||||||
"@jamescoyle/vue-icon": "^0.1.2",
|
"@jamescoyle/vue-icon": "^0.1.2",
|
||||||
"@mdi/js": "^7.4.47",
|
"@mdi/js": "^7.4.47",
|
||||||
"@mdi/svg": "^7.4.47",
|
"@mdi/svg": "^7.4.47",
|
||||||
"three": "^0.160.1",
|
"three": "^0.163.0",
|
||||||
"three-mesh-bvh": "^0.7.3",
|
"three-mesh-bvh": "^0.8.2",
|
||||||
"three-orientation-gizmo": "https://github.com/jrj2211/three-orientation-gizmo",
|
"three-orientation-gizmo": "https://github.com/jrj2211/three-orientation-gizmo",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.5.11",
|
||||||
"vuetify": "^3.5.13"
|
"vuetify": "^3.7.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tsconfig/node20": "^20.1.4",
|
"@tsconfig/node20": "^20.1.4",
|
||||||
"@types/node": "^20.12.2",
|
"@types/node": "^22.7.4",
|
||||||
"@types/three": "^0.160.0",
|
"@types/three": "^0.163.0",
|
||||||
"@vitejs/plugin-vue": "^5.0.3",
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||||
"@vue/tsconfig": "^0.5.1",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
"buffer": "^5.5.0||^6.0.0",
|
"buffer": "^5.5.0||^6.0.0",
|
||||||
"commander": "^12.0.0",
|
"commander": "^12.0.0",
|
||||||
"generate-license-file": "^3.0.1",
|
"generate-license-file": "^3.5.1",
|
||||||
"npm-run-all2": "^6.1.1",
|
"npm-run-all2": "^6.2.3",
|
||||||
"terser": "^5.30.0",
|
"terser": "^5.34.1",
|
||||||
"typescript": "~5.4.3",
|
"typescript": "~5.6.2",
|
||||||
"vite": "^5.2.7",
|
"vite": "^5.4.8",
|
||||||
"vue-tsc": "^2.0.7"
|
"vue-tsc": "^2.1.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
198
poetry.lock
generated
198
poetry.lock
generated
@@ -1,4 +1,4 @@
|
|||||||
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anytree"
|
name = "anytree"
|
||||||
@@ -34,21 +34,21 @@ test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "build123d"
|
name = "build123d"
|
||||||
version = "0.5.0"
|
version = "0.7.0"
|
||||||
description = "A python CAD programming library"
|
description = "A python CAD programming library"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "build123d-0.5.0-py3-none-any.whl", hash = "sha256:d0a4e82cdb0e53ef21fca8d2c84124351d7c7070077b5efa173d789002c8194c"},
|
{file = "build123d-0.7.0-py3-none-any.whl", hash = "sha256:0771b6f8adf326a4e21a5224cac730045217021c2040e9884fc5dd5028bec3c8"},
|
||||||
|
{file = "build123d-0.7.0.tar.gz", hash = "sha256:530fe4807b0c6fcb682f872973d961c9ad9dbc4ebaf33e3cc1088d3117de54e7"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
anytree = ">=2.8.0,<3"
|
anytree = ">=2.8.0,<3"
|
||||||
cadquery-ocp = ">=7.7.0"
|
cadquery-ocp = ">=7.7.0"
|
||||||
ezdxf = ">=1.0.0,<2"
|
ezdxf = ">=1.1.0,<2"
|
||||||
ipython = ">=8.0.0,<9"
|
ipython = ">=8.0.0,<9"
|
||||||
numpy = ">=1.24.1,<2"
|
numpy = ">=1.24.1,<2"
|
||||||
numpy-stl = ">=3.0.0,<4"
|
|
||||||
ocpsvg = "*"
|
ocpsvg = "*"
|
||||||
py-lib3mf = ">=2.3.1"
|
py-lib3mf = ">=2.3.1"
|
||||||
svgpathtools = ">=1.5.1,<2"
|
svgpathtools = ">=1.5.1,<2"
|
||||||
@@ -426,21 +426,6 @@ files = [
|
|||||||
{file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"},
|
{file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "numpy-stl"
|
|
||||||
version = "3.1.1"
|
|
||||||
description = "Library to make reading, writing and modifying both binary and ascii STL files easy."
|
|
||||||
optional = false
|
|
||||||
python-versions = ">3.6.0"
|
|
||||||
files = [
|
|
||||||
{file = "numpy-stl-3.1.1.tar.gz", hash = "sha256:f78eea62c80938bf53ea914fa5b6c92f448f0eab5609e0e5a737dde039404334"},
|
|
||||||
{file = "numpy_stl-3.1.1-py3-none-any.whl", hash = "sha256:b0b7f4455c29d26d3dc0eed894f5b17c64e4019b056d0060be48f93680f6e6d3"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
numpy = "*"
|
|
||||||
python-utils = ">=3.4.5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ocpsvg"
|
name = "ocpsvg"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -502,83 +487,95 @@ ptyprocess = ">=0.5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pillow"
|
name = "pillow"
|
||||||
version = "10.2.0"
|
version = "10.4.0"
|
||||||
description = "Python Imaging Library (Fork)"
|
description = "Python Imaging Library (Fork)"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"},
|
{file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"},
|
||||||
{file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"},
|
{file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"},
|
||||||
{file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"},
|
{file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"},
|
||||||
{file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"},
|
{file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"},
|
||||||
{file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"},
|
{file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"},
|
||||||
{file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"},
|
{file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"},
|
||||||
{file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"},
|
{file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"},
|
||||||
{file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"},
|
{file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"},
|
||||||
{file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"},
|
{file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"},
|
||||||
{file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"},
|
{file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"},
|
||||||
{file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"},
|
{file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"},
|
||||||
{file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"},
|
{file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"},
|
||||||
{file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"},
|
{file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"},
|
||||||
{file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"},
|
{file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"},
|
||||||
{file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"},
|
{file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"},
|
||||||
{file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"},
|
{file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"},
|
||||||
{file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"},
|
{file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"},
|
||||||
{file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"},
|
{file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"},
|
||||||
{file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"},
|
{file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"},
|
||||||
{file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"},
|
{file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"},
|
||||||
{file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"},
|
{file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"},
|
||||||
{file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"},
|
{file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"},
|
||||||
{file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"},
|
{file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"},
|
||||||
{file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"},
|
{file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"},
|
||||||
{file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"},
|
{file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"},
|
||||||
{file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"},
|
{file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"},
|
||||||
{file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"},
|
{file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"},
|
||||||
{file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"},
|
{file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"},
|
||||||
{file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"},
|
{file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"},
|
||||||
{file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"},
|
{file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"},
|
||||||
{file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"},
|
{file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"},
|
||||||
{file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"},
|
{file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"},
|
||||||
{file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"},
|
{file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"},
|
||||||
{file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"},
|
{file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"},
|
||||||
{file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"},
|
{file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"},
|
||||||
{file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"},
|
{file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"},
|
||||||
{file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"},
|
{file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"},
|
||||||
{file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"},
|
{file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"},
|
||||||
{file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"},
|
{file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"},
|
||||||
{file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"},
|
{file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"},
|
||||||
{file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"},
|
{file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"},
|
||||||
{file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"},
|
{file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"},
|
||||||
{file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"},
|
{file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"},
|
||||||
{file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"},
|
{file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"},
|
||||||
{file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"},
|
{file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"},
|
||||||
{file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"},
|
{file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"},
|
||||||
{file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"},
|
{file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"},
|
||||||
{file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"},
|
{file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"},
|
||||||
{file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"},
|
{file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"},
|
||||||
{file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"},
|
{file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"},
|
||||||
{file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"},
|
{file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"},
|
||||||
{file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"},
|
{file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"},
|
||||||
{file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"},
|
{file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"},
|
||||||
{file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"},
|
{file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"},
|
||||||
{file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"},
|
{file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"},
|
||||||
{file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"},
|
{file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"},
|
||||||
{file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"},
|
{file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"},
|
||||||
{file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"},
|
{file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"},
|
||||||
{file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"},
|
{file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"},
|
||||||
{file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"},
|
{file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"},
|
||||||
{file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"},
|
{file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"},
|
||||||
{file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"},
|
{file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"},
|
||||||
{file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"},
|
{file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"},
|
||||||
{file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"},
|
{file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"},
|
||||||
{file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"},
|
{file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"},
|
||||||
{file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"},
|
{file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"},
|
||||||
{file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"},
|
{file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"},
|
||||||
{file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"},
|
{file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"},
|
||||||
|
{file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"},
|
||||||
|
{file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"},
|
||||||
|
{file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"},
|
||||||
|
{file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"},
|
||||||
|
{file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"},
|
||||||
|
{file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"},
|
||||||
|
{file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"},
|
||||||
|
{file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"},
|
||||||
|
{file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"},
|
||||||
|
{file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"},
|
||||||
|
{file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"},
|
||||||
|
{file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"]
|
docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
|
||||||
fpx = ["olefile"]
|
fpx = ["olefile"]
|
||||||
mic = ["olefile"]
|
mic = ["olefile"]
|
||||||
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
|
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
|
||||||
@@ -677,25 +674,6 @@ files = [
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
diagrams = ["jinja2", "railroad-diagrams"]
|
diagrams = ["jinja2", "railroad-diagrams"]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "python-utils"
|
|
||||||
version = "3.8.2"
|
|
||||||
description = "Python Utils is a module with some convenient utilities not included with the standard Python install"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">3.8.0"
|
|
||||||
files = [
|
|
||||||
{file = "python-utils-3.8.2.tar.gz", hash = "sha256:c5d161e4ca58ce3f8c540f035e018850b261a41e7cb98f6ccf8e1deb7174a1f1"},
|
|
||||||
{file = "python_utils-3.8.2-py2.py3-none-any.whl", hash = "sha256:ad0ccdbd6f856d015cace07f74828b9840b5c4072d9e868a7f6a14fd195555a8"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
typing-extensions = ">3.10.0.2"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
docs = ["mock", "python-utils", "sphinx"]
|
|
||||||
loguru = ["loguru"]
|
|
||||||
tests = ["flake8", "loguru", "pytest", "pytest-asyncio", "pytest-cov", "pytest-mypy", "sphinx", "types-setuptools"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scipy"
|
name = "scipy"
|
||||||
version = "1.12.0"
|
version = "1.12.0"
|
||||||
@@ -951,4 +929,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.9"
|
python-versions = "^3.9"
|
||||||
content-hash = "612c2f4fcc3ff9e37bc9e604bf092452138843ec4dc529dadc210887f0e728fd"
|
content-hash = "c7ab4daa707db1d54f8af490ab8f85a5d80052049aead04b08c867739829418d"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "yacv-server"
|
name = "yacv-server"
|
||||||
version = "0.8.6"
|
version = "0.9.0"
|
||||||
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"
|
||||||
@@ -14,7 +14,7 @@ include = [
|
|||||||
python = "^3.9"
|
python = "^3.9"
|
||||||
|
|
||||||
# CAD
|
# CAD
|
||||||
build123d = "^0.5.0"
|
build123d = ">=0.5,<0.8"
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
pygltflib = "^1.16.2"
|
pygltflib = "^1.16.2"
|
||||||
|
|||||||
@@ -8,9 +8,8 @@
|
|||||||
"frontend/**/__tests__/*"
|
"frontend/**/__tests__/*"
|
||||||
],
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"declaration": false,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
|
|||||||
@@ -10,12 +10,29 @@ from OCP.TopExp import TopExp
|
|||||||
from OCP.TopLoc import TopLoc_Location
|
from OCP.TopLoc import TopLoc_Location
|
||||||
from OCP.TopTools import TopTools_IndexedMapOfShape
|
from OCP.TopTools import TopTools_IndexedMapOfShape
|
||||||
from OCP.TopoDS import TopoDS_Shape
|
from OCP.TopoDS import TopoDS_Shape
|
||||||
from build123d import Compound, Shape
|
from build123d import Compound, Shape, Color
|
||||||
|
|
||||||
from yacv_server.gltf import GLTFMgr
|
from yacv_server.gltf import GLTFMgr
|
||||||
|
|
||||||
CADCoreLike = Union[TopoDS_Shape, TopLoc_Location] # Faces, Edges, Vertices and Locations for now
|
CADCoreLike = Union[TopoDS_Shape, TopLoc_Location] # Faces, Edges, Vertices and Locations for now
|
||||||
CADLike = Union[CADCoreLike, any] # build123d and cadquery types
|
CADLike = Union[CADCoreLike, any] # build123d and cadquery types
|
||||||
|
ColorTuple = Tuple[float, float, float, float]
|
||||||
|
|
||||||
|
|
||||||
|
def get_color(obj: CADLike) -> Optional[ColorTuple]:
|
||||||
|
"""Get color from a CAD Object"""
|
||||||
|
if 'color' in dir(obj):
|
||||||
|
if isinstance(obj.color, tuple):
|
||||||
|
c = None
|
||||||
|
if len(obj.color) == 3:
|
||||||
|
c = obj.color + (1,)
|
||||||
|
elif len(obj.color) == 4:
|
||||||
|
c = obj.color
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
return [min(max(float(x), 0), 1) for x in c]
|
||||||
|
if isinstance(obj.color, Color):
|
||||||
|
return obj.color.to_tuple()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_shape(obj: CADLike, error: bool = True) -> Optional[CADCoreLike]:
|
def get_shape(obj: CADLike, error: bool = True) -> Optional[CADCoreLike]:
|
||||||
@@ -164,7 +181,7 @@ def image_to_gltf(source: str | bytes, center: any, width: Optional[float] = Non
|
|||||||
return b''.join(mgr.build().save_to_bytes()), name
|
return b''.join(mgr.build().save_to_bytes()), name
|
||||||
|
|
||||||
|
|
||||||
def _hashcode(obj: Union[bytes, CADCoreLike], **extras) -> str:
|
def _hashcode(obj: Union[bytes, CADCoreLike], color: Optional[ColorTuple], **extras) -> str:
|
||||||
"""Utility to compute the STABLE hash code of a shape"""
|
"""Utility to compute the STABLE hash code of a shape"""
|
||||||
# NOTE: obj.HashCode(MAX_HASH_CODE) is not stable across different runs of the same program
|
# NOTE: obj.HashCode(MAX_HASH_CODE) is not stable across different runs of the same program
|
||||||
# This is best-effort and not guaranteed to be unique
|
# This is best-effort and not guaranteed to be unique
|
||||||
@@ -172,6 +189,8 @@ def _hashcode(obj: Union[bytes, CADCoreLike], **extras) -> str:
|
|||||||
for k, v in extras.items():
|
for k, v in extras.items():
|
||||||
hasher.update(str(k).encode())
|
hasher.update(str(k).encode())
|
||||||
hasher.update(str(v).encode())
|
hasher.update(str(v).encode())
|
||||||
|
if color:
|
||||||
|
hasher.update(str(color).encode())
|
||||||
if isinstance(obj, bytes):
|
if isinstance(obj, bytes):
|
||||||
hasher.update(obj)
|
hasher.update(obj)
|
||||||
elif isinstance(obj, TopLoc_Location):
|
elif isinstance(obj, TopLoc_Location):
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ _checkerboard_image_bytes = base64.decodebytes(
|
|||||||
b'iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAF0lEQVQI12N49OjR////Gf'
|
b'iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAF0lEQVQI12N49OjR////Gf'
|
||||||
b'/////48WMATwULS8tcyj8AAAAASUVORK5CYII=')
|
b'/////48WMATwULS8tcyj8AAAAASUVORK5CYII=')
|
||||||
|
|
||||||
|
def get_version() -> str:
|
||||||
|
try:
|
||||||
|
return importlib.metadata.version("yacv_server")
|
||||||
|
except importlib.metadata.PackageNotFoundError:
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
|
||||||
class GLTFMgr:
|
class GLTFMgr:
|
||||||
"""A utility class to build our GLTF2 objects easily and incrementally"""
|
"""A utility class to build our GLTF2 objects easily and incrementally"""
|
||||||
@@ -32,7 +38,7 @@ class GLTFMgr:
|
|||||||
|
|
||||||
def __init__(self, image: Optional[Tuple[bytes, str]] = (_checkerboard_image_bytes, 'image/png')):
|
def __init__(self, image: Optional[Tuple[bytes, str]] = (_checkerboard_image_bytes, 'image/png')):
|
||||||
self.gltf = GLTF2(
|
self.gltf = GLTF2(
|
||||||
asset=Asset(generator=f"yacv_server@{importlib.metadata.version('yacv_server')}"),
|
asset=Asset(generator=f"yacv_server@{get_version()}"),
|
||||||
scene=0,
|
scene=0,
|
||||||
scenes=[Scene(nodes=[0])],
|
scenes=[Scene(nodes=[0])],
|
||||||
nodes=[Node(mesh=0)], # TODO: Server-side detection of shallow copies --> nodes
|
nodes=[Node(mesh=0)], # TODO: Server-side detection of shallow copies --> nodes
|
||||||
@@ -71,9 +77,9 @@ class GLTFMgr:
|
|||||||
return [p for p in self.gltf.meshes[0].primitives if p.mode == POINTS][0]
|
return [p for p in self.gltf.meshes[0].primitives if p.mode == POINTS][0]
|
||||||
|
|
||||||
def add_face(self, vertices_raw: List[Vector], indices_raw: List[Tuple[int, int, int]],
|
def add_face(self, vertices_raw: List[Vector], indices_raw: List[Tuple[int, int, int]],
|
||||||
tex_coord_raw: List[Tuple[float, float]],
|
tex_coord_raw: List[Tuple[float, float]], color: Optional[Tuple[float, float, float, float]] = None):
|
||||||
color: Tuple[float, float, float, float] = (1.0, 0.75, 0.0, 1.0)):
|
|
||||||
"""Add a face to the GLTF mesh"""
|
"""Add a face to the GLTF mesh"""
|
||||||
|
if color is None: color = (1.0, 0.75, 0.0, 1.0)
|
||||||
# assert len(vertices_raw) == len(tex_coord_raw), f"Vertices and texture coordinates have different lengths"
|
# assert len(vertices_raw) == len(tex_coord_raw), f"Vertices and texture coordinates have different lengths"
|
||||||
# assert min([i for t in indices_raw for i in t]) == 0, f"Face indices start at {min(indices_raw)}"
|
# assert min([i for t in indices_raw for i in t]) == 0, f"Face indices start at {min(indices_raw)}"
|
||||||
# assert max([e for t in indices_raw for e in t]) < len(vertices_raw), f"Indices have non-existing vertices"
|
# assert max([e for t in indices_raw for e in t]) < len(vertices_raw), f"Indices have non-existing vertices"
|
||||||
@@ -85,8 +91,9 @@ class GLTFMgr:
|
|||||||
self._faces_primitive.extras["face_triangles_end"].append(len(self.face_indices))
|
self._faces_primitive.extras["face_triangles_end"].append(len(self.face_indices))
|
||||||
|
|
||||||
def add_edge(self, vertices_raw: List[Tuple[Tuple[float, float, float], Tuple[float, float, float]]],
|
def add_edge(self, vertices_raw: List[Tuple[Tuple[float, float, float], Tuple[float, float, float]]],
|
||||||
color: Tuple[float, float, float, float] = (0.1, 0.1, 1.0, 1.0)):
|
color: Optional[Tuple[float, float, float, float]] = None):
|
||||||
"""Add an edge to the GLTF mesh"""
|
"""Add an edge to the GLTF mesh"""
|
||||||
|
if color is None: color = (0.1, 0.1, 1.0, 1.0)
|
||||||
vertices_flat = [v for t in vertices_raw for v in t] # Line from 0 to 1, 2 to 3, 4 to 5, etc.
|
vertices_flat = [v for t in vertices_raw for v in t] # Line from 0 to 1, 2 to 3, 4 to 5, etc.
|
||||||
base_index = len(self.edge_positions) // 3
|
base_index = len(self.edge_positions) // 3
|
||||||
self.edge_indices.extend([base_index + i for i in range(len(vertices_flat))])
|
self.edge_indices.extend([base_index + i for i in range(len(vertices_flat))])
|
||||||
@@ -94,9 +101,9 @@ class GLTFMgr:
|
|||||||
self.edge_colors.extend([col for _ in range(len(vertices_flat)) for col in color])
|
self.edge_colors.extend([col for _ in range(len(vertices_flat)) for col in color])
|
||||||
self._edges_primitive.extras["edge_points_end"].append(len(self.edge_indices))
|
self._edges_primitive.extras["edge_points_end"].append(len(self.edge_indices))
|
||||||
|
|
||||||
def add_vertex(self, vertex: Tuple[float, float, float],
|
def add_vertex(self, vertex: Tuple[float, float, float], color: Optional[Tuple[float, float, float, float]] = None):
|
||||||
color: Tuple[float, float, float, float] = (0.1, 0.1, 0.1, 1.0)):
|
|
||||||
"""Add a vertex to the GLTF mesh"""
|
"""Add a vertex to the GLTF mesh"""
|
||||||
|
if color is None: color = (0.1, 0.1, 0.1, 1.0)
|
||||||
base_index = len(self.vertex_positions) // 3
|
base_index = len(self.vertex_positions) // 3
|
||||||
self.vertex_indices.append(base_index)
|
self.vertex_indices.append(base_index)
|
||||||
self.vertex_positions.extend(vertex)
|
self.vertex_positions.extend(vertex)
|
||||||
|
|||||||
@@ -16,18 +16,25 @@ def build_logo(text: bool = True) -> Dict[str, Union[Part, Location, str]]:
|
|||||||
text_at_plane = Plane.YZ
|
text_at_plane = Plane.YZ
|
||||||
text_at_plane.origin = faces().group_by(Axis.X)[-1].face().center()
|
text_at_plane.origin = faces().group_by(Axis.X)[-1].face().center()
|
||||||
with BuildSketch(text_at_plane.location):
|
with BuildSketch(text_at_plane.location):
|
||||||
Text('Yet Another\nCAD Viewer', 7, font_path='/usr/share/fonts/TTF/OpenSans-Regular.ttf')
|
Text('Yet Another\nCAD Viewer', 6, font_path='/usr/share/fonts/TTF/Hack-Regular.ttf')
|
||||||
extrude(amount=1)
|
extrude(amount=1)
|
||||||
|
logo_obj.color = (0.7, 0.4, 0.1, 1) # Custom color for faces
|
||||||
|
|
||||||
|
# Highlight text edges with a custom color
|
||||||
|
to_highlight = logo_obj.edges().group_by(Axis.X)[-1]
|
||||||
|
logo_obj_hl = Compound(to_highlight).translate((1e-3, 0, 0)) # To avoid z-fighting
|
||||||
|
logo_obj_hl.color = (0, 0.3, 0.3, 1)
|
||||||
|
|
||||||
|
# Add a logo image to the CAD part
|
||||||
logo_img_location = logo_obj.faces().group_by(Axis.X)[0].face().center_location
|
logo_img_location = logo_obj.faces().group_by(Axis.X)[0].face().center_location
|
||||||
logo_img_location *= Location((0, 0, 4e-2), (0, 0, 90)) # Avoid overlapping and adjust placement
|
logo_img_location *= Location((0, 0, 4e-2), (0, 0, 90)) # Avoid overlapping and adjust placement
|
||||||
|
|
||||||
logo_img_path = os.path.join(ASSETS_DIR, 'img.jpg')
|
logo_img_path = os.path.join(ASSETS_DIR, 'img.jpg')
|
||||||
img_glb_bytes, img_name = image_to_gltf(logo_img_path, logo_img_location, height=18)
|
img_glb_bytes, img_name = image_to_gltf(logo_img_path, logo_img_location, height=18)
|
||||||
|
|
||||||
|
# Add an animated fox to the CAD part
|
||||||
fox_glb_bytes = open(os.path.join(ASSETS_DIR, 'fox.glb'), 'rb').read()
|
fox_glb_bytes = open(os.path.join(ASSETS_DIR, 'fox.glb'), 'rb').read()
|
||||||
|
|
||||||
return {'fox': fox_glb_bytes, 'logo': logo_obj, 'location': logo_img_location, img_name: img_glb_bytes}
|
return {'fox': fox_glb_bytes, 'logo': logo_obj, 'logo_hl': logo_obj_hl, 'location': logo_img_location, img_name: img_glb_bytes}
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
0
yacv_server/py.typed
Normal file
0
yacv_server/py.typed
Normal file
@@ -1,4 +1,4 @@
|
|||||||
from typing import List, Dict, Tuple
|
from typing import List, Dict, Tuple, Optional
|
||||||
|
|
||||||
from OCP.BRep import BRep_Tool
|
from OCP.BRep import BRep_Tool
|
||||||
from OCP.BRepAdaptor import BRepAdaptor_Curve
|
from OCP.BRepAdaptor import BRepAdaptor_Curve
|
||||||
@@ -8,7 +8,7 @@ from OCP.TopoDS import TopoDS_Face, TopoDS_Edge, TopoDS_Shape, TopoDS_Vertex
|
|||||||
from build123d import Shape, Vertex, Face, Location
|
from build123d import Shape, Vertex, Face, Location
|
||||||
from pygltflib import GLTF2
|
from pygltflib import GLTF2
|
||||||
|
|
||||||
from yacv_server.cad import CADCoreLike
|
from yacv_server.cad import CADCoreLike, ColorTuple
|
||||||
from yacv_server.gltf import GLTFMgr
|
from yacv_server.gltf import GLTFMgr
|
||||||
from yacv_server.mylogger import logger
|
from yacv_server.mylogger import logger
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@ def tessellate(
|
|||||||
faces: bool = True,
|
faces: bool = True,
|
||||||
edges: bool = True,
|
edges: bool = True,
|
||||||
vertices: bool = True,
|
vertices: bool = True,
|
||||||
|
obj_color: Optional[ColorTuple] = None,
|
||||||
) -> 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()
|
||||||
@@ -34,21 +35,25 @@ def tessellate(
|
|||||||
edge_to_faces: Dict[str, List[TopoDS_Face]] = {}
|
edge_to_faces: Dict[str, List[TopoDS_Face]] = {}
|
||||||
vertex_to_faces: Dict[str, List[TopoDS_Face]] = {}
|
vertex_to_faces: Dict[str, List[TopoDS_Face]] = {}
|
||||||
if faces:
|
if faces:
|
||||||
for face in shape.faces():
|
shape_faces = shape.faces()
|
||||||
_tessellate_face(mgr, face.wrapped, tolerance, angular_tolerance)
|
for face in shape_faces:
|
||||||
|
_tessellate_face(mgr, face.wrapped, tolerance, angular_tolerance, obj_color)
|
||||||
if edges:
|
if edges:
|
||||||
for edge in face.edges():
|
for edge in face.edges():
|
||||||
edge_to_faces[edge.wrapped] = edge_to_faces.get(edge.wrapped, []) + [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 len(shape_faces) > 0: obj_color = None # Don't color edges/vertices if faces are colored
|
||||||
if edges:
|
if edges:
|
||||||
for edge in shape.edges():
|
shape_edges = shape.edges()
|
||||||
|
for edge in shape_edges:
|
||||||
_tessellate_edge(mgr, edge.wrapped, edge_to_faces.get(edge.wrapped, []), angular_tolerance,
|
_tessellate_edge(mgr, edge.wrapped, edge_to_faces.get(edge.wrapped, []), angular_tolerance,
|
||||||
angular_tolerance)
|
angular_tolerance, obj_color)
|
||||||
|
if len(shape_edges) > 0: obj_color = None # Don't color vertices if edges are colored
|
||||||
if vertices:
|
if vertices:
|
||||||
for vertex in shape.vertices():
|
for vertex in shape.vertices():
|
||||||
_tessellate_vertex(mgr, vertex.wrapped, vertex_to_faces.get(vertex.wrapped, []))
|
_tessellate_vertex(mgr, vertex.wrapped, vertex_to_faces.get(vertex.wrapped, []), obj_color)
|
||||||
|
|
||||||
return mgr.build()
|
return mgr.build()
|
||||||
|
|
||||||
@@ -57,7 +62,8 @@ def _tessellate_face(
|
|||||||
mgr: GLTFMgr,
|
mgr: GLTFMgr,
|
||||||
ocp_face: TopoDS_Face,
|
ocp_face: TopoDS_Face,
|
||||||
tolerance: float = 1e-3,
|
tolerance: float = 1e-3,
|
||||||
angular_tolerance: float = 0.1
|
angular_tolerance: float = 0.1,
|
||||||
|
color: Optional[ColorTuple] = None,
|
||||||
):
|
):
|
||||||
face = Shape(ocp_face)
|
face = Shape(ocp_face)
|
||||||
# face.mesh(tolerance, angular_tolerance)
|
# face.mesh(tolerance, angular_tolerance)
|
||||||
@@ -75,7 +81,7 @@ def _tessellate_face(
|
|||||||
|
|
||||||
vertices = tri_mesh[0]
|
vertices = tri_mesh[0]
|
||||||
indices = tri_mesh[1]
|
indices = tri_mesh[1]
|
||||||
mgr.add_face(vertices, indices, uv)
|
mgr.add_face(vertices, indices, uv, color)
|
||||||
|
|
||||||
|
|
||||||
def _push_point(v: Tuple[float, float, float], faces: List[TopoDS_Face]) -> Tuple[float, float, float]:
|
def _push_point(v: Tuple[float, float, float], faces: List[TopoDS_Face]) -> Tuple[float, float, float]:
|
||||||
@@ -100,6 +106,7 @@ def _tessellate_edge(
|
|||||||
faces: List[TopoDS_Face],
|
faces: List[TopoDS_Face],
|
||||||
angular_deflection: float = 0.1,
|
angular_deflection: float = 0.1,
|
||||||
curvature_deflection: float = 0.1,
|
curvature_deflection: float = 0.1,
|
||||||
|
color: Optional[ColorTuple] = None,
|
||||||
):
|
):
|
||||||
# Use a curve discretizer to get the vertices
|
# Use a curve discretizer to get the vertices
|
||||||
curve = BRepAdaptor_Curve(ocp_edge)
|
curve = BRepAdaptor_Curve(ocp_edge)
|
||||||
@@ -117,11 +124,12 @@ def _tessellate_edge(
|
|||||||
|
|
||||||
# Convert strip of vertices to a list of pairs of vertices
|
# Convert strip of vertices to a list of pairs of vertices
|
||||||
vertices = [(vertices[i], vertices[i + 1]) for i in range(len(vertices) - 1)]
|
vertices = [(vertices[i], vertices[i + 1]) for i in range(len(vertices) - 1)]
|
||||||
mgr.add_edge(vertices)
|
mgr.add_edge(vertices, color)
|
||||||
|
|
||||||
|
|
||||||
def _tessellate_vertex(mgr: GLTFMgr, ocp_vertex: TopoDS_Vertex, faces: List[TopoDS_Face]):
|
def _tessellate_vertex(mgr: GLTFMgr, ocp_vertex: TopoDS_Vertex, faces: List[TopoDS_Face],
|
||||||
|
color: Optional[ColorTuple] = None):
|
||||||
c = Vertex(ocp_vertex).center()
|
c = Vertex(ocp_vertex).center()
|
||||||
mgr.add_vertex(_push_point((c.X, c.Y, c.Z), faces))
|
mgr.add_vertex(_push_point((c.X, c.Y, c.Z), faces), color)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from http.server import ThreadingHTTPServer
|
from http.server import ThreadingHTTPServer
|
||||||
from importlib.metadata import version
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import Optional, Dict, Union, Callable, List, Tuple
|
from typing import Optional, Dict, Union, Callable, List, Tuple
|
||||||
|
|
||||||
@@ -18,13 +17,14 @@ from OCP.TopoDS import TopoDS_Shape
|
|||||||
from build123d import Shape, Axis, Location, Vector
|
from build123d import Shape, Axis, Location, Vector
|
||||||
from dataclasses_json import dataclass_json
|
from dataclasses_json import dataclass_json
|
||||||
|
|
||||||
|
from yacv_server.cad import _hashcode, ColorTuple, get_color
|
||||||
from yacv_server.cad import get_shape, grab_all_cad, CADCoreLike, CADLike
|
from yacv_server.cad import get_shape, grab_all_cad, CADCoreLike, CADLike
|
||||||
|
from yacv_server.gltf import get_version
|
||||||
from yacv_server.myhttp import HTTPHandler
|
from yacv_server.myhttp import HTTPHandler
|
||||||
from yacv_server.mylogger import logger
|
from yacv_server.mylogger import logger
|
||||||
from yacv_server.pubsub import BufferedPubSub
|
from yacv_server.pubsub import BufferedPubSub
|
||||||
from yacv_server.rwlock import RWLock
|
from yacv_server.rwlock import RWLock
|
||||||
from yacv_server.tessellate import tessellate
|
from yacv_server.tessellate import tessellate
|
||||||
from yacv_server.cad import _hashcode
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass_json
|
@dataclass_json
|
||||||
@@ -44,16 +44,20 @@ YACVSupported = Union[bytes, CADCoreLike]
|
|||||||
|
|
||||||
class UpdatesApiFullData(UpdatesApiData):
|
class UpdatesApiFullData(UpdatesApiData):
|
||||||
obj: YACVSupported
|
obj: YACVSupported
|
||||||
"""The OCCT object, if any (not serialized)"""
|
"""The OCCT object (not serialized)"""
|
||||||
|
color: Optional[ColorTuple]
|
||||||
|
"""The color of the object, if any (not serialized)"""
|
||||||
kwargs: Optional[Dict[str, any]]
|
kwargs: Optional[Dict[str, any]]
|
||||||
"""The show_object options, if any (not serialized)"""
|
"""The show_object options, if any (not serialized)"""
|
||||||
|
|
||||||
def __init__(self, obj: YACVSupported, name: str, _hash: str, is_remove: Optional[bool] = False,
|
def __init__(self, obj: YACVSupported, name: str, _hash: str, is_remove: Optional[bool] = False,
|
||||||
|
color: Optional[ColorTuple] = None,
|
||||||
kwargs: Optional[Dict[str, any]] = None):
|
kwargs: Optional[Dict[str, any]] = None):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.hash = _hash
|
self.hash = _hash
|
||||||
self.is_remove = is_remove
|
self.is_remove = is_remove
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
|
self.color = color
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
|
|
||||||
def to_json(self) -> str:
|
def to_json(self) -> str:
|
||||||
@@ -98,7 +102,7 @@ class YACV:
|
|||||||
self.at_least_one_client = threading.Event()
|
self.at_least_one_client = threading.Event()
|
||||||
self.shutting_down = threading.Event()
|
self.shutting_down = threading.Event()
|
||||||
self.frontend_lock = RWLock()
|
self.frontend_lock = RWLock()
|
||||||
logger.info('Using yacv-server v%s', version('yacv-server'))
|
logger.info('Using yacv-server v%s', get_version())
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Starts the web server in the background"""
|
"""Starts the web server in the background"""
|
||||||
@@ -185,10 +189,11 @@ class YACV:
|
|||||||
|
|
||||||
# Publish the show event
|
# Publish the show event
|
||||||
for obj, name in zip(objs, names):
|
for obj, name in zip(objs, names):
|
||||||
|
color = get_color(obj)
|
||||||
if not isinstance(obj, bytes):
|
if not isinstance(obj, bytes):
|
||||||
obj = _preprocess_cad(obj, **kwargs)
|
obj = _preprocess_cad(obj, **kwargs)
|
||||||
_hash = _hashcode(obj, **kwargs)
|
_hash = _hashcode(obj, color, **kwargs)
|
||||||
event = UpdatesApiFullData(name=name, _hash=_hash, obj=obj, kwargs=kwargs or {})
|
event = UpdatesApiFullData(name=name, _hash=_hash, obj=obj, color=color, kwargs=kwargs or {})
|
||||||
self.show_events.publish(event)
|
self.show_events.publish(event)
|
||||||
|
|
||||||
logger.info('show %s took %.3f seconds', names, time.time() - start)
|
logger.info('show %s took %.3f seconds', names, time.time() - start)
|
||||||
@@ -277,7 +282,8 @@ class YACV:
|
|||||||
angular_tolerance=event.kwargs.get('angular_tolerance', 0.1),
|
angular_tolerance=event.kwargs.get('angular_tolerance', 0.1),
|
||||||
faces=event.kwargs.get('faces', True),
|
faces=event.kwargs.get('faces', True),
|
||||||
edges=event.kwargs.get('edges', True),
|
edges=event.kwargs.get('edges', True),
|
||||||
vertices=event.kwargs.get('vertices', True))
|
vertices=event.kwargs.get('vertices', True),
|
||||||
|
obj_color=event.color)
|
||||||
glb_list_of_bytes = gltf.save_to_bytes()
|
glb_list_of_bytes = gltf.save_to_bytes()
|
||||||
glb_bytes = b''.join(glb_list_of_bytes)
|
glb_bytes = b''.join(glb_list_of_bytes)
|
||||||
publish_to.publish(glb_bytes)
|
publish_to.publish(glb_bytes)
|
||||||
|
|||||||
Reference in New Issue
Block a user