several fixes to tessellation and extremely fast (in comparison) initial load of CAD objects

This commit is contained in:
Yeicor
2024-02-19 20:53:10 +01:00
parent 656daf1bf3
commit c9e8bde9ca
9 changed files with 183 additions and 152 deletions

View File

@@ -1,5 +1,4 @@
import numpy as np
from build123d import Vector
from pygltflib import *
_checkerboard_image_bytes = base64.decodebytes(
@@ -15,41 +14,45 @@ class GLTFMgr:
nodes=[Node(mesh=0)],
meshes=[Mesh(primitives=[])],
accessors=[],
bufferViews=[
BufferView(buffer=0, byteLength=len(_checkerboard_image_bytes), byteOffset=0, target=ELEMENT_ARRAY_BUFFER)],
bufferViews=[BufferView(buffer=0, byteLength=len(_checkerboard_image_bytes), byteOffset=0)],
buffers=[Buffer(byteLength=len(_checkerboard_image_bytes))],
samplers=[Sampler(magFilter=NEAREST)],
textures=[Texture(source=0, sampler=0)],
images=[Image(bufferView=0, mimeType='image/png')],
materials=[
Material(name="face", pbrMetallicRoughness=PbrMetallicRoughness(
Material(name="face", alphaCutoff=None, pbrMetallicRoughness=PbrMetallicRoughness(
baseColorTexture=TextureInfo(index=0), baseColorFactor=[1, 1, 0.5, 1])),
Material(name="edge", pbrMetallicRoughness=PbrMetallicRoughness(
baseColorTexture=TextureInfo(index=0), baseColorFactor=[0, 0, 0.5, 1])),
Material(name="vertex", pbrMetallicRoughness=PbrMetallicRoughness(
baseColorTexture=TextureInfo(index=0), baseColorFactor=[0.5, 0.5, 0.5, 1])),
Material(name="selected", pbrMetallicRoughness=PbrMetallicRoughness(
baseColorTexture=TextureInfo(index=0), baseColorFactor=[1, 0, 0, 1])),
Material(name="edge", alphaCutoff=None, pbrMetallicRoughness=PbrMetallicRoughness(
baseColorFactor=[0, 0, 0.5, 1])),
Material(name="vertex", alphaCutoff=None, pbrMetallicRoughness=PbrMetallicRoughness(
baseColorFactor=[0.5, 0.5, 0.5, 1])),
# Material(name="selected", alphaCutoff=None, pbrMetallicRoughness=PbrMetallicRoughness(
# baseColorTexture=TextureInfo(index=0), baseColorFactor=[1, 0, 0, 1])),
],
)
def __init__(self):
self.gltf.set_binary_blob(_checkerboard_image_bytes)
def add_face(self, vertices: np.ndarray, indices: np.ndarray, tex_coord: np.ndarray):
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=0)
def add_edge(self, vertices: np.ndarray):
def add_edge(self, vertices_raw: List[Tuple[float, float, float]]):
"""Add an edge to the GLTF as a new primitive of the unique mesh"""
indices = np.array(list(map(lambda i: [i, i + 1], range(len(vertices) - 1))), dtype=np.uint8)
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=1)
def add_vertex(self, vertex: Vector):
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.X, vertex.Y, vertex.Z]])
indices = np.array([[0]], dtype=np.uint8)
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=2)
@@ -63,9 +66,14 @@ class GLTFMgr:
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.uint8)
indices = indices.astype(np.uint32)
indices_blob = indices.flatten().tobytes()
# Check that all vertices are referenced by the indices
assert indices.max() == len(vertices) - 1, f"{indices.max()} != {len(vertices) - 1}"
assert indices.min() == 0
assert np.unique(indices.flatten()).size == len(vertices)
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)
@@ -86,7 +94,7 @@ class GLTFMgr:
self.gltf.accessors.extend([it for it in [
Accessor(
bufferView=buffer_view_base,
componentType=UNSIGNED_BYTE,
componentType=UNSIGNED_INT,
count=indices.size,
type=SCALAR,
max=[int(indices.max())],

View File

@@ -27,7 +27,7 @@ if __name__ == "__main__":
# Start an offline "server" to merge the CAD part of the logo with the animated GLTF part of the logo
os.environ['YACV_DISABLE_SERVER'] = '1'
from __init__ import show_object, server
from yacv_server import show_object, server
ASSETS_DIR = os.getenv('ASSETS_DIR', os.path.join(os.path.dirname(__file__), '..', 'assets'))
# Add the CAD part of the logo to the server

View File

@@ -2,15 +2,17 @@ import hashlib
import io
import re
import numpy as np
from OCP.BRep import BRep_Tool
from OCP.BRepAdaptor import BRepAdaptor_Curve
from OCP.GCPnts import GCPnts_TangentialDeflection
from OCP.TopExp import TopExp
from OCP.TopLoc import TopLoc_Location
from OCP.TopTools import TopTools_IndexedMapOfShape
from OCP.TopoDS import TopoDS_Face, TopoDS_Edge, TopoDS_Shape, TopoDS_Vertex
from build123d import Shape, Vertex
from pygltflib import GLTF2
import mylogger
from gltf import GLTFMgr
@@ -29,20 +31,16 @@ def tessellate(
mgr = GLTFMgr()
shape = Shape(ocp_shape)
# Triangulate all faces at the same time
# shape.mesh(tolerance, angular_tolerance)
_tessellate_face(mgr, shape.wrapped)
# Perform tessellation tasks
# if faces:
# for face in shape.faces():
# _tessellate_face(mgr, face.wrapped)
# if edges:
# for edge in shape.edges():
# _tessellate_edge(mgr, edge.wrapped, angular_tolerance, angular_tolerance)
# if vertices:
# for vertex in shape.vertices():
# _tessellate_vertex(mgr, vertex.wrapped)
if faces:
for face in shape.faces():
_tessellate_face(mgr, face.wrapped, tolerance, angular_tolerance)
if edges:
for edge in shape.edges():
_tessellate_edge(mgr, edge.wrapped, angular_tolerance, angular_tolerance)
if vertices:
for vertex in shape.vertices():
_tessellate_vertex(mgr, vertex.wrapped)
return mgr.gltf
@@ -54,24 +52,22 @@ def _tessellate_face(
angular_tolerance: float = 0.1
):
face = Shape(ocp_face)
# loc = TopLoc_Location()
# poly = BRep_Tool.Triangulation_s(face.wrapped, loc)
# if poly is None:
# mylogger.logger.warn("No triangulation found for face")
# return GLTF2()
face.mesh(tolerance, angular_tolerance)
poly = BRep_Tool.Triangulation_s(face.wrapped, TopLoc_Location())
if poly is None:
mylogger.logger.warn("No triangulation found for face")
return GLTF2()
tri_mesh = face.tessellate(tolerance, angular_tolerance)
# Get UV of each face from the parameters
# uv = [
# [v.X(), v.Y()]
# for v in (poly.UVNode(i) for i in range(1, poly.NbNodes() + 1))
# ]
uv = []
uv = [
(v.X(), v.Y())
for v in (poly.UVNode(i) for i in range(1, poly.NbNodes() + 1))
]
vertices = np.array(list(map(lambda v: [v.X, v.Y, v.Z], tri_mesh[0])))
indices = np.array(tri_mesh[1])
tex_coord = np.array(uv)
mgr.add_face(vertices, indices, tex_coord)
vertices = [(v.X, v.Y, v.Z) for v in tri_mesh[0]]
indices = tri_mesh[1]
mgr.add_face(vertices, indices, uv)
def _tessellate_edge(
@@ -84,37 +80,34 @@ def _tessellate_edge(
discretizer = GCPnts_TangentialDeflection(curve, angular_deflection, curvature_deflection)
assert discretizer.NbPoints() > 1, "Edge is too small??"
# TODO: get and apply transformation??
# add vertices
vertices: list[list[float]] = [
[v.X(), v.Y(), v.Z()]
vertices = [
(v.X(), v.Y(), v.Z())
for v in (
discretizer.Value(i) # .Transformed(transformation)
for i in range(1, discretizer.NbPoints() + 1)
)
]
mgr.add_edge(np.array(vertices))
mgr.add_edge(vertices)
def _tessellate_vertex(mgr: GLTFMgr, ocp_vertex: TopoDS_Vertex):
c = Vertex(ocp_vertex).center()
mgr.add_vertex(c)
mgr.add_vertex((c.X, c.Y, c.Z))
def _hashcode(obj: TopoDS_Shape) -> str:
"""Utility to compute the hash code of a shape recursively without the need to tessellate it"""
# NOTE: obj.HashCode(MAX_HASH_CODE) is not stable across different runs of the same program
# This is best-effort and not guaranteed to be unique
data = io.BytesIO()
map_of_shapes = TopTools_IndexedMapOfShape()
TopExp.MapShapes_s(obj, map_of_shapes)
hasher = hashlib.md5(usedforsecurity=False)
for i in range(1, map_of_shapes.Extent() + 1):
sub_shape = map_of_shapes.FindKey(i)
sub_data = io.BytesIO()
TopoDS_Shape.DumpJson(sub_shape, sub_data)
val = sub_data.getvalue()
val = re.sub(b'"this": "[^"]*"', b'', val) # Remove memory address
data.write(val)
to_hash = data.getvalue()
return hashlib.md5(to_hash, usedforsecurity=False).hexdigest()
hasher.update(val)
return hasher.hexdigest()