Migrate server to poetry and build glb models for faces and edges

This commit is contained in:
Yeicor
2024-02-02 13:25:13 +01:00
parent 8fd3a2247a
commit 2202a86464
5 changed files with 2246 additions and 38 deletions

2110
poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +1,25 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "yacv_server"
version = "0.0.1"
authors = [
{ name = "Yeicor" },
]
[tool.poetry]
name = "yacv-server"
version = "0.1.0"
description = "Yet Another CAD Viewer (server)"
authors = ["Yeicor <4929005+Yeicor@users.noreply.github.com>"]
license = "MIT"
readme = "README.md"
requires-python = ">=3.8"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
[project.urls]
Homepage = "https://github.com/yeicor-3d/yet-another-cad-viewer"
Issues = "https://github.com/yeicor-3d/yet-another-cad-viewer/issues"
[tool.poetry.dependencies]
python = "^3.9"
# CAD
build123d = "^0.3.0"
partcad = "^0.3.84"
# Web
aiohttp = "^3.9.3"
pygltflib = "^1.16.1"
[tool.poetry.group.dev.dependencies]
aiohttp-devtools = "^1.1.2"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

90
yacv_server/gltf.py Normal file
View File

@@ -0,0 +1,90 @@
import numpy as np
from pygltflib import *
from tessellate import TessellationUpdate
def create_gltf_from_update(update: TessellationUpdate) -> GLTF2:
"""Create a glTF object from a tessellation update."""
return create_gltf(
np.array(list(map(lambda v: [v.X, v.Y, v.Z], update.vertices))),
np.array(update.indices) if update.indices else None
)
def create_gltf(vertices: np.ndarray, indices_in: Optional[np.ndarray]) -> GLTF2:
"""Create a glTF object from vertices and optionally indices.
If indices are not set, vertices are interpreted as line_strip."""
assert vertices.ndim == 2
assert vertices.shape[1] == 3
vertices = vertices # .astype(np.float16)
vertices_blob = vertices.tobytes()
# print(vertices)
if indices_in is not None:
assert indices_in.ndim == 2
assert indices_in.shape[1] == 3
indices = indices_in # .astype(np.uint8)
else:
indices = np.array(list(map(lambda i: [i, i + 1], range(len(vertices) - 1))), dtype=np.uint8)
indices_blob = indices.flatten().tobytes()
# print(indices)
gltf = GLTF2(
scene=0,
scenes=[Scene(nodes=[0])],
nodes=[Node(mesh=0)],
meshes=[
Mesh(
primitives=[
Primitive(
attributes=Attributes(POSITION=1),
indices=0,
mode=TRIANGLES if indices_in is not None else LINE_STRIP
# TODO: Also support POINTS mode
)
]
)
],
accessors=[
Accessor(
bufferView=0,
componentType=UNSIGNED_BYTE,
count=indices.size,
type=SCALAR,
max=[int(indices.max())],
min=[int(indices.min())],
),
Accessor(
bufferView=1,
componentType=FLOAT,
count=len(vertices),
type=VEC3,
max=vertices.max(axis=0).tolist(),
min=vertices.min(axis=0).tolist(),
),
],
bufferViews=[
BufferView(
buffer=0,
byteLength=len(indices_blob),
target=ELEMENT_ARRAY_BUFFER,
),
BufferView(
buffer=0,
byteOffset=len(indices_blob),
byteLength=len(vertices_blob),
target=ARRAY_BUFFER,
),
],
buffers=[
Buffer(
byteLength=len(indices_blob) + len(vertices_blob)
)
],
)
gltf.set_binary_blob(indices_blob + vertices_blob)
return gltf

View File

@@ -1,6 +1,7 @@
from build123d import *
from tessellate import tessellate
from gltf import create_gltf_from_update
from tessellate import tessellate, TessellationUpdate
def logo() -> Compound:
@@ -12,4 +13,15 @@ def logo() -> Compound:
if __name__ == "__main__":
obj = logo()
tessellate(obj.wrapped, lambda *args: print(args))
def progress(update: TessellationUpdate):
gltf = create_gltf_from_update(update)
print(gltf)
if update.is_face:
gltf.save("logo_face.glb")
else:
gltf.save("logo_edge.glb")
tessellate(obj.wrapped, progress)

View File

@@ -13,15 +13,6 @@ from build123d import Face, Vector, Shape
from partcad.wrappers import cq_serialize
class UVMode(Enum):
"""UV mode for tesselation"""
TRIPLANAR = 0
"""Triplanar UV mapping"""
FACE = 1
"""Use UV coordinates from each face"""
@dataclass
class TessellationUpdate:
"""Tessellation update"""
@@ -40,6 +31,10 @@ class TessellationUpdate:
indices: Optional[list[Tuple[int, int, int]]]
"""List of indices (only for faces)"""
@property
def is_face(self):
return isinstance(self.shape, TopoDS_Face)
progress_callback_t = Callable[[TessellationUpdate], None]
@@ -58,7 +53,6 @@ def tessellate(
progress_callback: progress_callback_t = None,
tolerance: float = 0.1,
angular_tolerance: float = 0.1,
uv: Optional[UVMode] = None,
executor: Executor = ProcessPoolExecutor(), # Set to ThreadPoolExecutor if pickling fails...
):
"""Tessellate a whole shape into a list of triangle vertices and a list of triangle indices.
@@ -72,9 +66,9 @@ def tessellate(
# Submit tessellation tasks
for face in shape.faces():
futures.append(executor.submit(_tessellate_element, face.wrapped, tolerance, angular_tolerance, uv))
futures.append(executor.submit(_tessellate_element, face.wrapped, tolerance, angular_tolerance))
for edge in shape.edges():
futures.append(executor.submit(_tessellate_element, edge.wrapped, tolerance, angular_tolerance, uv))
futures.append(executor.submit(_tessellate_element, edge.wrapped, tolerance, angular_tolerance))
# Collect results as they come in
for i, future in enumerate(concurrent.futures.as_completed(futures)):
@@ -102,9 +96,9 @@ def _register_pickle_if_needed():
# Define the function that will tessellate each element in parallel
def _tessellate_element(element: TopoDS_Shape, tolerance, angular_tolerance, uv):
def _tessellate_element(element: TopoDS_Shape, tolerance: float, angular_tolerance: float):
if isinstance(element, TopoDS_Face):
return _tessellate_face(element, tolerance, angular_tolerance, uv), element
return _tessellate_face(element, tolerance, angular_tolerance), element
elif isinstance(element, TopoDS_Edge):
return _tessellate_edge(element, angular_tolerance, angular_tolerance), element
@@ -115,14 +109,13 @@ TriMesh = Tuple[list[Vector], list[Tuple[int, int, int]]]
def _tessellate_face(
ocp_face: TopoDS_Face,
tolerance: float = 0.1,
angular_tolerance: float = 0.1,
uv: Optional[UVMode] = None,
angular_tolerance: float = 0.1
) -> TriMesh:
"""Tessellate a face into a list of triangle vertices and a list of triangle indices"""
face = Face(ocp_face)
tri_mesh = face.tessellate(tolerance, angular_tolerance)
# TODO: UV mapping
# TODO: UV mapping of each face
return tri_mesh