197 lines
7 KiB
Python
197 lines
7 KiB
Python
#!/usr/bin/env python3
|
|
import time
|
|
from pathlib import Path
|
|
import numpy as np
|
|
|
|
import sys
|
|
repo_root = Path(__file__).resolve().parent.parent
|
|
if str(repo_root.parent) not in sys.path:
|
|
sys.path.insert(0, str(repo_root.parent))
|
|
if str(repo_root) not in sys.path:
|
|
sys.path.insert(0, str(repo_root))
|
|
|
|
from unittest.mock import MagicMock
|
|
class DummyNodeSocket: pass
|
|
class DummyNode: pass
|
|
class DummyObject:
|
|
def __init__(self, name="TestObj"):
|
|
self.name = name
|
|
class DummyMesh: pass
|
|
class DummyNodeSocketShader: pass
|
|
class DummyNodeCustomGroup: pass
|
|
class DummyNodeTree: pass
|
|
class DummyOperator: pass
|
|
class DummyMenu: pass
|
|
class DummyPanel: pass
|
|
class DummyPropertyGroup: pass
|
|
|
|
mock_types = MagicMock()
|
|
mock_types.NodeSocket = DummyNodeSocket
|
|
mock_types.Node = DummyNode
|
|
mock_types.Object = DummyObject
|
|
mock_types.Mesh = DummyMesh
|
|
mock_types.NodeSocketShader = DummyNodeSocketShader
|
|
mock_types.NodeCustomGroup = DummyNodeCustomGroup
|
|
mock_types.NodeTree = DummyNodeTree
|
|
mock_types.Operator = DummyOperator
|
|
mock_types.Menu = DummyMenu
|
|
mock_types.Panel = DummyPanel
|
|
mock_types.PropertyGroup = DummyPropertyGroup
|
|
|
|
mock_bpy = MagicMock()
|
|
mock_bpy.context.scene = MagicMock()
|
|
mock_bpy.context.scene.unit_settings.scale_length = 0.001
|
|
mock_bpy.types = mock_types
|
|
|
|
mock_previews = MagicMock()
|
|
mock_previews.ImagePreviewCollection = MagicMock
|
|
|
|
sys.modules['bpy'] = mock_bpy
|
|
sys.modules['bpy.types'] = mock_types
|
|
sys.modules['bpy.props'] = MagicMock()
|
|
sys.modules['bpy.utils'] = MagicMock()
|
|
sys.modules['bpy.utils.previews'] = mock_previews
|
|
sys.modules['bpy.app'] = MagicMock()
|
|
sys.modules['bpy.app.handlers'] = MagicMock()
|
|
|
|
from NodeSlicer.core.cudaslicer_loader import import_cudaslicer
|
|
from NodeSlicer.core.slice import align_path_seam_sharpest, SlicePaths
|
|
from NodeSlicer.infra.blender.blender_mesh_capture import SlicingObject
|
|
|
|
def load_stl(file_path):
|
|
with open(file_path, "rb") as f:
|
|
f.seek(80)
|
|
num_tris = int(np.frombuffer(f.read(4), dtype=np.uint32)[0])
|
|
record_dtype = np.dtype([
|
|
("normal", np.float32, 3),
|
|
("v0", np.float32, 3),
|
|
("v1", np.float32, 3),
|
|
("v2", np.float32, 3),
|
|
("attr", np.uint16),
|
|
])
|
|
data = np.fromfile(f, dtype=record_dtype, count=num_tris)
|
|
tris = np.stack([data["v0"], data["v1"], data["v2"]], axis=1) # (N, 3, 3)
|
|
normals = np.zeros((num_tris, 1, 3), dtype=np.float32)
|
|
mesh = np.concatenate([tris, normals], axis=1) # (N, 4, 3)
|
|
return mesh
|
|
|
|
def profile_node_pipeline():
|
|
stl_path = "/home/nicolas/Workspace/Development/cudaslicer/benchy.stl"
|
|
mesh_data = load_stl(stl_path)
|
|
so = SlicingObject()
|
|
so.mesh = mesh_data
|
|
objects = [so]
|
|
|
|
cudaslicer = import_cudaslicer()
|
|
|
|
layer_height = 0.20
|
|
fl_height = 0.20
|
|
perimeters_count = 2
|
|
layer_width = 0.45
|
|
pitch_mm = 0.10
|
|
pad_mm = 1.5
|
|
backend_choice = "AUTO"
|
|
|
|
# Warmup
|
|
_ = cudaslicer.slice_mesh_to_sdf(mesh_data[:100, :3, :], layer_height=1.0, pitch_mm=0.10)
|
|
|
|
print("==========================================================================")
|
|
print(f" PROFILING SLICENODE_CudaSliceMesh.evaluate_output (Benchy: {len(mesh_data):,} tris)")
|
|
print("==========================================================================")
|
|
|
|
t_total_0 = time.perf_counter()
|
|
|
|
# Step 1: Object merge
|
|
t0 = time.perf_counter()
|
|
merged_object = SlicingObject.merge(objects)
|
|
t_merge = (time.perf_counter() - t0) * 1000.0
|
|
|
|
# Step 2: Mesh array copy & float32 cast
|
|
t0 = time.perf_counter()
|
|
mesh_data_copy = merged_object.mesh.copy()
|
|
tris_3d = mesh_data_copy[:, :3, :].astype(np.float32)
|
|
t_copy = (time.perf_counter() - t0) * 1000.0
|
|
|
|
# Step 3: cudaslicer.slice_mesh_and_offset
|
|
t0 = time.perf_counter()
|
|
layers = cudaslicer.slice_mesh_and_offset(
|
|
mesh_or_triangles=tris_3d,
|
|
layer_height=float(layer_height),
|
|
first_layer_height=float(fl_height),
|
|
perimeters_count=int(perimeters_count),
|
|
layer_width=float(layer_width),
|
|
pitch_mm=float(pitch_mm),
|
|
pad_mm=float(pad_mm),
|
|
return_infill_mask=True,
|
|
backend="cpu" if backend_choice == "CPU" else backend_choice.lower(),
|
|
)
|
|
t_cudaslicer = (time.perf_counter() - t0) * 1000.0
|
|
|
|
# Step 4: Python conversion loop (unpacking contours, column_stack, vstack, align_path_seam_sharpest)
|
|
perim_paths = []
|
|
perim_groups = []
|
|
infill_paths = []
|
|
infill_groups = []
|
|
|
|
t0 = time.perf_counter()
|
|
t_seam_align = 0.0
|
|
t_stack = 0.0
|
|
|
|
for l_idx, layer in enumerate(layers):
|
|
z = float(layer.z)
|
|
for perim in getattr(layer, "perimeters", []):
|
|
for cnt in perim.get("contours", []):
|
|
if len(cnt) < 2:
|
|
continue
|
|
t_st0 = time.perf_counter()
|
|
cnt_3d = np.column_stack([cnt, np.full(len(cnt), z)])
|
|
if not np.allclose(cnt_3d[0], cnt_3d[-1]):
|
|
cnt_3d = np.vstack([cnt_3d, cnt_3d[0]])
|
|
t_stack += (time.perf_counter() - t_st0) * 1000.0
|
|
|
|
t_s0 = time.perf_counter()
|
|
cnt_3d = align_path_seam_sharpest(cnt_3d)
|
|
t_seam_align += (time.perf_counter() - t_s0) * 1000.0
|
|
|
|
perim_paths.append(cnt_3d)
|
|
perim_groups.append(l_idx)
|
|
|
|
for cnt in getattr(layer, "infill_contours", []):
|
|
if len(cnt) < 2:
|
|
continue
|
|
t_st0 = time.perf_counter()
|
|
cnt_3d = np.column_stack([cnt, np.full(len(cnt), z)])
|
|
if not np.allclose(cnt_3d[0], cnt_3d[-1]):
|
|
cnt_3d = np.vstack([cnt_3d, cnt_3d[0]])
|
|
t_stack += (time.perf_counter() - t_st0) * 1000.0
|
|
|
|
t_s0 = time.perf_counter()
|
|
cnt_3d = align_path_seam_sharpest(cnt_3d)
|
|
t_seam_align += (time.perf_counter() - t_s0) * 1000.0
|
|
|
|
infill_paths.append(cnt_3d)
|
|
infill_groups.append(l_idx)
|
|
|
|
t_loop_total = (time.perf_counter() - t0) * 1000.0
|
|
|
|
# Step 5: SlicePaths object instantiation
|
|
t0 = time.perf_counter()
|
|
perim_sp = SlicePaths(perim_paths, groups=perim_groups)
|
|
infill_sp = SlicePaths(infill_paths, groups=infill_groups)
|
|
t_slicepaths = (time.perf_counter() - t0) * 1000.0
|
|
|
|
t_total = (time.perf_counter() - t_total_0) * 1000.0
|
|
|
|
print(f" 1. SlicingObject.merge: {t_merge:7.2f} ms")
|
|
print(f" 2. Mesh copy & astype(float32): {t_copy:7.2f} ms")
|
|
print(f" 3. cudaslicer.slice_mesh_and_offset: {t_cudaslicer:7.2f} ms")
|
|
print(f" 4. Python Loop Total: {t_loop_total:7.2f} ms")
|
|
print(f" • np.column_stack & np.vstack: {t_stack:7.2f} ms")
|
|
print(f" • align_path_seam_sharpest (Python): {t_seam_align:7.2f} ms")
|
|
print(f" 5. SlicePaths instantiation: {t_slicepaths:7.2f} ms")
|
|
print(f"--------------------------------------------------------------------------")
|
|
print(f" TOTAL evaluate_output Wall Time: {t_total:7.2f} ms")
|
|
print("==========================================================================")
|
|
|
|
if __name__ == "__main__":
|
|
profile_node_pipeline()
|