cudaslicer/README.md
2026-09-26 16:15:44 +03:00

305 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CUDA Slicer: GPU-Accelerated Slicing & Polygon Offsetting Library
[![Python Version](https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-blue)](https://pypi.org/)
[![CUDA](https://img.shields.io/badge/CUDA-11.x%20%7C%2012.x-green.svg)](https://developer.nvidia.com/cuda-toolkit)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
A high-performance C/C++ and CUDA library with native Python bindings for **Linux & Windows** providing GPU-accelerated 2D/3D polygon offsetting, Jump Flooding Signed Distance Fields (JFA / SDF), and slicing for 3D printing, CAM, and computational geometry.
---
## 🚀 Benchmark Summary (3DBenchy, 239 Layers @ 0.20 mm)
Tested on **NVIDIA RTX A4500 Laptop GPU (16 GB VRAM)** vs **12th Gen Intel Core i7 CPU**:
| Pipeline | Total Time (Mesh $\to$ Offset Contours) | Slicing / Offsetting Rate | Speedup vs PrusaSlicer |
| :--- | :---: | :---: | :---: |
| **100% GPU Direct Mesh Slicing (0.10 mm Pitch)** | **74.4 ms** | **3,212 layers/sec** | **57.47x Faster** |
| **100% GPU Direct Mesh Slicing (0.05 mm Pitch)** | **190.9 ms** | **1,252 layers/sec** | **22.55x Faster** |
| **Hybrid CPU Slicing + GPU JFA (0.05 mm Pitch)** | 739.4 ms | 323 layers/sec | 5.82x Faster |
| **PrusaSlicer CLI Headless (CPU Baseline)** | 4,305.7 ms | 55 layers/sec | 1.00x (Baseline) |
> ⚡ **Key Optimizations Applied:**
> - **Stage 6 (Marching Squares Vectorization):** Reduced from **72.5 ms down to 35.4 ms (2.05x faster / 6,751 layer/s)** via zero-allocation discrete edge-table addressing, thread-local buffer reuse, and on-the-fly collinear point decimation (>95% vertex reduction).
> - **Stage 5 (D2H Transfer):** Reduced from **136.3 ms down to 4.5 ms (30x–48x faster)** via GPU 32-bit bit-packing + page-locked pinned host DMA (`cudaMallocHost`).
> - **Stage 3 (GPU JFA Distance Field):** Reduced from **118.9 ms down to 52.8 ms (2.25x faster)** via perimeter-band limited steps and integer arithmetic.
---
## 🐍 Python Package Integration
### 1. Installation
#### A. Install from Source (Linux & Windows)
Make sure you have NVIDIA CUDA Toolkit (11.5+ or 12.x) and a C++ compiler (GCC on Linux, MSVC 2019/2022 on Windows) installed:
```bash
# Clone the repository
cd cudaslicer
# Install directly with pip
pip install .
```
#### B. Build Wheel (.whl)
```bash
python -m pip install --upgrade build wheel
python -m build --wheel
# The wheel will be generated in dist/
# e.g., dist/cudaslicer-1.0.0-cp310-cp310-linux_x86_64.whl
# or dist/cudaslicer-1.0.0-cp310-cp310-win_amd64.whl
```
---
### 2. Python API Quickstart
#### A. Backend Selection & GPU Capability Check
```python
import cudaslicer
# 1. Check if a CUDA GPU is present
if cudaslicer.has_cuda():
print("CUDA GPU is active!")
else:
print("Running in multi-threaded CPU compatibility mode.")
# 2. Control compute backend globally ('auto', 'cuda', 'cpu')
cudaslicer.set_backend("auto") # Auto: use CUDA if available, fallback to CPU
```
#### B. Offsetting 2D Polygon Contours (Single Layer or Multi-Layer)
```python
import numpy as np
import cudaslicer
# Define a polygon (e.g. 50x50 mm square)
poly = np.array([
[10.0, 10.0],
[60.0, 10.0],
[60.0, 60.0],
[10.0, 60.0]
], dtype=np.float32)
# Generate 2 inward perimeters (at -0.5*w and -1.5*w) and infill mask (at -2.0*w)
# pitch_mm defaults to 0.10 mm, backend defaults to 'auto'
layer = cudaslicer.offset_polygons(
layers=[poly],
perimeters_count=2,
layer_width=0.45,
pitch_mm=0.10,
backend="auto" # 'auto', 'cuda', or 'cpu'
)
# Unpack directly
perimeters, infill_mask = layer
print(f"Outer Perimeter 1 (-0.225mm): {len(perimeters[0])} loop(s)")
print(f"Inner Perimeter 2 (-0.675mm): {len(perimeters[1])} loop(s)")
print(f"Infill Region Mask (-0.900mm): shape {infill_mask.shape}, {infill_mask.sum()} px")
```
#### C. Offsetting 2D / 3D Occupancy Masks Directly
```python
import numpy as np
import cudaslicer
# Create a 2D binary grid (e.g., 200 x 200 pixels)
mask = np.zeros((200, 200), dtype=np.uint8)
mask[40:160, 40:160] = 1
# Compute Euclidean perimeters and infill mask
perimeters, infill_mask = cudaslicer.offset_masks(
masks=mask,
perimeters_count=2,
layer_width=0.45,
pitch_mm=0.10,
backend="auto"
)
print("Perimeters shape:", perimeters.shape) # (2, 200, 200)
print("Infill mask shape:", infill_mask.shape) # (200, 200)
```
#### D. End-to-End Mesh Slicing with Custom First Layer Height
```python
import trimesh
import cudaslicer
mesh = trimesh.load("benchy.stl")
# 100% GPU Slicing & Perimeter Offsetting
layers = cudaslicer.slice_mesh(
mesh_or_triangles=mesh,
layer_height=0.20,
first_layer_height=0.25, # custom first layer height
perimeters_count=2,
layer_width=0.45,
pitch_mm=0.10 # default 0.10
)
print(f"Sliced {len(layers)} layers in GPU VRAM!")
layer_0 = layers[0] # First layer at Z = 0.25 mm
perims, infill_mask = layer_0
print(f"Layer 0 (Z = {layer_0.z} mm): {len(perims)} perimeters, infill mask: {infill_mask.shape}")
```
#### E. GPU Signed Distance Fields (SDF), Boolean CSG & Gyroid Infill
Execute boolean CSG operations (Union, Difference, Intersection, Smooth Blending) and 3D procedural infill directly on GPU SDF stacks with zero CPU memory roundtrips:
```python
import trimesh
import cudaslicer
mesh_a = trimesh.load("benchy.stl")
cutter_box = trimesh.creation.box(extents=(12.0, 60.0, 25.0))
# 1. Direct GPU slice to continuous SDF stacks
sdf_a = cudaslicer.slice_mesh_to_sdf(mesh_a, layer_height=0.20, pitch_mm=0.10)
sdf_b = cudaslicer.slice_mesh_to_sdf(cutter_box, layer_height=0.20, pitch_mm=0.10)
# 2. Boolean CSG subtraction entirely in GPU VRAM
sdf_carved = sdf_a.difference(sdf_b.offset(delta_mm=0.5))
# 3. GPU procedural 3D gyroid infill generation
sdf_infill = sdf_carved.generate_infill(pattern="gyroid", cell_size_mm=4.0, density=0.25)
# 4. Direct vectorization with sub-pixel LERP and RDP polyline simplification
layers = sdf_infill.vectorize(
perimeters_count=2,
layer_width=0.45,
subpixel_lerp=True, # Natural arbitrary angles (no 45° quantization)
simplify_tolerance_mm=0.02 # RDP polyline decimation
)
```
---
## 📐 Sub-Pixel Linear Interpolation (LERP) vs. Standard Marching Squares
### The Problem: 45° & 90° Angle Quantization in Standard Marching Squares
Standard discrete Marching Squares (and binary mask polygon extractors) evaluates binary cell states (0 or 1 at each voxel corner). When an edge intersects a boundary, it snaps the intersection point to the **geometric midpoint** of the grid cell edge ($t = 0.5$):
```
Discrete Marching Squares (Snap to t = 0.5):
(x, y+1) +---------+ (x+1, y+1)
| * | <- Midpoint snapping forces line segments to connect
| / | only at (0.5, 0), (0, 0.5), (1, 0.5), etc.
| * | <- Result: angles restricted to 0°, 45°, 90°, 135°!
(x, y) +---------+ (x+1, y)
```
Because segment vectors between grid edge midpoints are strictly constrained to Cartesian diagonals, diagonal walls and curves are quantized into jagged 45° chamfers and stair-stepped facets.
---
### The Solution: Continuous SDF Linear Interpolation (LERP)
In CUDASlicer, the vectorizer uses continuous floating-point Signed Distance Field (SDF) values ($d_0, d_1$) at the cell vertices to solve for the exact sub-pixel boundary crossing where the distance field equals the target isovalue ($iso\_val = 0.0$ for outer perimeter, $-d$ for offset contours):
$$t = \frac{iso\_val - d_0}{d_1 - d_0}, \quad t \in [0.0, 1.0]$$
```
Continuous SDF LERP (Sub-Pixel Placement):
(x, y+1) +---------+ (x+1, y+1)
| |
| * | <- Exact root crossing t = (0 - d0) / (d1 - d0)
| / | <- Arbitrary natural angles (e.g. 12.3°, 37.8°, smooth curves)
(x, y) +-----*---+ (x+1, y)
```
- **Horizontal Edge:** $P_x = \text{min\_x} + (x + t) \cdot \text{pitch}, \quad P_y = \text{min\_y} + y \cdot \text{pitch}$
- **Vertical Edge:** $P_x = \text{min\_x} + x \cdot \text{pitch}, \quad P_y = \text{min\_y} + (y + t) \cdot \text{pitch}$
Since $t$ varies continuously in $[0, 1]$, vertices fall at their true Euclidean geometric boundaries, eliminating jagged 45° artifacts.
---
### Polyline Simplification & Decimation
1. **On-the-Fly Collinear Decimation:** During loop traversal, collinear vertices ($\Delta x_1 \Delta y_2 - \Delta y_1 \Delta x_2 \approx 0$) along straight spans are pruned on the fly with zero memory overhead.
2. **Ramer-Douglas-Peucker (RDP) Polyline Simplification:** Traversed closed loops are post-processed with `simplify_contour_rdp(loop, tolerance_mm)` (default tolerance `0.02 mm`), removing sub-pixel jitter while preserving smooth curved contours and exact sharp corners.
---
## 🛠️ Building Standalone C/C++ Libraries (Windows & Linux)
### Linux Build (`libcudaslicer_offset.so` & `.a`)
```bash
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build . -j$(nproc)
```
Outputs:
- Shared Library: `build/lib/libcudaslicer_offset.so`
- Static Library: `build/lib/libcudaslicer_offset.a`
- C Benchmark CLI: `build/bin/gpu_slicer_c_bench`
### Windows Build (`cudaslicer_offset.dll` & `.lib`)
Open **x64 Native Tools Command Prompt for VS 2022** and run:
```cmd
mkdir build
cd build
cmake .. -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release
cmake --build . --config Release
```
Outputs:
- DLL: `build/lib/Release/cudaslicer_offset.dll`
- Import Lib: `build/lib/Release/cudaslicer_offset.lib`
- Benchmark: `build/bin/Release/gpu_slicer_c_bench.exe`
---
## ⚙️ Cross-Platform C API Reference (`include/gpu_offset.h`)
The public header provides a portable C API with `__declspec(dllexport)` / `__declspec(dllimport)` on Windows and default symbol visibility on Linux:
```c
#include "gpu_offset.h"
// 1. Initialize GPU
gpu_offset_init(0);
// 2. Configure slicing & offsetting parameters
GpuOffsetConfig config = {
.pitch_mm = 0.10f, // default 0.10 mm
.pad_mm = 1.5f,
.perimeters_count = 2, // 2 perimeter passes
.layer_width = 0.45f, // 0.45 mm extrusion width
.first_layer_height = 0.25f, // 0.25 mm first layer height
.export_infill_mask = 1, // generate 2D binary infill mask
.use_gpu_rasterizer = 1
};
// 3. 100% Direct GPU Mesh Slicing & Offsetting
GpuLayerResult* results = NULL;
size_t num_layers = 0;
GpuOffsetStats stats = {0};
gpu_offset_slice_mesh_and_offset(
triangles, num_triangles,
min_x, min_y, min_z, max_x, max_y, max_z,
0.20f, 0.25f, &config,
&results, &num_layers, &stats
);
// Access perimeters and infill mask
for (size_t l = 0; l < num_layers; ++l) {
float z = results[l].z_height;
size_t num_perims = results[l].num_perimeters; // 2
GpuContour* infill_cnts = results[l].infill.contours;
uint8_t* infill_mask = results[l].infill.mask;
}
// 4. Cleanup
gpu_offset_free_results(results, num_layers);
gpu_offset_cleanup();
```
---
## 🔄 Automated CI/CD (GitHub Actions)
- Building Linux Python wheels (`manylinux_2_35_x86_64`) across Python 3.9, 3.10, 3.11, 3.12.
- Building Windows x64 Python wheels (`win_amd64`) across Python 3.9, 3.10, 3.11, 3.12.
- Building standalone Linux `.so` and Windows `.dll` binaries as downloadable CI artifacts.
---
## 📄 License
MIT License. Free for commercial and research use.