Refactoring of ToolOrdering (wipe into infill / object)

Refactoring of GCode::_do_export()
Helper lower_bound and search functions similar to std, but without
needing the value object explicitely.
This commit is contained in:
bubnikv 2020-01-10 11:26:52 +01:00
parent cc2b9b8849
commit 15eedef74b
6 changed files with 428 additions and 322 deletions

View file

@ -792,153 +792,108 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
PROFILE_OUTPUT(debug_out_path("gcode-export-profile.txt").c_str()); PROFILE_OUTPUT(debug_out_path("gcode-export-profile.txt").c_str());
} }
#if ENABLE_THUMBNAIL_GENERATOR // free functions called by GCode::_do_export()
void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thumbnail_cb) namespace DoExport {
#else static void init_time_estimators(const PrintConfig &config, GCodeTimeEstimator &normal_time_estimator, GCodeTimeEstimator &silent_time_estimator, bool &silent_time_estimator_enabled)
void GCode::_do_export(Print& print, FILE* file)
#endif // ENABLE_THUMBNAIL_GENERATOR
{ {
PROFILE_FUNC();
// resets time estimators // resets time estimators
m_normal_time_estimator.reset(); normal_time_estimator.reset();
m_normal_time_estimator.set_dialect(print.config().gcode_flavor); normal_time_estimator.set_dialect(config.gcode_flavor);
m_normal_time_estimator.set_extrusion_axis(print.config().get_extrusion_axis()[0]); normal_time_estimator.set_extrusion_axis(config.get_extrusion_axis()[0]);
m_silent_time_estimator_enabled = (print.config().gcode_flavor == gcfMarlin) && print.config().silent_mode; silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlin) && config.silent_mode;
// Until we have a UI support for the other firmwares than the Marlin, use the hardcoded default values // Until we have a UI support for the other firmwares than the Marlin, use the hardcoded default values
// and let the user to enter the G-code limits into the start G-code. // and let the user to enter the G-code limits into the start G-code.
// If the following block is enabled for other firmwares than the Marlin, then the function // If the following block is enabled for other firmwares than the Marlin, then the function
// this->print_machine_envelope(file, print); // this->print_machine_envelope(file, print);
// shall be adjusted as well to produce a G-code block compatible with the particular firmware flavor. // shall be adjusted as well to produce a G-code block compatible with the particular firmware flavor.
if (print.config().gcode_flavor.value == gcfMarlin) { if (config.gcode_flavor.value == gcfMarlin) {
m_normal_time_estimator.set_max_acceleration((float)print.config().machine_max_acceleration_extruding.values[0]); normal_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values[0]);
m_normal_time_estimator.set_retract_acceleration((float)print.config().machine_max_acceleration_retracting.values[0]); normal_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values[0]);
m_normal_time_estimator.set_minimum_feedrate((float)print.config().machine_min_extruding_rate.values[0]); normal_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values[0]);
m_normal_time_estimator.set_minimum_travel_feedrate((float)print.config().machine_min_travel_rate.values[0]); normal_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values[0]);
m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)print.config().machine_max_acceleration_x.values[0]); normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values[0]);
m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)print.config().machine_max_acceleration_y.values[0]); normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values[0]);
m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)print.config().machine_max_acceleration_z.values[0]); normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values[0]);
m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)print.config().machine_max_acceleration_e.values[0]); normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values[0]);
m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)print.config().machine_max_feedrate_x.values[0]); normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values[0]);
m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)print.config().machine_max_feedrate_y.values[0]); normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values[0]);
m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)print.config().machine_max_feedrate_z.values[0]); normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values[0]);
m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)print.config().machine_max_feedrate_e.values[0]); normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values[0]);
m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)print.config().machine_max_jerk_x.values[0]); normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values[0]);
m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)print.config().machine_max_jerk_y.values[0]); normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values[0]);
m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)print.config().machine_max_jerk_z.values[0]); normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values[0]);
m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)print.config().machine_max_jerk_e.values[0]); normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values[0]);
if (m_silent_time_estimator_enabled) if (silent_time_estimator_enabled)
{ {
m_silent_time_estimator.reset(); silent_time_estimator.reset();
m_silent_time_estimator.set_dialect(print.config().gcode_flavor); silent_time_estimator.set_dialect(config.gcode_flavor);
m_silent_time_estimator.set_extrusion_axis(print.config().get_extrusion_axis()[0]); silent_time_estimator.set_extrusion_axis(config.get_extrusion_axis()[0]);
/* "Stealth mode" values can be just a copy of "normal mode" values /* "Stealth mode" values can be just a copy of "normal mode" values
* (when they aren't input for a printer preset). * (when they aren't input for a printer preset).
* Thus, use back value from values, instead of second one, which could be absent * Thus, use back value from values, instead of second one, which could be absent
*/ */
m_silent_time_estimator.set_max_acceleration((float)print.config().machine_max_acceleration_extruding.values.back()); silent_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values.back());
m_silent_time_estimator.set_retract_acceleration((float)print.config().machine_max_acceleration_retracting.values.back()); silent_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values.back());
m_silent_time_estimator.set_minimum_feedrate((float)print.config().machine_min_extruding_rate.values.back()); silent_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values.back());
m_silent_time_estimator.set_minimum_travel_feedrate((float)print.config().machine_min_travel_rate.values.back()); silent_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values.back());
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)print.config().machine_max_acceleration_x.values.back()); silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values.back());
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)print.config().machine_max_acceleration_y.values.back()); silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values.back());
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)print.config().machine_max_acceleration_z.values.back()); silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values.back());
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)print.config().machine_max_acceleration_e.values.back()); silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values.back());
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)print.config().machine_max_feedrate_x.values.back()); silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values.back());
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)print.config().machine_max_feedrate_y.values.back()); silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values.back());
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)print.config().machine_max_feedrate_z.values.back()); silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values.back());
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)print.config().machine_max_feedrate_e.values.back()); silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values.back());
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)print.config().machine_max_jerk_x.values.back()); silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values.back());
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)print.config().machine_max_jerk_y.values.back()); silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values.back());
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)print.config().machine_max_jerk_z.values.back()); silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values.back());
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)print.config().machine_max_jerk_e.values.back()); silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values.back());
if (print.config().single_extruder_multi_material) { if (config.single_extruder_multi_material) {
// As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
// are considered to be active for the single extruder multi-material printers only. // are considered to be active for the single extruder multi-material printers only.
m_silent_time_estimator.set_filament_load_times(print.config().filament_load_time.values); silent_time_estimator.set_filament_load_times(config.filament_load_time.values);
m_silent_time_estimator.set_filament_unload_times(print.config().filament_unload_time.values); silent_time_estimator.set_filament_unload_times(config.filament_unload_time.values);
} }
} }
} }
// Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful. // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful.
if (print.config().single_extruder_multi_material) { if (config.single_extruder_multi_material) {
// As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
// are considered to be active for the single extruder multi-material printers only. // are considered to be active for the single extruder multi-material printers only.
m_normal_time_estimator.set_filament_load_times(print.config().filament_load_time.values); normal_time_estimator.set_filament_load_times(config.filament_load_time.values);
m_normal_time_estimator.set_filament_unload_times(print.config().filament_unload_time.values); normal_time_estimator.set_filament_unload_times(config.filament_unload_time.values);
}
} }
static void init_gcode_analyzer(const PrintConfig &config, GCodeAnalyzer &analyzer)
{
// resets analyzer // resets analyzer
m_analyzer.reset(); analyzer.reset();
// send extruder offset data to analyzer // send extruder offset data to analyzer
GCodeAnalyzer::ExtruderOffsetsMap extruder_offsets; GCodeAnalyzer::ExtruderOffsetsMap extruder_offsets;
for (unsigned int extruder_id : print.extruders()) unsigned int num_extruders = static_cast<unsigned int>(config.nozzle_diameter.values.size());
for (unsigned int extruder_id = 0; extruder_id < num_extruders; ++ extruder_id)
{ {
Vec2d offset = print.config().extruder_offset.get_at(extruder_id); Vec2d offset = config.extruder_offset.get_at(extruder_id);
if (!offset.isApprox(Vec2d::Zero())) if (!offset.isApprox(Vec2d::Zero()))
extruder_offsets[extruder_id] = offset; extruder_offsets[extruder_id] = offset;
} }
m_analyzer.set_extruder_offsets(extruder_offsets); analyzer.set_extruder_offsets(extruder_offsets);
// tell analyzer about the extrusion axis // tell analyzer about the extrusion axis
m_analyzer.set_extrusion_axis(print.config().get_extrusion_axis()[0]); analyzer.set_extrusion_axis(config.get_extrusion_axis()[0]);
// send extruders count to analyzer to allow it to detect invalid extruder idxs // send extruders count to analyzer to allow it to detect invalid extruder idxs
const ConfigOptionStrings* extruders_opt = dynamic_cast<const ConfigOptionStrings*>(print.config().option("extruder_colour")); analyzer.set_extruders_count(num_extruders);
const ConfigOptionStrings* filamemts_opt = dynamic_cast<const ConfigOptionStrings*>(print.config().option("filament_colour"));
m_analyzer.set_extruders_count(std::max((unsigned int)extruders_opt->values.size(), (unsigned int)filamemts_opt->values.size()));
// tell analyzer about the gcode flavor // tell analyzer about the gcode flavor
m_analyzer.set_gcode_flavor(print.config().gcode_flavor); analyzer.set_gcode_flavor(config.gcode_flavor);
// resets analyzer's tracking data
m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm;
m_last_width = GCodeAnalyzer::Default_Width;
m_last_height = GCodeAnalyzer::Default_Height;
// How many times will be change_layer() called?
// change_layer() in turn increments the progress bar status.
m_layer_count = 0;
if (print.config().complete_objects.value) {
// Add each of the object's layers separately.
for (auto object : print.objects()) {
std::vector<coordf_t> zs;
zs.reserve(object->layers().size() + object->support_layers().size());
for (auto layer : object->layers())
zs.push_back(layer->print_z);
for (auto layer : object->support_layers())
zs.push_back(layer->print_z);
std::sort(zs.begin(), zs.end());
m_layer_count += (unsigned int)(object->copies().size() * (std::unique(zs.begin(), zs.end()) - zs.begin()));
} }
}
else {
// Print all objects with the same print_z together.
std::vector<coordf_t> zs;
for (auto object : print.objects()) {
zs.reserve(zs.size() + object->layers().size() + object->support_layers().size());
for (auto layer : object->layers())
zs.push_back(layer->print_z);
for (auto layer : object->support_layers())
zs.push_back(layer->print_z);
}
std::sort(zs.begin(), zs.end());
m_layer_count = (unsigned int)(std::unique(zs.begin(), zs.end()) - zs.begin());
}
print.throw_if_canceled();
m_enable_cooling_markers = true; static double autospeed_volumetric_limit(const Print &print)
this->apply_print_config(print.config());
this->set_extruders(print.extruders());
// Initialize custom gcode
Model* model = print.get_object(0)->model_object()->get_model();
m_custom_gcode_per_print_z = model->custom_gcode_per_print_z;
// Initialize autospeed.
{ {
// get the minimum cross-section used in the print // get the minimum cross-section used in the print
std::vector<double> mm3_per_mm; std::vector<double> mm3_per_mm;
@ -964,21 +919,227 @@ void GCode::_do_export(Print& print, FILE* file)
for (auto layer : object->support_layers()) for (auto layer : object->support_layers())
mm3_per_mm.push_back(layer->support_fills.min_mm3_per_mm()); mm3_per_mm.push_back(layer->support_fills.min_mm3_per_mm());
} }
print.throw_if_canceled();
// filter out 0-width segments // filter out 0-width segments
mm3_per_mm.erase(std::remove_if(mm3_per_mm.begin(), mm3_per_mm.end(), [](double v) { return v < 0.000001; }), mm3_per_mm.end()); mm3_per_mm.erase(std::remove_if(mm3_per_mm.begin(), mm3_per_mm.end(), [](double v) { return v < 0.000001; }), mm3_per_mm.end());
double volumetric_speed = 0.;
if (! mm3_per_mm.empty()) { if (! mm3_per_mm.empty()) {
// In order to honor max_print_speed we need to find a target volumetric // 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 // speed that we can use throughout the print. So we define this target
// volumetric speed as the volumetric speed produced by printing the // volumetric speed as the volumetric speed produced by printing the
// smallest cross-section at the maximum speed: any larger cross-section // smallest cross-section at the maximum speed: any larger cross-section
// will need slower feedrates. // will need slower feedrates.
m_volumetric_speed = *std::min_element(mm3_per_mm.begin(), mm3_per_mm.end()) * print.config().max_print_speed.value; volumetric_speed = *std::min_element(mm3_per_mm.begin(), mm3_per_mm.end()) * print.config().max_print_speed.value;
// limit such volumetric speed with max_volumetric_speed if set // limit such volumetric speed with max_volumetric_speed if set
if (print.config().max_volumetric_speed.value > 0) if (print.config().max_volumetric_speed.value > 0)
m_volumetric_speed = std::min(m_volumetric_speed, print.config().max_volumetric_speed.value); volumetric_speed = std::min(volumetric_speed, print.config().max_volumetric_speed.value);
}
return volumetric_speed;
}
static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention)
{
// Calculate wiping points if needed
if (print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material) {
Points skirt_points;
for (const ExtrusionEntity *ee : print.skirt().entities)
for (const ExtrusionPath &path : dynamic_cast<const ExtrusionLoop*>(ee)->paths)
append(skirt_points, path.polyline.points);
if (! skirt_points.empty()) {
Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points);
Polygons skirts;
for (unsigned int extruder_id : print.extruders()) {
const Vec2d &extruder_offset = print.config().extruder_offset.get_at(extruder_id);
Polygon s(outer_skirt);
s.translate(Point::new_scale(-extruder_offset(0), -extruder_offset(1)));
skirts.emplace_back(std::move(s));
}
ooze_prevention.enable = true;
ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), scale_(3.f)).front().equally_spaced_points(scale_(10.));
#if 0
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"ooze_prevention.svg",
red_polygons => \@skirts,
polygons => [$outer_skirt],
points => $gcodegen->ooze_prevention->standby_points,
);
#endif
} }
} }
}
#if ENABLE_THUMBNAIL_GENERATOR
template<typename WriteToOutput, typename ThrowIfCanceledCallback>
static void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector<Vec2d> &sizes, WriteToOutput &output, ThrowIfCanceledCallback throw_if_canceled)
{
// Write thumbnails using base64 encoding
if (thumbnail_cb != nullptr)
{
const size_t max_row_length = 78;
ThumbnailsList thumbnails;
thumbnail_cb(thumbnails, sizes, true, true, true, true);
for (const ThumbnailData& data : thumbnails)
{
if (data.is_valid())
{
size_t png_size = 0;
void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1);
if (png_data != nullptr)
{
std::string encoded;
encoded.resize(boost::beast::detail::base64::encoded_size(png_size));
encoded.resize(boost::beast::detail::base64::encode((void*)&encoded[0], (const void*)png_data, png_size));
output((boost::format("\n;\n; thumbnail begin %dx%d %d\n") % data.width % data.height % encoded.size()).str().c_str());
unsigned int row_count = 0;
while (encoded.size() > max_row_length)
{
output((boost::format("; %s\n") % encoded.substr(0, max_row_length)).str().c_str());
encoded = encoded.substr(max_row_length);
++row_count;
}
if (encoded.size() > 0)
output((boost::format("; %s\n") % encoded).str().c_str());
output("; thumbnail end\n;\n");
mz_free(png_data);
}
}
throw_if_canceled();
}
}
}
#endif // ENABLE_THUMBNAIL_GENERATOR
// Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section.
static std::string update_print_stats_and_format_filament_stats(
const GCodeTimeEstimator &normal_time_estimator,
const GCodeTimeEstimator &silent_time_estimator,
const bool silent_time_estimator_enabled,
const bool has_wipe_tower,
const WipeTowerData &wipe_tower_data,
const std::vector<Extruder> &extruders,
PrintStatistics &print_statistics)
{
std::string filament_stats_string_out;
print_statistics.clear();
print_statistics.estimated_normal_print_time = normal_time_estimator.get_time_dhms();
print_statistics.estimated_silent_print_time = silent_time_estimator_enabled ? silent_time_estimator.get_time_dhms() : "N/A";
print_statistics.estimated_normal_color_print_times = normal_time_estimator.get_color_times_dhms(true);
if (silent_time_estimator_enabled)
print_statistics.estimated_silent_color_print_times = silent_time_estimator.get_color_times_dhms(true);
print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges);
if (! extruders.empty()) {
std::pair<std::string, unsigned int> out_filament_used_mm ("; filament used [mm] = ", 0);
std::pair<std::string, unsigned int> out_filament_used_cm3("; filament used [cm3] = ", 0);
std::pair<std::string, unsigned int> out_filament_used_g ("; filament used [g] = ", 0);
std::pair<std::string, unsigned int> out_filament_cost ("; filament cost = ", 0);
for (const Extruder &extruder : extruders) {
double used_filament = extruder.used_filament() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] : 0.f);
double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter
double filament_weight = extruded_volume * extruder.filament_density() * 0.001;
double filament_cost = filament_weight * extruder.filament_cost() * 0.001;
auto append = [&extruder, &extruders](std::pair<std::string, unsigned int> &dst, const char *tmpl, double value) {
while (dst.second < extruder.id()) {
// Fill in the non-printing extruders with zeros.
dst.first += (dst.second > 0) ? ", 0" : "0";
++ dst.second;
}
if (dst.second > 0)
dst.first += ", ";
char buf[64];
sprintf(buf, tmpl, value);
dst.first += buf;
++ dst.second;
};
print_statistics.filament_stats.insert(std::pair<size_t, float>{extruder.id(), (float)used_filament});
append(out_filament_used_mm, "%.1lf", used_filament);
append(out_filament_used_cm3, "%.1lf", extruded_volume * 0.001);
if (filament_weight > 0.) {
print_statistics.total_weight = print_statistics.total_weight + filament_weight;
append(out_filament_used_g, "%.1lf", filament_weight);
if (filament_cost > 0.) {
print_statistics.total_cost = print_statistics.total_cost + filament_cost;
append(out_filament_cost, "%.1lf", filament_cost);
}
}
print_statistics.total_used_filament += used_filament;
print_statistics.total_extruded_volume += extruded_volume;
print_statistics.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.;
print_statistics.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.;
}
filament_stats_string_out += out_filament_used_mm.first;
filament_stats_string_out += out_filament_used_cm3.first;
if (out_filament_used_g.second)
filament_stats_string_out += out_filament_used_g.first;
if (out_filament_cost.second)
filament_stats_string_out += out_filament_cost.first;
}
return filament_stats_string_out;
}
}
#if ENABLE_THUMBNAIL_GENERATOR
void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thumbnail_cb)
#else
void GCode::_do_export(Print& print, FILE* file)
#endif // ENABLE_THUMBNAIL_GENERATOR
{
PROFILE_FUNC();
DoExport::init_time_estimators(print.config(),
// modifies the following:
m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled);
DoExport::init_gcode_analyzer(print.config(), m_analyzer);
// resets analyzer's tracking data
m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm;
m_last_width = GCodeAnalyzer::Default_Width;
m_last_height = GCodeAnalyzer::Default_Height;
// How many times will be change_layer() called?
// change_layer() in turn increments the progress bar status.
m_layer_count = 0;
if (print.config().complete_objects.value) {
// Add each of the object's layers separately.
for (auto object : print.objects()) {
std::vector<coordf_t> zs;
zs.reserve(object->layers().size() + object->support_layers().size());
for (auto layer : object->layers())
zs.push_back(layer->print_z);
for (auto layer : object->support_layers())
zs.push_back(layer->print_z);
std::sort(zs.begin(), zs.end());
m_layer_count += (unsigned int)(object->copies().size() * (std::unique(zs.begin(), zs.end()) - zs.begin()));
}
} else {
// Print all objects with the same print_z together.
std::vector<coordf_t> zs;
for (auto object : print.objects()) {
zs.reserve(zs.size() + object->layers().size() + object->support_layers().size());
for (auto layer : object->layers())
zs.push_back(layer->print_z);
for (auto layer : object->support_layers())
zs.push_back(layer->print_z);
}
std::sort(zs.begin(), zs.end());
m_layer_count = (unsigned int)(std::unique(zs.begin(), zs.end()) - zs.begin());
}
print.throw_if_canceled();
m_enable_cooling_markers = true;
this->apply_print_config(print.config());
this->set_extruders(print.extruders());
// Initialize custom gcode
Model* model = print.get_object(0)->model_object()->get_model();
m_custom_gcode_per_print_z = model->custom_gcode_per_print_z;
m_volumetric_speed = DoExport::autospeed_volumetric_limit(print);
print.throw_if_canceled(); print.throw_if_canceled();
m_cooling_buffer = make_unique<CoolingBuffer>(*this); m_cooling_buffer = make_unique<CoolingBuffer>(*this);
@ -996,47 +1157,9 @@ void GCode::_do_export(Print& print, FILE* file)
// Write information on the generator. // Write information on the generator.
_write_format(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str()); _write_format(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str());
#if ENABLE_THUMBNAIL_GENERATOR DoExport::export_thumbnails_to_file(thumbnail_cb, print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values,
// Write thumbnails using base64 encoding [this, file](const char* sz) { this->_write(file, sz); },
if (thumbnail_cb != nullptr) [&print]() { print.throw_if_canceled(); });
{
const size_t max_row_length = 78;
ThumbnailsList thumbnails;
thumbnail_cb(thumbnails, print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true, true);
for (const ThumbnailData& data : thumbnails)
{
if (data.is_valid())
{
size_t png_size = 0;
void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1);
if (png_data != nullptr)
{
std::string encoded;
encoded.resize(boost::beast::detail::base64::encoded_size(png_size));
encoded.resize(boost::beast::detail::base64::encode((void*)&encoded[0], (const void*)png_data, png_size));
_write_format(file, "\n;\n; thumbnail begin %dx%d %d\n", data.width, data.height, encoded.size());
unsigned int row_count = 0;
while (encoded.size() > max_row_length)
{
_write_format(file, "; %s\n", encoded.substr(0, max_row_length).c_str());
encoded = encoded.substr(max_row_length);
++row_count;
}
if (encoded.size() > 0)
_write_format(file, "; %s\n", encoded.c_str());
_write(file, "; thumbnail end\n;\n");
mz_free(png_data);
}
}
print.throw_if_canceled();
}
}
#endif // ENABLE_THUMBNAIL_GENERATOR
// Write notes (content of the Print Settings tab -> Notes) // Write notes (content of the Print Settings tab -> Notes)
{ {
@ -1079,9 +1202,6 @@ void GCode::_do_export(Print& print, FILE* file)
_writeln(file, GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag); _writeln(file, GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag);
} }
// Hold total number of print toolchanges. Check for negative toolchanges (single extruder mode) and set to 0 (no tool change).
int total_toolchanges = std::max(0, print.wipe_tower_data().number_of_toolchanges);
// Prepare the helper object for replacing placeholders in custom G-code and output filename. // Prepare the helper object for replacing placeholders in custom G-code and output filename.
m_placeholder_parser = print.placeholder_parser(); m_placeholder_parser = print.placeholder_parser();
m_placeholder_parser.update_timestamp(); m_placeholder_parser.update_timestamp();
@ -1187,7 +1307,8 @@ void GCode::_do_export(Print& print, FILE* file)
// For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided. // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided.
m_placeholder_parser.set("has_wipe_tower", has_wipe_tower); m_placeholder_parser.set("has_wipe_tower", has_wipe_tower);
m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming); m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming);
m_placeholder_parser.set("total_toolchanges", total_toolchanges); m_placeholder_parser.set("total_toolchanges", std::max(0, print.wipe_tower_data().number_of_toolchanges)); // Check for negative toolchanges (single extruder mode) and set to 0 (no tool change).
std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id); std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id);
// Set bed temperature if the start G-code does not contain any bed temp control G-codes. // Set bed temperature if the start G-code does not contain any bed temp control G-codes.
this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true); this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true);
@ -1195,12 +1316,8 @@ void GCode::_do_export(Print& print, FILE* file)
this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false); this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false);
if (m_enable_analyzer) if (m_enable_analyzer)
{
// adds tag for analyzer // adds tag for analyzer
char buf[32]; _write_format(file, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
_writeln(file, buf);
}
// Write the custom start G-code // Write the custom start G-code
_writeln(file, start_gcode); _writeln(file, start_gcode);
@ -1228,35 +1345,8 @@ void GCode::_do_export(Print& print, FILE* file)
} }
// Calculate wiping points if needed // Calculate wiping points if needed
if (print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material) { DoExport::init_ooze_prevention(print, m_ooze_prevention);
Points skirt_points;
for (const ExtrusionEntity *ee : print.skirt().entities)
for (const ExtrusionPath &path : dynamic_cast<const ExtrusionLoop*>(ee)->paths)
append(skirt_points, path.polyline.points);
if (! skirt_points.empty()) {
Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points);
Polygons skirts;
for (unsigned int extruder_id : print.extruders()) {
const Vec2d &extruder_offset = print.config().extruder_offset.get_at(extruder_id);
Polygon s(outer_skirt);
s.translate(Point::new_scale(-extruder_offset(0), -extruder_offset(1)));
skirts.emplace_back(std::move(s));
}
m_ooze_prevention.enable = true;
m_ooze_prevention.standby_points =
offset(Slic3r::Geometry::convex_hull(skirts), scale_(3.f)).front().equally_spaced_points(scale_(10.));
#if 0
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"ooze_prevention.svg",
red_polygons => \@skirts,
polygons => [$outer_skirt],
points => $gcodegen->ooze_prevention->standby_points,
);
#endif
}
print.throw_if_canceled(); print.throw_if_canceled();
}
if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) { if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) {
// Set initial extruder only after custom start G-code. // Set initial extruder only after custom start G-code.
@ -1387,12 +1477,8 @@ void GCode::_do_export(Print& print, FILE* file)
_write(file, m_writer.set_fan(false)); _write(file, m_writer.set_fan(false));
if (m_enable_analyzer) if (m_enable_analyzer)
{
// adds tag for analyzer // adds tag for analyzer
char buf[32]; _write_format(file, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
_writeln(file, buf);
}
// Process filament-specific gcode in extruder order. // Process filament-specific gcode in extruder order.
{ {
@ -1432,60 +1518,13 @@ void GCode::_do_export(Print& print, FILE* file)
m_silent_time_estimator.calculate_time(false); m_silent_time_estimator.calculate_time(false);
// Get filament stats. // Get filament stats.
print.m_print_statistics.clear(); _write(file, DoExport::update_print_stats_and_format_filament_stats(
print.m_print_statistics.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms(); // Const inputs
print.m_print_statistics.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A"; m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled,
print.m_print_statistics.estimated_normal_color_print_times = m_normal_time_estimator.get_color_times_dhms(true); has_wipe_tower, print.wipe_tower_data(),
if (m_silent_time_estimator_enabled) m_writer.extruders(),
print.m_print_statistics.estimated_silent_color_print_times = m_silent_time_estimator.get_color_times_dhms(true); // Modifies
print.m_print_statistics.total_toolchanges = total_toolchanges; print.m_print_statistics));
std::vector<Extruder> extruders = m_writer.extruders();
if (! extruders.empty()) {
std::pair<std::string, unsigned int> out_filament_used_mm ("; filament used [mm] = ", 0);
std::pair<std::string, unsigned int> out_filament_used_cm3("; filament used [cm3] = ", 0);
std::pair<std::string, unsigned int> out_filament_used_g ("; filament used [g] = ", 0);
std::pair<std::string, unsigned int> out_filament_cost ("; filament cost = ", 0);
for (const Extruder &extruder : extruders) {
double used_filament = extruder.used_filament() + (has_wipe_tower ? print.wipe_tower_data().used_filament[extruder.id()] : 0.f);
double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? print.wipe_tower_data().used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter
double filament_weight = extruded_volume * extruder.filament_density() * 0.001;
double filament_cost = filament_weight * extruder.filament_cost() * 0.001;
auto append = [&extruder, &extruders](std::pair<std::string, unsigned int> &dst, const char *tmpl, double value) {
while (dst.second < extruder.id()) {
// Fill in the non-printing extruders with zeros.
dst.first += (dst.second > 0) ? ", 0" : "0";
++ dst.second;
}
if (dst.second > 0)
dst.first += ", ";
char buf[64];
sprintf(buf, tmpl, value);
dst.first += buf;
++ dst.second;
};
print.m_print_statistics.filament_stats.insert(std::pair<size_t, float>{extruder.id(), (float)used_filament});
append(out_filament_used_mm, "%.1lf", used_filament);
append(out_filament_used_cm3, "%.1lf", extruded_volume * 0.001);
if (filament_weight > 0.) {
print.m_print_statistics.total_weight = print.m_print_statistics.total_weight + filament_weight;
append(out_filament_used_g, "%.1lf", filament_weight);
if (filament_cost > 0.) {
print.m_print_statistics.total_cost = print.m_print_statistics.total_cost + filament_cost;
append(out_filament_cost, "%.1lf", filament_cost);
}
}
print.m_print_statistics.total_used_filament += used_filament;
print.m_print_statistics.total_extruded_volume += extruded_volume;
print.m_print_statistics.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.;
print.m_print_statistics.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.;
}
_writeln(file, out_filament_used_mm.first);
_writeln(file, out_filament_used_cm3.first);
if (out_filament_used_g.second)
_writeln(file, out_filament_used_g.first);
if (out_filament_cost.second)
_writeln(file, out_filament_cost.first);
}
_write_format(file, "; total filament used [g] = %.1lf\n", print.m_print_statistics.total_weight); _write_format(file, "; total filament used [g] = %.1lf\n", print.m_print_statistics.total_weight);
_write_format(file, "; total filament cost = %.1lf\n", print.m_print_statistics.total_cost); _write_format(file, "; total filament cost = %.1lf\n", print.m_print_statistics.total_cost);
if (print.m_print_statistics.total_toolchanges > 0) if (print.m_print_statistics.total_toolchanges > 0)
@ -2015,7 +2054,7 @@ void GCode::process_layer(
slices_test_order.reserve(n_slices); slices_test_order.reserve(n_slices);
for (size_t i = 0; i < n_slices; ++ i) for (size_t i = 0; i < n_slices; ++ i)
slices_test_order.emplace_back(i); slices_test_order.emplace_back(i);
std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](int i, int j) { std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](size_t i, size_t j) {
const Vec2d s1 = layer_surface_bboxes[i].size().cast<double>(); const Vec2d s1 = layer_surface_bboxes[i].size().cast<double>();
const Vec2d s2 = layer_surface_bboxes[j].size().cast<double>(); const Vec2d s2 = layer_surface_bboxes[j].size().cast<double>();
return s1.x() * s1.y() < s2.x() * s2.y(); return s1.x() * s1.y() < s2.x() * s2.y();
@ -2188,7 +2227,7 @@ void GCode::process_layer(
m_layer = layers[instance_to_print.layer_id].layer(); m_layer = layers[instance_to_print.layer_id].layer();
} }
for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) { for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) {
const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, instance_to_print.instance_id, extruder_id, print_wipe_extrusions) : island.by_region; const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast<unsigned int>(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region;
//FIXME the following code prints regions in the order they are defined, the path is not optimized in any way. //FIXME the following code prints regions in the order they are defined, the path is not optimized in any way.
if (print.config().infill_first) { if (print.config().infill_first) {
gcode += this->extrude_infill(print, by_region_specific); gcode += this->extrude_infill(print, by_region_specific);
@ -3277,7 +3316,7 @@ Point GCode::gcode_to_point(const Vec2d &point) const
// Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed // Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed
// during infill/perimeter wiping, or normally (depends on wiping_entities parameter) // during infill/perimeter wiping, or normally (depends on wiping_entities parameter)
// Fills in by_region_per_copy_cache and returns its reference. // Fills in by_region_per_copy_cache and returns its reference.
const std::vector<GCode::ObjectByExtruder::Island::Region>& GCode::ObjectByExtruder::Island::by_region_per_copy(std::vector<Region> &by_region_per_copy_cache, unsigned int copy, int extruder, bool wiping_entities) const const std::vector<GCode::ObjectByExtruder::Island::Region>& GCode::ObjectByExtruder::Island::by_region_per_copy(std::vector<Region> &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities) const
{ {
bool has_overrides = false; bool has_overrides = false;
for (const auto& reg : by_region) for (const auto& reg : by_region)
@ -3313,7 +3352,7 @@ const std::vector<GCode::ObjectByExtruder::Island::Region>& GCode::ObjectByExtru
for (unsigned int i = 0; i < overrides.size(); ++ i) { for (unsigned int i = 0; i < overrides.size(); ++ i) {
const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i];
// This copy (aka object instance) should be printed with this extruder, which overrides the default one. // This copy (aka object instance) should be printed with this extruder, which overrides the default one.
if (this_override != nullptr && (*this_override)[copy] == extruder) if (this_override != nullptr && (*this_override)[copy] == int(extruder))
target_eec.emplace_back(entities[i]); target_eec.emplace_back(entities[i]);
} }
} else { } else {
@ -3322,7 +3361,7 @@ const std::vector<GCode::ObjectByExtruder::Island::Region>& GCode::ObjectByExtru
for (; i < overrides.size(); ++ i) { for (; i < overrides.size(); ++ i) {
const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i];
// This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one. // This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one.
if (this_override == nullptr || (*this_override)[copy] == -extruder-1) if (this_override == nullptr || (*this_override)[copy] == -int(extruder)-1)
target_eec.emplace_back(entities[i]); target_eec.emplace_back(entities[i]);
} }
for (; i < overrides.size(); ++ i) for (; i < overrides.size(); ++ i)

View file

@ -197,7 +197,7 @@ public:
// append full config to the given string // append full config to the given string
static void append_full_config(const Print& print, std::string& str); static void append_full_config(const Print& print, std::string& str);
protected: private:
#if ENABLE_THUMBNAIL_GENERATOR #if ENABLE_THUMBNAIL_GENERATOR
void _do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thumbnail_cb); void _do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thumbnail_cb);
#else #else
@ -274,7 +274,7 @@ protected:
std::vector<Region> by_region; // all extrusions for this island, grouped by regions std::vector<Region> by_region; // all extrusions for this island, grouped by regions
// Fills in by_region_per_copy_cache and returns its reference. // Fills in by_region_per_copy_cache and returns its reference.
const std::vector<Region>& by_region_per_copy(std::vector<Region> &by_region_per_copy_cache, unsigned int copy, int extruder, bool wiping_entities = false) const; const std::vector<Region>& by_region_per_copy(std::vector<Region> &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities = false) const;
}; };
std::vector<Island> islands; std::vector<Island> islands;
}; };

