mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 22:24:17 +01:00
Merge pull request #242 from andyross/vis-defaults
Update visual defaults
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"append": [
|
"append": [
|
||||||
"assets/fox.glb.license",
|
"assets/fox.glb.license",
|
||||||
|
"assets/qwantani_afternoon_1k.hdr.license",
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"replace": {
|
"replace": {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ https://www.npmjs.com/package/generate-license-file
|
|||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- @google/model-viewer@4.0.0
|
- @google/model-viewer@4.1.0
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
@@ -522,7 +522,7 @@ Apache License
|
|||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- detect-libc@2.0.3
|
- detect-libc@2.0.4
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
@@ -1045,9 +1045,9 @@ END OF TERMS AND CONDITIONS
|
|||||||
|
|
||||||
The following npm packages may be included in this product:
|
The following npm packages may be included in this product:
|
||||||
|
|
||||||
- @lit/reactive-element@1.6.3
|
- @lit/reactive-element@2.1.1
|
||||||
- lit-element@3.3.3
|
- lit-element@4.2.1
|
||||||
- lit@2.8.0
|
- lit@3.3.1
|
||||||
|
|
||||||
These packages each contain the following license:
|
These packages each contain the following license:
|
||||||
|
|
||||||
@@ -1084,7 +1084,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- lit-html@2.8.0
|
- lit-html@3.3.1
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
@@ -1121,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:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- @lit-labs/ssr-dom-shim@1.3.0
|
- @lit-labs/ssr-dom-shim@1.4.0
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
@@ -1247,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:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- @babel/parser@7.27.0
|
- @babel/parser@7.28.0
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
@@ -1381,6 +1381,34 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|||||||
|
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
|
- @jridgewell/sourcemap-codec@1.5.4
|
||||||
|
|
||||||
|
This package contains the following license:
|
||||||
|
|
||||||
|
Copyright 2024 Justin Ridgewell <justin@ridgewell.name>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
-----------
|
||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- picocolors@1.1.1
|
- picocolors@1.1.1
|
||||||
@@ -1418,9 +1446,9 @@ LGPL-3.0-or-later
|
|||||||
|
|
||||||
The following npm packages may be included in this product:
|
The following npm packages may be included in this product:
|
||||||
|
|
||||||
- @babel/helper-string-parser@7.25.9
|
- @babel/helper-string-parser@7.27.1
|
||||||
- @babel/helper-validator-identifier@7.25.9
|
- @babel/helper-validator-identifier@7.27.1
|
||||||
- @babel/types@7.27.0
|
- @babel/types@7.28.1
|
||||||
|
|
||||||
These packages each contain the following license:
|
These packages each contain the following license:
|
||||||
|
|
||||||
@@ -1451,7 +1479,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- three-mesh-bvh@0.9.0
|
- three-mesh-bvh@0.9.1
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
@@ -1601,7 +1629,7 @@ The MIT license applies to all non-font and non-icon files.
|
|||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- semver@7.7.1
|
- semver@7.7.2
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
@@ -1623,36 +1651,6 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|||||||
|
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
The following npm package may be included in this product:
|
|
||||||
|
|
||||||
- @jridgewell/sourcemap-codec@1.5.0
|
|
||||||
|
|
||||||
This package contains the following license:
|
|
||||||
|
|
||||||
The MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2015 Rich Harris
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
-----------
|
|
||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- three@0.125.2
|
- three@0.125.2
|
||||||
@@ -1685,7 +1683,7 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- three@0.175.0
|
- three@0.178.0
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
@@ -1775,13 +1773,13 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- vuetify@3.8.0
|
- vuetify@3.9.0
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016-2023 John Jeremy Leider
|
Copyright (c) 2016-now Vuetify, LLC
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -1805,16 +1803,16 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
The following npm packages may be included in this product:
|
The following npm packages may be included in this product:
|
||||||
|
|
||||||
- @vue/compiler-core@3.5.13
|
- @vue/compiler-core@3.5.17
|
||||||
- @vue/compiler-dom@3.5.13
|
- @vue/compiler-dom@3.5.17
|
||||||
- @vue/compiler-sfc@3.5.13
|
- @vue/compiler-sfc@3.5.17
|
||||||
- @vue/compiler-ssr@3.5.13
|
- @vue/compiler-ssr@3.5.17
|
||||||
- @vue/reactivity@3.5.13
|
- @vue/reactivity@3.5.17
|
||||||
- @vue/runtime-core@3.5.13
|
- @vue/runtime-core@3.5.17
|
||||||
- @vue/runtime-dom@3.5.13
|
- @vue/runtime-dom@3.5.17
|
||||||
- @vue/server-renderer@3.5.13
|
- @vue/server-renderer@3.5.17
|
||||||
- @vue/shared@3.5.13
|
- @vue/shared@3.5.17
|
||||||
- vue@3.5.13
|
- vue@3.5.17
|
||||||
|
|
||||||
These packages each contain the following license:
|
These packages each contain the following license:
|
||||||
|
|
||||||
@@ -1844,7 +1842,7 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- ktx-parse@1.0.0
|
- ktx-parse@1.0.1
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
@@ -1906,9 +1904,9 @@ SOFTWARE.
|
|||||||
|
|
||||||
The following npm packages may be included in this product:
|
The following npm packages may be included in this product:
|
||||||
|
|
||||||
- @gltf-transform/core@4.1.3
|
- @gltf-transform/core@4.2.0
|
||||||
- @gltf-transform/extensions@4.1.3
|
- @gltf-transform/extensions@4.2.0
|
||||||
- @gltf-transform/functions@4.1.3
|
- @gltf-transform/functions@4.2.0
|
||||||
|
|
||||||
These packages each contain the following license:
|
These packages each contain the following license:
|
||||||
|
|
||||||
@@ -1968,7 +1966,7 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- postcss@8.5.3
|
- postcss@8.5.6
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
@@ -2049,6 +2047,11 @@ glTF conversion by @AsoboStudio and @scurest
|
|||||||
|
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
CC0: Qwantani Afternoon by Greg Zaal (Photography) and Jarod Guest (Processing)
|
||||||
|
https://polyhaven.com/a/qwantani_afternoon
|
||||||
|
|
||||||
|
-----------
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2024 Yeicor
|
Copyright (c) 2024 Yeicor
|
||||||
|
|||||||
BIN
assets/qwantani_afternoon_1k.hdr
Normal file
BIN
assets/qwantani_afternoon_1k.hdr
Normal file
Binary file not shown.
2
assets/qwantani_afternoon_1k.hdr.license
Normal file
2
assets/qwantani_afternoon_1k.hdr.license
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
CC0: Qwantani Afternoon by Greg Zaal (Photography) and Jarod Guest (Processing)
|
||||||
|
https://polyhaven.com/a/qwantani_afternoon
|
||||||
@@ -29,7 +29,14 @@ export async function settings() {
|
|||||||
panSensitivity: 1,
|
panSensitivity: 1,
|
||||||
exposure: 1,
|
exposure: 1,
|
||||||
shadowIntensity: 0,
|
shadowIntensity: 0,
|
||||||
background: '',
|
// Nice low-res outdoor/high-contrast HDRI image (CC0 licensed) for lighting
|
||||||
|
background: new URL('../../assets/qwantani_afternoon_1k.hdr', import.meta.url).href,
|
||||||
|
// Uniform (1x1 pixel) medium gray background for visibility (following dark/light mode)
|
||||||
|
skybox: (window.matchMedia("(prefers-color-scheme: dark)").matches ?
|
||||||
|
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEU4ODiyn42XAAAACklEQVQI" +
|
||||||
|
"12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==" :
|
||||||
|
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEW6urpaLVq8AAAACklEQVQI" +
|
||||||
|
"12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=="),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Auto-override any settings from the URL
|
// Auto-override any settings from the URL
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ watch(disableTap, (newDisableTap) => {
|
|||||||
<model-viewer ref="elem" v-if="sett != null" :ar="sett.arModes.length > 0" :ar-modes="sett.arModes"
|
<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"
|
:environment-image="sett.background" :exposure="sett.exposure" :autoplay="sett.autoplay"
|
||||||
:orbit-sensitivity="sett.orbitSensitivity" :pan-sensitivity="sett.panSensitivity"
|
:orbit-sensitivity="sett.orbitSensitivity" :pan-sensitivity="sett.panSensitivity"
|
||||||
:poster="poster" :shadow-intensity="sett.shadowIntensity" :skybox-image="sett.background"
|
:poster="poster" :shadow-intensity="sett.shadowIntensity" :skybox-image="sett.skybox"
|
||||||
:src="props.src" :zoom-sensitivity="sett.zoomSensitivity" alt="The 3D model(s)" camera-controls
|
: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"
|
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%">
|
min-camera-orbit="-Infinity 0deg 5%" style="width: 100%; height: 100%">
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default defineConfig({
|
|||||||
build: {
|
build: {
|
||||||
assetsDir: '.', // Support deploying to a subdirectory using relative URLs
|
assetsDir: '.', // Support deploying to a subdirectory using relative URLs
|
||||||
cssCodeSplit: false, // Small enough to inline
|
cssCodeSplit: false, // Small enough to inline
|
||||||
chunkSizeWarningLimit: 550, // Three.js is big. Draco is even bigger but not likely to be used.
|
chunkSizeWarningLimit: 1024, // Three.js is big. Draco is even bigger but not likely to be used.
|
||||||
sourcemap: true, // For debugging production
|
sourcemap: true, // For debugging production
|
||||||
},
|
},
|
||||||
define: {
|
define: {
|
||||||
|
|||||||
@@ -78,7 +78,8 @@ def get_shape(obj: CADLike, error: bool = True) -> Optional[CADCoreLike]:
|
|||||||
# Sorting is required to improve hashcode consistency
|
# Sorting is required to improve hashcode consistency
|
||||||
shapes_raw_filtered_sorted = sorted(shapes_raw_filtered, key=lambda x: _hashcode(x))
|
shapes_raw_filtered_sorted = sorted(shapes_raw_filtered, key=lambda x: _hashcode(x))
|
||||||
# Build a single compound shape (skip locations/axes here, they can't be in a Compound)
|
# Build a single compound shape (skip locations/axes here, they can't be in a Compound)
|
||||||
shapes_bd = [Compound(shape) for shape in shapes_raw_filtered_sorted if shape is not None and not isinstance(shape, TopLoc_Location)]
|
shapes_bd = [Compound(shape) for shape in shapes_raw_filtered_sorted if
|
||||||
|
shape is not None and not isinstance(shape, TopLoc_Location)]
|
||||||
return get_shape(Compound(shapes_bd), error)
|
return get_shape(Compound(shapes_bd), error)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
@@ -168,7 +169,7 @@ def image_to_gltf(source: str | bytes, center: any, width: Optional[float] = Non
|
|||||||
vert(plane.origin + plane.x_dir * width / 2 + plane.y_dir * height / 2),
|
vert(plane.origin + plane.x_dir * width / 2 + plane.y_dir * height / 2),
|
||||||
vert(plane.origin + plane.x_dir * width / 2 - plane.y_dir * height / 2),
|
vert(plane.origin + plane.x_dir * width / 2 - plane.y_dir * height / 2),
|
||||||
vert(plane.origin - plane.x_dir * width / 2 - plane.y_dir * height / 2),
|
vert(plane.origin - plane.x_dir * width / 2 - plane.y_dir * height / 2),
|
||||||
], [
|
], [vert(plane.z_dir)] * 4, [
|
||||||
(0, 2, 1),
|
(0, 2, 1),
|
||||||
(0, 3, 2),
|
(0, 3, 2),
|
||||||
], [
|
], [
|
||||||
|
|||||||
@@ -4,9 +4,6 @@ import numpy as np
|
|||||||
from build123d import Location, Plane, Vector
|
from build123d import Location, Plane, Vector
|
||||||
from pygltflib import *
|
from pygltflib import *
|
||||||
|
|
||||||
_checkerboard_image_bytes = base64.decodebytes(
|
|
||||||
b'iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAF0lEQVQI12N49OjR////Gf'
|
|
||||||
b'/////48WMATwULS8tcyj8AAAAASUVORK5CYII=')
|
|
||||||
|
|
||||||
def get_version() -> str:
|
def get_version() -> str:
|
||||||
try:
|
try:
|
||||||
@@ -24,6 +21,7 @@ class GLTFMgr:
|
|||||||
# - Face data
|
# - Face data
|
||||||
face_indices: List[int] # 3 indices per triangle
|
face_indices: List[int] # 3 indices per triangle
|
||||||
face_positions: List[float] # x, y, z
|
face_positions: List[float] # x, y, z
|
||||||
|
face_normals: List[float] # x, y, z
|
||||||
face_tex_coords: List[float] # u, v
|
face_tex_coords: List[float] # u, v
|
||||||
face_colors: List[float] # r, g, b, a
|
face_colors: List[float] # r, g, b, a
|
||||||
image: Optional[Tuple[bytes, str]] # image/png
|
image: Optional[Tuple[bytes, str]] # image/png
|
||||||
@@ -36,7 +34,7 @@ class GLTFMgr:
|
|||||||
vertex_positions: List[float] # x, y, z
|
vertex_positions: List[float] # x, y, z
|
||||||
vertex_colors: List[float] # r, g, b, a
|
vertex_colors: List[float] # r, g, b, a
|
||||||
|
|
||||||
def __init__(self, image: Optional[Tuple[bytes, str]] = (_checkerboard_image_bytes, 'image/png')):
|
def __init__(self, image: Optional[Tuple[bytes, str]] = None):
|
||||||
self.gltf = GLTF2(
|
self.gltf = GLTF2(
|
||||||
asset=Asset(generator=f"yacv_server@{get_version()}"),
|
asset=Asset(generator=f"yacv_server@{get_version()}"),
|
||||||
scene=0,
|
scene=0,
|
||||||
@@ -54,6 +52,7 @@ class GLTFMgr:
|
|||||||
)
|
)
|
||||||
self.face_indices = []
|
self.face_indices = []
|
||||||
self.face_positions = []
|
self.face_positions = []
|
||||||
|
self.face_normals = []
|
||||||
self.face_tex_coords = []
|
self.face_tex_coords = []
|
||||||
self.face_colors = []
|
self.face_colors = []
|
||||||
self.image = image
|
self.image = image
|
||||||
@@ -76,24 +75,23 @@ class GLTFMgr:
|
|||||||
def _vertices_primitive(self) -> Primitive:
|
def _vertices_primitive(self) -> Primitive:
|
||||||
return [p for p in self.gltf.meshes[0].primitives if p.mode == POINTS][0]
|
return [p for p in self.gltf.meshes[0].primitives if p.mode == POINTS][0]
|
||||||
|
|
||||||
def add_face(self, vertices_raw: List[Vector], indices_raw: List[Tuple[int, int, int]],
|
def add_face(self, vertices_raw: List[Vector], normals: List[Vector], indices_raw: List[Tuple[int, int, int]],
|
||||||
tex_coord_raw: List[Tuple[float, float]], color: Optional[Tuple[float, float, float, float]] = None):
|
tex_coord_raw: List[Tuple[float, float]], color: Tuple[float, float, float, float]):
|
||||||
"""Add a face to the GLTF mesh"""
|
"""Add a face to the GLTF mesh"""
|
||||||
if color is None: color = (1.0, 0.75, 0.0, 1.0)
|
|
||||||
# assert len(vertices_raw) == len(tex_coord_raw), f"Vertices and texture coordinates have different lengths"
|
# assert len(vertices_raw) == len(tex_coord_raw), f"Vertices and texture coordinates have different lengths"
|
||||||
# assert min([i for t in indices_raw for i in t]) == 0, f"Face indices start at {min(indices_raw)}"
|
# assert min([i for t in indices_raw for i in t]) == 0, f"Face indices start at {min(indices_raw)}"
|
||||||
# assert max([e for t in indices_raw for e in t]) < len(vertices_raw), f"Indices have non-existing vertices"
|
# assert max([e for t in indices_raw for e in t]) < len(vertices_raw), f"Indices have non-existing vertices"
|
||||||
base_index = len(self.face_positions) // 3 # All the new indices reference the new vertices
|
base_index = len(self.face_positions) // 3 # All the new indices reference the new vertices
|
||||||
self.face_indices.extend([base_index + i for t in indices_raw for i in t])
|
self.face_indices.extend([base_index + i for t in indices_raw for i in t])
|
||||||
self.face_positions.extend([v for t in vertices_raw for v in t])
|
self.face_positions.extend([v for t in vertices_raw for v in t])
|
||||||
|
self.face_normals.extend([n for t in normals for n in t])
|
||||||
self.face_tex_coords.extend([c for t in tex_coord_raw for c in t])
|
self.face_tex_coords.extend([c for t in tex_coord_raw for c in t])
|
||||||
self.face_colors.extend([col for _ in range(len(vertices_raw)) for col in color])
|
self.face_colors.extend([col for _ in range(len(vertices_raw)) for col in color])
|
||||||
self._faces_primitive.extras["face_triangles_end"].append(len(self.face_indices))
|
self._faces_primitive.extras["face_triangles_end"].append(len(self.face_indices))
|
||||||
|
|
||||||
def add_edge(self, vertices_raw: List[Tuple[Tuple[float, float, float], Tuple[float, float, float]]],
|
def add_edge(self, vertices_raw: List[Tuple[Tuple[float, float, float], Tuple[float, float, float]]],
|
||||||
color: Optional[Tuple[float, float, float, float]] = None):
|
color: Tuple[float, float, float, float]):
|
||||||
"""Add an edge to the GLTF mesh"""
|
"""Add an edge to the GLTF mesh"""
|
||||||
if color is None: color = (0.1, 0.1, 1.0, 1.0)
|
|
||||||
vertices_flat = [v for t in vertices_raw for v in t] # Line from 0 to 1, 2 to 3, 4 to 5, etc.
|
vertices_flat = [v for t in vertices_raw for v in t] # Line from 0 to 1, 2 to 3, 4 to 5, etc.
|
||||||
base_index = len(self.edge_positions) // 3
|
base_index = len(self.edge_positions) // 3
|
||||||
self.edge_indices.extend([base_index + i for i in range(len(vertices_flat))])
|
self.edge_indices.extend([base_index + i for i in range(len(vertices_flat))])
|
||||||
@@ -101,9 +99,8 @@ class GLTFMgr:
|
|||||||
self.edge_colors.extend([col for _ in range(len(vertices_flat)) for col in color])
|
self.edge_colors.extend([col for _ in range(len(vertices_flat)) for col in color])
|
||||||
self._edges_primitive.extras["edge_points_end"].append(len(self.edge_indices))
|
self._edges_primitive.extras["edge_points_end"].append(len(self.edge_indices))
|
||||||
|
|
||||||
def add_vertex(self, vertex: Tuple[float, float, float], color: Optional[Tuple[float, float, float, float]] = None):
|
def add_vertex(self, vertex: Tuple[float, float, float], color: Tuple[float, float, float, float]):
|
||||||
"""Add a vertex to the GLTF mesh"""
|
"""Add a vertex to the GLTF mesh"""
|
||||||
if color is None: color = (0.1, 0.1, 0.1, 1.0)
|
|
||||||
base_index = len(self.vertex_positions) // 3
|
base_index = len(self.vertex_positions) // 3
|
||||||
self.vertex_indices.append(base_index)
|
self.vertex_indices.append(base_index)
|
||||||
self.vertex_positions.extend(vertex)
|
self.vertex_positions.extend(vertex)
|
||||||
@@ -117,10 +114,11 @@ class GLTFMgr:
|
|||||||
return v.X, v.Y, v.Z
|
return v.X, v.Y, v.Z
|
||||||
|
|
||||||
# Add 1 origin vertex and 3 edges with custom colors to identify the X, Y and Z axis
|
# Add 1 origin vertex and 3 edges with custom colors to identify the X, Y and Z axis
|
||||||
self.add_vertex(vert(pl.origin))
|
# The colors are hardcoded. You can add vertices and edges manually to change them.
|
||||||
self.add_edge([(vert(pl.origin), vert(pl.origin + pl.x_dir))], color=(0.97, 0.24, 0.24, 1))
|
self.add_vertex(vert(pl.origin), color=(0.1, 0.1, 0.1, 1.0))
|
||||||
self.add_edge([(vert(pl.origin), vert(pl.origin + pl.y_dir))], color=(0.42, 0.8, 0.15, 1))
|
self.add_edge([(vert(pl.origin), vert(pl.origin + pl.x_dir))], color=(0.97, 0.24, 0.24, 1.0))
|
||||||
self.add_edge([(vert(pl.origin), vert(pl.origin + pl.z_dir))], color=(0.09, 0.55, 0.94, 1))
|
self.add_edge([(vert(pl.origin), vert(pl.origin + pl.y_dir))], color=(0.42, 0.8, 0.15, 1.0))
|
||||||
|
self.add_edge([(vert(pl.origin), vert(pl.origin + pl.z_dir))], color=(0.09, 0.55, 0.94, 1.0))
|
||||||
|
|
||||||
def build(self) -> GLTF2:
|
def build(self) -> GLTF2:
|
||||||
"""Merge the intermediate data into the GLTF object and return it"""
|
"""Merge the intermediate data into the GLTF object and return it"""
|
||||||
@@ -131,6 +129,8 @@ class GLTFMgr:
|
|||||||
buffers_list.append(_gen_buffer_metadata(self.face_indices, 1))
|
buffers_list.append(_gen_buffer_metadata(self.face_indices, 1))
|
||||||
self._faces_primitive.attributes.POSITION = len(buffers_list)
|
self._faces_primitive.attributes.POSITION = len(buffers_list)
|
||||||
buffers_list.append(_gen_buffer_metadata(self.face_positions, 3))
|
buffers_list.append(_gen_buffer_metadata(self.face_positions, 3))
|
||||||
|
self._faces_primitive.attributes.NORMAL = len(buffers_list)
|
||||||
|
buffers_list.append(_gen_buffer_metadata(self.face_normals, 3))
|
||||||
self._faces_primitive.attributes.TEXCOORD_0 = len(buffers_list)
|
self._faces_primitive.attributes.TEXCOORD_0 = len(buffers_list)
|
||||||
buffers_list.append(_gen_buffer_metadata(self.face_tex_coords, 2))
|
buffers_list.append(_gen_buffer_metadata(self.face_tex_coords, 2))
|
||||||
self._faces_primitive.attributes.COLOR_0 = len(buffers_list)
|
self._faces_primitive.attributes.COLOR_0 = len(buffers_list)
|
||||||
|
|||||||
@@ -6,24 +6,30 @@ from build123d import *
|
|||||||
ASSETS_DIR = os.getenv('ASSETS_DIR', os.path.join(os.path.dirname(__file__), '..', 'assets'))
|
ASSETS_DIR = os.getenv('ASSETS_DIR', os.path.join(os.path.dirname(__file__), '..', 'assets'))
|
||||||
|
|
||||||
|
|
||||||
def build_logo(text: bool = True) -> Dict[str, Union[Part, Location, str]]:
|
def build_logo(text: bool = True) -> Dict[str, Union[Compound, Location, str]]:
|
||||||
"""Builds the CAD part of the logo"""
|
"""Builds the CAD part of the logo"""
|
||||||
with BuildPart(Plane.XY.offset(50)) as logo_obj:
|
with BuildPart(Plane.XY.offset(50)) as logo_obj:
|
||||||
Box(22, 40, 30)
|
Box(22, 40, 30)
|
||||||
fillet(edges().filter_by(Axis.Y).group_by(Axis.Z)[-1], 10)
|
fillet(edges().filter_by(Axis.Y).group_by(Axis.Z)[-1], 10)
|
||||||
offset(solid(), 2, openings=faces().group_by(Axis.Z)[0] + faces().filter_by(Plane.XZ))
|
offset(solid(), 2, openings=faces().group_by(Axis.Z)[0] + faces().filter_by(Plane.XZ))
|
||||||
if text:
|
if text:
|
||||||
text_at_plane = Plane.YZ
|
with BuildSketch(Plane.YZ.move(Pos(faces().group_by(Axis.X)[-1].face().center()))):
|
||||||
text_at_plane.origin = faces().group_by(Axis.X)[-1].face().center()
|
|
||||||
with BuildSketch(text_at_plane.location):
|
|
||||||
Text('Yet Another\nCAD Viewer', 6, font_path='/usr/share/fonts/TTF/Hack-Regular.ttf')
|
Text('Yet Another\nCAD Viewer', 6, font_path='/usr/share/fonts/TTF/Hack-Regular.ttf')
|
||||||
extrude(amount=1)
|
extrude(amount=1)
|
||||||
|
logo_face_curved_front = faces().filter_by(GeomType.CYLINDER).group_by(Axis.X)[-1].face()
|
||||||
|
|
||||||
# Highlight text edges with a custom color
|
# Highlight text edges with a custom color
|
||||||
to_highlight = logo_obj.edges().group_by(Axis.X)[-1]
|
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 = Compound(to_highlight).translate((1e-3, 0, 0)) # To avoid z-fighting
|
||||||
logo_obj_hl.color = (0, 0.3, 0.3, 1)
|
logo_obj_hl.color = (0, 0.3, 0.3, 1)
|
||||||
|
|
||||||
|
# Highlight face with custom texture
|
||||||
|
logo_face_curved_front.yacv_texture = \
|
||||||
|
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAF0lEQVQI12N49OjR////Gf' \
|
||||||
|
'/////48WMATwULS8tcyj8AAAAASUVORK5CYII='
|
||||||
|
logo_face_curved_front.color = (0, 0.5, 0.0, 1)
|
||||||
|
logo_obj = Compound(logo_obj.faces() - ShapeList([logo_face_curved_front])) # Remove face from the main object
|
||||||
|
|
||||||
# Add a logo image to the CAD part
|
# Add a logo image to the CAD part
|
||||||
logo_img_location = logo_obj.faces().group_by(Axis.X)[0].face().center_location
|
logo_img_location = logo_obj.faces().group_by(Axis.X)[0].face().center_location
|
||||||
logo_img_location *= Location((0, 0, 4e-2), (0, 0, 90)) # Avoid overlapping and adjust placement
|
logo_img_location *= Location((0, 0, 4e-2), (0, 0, 90)) # Avoid overlapping and adjust placement
|
||||||
@@ -33,7 +39,8 @@ def build_logo(text: bool = True) -> Dict[str, Union[Part, Location, str]]:
|
|||||||
# Add an animated fox to the CAD part
|
# Add an animated fox to the CAD part
|
||||||
fox_glb_bytes = open(os.path.join(ASSETS_DIR, 'fox.glb'), 'rb').read()
|
fox_glb_bytes = open(os.path.join(ASSETS_DIR, 'fox.glb'), 'rb').read()
|
||||||
|
|
||||||
return {'fox': fox_glb_bytes, 'logo': logo_obj, 'logo_hl': logo_obj_hl, 'location': logo_img_location, img_name: img_glb_bytes}
|
return {'fox': fox_glb_bytes, 'logo': logo_obj, 'logo_hl': logo_obj_hl, 'logo_hl_tex': logo_face_curved_front,
|
||||||
|
'location': logo_img_location, img_name: img_glb_bytes}
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
from os import system
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Just a reminder that a hot-reloading server can be started with the following command:
|
|
||||||
# Need to disable auto-start to avoid conflicts with the hot-reloading server
|
|
||||||
system('YACV_DISABLE_SERVER=true aiohttp-devtools runserver __init__.py --port 32323 --app-factory _get_app')
|
|
||||||
@@ -3,9 +3,11 @@ from typing import List, Dict, Tuple, Optional
|
|||||||
from OCP.BRep import BRep_Tool
|
from OCP.BRep import BRep_Tool
|
||||||
from OCP.BRepAdaptor import BRepAdaptor_Curve
|
from OCP.BRepAdaptor import BRepAdaptor_Curve
|
||||||
from OCP.GCPnts import GCPnts_TangentialDeflection
|
from OCP.GCPnts import GCPnts_TangentialDeflection
|
||||||
|
from OCP.OCP.BRepLib import BRepLib_ToolTriangulatedShape
|
||||||
|
from OCP.OCP.TopAbs import TopAbs_Orientation
|
||||||
from OCP.TopLoc import TopLoc_Location
|
from OCP.TopLoc import TopLoc_Location
|
||||||
from OCP.TopoDS import TopoDS_Face, TopoDS_Edge, TopoDS_Shape, TopoDS_Vertex
|
from OCP.TopoDS import TopoDS_Face, TopoDS_Edge, TopoDS_Shape, TopoDS_Vertex
|
||||||
from build123d import Vertex, Face, Location, Compound
|
from build123d import Vertex, Face, Location, Compound, Vector
|
||||||
from pygltflib import GLTF2
|
from pygltflib import GLTF2
|
||||||
|
|
||||||
from yacv_server.cad import CADCoreLike, ColorTuple
|
from yacv_server.cad import CADCoreLike, ColorTuple
|
||||||
@@ -14,14 +16,9 @@ from yacv_server.mylogger import logger
|
|||||||
|
|
||||||
|
|
||||||
def tessellate(
|
def tessellate(
|
||||||
cad_like: CADCoreLike,
|
cad_like: CADCoreLike, color_faces: ColorTuple, color_edges: ColorTuple, color_vertices: ColorTuple,
|
||||||
tolerance: float = 0.1,
|
color_obj: Optional[ColorTuple] = None, tolerance: float = 0.1, angular_tolerance: float = 0.1,
|
||||||
angular_tolerance: float = 0.1,
|
faces: bool = True, edges: bool = True, vertices: bool = True, texture: Optional[Tuple[bytes, str]] = None,
|
||||||
faces: bool = True,
|
|
||||||
edges: bool = True,
|
|
||||||
vertices: bool = True,
|
|
||||||
obj_color: Optional[ColorTuple] = None,
|
|
||||||
texture: Optional[Tuple[bytes, str]] = None,
|
|
||||||
) -> GLTF2:
|
) -> GLTF2:
|
||||||
"""Tessellate a whole shape into a list of triangle vertices and a list of triangle indices."""
|
"""Tessellate a whole shape into a list of triangle vertices and a list of triangle indices."""
|
||||||
if texture is None:
|
if texture is None:
|
||||||
@@ -41,23 +38,24 @@ def tessellate(
|
|||||||
if faces and hasattr(shape, 'faces'):
|
if faces and hasattr(shape, 'faces'):
|
||||||
shape_faces = shape.faces()
|
shape_faces = shape.faces()
|
||||||
for face in shape_faces:
|
for face in shape_faces:
|
||||||
_tessellate_face(mgr, face.wrapped, tolerance, angular_tolerance, obj_color)
|
_tessellate_face(mgr, face.wrapped, color_obj or color_faces, tolerance, angular_tolerance)
|
||||||
if edges:
|
if edges:
|
||||||
for edge in face.edges():
|
for edge in face.edges():
|
||||||
edge_to_faces[edge.wrapped] = edge_to_faces.get(edge.wrapped, []) + [face.wrapped]
|
edge_to_faces[edge.wrapped] = edge_to_faces.get(edge.wrapped, []) + [face.wrapped]
|
||||||
if vertices:
|
if vertices:
|
||||||
for vertex in face.vertices():
|
for vertex in face.vertices():
|
||||||
vertex_to_faces[vertex.wrapped] = vertex_to_faces.get(vertex.wrapped, []) + [face.wrapped]
|
vertex_to_faces[vertex.wrapped] = vertex_to_faces.get(vertex.wrapped, []) + [face.wrapped]
|
||||||
if len(shape_faces) > 0: obj_color = None # Don't color edges/vertices if faces are colored
|
if len(shape_faces) > 0: color_obj = None # Don't color edges/vertices if faces are colored
|
||||||
if edges and hasattr(shape, 'edges'):
|
if edges and hasattr(shape, 'edges'):
|
||||||
shape_edges = shape.edges()
|
shape_edges = shape.edges()
|
||||||
for edge in shape_edges:
|
for edge in shape_edges:
|
||||||
_tessellate_edge(mgr, edge.wrapped, edge_to_faces.get(edge.wrapped, []), angular_tolerance,
|
_tessellate_edge(mgr, edge.wrapped, edge_to_faces.get(edge.wrapped, []), color_obj or color_edges,
|
||||||
angular_tolerance, obj_color)
|
angular_tolerance, angular_tolerance)
|
||||||
if len(shape_edges) > 0: obj_color = None # Don't color vertices if edges are colored
|
if len(shape_edges) > 0: color_obj = None # Don't color vertices if edges are colored
|
||||||
if vertices and hasattr(shape, 'vertices'):
|
if vertices and hasattr(shape, 'vertices'):
|
||||||
for vertex in shape.vertices():
|
for vertex in shape.vertices():
|
||||||
_tessellate_vertex(mgr, vertex.wrapped, vertex_to_faces.get(vertex.wrapped, []), obj_color)
|
_tessellate_vertex(mgr, vertex.wrapped, vertex_to_faces.get(vertex.wrapped, []),
|
||||||
|
color_obj or color_vertices)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise TypeError(f"Unsupported type: {type(cad_like)}: {cad_like}")
|
raise TypeError(f"Unsupported type: {type(cad_like)}: {cad_like}")
|
||||||
@@ -68,9 +66,9 @@ def tessellate(
|
|||||||
def _tessellate_face(
|
def _tessellate_face(
|
||||||
mgr: GLTFMgr,
|
mgr: GLTFMgr,
|
||||||
ocp_face: TopoDS_Face,
|
ocp_face: TopoDS_Face,
|
||||||
|
color: ColorTuple,
|
||||||
tolerance: float = 1e-3,
|
tolerance: float = 1e-3,
|
||||||
angular_tolerance: float = 0.1,
|
angular_tolerance: float = 0.1,
|
||||||
color: Optional[ColorTuple] = None,
|
|
||||||
):
|
):
|
||||||
face = Compound(ocp_face)
|
face = Compound(ocp_face)
|
||||||
# face.mesh(tolerance, angular_tolerance)
|
# face.mesh(tolerance, angular_tolerance)
|
||||||
@@ -81,6 +79,14 @@ def _tessellate_face(
|
|||||||
logger.warn("No triangulation found for face")
|
logger.warn("No triangulation found for face")
|
||||||
return GLTF2()
|
return GLTF2()
|
||||||
|
|
||||||
|
# Get the normal for each vertex (for smooth instead of flat shading!)
|
||||||
|
BRepLib_ToolTriangulatedShape.ComputeNormals_s(face.wrapped, poly)
|
||||||
|
reversed_face = face.wrapped.Orientation() == TopAbs_Orientation.TopAbs_REVERSED
|
||||||
|
normals = [
|
||||||
|
-Vector(v) if reversed_face else Vector(v)
|
||||||
|
for v in (poly.Normal(i) for i in range(1, poly.NbNodes() + 1))
|
||||||
|
]
|
||||||
|
|
||||||
# Get UV of each face from the parameters
|
# Get UV of each face from the parameters
|
||||||
uv = [
|
uv = [
|
||||||
(v.X(), v.Y())
|
(v.X(), v.Y())
|
||||||
@@ -89,7 +95,7 @@ def _tessellate_face(
|
|||||||
|
|
||||||
vertices = tri_mesh[0]
|
vertices = tri_mesh[0]
|
||||||
indices = tri_mesh[1]
|
indices = tri_mesh[1]
|
||||||
mgr.add_face(vertices, indices, uv, color)
|
mgr.add_face(vertices, normals, indices, uv, color)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -113,9 +119,9 @@ def _tessellate_edge(
|
|||||||
mgr: GLTFMgr,
|
mgr: GLTFMgr,
|
||||||
ocp_edge: TopoDS_Edge,
|
ocp_edge: TopoDS_Edge,
|
||||||
faces: List[TopoDS_Face],
|
faces: List[TopoDS_Face],
|
||||||
|
color: ColorTuple,
|
||||||
angular_deflection: float = 0.1,
|
angular_deflection: float = 0.1,
|
||||||
curvature_deflection: float = 0.1,
|
curvature_deflection: float = 0.1,
|
||||||
color: Optional[ColorTuple] = None,
|
|
||||||
):
|
):
|
||||||
# Use a curve discretizer to get the vertices
|
# Use a curve discretizer to get the vertices
|
||||||
curve = BRepAdaptor_Curve(ocp_edge)
|
curve = BRepAdaptor_Curve(ocp_edge)
|
||||||
@@ -136,9 +142,6 @@ def _tessellate_edge(
|
|||||||
mgr.add_edge(vertices, color)
|
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: ColorTuple):
|
||||||
color: Optional[ColorTuple] = None):
|
|
||||||
c = Vertex(ocp_vertex).center()
|
c = Vertex(ocp_vertex).center()
|
||||||
mgr.add_vertex(_push_point((c.X, c.Y, c.Z), faces), color)
|
mgr.add_vertex(_push_point((c.X, c.Y, c.Z), faces), color)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from PIL import Image
|
|||||||
from build123d import Shape, Axis, Location, Vector
|
from build123d import Shape, Axis, Location, Vector
|
||||||
from dataclasses_json import dataclass_json
|
from dataclasses_json import dataclass_json
|
||||||
|
|
||||||
from yacv_server.cad import _hashcode, get_color
|
from yacv_server.cad import _hashcode, get_color, ColorTuple
|
||||||
from yacv_server.cad import get_shape, grab_all_cad, CADCoreLike, CADLike
|
from yacv_server.cad import get_shape, grab_all_cad, CADCoreLike, CADLike
|
||||||
from yacv_server.gltf import get_version
|
from yacv_server.gltf import get_version
|
||||||
from yacv_server.myhttp import HTTPHandler
|
from yacv_server.myhttp import HTTPHandler
|
||||||
@@ -93,12 +93,39 @@ class YACV:
|
|||||||
|
|
||||||
texture: Optional[Tuple[bytes, str]]
|
texture: Optional[Tuple[bytes, str]]
|
||||||
"""Default texture to use for model faces, in (data, mimetype) format.
|
"""Default texture to use for model faces, in (data, mimetype) format.
|
||||||
If left as None, a default checkerboard texture will be used.
|
If left as None, no texture will be used.
|
||||||
|
|
||||||
It can be set with the YACV_BASE_TEXTURE=<uri> and overridden by `show(..., texture="<uri>")`.
|
It can be set with the YACV_TEXTURE=<uri> and overridden by the custom `yacv_texture` attribute of an object.
|
||||||
The <uri> can be file:<path> or data:<mime>;base64,<data> where <mime> is the mime type and
|
The <uri> can be file:<path> or data:<mime>;base64,<data> where <mime> is the mime type and
|
||||||
<data> is the base64 encoded image."""
|
<data> is the base64 encoded image."""
|
||||||
|
|
||||||
|
color_faces: Optional[ColorTuple]
|
||||||
|
"""Overrides the default color to use for model faces. Applies even if a texture is used.
|
||||||
|
|
||||||
|
You can use `show(..., color_faces=...)` or the standard way of setting colors for build123d/cadquery objects to
|
||||||
|
override this color.
|
||||||
|
|
||||||
|
It can be set with the YACV_COLOR_FACES=<color> environment variable, where <color> is a color
|
||||||
|
in the hexadecimal format #RRGGBB or #RRGGBBAA."""
|
||||||
|
|
||||||
|
color_edges: Optional[ColorTuple]
|
||||||
|
"""Overrides the default color to use for model edges.
|
||||||
|
|
||||||
|
You can use `show(..., color_edges=...) or the standard way of setting colors for build123d/cadquery objects to
|
||||||
|
override this color.
|
||||||
|
|
||||||
|
It can be set with the YACV_COLOR_EDGES=<color> environment variable, where <color> is a color
|
||||||
|
in the hexadecimal format #RRGGBB or #RRGGBBAA."""
|
||||||
|
|
||||||
|
color_vertices: Optional[ColorTuple]
|
||||||
|
"""Overrides the default color to use for model vertices.
|
||||||
|
|
||||||
|
You can use `show(..., color_vertices=...)` or the standard way of setting colors for build123d/cadquery objects to
|
||||||
|
override this color.
|
||||||
|
|
||||||
|
It can be set with the YACV_COLOR_VERTICES=<color> environment variable, where <color> is a color
|
||||||
|
in the hexadecimal format #RRGGBB or #RRGGBBAA."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.server_thread = None
|
self.server_thread = None
|
||||||
self.server = None
|
self.server = None
|
||||||
@@ -109,7 +136,10 @@ class YACV:
|
|||||||
self.at_least_one_client = threading.Event()
|
self.at_least_one_client = threading.Event()
|
||||||
self.shutting_down = threading.Event()
|
self.shutting_down = threading.Event()
|
||||||
self.frontend_lock = RWLock()
|
self.frontend_lock = RWLock()
|
||||||
self.texture = _read_texture_uri(os.getenv("YACV_BASE_TEXTURE"))
|
self.texture = _read_texture_uri(os.getenv("YACV_TEXTURE"))
|
||||||
|
self.color_faces = _read_color(os.getenv("YACV_COLOR_FACES", "#ffbf00")) # Default yellow
|
||||||
|
self.color_edges = _read_color(os.getenv("YACV_COLOR_EDGES", "#1a1aff")) # Default blue
|
||||||
|
self.color_vertices = _read_color(os.getenv("YACV_COLOR_VERTICES", "#1a1a1a")) # Default dark gray
|
||||||
logger.info('Using yacv-server v%s', get_version())
|
logger.info('Using yacv-server v%s', get_version())
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
@@ -201,8 +231,9 @@ class YACV:
|
|||||||
if isinstance(names, str):
|
if isinstance(names, str):
|
||||||
names = [names]
|
names = [names]
|
||||||
assert len(names) == len(objs), 'Number of names must match the number of objects'
|
assert len(names) == len(objs), 'Number of names must match the number of objects'
|
||||||
if 'color' in kwargs:
|
for color_name in ('color_faces', 'color_edges', 'color_vertices'):
|
||||||
kwargs['color'] = get_color(kwargs['color'])
|
if color_name in kwargs:
|
||||||
|
kwargs[color_name] = get_color(kwargs[color_name]) or _read_color(kwargs[color_name])
|
||||||
|
|
||||||
# Handle auto clearing of previous objects
|
# Handle auto clearing of previous objects
|
||||||
if kwargs.get('auto_clear', True):
|
if kwargs.get('auto_clear', True):
|
||||||
@@ -218,13 +249,15 @@ class YACV:
|
|||||||
# Publish the show event
|
# Publish the show event
|
||||||
for obj, name in zip(objs, names):
|
for obj, name in zip(objs, names):
|
||||||
obj_color = get_color(obj)
|
obj_color = get_color(obj)
|
||||||
|
# Some properties may be lost in preprocessing, so save them in kwargs
|
||||||
|
_kwargs = kwargs.copy()
|
||||||
if obj_color is not None:
|
if obj_color is not None:
|
||||||
kwargs = kwargs.copy()
|
_kwargs['color_obj'] = obj_color # Only applies to highest-dimensional objects
|
||||||
kwargs['color'] = obj_color
|
_kwargs['texture'] = _read_texture_uri(getattr(obj, 'yacv_texture', None) or kwargs.get('texture', None))
|
||||||
if not isinstance(obj, bytes):
|
if not isinstance(obj, bytes):
|
||||||
obj = _preprocess_cad(obj, **kwargs)
|
obj = _preprocess_cad(obj, **_kwargs)
|
||||||
_hash = _hashcode(obj, **kwargs)
|
_hash = _hashcode(obj, **_kwargs)
|
||||||
event = UpdatesApiFullData(name=name, _hash=_hash, obj=obj, kwargs=kwargs or {})
|
event = UpdatesApiFullData(name=name, _hash=_hash, obj=obj, kwargs=_kwargs or {})
|
||||||
self.show_events.publish(event)
|
self.show_events.publish(event)
|
||||||
|
|
||||||
logger.info('show %s took %.3f seconds', names, time.time() - start)
|
logger.info('show %s took %.3f seconds', names, time.time() - start)
|
||||||
@@ -309,17 +342,17 @@ class YACV:
|
|||||||
if isinstance(event.obj, bytes): # Already a GLTF
|
if isinstance(event.obj, bytes): # Already a GLTF
|
||||||
publish_to.publish(event.obj)
|
publish_to.publish(event.obj)
|
||||||
else: # CAD object to tessellate and convert to GLTF
|
else: # CAD object to tessellate and convert to GLTF
|
||||||
texture_override_uri = event.kwargs.get('texture', None)
|
gltf = tessellate(
|
||||||
texture_override = None
|
event.obj,
|
||||||
if isinstance(texture_override_uri, str):
|
color_faces=event.kwargs.get('color_faces', self.color_faces),
|
||||||
texture_override = _read_texture_uri(texture_override_uri)
|
color_edges=event.kwargs.get('color_edges', self.color_edges),
|
||||||
gltf = tessellate(event.obj, tolerance=event.kwargs.get('tolerance', 0.1),
|
color_vertices=event.kwargs.get('color_vertices', self.color_vertices),
|
||||||
|
color_obj=event.kwargs.get('color_obj', None),
|
||||||
|
tolerance=event.kwargs.get('tolerance', 0.1),
|
||||||
angular_tolerance=event.kwargs.get('angular_tolerance', 0.1),
|
angular_tolerance=event.kwargs.get('angular_tolerance', 0.1),
|
||||||
faces=event.kwargs.get('faces', True),
|
faces=event.kwargs.get('faces', True), edges=event.kwargs.get('edges', True),
|
||||||
edges=event.kwargs.get('edges', True),
|
|
||||||
vertices=event.kwargs.get('vertices', True),
|
vertices=event.kwargs.get('vertices', True),
|
||||||
obj_color=event.kwargs.get('color', None),
|
texture=event.kwargs.get('texture', self.texture))
|
||||||
texture=texture_override or self.texture)
|
|
||||||
glb_list_of_bytes = gltf.save_to_bytes()
|
glb_list_of_bytes = gltf.save_to_bytes()
|
||||||
glb_bytes = b''.join(glb_list_of_bytes)
|
glb_bytes = b''.join(glb_list_of_bytes)
|
||||||
publish_to.publish(glb_bytes)
|
publish_to.publish(glb_bytes)
|
||||||
@@ -363,6 +396,20 @@ def _read_texture_uri(uri: str) -> Optional[Tuple[bytes, str]]:
|
|||||||
return data, mtype
|
return data, mtype
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _read_color(color: str) -> Optional[ColorTuple]:
|
||||||
|
"""Reads a color from a string in the format #RRGGBB or #RRGGBBAA"""
|
||||||
|
if color is None:
|
||||||
|
return None
|
||||||
|
if not color.startswith('#') or len(color) not in (7, 9):
|
||||||
|
raise ValueError(f'Invalid color format: {color}')
|
||||||
|
r = float(int(color[1:3], 16)) / 255.0
|
||||||
|
g = float(int(color[3:5], 16)) / 255.0
|
||||||
|
b = float(int(color[5:7], 16)) / 255.0
|
||||||
|
a = float(int(color[7:9], 16)) / 255.0 if len(color) == 9 else 1.0
|
||||||
|
return r, g, b, a
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
def _preprocess_cad(obj: CADLike, **kwargs) -> CADCoreLike:
|
def _preprocess_cad(obj: CADLike, **kwargs) -> CADCoreLike:
|
||||||
# Get the shape of a CAD-like object
|
# Get the shape of a CAD-like object
|
||||||
@@ -384,6 +431,7 @@ def _preprocess_cad(obj: CADLike, **kwargs) -> CADCoreLike:
|
|||||||
|
|
||||||
_obj_name_counts = {}
|
_obj_name_counts = {}
|
||||||
|
|
||||||
|
|
||||||
def _find_var_name(obj: any, avoid_levels: int = 2) -> str:
|
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"""
|
"""A hacky way to get a stable name for an object that may change over time"""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user