As mentioned earlier in the post above, after processing (print -> process()) a loaded 3D STL model or a 3MF project, PrusaSlicer generates a Print object, which can be illustrated in the graph below.

// Loading 3D models and processing configurations
FullPrintConfig fff_print_config;
Model model = Model::read_from_file(...);

// Creating a printable object data structure
Print fff_print;
PrintBase *print = static_cast<PrintBase *>(&fff_print);

// Applying the configurations and processing the created object
print -> apply(model, fff_print_config);
print -> process();

Here we load a model from the file, apply the specified configurations, and process the print object. The apply() method utilizes the provided printer and slicing configurations, and the process() function converts the input STL/3MF/… files into a suitable data structure for G-Code generation.

The print->process() refers to void Slic3r::Print::process() method declared in Print.hpp. It is responsible for slicing the loaded 3D model mesh and creating its skirt, brim, wipe tower, support structure, perimeters, infilling, and ironing sequences for each model object.

Thus, after processing, we get the following data and are ready to generate G-Code (each class in the image belongs to the libslic3r library, for example, Print means Slic3r::Print). Classes are in bold and their main fields are listed below each class name:

Structural relationships of the generated print object (fff_print). Each class in the image belongs to the libslic3r library, e.g. Print means Slic3r::Print. Classes are in bold and their main fields are listed below each class.
  • PrintBase — abstracts the slice — > convert to instructions — > send to printer flow for different 3D printing technologies.
  • Print — complete print tray with possibly multiple objects, where each of them is defined as an instance of PrintObject class.
  • PrintRegion — represents a group of volumes to print sharing the same configurations, including the same assigned extruder or multiple extruders.
  • Layer — horizontal section of a certain thickness at the geometric cut level, containing all the color regions.
  • Model — represents all the 3D objects within the scene you want to slice.
  • ModelObject — a printable object, possibly having multiple print volumes (each with its own set of parameters and materials), and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials. Each ModelObject may be instantiated multiple times, each instance having a different placement on the print bed, different rotation, and different uniform scaling.
  • ModelVolume — represents a part of the STL model which has a different color or is part of another STL model

All the slicing parameters could be stored in the SlicingParameters field (Slic3r::PrintObject::m_slicing_params). It collects data to be used by the variable layer thickness algorithm, the interactive layer height editor, and the printing process itself. The slicing parameters depend on various configuration values: layer height, first layer height, raft settings, print nozzle diameter, etc.

// Slic3r::PrintObject::m_slicing_params
const SlicingParameters& slicing_parameters() const
{return m_slicing_params;}

Every sliced Layer, in turn, has the following structure:

Structural relationships of the sliced layer object

Starting with points, each part contributes to the individual surfaces which are combined into regions, and, finally, to the entire layer. It is to this structure (a set of various XYZ coordinates) we apply the group of functions to get the correct printer instructions (G-Code).

When the loaded model with all its STL meshes inside is sliced (PrintObject::slice()), the program creates the following layer hierarchy:

Sliced layer hierarchy

Before the slicing stage, one can also manipulate the individual model volumes, as in the Eiffel Tower example below, where different colors refer to different model volumes. This step precedes the slicing procedure, so the overall algorithm takes into account possible collisions and provides a corrected G-Code.

Manipulating with individual model volumes. This step precedes the slicing procedure, so the overall algorithm takes into account possible collisions and provides a corrected G-Code.

Now, let’s have a closer look at the PrintObject processing functions:

Print object processing pipeline

Functionmake_perimeters() generates perimeters for each layer. It also compares each layer to the one below and marks those slices needing one additional inner perimeter, like the top of domed objects.

PrintObject::slice() is called by make_perimeters() method and is located in PrintObjectSlice.cpp . It 1️⃣ decides Z positions of the layers, 2️⃣ initializes layers and their regions, 3️⃣ slices the object meshes, 4️⃣ slices the modifier meshes and reclassifies the slices of the object meshes by the slices of the modifier meshes, 5️⃣ applies for size compensation (offsets the slices in XY plane), and 6️⃣ replaces bad slices by the ones reconstructed from the upper/lower layer. The resulting layer polygons are marked as “internal”. PrintObject::slice_volumes(), in turn, merges all regions’ slices to get islands and chains them by the shortest path.

Infill(), ironing(), and generate_support_material() are small functions, so it is useful to bring their entire code here:

Infill()

void PrintObject::infill()
{
// prerequisites
this->prepare_infill();

if (this->set_started(posInfill)) {
auto [adaptive_fill_octree, support_fill_octree] = this->prepare_adaptive_infill_data();
auto lightning_generator = this->prepare_lightning_infill_data();

BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start";
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_layers.size()),
[this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree, &lightning_generator](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
m_print->throw_if_canceled();
m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get(), lightning_generator.get());
}
}
);
m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - end";
this->set_done(posInfill);
}
}

The prerequisite for the infilling method is PrintObject::prepare_infill() which:

  • Detects surface types (top/bottom/internal) and decides what surfaces are to be filled.
  • Processes external surfaces, where it detects bridges and produces enlarged overlapping bridging areas.
  • Merges surfaces with the same style.
  • Discovers vertical and horizontal shells and adds solid fills to ensure their sustainable thickness.
  • Clips fill surfaces and trims the sparse infill to make it act as an internal support.

Ironing()

