cuda-accelerated SDF slicing
Find a file
2026-09-26 17:26:43 +03:00
examples cudaslicer 2026-09-26 14:20:07 +03:00
include up 2026-09-26 16:15:44 +03:00
python/cudaslicer up 2026-09-26 16:15:44 +03:00
src up 2026-09-26 16:15:44 +03:00
.gitignore new 2026-09-25 18:09:19 +03:00
benchmark.py benchmark 2026-09-26 17:26:43 +03:00
benchy.stl new 2026-09-25 18:09:19 +03:00
build.sh up 2026-09-26 16:15:44 +03:00
CMakeLists.txt up 2026-09-26 16:15:44 +03:00
Makefile up 2026-09-26 16:15:44 +03:00
pyproject.toml up 2026-09-26 16:15:44 +03:00
README.md up 2026-09-26 16:15:44 +03:00
setup.py up 2026-09-26 16:15:44 +03:00

CUDA Slicer: GPU-Accelerated Slicing & Polygon Offsetting Library

Python Version CUDA License: 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:

# Clone the repository
cd cudaslicer

# Install directly with pip
pip install .

B. Build Wheel (.whl)

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

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)

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

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

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:

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)

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:

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:

#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.