View file

@ -13,6 +13,8 @@
#include <cassert> #include <cassert>
#include <limits> #include <limits>
#include <libslic3r.h>
namespace Slic3r { namespace Slic3r {
@ -150,7 +152,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object)
if (m_print_config_ptr) { // in this case complete_objects is false (see ToolOrdering constructors) if (m_print_config_ptr) { // in this case complete_objects is false (see ToolOrdering constructors)
something_nonoverriddable = false; something_nonoverriddable = false;
for (const auto& eec : layerm->perimeters.entities) // let's check if there are nonoverriddable entities for (const auto& eec : layerm->perimeters.entities) // let's check if there are nonoverriddable entities
if (!layer_tools.wiping_extrusions().is_overriddable(dynamic_cast<const ExtrusionEntityCollection&>(*eec), *m_print_config_ptr, object, region)) { if (!layer_tools.wiping_extrusions().is_overriddable_and_mark(dynamic_cast<const ExtrusionEntityCollection&>(*eec), *m_print_config_ptr, object, region)) {
something_nonoverriddable = true; something_nonoverriddable = true;
break; break;
} }
@ -176,7 +178,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object)
has_infill = true; has_infill = true;
if (m_print_config_ptr) { if (m_print_config_ptr) {
if (!something_nonoverriddable && !layer_tools.wiping_extrusions().is_overriddable(*fill, *m_print_config_ptr, object, region)) if (!something_nonoverriddable && !layer_tools.wiping_extrusions().is_overriddable_and_mark(*fill, *m_print_config_ptr, object, region))
something_nonoverriddable = true; something_nonoverriddable = true;
} }
} }
@ -463,7 +465,6 @@ bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, con
return true; return true;
} }
// Following function iterates through all extrusions on the layer, remembers those that could be used for wiping after toolchange // Following function iterates through all extrusions on the layer, remembers those that could be used for wiping after toolchange
// and returns volume that is left to be wiped on the wipe tower. // and returns volume that is left to be wiped on the wipe tower.
float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe) float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe)
@ -471,7 +472,8 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
const LayerTools& lt = *m_layer_tools; const LayerTools& lt = *m_layer_tools;
const float min_infill_volume = 0.f; // ignore infill with smaller volume than this const float min_infill_volume = 0.f; // ignore infill with smaller volume than this
if (print.config().filament_soluble.get_at(old_extruder) || print.config().filament_soluble.get_at(new_extruder)) assert(volume_to_wipe >= 0.);
if (! this->something_overridable || volume_to_wipe == 0. || print.config().filament_soluble.get_at(old_extruder) || print.config().filament_soluble.get_at(new_extruder))
return volume_to_wipe; // Soluble filament cannot be wiped in a random infill, neither the filament after it return volume_to_wipe; // Soluble filament cannot be wiped in a random infill, neither the filament after it
// we will sort objects so that dedicated for wiping are at the beginning: // we will sort objects so that dedicated for wiping are at the beginning:
@ -495,13 +497,13 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
const PrintObject* object = object_list[i]; const PrintObject* object = object_list[i];
// Finds this layer: // Finds this layer:
auto this_layer_it = std::find_if(object->layers().begin(), object->layers().end(), [&lt](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)<EPSILON; }); const Layer* this_layer = object->get_layer_at_printz(lt.print_z, EPSILON);
if (this_layer_it == object->layers().end()) if (this_layer == nullptr)
continue; continue;
const Layer* this_layer = *this_layer_it;
size_t num_of_copies = object->copies().size(); size_t num_of_copies = object->copies().size();
for (unsigned int copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves // iterate through copies (aka PrintObject instances) first, so that we mark neighbouring infills to minimize travel moves
for (unsigned int copy = 0; copy < num_of_copies; ++copy) {
for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) { for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) {
const auto& region = *object->print()->regions()[region_id]; const auto& region = *object->print()->regions()[region_id];
@ -509,18 +511,15 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
if (!region.config().wipe_into_infill && !object->config().wipe_into_objects) if (!region.config().wipe_into_infill && !object->config().wipe_into_objects)
continue; continue;
bool wipe_into_infill_only = ! object->config().wipe_into_objects && region.config().wipe_into_infill;
if ((!print.config().infill_first ? perimeters_done : !perimeters_done) || (!object->config().wipe_into_objects && region.config().wipe_into_infill)) { if (print.config().infill_first != perimeters_done || wipe_into_infill_only) {
for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->fills.entities) { // iterate through all infill Collections for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->fills.entities) { // iterate through all infill Collections
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee); auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
if (!is_overriddable(*fill, print.config(), *object, region)) if (!is_overriddable(*fill, print.config(), *object, region))
continue; continue;
if (volume_to_wipe<=0) if (wipe_into_infill_only && ! print.config().infill_first)
continue;
if (!object->config().wipe_into_objects && !print.config().infill_first && region.config().wipe_into_infill)
// In this case we must check that the original extruder is used on this layer before the one we are overridding // In this case we must check that the original extruder is used on this layer before the one we are overridding
// (and the perimeters will be finished before the infill is printed): // (and the perimeters will be finished before the infill is printed):
if (!lt.is_extruder_order(region.config().perimeter_extruder - 1, new_extruder)) if (!lt.is_extruder_order(region.config().perimeter_extruder - 1, new_extruder))
@ -528,32 +527,32 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
if ((!is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume)) { // this infill will be used to wipe this extruder if ((!is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume)) { // this infill will be used to wipe this extruder
set_extruder_override(fill, copy, new_extruder, num_of_copies); set_extruder_override(fill, copy, new_extruder, num_of_copies);
volume_to_wipe -= float(fill->total_volume()); if ((volume_to_wipe -= float(fill->total_volume())) <= 0.f)
// More material was purged already than asked for.
return 0.f;
} }
} }
} }
// Now the same for perimeters - see comments above for explanation: // Now the same for perimeters - see comments above for explanation:
if (object->config().wipe_into_objects && (print.config().infill_first ? perimeters_done : !perimeters_done)) if (object->config().wipe_into_objects && print.config().infill_first == perimeters_done)
{ {
for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->perimeters.entities) { for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->perimeters.entities) {
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee); auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
if (!is_overriddable(*fill, print.config(), *object, region)) if (is_overriddable(*fill, print.config(), *object, region) && !is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume) {
continue;
if (volume_to_wipe<=0)
continue;
if ((!is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume)) {
set_extruder_override(fill, copy, new_extruder, num_of_copies); set_extruder_override(fill, copy, new_extruder, num_of_copies);
volume_to_wipe -= float(fill->total_volume()); if ((volume_to_wipe -= float(fill->total_volume())) <= 0.f)
// More material was purged already than asked for.
return 0.f;
} }
} }
} }
} }
} }
} }
return std::max(0.f, volume_to_wipe); // Some purge remains to be done on the Wipe Tower.
assert(volume_to_wipe > 0.);
return volume_to_wipe;
} }
@ -564,16 +563,18 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
// them again and make sure we override it. // them again and make sure we override it.
void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
{ {
if (! this->something_overridable)
return;
const LayerTools& lt = *m_layer_tools; const LayerTools& lt = *m_layer_tools;
unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config()); unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config());
unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config()); unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config());
for (const PrintObject* object : print.objects()) { for (const PrintObject* object : print.objects()) {
// Finds this layer: // Finds this layer:
auto this_layer_it = std::find_if(object->layers().begin(), object->layers().end(), [&lt](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)<EPSILON; }); const Layer* this_layer = object->get_layer_at_printz(lt.print_z, EPSILON);
if (this_layer_it == object->layers().end()) if (this_layer == nullptr)
continue; continue;
const Layer* this_layer = *this_layer_it;
size_t num_of_copies = object->copies().size(); size_t num_of_copies = object->copies().size();
for (size_t copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves for (size_t copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves
@ -597,8 +598,7 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
if (print.config().infill_first if (print.config().infill_first
|| object->config().wipe_into_objects // in this case the perimeter is overridden, so we can override by the last one safely || object->config().wipe_into_objects // in this case the perimeter is overridden, so we can override by the last one safely
|| lt.is_extruder_order(region.config().perimeter_extruder - 1, last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints || lt.is_extruder_order(region.config().perimeter_extruder - 1, last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints
|| std::find(lt.extruders.begin(), lt.extruders.end(), region.config().infill_extruder - 1) == lt.extruders.end()) // we have to force override - this could violate infill_first (FIXME) || ! lt.has_extruder(region.config().infill_extruder - 1))) // we have to force override - this could violate infill_first (FIXME)
)
set_extruder_override(fill, copy, (print.config().infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies); set_extruder_override(fill, copy, (print.config().infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies);
else { else {
// In this case we can (and should) leave it to be printed normally. // In this case we can (and should) leave it to be printed normally.
@ -609,10 +609,7 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
// Now the same for perimeters - see comments above for explanation: // Now the same for perimeters - see comments above for explanation:
for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->perimeters.entities) { // iterate through all perimeter Collections for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->perimeters.entities) { // iterate through all perimeter Collections
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee); auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
if (!is_overriddable(*fill, print.config(), *object, region) if (is_overriddable(*fill, print.config(), *object, region) && ! is_entity_overridden(fill, copy))
|| is_entity_overridden(fill, copy) )
continue;
set_extruder_override(fill, copy, (print.config().infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies); set_extruder_override(fill, copy, (print.config().infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies);
} }
} }

View file

@ -48,6 +48,11 @@ public:
void ensure_perimeters_infills_order(const Print& print); void ensure_perimeters_infills_order(const Print& print);
bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const; bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const;
bool is_overriddable_and_mark(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) {
bool out = this->is_overriddable(ee, print_config, object, region);
this->something_overridable |= out;
return out;
}
void set_layer_tools_ptr(const LayerTools* lt) { m_layer_tools = lt; } void set_layer_tools_ptr(const LayerTools* lt) { m_layer_tools = lt; }
@ -60,10 +65,12 @@ private:
// Returns true in case that entity is not printed with its usual extruder for a given copy: // Returns true in case that entity is not printed with its usual extruder for a given copy:
bool is_entity_overridden(const ExtrusionEntity* entity, size_t copy_id) const { bool is_entity_overridden(const ExtrusionEntity* entity, size_t copy_id) const {
return (entity_map.find(entity) == entity_map.end() ? false : entity_map.at(entity).at(copy_id) != -1); auto it = entity_map.find(entity);
return it == entity_map.end() ? false : it->second[copy_id] != -1;
} }
std::map<const ExtrusionEntity*, ExtruderPerCopy> entity_map; // to keep track of who prints what std::map<const ExtrusionEntity*, ExtruderPerCopy> entity_map; // to keep track of who prints what
bool something_overridable = false;
bool something_overridden = false; bool something_overridden = false;
const LayerTools* m_layer_tools; // so we know which LayerTools object this belongs to const LayerTools* m_layer_tools; // so we know which LayerTools object this belongs to
}; };

View file

@ -15,6 +15,8 @@
#include "GCode/ThumbnailData.hpp" #include "GCode/ThumbnailData.hpp"
#endif // ENABLE_THUMBNAIL_GENERATOR #endif // ENABLE_THUMBNAIL_GENERATOR
#include "libslic3r.h"
namespace Slic3r { namespace Slic3r {
class Print; class Print;
@ -116,8 +118,21 @@ public:
size_t total_layer_count() const { return this->layer_count() + this->support_layer_count(); } size_t total_layer_count() const { return this->layer_count() + this->support_layer_count(); }
size_t layer_count() const { return m_layers.size(); } size_t layer_count() const { return m_layers.size(); }
void clear_layers(); void clear_layers();
Layer* get_layer(int idx) { return m_layers[idx]; }
const Layer* get_layer(int idx) const { return m_layers[idx]; } const Layer* get_layer(int idx) const { return m_layers[idx]; }
Layer* get_layer(int idx) { return m_layers[idx]; }
// Get a layer exactly at print_z.
const Layer* get_layer_at_printz(coordf_t print_z) const {
auto it = Slic3r::lower_bound_by_predicate(m_layers.begin(), m_layers.end(), [print_z](const Layer *layer) { return layer->print_z < print_z; });
return (it == m_layers.end() || (*it)->print_z != print_z) ? nullptr : *it;
}
Layer* get_layer_at_printz(coordf_t print_z) { return const_cast<Layer*>(std::as_const(*this).get_layer_at_printz(print_z)); }
// Get a layer approximately at print_z.
const Layer* get_layer_at_printz(coordf_t print_z, coordf_t epsilon) const {
coordf_t limit = print_z + epsilon;
auto it = Slic3r::lower_bound_by_predicate(m_layers.begin(), m_layers.end(), [limit](const Layer *layer) { return layer->print_z < limit; });
return (it == m_layers.end() || (*it)->print_z < print_z - epsilon) ? nullptr : *it;
}
Layer* get_layer_at_printz(coordf_t print_z, coordf_t epsilon) { return const_cast<Layer*>(std::as_const(*this).get_layer_at_printz(print_z, epsilon)); }
// print_z: top of the layer; slice_z: center of the layer. // print_z: top of the layer; slice_z: center of the layer.
Layer* add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z); Layer* add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z);
@ -345,6 +360,7 @@ public:
const PrintConfig& config() const { return m_config; } const PrintConfig& config() const { return m_config; }
const PrintObjectConfig& default_object_config() const { return m_default_object_config; } const PrintObjectConfig& default_object_config() const { return m_default_object_config; }
const PrintRegionConfig& default_region_config() const { return m_default_region_config; } const PrintRegionConfig& default_region_config() const { return m_default_region_config; }
//FIXME returning const vector to non-const PrintObject*, caller could modify PrintObjects!
const PrintObjectPtrs& objects() const { return m_objects; } const PrintObjectPtrs& objects() const { return m_objects; }
PrintObject* get_object(size_t idx) { return m_objects[idx]; } PrintObject* get_object(size_t idx) { return m_objects[idx]; }
const PrintObject* get_object(size_t idx) const { return m_objects[idx]; } const PrintObject* get_object(size_t idx) const { return m_objects[idx]; }

View file

@ -158,6 +158,53 @@ inline std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
} }
// Variant of std::lower_bound() with compare predicate, but without the key.
// This variant is very useful in case that the T type is large or it does not even have a public constructor.
template<class ForwardIt, class LowerThanKeyPredicate>
ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKeyPredicate lower_thank_key)
{
ForwardIt it;
typename std::iterator_traits<ForwardIt>::difference_type count, step;
count = std::distance(first, last);
while (count > 0) {
it = first;
step = count / 2;
std::advance(it, step);
if (lower_thank_key(*it)) {
first = ++it;
count -= step + 1;
}
else
count = step;
}
return first;
}
// from https://en.cppreference.com/w/cpp/algorithm/lower_bound
template<class ForwardIt, class T, class Compare=std::less<>>
ForwardIt binary_find(ForwardIt first, ForwardIt last, const T& value, Compare comp={})
{
// Note: BOTH type T and the type after ForwardIt is dereferenced
// must be implicitly convertible to BOTH Type1 and Type2, used in Compare.
// This is stricter than lower_bound requirement (see above)
first = std::lower_bound(first, last, value, comp);
return first != last && !comp(value, *first) ? first : last;
}
// from https://en.cppreference.com/w/cpp/algorithm/lower_bound
template<class ForwardIt, class LowerThanKeyPredicate, class EqualToKeyPredicate>
ForwardIt binary_find_by_predicate(ForwardIt first, ForwardIt last, LowerThanKeyPredicate lower_thank_key, EqualToKeyPredicate equal_to_key)
{
// Note: BOTH type T and the type after ForwardIt is dereferenced
// must be implicitly convertible to BOTH Type1 and Type2, used in Compare.
// This is stricter than lower_bound requirement (see above)
first = lower_bound_by_predicate(first, last, lower_thank_key);
return first != last && equal_to_key(*first) ? first : last;
}
template<typename T> template<typename T>
static inline T sqr(T x) static inline T sqr(T x)
{ {