mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 14:14:13 +01:00
Minor backend improvements: better color and textured handling, smooth shading, better demo
This commit is contained in:
@@ -78,7 +78,8 @@ def get_shape(obj: CADLike, error: bool = True) -> Optional[CADCoreLike]:
|
||||
# Sorting is required to improve hashcode consistency
|
||||
shapes_raw_filtered_sorted = sorted(shapes_raw_filtered, key=lambda x: _hashcode(x))
|
||||
# Build a single compound shape (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)
|
||||
except TypeError:
|
||||
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.z_dir)] * 4, [
|
||||
(0, 2, 1),
|
||||
(0, 3, 2),
|
||||
], [
|
||||
|
||||
@@ -4,10 +4,6 @@ import numpy as np
|
||||
from build123d import Location, Plane, Vector
|
||||
from pygltflib import *
|
||||
|
||||
# PNG file containing 1x1 while pixel
|
||||
_default_tex = (b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=')
|
||||
|
||||
_default_color = (1.0, 0.75, 0.0, 1.0)
|
||||
|
||||
def get_version() -> str:
|
||||
try:
|
||||
@@ -25,6 +21,7 @@ class GLTFMgr:
|
||||
# - Face data
|
||||
face_indices: List[int] # 3 indices per triangle
|
||||
face_positions: List[float] # x, y, z
|
||||
face_normals: List[float] # x, y, z
|
||||
face_tex_coords: List[float] # u, v
|
||||
face_colors: List[float] # r, g, b, a
|
||||
image: Optional[Tuple[bytes, str]] # image/png
|
||||
@@ -37,7 +34,7 @@ class GLTFMgr:
|
||||
vertex_positions: List[float] # x, y, z
|
||||
vertex_colors: List[float] # r, g, b, a
|
||||
|
||||
def __init__(self, image: Optional[Tuple[bytes, str]] = (_default_tex, 'image/png')):
|
||||
def __init__(self, image: Optional[Tuple[bytes, str]] = None):
|
||||
self.gltf = GLTF2(
|
||||
asset=Asset(generator=f"yacv_server@{get_version()}"),
|
||||
scene=0,
|
||||
@@ -55,6 +52,7 @@ class GLTFMgr:
|
||||
)
|
||||
self.face_indices = []
|
||||
self.face_positions = []
|
||||
self.face_normals = []
|
||||
self.face_tex_coords = []
|
||||
self.face_colors = []
|
||||
self.image = image
|
||||
@@ -77,24 +75,23 @@ class GLTFMgr:
|
||||
def _vertices_primitive(self) -> Primitive:
|
||||
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: Optional[Tuple[float, float, float, float]] = None):
|
||||
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: Tuple[float, float, float, float]):
|
||||
"""Add a face to the GLTF mesh"""
|
||||
if color is None: color = _default_color
|
||||
# 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"
|
||||
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_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_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))
|
||||
|
||||
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"""
|
||||
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))])
|
||||
@@ -102,9 +99,8 @@ 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: 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"""
|
||||
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)
|
||||
@@ -118,10 +114,11 @@ class GLTFMgr:
|
||||
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
|
||||
self.add_vertex(vert(pl.origin))
|
||||
self.add_edge([(vert(pl.origin), vert(pl.origin + pl.x_dir))], color=(0.97, 0.24, 0.24, 1))
|
||||
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.z_dir))], color=(0.09, 0.55, 0.94, 1))
|
||||
# The colors are hardcoded. You can add vertices and edges manually to change them.
|
||||
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.x_dir))], color=(0.97, 0.24, 0.24, 1.0))
|
||||
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:
|
||||
"""Merge the intermediate data into the GLTF object and return it"""
|
||||
@@ -132,6 +129,8 @@ class GLTFMgr:
|
||||
buffers_list.append(_gen_buffer_metadata(self.face_indices, 1))
|
||||
self._faces_primitive.attributes.POSITION = len(buffers_list)
|
||||
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)
|
||||
buffers_list.append(_gen_buffer_metadata(self.face_tex_coords, 2))
|
||||
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'))
|
||||
|
||||
|
||||
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"""
|
||||
with BuildPart(Plane.XY.offset(50)) as logo_obj:
|
||||
Box(22, 40, 30)
|
||||
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))
|
||||
if text:
|
||||
text_at_plane = Plane.YZ
|
||||
text_at_plane.origin = faces().group_by(Axis.X)[-1].face().center()
|
||||
with BuildSketch(text_at_plane.location):
|
||||
with BuildSketch(Plane.YZ.move(Pos(faces().group_by(Axis.X)[-1].face().center()))):
|
||||
Text('Yet Another\nCAD Viewer', 6, font_path='/usr/share/fonts/TTF/Hack-Regular.ttf')
|
||||
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
|
||||
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)
|
||||
|
||||
# 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
|
||||
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
|
||||
@@ -33,7 +39,8 @@ def build_logo(text: bool = True) -> Dict[str, Union[Part, Location, str]]:
|
||||
# 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, '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__":
|
||||
|
||||
@@ -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.BRepAdaptor import BRepAdaptor_Curve
|
||||
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.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 yacv_server.cad import CADCoreLike, ColorTuple
|
||||
@@ -14,14 +16,9 @@ from yacv_server.mylogger import logger
|
||||
|
||||
|
||||
def tessellate(
|
||||
cad_like: CADCoreLike,
|
||||
tolerance: float = 0.1,
|
||||
angular_tolerance: float = 0.1,
|
||||
faces: bool = True,
|
||||
edges: bool = True,
|
||||
vertices: bool = True,
|
||||
obj_color: Optional[ColorTuple] = None,
|
||||
texture: Optional[Tuple[bytes, str]] = None,
|
||||
cad_like: CADCoreLike, color_faces: ColorTuple, color_edges: ColorTuple, color_vertices: ColorTuple,
|
||||
color_obj: Optional[ColorTuple] = None, tolerance: float = 0.1, angular_tolerance: float = 0.1,
|
||||
faces: bool = True, edges: bool = True, vertices: bool = True, texture: Optional[Tuple[bytes, str]] = None,
|
||||
) -> GLTF2:
|
||||
"""Tessellate a whole shape into a list of triangle vertices and a list of triangle indices."""
|
||||
if texture is None:
|
||||
@@ -41,23 +38,24 @@ def tessellate(
|
||||
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)
|
||||
_tessellate_face(mgr, face.wrapped, color_obj or color_faces, tolerance, angular_tolerance)
|
||||
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 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'):
|
||||
shape_edges = shape.edges()
|
||||
for edge in shape_edges:
|
||||
_tessellate_edge(mgr, edge.wrapped, edge_to_faces.get(edge.wrapped, []), angular_tolerance,
|
||||
angular_tolerance, obj_color)
|
||||
if len(shape_edges) > 0: obj_color = None # Don't color vertices if edges are colored
|
||||
_tessellate_edge(mgr, edge.wrapped, edge_to_faces.get(edge.wrapped, []), color_obj or color_edges,
|
||||
angular_tolerance, angular_tolerance)
|
||||
if len(shape_edges) > 0: color_obj = 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, []), obj_color)
|
||||
_tessellate_vertex(mgr, vertex.wrapped, vertex_to_faces.get(vertex.wrapped, []),
|
||||
color_obj or color_vertices)
|
||||
|
||||
else:
|
||||
raise TypeError(f"Unsupported type: {type(cad_like)}: {cad_like}")
|
||||
@@ -68,9 +66,9 @@ def tessellate(
|
||||
def _tessellate_face(
|
||||
mgr: GLTFMgr,
|
||||
ocp_face: TopoDS_Face,
|
||||
color: ColorTuple,
|
||||
tolerance: float = 1e-3,
|
||||
angular_tolerance: float = 0.1,
|
||||
color: Optional[ColorTuple] = None,
|
||||
):
|
||||
face = Compound(ocp_face)
|
||||
# face.mesh(tolerance, angular_tolerance)
|
||||
@@ -81,6 +79,14 @@ def _tessellate_face(
|
||||
logger.warn("No triangulation found for face")
|
||||
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
|
||||
uv = [
|
||||
(v.X(), v.Y())
|
||||
@@ -89,7 +95,7 @@ def _tessellate_face(
|
||||
|
||||
vertices = tri_mesh[0]
|
||||
indices = tri_mesh[1]
|
||||
mgr.add_face(vertices, indices, uv, color)
|
||||
mgr.add_face(vertices, normals, indices, uv, color)
|
||||
return None
|
||||
|
||||
|
||||
@@ -113,9 +119,9 @@ def _tessellate_edge(
|
||||
mgr: GLTFMgr,
|
||||
ocp_edge: TopoDS_Edge,
|
||||
faces: List[TopoDS_Face],
|
||||
color: ColorTuple,
|
||||
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)
|
||||
@@ -136,9 +142,6 @@ def _tessellate_edge(
|
||||
mgr.add_edge(vertices, color)
|
||||
|
||||
|
||||
def _tessellate_vertex(mgr: GLTFMgr, ocp_vertex: TopoDS_Vertex, faces: List[TopoDS_Face],
|
||||
color: Optional[ColorTuple] = None):
|
||||
def _tessellate_vertex(mgr: GLTFMgr, ocp_vertex: TopoDS_Vertex, faces: List[TopoDS_Face], color: ColorTuple):
|
||||
c = Vertex(ocp_vertex).center()
|
||||
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 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.gltf import get_version
|
||||
from yacv_server.myhttp import HTTPHandler
|
||||
@@ -93,12 +93,39 @@ class YACV:
|
||||
|
||||
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.
|
||||
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
|
||||
<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):
|
||||
self.server_thread = None
|
||||
self.server = None
|
||||
@@ -109,7 +136,10 @@ class YACV:
|
||||
self.at_least_one_client = threading.Event()
|
||||
self.shutting_down = threading.Event()
|
||||
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())
|
||||
|
||||
def start(self):
|
||||
@@ -201,8 +231,9 @@ class YACV:
|
||||
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'])
|
||||
for color_name in ('color_faces', 'color_edges', 'color_vertices'):
|
||||
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
|
||||
if kwargs.get('auto_clear', True):
|
||||
@@ -218,13 +249,15 @@ class YACV:
|
||||
# Publish the show event
|
||||
for obj, name in zip(objs, names):
|
||||
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:
|
||||
kwargs = kwargs.copy()
|
||||
kwargs['color'] = obj_color
|
||||
_kwargs['color_obj'] = obj_color # Only applies to highest-dimensional objects
|
||||
_kwargs['texture'] = _read_texture_uri(getattr(obj, 'yacv_texture', None) or kwargs.get('texture', None))
|
||||
if not isinstance(obj, bytes):
|
||||
obj = _preprocess_cad(obj, **kwargs)
|
||||
_hash = _hashcode(obj, **kwargs)
|
||||
event = UpdatesApiFullData(name=name, _hash=_hash, obj=obj, kwargs=kwargs or {})
|
||||
obj = _preprocess_cad(obj, **_kwargs)
|
||||
_hash = _hashcode(obj, **_kwargs)
|
||||
event = UpdatesApiFullData(name=name, _hash=_hash, obj=obj, kwargs=_kwargs or {})
|
||||
self.show_events.publish(event)
|
||||
|
||||
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
|
||||
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),
|
||||
obj_color=event.kwargs.get('color', None),
|
||||
texture=texture_override or self.texture)
|
||||
gltf = tessellate(
|
||||
event.obj,
|
||||
color_faces=event.kwargs.get('color_faces', self.color_faces),
|
||||
color_edges=event.kwargs.get('color_edges', self.color_edges),
|
||||
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),
|
||||
faces=event.kwargs.get('faces', True), edges=event.kwargs.get('edges', True),
|
||||
vertices=event.kwargs.get('vertices', True),
|
||||
texture=event.kwargs.get('texture', self.texture))
|
||||
glb_list_of_bytes = gltf.save_to_bytes()
|
||||
glb_bytes = b''.join(glb_list_of_bytes)
|
||||
publish_to.publish(glb_bytes)
|
||||
@@ -355,7 +388,7 @@ def _read_texture_uri(uri: str) -> Optional[Tuple[bytes, str]]:
|
||||
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)
|
||||
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]
|
||||
@@ -363,6 +396,20 @@ def _read_texture_uri(uri: str) -> Optional[Tuple[bytes, str]]:
|
||||
return data, mtype
|
||||
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
|
||||
def _preprocess_cad(obj: CADLike, **kwargs) -> CADCoreLike:
|
||||
# Get the shape of a CAD-like object
|
||||
@@ -384,6 +431,7 @@ def _preprocess_cad(obj: CADLike, **kwargs) -> CADCoreLike:
|
||||
|
||||
_obj_name_counts = {}
|
||||
|
||||
|
||||
def _find_var_name(obj: any, avoid_levels: int = 2) -> str:
|
||||
"""A hacky way to get a stable name for an object that may change over time"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user