3dSynth

Tutorials / Custom G-code

Open in the interactive tutorials reader

---
title: Tutorial 43 Building a Custom Slicer Core
description: The ultimate G-code challenge.
---

Tutorial 43 Building a Custom Slicer Core

1. Lesson Header

2. Concept Introduction

The Slicing Pipeline.
How does Cura/PrusaSlicer work?
1. Import: Read STL (Triangles).
2. Slice: Intersect triangles with a Plane at Z height. Result: Lines.
3. Contour: Connect lines into closed Polygons.
4. Offset: Shrink polygons by Nozzle_Width / 2 (Inset).
5. Path: Generate G-code for the inset polygons.
6. Infill: Fill the inside.

We will build Steps 1-5 for a single-wall vase.

3. Machine State Explanation

Plane-Triangle Intersection.
A triangle has 3 vertices (V1, V2, V3).
A plane has height Z.
If all 3 vertices are above/below Z -> No intersection.
If 1 is above and 2 below (or vice versa) -> The plane cuts the triangle. The intersection is a Line Segment.

4. Command Breakdown

5. Minimal Working Example

The Triangle Slice.
V1=(0,0,0), V2=(10,0,0), V3=(0,10,10).
Slice at Z=5.
Edge V1-V3 crosses Z=5 at (0,5,5).
Edge V2-V3 crosses Z=5 at (5,5,5).
Segment: (0,5) to (5,5).

6. Visual Representation

Interactive preview is available in the interactive reader.

7. Build Exercise

Task: Write a Python Slicer.
Input: cube.stl.
Output: cube.gcode.
Mode: Vase (Spiralize).

Algorithm:
1. Load STL.
2. Find Z min/max.
3. Loop Z from min to max by layer_height.
4. Find all intersecting segments.
5. Chain segments into a loop (Nearest Neighbor).
6. Write G-code points.

8. Deep Insight Section

Manifoldness.
Real STLs are messy. Holes, flipped normals, self-intersections.
Robust slicers spend 50% of their code fixing bad geometry.
Our simple slicer will fail on bad meshes.
Solution: Repair with Netfabb/Meshmixer first.

9. Common Failure Modes

  1. Unsorted Segments: The intersection gives a bag of lines. You must sort them End -> Start to form a continuous path.
  2. Floating Point Errors: Z=10.0000001 might miss a vertex at Z=10. Use an epsilon tolerance.

10. Real-World Application

Non-Planar Slicers.
Simulating 5-axis printing requires slicing with Curved Surfaces instead of flat planes.
The math is the same (Intersection), just harder geometry.

11. Final Clean Version

The Mini Slicer:

import numpy as np
from stl import mesh

def intersect_triangle_plane(v1, v2, v3, z):
    # Check if edges cross Z
    points = []
    
    def get_intersect(p1, p2, z):
        if p2[2] == p1[2]: return None
        t = (z - p1[2]) / (p2[2] - p1[2])
        if 0 <= t <= 1:
            return p1 + t * (p2 - p1)
        return None

    # Check 3 edges
    i1 = get_intersect(v1, v2, z)
    i2 = get_intersect(v2, v3, z)
    i3 = get_intersect(v3, v1, z)
    
    # Collect valid points
    if i1 is not None: points.append(i1)
    if i2 is not None: points.append(i2)
    if i3 is not None: points.append(i3)
    
    # Remove duplicates
    unique = []
    for p in points:
        if not any(np.allclose(p, u) for u in unique):
            unique.append(p)
            
    if len(unique) == 2:
        return (unique[0], unique[1])
    return None

# Main Slicing Loop
my_mesh = mesh.Mesh.from_file('vase.stl')
z_min, z_max = my_mesh.z.min(), my_mesh.z.max()
layer_height = 0.2

with open("sliced.gcode", "w") as f:
    f.write("G28\nG1 Z0.2\n")
    
    for z in np.arange(z_min, z_max, layer_height):
        segments = []
        for i in range(len(my_mesh.vectors)):
            tri = my_mesh.vectors[i]
            seg = intersect_triangle_plane(tri[0], tri[1], tri[2], z)
            if seg: segments.append(seg)
            
        # Sort segments to form a contour
        # (Simplified: Just write them as lines, printer will jump)
        # Real slicer needs sorting!
        
        for s in segments:
            f.write(f"G0 X{s[0][0]:.3f} Y{s[0][1]:.3f}\n")
            f.write(f"G1 X{s[1][0]:.3f} Y{s[1][1]:.3f} E...\n")

    f.write("G28 X0 Y0\n")

12. Stretch Challenge

Challenge: Implement Offsetting.
The contour is the edge of the object.
The nozzle must move inside by Nozzle_Radius.
Calculate the normal vector of each segment and shift it inwards.
This is hard for concave shapes (Self-intersection). Use the Shapely library.