mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 22:24:17 +01:00
several fixes to tessellation and extremely fast (in comparison) initial load of CAD objects
This commit is contained in:
@@ -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())],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user