void PrintObject::ironing()
{
if (this->set_started(posIroning)) {
BOOST_LOG_TRIVIAL(debug) << "Ironing in parallel - start";
tbb::parallel_for(
// Ironing starting with layer 0 to support ironing all surfaces.
tbb::blocked_range<size_t>(0, m_layers.size()),
[this](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
m_print->throw_if_canceled();
m_layers[layer_idx]->make_ironing();
}
}
);
m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Ironing in parallel - end";
this->set_done(posIroning);
}
}

Generate_support_material()

void PrintObject::generate_support_material()
{
if (this->set_started(posSupportMaterial)) {
this->clear_support_layers();
if ((this->has_support() && m_layers.size() > 1) || (this->has_raft() && ! m_layers.empty())) {
m_print->set_status(85, L("Generating support material"));
this->_generate_support_material();
m_print->throw_if_canceled();
}
this->set_done(posSupportMaterial);
}
}

Perimeters, infills, and ironing work for each sliced layer:

PrintObject::m_layers[layer_idx]->make_perimeters(); // defined in Layer.cpp
PrintObject::m_layers[layer_idx]->make_fills(...); // in Fill.cpp
PrintObject::m_layers[layer_idx]->make_ironing(); // in Fill.cpp

Support method applies for each printing object (each 3D STL mesh):

*PrintObject -> _generate_support_material(); // defined in PrintObject.cpp

void PrintObject::_generate_support_material()
{
PrintObjectSupportMaterial support_material(*PrintObject, m_slicing_params);
support_material.generate(*PrintObject);
}

Print::_make_wipe_tower() and Print::_make_skirt() methods are used for the whole 3D scene to take into account all existing objects.

The core functionality of Print::_make_wipe_tower() can be summarized as follows:

void Print::_make_wipe_tower(){
...
// Initialize the wipe tower.
WipeTower wipe_tower(m_config, wipe_volumes, m_wipe_tower_data.tool_ordering.first_extruder());

// Generate the wipe tower layers.
m_wipe_tower_data.tool_changes.reserve(m_wipe_tower_data.tool_ordering.layer_tools().size());
wipe_tower.generate(m_wipe_tower_data.tool_changes); // GCode/WipeTower.cpp
m_wipe_tower_data.depth = wipe_tower.get_depth();
m_wipe_tower_data.brim_width = wipe_tower.get_brim_width();
...}

Where the WipeTower::generate(…) is:

// Processes vector m_plan and calls respective functions to generate G-code for the wipe tower
// Resulting ToolChangeResults are appended into vector "result"
void WipeTower::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result)
{
if (m_plan.empty())
return;

m_extra_spacing = 1.f;

plan_tower();
for (int i=0;i<5;++i) {
save_on_last_wipe();
plan_tower();
}

m_layer_info = m_plan.begin();

// we don't know which extruder to start with - we'll set it according to the first toolchange
for (const auto& layer : m_plan) {
if (!layer.tool_changes.empty()) {
m_current_tool = layer.tool_changes.front().old_tool;
break;
}
}

for (auto& used : m_used_filament_length) // reset used filament stats
used = 0.f;

m_old_temperature = -1; // reset last temperature written in the gcode

std::vector<WipeTower::ToolChangeResult> layer_result;
for (auto layer : m_plan)
{
set_layer(layer.z, layer.height, 0, false/*layer.z == m_plan.front().z*/, layer.z == m_plan.back().z);
m_internal_rotation += 180.f;

if (m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width)
m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f;

int idx = first_toolchange_to_nonsoluble(layer.tool_changes);
ToolChangeResult finish_layer_tcr;

if (idx == -1) {
// if there is no toolchange switching to non-soluble, finish layer
// will be called at the very beginning. That's the last possibility
// where a nonsoluble tool can be.
finish_layer_tcr = finish_layer();
}

for (int i=0; i<int(layer.tool_changes.size()); ++i) {
layer_result.emplace_back(tool_change(layer.tool_changes[i].new_tool));
if (i == idx) // finish_layer will be called after this toolchange
finish_layer_tcr = finish_layer();
}

if (layer_result.empty()) {
// there is nothing to merge finish_layer with
layer_result.emplace_back(std::move(finish_layer_tcr));
}
else {
if (idx == -1)
layer_result[0] = merge_tcr(finish_layer_tcr, layer_result[0]);
else
layer_result[idx] = merge_tcr(layer_result[idx], finish_layer_tcr);
}

result.emplace_back(std::move(layer_result));
}
}

Print::_make_skirt()

void Print::_make_skirt(){
...
// Generate the skirt centerline.
Polygon loop;
{
Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, float(scale_(0.1)));
Geometry::simplify_polygons(loops, scale_(0.05), &loops);
if (loops.empty())
break;
loop = loops.front();
}
// Extrude the skirt loop.
ExtrusionLoop eloop(elrSkirt);
eloop.paths.emplace_back(ExtrusionPath(
ExtrusionPath(
erSkirt,
(float)mm3_per_mm, // this will be overridden at G-code export time
flow.width(),
(float)first_layer_height // this will be overridden at G-code export time
)));
eloop.paths.back().polyline = loop.split_at_first_point();
m_skirt.append(eloop);
...}

Class ExtrusionLoop represents a single continuous extrusion loop, possibly with varying extrusion thickness, extrusion height, and bridging.

▶️ Next

The next chapter will cover details about the GCode class.

--

--

Aliaksei Petsiuk

Researcher at Western University (Canada) focusing on computer vision in additive manufacturing. https://www.linkedin.com/in/apetsiuk/