Slices mesh geometry into layers consisting of curves. Written in C++ as an addon for openFrameworks. Includes basic rectilinear infill generation and a bridge to ofxGCode for G-code output.
The slicing algorithm goes something like this:
- Create a list containing all triangles of the mesh model.
- Mesh slicing: Calculate triangle intersection points on each plane.
- Construct contours: Create polygons from the intersection points for each plane.
- Make sense of the polygons (Clockwise/Counterclockwise).
Getting the triangles was a bit of a struggle in openFrameworks. To import .stl files, we use the ofxAssimpModelLoader addon. It took some tweaking to get the triangle faces with their belonging vertices extracted from the assimp class. All the triangles are sorted in ascending order in terms of the lowest point in the triangle.
It would probably be easier to use some kind of existing C++ framework for geometry like CGAL.
Once we have the triangles it's time to calculate the intersection points on each layer. Have a look at this figure.
There are basically three different situations:
- The triangle is located on the topside of the layer plane.
- The triangle is intersecting with the plane.
- The triangle is underneath the plane. This means that the slicer is finished processing it.
To improve the speed of the algorithm the triangles that are finished processing are removed from the triangle list that is used in the calculation. This condition applies when the entire triangle is located underneath the layer plane. See figure.
After calculating the intersection points for each layer, the slicer needs to assemble them into closed contour polygons. This is done in createContours() using a hash-table approach:
-
Build an adjacency hash. Each line segment produced by the triangle intersection step has two endpoints. Both endpoints are inserted into a hash map keyed by their XY(Z) position (
vec2key). Each key maps to its two neighbouring vertices, forming a doubly-linked chain of edges. -
Walk the chains. Starting from an arbitrary entry in the hash, the algorithm picks a direction and follows the chain of neighbours (
addToLoop), removing visited entries as it goes. When the walk returns to the starting vertex the contour is closed. -
Repeat. The process repeats until the hash map is empty, producing one
ofPolylineper closed contour. All contours for a given layer are stored inlayer.contours.
This approach efficiently reconstructs polygon boundaries from an unordered soup of edge segments, handling both outer perimeters and inner holes.
The Infill class (Infill.h / Infill.cpp) generates basic rectilinear fill lines for closed contour polygons.
Infill infill;
infill.lineSpacing = 2.0f; // mm between lines
// or set by density percentage:
infill.setDensity(20.0f); // 20% infill
auto fillLines = infill.generate(contour, layerIndex);- Alternating direction: Even layers produce horizontal lines, odd layers produce vertical lines (offset by
baseAngle). - Scanline clipping: Lines are clipped to the contour boundary using a scanline intersection algorithm.
- Safety: A
lineSpacing <= 0guard prevents infinite loops.
The SlicerToGCode class (SlicerToGCode.h / SlicerToGCode.cpp) converts slicer output directly into G-code via ofxGCode.
SlicerToGCode bridge;
bridge.feedRate = 600.0f; // cutting feed rate (mm/min)
bridge.travelFeedRate = 3000.0f; // rapid travel rate (mm/min)
bridge.safeZ = 5.0f; // retract height for travel moves
// Convert layers to an ofxGCode object
ofxGCode gcode = bridge.convert(slicer.layers);
gcode.save3D("output.nc", bridge.safeZ);
// Or get the G-code as a string directly
std::string gcodeStr = bridge.generateGCodeString(slicer.layers);The bridge walks each layer's contours and jobs, converts them to GLine segments with per-line Z values, and delegates the actual G-code formatting to ofxGCode::toGCodeString(). Travel moves (pen-up / retract to safe Z) are inserted automatically between disconnected contours.
Reusing the slicer in your openFrameworks project should be pretty straightforward:
- Clone the repo into your local addons folder.
- Use the openFrameworks projectGenerator to include
ofxSlicerin your project. - Add
ofxAssimpModelLoader(for STL loading) and optionallyofxGCode(for G-code output) to youraddons.make. - Create an
ofxSlicerobject, load an STL, and start slicing:
ofxSlicer slicer;
slicer.layerHeight = 0.2;
slicer.loadFile("model.stl");
slicer.startSlice(); // runs in a background thread
// When slicer.sliceFinished == true:
for (auto& layer : slicer.layers) {
// layer.contours -- closed perimeter polylines
// layer.jobs -- additional toolpaths (infill, etc.)
}