mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 22:24:17 +01:00
Migrate server to poetry and build glb models for faces and edges
This commit is contained in:
2110
poetry.lock
generated
Normal file
2110
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
90
yacv_server/gltf.py
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user