mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-20 14:37:03 +01:00
Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
437e8eb4ad | ||
|
|
7051a71710 | ||
|
|
28282f4b06 | ||
|
|
7f43367459 | ||
|
|
ba64f70300 | ||
|
|
05963d58f2 | ||
|
|
405c061f93 | ||
|
|
e0a00b2d32 | ||
|
|
90f4611ee8 | ||
|
|
ee954622bb | ||
|
|
f5be0618ad | ||
|
|
f92701a969 | ||
|
|
949f92a28f | ||
|
|
7618581ef5 | ||
|
|
5460d19fdb | ||
|
|
f84292e4b7 | ||
|
|
b597223228 | ||
|
|
8ec9a3f507 | ||
|
|
0ec339283e | ||
|
|
1d50dc20ba | ||
|
|
35331746b1 | ||
|
|
287f76c0ad | ||
|
|
dadb2b7a39 | ||
|
|
722432dfb0 | ||
|
|
a9184b224f | ||
|
|
3c732d7c85 | ||
|
|
d8fa1f1e0b | ||
|
|
1b03699677 | ||
|
|
7f6f777b47 | ||
|
|
68fb3b1a67 | ||
|
|
cd4a1d523b | ||
|
|
84cd6ba710 | ||
|
|
aa8492cd3d | ||
|
|
6f8710bd7a | ||
|
|
8b175b369a | ||
|
|
0be05967ca | ||
|
|
51f6165290 | ||
|
|
f147c83604 | ||
|
|
881de107c7 | ||
|
|
6b533dfbe1 | ||
|
|
2e3fb8beae | ||
|
|
e8baf5cd52 | ||
|
|
6c0289208f | ||
|
|
496f90fb56 | ||
|
|
696333e105 | ||
|
|
099a7aa972 | ||
|
|
b2aa568eb2 | ||
|
|
e685c8adcf | ||
|
|
19ddb670db | ||
|
|
8f1cd3e203 | ||
|
|
6b0fcd743f | ||
|
|
1d275936a4 | ||
|
|
f7c28a42c0 | ||
|
|
fa54829328 | ||
|
|
c8a7a3ac67 | ||
|
|
791b2608c5 | ||
|
|
ba8e40bc48 | ||
|
|
712626e791 | ||
|
|
734387a866 | ||
|
|
0988db9269 | ||
|
|
97d620b982 | ||
|
|
857d0a602f | ||
|
|
23f8aa8bdd | ||
|
|
03a04bd3fd | ||
|
|
57f91d046f | ||
|
|
758bc9b874 | ||
|
|
490a86796c | ||
|
|
f44270d913 | ||
|
|
4eed1b063e | ||
|
|
e3fe562d53 | ||
|
|
84ba81a5e5 | ||
|
|
d469f5da40 | ||
|
|
98e8de75dd | ||
|
|
e8d7985dba | ||
|
|
6aa680bf43 | ||
|
|
b629f07f5e | ||
|
|
9e4f571808 | ||
|
|
eaad9f3774 | ||
|
|
79f6359af1 | ||
|
|
ca5e9e03ab | ||
|
|
2ebdee2d42 | ||
|
|
c1773fb156 | ||
|
|
e05cc70f3a | ||
|
|
7d97ed5e93 | ||
|
|
823ee5462f | ||
|
|
64aebb5cf7 | ||
|
|
979713bb48 | ||
|
|
3b8efd628c | ||
|
|
f91033ef3d | ||
|
|
0f881a6de5 | ||
|
|
fe919f539e | ||
|
|
0b2efb006c | ||
|
|
7584af683e | ||
|
|
64358469ae | ||
|
|
28ad995982 | ||
|
|
3efb47fef1 | ||
|
|
1ca655f2f4 | ||
|
|
4fd6fc6e23 | ||
|
|
b6d21e7ef1 | ||
|
|
fb484b61da | ||
|
|
1ebfa3dd3f | ||
|
|
35bfb8679a | ||
|
|
dce407ca2b | ||
|
|
f9e90bee25 | ||
|
|
ea44096200 | ||
|
|
f3d19911c7 | ||
|
|
2214a4812c | ||
|
|
8b08afc1ea | ||
|
|
4d77723fe6 | ||
|
|
bd0364fcea | ||
|
|
3b466c0291 | ||
|
|
ade6faa6a3 | ||
|
|
c88959cc11 | ||
|
|
1e7fe81a60 | ||
|
|
726e3927f4 | ||
|
|
5f6b6d7ba0 | ||
|
|
62bc825bc8 | ||
|
|
de789ad8a9 | ||
|
|
e657acaee3 | ||
|
|
ba94f64ca0 | ||
|
|
26722781d2 | ||
|
|
2434df71aa | ||
|
|
f02d561d8b | ||
|
|
73c8715517 | ||
|
|
1a0a8cae67 | ||
|
|
a00665a3a1 | ||
|
|
126d514ee3 | ||
|
|
c0014f989f | ||
|
|
874413c3c2 | ||
|
|
bff2140b9f | ||
|
|
0aa20c7e8e | ||
|
|
8bf29b18da | ||
|
|
8330060bd9 | ||
|
|
b09d5a87fa | ||
|
|
b976976cda | ||
|
|
2a82d37da1 | ||
|
|
632f09382f | ||
|
|
558cc306e2 | ||
|
|
f1476befe2 | ||
|
|
cb386f0020 | ||
|
|
ef37fad7fc | ||
|
|
f42fa9515b | ||
|
|
fca2018279 | ||
|
|
1e0e755b0e | ||
|
|
757007ad7b | ||
|
|
bc2cc76b29 |
14
.github/renovate.json5
vendored
Normal file
14
.github/renovate.json5
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended",
|
||||
":disableDependencyDashboard"
|
||||
],
|
||||
"automerge": true,
|
||||
"automergeType": "branch",
|
||||
"schedule": [ "* * * * 0,6" ],
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true,
|
||||
"schedule": [ "* * * * 0,6" ]
|
||||
}
|
||||
}
|
||||
16
.github/workflows/build.yml
vendored
16
.github/workflows/build.yml
vendored
@@ -1,10 +1,6 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
pull_request:
|
||||
branches:
|
||||
- "master"
|
||||
workflow_call:
|
||||
inputs:
|
||||
ref:
|
||||
@@ -42,9 +38,9 @@ jobs:
|
||||
- run: "pipx install poetry"
|
||||
- uses: "actions/setup-python@v5"
|
||||
with:
|
||||
python-version: "3.11"
|
||||
python-version: "3.12"
|
||||
cache: "poetry"
|
||||
- run: "SKIP_BUILD_FRONTEND=true poetry lock --no-update"
|
||||
- run: "SKIP_BUILD_FRONTEND=true poetry lock"
|
||||
- run: "SKIP_BUILD_FRONTEND=true poetry install"
|
||||
- run: "SKIP_BUILD_FRONTEND=true poetry build"
|
||||
|
||||
@@ -58,9 +54,9 @@ jobs:
|
||||
- run: "pipx install poetry"
|
||||
- uses: "actions/setup-python@v5"
|
||||
with:
|
||||
python-version: "3.11"
|
||||
python-version: "3.12"
|
||||
cache: "poetry"
|
||||
- run: "SKIP_BUILD_FRONTEND=true poetry lock --no-update"
|
||||
- run: "SKIP_BUILD_FRONTEND=true poetry lock"
|
||||
- run: "SKIP_BUILD_FRONTEND=true poetry install"
|
||||
- run: "poetry run python yacv_server/logo.py"
|
||||
- uses: "actions/upload-artifact@v4"
|
||||
@@ -79,9 +75,9 @@ jobs:
|
||||
- run: "pipx install poetry"
|
||||
- uses: "actions/setup-python@v5"
|
||||
with:
|
||||
python-version: "3.11"
|
||||
python-version: "3.12"
|
||||
cache: "poetry"
|
||||
- run: "SKIP_BUILD_FRONTEND=true poetry lock --no-update"
|
||||
- run: "SKIP_BUILD_FRONTEND=true poetry lock"
|
||||
- run: "SKIP_BUILD_FRONTEND=true poetry install"
|
||||
- run: "YACV_DISABLE_SERVER=true poetry run python example/object.py"
|
||||
- uses: "actions/upload-artifact@v4"
|
||||
|
||||
2
.github/workflows/deploy1.yml
vendored
2
.github/workflows/deploy1.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
- run: "pipx install poetry"
|
||||
- uses: "actions/setup-python@v5"
|
||||
with:
|
||||
python-version: "3.11"
|
||||
python-version: "3.12"
|
||||
cache: "poetry"
|
||||
- run: "poetry version $CLEAN_VERSION"
|
||||
# Commit the changes and move the tag!
|
||||
|
||||
2
.github/workflows/deploy2.yml
vendored
2
.github/workflows/deploy2.yml
vendored
@@ -67,7 +67,7 @@ jobs:
|
||||
- run: "pipx install poetry"
|
||||
- uses: "actions/setup-python@v5"
|
||||
with:
|
||||
python-version: "3.11"
|
||||
python-version: "3.12"
|
||||
cache: "poetry"
|
||||
- run: "poetry install"
|
||||
- run: "poetry config pypi-token.pypi ${{ secrets.PYPI_TOKEN }}"
|
||||
|
||||
@@ -211,12 +211,11 @@ Apache License
|
||||
|
||||
-----------
|
||||
|
||||
The following npm packages may be included in this product:
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- source-map-js@1.2.0
|
||||
- source-map-js@1.2.1
|
||||
|
||||
These packages each contain the following license:
|
||||
This package contains the following license:
|
||||
|
||||
Copyright (c) 2009-2011, Mozilla Foundation and contributors
|
||||
All rights reserved.
|
||||
@@ -778,11 +777,13 @@ The above copyright notice and this permission notice shall be included in all c
|
||||
|
||||
-----------
|
||||
|
||||
The following npm package may be included in this product:
|
||||
The following npm packages may be included in this product:
|
||||
|
||||
- sharp@0.33.4
|
||||
- @img/sharp-linux-x64@0.33.5
|
||||
- @img/sharp-linuxmusl-x64@0.33.5
|
||||
- sharp@0.33.5
|
||||
|
||||
This package contains the following license:
|
||||
These packages each contain the following license:
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
@@ -980,7 +981,7 @@ third-party archives.
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- typescript@5.6.3
|
||||
- typescript@5.8.3
|
||||
|
||||
This package contains the following license:
|
||||
|
||||
@@ -1120,7 +1121,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- @lit-labs/ssr-dom-shim@1.2.0
|
||||
- @lit-labs/ssr-dom-shim@1.3.0
|
||||
|
||||
This package contains the following license:
|
||||
|
||||
@@ -1246,7 +1247,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- @babel/parser@7.25.3
|
||||
- @babel/parser@7.27.0
|
||||
|
||||
This package contains the following license:
|
||||
|
||||
@@ -1366,7 +1367,7 @@ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- magic-string@0.30.11
|
||||
- magic-string@0.30.17
|
||||
|
||||
This package contains the following license:
|
||||
|
||||
@@ -1404,30 +1405,22 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
-----------
|
||||
|
||||
The following npm package may be included in this product:
|
||||
The following npm packages may be included in this product:
|
||||
|
||||
- to-fast-properties@2.0.0
|
||||
- @img/sharp-libvips-linux-x64@1.0.4
|
||||
- @img/sharp-libvips-linuxmusl-x64@1.0.4
|
||||
|
||||
This package contains the following license:
|
||||
These packages each contain the following license:
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2014 Petka Antonov
|
||||
2015 Sindre Sorhus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
LGPL-3.0-or-later
|
||||
|
||||
-----------
|
||||
|
||||
The following npm packages may be included in this product:
|
||||
|
||||
- @babel/helper-string-parser@7.24.8
|
||||
- @babel/helper-validator-identifier@7.24.7
|
||||
- @babel/types@7.25.2
|
||||
- @babel/helper-string-parser@7.25.9
|
||||
- @babel/helper-validator-identifier@7.25.9
|
||||
- @babel/types@7.27.0
|
||||
|
||||
These packages each contain the following license:
|
||||
|
||||
@@ -1458,7 +1451,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- three-mesh-bvh@0.8.3
|
||||
- three-mesh-bvh@0.9.0
|
||||
|
||||
This package contains the following license:
|
||||
|
||||
@@ -1518,7 +1511,7 @@ SOFTWARE.
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- @monogrid/gainmap-js@3.0.3
|
||||
- @monogrid/gainmap-js@3.1.0
|
||||
|
||||
This package contains the following license:
|
||||
|
||||
@@ -1608,7 +1601,7 @@ The MIT license applies to all non-font and non-icon files.
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- semver@7.6.2
|
||||
- semver@7.7.1
|
||||
|
||||
This package contains the following license:
|
||||
|
||||
@@ -1692,13 +1685,13 @@ THE SOFTWARE.
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- three@0.169.0
|
||||
- three@0.175.0
|
||||
|
||||
This package contains the following license:
|
||||
|
||||
The MIT License
|
||||
|
||||
Copyright © 2010-2024 three.js authors
|
||||
Copyright © 2010-2025 three.js authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -1782,7 +1775,7 @@ THE SOFTWARE.
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- vuetify@3.7.4
|
||||
- vuetify@3.8.0
|
||||
|
||||
This package contains the following license:
|
||||
|
||||
@@ -1851,7 +1844,7 @@ THE SOFTWARE.
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- ktx-parse@0.7.1
|
||||
- ktx-parse@1.0.0
|
||||
|
||||
This package contains the following license:
|
||||
|
||||
@@ -1913,9 +1906,9 @@ SOFTWARE.
|
||||
|
||||
The following npm packages may be included in this product:
|
||||
|
||||
- @gltf-transform/core@4.1.0
|
||||
- @gltf-transform/extensions@4.1.0
|
||||
- @gltf-transform/functions@4.1.0
|
||||
- @gltf-transform/core@4.1.3
|
||||
- @gltf-transform/extensions@4.1.3
|
||||
- @gltf-transform/functions@4.1.3
|
||||
|
||||
These packages each contain the following license:
|
||||
|
||||
@@ -1975,7 +1968,7 @@ THE SOFTWARE.
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- postcss@8.4.49
|
||||
- postcss@8.5.3
|
||||
|
||||
This package contains the following license:
|
||||
|
||||
@@ -2004,7 +1997,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- nanoid@3.3.7
|
||||
- nanoid@3.3.11
|
||||
|
||||
This package contains the following license:
|
||||
|
||||
|
||||
@@ -52,10 +52,11 @@ async function onModelUpdateRequest(event: NetworkUpdateEvent) {
|
||||
let model = event.models[modelIndex];
|
||||
tools.value?.removeObjectSelections(model.name);
|
||||
try {
|
||||
let loadHelpers = (await settings()).loadHelpers;
|
||||
if (!model.isRemove) {
|
||||
doc = await SceneMgr.loadModel(sceneUrl, doc, model.name, model.url, isLast && settings.loadHelpers, isLast);
|
||||
doc = await SceneMgr.loadModel(sceneUrl, doc, model.name, model.url, isLast && loadHelpers, isLast);
|
||||
} else {
|
||||
doc = await SceneMgr.removeModel(sceneUrl, doc, model.name, isLast && settings.loadHelpers, isLast);
|
||||
doc = await SceneMgr.removeModel(sceneUrl, doc, model.name, isLast && loadHelpers, isLast);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error loading model", model, e);
|
||||
@@ -79,16 +80,18 @@ let networkMgr = new NetworkManager();
|
||||
networkMgr.addEventListener('update-early',
|
||||
(e) => viewer.value?.onProgress((e as CustomEvent<Array<any>>).detail.length * 0.01));
|
||||
networkMgr.addEventListener('update', (e) => onModelUpdateRequest(e as NetworkUpdateEvent));
|
||||
// Start loading all configured models ASAP
|
||||
for (let model of settings.preload) {
|
||||
networkMgr.load(model);
|
||||
}
|
||||
watch(viewer, (newViewer) => {
|
||||
(async () => { // Start loading all configured models ASAP
|
||||
let sett = await settings();
|
||||
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(""));
|
||||
' models from:</tspan>' + sett.preload.map((url: string) => '<tspan x="50%" dy="1.2em">- ' + url + '</tspan>').join(""));
|
||||
}
|
||||
});
|
||||
});
|
||||
for (let model of sett.preload) {
|
||||
await networkMgr.load(model);
|
||||
}
|
||||
})();
|
||||
|
||||
async function loadModelManual() {
|
||||
const modelUrl = prompt("For an improved experience in viewing CAD/GLTF models with automatic updates, it's recommended to use the official yacv_server Python package. This ensures seamless serving of models and automatic updates.\n\nOtherwise, enter the URL of the model to load:");
|
||||
|
||||
@@ -61,6 +61,7 @@ export class NetworkManager extends EventTarget {
|
||||
|
||||
private async monitorDevServer(url: URL, stop: () => boolean = () => false) {
|
||||
while (!stop()) {
|
||||
let monitorEveryMs = (await settings()).monitorEveryMs;
|
||||
try {
|
||||
// WARNING: This will spam the console logs with failed requests when the server is down
|
||||
const controller = new AbortController();
|
||||
@@ -82,12 +83,12 @@ export class NetworkManager extends EventTarget {
|
||||
}
|
||||
} else {
|
||||
// Server is down, wait a little longer before retrying
|
||||
await new Promise(resolve => setTimeout(resolve, 10 * settings.monitorEveryMs));
|
||||
await new Promise(resolve => setTimeout(resolve, 10 * monitorEveryMs));
|
||||
}
|
||||
controller.abort();
|
||||
} catch (e) { // Ignore errors (retry very soon)
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, settings.monitorEveryMs));
|
||||
await new Promise(resolve => setTimeout(resolve, monitorEveryMs));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
// These are the default values for the settings, which are overridden below
|
||||
export const settings = {
|
||||
let settingsCache: any = null;
|
||||
|
||||
export async function settings() {
|
||||
if (settingsCache !== null) return settingsCache;
|
||||
let settings = {
|
||||
preload: [
|
||||
// @ts-ignore
|
||||
// new URL('../../assets/fox.glb', import.meta.url).href,
|
||||
@@ -26,10 +30,37 @@ export const settings = {
|
||||
exposure: 1,
|
||||
shadowIntensity: 0,
|
||||
background: '',
|
||||
};
|
||||
|
||||
// Auto-override any settings from the URL
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.forEach((value, key) => {
|
||||
if (key in settings) (settings as any)[key] = parseSetting(key, value, settings);
|
||||
})
|
||||
|
||||
// Get the default preload URL if not overridden (requires a fetch that is avoided if possible)
|
||||
for (let i = 0; i < settings.preload.length; i++) {
|
||||
let url = settings.preload[i];
|
||||
if (url === '<auto>') {
|
||||
const possibleBackend = new URL("./?api_updates=true", window.location.href)
|
||||
await fetch(possibleBackend, {method: "HEAD"}).then((response) => {
|
||||
if (response.ok && response.headers.get("Content-Type") === "text/event-stream") {
|
||||
// Frontend served by the backend: default to this URL for updates
|
||||
url = "dev+" + possibleBackend.href;
|
||||
}
|
||||
}).catch((error) => console.error("Failed to check for backend:", error));
|
||||
if (url === '<auto>') { // Fallback to the default preload URL of localhost
|
||||
url = "dev+http://localhost:32323";
|
||||
}
|
||||
}
|
||||
settings.preload[i] = url;
|
||||
}
|
||||
settingsCache = settings;
|
||||
return settings;
|
||||
}
|
||||
|
||||
const firstTimeNames: Array<string> = []; // Needed for array values, which clear the array when overridden
|
||||
function parseSetting(name: string, value: string): any {
|
||||
function parseSetting(name: string, value: string, settings: any): any {
|
||||
let arrayElem = name.endsWith(".0")
|
||||
if (arrayElem) name = name.slice(0, -2);
|
||||
let prevValue = (settings as any)[name];
|
||||
@@ -42,7 +73,7 @@ function parseSetting(name: string, value: string): any {
|
||||
} else {
|
||||
toExtend = prevValue;
|
||||
}
|
||||
toExtend.push(parseSetting(name + ".0", value));
|
||||
toExtend.push(parseSetting(name + ".0", value, settings));
|
||||
return toExtend;
|
||||
} else {
|
||||
prevValue = prevValue[0];
|
||||
@@ -59,27 +90,3 @@ function parseSetting(name: string, value: string): any {
|
||||
throw new Error(`Unknown setting type: ${typeof prevValue} -- ${prevValue}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-override any settings from the URL
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.forEach((value, key) => {
|
||||
if (key in settings) (settings as any)[key] = parseSetting(key, value);
|
||||
})
|
||||
|
||||
// Get the default preload URL if not overridden (requires a fetch that is avoided if possible)
|
||||
for (let i = 0; i < settings.preload.length; i++) {
|
||||
let url = settings.preload[i];
|
||||
if (url === '<auto>') {
|
||||
const possibleBackend = new URL("./?api_updates=true", window.location.href)
|
||||
await fetch(possibleBackend, {method: "HEAD"}).then((response) => {
|
||||
if (response.ok && response.headers.get("Content-Type") === "text/event-stream") {
|
||||
// Frontend served by the backend: default to this URL for updates
|
||||
url = "dev+" + possibleBackend.href;
|
||||
}
|
||||
}).catch((error) => console.error("Failed to check for backend:", error));
|
||||
if (url === '<auto>') { // Fallback to the default preload URL of localhost
|
||||
url = "dev+http://localhost:32323";
|
||||
}
|
||||
}
|
||||
settings.preload[i] = url;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,11 @@ const clipPlaneY = ref(1);
|
||||
const clipPlaneSwappedY = ref(false);
|
||||
const clipPlaneZ = ref(1);
|
||||
const clipPlaneSwappedZ = ref(false);
|
||||
const edgeWidth = ref(settings.edgeWidth);
|
||||
const edgeWidth = ref(0);
|
||||
(async () => {
|
||||
let s = await settings();
|
||||
edgeWidth.value = s.edgeWidth;
|
||||
})();
|
||||
|
||||
// Misc properties
|
||||
const enabledFeatures = defineModel<Array<number>>("enabledFeatures", {default: [0, 1, 2]});
|
||||
|
||||
@@ -11,7 +11,7 @@ const emit = defineEmits<{ remove: [string] }>()
|
||||
|
||||
let {sceneDocument} = inject<{ sceneDocument: Ref<Document> }>('sceneDocument')!!;
|
||||
|
||||
let expandedNames = ref<Array<string>>([]);
|
||||
const expandedNames = ref<Array<string>>([]);
|
||||
|
||||
function meshesList(sceneDocument: Document): Array<Array<Mesh>> {
|
||||
// Grouped by shared name
|
||||
@@ -44,7 +44,7 @@ defineExpose({findModel})
|
||||
|
||||
<template>
|
||||
<v-expansion-panels v-for="meshes in meshesList(sceneDocument)" :key="meshName(meshes[0])"
|
||||
v-model="expandedNames" multiple>
|
||||
v-model="expandedNames as any" multiple>
|
||||
<model :meshes="meshes" :viewer="props.viewer" @remove="onRemove(meshes[0])"/>
|
||||
</v-expansion-panels>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import {defineModel, inject, ref, type ShallowRef, watch} from "vue";
|
||||
import {inject, ref, type ShallowRef, watch} from "vue";
|
||||
import {VBtn, VSelect, VTooltip} from "vuetify/lib/components/index.mjs";
|
||||
import SvgIcon from '@jamescoyle/vue-icon';
|
||||
import type {ModelViewerElement} from '@google/model-viewer';
|
||||
@@ -333,7 +333,7 @@ function updateBoundingBox() {
|
||||
for (let i = 0; i < 2; i++) { // Find the 2nd closest one by running twice dropping the first
|
||||
edge = axisEdges[0];
|
||||
let edgeDist = Infinity;
|
||||
let cameraPos: Vector3 = props.viewer?.scene.camera.position;
|
||||
let cameraPos: Vector3 = props.viewer?.scene?.camera?.position ?? new Vector3();
|
||||
for (let testEdge of axisEdges) {
|
||||
let from = new Vector3(...corners[testEdge[0]]);
|
||||
let to = new Vector3(...corners[testEdge[1]]);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import {settings} from "../misc/settings";
|
||||
import {inject, onMounted, type Ref, ref, watch} from "vue";
|
||||
import {inject, onUpdated, type Ref, ref, watch} from "vue";
|
||||
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';
|
||||
@@ -27,14 +27,19 @@ const scene = ref<ModelScene | null>(null);
|
||||
const renderer = ref<Renderer | null>(null);
|
||||
const controls = ref<SmoothControls | null>(null);
|
||||
|
||||
const sett = ref<any | null>(null);
|
||||
(async () => sett.value = await settings())();
|
||||
|
||||
let lastCameraTargetPosition: Vector3 | undefined = undefined;
|
||||
let lastCameraZoom: number | undefined = undefined;
|
||||
let lastCameraUrl = props.src.toString();
|
||||
onMounted(() => {
|
||||
if (!elem.value) return;
|
||||
let initialized = false
|
||||
onUpdated(() => {
|
||||
if (!elem.value) return; // Not ready yet
|
||||
if (initialized) return; // Already initialized
|
||||
initialized = true;
|
||||
elem.value.addEventListener('before-render', () => {
|
||||
if (!elem.value) return;
|
||||
if (!elem.value) return
|
||||
// Extract internals of model-viewer in order to hack unsupported features
|
||||
scene.value = elem.value[$scene] as ModelScene;
|
||||
renderer.value = elem.value[$renderer] as Renderer;
|
||||
@@ -207,11 +212,11 @@ watch(disableTap, (newDisableTap) => {
|
||||
|
||||
<template>
|
||||
<!-- The main 3D model viewer -->
|
||||
<model-viewer ref="elem" :ar="settings.arModes.length > 0" :ar-modes="settings.arModes" :autoplay="settings.autoplay"
|
||||
:environment-image="settings.background" :exposure="settings.exposure"
|
||||
:orbit-sensitivity="settings.orbitSensitivity" :pan-sensitivity="settings.panSensitivity"
|
||||
:poster="poster" :shadow-intensity="settings.shadowIntensity" :skybox-image="settings.background"
|
||||
:src="props.src" :zoom-sensitivity="settings.zoomSensitivity" alt="The 3D model(s)" camera-controls
|
||||
<model-viewer ref="elem" v-if="sett != null" :ar="sett.arModes.length > 0" :ar-modes="sett.arModes"
|
||||
:environment-image="sett.background" :exposure="sett.exposure" :autoplay="sett.autoplay"
|
||||
:orbit-sensitivity="sett.orbitSensitivity" :pan-sensitivity="sett.panSensitivity"
|
||||
:poster="poster" :shadow-intensity="sett.shadowIntensity" :skybox-image="sett.background"
|
||||
:src="props.src" :zoom-sensitivity="sett.zoomSensitivity" alt="The 3D model(s)" camera-controls
|
||||
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>
|
||||
|
||||
25
package.json
25
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "yet-another-cad-viewer",
|
||||
"version": "0.9.3",
|
||||
"version": "0.9.6",
|
||||
"description": "",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
@@ -22,8 +22,8 @@
|
||||
"@jamescoyle/vue-icon": "^0.1.2",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@mdi/svg": "^7.4.47",
|
||||
"three": "^0.170.0",
|
||||
"three-mesh-bvh": "^0.8.3",
|
||||
"three": "^0.177.0",
|
||||
"three-mesh-bvh": "^0.9.0",
|
||||
"three-orientation-gizmo": "https://github.com/jrj2211/three-orientation-gizmo",
|
||||
"vue": "^3.5.13",
|
||||
"vuetify": "^3.7.4"
|
||||
@@ -31,18 +31,17 @@
|
||||
"devDependencies": {
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/node": "^22.9.3",
|
||||
"@types/three": "^0.170.0",
|
||||
"@vitejs/plugin-vue": "^5.2.0",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.0",
|
||||
"@vue/tsconfig": "^0.6.0",
|
||||
"@types/three": "^0.177.0",
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"@vitejs/plugin-vue-jsx": "^5.0.0",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"buffer": "^5.5.0||^6.0.0",
|
||||
"commander": "^12.0.0",
|
||||
"generate-license-file": "^3.6.0",
|
||||
"npm-run-all2": "^7.0.1",
|
||||
"commander": "^14.0.0",
|
||||
"generate-license-file": "^4.0.0",
|
||||
"npm-run-all2": "^8.0.0",
|
||||
"terser": "^5.36.0",
|
||||
"typescript": "~5.6.3",
|
||||
"vite": "^5.4.11",
|
||||
"vite-plugin-top-level-await": "^1.4.4",
|
||||
"typescript": "~5.8.0",
|
||||
"vite": "^7.0.0",
|
||||
"vue-tsc": "^2.1.10"
|
||||
}
|
||||
}
|
||||
|
||||
1275
poetry.lock
generated
1275
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "yacv-server"
|
||||
version = "0.9.3"
|
||||
version = "0.9.6"
|
||||
description = "Yet Another CAD Viewer (server)"
|
||||
authors = ["Yeicor <4929005+Yeicor@users.noreply.github.com>"]
|
||||
license = "MIT"
|
||||
@@ -11,10 +11,10 @@ include = [
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.9,<3.13"
|
||||
python = ">=3.10,<3.13" # Due to vtk transitive dependency of build123d -> cadquery-ocp -> vtk
|
||||
|
||||
# CAD
|
||||
build123d = ">=0.8,<0.9"
|
||||
build123d = ">=0.9,<0.10"
|
||||
|
||||
# Misc
|
||||
pygltflib = "^1.16.2"
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:recommended", ":disableDependencyDashboard"],
|
||||
"automerge": true,
|
||||
"schedule": [
|
||||
"before 9am on Saturday"
|
||||
],
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true,
|
||||
"schedule": ["before 9am on Saturday"]
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import {name, version} from './package.json'
|
||||
import {execSync} from 'child_process'
|
||||
import topLevelAwait from "vite-plugin-top-level-await";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
@@ -19,12 +18,6 @@ export default defineConfig({
|
||||
}
|
||||
}),
|
||||
vueJsx(),
|
||||
topLevelAwait({
|
||||
// The export name of top-level await promise for each chunk module
|
||||
promiseExportName: "__tla",
|
||||
// The function to generate import names of top-level await promise in each chunk module
|
||||
promiseImportName: i => `__tla_${i}`
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
|
||||
@@ -10,7 +10,7 @@ from OCP.TopExp import TopExp
|
||||
from OCP.TopLoc import TopLoc_Location
|
||||
from OCP.TopTools import TopTools_IndexedMapOfShape
|
||||
from OCP.TopoDS import TopoDS_Shape
|
||||
from build123d import Compound, Shape, Color
|
||||
from build123d import Compound, Color
|
||||
|
||||
from yacv_server.gltf import GLTFMgr
|
||||
|
||||
@@ -78,7 +78,7 @@ def get_shape(obj: CADLike, error: bool = True) -> Optional[CADCoreLike]:
|
||||
# Sorting is required to improve hashcode consistency
|
||||
shapes_raw_filtered_sorted = sorted(shapes_raw_filtered, key=lambda x: _hashcode(x))
|
||||
# Build a single compound shape
|
||||
shapes_bd = [Shape(shape) for shape in shapes_raw_filtered_sorted if shape is not None]
|
||||
shapes_bd = [Compound(shape) for shape in shapes_raw_filtered_sorted if shape is not None]
|
||||
return get_shape(Compound(shapes_bd), error)
|
||||
except TypeError:
|
||||
pass
|
||||
@@ -121,11 +121,11 @@ def image_to_gltf(source: str | bytes, center: any, width: Optional[float] = Non
|
||||
hasher = hashlib.md5()
|
||||
hasher.update(source)
|
||||
name = 'image_' + hasher.hexdigest()
|
||||
format: str
|
||||
_format: str
|
||||
if save_mime == 'image/jpeg':
|
||||
format = 'JPEG'
|
||||
_format = 'JPEG'
|
||||
elif save_mime == 'image/png':
|
||||
format = 'PNG'
|
||||
_format = 'PNG'
|
||||
else:
|
||||
raise ValueError(f'Unsupported save MIME type (for GLTF files): {save_mime}')
|
||||
|
||||
@@ -154,7 +154,7 @@ def image_to_gltf(source: str | bytes, center: any, width: Optional[float] = Non
|
||||
img = img.resize((new_width, new_height))
|
||||
|
||||
# Save the image to a buffer
|
||||
img.save(img_buf, format=format)
|
||||
img.save(img_buf, format=_format)
|
||||
img_buf = img_buf.getvalue()
|
||||
|
||||
# Convert coordinates system as a last step (gltf is Y-up instead of Z-up)
|
||||
|
||||
@@ -169,6 +169,7 @@ class GLTFMgr:
|
||||
self.gltf.images = [Image(bufferView=len(buffers_list), mimeType=self.image[1])]
|
||||
self.gltf.textures = [Texture(source=0, sampler=0)]
|
||||
self.gltf.samplers = [Sampler(magFilter=NEAREST)]
|
||||
# noinspection PyPep8Naming
|
||||
self.gltf.materials[0].pbrMetallicRoughness.baseColorTexture = TextureInfo(index=0)
|
||||
buffers_list.append((Accessor(), BufferView(), self.image[0]))
|
||||
|
||||
|
||||
@@ -138,3 +138,4 @@ class HTTPHandler(SimpleHTTPRequestHandler):
|
||||
self.send_header('E-Tag', f'"{_hash}"')
|
||||
self.end_headers()
|
||||
self.wfile.write(exported_glb)
|
||||
return None
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import queue
|
||||
import queue
|
||||
import threading
|
||||
from typing import List, TypeVar, \
|
||||
Generic, Generator
|
||||
from typing import List, TypeVar, Generic, Generator
|
||||
|
||||
from yacv_server.mylogger import logger
|
||||
|
||||
@@ -58,7 +56,7 @@ class BufferedPubSub(Generic[T]):
|
||||
|
||||
def subscribe(self, include_buffered: bool = True, include_future: bool = True, yield_timeout: float = 0.0) -> \
|
||||
Generator[T, None, None]:
|
||||
"""Subscribes to events as an generator that yields events and automatically unsubscribes"""
|
||||
"""Subscribes to events as a generator that yields events and automatically unsubscribes"""
|
||||
q = self._subscribe(include_buffered, include_future)
|
||||
try:
|
||||
while True:
|
||||
|
||||
@@ -5,7 +5,7 @@ from OCP.BRepAdaptor import BRepAdaptor_Curve
|
||||
from OCP.GCPnts import GCPnts_TangentialDeflection
|
||||
from OCP.TopLoc import TopLoc_Location
|
||||
from OCP.TopoDS import TopoDS_Face, TopoDS_Edge, TopoDS_Shape, TopoDS_Vertex
|
||||
from build123d import Shape, Vertex, Face, Location
|
||||
from build123d import Vertex, Face, Location, Compound
|
||||
from pygltflib import GLTF2
|
||||
|
||||
from yacv_server.cad import CADCoreLike, ColorTuple
|
||||
@@ -33,12 +33,12 @@ def tessellate(
|
||||
mgr.add_location(Location(cad_like))
|
||||
|
||||
elif isinstance(cad_like, TopoDS_Shape):
|
||||
shape = Shape(cad_like)
|
||||
shape = Compound(cad_like)
|
||||
|
||||
# Perform tessellation tasks
|
||||
edge_to_faces: Dict[str, List[TopoDS_Face]] = {}
|
||||
vertex_to_faces: Dict[str, List[TopoDS_Face]] = {}
|
||||
if faces:
|
||||
if faces and hasattr(shape, 'faces'):
|
||||
shape_faces = shape.faces()
|
||||
for face in shape_faces:
|
||||
_tessellate_face(mgr, face.wrapped, tolerance, angular_tolerance, obj_color)
|
||||
@@ -49,16 +49,19 @@ def tessellate(
|
||||
for vertex in face.vertices():
|
||||
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 and hasattr(shape, 'edges'):
|
||||
shape_edges = shape.edges()
|
||||
for edge in shape_edges:
|
||||
_tessellate_edge(mgr, edge.wrapped, edge_to_faces.get(edge.wrapped, []), 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 and hasattr(shape, 'vertices'):
|
||||
for vertex in shape.vertices():
|
||||
_tessellate_vertex(mgr, vertex.wrapped, vertex_to_faces.get(vertex.wrapped, []), obj_color)
|
||||
|
||||
else:
|
||||
raise TypeError(f"Unsupported type: {type(cad_like)}: {cad_like}")
|
||||
|
||||
return mgr.build()
|
||||
|
||||
|
||||
@@ -69,9 +72,10 @@ def _tessellate_face(
|
||||
angular_tolerance: float = 0.1,
|
||||
color: Optional[ColorTuple] = None,
|
||||
):
|
||||
face = Shape(ocp_face)
|
||||
face = Compound(ocp_face)
|
||||
# face.mesh(tolerance, angular_tolerance)
|
||||
tri_mesh = face.tessellate(tolerance, angular_tolerance)
|
||||
# noinspection PyArgumentList
|
||||
poly = BRep_Tool.Triangulation_s(face.wrapped, TopLoc_Location())
|
||||
if poly is None:
|
||||
logger.warn("No triangulation found for face")
|
||||
@@ -86,6 +90,7 @@ def _tessellate_face(
|
||||
vertices = tri_mesh[0]
|
||||
indices = tri_mesh[1]
|
||||
mgr.add_face(vertices, indices, uv, color)
|
||||
return None
|
||||
|
||||
|
||||
def _push_point(v: Tuple[float, float, float], faces: List[TopoDS_Face]) -> Tuple[float, float, float]:
|
||||
|
||||
@@ -9,18 +9,18 @@ import threading
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from http.server import ThreadingHTTPServer
|
||||
from io import BytesIO
|
||||
from threading import Thread
|
||||
from typing import Optional, Dict, Union, Callable, List, Tuple
|
||||
|
||||
from OCP.TopLoc import TopLoc_Location
|
||||
from OCP.TopoDS import TopoDS_Shape
|
||||
# noinspection PyProtectedMember
|
||||
from build123d import Shape, Axis, Location, Vector, Color
|
||||
from dataclasses_json import dataclass_json
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
# noinspection PyProtectedMember
|
||||
from build123d import Shape, Axis, Location, Vector
|
||||
from dataclasses_json import dataclass_json
|
||||
|
||||
from yacv_server.cad import _hashcode, ColorTuple, get_color
|
||||
from yacv_server.cad import _hashcode, get_color
|
||||
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
|
||||
@@ -95,7 +95,7 @@ class YACV:
|
||||
"""Default texture to use for model faces, in (data, mimetype) format.
|
||||
If left as None, a default checkerboard texture will be used.
|
||||
|
||||
It can be set with the YACV_BASE_TEXTURE=<uri> and overriden by `show(..., texture="<uri>")`.
|
||||
It can be set with the YACV_BASE_TEXTURE=<uri> and overridden by `show(..., texture="<uri>")`.
|
||||
The <uri> can be file:<path> or data:<mime>;base64,<data> where <mime> is the mime type and
|
||||
<data> is the base64 encoded image."""
|
||||
|
||||
@@ -331,6 +331,7 @@ class YACV:
|
||||
try:
|
||||
return next(subscription), event.hash
|
||||
finally:
|
||||
# noinspection PyInconsistentReturns
|
||||
subscription.close()
|
||||
|
||||
def export_all(self, folder: str,
|
||||
@@ -381,19 +382,30 @@ def _preprocess_cad(obj: CADLike, **kwargs) -> CADCoreLike:
|
||||
return obj
|
||||
|
||||
|
||||
_find_var_name_count = 0
|
||||
|
||||
_obj_name_counts = {}
|
||||
|
||||
def _find_var_name(obj: any, avoid_levels: int = 2) -> str:
|
||||
"""A hacky way to get a stable name for an object that may change over time"""
|
||||
global _find_var_name_count
|
||||
|
||||
# Build123d objects have a "label" property, CadQuery Assembly's have "name"
|
||||
for f in ('label', 'name'):
|
||||
if hasattr(obj, f):
|
||||
v = getattr(obj, f)
|
||||
if v != '':
|
||||
return v;
|
||||
|
||||
# Otherwise walk up our stack to see if there's a local variable that points to it
|
||||
obj_shape = get_shape(obj, error=False) or obj
|
||||
for frame in inspect.stack()[avoid_levels:]:
|
||||
for key, value in frame.frame.f_locals.items():
|
||||
if get_shape(value, error=False) is obj_shape:
|
||||
return key
|
||||
_find_var_name_count += 1
|
||||
return 'unknown_var_' + str(_find_var_name_count)
|
||||
|
||||
# Last resort, name it for its type with a disambiguating number
|
||||
global _obj_name_counts
|
||||
t = obj.__class__.__name__
|
||||
_obj_name_counts[t] = 1 if t not in _obj_name_counts else _obj_name_counts[t] + 1
|
||||
return t + str(_obj_name_counts[t])
|
||||
|
||||
|
||||
def sizeof_fmt(num, suffix="B"):
|
||||
|
||||
Reference in New Issue
Block a user