Why do this?
3D printing slicers are an essential tool for taking a set of imaginary triangles and creating a real, solid hunk of plastic in a similar shape. And honestly, the way they work feels pretty intuitive. They look at your virtual geometry, take a bunch 2D slices like they’re looking at a CT scan, and divide those slices up into a series of thin plastic strings that approximate the thing you originally asked for. But is it really that simple?
Some disclaimers
There are several really great slicers that are already out there, and I did not look at any of them. I ignored these projects for the same reason I didn’t use any coding assistants to help me create my implementation: this is first and foremost a learning exercise. This slicer is also missing a lot of features that make 3D printing really useful as well. Things like supports and dimensional accuracy are pretty critical for actual printing, but they are not considered here. I will consider this slicer a success if it is able to convert a mesh to a series of gcode commands that create a physical object on a printer. I’m treating this in a similar way to the popular “Speed Benchy” challenge: anything generally benchy-shaped is acceptable.
Moving down through the dimensions
Fundamentally, slicing consists of moving down through the dimensions. We start with a 3D object, and in the end we have a series of straight lines to be executed by a printer, with some intermediate work being done in a 2D context. This writeup is organized to reflect this: the first section will cover conversion of a 3D mesh to a series of 2D layers. Then, the second section discusses how to convert these 2D polygons into extruded lines.
Step One: Going to the Deli
Our first challenge is cutting the 3D model into a series of 2D polygonal layers. Since we are working with triangle meshes this boils down to defining a plane, finding all triangles that intersect with that plane, and storing all of the line segments generated by those intersections. Luckily, we’re dealing with pretty simple planes in this case: all planes are offset versions of the x-y axis. To find which triangles intersect with a given z-height, just compute the minimum and maximum z-values of the triangle vertices, and if the given layer height falls within those values, the triangle intersects the plane.
When a triangle and plane intersect, the result is a line segment that’s vertices lie on two edges of the triangle. To find vertices, we just take all three triangle edges and compute their intersections with the plane. Again, we don’t need to bother with computing the intersection between any plane and a line segment, we just need to consider cases where the plane is parallel to the x-y plane. If we parameterize the line segment as an origin and a ray, we can compute how far along the ray a point at the appropriate z-height is located.
This gives us a bunch of line segments, but they aren’t really that useful to us in the current form since they aren’t connected to each other. To figure out what line segments share vertices, we can look at the information from the underlying mesh. Each mesh vertex has an index, and each vertex created by intersecting a line segment with the layer plane has a pair of indices corresponding to the two vertices forming the segment. Line segments that neighbor each other have vertices that have matching ordered pairs of indices. By treating these ordered pairs as keys into a map, we can associate the line segments with each other, linking them into a contiguous polygon.
Admittedly, this is really convoluted and there’s probably a way better way to handle this. Building up adjacency data using something like a half-edge mesh representation would allow you to traverse directly from one triangle to the next instead of making the line segment soup-mess first and then fixing it afterwards.
The final consideration here is handed-ness; to properly establish the inside and outside of a polygon, it matters whether polygons are stored as clockwise or counterclockwise. Assuming the input mesh has consistent normals, we can just store the order of the vertices in the line segment that has a consistent cross-product direction with the surface normal.
Step Two: Coloring Inside the Lines
The second challenge is going from 2D polygons to 1D polymer extrusions. This requires a robust polygon manipulation library. I originally planned to write this myself as well, but after reading up on the subject… no. This subject is a minefield of numerical issues and edge cases and, at the end of the day, I am supposed to be having fun with this. Luckily, this is such a common task that there seems to be a “polygon offsetting / clipping” library in just about every language. There is a fully featured one in Rust, which means whatever reasonably popular language you want to use will probably have one.
First, let’s focus on walls. You could imagine printing a single-extrusion wall by just following the line segments on the polygon. To add more lines, we just offset the polygon inwards by the width of the extrusion.
Next, we want floors and ceilings. These are a bit less straightforward than walls, as we need to consider more than just the current layer to determine what areas need to be printed this way. A part of a layer is a floor if the section on the layer below it is missing, and a ceiling if the layer above is missing. Ok, but how does this get executed as a series of polygon manipulations? We take the layers above and below and compute the boolean union of their areas. Then, we subtract that area from the current layer. The area that we’re left with is the part of the layer exposed to air, which needs to be filled in as a floor or ceiling.
To fill it in, the program computes the bounding box of the layer, generates a bunch of straight lines to represent the extrusion, then clips those lines with the polygon to fill in the area.

In a real slicer, one might want to have multiple layers of ceilings and floors so models have more than a single layer of plastic on their border. This is, in theory, simply a matter of considering more layers than the ones immediately above and below the current one, but for the sake of simplicity I stopped at one layer above and below.
Our ceilings are going to collapse if they aren’t supported during printing, so we’re left with the last type of tool path I’m going to support: infill. Whatever portion of a layer isn’t wall, ceiling, or floor is infill. We can find the infill area using the opposite of the floor and ceiling operation above. The area of the polygon remaining after computing the intersection of the below, current, and above layers needs to be infill. Even the way we generate the infill lines is the same as the ceiling, but the spacing is determined by the infill percentage and not the extrusion width.
Finally, all we have to do is convert these series of moves into gcode. To generate the gcode, I leaned heavily on the output of Orcaslicer. All of the movements are G1, which moves all axes in a coordinated motion at a fixed feed rate. The feed rate is set significantly higher for travel moves than for extrusion moves. Aside from some basic boilerplate for setting temperatures and fan speeds, the conversion to gcode just consists of taking the end point of each line segment we generated and creating a G1 command that moves the axes to that destination.
But, how much should the extruder motor move? To determine the layer height and amount to offset walls, we’ve already been relying on pre-set values for extrusion height and width. If we treat the extrusion like a rectangular prism, we can compute the volume of plastic extruder per mm of movement. Dividing this amount by the cross-section area of the filament, in this case the area of a circle with diameter 1.75mm, gives us the distance the extrusion motor should move per mm of cartesian movement at the extruder.
Conclusion
This project ended up being really fun and taught me a lot about what is actually hard and easy about making a slicer. Looking back at the early days of DIY 3D printing, it feels like everyone was obsessed with vase mode, where only the walls of a model are printed. After building this it makes a lot more sense: walls were easily the most reliable of the three extrusion types and minimized travel moves that introduce blobs and stringing, and potentially overall print failure.
It was also wild to see how much automatic mesh fixing modern slicers must be doing under the hood. I tried a bunch of other models than the one I ended up printing, and solidly half of them were non-manifold. But, loading the models into something like OrcaSlicer showed no issues and the models were sliced just fine.
I’d like to keep pulling the thread on this project, potentially looking at multi-axis printing. Existing methods rely heavily on existing slicers and transformations to achieve multi-axis prints, but I’d like to see what it could look like to directly slice a model in a 5-axis fashion.