Slic3r/xs/src/libslic3r/PrintGCode.cpp
Joseph Lenox dabb5f3823 Fix skirt/Brim (#4669)
* Start implementing skirt/brim tests.

* Move skirt_height_z (representing the highest skirt height in actual Z coordinates) to Print and prime it with make_brim so it can be used in PrintGCode.

Updated test to correctly verify skirt is generated for one and multiple objects.

* Fallout from the config refactor; use correct accessors.

* Fix bug where brim was not generated (inverted logic)

* Finish porting tests, left one intentional failure as support material is not generated yet (segfaults).
2019-01-06 23:58:08 +01:00

805 lines
37 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "PrintGCode.hpp"
#include "PrintConfig.hpp"
#include <ctime>
#include <iostream>
namespace Slic3r {
void
PrintGCode::output()
{
// Write information about the generator.
time_t rawtime; tm * timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
fh << "; generated by Slic3r " << SLIC3R_VERSION << " on ";
fh << asctime(timeinfo) << "\n";
fh << "; Git Commit: " << BUILD_COMMIT << "\n\n";
// Writes notes (content of all Settings tabs -> Notes)
fh << _gcodegen.notes();
// Write some terse information on the slicing parameters.
PrintObject& first_object { *this->objects.at(0) };
const auto layer_height = first_object.config.layer_height.getFloat();
for (auto* region : _print.regions) {
{
const Flow flow { region->flow(frExternalPerimeter, layer_height, false, false, -1, first_object) };
auto vol_speed = flow.mm3_per_mm() * region->config.get_abs_value("external_perimeter_speed");
if (config.max_volumetric_speed.getFloat() > 0)
vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat());
fh << "; external perimeters extrusion width = ";
fh << std::fixed << std::setprecision(2) << flow.width << "mm ";
fh << "(" << vol_speed << "mm^3/s)\n";
}
{
const Flow flow { region->flow(frPerimeter, layer_height, false, false, -1, first_object) };
auto vol_speed = flow.mm3_per_mm() * region->config.get_abs_value("perimeter_speed");
if (config.max_volumetric_speed.getFloat() > 0)
vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat());
fh << "; perimeters extrusion width = ";
fh << std::fixed << std::setprecision(2) << flow.width << "mm ";
fh << "(" << vol_speed << "mm^3/s)\n";
}
{
const Flow flow { region->flow(frInfill, layer_height, false, false, -1, first_object) };
auto vol_speed = flow.mm3_per_mm() * region->config.get_abs_value("infill_speed");
if (config.max_volumetric_speed.getFloat() > 0)
vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat());
fh << "; infill extrusion width = ";
fh << std::fixed << std::setprecision(2) << flow.width << "mm ";
fh << "(" << vol_speed << "mm^3/s)\n";
}
{
const Flow flow { region->flow(frSolidInfill, layer_height, false, false, -1, first_object) };
auto vol_speed = flow.mm3_per_mm() * region->config.get_abs_value("solid_infill_speed");
if (config.max_volumetric_speed.getFloat() > 0)
vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat());
fh << "; solid infill extrusion width = ";
fh << std::fixed << std::setprecision(2) << flow.width << "mm ";
fh << "(" << vol_speed << "mm^3/s)\n";
}
{
const Flow flow { region->flow(frTopSolidInfill, layer_height, false, false, -1, first_object) };
auto vol_speed = flow.mm3_per_mm() * region->config.get_abs_value("top_solid_infill_speed");
if (config.max_volumetric_speed.getFloat() > 0)
vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat());
fh << "; top solid infill extrusion width = ";
fh << std::fixed << std::setprecision(2) << flow.width << "mm ";
fh << "(" << vol_speed << "mm^3/s)\n";
}
if (_print.has_support_material()) {
const Flow flow { first_object._support_material_flow() };
auto vol_speed = flow.mm3_per_mm() * first_object.config.get_abs_value("support_material_speed");
if (config.max_volumetric_speed.getFloat() > 0)
vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat());
fh << "; support material extrusion width = ";
fh << std::fixed << std::setprecision(2) << flow.width << "mm ";
fh << "(" << vol_speed << "mm^3/s)\n";
}
if (_print.config.first_layer_extrusion_width.get_abs_value(layer_height) > 0) {
const Flow flow { region->flow(frPerimeter, layer_height, false, false, -1, first_object) };
// auto vol_speed = flow.mm3_per_mm() * _print.config.get_abs_value("first_layer_speed");
// if (config.max_volumetric_speed.getFloat() > 0)
// vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat());
fh << "; first layer extrusion width = ";
fh << std::fixed << std::setprecision(2) << flow.width << "mm ";
// fh << "(" << vol_speed << "mm^3/s)\n";
}
fh << std::endl;
}
// Prepare the helper object for replacing placeholders in custom G-Code and output filename
_print.placeholder_parser.update_timestamp();
// GCode sets this automatically when change_layer() is called, but needed for skirt/brim as well
_gcodegen.first_layer = true;
// disable fan
if (config.cooling.getBool() && config.disable_fan_first_layers.getInt() > 0) {
fh << _gcodegen.writer.set_fan(0,1) << "\n";
}
// set bed temperature
const auto temp = config.first_layer_bed_temperature.getInt();
if (config.has_heatbed && temp > 0 && std::regex_search(config.start_gcode.getString(), bed_temp_regex)) {
fh << _gcodegen.writer.set_bed_temperature(temp, 1);
}
// Set extruder(s) temperature before and after start gcode.
auto include_start_extruder_temp = !std::regex_search(config.start_gcode.getString(), ex_temp_regex);
for(const auto& start_gcode : config.start_filament_gcode.values) {
include_start_extruder_temp = include_start_extruder_temp && !std::regex_search(start_gcode, ex_temp_regex);
}
auto include_end_extruder_temp = !std::regex_search(config.end_gcode.getString(), ex_temp_regex);
for(const auto& end_gcode : config.end_filament_gcode.values) {
include_end_extruder_temp = include_end_extruder_temp && !std::regex_search(end_gcode, ex_temp_regex);
}
if (include_start_extruder_temp) this->_print_first_layer_temperature(0);
// Apply gcode math to start gcode
fh << apply_math(_gcodegen.placeholder_parser->process(config.start_gcode.value));
{
auto filament_extruder = 0U;
for(const auto& start_gcode : config.start_filament_gcode.values) {
_gcodegen.placeholder_parser->set("filament_extruder_id", filament_extruder++);
fh << apply_math(_gcodegen.placeholder_parser->process(start_gcode));
}
}
if (include_start_extruder_temp) this->_print_first_layer_temperature(1);
// Set other general things (preamble)
fh << _gcodegen.preamble();
// initialize motion planner for object-to-object travel moves
if (config.avoid_crossing_perimeters.getBool()) {
// compute the offsetted convex hull for each object and repeat it for each copy
Polygons islands_p {};
for (auto object : this->objects) {
Polygons polygons {};
// Add polygons that aren't just thin walls.
for (auto layer : object->layers) {
const ExPolygons& slice { layer->slices };
std::for_each(slice.cbegin(), slice.cend(), [&polygons] (const ExPolygon& a) { polygons.emplace_back(a.contour); });
}
if (polygons.size() == 0) continue;
for (auto copy : object->_shifted_copies) {
Polygons copy_islands_p {polygons};
std::for_each(copy_islands_p.begin(), copy_islands_p.end(), [copy] (Polygon& obj) { obj.translate(copy); });
islands_p.insert(islands_p.cend(), copy_islands_p.begin(), copy_islands_p.end());
}
}
_gcodegen.avoid_crossing_perimeters.init_external_mp(union_ex(islands_p));
}
const auto extruders = _print.extruders();
// Calculate wiping points if needed.
if (config.ooze_prevention && extruders.size() > 1) {
/*
TODO: PORT THIS!
my @skirt_points = map @$_, map @$_, @{$self->print->skirt};
if (@skirt_points) {
my $outer_skirt = convex_hull(\@skirt_points);
my @skirts = ();
foreach my $extruder_id (@extruders) {
my $extruder_offset = $self->config->get_at('extruder_offset', $extruder_id);
push @skirts, my $s = $outer_skirt->clone;
$s->translate(-scale($extruder_offset->x), -scale($extruder_offset->y)); #)
}
my $convex_hull = convex_hull([ map @$_, @skirts ]);
$gcodegen->ooze_prevention->set_enable(1);
$gcodegen->ooze_prevention->set_standby_points(
[ map @{$_->equally_spaced_points(scale 10)}, @{offset([$convex_hull], scale 3)} ]
);
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"ooze_prevention.svg",
red_polygons => \@skirts,
polygons => [$outer_skirt],
points => $gcodegen->ooze_prevention->standby_points,
);
}
}
*/
}
// Set initial extruder only after custom start gcode
fh << _gcodegen.set_extruder( *(extruders.begin()) );
// Do all objects for each layer.
if (config.complete_objects) {
// print objects from the smallest to the tallest to avoid collisions
// when moving onto next object starting point
std::sort(_print.objects.begin(), _print.objects.end(), [] (const PrintObject* a, const PrintObject* b) {
return (a->config.sequential_print_priority < a->config.sequential_print_priority) || (a->size.z < b->size.z);
});
size_t finished_objects {0};
for (size_t obj_idx {0}; obj_idx < _print.objects.size(); ++obj_idx) {
PrintObject& object {*(this->objects.at(obj_idx))};
for (const Point& copy : object._shifted_copies) {
if (finished_objects > 0) {
_gcodegen.set_origin(Pointf::new_unscale(copy));
_gcodegen.enable_cooling_markers = false;
_gcodegen.avoid_crossing_perimeters.use_external_mp_once = true;
fh << _gcodegen.retract();
fh << _gcodegen.travel_to(Point(0,0), erNone, "move to origin position for next object");
_gcodegen.enable_cooling_markers = true;
// disable motion planner when traveling to first object point
_gcodegen.avoid_crossing_perimeters.disable_once = true;
}
std::vector<Layer*> layers;
layers.reserve(object.layers.size() + object.support_layers.size());
for (auto l : object.layers) {
layers.emplace_back(l);
}
for (auto l : object.support_layers) {
layers.emplace_back(static_cast<Layer*>(l));
}
std::sort(layers.begin(), layers.end(), [] (const Layer* a, const Layer* b) { return a->print_z < b->print_z; });
for (Layer* layer : layers) {
// if we are printing the bottom layer of an object, and we have already finished
// another one, set first layer temperatures. this happens before the Z move
// is triggered, so machine has more time to reach such temperatures
if (layer->id() == 0 && finished_objects > 0) {
if (config.first_layer_bed_temperature > 0 &&
config.has_heatbed &&
std::regex_search(config.between_objects_gcode.getString(), bed_temp_regex))
{
fh << _gcodegen.writer.set_bed_temperature(config.first_layer_bed_temperature);
}
if (std::regex_search(config.between_objects_gcode.getString(), ex_temp_regex)) {
_print_first_layer_temperature(false);
}
}
this->process_layer(obj_idx, layer, Points({copy}));
}
this->flush_filters();
finished_objects++;
this->_second_layer_things_done = false;
}
}
} else {
// order objects using a nearest neighbor search
std::vector<Points::size_type> obj_idx {};
Points p;
for (const auto obj : this->objects )
p.emplace_back(obj->_shifted_copies.at(0));
Geometry::chained_path(p, obj_idx);
std::vector<size_t> z;
z.reserve(100); // preallocate with 100 layers
std::map<coord_t, std::map<size_t, LayerPtrs > > layers {};
for (size_t idx = 0U; idx < _print.objects.size(); ++idx) {
const PrintObject& object { *objects.at(idx) };
// sort layers by Z into buckets
for (Layer* layer : object.layers) {
if (layers.count(scale_(layer->print_z)) == 0) { // initialize bucket if empty
layers[scale_(layer->print_z)] = std::map<size_t, LayerPtrs >();
layers[scale_(layer->print_z)][idx] = LayerPtrs();
z.emplace_back(scale_(layer->print_z));
}
layers[scale_(layer->print_z)][idx].emplace_back(layer);
}
for (Layer* layer : object.support_layers) { // don't use auto here to not have to cast later
if (layers.count(scale_(layer->print_z)) == 0) { // initialize bucket if empty
layers[scale_(layer->print_z)] = std::map<size_t, LayerPtrs >();
layers[scale_(layer->print_z)][idx] = LayerPtrs();
}
layers[scale_(layer->print_z)][idx].emplace_back(layer);
}
}
// pass the comparator to leave no doubt.
std::sort(z.begin(), z.end(), std::less<size_t>());
// call process_layers in the order given by obj_idx
for (const auto& print_z : z) {
for (const auto& idx : obj_idx) {
for (const auto* layer : layers[print_z][idx] ) {
this->process_layer(idx, layer, layer->object()->_shifted_copies);
}
}
_gcodegen.placeholder_parser->set("layer_z", unscale(print_z));
_gcodegen.placeholder_parser->set("layer_num", _gcodegen.layer_index);
}
this->flush_filters();
}
// Write end commands to file.
fh << _gcodegen.retract(); // TODO: process this retract through PressureRegulator in order to discharge fully
{
auto filament_extruder = 0U;
for(const auto& end_gcode : config.end_filament_gcode.values) {
_gcodegen.placeholder_parser->set("filament_extruder_id", filament_extruder++);
fh << apply_math(_gcodegen.placeholder_parser->process(end_gcode));
}
}
fh << apply_math(_gcodegen.placeholder_parser->process(config.end_gcode));
// set bed temperature
if (config.has_heatbed && temp > 0 && std::regex_search(config.end_gcode.getString(), bed_temp_regex)) {
fh << _gcodegen.writer.set_bed_temperature(0, 0);
}
// Get filament stats
_print.filament_stats.clear();
_print.total_used_filament = 0.0;
_print.total_extruded_volume = 0.0;
_print.total_weight = 0.0;
_print.total_cost = 0.0;
for (auto extruder_pair : _gcodegen.writer.extruders) {
const Extruder& extruder { extruder_pair.second };
const auto used_material = extruder.used_filament();
const auto extruded_volume = extruder.extruded_volume();
const auto material_weight = extruded_volume * extruder.filament_density() / 1000.0;
const auto material_cost = material_weight * (extruder.filament_cost() / 1000.0);
_print.filament_stats[extruder.id] = used_material;
fh << "; material used = ";
fh << std::fixed << std::setprecision(2) << used_material << "mm ";
fh << "(" << std::fixed << std::setprecision(2)
<< extruded_volume / 1000.0
<< used_material << "cm3)\n";
if (material_weight > 0) {
_print.total_weight += material_weight;
fh << "; material used = "
<< std::fixed << std::setprecision(2) << material_weight << "g\n";
if (material_cost > 0) {
_print.total_cost += material_cost;
fh << "; material cost = "
<< std::fixed << std::setprecision(2) << material_weight << "g\n";
}
}
_print.total_used_filament += used_material;
_print.total_extruded_volume += extruded_volume;
}
fh << "; total filament cost = "
<< std::fixed << std::setprecision(2) << _print.total_cost << "\n";
// Append full config
fh << std::endl;
// print config
_print_config(_print.config);
_print_config(_print.default_object_config);
_print_config(_print.default_region_config);
}
std::string
PrintGCode::filter(const std::string& in, bool wait)
{
return in;
}
void
PrintGCode::process_layer(size_t idx, const Layer* layer, const Points& copies)
{
std::string gcode {""};
const PrintObject& obj { *layer->object() };
_gcodegen.config.apply(obj.config, true);
// check for usage of spiralvase logic.
this->_spiral_vase.enable = (
layer->id() > 0
&& (_print.config.skirts == 0 || (layer->id() >= _print.config.skirt_height && !_print.has_infinite_skirt()))
&& std::find_if(layer->regions.cbegin(), layer->regions.cend(), [layer] (const LayerRegion* l)
{ return l->region()->config.bottom_solid_layers > layer->id()
|| l->perimeters.items_count() > 1
|| l->fills.items_count() > 0;
}) == layer->regions.cend()
);
this->_gcodegen.enable_loop_clipping = this->_spiral_vase.enable;
// if using spiralvase, disable loop clipping.
// initialize autospeed.
{
// get the minimum cross-section used in the layer.
std::vector<double> mm3_per_mm;
for (auto region_id = 0U; region_id < _print.regions.size(); ++region_id) {
const PrintRegion* region = _print.get_region(region_id);
const LayerRegion* layerm = layer->get_region(region_id);
if (!(region->config.get_abs_value("perimeter_speed") > 0 &&
region->config.get_abs_value("small_perimeter_speed") > 0 &&
region->config.get_abs_value("external_perimeter_speed") > 0 &&
region->config.get_abs_value("bridge_speed") > 0))
{
mm3_per_mm.emplace_back(layerm->perimeters.min_mm3_per_mm());
}
if (!(region->config.get_abs_value("infill_speed") > 0 &&
region->config.get_abs_value("solid_infill_speed") > 0 &&
region->config.get_abs_value("top_solid_infill_speed") > 0 &&
region->config.get_abs_value("bridge_speed") > 0 &&
region->config.get_abs_value("gap_fill_speed") > 0)) // TODO: make this configurable?
{
mm3_per_mm.emplace_back(layerm->fills.min_mm3_per_mm());
}
}
if (typeid(layer) == typeid(SupportLayer*)) {
const SupportLayer* slayer = dynamic_cast<const SupportLayer*>(layer);
if (!(obj.config.get_abs_value("support_material_speed") > 0 &&
obj.config.get_abs_value("support_material_interface_speed") > 0))
{
mm3_per_mm.emplace_back(slayer->support_fills.min_mm3_per_mm());
mm3_per_mm.emplace_back(slayer->support_interface_fills.min_mm3_per_mm());
}
}
// ignore too-thin segments.
// TODO make the definition of "too thin" based on a config somewhere
mm3_per_mm.erase(std::remove_if(mm3_per_mm.begin(), mm3_per_mm.end(), [] (const double& vol) { return vol <= 0.01;} ), mm3_per_mm.end());
if (mm3_per_mm.size() > 0) {
const double min_mm3_per_mm { *(std::min_element(mm3_per_mm.begin(), mm3_per_mm.end())) };
// In order to honor max_print_speed we need to find a target volumetric
// speed that we can use throughout the _print. So we define this target
// volumetric speed as the volumetric speed produced by printing the
// smallest cross-section at the maximum speed: any larger cross-section
// will need slower feedrates.
double volumetric_speed { min_mm3_per_mm * config.max_print_speed };
if (config.max_volumetric_speed > 0) {
volumetric_speed = std::min(volumetric_speed, config.max_volumetric_speed.getFloat());
}
_gcodegen.volumetric_speed = volumetric_speed;
}
}
// set the second layer + temp
if (!this->_second_layer_things_done && layer->id() == 1) {
for (const auto& extruder_ref : _gcodegen.writer.extruders) {
const Extruder& extruder { extruder_ref.second };
auto temp = config.temperature.get_at(extruder.id);
if (temp > 0 && temp != config.first_layer_temperature.get_at(extruder.id) )
gcode += _gcodegen.writer.set_temperature(temp, 0, extruder.id);
}
if (config.has_heatbed && _print.config.first_layer_bed_temperature > 0 && _print.config.bed_temperature != _print.config.first_layer_bed_temperature) {
gcode += _gcodegen.writer.set_bed_temperature(_print.config.bed_temperature);
}
this->_second_layer_things_done = true;
}
// set new layer - this will change Z and force a retraction if retract_layer_change is enabled
if (_print.config.before_layer_gcode.getString().size() > 0) {
PlaceholderParser pp { *_gcodegen.placeholder_parser };
pp.set("layer_num", _gcodegen.layer_index);
pp.set("layer_z", layer->print_z);
pp.set("current_retraction", _gcodegen.writer.extruder()->retracted);
gcode += apply_math(pp.process(_print.config.before_layer_gcode.getString()));
gcode += "\n";
}
gcode += _gcodegen.change_layer(*layer);
if (_print.config.layer_gcode.getString().size() > 0) {
PlaceholderParser pp { *_gcodegen.placeholder_parser };
pp.set("layer_num", _gcodegen.layer_index);
pp.set("layer_z", layer->print_z);
pp.set("current_retraction", _gcodegen.writer.extruder()->retracted);
gcode += apply_math(pp.process(_print.config.layer_gcode.getString()));
gcode += "\n";
}
// extrude skirt along raft layers and normal obj layers
// (not along interlaced support material layers)
if (layer->id() < static_cast<size_t>(obj.config.raft_layers)
|| ((_print.has_infinite_skirt()
|| _skirt_done.size() == 0
|| (_skirt_done.rbegin())->first < scale_(_print.skirt_height_z))
&& _skirt_done.count(scale_(layer->print_z)) == 0
&& typeid(layer) != typeid(SupportLayer*)) ) {
_gcodegen.set_origin(Pointf(0,0));
_gcodegen.avoid_crossing_perimeters.use_external_mp = true;
/// data load
std::vector<size_t> extruder_ids;
extruder_ids.reserve(_gcodegen.writer.extruders.size());
std::transform(_gcodegen.writer.extruders.cbegin(), _gcodegen.writer.extruders.cend(), std::back_inserter(extruder_ids),
[] (const std::pair<unsigned int, Extruder>& z) -> std::size_t { return z.second.id; } );
gcode += _gcodegen.set_extruder(extruder_ids.at(0));
// skip skirt if a large brim
if (_print.has_infinite_skirt() || layer->id() < static_cast<size_t>(_print.config.skirt_height)) {
const Flow skirt_flow { _print.skirt_flow() };
// distribute skirt loops across all extruders in layer 0
const auto skirt_loops = _print.skirt.flatten().entities;
for (size_t i = 0; i < skirt_loops.size(); ++i) {
// when printing layers > 0 ignore 'min_skirt_length' and
// just use the 'skirts' setting; also just use the current extruder
if (layer->id() > 0 && i >= static_cast<size_t>(_print.config.skirts)) break;
const size_t extruder_id { extruder_ids.at((i / extruder_ids.size()) % extruder_ids.size()) };
if (layer->id() == 0)
gcode += _gcodegen.set_extruder(extruder_id);
// adjust flow according to layer height
auto& loop = *dynamic_cast<ExtrusionLoop*>(skirt_loops.at(i));
{
Flow layer_skirt_flow(skirt_flow);
layer_skirt_flow.height = layer->height;
const auto mm3_per_mm = layer_skirt_flow.mm3_per_mm();
for (auto& path : loop.paths) {
path.height = layer->height;
path.mm3_per_mm = mm3_per_mm;
}
}
gcode += _gcodegen.extrude(loop, "skirt", obj.config.support_material_speed);
}
}
this->_skirt_done[scale_(layer->print_z)] = true;
_gcodegen.avoid_crossing_perimeters.use_external_mp = false;
if (layer->id() == 0) _gcodegen.avoid_crossing_perimeters.disable_once = true;
}
// extrude brim
if (!this->_brim_done) {
gcode += _gcodegen.set_extruder(_print.brim_extruder() - 1);
_gcodegen.set_origin(Pointf(0,0));
_gcodegen.avoid_crossing_perimeters.use_external_mp = true;
for (const auto& b : _print.brim.entities) {
gcode += _gcodegen.extrude(*b, "brim", obj.config.get_abs_value("support_material_speed"));
}
this->_brim_done = true;
_gcodegen.avoid_crossing_perimeters.use_external_mp = false;
// allow a straight travel move to the first object point
_gcodegen.avoid_crossing_perimeters.disable_once = true;
}
auto copy_idx = 0U;
for (const auto& copy : copies) {
if (config.label_printed_objects) {
gcode += "; printing object " + obj.model_object().name + " id:" + std::to_string(idx) + " copy " + std::to_string(copy_idx) + "\n";
}
// when starting a new object, use the external motion planner for the first travel move
if (this->_last_obj_copy.first != copy && this->_last_obj_copy.second )
_gcodegen.avoid_crossing_perimeters.use_external_mp = true;
this->_last_obj_copy.first = copy;
this->_last_obj_copy.second = true;
_gcodegen.set_origin(Pointf::new_unscale(copy));
// extrude support material before other things because it might use a lower Z
// and also because we avoid travelling on other things when printing it
if(layer->is_support()) {
const SupportLayer* slayer = dynamic_cast<const SupportLayer*>(layer);
ExtrusionEntityCollection paths;
if (slayer->support_interface_fills.size() > 0) {
gcode += _gcodegen.set_extruder(obj.config.support_material_interface_extruder - 1);
slayer->support_interface_fills.chained_path_from(_gcodegen.last_pos(), &paths, false);
for (const auto& path : paths) {
gcode += _gcodegen.extrude(*path, "support material interface", obj.config.get_abs_value("support_material_interface_speed"));
}
}
if (slayer->support_fills.size() > 0) {
gcode += _gcodegen.set_extruder(obj.config.support_material_extruder - 1);
slayer->support_fills.chained_path_from(_gcodegen.last_pos(), &paths, false);
for (const auto& path : paths) {
gcode += _gcodegen.extrude(*path, "support material", obj.config.get_abs_value("support_material_speed"));
}
}
}
// We now define a strategy for building perimeters and fills. The separation
// between regions doesn't matter in terms of printing order, as we follow
// another logic instead:
// - we group all extrusions by extruder so that we minimize toolchanges
// - we start from the last used extruder
// - for each extruder, we group extrusions by island
// - for each island, we extrude perimeters first, unless user set the infill_first
// option
// (Still, we have to keep track of regions because we need to apply their config)
// group extrusions by extruder and then by island
// extruder island
std::map<size_t,std::map<size_t,
// region
std::tuple<std::map<size_t,ExtrusionEntityCollection>, // perimeters
std::map<size_t,ExtrusionEntityCollection>> // infill
>> by_extruder;
// cache bounding boxes of layer slices
std::vector<BoundingBox> layer_slices_bb;
std::transform(layer->slices.cbegin(), layer->slices.cend(), std::back_inserter(layer_slices_bb), [] (const ExPolygon& s)-> BoundingBox { return s.bounding_box(); });
auto point_inside_surface = [&layer_slices_bb, &layer] (size_t i, Point point) -> bool {
const BoundingBox& bbox { layer_slices_bb.at(i) };
return bbox.contains(point) && layer->slices.at(i).contour.contains(point);
};
const size_t n_slices { layer->slices.size() };
for (auto region_id = 0U; region_id < _print.regions.size(); ++region_id) {
const LayerRegion* layerm;
try {
layerm = layer->get_region(region_id); // we promise to be good and not give this to anyone who will modify it
} catch (std::out_of_range &e) {
continue; // if no regions, bail;
}
const PrintRegion* region { _print.get_region(region_id) };
// process perimeters
{
auto extruder_id = region->config.perimeter_extruder-1;
// Casting away const just to avoid double dereferences
for(const auto* perimeter_coll : layerm->perimeters.flatten().entities) {
if(perimeter_coll->length() == 0) continue; // this shouldn't happen but first_point() would fail
// perimeter_coll is an ExtrusionPath::Collection object representing a single slice
for(auto i = 0U; i < n_slices; i++){
if (// perimeter_coll->first_point does not fit inside any slice
i == n_slices - 1
// perimeter_coll->first_point fits inside ith slice
|| point_inside_surface(i, perimeter_coll->first_point())) {
std::get<0>(by_extruder[extruder_id][i])[region_id].append(*perimeter_coll);
break;
}
}
}
}
// process infill
// $layerm->fills is a collection of ExtrusionPath::Collection objects, each one containing
// the ExtrusionPath objects of a certain infill "group" (also called "surface"
// throughout the code). We can redefine the order of such Collections but we have to
// do each one completely at once.
for(auto* fill : layerm->fills.flatten().entities) {
if(fill->length() == 0) continue; // this shouldn't happen but first_point() would fail
auto extruder_id = fill->is_solid_infill()
? region->config.solid_infill_extruder-1
: region->config.infill_extruder-1;
// $fill is an ExtrusionPath::Collection object
for(auto i = 0U; i < n_slices; i++){
if (i == n_slices - 1
|| point_inside_surface(i, fill->first_point())) {
std::get<1>(by_extruder[extruder_id][i])[region_id].append(*fill);
break;
}
}
}
}
// tweak extruder ordering to save toolchanges
auto last_extruder = _gcodegen.writer.extruder()->id;
if (by_extruder.count(last_extruder)) {
for(auto &island : by_extruder[last_extruder]) {
if (_print.config.infill_first()) {
gcode += this->_extrude_infill(std::get<1>(island.second));
gcode += this->_extrude_perimeters(std::get<0>(island.second));
} else {
gcode += this->_extrude_perimeters(std::get<0>(island.second));
gcode += this->_extrude_infill(std::get<1>(island.second));
}
}
}
for(auto &pair : by_extruder) {
if(pair.first == last_extruder)continue;
gcode += _gcodegen.set_extruder(pair.first);
for(auto &island : pair.second) {
if (_print.config.infill_first()) {
gcode += this->_extrude_infill(std::get<1>(island.second));
gcode += this->_extrude_perimeters(std::get<0>(island.second));
} else {
gcode += this->_extrude_perimeters(std::get<0>(island.second));
gcode += this->_extrude_infill(std::get<1>(island.second));
}
}
}
if (config.label_printed_objects) {
gcode += "; stop printing object " + obj.model_object().name + " id:" + std::to_string(idx) + " copy " + std::to_string(copy_idx) + "\n";
}
copy_idx++;
}
// Apply spiral vase post-processing if this layer contains suitable geometry
// (we must feed all the G-code into the post-processor, including the first
// bottom non-spiral layers otherwise it will mess with positions)
// we apply spiral vase at this stage because it requires a full layer
gcode = this->_spiral_vase.process_layer(gcode);
// Apply the cooling logic.
gcode = this->_cooling_buffer.append(gcode, std::to_string(reinterpret_cast<long long unsigned int>(layer->object())) + std::string(typeid(layer).name()),
layer->id(), layer->print_z);
// write the resulting gcode
fh << this->filter(gcode);
}
// Extrude perimeters: Decide where to put seams (hide or align seams).
std::string
PrintGCode::_extrude_perimeters(std::map<size_t,ExtrusionEntityCollection> &by_region)
{
std::string gcode = "";
for(auto& pair : by_region) {
this->_gcodegen.config.apply(this->_print.get_region(pair.first)->config);
for(auto& ee : pair.second){
gcode += this->_gcodegen.extrude(*ee, "perimeter");
}
}
return gcode;
}
// Chain the paths hierarchically by a greedy algorithm to minimize a travel distance.
std::string
PrintGCode::_extrude_infill(std::map<size_t,ExtrusionEntityCollection> &by_region)
{
std::string gcode = "";
for(auto& pair : by_region) {
this->_gcodegen.config.apply(this->_print.get_region(pair.first)->config);
ExtrusionEntityCollection tmp;
pair.second.chained_path_from(this->_gcodegen.last_pos(),&tmp);
for(auto& ee : tmp){
gcode += this->_gcodegen.extrude(*ee, "infill");
}
}
return gcode;
}
void
PrintGCode::_print_first_layer_temperature(bool wait)
{
for (auto& t : _print.extruders()) {
auto temp = config.first_layer_temperature.get_at(t);
if (config.ooze_prevention.value) temp += config.standby_temperature_delta.value;
if (temp > 0) fh << _gcodegen.writer.set_temperature(temp, wait, t);
}
}
void
PrintGCode::_print_config(const ConfigBase& config)
{
for (const auto& key : config.keys()) {
// skip if a shortcut option
// if (std::find(print_config_def.cbegin(), print_config_def.cend(), key) > 0) continue;
fh << "; " << key << " = " << config.serialize(key) << "\n";
}
}
PrintGCode::PrintGCode(Slic3r::Print& print, std::ostream& _fh) :
_print(print),
config(_print.config),
_gcodegen(Slic3r::GCode()),
objects(_print.objects),
fh(_fh),
_cooling_buffer(Slic3r::CoolingBuffer(this->_gcodegen)),
_spiral_vase(Slic3r::SpiralVase(this->config))
{
size_t layer_count {0};
if (config.complete_objects) {
layer_count = std::accumulate(objects.cbegin(), objects.cend(), layer_count, [](const size_t& ret, const PrintObject* obj){ return ret + (obj->copies().size() * obj->total_layer_count()); });
} else {
layer_count = std::accumulate(objects.cbegin(), objects.cend(), layer_count, [](const size_t& ret, const PrintObject* obj){ return ret + obj->total_layer_count(); });
}
_gcodegen.placeholder_parser = &(_print.placeholder_parser); // initialize
_gcodegen.layer_count = layer_count;
_gcodegen.enable_cooling_markers = true;
_gcodegen.apply_print_config(config);
if (config.spiral_vase) _spiral_vase.enable = true;
const auto extruders = _print.extruders();
_gcodegen.set_extruders(extruders.cbegin(), extruders.cend());
}
} // namespace Slic3r