mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 22:24:17 +01:00
Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
6cc997d4ee | ||
|
|
a2f24be087 | ||
|
|
dd38a66d2f | ||
|
|
c8c6927962 | ||
|
|
be7c4e3c8e | ||
|
|
c20849222c | ||
|
|
1e0cee46cd | ||
|
|
77f6a0ae89 | ||
|
|
b9bff03db1 | ||
|
|
74126b54ab | ||
|
|
48e81bc3bc | ||
|
|
0f98f0d1a3 | ||
|
|
494b20ba15 | ||
|
|
13886186c0 | ||
|
|
1611a8dded | ||
|
|
24fb41bb77 | ||
|
|
8977963b58 | ||
|
|
b5f50b1a5f | ||
|
|
0f52a0b6c6 | ||
|
|
9a783750b7 | ||
|
|
ec083fb951 | ||
|
|
e894dbb997 | ||
|
|
b36bc2523c | ||
|
|
0c0db36718 | ||
|
|
4343c52466 | ||
|
|
ea181dac0b | ||
|
|
09525daae8 | ||
|
|
874f9e8d6e | ||
|
|
822672c288 | ||
|
|
383586c5a4 | ||
|
|
b4d557534d | ||
|
|
e6921144fb | ||
|
|
f95509f4f4 | ||
|
|
5a0228d4fc | ||
|
|
5c96e4e44b | ||
|
|
4012bac31f | ||
|
|
4439c436fc | ||
|
|
b23119e44c | ||
|
|
e428d9cb5a | ||
|
|
6574db6f81 | ||
|
|
844d25483f | ||
|
|
2f6e7def42 | ||
|
|
dbf29c02a9 | ||
|
|
8303dd2d37 | ||
|
|
3e24a29e73 | ||
|
|
e3b32a7e92 | ||
|
|
20def41a6c | ||
|
|
aff3a367e6 | ||
|
|
80c94fec80 | ||
|
|
2d8d8178ba | ||
|
|
6ac1365e27 | ||
|
|
ef3938ca4a | ||
|
|
334a23c04d | ||
|
|
57be98ede3 | ||
|
|
52f97963f0 | ||
|
|
61a3f157ac | ||
|
|
bf4bf38dd2 | ||
|
|
4119656f69 | ||
|
|
3834e8d506 | ||
|
|
b4380d3a2f | ||
|
|
22ea0617e2 | ||
|
|
09c0994a34 | ||
|
|
0939e25da2 | ||
|
|
712e0a06e6 | ||
|
|
e73f745800 | ||
|
|
773ea797a1 | ||
|
|
aae20aeedf | ||
|
|
a3004e59fb | ||
|
|
5e76193f43 | ||
|
|
50a2627b55 | ||
|
|
cbddacb7b8 | ||
|
|
887e71b7b2 |
26
.github/dependabot.yml
vendored
26
.github/dependabot.yml
vendored
@@ -1,26 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "saturday"
|
||||
time: "09:00"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "saturday"
|
||||
time: "09:00"
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/example"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "saturday"
|
||||
time: "09:00"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/.github/workflows/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "saturday"
|
||||
time: "09:00"
|
||||
18
.github/renovate.json5
vendored
Normal file
18
.github/renovate.json5
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended",
|
||||
":disableDependencyDashboard"
|
||||
],
|
||||
"automerge": true,
|
||||
"automergeType": "branch",
|
||||
"schedule": [
|
||||
"before 3am on Saturday"
|
||||
],
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true,
|
||||
"schedule": [
|
||||
"before 3am on Saturday"
|
||||
]
|
||||
}
|
||||
}
|
||||
35
.github/workflows/autoupdate.yml
vendored
35
.github/workflows/autoupdate.yml
vendored
@@ -1,35 +0,0 @@
|
||||
on: "pull_request_target"
|
||||
|
||||
permissions:
|
||||
pull-requests: "write"
|
||||
contents: "write"
|
||||
|
||||
jobs:
|
||||
dependabot:
|
||||
runs-on: "ubuntu-latest"
|
||||
# Checking the actor will prevent your Action run failing on non-Dependabot
|
||||
# PRs but also ensures that it only does work for Dependabot PRs.
|
||||
if: "${{ github.actor == 'dependabot[bot]' }}"
|
||||
steps:
|
||||
# This first step will fail if there's no metadata and so the approval
|
||||
# will not occur.
|
||||
- name: "Dependabot metadata"
|
||||
id: "dependabot-metadata"
|
||||
uses: "dependabot/fetch-metadata@v2"
|
||||
with:
|
||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
# Here the PR gets approved.
|
||||
- uses: "actions/checkout@v4"
|
||||
- name: "Approve a PR"
|
||||
run: "gh pr review --approve $PR_URL"
|
||||
env:
|
||||
PR_URL: "${{ github.event.pull_request.html_url }}"
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
# Finally, this sets the PR to allow auto-merging for patch and minor
|
||||
# updates if all checks pass
|
||||
- name: "Enable auto-merge for Dependabot PRs"
|
||||
#if: "${{ steps.dependabot-metadata.outputs.update-type != 'version-update:semver-major' }}"
|
||||
run: "gh pr merge --auto --squash $PR_URL"
|
||||
env:
|
||||
PR_URL: "${{ github.event.pull_request.html_url }}"
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
@@ -1,10 +1,6 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
pull_request:
|
||||
branches:
|
||||
- "master"
|
||||
workflow_call:
|
||||
inputs:
|
||||
ref:
|
||||
@@ -42,8 +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"
|
||||
- run: "SKIP_BUILD_FRONTEND=true poetry install"
|
||||
- run: "SKIP_BUILD_FRONTEND=true poetry build"
|
||||
|
||||
@@ -57,8 +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"
|
||||
- run: "SKIP_BUILD_FRONTEND=true poetry install"
|
||||
- run: "poetry run python yacv_server/logo.py"
|
||||
- uses: "actions/upload-artifact@v4"
|
||||
@@ -77,8 +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"
|
||||
- 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 }}"
|
||||
|
||||
11
README.md
11
README.md
@@ -21,8 +21,15 @@ in a web browser.
|
||||
The [example](example) is a fully working project that shows how to use the viewer.
|
||||
|
||||
You can play with the latest
|
||||
demo [here](https://yeicor-3d.github.io/yet-another-cad-viewer/?preload=logo.glb&preload=fox.glb&preload=img.jpg.glb&preload=location.glb)
|
||||
demo [here](https://yeicor-3d.github.io/yet-another-cad-viewer/?preload=logo.glb&preload=logo_hl.glb&preload=fox.glb&preload=img.jpg.glb&preload=location.glb)
|
||||
(or
|
||||
[without animation](https://yeicor-3d.github.io/yet-another-cad-viewer/?autoplay=false&preload=logo.glb&preload=fox.glb&preload=img.jpg.glb&preload=location.glb)).
|
||||
[without animation](https://yeicor-3d.github.io/yet-another-cad-viewer/?autoplay=false&preload=logo.glb&preload=logo_hl.glb&preload=fox.glb&preload=img.jpg.glb&preload=location.glb)).
|
||||
|
||||

|
||||
|
||||
## Related projects
|
||||
|
||||
- [cq-studio](https://github.com/ccazabon/cq-studio) provides an alternative workflow that detects file changes instead
|
||||
of relying on an interactive environment like Jupyter for hot-reloading.
|
||||
Uses the same backend and frontend behind the scenes.
|
||||
- [build123d-docker](https://github.com/derhuerst/build123d-docker/pkgs/container/build123d) provides docker images for Yet Another CAD Viewer and other projects, with automatic updates.
|
||||
|
||||
@@ -3,9 +3,9 @@ https://www.npmjs.com/package/generate-license-file
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- @google/model-viewer@3.5.0
|
||||
- @google/model-viewer@4.0.0
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
@@ -213,9 +213,9 @@ Apache License
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- source-map-js@1.2.0
|
||||
- source-map-js@1.2.1
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
Copyright (c) 2009-2011, Mozilla Foundation and contributors
|
||||
All rights reserved.
|
||||
@@ -254,7 +254,7 @@ The following npm packages may be included in this product:
|
||||
- ndarray-ops@1.2.2
|
||||
- uniq@1.0.1
|
||||
|
||||
These packages each contain the following license and notice below:
|
||||
These packages each contain the following license:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -284,7 +284,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- ndarray@1.0.19
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -314,7 +314,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- promise-worker-transferable@1.0.4
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
@@ -524,7 +524,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- detect-libc@2.0.3
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
@@ -735,7 +735,7 @@ The following npm packages may be included in this product:
|
||||
- @types/ndarray@1.0.14
|
||||
- @types/trusted-types@2.0.7
|
||||
|
||||
These packages each contain the following license and notice below:
|
||||
These packages each contain the following license:
|
||||
|
||||
MIT License
|
||||
|
||||
@@ -765,7 +765,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- lie@3.3.0
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
#Copyright (c) 2014-2018 Calvin Metcalf, Jordan Harband
|
||||
|
||||
@@ -777,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 and notice below:
|
||||
These packages each contain the following license:
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
@@ -979,9 +981,9 @@ third-party archives.
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- typescript@5.6.2
|
||||
- typescript@5.7.2
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
Apache License
|
||||
|
||||
@@ -1047,7 +1049,7 @@ The following npm packages may be included in this product:
|
||||
- lit-element@3.3.3
|
||||
- lit@2.8.0
|
||||
|
||||
These packages each contain the following license and notice below:
|
||||
These packages each contain the following license:
|
||||
|
||||
BSD 3-Clause License
|
||||
|
||||
@@ -1084,7 +1086,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- lit-html@2.8.0
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
BSD 3-Clause License
|
||||
|
||||
@@ -1119,9 +1121,9 @@ 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.2.1
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
BSD-3-Clause
|
||||
|
||||
@@ -1131,7 +1133,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- color-string@1.9.1
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
Copyright (c) 2011 Heather Arthur <fayearthur@gmail.com>
|
||||
|
||||
@@ -1160,7 +1162,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- color-convert@2.0.1
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
Copyright (c) 2011-2016 Heather Arthur <fayearthur@gmail.com>
|
||||
|
||||
@@ -1189,7 +1191,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- immediate@3.0.6
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
Copyright (c) 2012 Barnesandnoble.com, llc, Donavon West, Domenic Denicola, Brian Cavalier
|
||||
|
||||
@@ -1218,7 +1220,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- color@4.2.3
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
Copyright (c) 2012 Heather Arthur
|
||||
|
||||
@@ -1245,9 +1247,9 @@ 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.26.3
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
Copyright (C) 2012-2014 by various contributors (see AUTHORS)
|
||||
|
||||
@@ -1275,7 +1277,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- is-promise@2.2.2
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
Copyright (c) 2014 Forbes Lindesay
|
||||
|
||||
@@ -1303,7 +1305,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- estree-walker@2.0.2
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
Copyright (c) 2015-20 [these people](https://github.com/Rich-Harris/estree-walker/graphs/contributors)
|
||||
|
||||
@@ -1319,7 +1321,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- csstype@3.1.3
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
Copyright (c) 2017-2018 Fredrik Nicol
|
||||
|
||||
@@ -1347,7 +1349,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- entities@4.5.0
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
Copyright (c) Felix Böhm
|
||||
All rights reserved.
|
||||
@@ -1365,9 +1367,9 @@ 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 and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
Copyright 2018 Rich Harris
|
||||
|
||||
@@ -1381,13 +1383,13 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- picocolors@1.0.1
|
||||
- picocolors@1.1.1
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2021 Alexey Raspopov, Kostiantyn Denysov, Anton Verinov
|
||||
Copyright (c) 2021-2024 Oleksii Raspopov, Kostiantyn Denysov, Anton Verinov
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -1403,32 +1405,24 @@ 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 and notice below:
|
||||
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.26.3
|
||||
|
||||
These packages each contain the following license and notice below:
|
||||
These packages each contain the following license:
|
||||
|
||||
MIT License
|
||||
|
||||
@@ -1457,9 +1451,9 @@ 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.7.8
|
||||
- three-mesh-bvh@0.8.3
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
MIT License
|
||||
|
||||
@@ -1489,7 +1483,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- three-orientation-gizmo@1.1.0
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
MIT License
|
||||
|
||||
@@ -1517,9 +1511,9 @@ 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 and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
MIT License
|
||||
|
||||
@@ -1549,7 +1543,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- @jamescoyle/vue-icon@0.1.2
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
MIT License
|
||||
|
||||
@@ -1580,7 +1574,7 @@ The following npm packages may be included in this product:
|
||||
- @mdi/js@7.4.47
|
||||
- @mdi/svg@7.4.47
|
||||
|
||||
These packages each contain the following license and notice below:
|
||||
These packages each contain the following license:
|
||||
|
||||
Pictogrammers Free License
|
||||
--------------------------
|
||||
@@ -1607,9 +1601,9 @@ 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.6.3
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
The ISC License
|
||||
|
||||
@@ -1633,7 +1627,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- @jridgewell/sourcemap-codec@1.5.0
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
The MIT License
|
||||
|
||||
@@ -1663,7 +1657,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- three@0.125.2
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
The MIT License
|
||||
|
||||
@@ -1691,9 +1685,9 @@ THE SOFTWARE.
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- three@0.163.0
|
||||
- three@0.171.0
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
The MIT License
|
||||
|
||||
@@ -1723,7 +1717,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- is-arrayish@0.3.2
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -1753,7 +1747,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- simple-swizzle@0.2.2
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -1781,9 +1775,9 @@ THE SOFTWARE.
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- vuetify@3.7.1
|
||||
- vuetify@3.7.6
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -1811,18 +1805,18 @@ THE SOFTWARE.
|
||||
|
||||
The following npm packages may be included in this product:
|
||||
|
||||
- @vue/compiler-core@3.5.5
|
||||
- @vue/compiler-dom@3.5.5
|
||||
- @vue/compiler-sfc@3.5.5
|
||||
- @vue/compiler-ssr@3.5.5
|
||||
- @vue/reactivity@3.5.5
|
||||
- @vue/runtime-core@3.5.5
|
||||
- @vue/runtime-dom@3.5.5
|
||||
- @vue/server-renderer@3.5.5
|
||||
- @vue/shared@3.5.5
|
||||
- vue@3.5.5
|
||||
- @vue/compiler-core@3.5.13
|
||||
- @vue/compiler-dom@3.5.13
|
||||
- @vue/compiler-sfc@3.5.13
|
||||
- @vue/compiler-ssr@3.5.13
|
||||
- @vue/reactivity@3.5.13
|
||||
- @vue/runtime-core@3.5.13
|
||||
- @vue/runtime-dom@3.5.13
|
||||
- @vue/server-renderer@3.5.13
|
||||
- @vue/shared@3.5.13
|
||||
- vue@3.5.13
|
||||
|
||||
These packages each contain the following license and notice below:
|
||||
These packages each contain the following license:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -1850,9 +1844,9 @@ THE SOFTWARE.
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- ktx-parse@0.7.0
|
||||
- ktx-parse@0.7.1
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -1882,9 +1876,9 @@ The following npm packages may be included in this product:
|
||||
|
||||
- ndarray-lanczos@0.3.0
|
||||
- ndarray-pixels@4.1.0
|
||||
- property-graph@2.0.0
|
||||
- property-graph@3.0.0
|
||||
|
||||
These packages each contain the following license and notice below:
|
||||
These packages each contain the following license:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -1912,11 +1906,11 @@ SOFTWARE.
|
||||
|
||||
The following npm packages may be included in this product:
|
||||
|
||||
- @gltf-transform/core@4.0.8
|
||||
- @gltf-transform/extensions@4.0.8
|
||||
- @gltf-transform/functions@4.0.8
|
||||
- @gltf-transform/core@4.1.1
|
||||
- @gltf-transform/extensions@4.1.1
|
||||
- @gltf-transform/functions@4.1.1
|
||||
|
||||
These packages each contain the following license and notice below:
|
||||
These packages each contain the following license:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -1946,7 +1940,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- is-buffer@1.1.6
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -1974,9 +1968,9 @@ THE SOFTWARE.
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- postcss@8.4.45
|
||||
- postcss@8.4.49
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -2003,9 +1997,9 @@ 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.8
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -2034,7 +2028,7 @@ The following npm package may be included in this product:
|
||||
|
||||
- color-name@1.1.4
|
||||
|
||||
This package contains the following license and notice below:
|
||||
This package contains the following license:
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2015 Dmitry Ivanov
|
||||
|
||||
@@ -36,5 +36,5 @@ Once you have the `object.glb` file, you can host it on any static file server a
|
||||
|
||||
For the example model, the build process is set up in [build.yml](../.github/workflows/build.yml), the upload process
|
||||
is set up in [deploy.yml](../.github/workflows/deploy.yml), and the final link is:
|
||||
https://yeicor-3d.github.io/yet-another-cad-viewer/?preload=example.glb
|
||||
https://yeicor-3d.github.io/yet-another-cad-viewer/?preload=example.glb&preload=example_hl.glb
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import logging
|
||||
import os
|
||||
|
||||
from build123d import * # Also works with cadquery objects!
|
||||
from build123d import Compound
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
@@ -15,9 +16,17 @@ with BuildPart() as example:
|
||||
Box(10, 10, 5)
|
||||
Cylinder(4, 5, mode=Mode.SUBTRACT)
|
||||
|
||||
# Show it in the frontend with hot-reloading
|
||||
show(example)
|
||||
# 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 (texture and other keyword arguments are optional)
|
||||
texture = ( # MIT License Framework7 Line Icons: https://www.svgrepo.com/svg/437552/checkmark-seal
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASAQAAAAB+tbP6AAAAQ0lEQVQI12P4b3+A4Z/8AYYHBw8w"
|
||||
"HHxwgOH8HyD+AsRPDjDMP+fAYD+fgcESiGfYOTCcqTnAcK4GogakFqQHpBdoBgAbGiPSbdzkhgAAAABJRU5ErkJggg==")
|
||||
show(example, example_hl, texture=texture)
|
||||
|
||||
# %%
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
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 () => { // 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>' + 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:");
|
||||
|
||||
@@ -43,7 +43,7 @@ export class NetworkManager extends EventTarget {
|
||||
* Updates will be emitted as "update" events, including the download URL and the model name.
|
||||
*/
|
||||
async load(url: string) {
|
||||
if (url.startsWith("dev+")) {
|
||||
if (url.startsWith("dev+") || url.startsWith("dev ")) {
|
||||
let baseUrl = new URL(url.slice(4));
|
||||
baseUrl.searchParams.set("api_updates", "true");
|
||||
await this.monitorDevServer(baseUrl);
|
||||
@@ -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,35 +1,66 @@
|
||||
// These are the default values for the settings, which are overridden below
|
||||
export const settings = {
|
||||
preload: [
|
||||
// @ts-ignore
|
||||
// new URL('../../assets/fox.glb', import.meta.url).href,
|
||||
// @ts-ignore
|
||||
// new URL('../../assets/logo_build/base.glb', import.meta.url).href,
|
||||
// @ts-ignore
|
||||
// new URL('../../assets/logo_build/location.glb', import.meta.url).href,
|
||||
// @ts-ignore
|
||||
// new URL('../../assets/logo_build/img.jpg.glb', import.meta.url).href,
|
||||
// Websocket URLs automatically listen for new models from the python backend
|
||||
"dev+http://127.0.0.1:32323/"
|
||||
],
|
||||
loadHelpers: true,
|
||||
edgeWidth: 0, /* The default line size for edges, set to 0 to use basic gl.LINEs */
|
||||
displayLoadingEveryMs: 1000, /* How often to display partially loaded models */
|
||||
monitorEveryMs: 100,
|
||||
monitorOpenTimeoutMs: 1000,
|
||||
// ModelViewer settings
|
||||
autoplay: true, // Global animation toggle
|
||||
arModes: 'webxr scene-viewer quick-look',
|
||||
zoomSensitivity: 0.25,
|
||||
orbitSensitivity: 1,
|
||||
panSensitivity: 1,
|
||||
exposure: 1,
|
||||
shadowIntensity: 0,
|
||||
background: '',
|
||||
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,
|
||||
// @ts-ignore
|
||||
// new URL('../../assets/logo_build/base.glb', import.meta.url).href,
|
||||
// @ts-ignore
|
||||
// new URL('../../assets/logo_build/location.glb', import.meta.url).href,
|
||||
// @ts-ignore
|
||||
// new URL('../../assets/logo_build/img.jpg.glb', import.meta.url).href,
|
||||
// Websocket URLs automatically listen for new models from the python backend
|
||||
'<auto>', // Get the default preload URL if not overridden
|
||||
],
|
||||
loadHelpers: true,
|
||||
edgeWidth: 0, /* The default line size for edges, set to 0 to use basic gl.LINEs */
|
||||
displayLoadingEveryMs: 1000, /* How often to display partially loaded models */
|
||||
monitorEveryMs: 100,
|
||||
monitorOpenTimeoutMs: 1000,
|
||||
// ModelViewer settings
|
||||
autoplay: true, // Global animation toggle
|
||||
arModes: 'webxr scene-viewer quick-look',
|
||||
zoomSensitivity: 0.25,
|
||||
orbitSensitivity: 1,
|
||||
panSensitivity: 1,
|
||||
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,9 +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);
|
||||
})
|
||||
@@ -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]});
|
||||
|
||||
@@ -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]]);
|
||||
|
||||
@@ -78,7 +78,7 @@ export function hitToSelectionInfo(hit: Intersection<MObject3D>): SelectionInfo
|
||||
|
||||
function hitFaceTriangleIndices(hit: Intersection<MObject3D>): [number, number] | null {
|
||||
let faceTrianglesEnd = hit?.object?.geometry?.userData?.face_triangles_end;
|
||||
if (hit.faceIndex === undefined) return null;
|
||||
if (!hit.faceIndex) return null;
|
||||
if (!faceTrianglesEnd) { // Fallback to selecting the whole imported mesh
|
||||
//console.log("No face_triangles_end found, selecting the whole mesh");
|
||||
return [0, (hit.object.geometry.index ?? hit.object.geometry.attributes.position).count];
|
||||
|
||||
@@ -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>
|
||||
|
||||
42
package.json
42
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "yet-another-cad-viewer",
|
||||
"version": "0.8.11",
|
||||
"version": "0.9.4",
|
||||
"description": "",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
@@ -15,33 +15,33 @@
|
||||
"update-licenses": "generate-license-file --input package.json --output assets/licenses.txt --overwrite"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gltf-transform/core": "^4.0.4",
|
||||
"@gltf-transform/extensions": "^4.0.8",
|
||||
"@gltf-transform/functions": "^4.0.8",
|
||||
"@google/model-viewer": "^3.5.0",
|
||||
"@gltf-transform/core": "^4.1.0",
|
||||
"@gltf-transform/extensions": "^4.1.0",
|
||||
"@gltf-transform/functions": "^4.1.0",
|
||||
"@google/model-viewer": "^4.0.0",
|
||||
"@jamescoyle/vue-icon": "^0.1.2",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@mdi/svg": "^7.4.47",
|
||||
"three": "^0.163.0",
|
||||
"three-mesh-bvh": "^0.8.0",
|
||||
"three": "^0.173.0",
|
||||
"three-mesh-bvh": "^0.9.0",
|
||||
"three-orientation-gizmo": "https://github.com/jrj2211/three-orientation-gizmo",
|
||||
"vue": "^3.5.7",
|
||||
"vuetify": "^3.7.2"
|
||||
"vue": "^3.5.13",
|
||||
"vuetify": "^3.7.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/node": "^22.5.5",
|
||||
"@types/three": "^0.163.0",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"@types/node": "^22.9.3",
|
||||
"@types/three": "^0.173.0",
|
||||
"@vitejs/plugin-vue": "^5.2.0",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.0",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"buffer": "^5.5.0||^6.0.0",
|
||||
"commander": "^12.0.0",
|
||||
"generate-license-file": "^3.5.1",
|
||||
"npm-run-all2": "^6.2.3",
|
||||
"terser": "^5.33.0",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.7",
|
||||
"vue-tsc": "^2.1.6"
|
||||
"commander": "^13.0.0",
|
||||
"generate-license-file": "^3.6.0",
|
||||
"npm-run-all2": "^7.0.1",
|
||||
"terser": "^5.36.0",
|
||||
"typescript": "~5.7.0",
|
||||
"vite": "^6.0.0",
|
||||
"vue-tsc": "^2.1.10"
|
||||
}
|
||||
}
|
||||
|
||||
1213
poetry.lock
generated
1213
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "yacv-server"
|
||||
version = "0.8.11"
|
||||
version = "0.9.4"
|
||||
description = "Yet Another CAD Viewer (server)"
|
||||
authors = ["Yeicor <4929005+Yeicor@users.noreply.github.com>"]
|
||||
license = "MIT"
|
||||
@@ -11,14 +11,14 @@ include = [
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
python = ">=3.10,<3.13" # Due to vtk transitive dependency of build123d -> cadquery-ocp -> vtk
|
||||
|
||||
# CAD
|
||||
build123d = ">=0.5,<0.8"
|
||||
build123d = ">=0.9,<0.10"
|
||||
|
||||
# Misc
|
||||
pygltflib = "^1.16.2"
|
||||
pillow = "^10.2.0"
|
||||
pillow = ">=10.2,<12.0"
|
||||
|
||||
[tool.poetry.build]
|
||||
generate-setup-file = false
|
||||
|
||||
@@ -10,12 +10,30 @@ 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
|
||||
from build123d import Compound, Shape, Color
|
||||
|
||||
from yacv_server.gltf import GLTFMgr
|
||||
|
||||
CADCoreLike = Union[TopoDS_Shape, TopLoc_Location] # Faces, Edges, Vertices and Locations for now
|
||||
CADLike = Union[CADCoreLike, any] # build123d and cadquery types
|
||||
ColorTuple = Tuple[float, float, float, float]
|
||||
|
||||
|
||||
def get_color(obj: any) -> Optional[ColorTuple]:
|
||||
"""Get color from a CAD Object or any other color-like object"""
|
||||
if 'color' in dir(obj):
|
||||
obj = obj.color
|
||||
if isinstance(obj, tuple):
|
||||
c = None
|
||||
if len(obj) == 3:
|
||||
c = obj + (1,)
|
||||
elif len(obj) == 4:
|
||||
c = obj
|
||||
# noinspection PyTypeChecker
|
||||
return [min(max(float(x), 0), 1) for x in c]
|
||||
if isinstance(obj, Color):
|
||||
return obj.to_tuple()
|
||||
return None
|
||||
|
||||
|
||||
def get_shape(obj: CADLike, error: bool = True) -> Optional[CADCoreLike]:
|
||||
|
||||
@@ -8,6 +8,12 @@ _checkerboard_image_bytes = base64.decodebytes(
|
||||
b'iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAF0lEQVQI12N49OjR////Gf'
|
||||
b'/////48WMATwULS8tcyj8AAAAASUVORK5CYII=')
|
||||
|
||||
def get_version() -> str:
|
||||
try:
|
||||
return importlib.metadata.version("yacv_server")
|
||||
except importlib.metadata.PackageNotFoundError:
|
||||
return "unknown"
|
||||
|
||||
|
||||
class GLTFMgr:
|
||||
"""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')):
|
||||
self.gltf = GLTF2(
|
||||
asset=Asset(generator=f"yacv_server@{importlib.metadata.version('yacv_server')}"),
|
||||
asset=Asset(generator=f"yacv_server@{get_version()}"),
|
||||
scene=0,
|
||||
scenes=[Scene(nodes=[0])],
|
||||
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]
|
||||
|
||||
def add_face(self, vertices_raw: List[Vector], indices_raw: List[Tuple[int, int, int]],
|
||||
tex_coord_raw: List[Tuple[float, float]],
|
||||
color: Tuple[float, float, float, float] = (1.0, 0.75, 0.0, 1.0)):
|
||||
tex_coord_raw: List[Tuple[float, float]], color: Optional[Tuple[float, float, float, float]] = None):
|
||||
"""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 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"
|
||||
@@ -85,8 +91,9 @@ class GLTFMgr:
|
||||
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]]],
|
||||
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"""
|
||||
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.
|
||||
base_index = len(self.edge_positions) // 3
|
||||
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._edges_primitive.extras["edge_points_end"].append(len(self.edge_indices))
|
||||
|
||||
def add_vertex(self, vertex: Tuple[float, float, float],
|
||||
color: Tuple[float, float, float, float] = (0.1, 0.1, 0.1, 1.0)):
|
||||
def add_vertex(self, vertex: Tuple[float, float, float], color: Optional[Tuple[float, float, float, float]] = None):
|
||||
"""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
|
||||
self.vertex_indices.append(base_index)
|
||||
self.vertex_positions.extend(vertex)
|
||||
|
||||
@@ -16,18 +16,24 @@ def build_logo(text: bool = True) -> Dict[str, Union[Part, Location, str]]:
|
||||
text_at_plane = Plane.YZ
|
||||
text_at_plane.origin = faces().group_by(Axis.X)[-1].face().center()
|
||||
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)
|
||||
|
||||
# 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 *= Location((0, 0, 4e-2), (0, 0, 90)) # Avoid overlapping and adjust placement
|
||||
|
||||
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)
|
||||
|
||||
# Add an animated fox to the CAD part
|
||||
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__":
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import io
|
||||
import os
|
||||
import urllib.parse
|
||||
from http import HTTPStatus
|
||||
from http import HTTPStatus, HTTPMethod
|
||||
from http.server import SimpleHTTPRequestHandler
|
||||
|
||||
from yacv_server.mylogger import logger
|
||||
@@ -71,6 +71,19 @@ class HTTPHandler(SimpleHTTPRequestHandler):
|
||||
def _api_updates(self):
|
||||
"""Handles a publish-only websocket connection that send show_object events along with their hashes and URLs"""
|
||||
|
||||
self.send_response(HTTPStatus.OK)
|
||||
self.send_header("Content-Type", "text/event-stream")
|
||||
self.send_header("Cache-Control", "no-cache")
|
||||
if not self.requestline.startswith(HTTPMethod.HEAD):
|
||||
# Chunked transfer encoding!
|
||||
self.send_header("Transfer-Encoding", "chunked")
|
||||
else:
|
||||
self.send_header("Content-Length", "0")
|
||||
self.end_headers()
|
||||
|
||||
if self.requestline.startswith(HTTPMethod.HEAD):
|
||||
return
|
||||
|
||||
# Keep a shared read lock to know if any frontend is still working before shutting down
|
||||
with self.yacv.frontend_lock.r_locked():
|
||||
|
||||
@@ -81,13 +94,6 @@ class HTTPHandler(SimpleHTTPRequestHandler):
|
||||
self.yacv.at_least_one_client.set()
|
||||
logger.debug('Updates client connected')
|
||||
|
||||
self.send_response(HTTPStatus.OK)
|
||||
self.send_header("Content-Type", "text/event-stream")
|
||||
self.send_header("Cache-Control", "no-cache")
|
||||
# Chunked transfer encoding!
|
||||
self.send_header("Transfer-Encoding", "chunked")
|
||||
self.end_headers()
|
||||
|
||||
def write_chunk(_chunk_data: str):
|
||||
self.wfile.write(hex(len(_chunk_data))[2:].encode('utf-8'))
|
||||
self.wfile.write(b'\r\n')
|
||||
@@ -107,7 +113,7 @@ class HTTPHandler(SimpleHTTPRequestHandler):
|
||||
# noinspection PyUnresolvedReferences
|
||||
to_send = data.to_json()
|
||||
write_chunk(f'data: {to_send}\n\n')
|
||||
except BrokenPipeError: # Client disconnected normally
|
||||
except (BrokenPipeError, ConnectionResetError): # Client disconnected normally
|
||||
pass
|
||||
finally:
|
||||
subscription.close()
|
||||
|
||||
@@ -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.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 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.mylogger import logger
|
||||
|
||||
@@ -20,9 +20,14 @@ def tessellate(
|
||||
faces: bool = True,
|
||||
edges: bool = True,
|
||||
vertices: bool = True,
|
||||
obj_color: Optional[ColorTuple] = None,
|
||||
texture: Optional[Tuple[bytes, str]] = None,
|
||||
) -> GLTF2:
|
||||
"""Tessellate a whole shape into a list of triangle vertices and a list of triangle indices."""
|
||||
mgr = GLTFMgr()
|
||||
if texture is None:
|
||||
mgr = GLTFMgr()
|
||||
else:
|
||||
mgr = GLTFMgr(texture)
|
||||
|
||||
if isinstance(cad_like, TopLoc_Location):
|
||||
mgr.add_location(Location(cad_like))
|
||||
@@ -33,22 +38,26 @@ def tessellate(
|
||||
# Perform tessellation tasks
|
||||
edge_to_faces: Dict[str, List[TopoDS_Face]] = {}
|
||||
vertex_to_faces: Dict[str, List[TopoDS_Face]] = {}
|
||||
if faces:
|
||||
for face in shape.faces():
|
||||
_tessellate_face(mgr, face.wrapped, tolerance, angular_tolerance)
|
||||
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)
|
||||
if edges:
|
||||
for edge in face.edges():
|
||||
edge_to_faces[edge.wrapped] = edge_to_faces.get(edge.wrapped, []) + [face.wrapped]
|
||||
if vertices:
|
||||
for vertex in face.vertices():
|
||||
vertex_to_faces[vertex.wrapped] = vertex_to_faces.get(vertex.wrapped, []) + [face.wrapped]
|
||||
if edges:
|
||||
for edge in shape.edges():
|
||||
if len(shape_faces) > 0: obj_color = None # Don't color edges/vertices if faces are colored
|
||||
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)
|
||||
if vertices:
|
||||
angular_tolerance, obj_color)
|
||||
if len(shape_edges) > 0: obj_color = None # Don't color vertices if edges are colored
|
||||
if vertices and hasattr(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()
|
||||
|
||||
@@ -57,7 +66,8 @@ def _tessellate_face(
|
||||
mgr: GLTFMgr,
|
||||
ocp_face: TopoDS_Face,
|
||||
tolerance: float = 1e-3,
|
||||
angular_tolerance: float = 0.1
|
||||
angular_tolerance: float = 0.1,
|
||||
color: Optional[ColorTuple] = None,
|
||||
):
|
||||
face = Shape(ocp_face)
|
||||
# face.mesh(tolerance, angular_tolerance)
|
||||
@@ -75,7 +85,7 @@ def _tessellate_face(
|
||||
|
||||
vertices = tri_mesh[0]
|
||||
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]:
|
||||
@@ -100,6 +110,7 @@ def _tessellate_edge(
|
||||
faces: List[TopoDS_Face],
|
||||
angular_deflection: float = 0.1,
|
||||
curvature_deflection: float = 0.1,
|
||||
color: Optional[ColorTuple] = None,
|
||||
):
|
||||
# Use a curve discretizer to get the vertices
|
||||
curve = BRepAdaptor_Curve(ocp_edge)
|
||||
@@ -117,11 +128,12 @@ def _tessellate_edge(
|
||||
|
||||
# Convert strip of vertices to a list of pairs of vertices
|
||||
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()
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import atexit
|
||||
import base64
|
||||
import copy
|
||||
import inspect
|
||||
import os
|
||||
@@ -8,23 +9,25 @@ import threading
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from http.server import ThreadingHTTPServer
|
||||
from importlib.metadata import version
|
||||
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
|
||||
from build123d import Shape, Axis, Location, Vector, Color
|
||||
from dataclasses_json import dataclass_json
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
|
||||
from yacv_server.cad import _hashcode, ColorTuple, 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
|
||||
from yacv_server.mylogger import logger
|
||||
from yacv_server.pubsub import BufferedPubSub
|
||||
from yacv_server.rwlock import RWLock
|
||||
from yacv_server.tessellate import tessellate
|
||||
from yacv_server.cad import _hashcode
|
||||
|
||||
|
||||
@dataclass_json
|
||||
@@ -44,7 +47,7 @@ YACVSupported = Union[bytes, CADCoreLike]
|
||||
|
||||
class UpdatesApiFullData(UpdatesApiData):
|
||||
obj: YACVSupported
|
||||
"""The OCCT object, if any (not serialized)"""
|
||||
"""The OCCT object (not serialized)"""
|
||||
kwargs: Optional[Dict[str, any]]
|
||||
"""The show_object options, if any (not serialized)"""
|
||||
|
||||
@@ -88,6 +91,14 @@ class YACV:
|
||||
frontend_lock: RWLock
|
||||
"""Lock to ensure that the frontend has finished working before we shut down"""
|
||||
|
||||
texture: Optional[Tuple[bytes, str]]
|
||||
"""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>")`.
|
||||
The <uri> can be file:<path> or data:<mime>;base64,<data> where <mime> is the mime type and
|
||||
<data> is the base64 encoded image."""
|
||||
|
||||
def __init__(self):
|
||||
self.server_thread = None
|
||||
self.server = None
|
||||
@@ -98,7 +109,8 @@ class YACV:
|
||||
self.at_least_one_client = threading.Event()
|
||||
self.shutting_down = threading.Event()
|
||||
self.frontend_lock = RWLock()
|
||||
logger.info('Using yacv-server v%s', version('yacv-server'))
|
||||
self.texture = _read_texture_uri(os.getenv("YACV_BASE_TEXTURE"))
|
||||
logger.info('Using yacv-server v%s', get_version())
|
||||
|
||||
def start(self):
|
||||
"""Starts the web server in the background"""
|
||||
@@ -165,12 +177,32 @@ class YACV:
|
||||
self.server.serve_forever()
|
||||
|
||||
def show(self, *objs: List[YACVSupported], names: Optional[Union[str, List[str]]] = None, **kwargs):
|
||||
"""
|
||||
Shows the given CAD objects in the frontend. The objects will be tessellated and converted to GLTF. Optionally,
|
||||
the following keyword arguments can be used:
|
||||
|
||||
- auto_clear: Whether to clear the previous objects before showing the new ones (default: True)
|
||||
- texture: The texture to use for the faces of the object (see `YACV.texture` for more info)
|
||||
- color: The default color to use for the objects (can be overridden by the `color` attribute of each object)
|
||||
- tolerance: The tolerance for tessellating the object (default: 0.1)
|
||||
- angular_tolerance: The angular tolerance for tessellating the object (default: 0.1)
|
||||
- faces: Whether to tessellate and show the faces of the object (default: True)
|
||||
- edges: Whether to tessellate and show the edges of the object (default: True)
|
||||
- vertices: Whether to tessellate and show the vertices of the object (default: True)
|
||||
|
||||
:param objs: The CAD objects to show. Can be CAD-like objects (solids, locations, etc.) or bytes (GLTF) objects.
|
||||
:param names: The names of the objects. If None, the variable names will be used (if possible). The number of
|
||||
names must match the number of objects. An object of the same name will be replaced in the frontend.
|
||||
:param kwargs: Additional options for the show_object event.
|
||||
"""
|
||||
# Prepare the arguments
|
||||
start = time.time()
|
||||
names = names or [_find_var_name(obj) for obj in objs]
|
||||
if isinstance(names, str):
|
||||
names = [names]
|
||||
assert len(names) == len(objs), 'Number of names must match the number of objects'
|
||||
if 'color' in kwargs:
|
||||
kwargs['color'] = get_color(kwargs['color'])
|
||||
|
||||
# Handle auto clearing of previous objects
|
||||
if kwargs.get('auto_clear', True):
|
||||
@@ -185,6 +217,10 @@ class YACV:
|
||||
|
||||
# Publish the show event
|
||||
for obj, name in zip(objs, names):
|
||||
obj_color = get_color(obj)
|
||||
if obj_color is not None:
|
||||
kwargs = kwargs.copy()
|
||||
kwargs['color'] = obj_color
|
||||
if not isinstance(obj, bytes):
|
||||
obj = _preprocess_cad(obj, **kwargs)
|
||||
_hash = _hashcode(obj, **kwargs)
|
||||
@@ -194,7 +230,7 @@ class YACV:
|
||||
logger.info('show %s took %.3f seconds', names, time.time() - start)
|
||||
|
||||
def show_cad_all(self, **kwargs):
|
||||
"""Publishes all CAD objects in the current scope to the server"""
|
||||
"""Publishes all CAD objects in the current scope to the server. See `show` for more details."""
|
||||
all_cad = list(grab_all_cad()) # List for reproducible iteration order
|
||||
self.show(*[cad for _, cad in all_cad], names=[name for name, _ in all_cad], **kwargs)
|
||||
|
||||
@@ -273,11 +309,17 @@ class YACV:
|
||||
if isinstance(event.obj, bytes): # Already a GLTF
|
||||
publish_to.publish(event.obj)
|
||||
else: # CAD object to tessellate and convert to GLTF
|
||||
texture_override_uri = event.kwargs.get('texture', None)
|
||||
texture_override = None
|
||||
if isinstance(texture_override_uri, str):
|
||||
texture_override = _read_texture_uri(texture_override_uri)
|
||||
gltf = tessellate(event.obj, tolerance=event.kwargs.get('tolerance', 0.1),
|
||||
angular_tolerance=event.kwargs.get('angular_tolerance', 0.1),
|
||||
faces=event.kwargs.get('faces', True),
|
||||
edges=event.kwargs.get('edges', True),
|
||||
vertices=event.kwargs.get('vertices', True))
|
||||
vertices=event.kwargs.get('vertices', True),
|
||||
obj_color=event.kwargs.get('color', None),
|
||||
texture=texture_override or self.texture)
|
||||
glb_list_of_bytes = gltf.save_to_bytes()
|
||||
glb_bytes = b''.join(glb_list_of_bytes)
|
||||
publish_to.publish(glb_bytes)
|
||||
@@ -301,6 +343,25 @@ class YACV:
|
||||
f.write(self.export(name)[0])
|
||||
|
||||
|
||||
def _read_texture_uri(uri: str) -> Optional[Tuple[bytes, str]]:
|
||||
if uri is None:
|
||||
return None
|
||||
if uri.startswith("file:"):
|
||||
path = uri[len("file:"):]
|
||||
with open(path, 'rb') as f:
|
||||
data = f.read()
|
||||
buf = BytesIO(data)
|
||||
img = Image.open(buf)
|
||||
mtype = img.get_format_mimetype()
|
||||
return data, mtype
|
||||
if uri.startswith("data:"): # https://en.wikipedia.org/wiki/Data_URI_scheme#Syntax (limited)
|
||||
mtype_and_data = uri[len("data:"):]
|
||||
mtype = mtype_and_data.split(";", 1)[0]
|
||||
data_str = mtype_and_data.split(",", 1)[1]
|
||||
data = base64.b64decode(data_str)
|
||||
return data, mtype
|
||||
return None
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def _preprocess_cad(obj: CADLike, **kwargs) -> CADCoreLike:
|
||||
# Get the shape of a CAD-like object
|
||||
|
||||
Reference in New Issue
Block a user