mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 14:14:13 +01:00
lots of performance improvements, bug fixes and some new features
This commit is contained in:
@@ -15,7 +15,7 @@ if 'YACV_DISABLE_SERVER' not in os.environ:
|
||||
# Expose some nice aliases using the default server instance
|
||||
show = yacv.show
|
||||
show_all = yacv.show_cad_all
|
||||
prepare_image = image_to_gltf
|
||||
image_to_gltf = image_to_gltf
|
||||
export_all = yacv.export_all
|
||||
remove = yacv.remove
|
||||
clear = yacv.clear
|
||||
|
||||
@@ -60,7 +60,8 @@ def grab_all_cad() -> List[Tuple[str, CADCoreLike]]:
|
||||
|
||||
|
||||
def image_to_gltf(source: str | bytes, center: any, width: Optional[float] = None, height: Optional[float] = None,
|
||||
name: Optional[str] = None, save_mime: str = 'image/jpeg') -> Tuple[bytes, str]:
|
||||
name: Optional[str] = None, save_mime: str = 'image/jpeg', power_of_two: bool = True) \
|
||||
-> Tuple[bytes, str]:
|
||||
"""Convert an image to a GLTF CAD object."""
|
||||
from PIL import Image
|
||||
import io
|
||||
@@ -101,17 +102,27 @@ def image_to_gltf(source: str | bytes, center: any, width: Optional[float] = Non
|
||||
# Load the image to a byte buffer
|
||||
img = Image.open(source)
|
||||
img_buf = io.BytesIO()
|
||||
img.save(img_buf, format=format)
|
||||
img_buf = img_buf.getvalue()
|
||||
|
||||
# Build the gltf
|
||||
mgr = GLTFMgr(image=(img_buf, save_mime))
|
||||
# Use the original dimensions for scaling the model
|
||||
if width is None and height is None:
|
||||
raise ValueError('At least one of width or height must be specified') # Fallback to pixels == mm?
|
||||
elif width is None:
|
||||
width = img.width / img.height * height
|
||||
elif height is None:
|
||||
height = height or img.height / img.width * width # Apply default aspect ratio if unspecified
|
||||
|
||||
# Resize the image to a power of two if requested (recommended for GLTF)
|
||||
if power_of_two:
|
||||
new_width = 2 ** (img.width - 1).bit_length()
|
||||
new_height = 2 ** (img.height - 1).bit_length()
|
||||
img = img.resize((new_width, new_height))
|
||||
|
||||
# Save the image to a buffer
|
||||
img.save(img_buf, format=format)
|
||||
img_buf = img_buf.getvalue()
|
||||
|
||||
# Build the gltf
|
||||
mgr = GLTFMgr(image=(img_buf, save_mime))
|
||||
mgr.add_face([
|
||||
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),
|
||||
@@ -125,7 +136,7 @@ def image_to_gltf(source: str | bytes, center: any, width: Optional[float] = Non
|
||||
(1, 0),
|
||||
(1, 1),
|
||||
(0, 1),
|
||||
])
|
||||
], (1, 1, 1, 1))
|
||||
|
||||
# Return the GLTF binary blob and the suggested name of the image
|
||||
return b''.join(mgr.gltf.save_to_bytes()), name
|
||||
return b''.join(mgr.build().save_to_bytes()), name
|
||||
|
||||
@@ -12,45 +12,95 @@ _checkerboard_image_bytes = base64.decodebytes(
|
||||
class GLTFMgr:
|
||||
"""A utility class to build our GLTF2 objects easily and incrementally"""
|
||||
|
||||
def __init__(self, image: Tuple[bytes, str] = (_checkerboard_image_bytes, 'image/png')):
|
||||
gltf: GLTF2
|
||||
|
||||
# Intermediate data to be filled by the add_* methods and merged into the GLTF object
|
||||
# - Face data
|
||||
face_indices: List[int] # 3 indices per triangle
|
||||
face_positions: 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
|
||||
# - Edge data
|
||||
edge_indices: List[int] # 2 indices per edge
|
||||
edge_positions: List[float] # x, y, z
|
||||
edge_colors: List[float] # r, g, b, a
|
||||
# - Vertex data
|
||||
vertex_indices: List[int] # 1 index per vertex
|
||||
vertex_positions: List[float] # x, y, z
|
||||
vertex_colors: List[float] # r, g, b, a
|
||||
|
||||
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')}"),
|
||||
scene=0,
|
||||
scenes=[Scene(nodes=[0])],
|
||||
nodes=[Node(mesh=0)],
|
||||
meshes=[Mesh(primitives=[])],
|
||||
accessors=[],
|
||||
bufferViews=[BufferView(buffer=0, byteLength=len(image[0]), byteOffset=0)],
|
||||
buffers=[Buffer(byteLength=len(image[0]))],
|
||||
samplers=[Sampler(magFilter=NEAREST)],
|
||||
textures=[Texture(source=0, sampler=0)],
|
||||
images=[Image(bufferView=0, mimeType=image[1])],
|
||||
nodes=[Node(mesh=0)], # TODO: Server-side detection of shallow copies --> nodes
|
||||
meshes=[Mesh(primitives=[
|
||||
Primitive(indices=-1, attributes=Attributes(), mode=TRIANGLES, material=0,
|
||||
extras={"face_triangles_end": []}),
|
||||
Primitive(indices=-1, attributes=Attributes(), mode=LINES, material=0,
|
||||
extras={"edge_points_end": []}),
|
||||
Primitive(indices=-1, attributes=Attributes(), mode=POINTS, material=0),
|
||||
])],
|
||||
materials=[Material(pbrMetallicRoughness=PbrMetallicRoughness(metallicFactor=0.1, roughnessFactor=1.0),
|
||||
alphaCutoff=None)],
|
||||
)
|
||||
# TODO: Reduce the number of draw calls by merging all faces into a single primitive, and using
|
||||
# color attributes + extension? to differentiate them (same for edges and vertices)
|
||||
self.gltf.set_binary_blob(image[0])
|
||||
self.face_indices = []
|
||||
self.face_positions = []
|
||||
self.face_tex_coords = []
|
||||
self.face_colors = []
|
||||
self.image = image
|
||||
self.edge_indices = []
|
||||
self.edge_positions = []
|
||||
self.edge_colors = []
|
||||
self.vertex_indices = []
|
||||
self.vertex_positions = []
|
||||
self.vertex_colors = []
|
||||
|
||||
@property
|
||||
def _faces_primitive(self) -> Primitive:
|
||||
return [p for p in self.gltf.meshes[0].primitives if p.mode == TRIANGLES][0]
|
||||
|
||||
@property
|
||||
def _edges_primitive(self) -> Primitive:
|
||||
return [p for p in self.gltf.meshes[0].primitives if p.mode == LINES][0]
|
||||
|
||||
@property
|
||||
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[Tuple[float, float, float]], indices_raw: List[Tuple[int, int, int]],
|
||||
tex_coord_raw: List[Tuple[float, float]]):
|
||||
"""Add a face to the GLTF as a new primitive of the unique mesh"""
|
||||
vertices = np.array([[v[0], v[1], v[2]] for v in vertices_raw], dtype=np.float32)
|
||||
indices = np.array([[i[0], i[1], i[2]] for i in indices_raw], dtype=np.uint32)
|
||||
tex_coord = np.array([[t[0], t[1]] for t in tex_coord_raw], dtype=np.float32)
|
||||
self._add_any(vertices, indices, tex_coord, mode=TRIANGLES, material="face")
|
||||
tex_coord_raw: List[Tuple[float, float]],
|
||||
color: Tuple[float, float, float, float] = (1.0, 0.75, 0.0, 1.0)):
|
||||
"""Add a face to the GLTF mesh"""
|
||||
# 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_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[float, float, float]], mat: str = None):
|
||||
"""Add an edge to the GLTF as a new primitive of the unique mesh"""
|
||||
vertices = np.array([[v[0], v[1], v[2]] for v in vertices_raw], dtype=np.float32)
|
||||
indices = np.array(list(map(lambda i: [i, i + 1], range(len(vertices) - 1))), dtype=np.uint32)
|
||||
tex_coord = np.array([])
|
||||
self._add_any(vertices, indices, tex_coord, mode=LINE_STRIP, material=mat or "edge")
|
||||
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, 0.4, 1.0)):
|
||||
"""Add an edge to the GLTF mesh"""
|
||||
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))])
|
||||
self.edge_positions.extend([v for t in vertices_flat for v in t])
|
||||
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]):
|
||||
"""Add a vertex to the GLTF as a new primitive of the unique mesh"""
|
||||
vertices = np.array([[vertex[0], vertex[1], vertex[2]]])
|
||||
indices = np.array([[0]], dtype=np.uint32)
|
||||
tex_coord = np.array([], dtype=np.float32)
|
||||
self._add_any(vertices, indices, tex_coord, mode=POINTS, material="vertex")
|
||||
def add_vertex(self, vertex: Tuple[float, float, float],
|
||||
color: Tuple[float, float, float, float] = (0.1, 0.4, 0.1, 1.0)):
|
||||
"""Add a vertex to the GLTF mesh"""
|
||||
base_index = len(self.vertex_positions) // 3
|
||||
self.vertex_indices.append(base_index)
|
||||
self.vertex_positions.extend(vertex)
|
||||
self.vertex_colors.extend(color)
|
||||
|
||||
def add_location(self, loc: Location):
|
||||
"""Add a location to the GLTF as a new primitive of the unique mesh"""
|
||||
@@ -61,120 +111,91 @@ class GLTFMgr:
|
||||
|
||||
# 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)], mat="locX")
|
||||
self.add_edge([vert(pl.origin), vert(pl.origin + pl.y_dir)], mat="locY")
|
||||
self.add_edge([vert(pl.origin), vert(pl.origin + pl.z_dir)], mat="locZ")
|
||||
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))
|
||||
|
||||
def add_material(self, kind: str) -> int:
|
||||
"""It is important to use a different material for each primitive to be able to change them at runtime"""
|
||||
new_material: Material
|
||||
if kind == "face":
|
||||
new_material = Material(name="face", alphaCutoff=None, pbrMetallicRoughness=PbrMetallicRoughness(
|
||||
baseColorTexture=TextureInfo(index=0), baseColorFactor=[1, 1, 0.5, 1]), doubleSided=True)
|
||||
elif kind == "edge":
|
||||
new_material = Material(name="edge", alphaCutoff=None, pbrMetallicRoughness=PbrMetallicRoughness(
|
||||
baseColorFactor=[0, 0, 0.5, 1]))
|
||||
elif kind == "vertex":
|
||||
new_material = Material(name="vertex", alphaCutoff=None, pbrMetallicRoughness=PbrMetallicRoughness(
|
||||
baseColorFactor=[0, 0.3, 0.3, 1]))
|
||||
elif kind == "locX":
|
||||
new_material = Material(name="locX", alphaCutoff=None, pbrMetallicRoughness=PbrMetallicRoughness(
|
||||
baseColorFactor=[0.97, 0.24, 0.24, 1]))
|
||||
elif kind == "locY":
|
||||
new_material = Material(name="locY", alphaCutoff=None, pbrMetallicRoughness=PbrMetallicRoughness(
|
||||
baseColorFactor=[0.42, 0.8, 0.15, 1]))
|
||||
elif kind == "locZ":
|
||||
new_material = Material(name="locZ", alphaCutoff=None, pbrMetallicRoughness=PbrMetallicRoughness(
|
||||
baseColorFactor=[0.09, 0.55, 0.94, 1]))
|
||||
def build(self) -> GLTF2:
|
||||
"""Merge the intermediate data into the GLTF object and return it"""
|
||||
buffers_list: List[Tuple[Accessor, BufferView, bytes]] = []
|
||||
|
||||
if len(self.face_indices) > 0:
|
||||
self._faces_primitive.indices = len(buffers_list)
|
||||
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.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)
|
||||
buffers_list.append(_gen_buffer_metadata(self.face_colors, 4))
|
||||
else:
|
||||
raise ValueError(f"Unknown material kind {kind}")
|
||||
self.gltf.materials.append(new_material)
|
||||
return len(self.gltf.materials) - 1
|
||||
self.image = None # Unused image
|
||||
self.gltf.meshes[0].primitives = list( # Remove unused faces primitive
|
||||
filter(lambda p: p.mode != TRIANGLES, self.gltf.meshes[0].primitives))
|
||||
|
||||
def _add_any(self, vertices: np.ndarray, indices: np.ndarray, tex_coord: np.ndarray, mode: int = TRIANGLES,
|
||||
material: str = "face"):
|
||||
assert vertices.ndim == 2
|
||||
assert vertices.shape[1] == 3
|
||||
vertices = vertices.astype(np.float32)
|
||||
vertices_blob = vertices.tobytes()
|
||||
edges_and_vertices_mat = 0
|
||||
if self.image is not None and (len(self.edge_indices) > 0 or len(self.vertex_indices) > 0):
|
||||
# Create a material without texture for edges and vertices
|
||||
edges_and_vertices_mat = len(self.gltf.materials)
|
||||
new_mat = copy.deepcopy(self.gltf.materials[0])
|
||||
new_mat.pbrMetallicRoughness.baseColorTexture = None
|
||||
self.gltf.materials.append(new_mat)
|
||||
|
||||
assert indices.ndim == 2
|
||||
assert indices.shape[1] == 3 and mode == TRIANGLES or indices.shape[1] == 2 and mode == LINE_STRIP or \
|
||||
indices.shape[1] == 1 and mode == POINTS
|
||||
indices = indices.astype(np.uint32)
|
||||
indices_blob = indices.flatten().tobytes()
|
||||
# Treat edges and vertices the same way
|
||||
for (indices, positions, colors, primitive, kind) in [
|
||||
(self.edge_indices, self.edge_positions, self.edge_colors, self._edges_primitive, LINES),
|
||||
(self.vertex_indices, self.vertex_positions, self.vertex_colors, self._vertices_primitive, POINTS)
|
||||
]:
|
||||
if len(indices) > 0:
|
||||
primitive.material = edges_and_vertices_mat
|
||||
primitive.indices = len(buffers_list)
|
||||
buffers_list.append(_gen_buffer_metadata(indices, 1))
|
||||
primitive.attributes.POSITION = len(buffers_list)
|
||||
buffers_list.append(_gen_buffer_metadata(positions, 3))
|
||||
primitive.attributes.COLOR_0 = len(buffers_list)
|
||||
buffers_list.append(_gen_buffer_metadata(colors, 4))
|
||||
else:
|
||||
self.gltf.meshes[0].primitives = list( # Remove unused edges primitive
|
||||
filter(lambda p: p.mode != kind, self.gltf.meshes[0].primitives))
|
||||
|
||||
# Check that all vertices are referenced by the indices
|
||||
# This can happen on broken faces like on some fonts
|
||||
# assert indices.max() == len(vertices) - 1, f"{indices.max()} != {len(vertices) - 1}"
|
||||
# assert indices.min() == 0, f"min({indices}) != 0"
|
||||
# assert np.unique(indices.flatten()).size == len(vertices)
|
||||
if self.image is not None: # Add texture last as it creates a fake accessor that is not added!
|
||||
self.gltf.images = [Image(bufferView=len(buffers_list), mimeType=self.image[1])]
|
||||
self.gltf.textures = [Texture(source=0, sampler=0)]
|
||||
self.gltf.samplers = [Sampler(magFilter=NEAREST)]
|
||||
self.gltf.materials[0].pbrMetallicRoughness.baseColorTexture = TextureInfo(index=0)
|
||||
buffers_list.append((Accessor(), BufferView(), self.image[0]))
|
||||
|
||||
assert len(tex_coord) == 0 or tex_coord.ndim == 2
|
||||
assert len(tex_coord) == 0 or tex_coord.shape[1] == 2
|
||||
tex_coord = tex_coord.astype(np.float32)
|
||||
tex_coord_blob = tex_coord.tobytes()
|
||||
|
||||
accessor_base = len(self.gltf.accessors)
|
||||
self.gltf.meshes[0].primitives.append(
|
||||
Primitive(
|
||||
attributes=Attributes(POSITION=accessor_base + 1, TEXCOORD_0=accessor_base + 2)
|
||||
if len(tex_coord) > 0 else Attributes(POSITION=accessor_base + 1),
|
||||
indices=accessor_base,
|
||||
mode=mode,
|
||||
material=self.add_material(material),
|
||||
)
|
||||
)
|
||||
|
||||
buffer_view_base = len(self.gltf.bufferViews)
|
||||
self.gltf.accessors.extend([it for it in [
|
||||
Accessor(
|
||||
bufferView=buffer_view_base,
|
||||
componentType=UNSIGNED_INT,
|
||||
count=indices.size,
|
||||
type=SCALAR,
|
||||
max=[int(indices.max())],
|
||||
min=[int(indices.min())],
|
||||
),
|
||||
Accessor(
|
||||
bufferView=buffer_view_base + 1,
|
||||
componentType=FLOAT,
|
||||
count=len(vertices),
|
||||
type=VEC3,
|
||||
max=vertices.max(axis=0).tolist(),
|
||||
min=vertices.min(axis=0).tolist(),
|
||||
),
|
||||
Accessor(
|
||||
bufferView=buffer_view_base + 2,
|
||||
componentType=FLOAT,
|
||||
count=len(tex_coord),
|
||||
type=VEC2,
|
||||
max=tex_coord.max(axis=0).tolist(),
|
||||
min=tex_coord.min(axis=0).tolist(),
|
||||
) if len(tex_coord) > 0 else None
|
||||
] if it is not None])
|
||||
|
||||
prev_binary_blob = self.gltf.binary_blob()
|
||||
# Once all the data is ready, we can concatenate the buffers updating the accessors and views
|
||||
prev_binary_blob = self.gltf.binary_blob() or b''
|
||||
byte_offset_base = len(prev_binary_blob)
|
||||
self.gltf.bufferViews.extend([bv for bv in [
|
||||
BufferView(
|
||||
buffer=0,
|
||||
byteOffset=byte_offset_base,
|
||||
byteLength=len(indices_blob),
|
||||
target=ELEMENT_ARRAY_BUFFER,
|
||||
),
|
||||
BufferView(
|
||||
buffer=0,
|
||||
byteOffset=byte_offset_base + len(indices_blob),
|
||||
byteLength=len(vertices_blob),
|
||||
target=ARRAY_BUFFER,
|
||||
),
|
||||
BufferView(
|
||||
buffer=0,
|
||||
byteOffset=byte_offset_base + len(indices_blob) + len(vertices_blob),
|
||||
byteLength=len(tex_coord_blob),
|
||||
target=ARRAY_BUFFER,
|
||||
)
|
||||
] if bv.byteLength > 0])
|
||||
for accessor, bufferView, blob in buffers_list:
|
||||
|
||||
self.gltf.set_binary_blob(prev_binary_blob + indices_blob + vertices_blob + tex_coord_blob)
|
||||
if accessor.componentType is not None: # Remove accessor of texture
|
||||
buffer_view_base = len(self.gltf.bufferViews)
|
||||
accessor.bufferView = buffer_view_base
|
||||
self.gltf.accessors.append(accessor)
|
||||
|
||||
bufferView.buffer = 0
|
||||
bufferView.byteOffset = byte_offset_base
|
||||
bufferView.byteLength = len(blob)
|
||||
self.gltf.bufferViews.append(bufferView)
|
||||
|
||||
byte_offset_base += len(blob)
|
||||
prev_binary_blob += blob
|
||||
|
||||
self.gltf.buffers.append(Buffer(byteLength=byte_offset_base))
|
||||
self.gltf.set_binary_blob(prev_binary_blob)
|
||||
|
||||
return self.gltf
|
||||
|
||||
|
||||
def _gen_buffer_metadata(data: List[any], chunk: int) -> Tuple[Accessor, BufferView, bytes]:
|
||||
return Accessor(
|
||||
componentType={1: UNSIGNED_INT, 2: FLOAT, 3: FLOAT, 4: FLOAT}[chunk],
|
||||
count=len(data) // chunk,
|
||||
type={1: SCALAR, 2: VEC2, 3: VEC3, 4: VEC4}[chunk],
|
||||
max=[max(data[i::chunk]) for i in range(chunk)],
|
||||
min=[min(data[i::chunk]) for i in range(chunk)],
|
||||
), BufferView(
|
||||
target={1: ELEMENT_ARRAY_BUFFER, 2: ARRAY_BUFFER, 3: ARRAY_BUFFER, 4: ARRAY_BUFFER}[chunk],
|
||||
), np.array(data, dtype={1: np.uint32, 2: np.float32, 3: np.float32, 4: np.float32}[chunk]).tobytes()
|
||||
|
||||
@@ -24,11 +24,11 @@ def build_logo(text: bool = True) -> Dict[str, Union[Part, Location, str]]:
|
||||
logo_img_location.position.Z)
|
||||
|
||||
logo_img_path = os.path.join(ASSETS_DIR, 'img.jpg')
|
||||
img_bytes, img_name = prepare_image(logo_img_path, logo_img_location, height=18)
|
||||
img_glb_bytes, img_name = image_to_gltf(logo_img_path, logo_img_location, height=18)
|
||||
|
||||
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_bytes}
|
||||
return {'fox': fox_glb_bytes, 'logo': logo_obj, 'location': logo_img_location, img_name: img_glb_bytes}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -43,7 +43,7 @@ if __name__ == "__main__":
|
||||
# If this is not set, the server will auto-start on import and show_* calls will provide live updates
|
||||
os.environ['YACV_DISABLE_SERVER'] = 'True'
|
||||
|
||||
from yacv_server import export_all, remove, prepare_image, show
|
||||
from yacv_server import export_all, show, image_to_gltf
|
||||
|
||||
# Build the CAD part of the logo
|
||||
logo = build_logo()
|
||||
@@ -52,7 +52,8 @@ if __name__ == "__main__":
|
||||
show(*[obj for obj in logo.values()], names=[name for name in logo.keys()])
|
||||
|
||||
if testing_server:
|
||||
remove('location') # Test removing a part
|
||||
# remove('location') # Test removing a part
|
||||
pass
|
||||
else:
|
||||
# Save the complete logo to multiple GLB files
|
||||
export_all(os.path.join(ASSETS_DIR, 'logo_build'))
|
||||
|
||||
@@ -36,26 +36,26 @@ def tessellate(
|
||||
shape = Shape(cad_like)
|
||||
|
||||
# Perform tessellation tasks
|
||||
edge_to_faces: Dict[TopoDS_Edge, List[TopoDS_Face]] = {}
|
||||
vertex_to_faces: Dict[TopoDS_Vertex, List[TopoDS_Face]] = {}
|
||||
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 edges:
|
||||
for edge in face.edges():
|
||||
edge_to_faces[edge.wrapped] = edge_to_faces.get(edge.wrapped, []) + [face.wrapped]
|
||||
edge_to_faces[_hashcode(edge.wrapped)] = edge_to_faces.get(_hashcode(edge.wrapped), []) + [face.wrapped]
|
||||
if vertices:
|
||||
for vertex in face.vertices():
|
||||
vertex_to_faces[vertex.wrapped] = vertex_to_faces.get(vertex.wrapped, []) + [face.wrapped]
|
||||
vertex_to_faces[_hashcode(vertex.wrapped)] = vertex_to_faces.get(_hashcode(vertex.wrapped), []) + [face.wrapped]
|
||||
if 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(_hashcode(edge.wrapped), []), angular_tolerance,
|
||||
angular_tolerance)
|
||||
if 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(_hashcode(vertex.wrapped), []))
|
||||
|
||||
return mgr.gltf
|
||||
return mgr.build()
|
||||
|
||||
|
||||
def _tessellate_face(
|
||||
@@ -91,9 +91,9 @@ def _push_point(v: Tuple[float, float, float], faces: List[TopoDS_Face]) -> Tupl
|
||||
push_dir = (push_dir[0] + normal.X, push_dir[1] + normal.Y, push_dir[2] + normal.Z)
|
||||
if push_dir != (0, 0, 0):
|
||||
# Normalize the push direction by the number of faces and a constant factor
|
||||
# NOTE: Don't overdo it, or metrics will be wrong
|
||||
n = len(faces) / 1e-3
|
||||
push_dir = (push_dir[0] / n, push_dir[1] / n, push_dir[2] / n)
|
||||
# NOTE: Don't overdo it, or metrics will be (more) wrong
|
||||
n = 1e-3 / len(faces)
|
||||
push_dir = (push_dir[0] * n, push_dir[1] * n, push_dir[2] * n)
|
||||
# Push the vertex by the normal
|
||||
v = (v[0] + push_dir[0], v[1] + push_dir[1], v[2] + push_dir[2])
|
||||
return v
|
||||
@@ -119,6 +119,9 @@ def _tessellate_edge(
|
||||
for i in range(1, discretizer.NbPoints() + 1)
|
||||
)
|
||||
]
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user