118 lines
4.5 KiB
Python
118 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
GPU-Resident Signed Distance Field (SDF) CSG & Infill Example
|
|
=============================================================
|
|
Demonstrates chaining 3D mesh slicing, Boolean CSG operations (Union, Intersection,
|
|
Difference, Smooth Blend), constant-distance offsetting, volume shelling, procedural
|
|
3D gyroid infill generation, and contour vectorization entirely in GPU VRAM without
|
|
host-to-device memory roundtrips.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
import numpy as np
|
|
|
|
# Ensure local build is in path
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "python"))
|
|
import cudaslicer
|
|
import trimesh
|
|
|
|
|
|
def main():
|
|
print("=" * 70)
|
|
print(" CUDASlicer: GPU Signed Distance Field (SDF) & CSG Engine")
|
|
print("=" * 70)
|
|
|
|
if not cudaslicer.has_cuda():
|
|
print("[Error] CUDA-capable GPU is required for this demo.")
|
|
return
|
|
|
|
# 1. Load or generate 3D models
|
|
stl_path = os.path.join(os.path.dirname(__file__), "..", "benchy.stl")
|
|
if os.path.exists(stl_path):
|
|
print(f"\n[1] Loading main model: {stl_path}")
|
|
mesh_a = trimesh.load(stl_path)
|
|
else:
|
|
print("\n[1] Generating synthetic main cylinder...")
|
|
mesh_a = trimesh.creation.cylinder(radius=15.0, height=30.0)
|
|
|
|
# Generate a modifier box volume to subtract from the model
|
|
print("[1] Generating modifier cutting box...")
|
|
mesh_b = trimesh.creation.box(extents=(12.0, 60.0, 25.0))
|
|
mesh_b.apply_translation((0.0, 0.0, 15.0))
|
|
|
|
# 2. Direct GPU Slicing to SDF Stacks
|
|
layer_height_mm = 0.20
|
|
pitch_mm = 0.10
|
|
|
|
t0 = time.perf_counter()
|
|
sdf_a = cudaslicer.slice_mesh_to_sdf(mesh_a, layer_height=layer_height_mm, pitch_mm=pitch_mm)
|
|
sdf_b = cudaslicer.slice_mesh_to_sdf(mesh_b, layer_height=layer_height_mm, pitch_mm=pitch_mm)
|
|
t_slice = (time.perf_counter() - t0) * 1000.0
|
|
|
|
print(f"\n[2] Direct GPU Slicing & Signed Distance Generation ({t_slice:.2f} ms):")
|
|
print(f" - Main Model Stack: {sdf_a}")
|
|
print(f" Layer Z range (µm): {sdf_a.layer_z_um[0]} µm to {sdf_a.layer_z_um[-1]} µm ({len(sdf_a.layer_z_um)} layers)")
|
|
print(f" - Modifier Box Stack: {sdf_b}")
|
|
print(f" Layer Z range (µm): {sdf_b.layer_z_um[0]} µm to {sdf_b.layer_z_um[-1]} µm ({len(sdf_b.layer_z_um)} layers)")
|
|
|
|
# 3. GPU-Resident Boolean Operations (Zero CPU transfers)
|
|
t0 = time.perf_counter()
|
|
# Chained expression: (Model A \ (Modifier B + 0.5mm offset))
|
|
modifier_expanded = sdf_b.offset(delta_mm=0.5)
|
|
sdf_carved = sdf_a.difference(modifier_expanded)
|
|
t_csg = (time.perf_counter() - t0) * 1000.0
|
|
|
|
print(f"\n[3] Chained GPU CSG (Carve / Subtraction) ({t_csg:.2f} ms):")
|
|
print(f" - Result Stack: {sdf_carved}")
|
|
print(f" - Automatic spatial bounding grid expansion: {sdf_carved.shape}")
|
|
|
|
# 4. Procedural 3D Gyroid Infill Generation on GPU
|
|
t0 = time.perf_counter()
|
|
sdf_gyroid = sdf_carved.generate_infill(
|
|
pattern="gyroid",
|
|
cell_size_mm=4.0,
|
|
density=0.25,
|
|
wall_offset_mm=1.20
|
|
)
|
|
t_infill = (time.perf_counter() - t0) * 1000.0
|
|
|
|
print(f"\n[4] GPU Procedural Gyroid Infill Generation ({t_infill:.2f} ms):")
|
|
print(f" - Infill Stack: {sdf_gyroid}")
|
|
|
|
# 5. Vectorize directly to polygon perimeters and infill contours
|
|
t0 = time.perf_counter()
|
|
layer_results = sdf_gyroid.vectorize(
|
|
perimeters_count=2,
|
|
layer_width=0.45,
|
|
export_infill_mask=True
|
|
)
|
|
t_vec = (time.perf_counter() - t0) * 1000.0
|
|
|
|
total_contours = sum(
|
|
sum(len(p["contours"]) for p in l.perimeters) + len(l.infill_contours)
|
|
for l in layer_results
|
|
)
|
|
|
|
print(f"\n[5] Vectorization to Slicer Contours ({t_vec:.2f} ms):")
|
|
print(f" - Total Layers Output: {len(layer_results)}")
|
|
print(f" - Total Vector Contours Extracted: {total_contours}")
|
|
|
|
# Sample mid layer
|
|
mid_idx = len(layer_results) // 2
|
|
sample_layer = layer_results[mid_idx]
|
|
print(f"\n[6] Sample Mid Layer (Layer {mid_idx}):")
|
|
print(f" - Z Height: {sample_layer.z:.3f} mm ({sample_layer.z_um} µm)")
|
|
print(f" - Perimeters: {len(sample_layer.perimeters)}")
|
|
for i, p in enumerate(sample_layer.perimeters):
|
|
print(f" * Perimeter {i+1} (offset {p['offset_mm']:.2f} mm): {len(p['contours'])} contour loop(s)")
|
|
print(f" - Infill Contours: {len(sample_layer.infill_contours)} contour loop(s)")
|
|
|
|
print("\n" + "=" * 70)
|
|
print(" Pipeline completed successfully with 100% GPU-resident computation!")
|
|
print("=" * 70)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|