#!/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()