|
|
|
|
@@ -83,7 +83,6 @@ namespace Slic3r {
|
|
|
|
|
gcode += '\n';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Return true if tch_prefix is found in custom_gcode
|
|
|
|
|
static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder)
|
|
|
|
|
{
|
|
|
|
|
@@ -146,13 +145,14 @@ namespace Slic3r {
|
|
|
|
|
|
|
|
|
|
int OozePrevention::_get_temp(const GCodeGenerator &gcodegen) const
|
|
|
|
|
{
|
|
|
|
|
// First layer temperature should be used when on the first layer (obviously) and when
|
|
|
|
|
// "other layers" is set to zero (which means it should not be used).
|
|
|
|
|
return (gcodegen.layer() == nullptr || gcodegen.layer()->id() == 0
|
|
|
|
|
|| gcodegen.config().temperature.get_at(gcodegen.writer().extruder()->id()) == 0)
|
|
|
|
|
? gcodegen.config().first_layer_temperature.get_at(gcodegen.writer().extruder()->id())
|
|
|
|
|
: gcodegen.config().temperature.get_at(gcodegen.writer().extruder()->id());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const std::vector<std::string> ColorPrintColors::Colors = { "#C0392B", "#E67E22", "#F1C40F", "#27AE60", "#1ABC9C", "#2980B9", "#9B59B6" };
|
|
|
|
|
|
|
|
|
|
#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id())
|
|
|
|
|
@@ -208,6 +208,7 @@ void GCodeGenerator::PlaceholderParserIntegration::init(const GCodeWriter &write
|
|
|
|
|
this->position.assign(3, 0);
|
|
|
|
|
this->opt_position = new ConfigOptionFloats(this->position);
|
|
|
|
|
this->output_config.set_key_value("position", this->opt_position);
|
|
|
|
|
|
|
|
|
|
// Store zhop variable into the parser itself, it is a read-only variable to the script.
|
|
|
|
|
this->opt_zhop = new ConfigOptionFloat(writer.get_zhop());
|
|
|
|
|
this->parser.set("zhop", this->opt_zhop);
|
|
|
|
|
@@ -230,6 +231,7 @@ void GCodeGenerator::PlaceholderParserIntegration::update_from_gcodewriter(const
|
|
|
|
|
for (const Extruder &e : extruders) {
|
|
|
|
|
this->e_retracted[e.id()] = e.retracted();
|
|
|
|
|
this->e_restart_extra[e.id()] = e.restart_extra();
|
|
|
|
|
|
|
|
|
|
// Wipe tower filament consumption has to be added separately, because that gcode is not generated by GCodeWriter.
|
|
|
|
|
double wt_vol = 0.;
|
|
|
|
|
const std::vector<std::pair<float, std::vector<float>>>& wtuf = wipe_tower_data.used_filament_until_layer;
|
|
|
|
|
@@ -552,8 +554,11 @@ GCodeGenerator::GCodeGenerator(const Print* print) :
|
|
|
|
|
m_brim_done(false),
|
|
|
|
|
m_second_layer_things_done(false),
|
|
|
|
|
m_silent_time_estimator_enabled(false),
|
|
|
|
|
//Y27
|
|
|
|
|
m_resonance_avoidance(true),
|
|
|
|
|
m_print(print)
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
void GCodeGenerator::do_export(Print* print, const char* path, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb)
|
|
|
|
|
{
|
|
|
|
|
CNumericLocalesSetter locales_setter;
|
|
|
|
|
@@ -789,6 +794,7 @@ namespace DoExport {
|
|
|
|
|
print_statistics.total_wipe_tower_filament_weight += has_wipe_tower ? (extruded_volume - extruder.extruded_volume()) * extruder.filament_density() * 0.001 : 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.;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!export_binary_data) {
|
|
|
|
|
filament_stats_string_out += out_filament_used_mm.first;
|
|
|
|
|
filament_stats_string_out += "\n" + out_filament_used_cm3.first;
|
|
|
|
|
@@ -899,6 +905,7 @@ static inline std::optional<std::string> find_M84(const std::string &gcode) {
|
|
|
|
|
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb)
|
|
|
|
|
{
|
|
|
|
|
const bool export_to_binary_gcode = print.full_print_config().option<ConfigOptionBool>("binary_gcode")->value;
|
|
|
|
|
@@ -1002,15 +1009,16 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
|
|
|
|
// Write information on the generator.
|
|
|
|
|
file.write_format("; %s\n\n", Slic3r::header_slic3r_generated().c_str());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (! export_to_binary_gcode) {
|
|
|
|
|
// if exporting gcode in ascii format, generate the thumbnails here
|
|
|
|
|
auto [thumbnails, errors] = GCodeThumbnails::make_and_check_thumbnail_list(print.full_print_config());
|
|
|
|
|
|
|
|
|
|
if (errors != enum_bitmask<ThumbnailError>()) {
|
|
|
|
|
std::string error_str = format("Invalid thumbnails value:");
|
|
|
|
|
error_str += GCodeThumbnails::get_error_string(errors);
|
|
|
|
|
throw Slic3r::ExportError(error_str);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!thumbnails.empty())
|
|
|
|
|
GCodeThumbnails::export_thumbnails_to_file(thumbnail_cb, thumbnails,
|
|
|
|
|
[&file](const char* sz) { file.write(sz); },
|
|
|
|
|
@@ -1053,8 +1061,9 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
|
|
|
|
}
|
|
|
|
|
print.throw_if_canceled();
|
|
|
|
|
}
|
|
|
|
|
//B41
|
|
|
|
|
|
|
|
|
|
// adds tags for time estimators
|
|
|
|
|
//B41
|
|
|
|
|
// if (print.config().remaining_times.value)
|
|
|
|
|
// file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::First_Line_M73_Placeholder).c_str());
|
|
|
|
|
|
|
|
|
|
@@ -1229,6 +1238,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GCode::SmoothPathCache smooth_path_cache_global = smooth_path_interpolate_global(print);
|
|
|
|
|
|
|
|
|
|
// Do all objects for each layer.
|
|
|
|
|
if (print.config().complete_objects.value) {
|
|
|
|
|
size_t finished_objects = 0;
|
|
|
|
|
@@ -1285,6 +1295,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
|
|
|
|
m_second_layer_things_done = false;
|
|
|
|
|
prev_object = &object;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file.write(m_label_objects.maybe_stop_instance());
|
|
|
|
|
} else {
|
|
|
|
|
// Sort layers by Z.
|
|
|
|
|
@@ -1293,10 +1304,12 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
|
|
|
|
// QIDI Multi-Material wipe tower.
|
|
|
|
|
if (has_wipe_tower && ! layers_to_print.empty()) {
|
|
|
|
|
m_wipe_tower = std::make_unique<GCode::WipeTowerIntegration>(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get());
|
|
|
|
|
|
|
|
|
|
// Set position for wipe tower generation.
|
|
|
|
|
Vec3d new_position = this->writer().get_position();
|
|
|
|
|
new_position.z() = first_layer_height;
|
|
|
|
|
this->writer().update_position(new_position);
|
|
|
|
|
|
|
|
|
|
if (print.config().single_extruder_multi_material_priming) {
|
|
|
|
|
file.write(m_wipe_tower->prime(*this));
|
|
|
|
|
// Verify, whether the print overaps the priming extrusions.
|
|
|
|
|
@@ -1414,6 +1427,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
|
|
|
|
file.write_format("; objects_info = %s\n", m_label_objects.all_objects_header_singleline_json().c_str());
|
|
|
|
|
file.write(filament_stats_string_out);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (export_to_binary_gcode) {
|
|
|
|
|
bgcode::binarize::BinaryData& binary_data = m_processor.get_binary_data();
|
|
|
|
|
if (print.m_print_statistics.total_toolchanges > 0)
|
|
|
|
|
@@ -1421,6 +1435,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
|
|
|
|
char buf[1024];
|
|
|
|
|
sprintf(buf, "%.2lf", m_max_layer_z);
|
|
|
|
|
binary_data.printer_metadata.raw_data.emplace_back("max_layer_z", buf);
|
|
|
|
|
|
|
|
|
|
// Now the objects info.
|
|
|
|
|
binary_data.printer_metadata.raw_data.emplace_back("objects_info", m_label_objects.all_objects_header_singleline_json());
|
|
|
|
|
}
|
|
|
|
|
@@ -1436,7 +1451,6 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
|
|
|
|
|
|
|
|
|
//B3
|
|
|
|
|
if (!export_to_binary_gcode) {
|
|
|
|
|
// if exporting gcode in ascii format, generate the thumbnails here
|
|
|
|
|
auto [thumbnails, errors] = GCodeThumbnails::make_and_check_thumbnail_list(print.full_print_config());
|
|
|
|
|
if (errors != enum_bitmask<ThumbnailError>()) {
|
|
|
|
|
std::string error_str = format("Invalid thumbnails value:");
|
|
|
|
|
@@ -1450,6 +1464,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
|
|
|
|
|
|
|
|
|
file.write("\n");
|
|
|
|
|
|
|
|
|
|
// if exporting gcode in ascii format, config export is done here
|
|
|
|
|
// Append full config, delimited by two 'phony' configuration keys qidislicer_config = begin and qidislicer_config = end.
|
|
|
|
|
// The delimiters are structured as configuration key / value pairs to be parsable by older versions of QIDISlicer G-code viewer.
|
|
|
|
|
{
|
|
|
|
|
@@ -1485,6 +1500,7 @@ void GCodeGenerator::smooth_path_interpolate(
|
|
|
|
|
if (const SupportLayer *layer = object_layer_to_print.support_layer; layer)
|
|
|
|
|
out.interpolate_add(layer->support_fills, params);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Process all layers of all objects (non-sequential mode) with a parallel pipeline:
|
|
|
|
|
// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
|
|
|
|
|
// and export G-code into file.
|
|
|
|
|
@@ -1542,7 +1558,6 @@ void GCodeGenerator::process_layers(
|
|
|
|
|
[spiral_vase = this->m_spiral_vase.get(), &layers_to_print](LayerResult in) -> LayerResult {
|
|
|
|
|
if (in.nop_layer_result)
|
|
|
|
|
return in;
|
|
|
|
|
|
|
|
|
|
spiral_vase->enable(in.spiral_vase_enable);
|
|
|
|
|
bool last_layer = in.layer_id == layers_to_print.size() - 1;
|
|
|
|
|
return { spiral_vase->process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush};
|
|
|
|
|
@@ -1575,10 +1590,10 @@ void GCodeGenerator::process_layers(
|
|
|
|
|
tbb::filter<LayerResult, std::string> pipeline_to_string = cooling;
|
|
|
|
|
if (m_find_replace)
|
|
|
|
|
pipeline_to_string = pipeline_to_string & find_replace;
|
|
|
|
|
|
|
|
|
|
// It registers a handler that sets locales to "C" before any TBB thread starts participating in tbb::parallel_pipeline.
|
|
|
|
|
// Handler is unregistered when the destructor is called.
|
|
|
|
|
TBBLocalesSetter locales_setter;
|
|
|
|
|
|
|
|
|
|
// The pipeline elements are joined using const references, thus no copying is performed.
|
|
|
|
|
output_stream.find_replace_supress();
|
|
|
|
|
tbb::parallel_pipeline(12, pipeline_to_layerresult & pipeline_to_string & output);
|
|
|
|
|
@@ -1668,10 +1683,10 @@ void GCodeGenerator::process_layers(
|
|
|
|
|
tbb::filter<LayerResult, std::string> pipeline_to_string = cooling;
|
|
|
|
|
if (m_find_replace)
|
|
|
|
|
pipeline_to_string = pipeline_to_string & find_replace;
|
|
|
|
|
|
|
|
|
|
// It registers a handler that sets locales to "C" before any TBB thread starts participating in tbb::parallel_pipeline.
|
|
|
|
|
// Handler is unregistered when the destructor is called.
|
|
|
|
|
TBBLocalesSetter locales_setter;
|
|
|
|
|
|
|
|
|
|
// The pipeline elements are joined using const references, thus no copying is performed.
|
|
|
|
|
output_stream.find_replace_supress();
|
|
|
|
|
tbb::parallel_pipeline(12, pipeline_to_layerresult & pipeline_to_string & output);
|
|
|
|
|
@@ -1709,6 +1724,7 @@ std::string GCodeGenerator::placeholder_parser_process(
|
|
|
|
|
throw Slic3r::PlaceholderParserError(format("\"%s\" custom G-code needs to be added to s_CustomGcodeSpecificOptions", name.c_str()));
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
PlaceholderParserIntegration &ppi = m_placeholder_parser_integration;
|
|
|
|
|
try {
|
|
|
|
|
ppi.update_from_gcodewriter(m_writer, m_print->wipe_tower_data());
|
|
|
|
|
@@ -2016,8 +2032,10 @@ static std::string emit_custom_color_change_gcode_per_print_z(
|
|
|
|
|
// see GH issue #6362
|
|
|
|
|
gcodegen.writer().unretract();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return gcode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static std::string emit_custom_gcode_per_print_z(
|
|
|
|
|
GCodeGenerator &gcodegen,
|
|
|
|
|
const CustomGCode::Item &custom_gcode,
|
|
|
|
|
@@ -2043,11 +2061,13 @@ static std::string emit_custom_color_change_gcode_per_print_z(
|
|
|
|
|
} else {
|
|
|
|
|
if (gcode_type == CustomGCode::PausePrint) { // Pause print
|
|
|
|
|
const std::string pause_print_msg = custom_gcode.extra;
|
|
|
|
|
|
|
|
|
|
// add tag for processor
|
|
|
|
|
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Pause_Print) + "\n";
|
|
|
|
|
//! FIXME_in_fw show message during print pause
|
|
|
|
|
if (!pause_print_msg.empty())
|
|
|
|
|
gcode += "M117 " + pause_print_msg + "\n";
|
|
|
|
|
|
|
|
|
|
DynamicConfig cfg;
|
|
|
|
|
cfg.set_key_value("color_change_extruder", new ConfigOptionInt(int(current_extruder_id)));
|
|
|
|
|
gcode += gcodegen.placeholder_parser_process("pause_print_gcode", config.pause_print_gcode, current_extruder_id, &cfg);
|
|
|
|
|
@@ -2058,7 +2078,6 @@ static std::string emit_custom_color_change_gcode_per_print_z(
|
|
|
|
|
gcode += gcodegen.placeholder_parser_process("template_custom_gcode", config.template_custom_gcode, current_extruder_id);
|
|
|
|
|
else // custom Gcode
|
|
|
|
|
gcode += custom_gcode.extra;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
gcode += "\n";
|
|
|
|
|
}
|
|
|
|
|
@@ -2146,6 +2165,7 @@ bool GCodeGenerator::line_distancer_is_required(const std::vector<unsigned int>&
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Polyline GCodeGenerator::get_layer_change_xy_path(const Vec3d &from, const Vec3d &to) {
|
|
|
|
|
|
|
|
|
|
bool could_be_wipe_disabled{false};
|
|
|
|
|
@@ -2233,6 +2253,7 @@ std::string GCodeGenerator::generate_ramping_layer_change_gcode(
|
|
|
|
|
const GCode::Impl::Travels::ElevatedTravelParams &elevation_params
|
|
|
|
|
) const {
|
|
|
|
|
using namespace GCode::Impl::Travels;
|
|
|
|
|
|
|
|
|
|
const std::vector<double> ensure_points_at_distances = linspace(
|
|
|
|
|
elevation_params.slope_end - elevation_params.blend_width / 2.0,
|
|
|
|
|
elevation_params.slope_end + elevation_params.blend_width / 2.0,
|
|
|
|
|
@@ -2254,6 +2275,7 @@ std::string GCodeGenerator::generate_ramping_layer_change_gcode(
|
|
|
|
|
}
|
|
|
|
|
return travel_gcode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// In sequential mode, process_layer is called once per each object and its copy,
|
|
|
|
|
// therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object.
|
|
|
|
|
// In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated.
|
|
|
|
|
@@ -2330,6 +2352,7 @@ LayerResult GCodeGenerator::process_layer(
|
|
|
|
|
m_enable_loop_clipping = !enable;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::string gcode;
|
|
|
|
|
assert(is_decimal_separator_point()); // for the sprintfs
|
|
|
|
|
|
|
|
|
|
@@ -2365,6 +2388,7 @@ LayerResult GCodeGenerator::process_layer(
|
|
|
|
|
m_layer = &layer;
|
|
|
|
|
if (this->line_distancer_is_required(layer_tools.extruders) && this->m_layer != nullptr && this->m_layer->lower_layer != nullptr)
|
|
|
|
|
m_travel_obstacle_tracker.init_layer(layer, layers);
|
|
|
|
|
|
|
|
|
|
m_object_layer_over_raft = false;
|
|
|
|
|
if (!first_layer && ! print.config().layer_gcode.value.empty()) {
|
|
|
|
|
DynamicConfig config;
|
|
|
|
|
@@ -2452,11 +2476,13 @@ LayerResult GCodeGenerator::process_layer(
|
|
|
|
|
// Now we have picked the right extruder, so we can emit the custom g-code.
|
|
|
|
|
gcode += ProcessLayer::emit_custom_gcode_per_print_z(*this, *layer_tools.custom_gcode, m_writer.extruder()->id(), first_extruder_id, print.config());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) {
|
|
|
|
|
if (!this->m_config.complete_objects.value) {
|
|
|
|
|
gcode += this->m_label_objects.maybe_stop_instance();
|
|
|
|
|
}
|
|
|
|
|
this->m_label_objects.update(nullptr);
|
|
|
|
|
|
|
|
|
|
const std::pair<size_t, size_t> loops = loops_it->second;
|
|
|
|
|
this->set_origin(0., 0.);
|
|
|
|
|
m_avoid_crossing_perimeters.use_external_mp();
|
|
|
|
|
@@ -2478,10 +2504,12 @@ LayerResult GCodeGenerator::process_layer(
|
|
|
|
|
|
|
|
|
|
// Extrude brim with the extruder of the 1st region.
|
|
|
|
|
if (! m_brim_done) {
|
|
|
|
|
|
|
|
|
|
if (!this->m_config.complete_objects.value) {
|
|
|
|
|
gcode += this->m_label_objects.maybe_stop_instance();
|
|
|
|
|
}
|
|
|
|
|
this->m_label_objects.update(nullptr);
|
|
|
|
|
|
|
|
|
|
this->set_origin(0., 0.);
|
|
|
|
|
m_avoid_crossing_perimeters.use_external_mp();
|
|
|
|
|
for (const ExtrusionEntity *ee : print.brim().entities)
|
|
|
|
|
@@ -2491,7 +2519,6 @@ LayerResult GCodeGenerator::process_layer(
|
|
|
|
|
// Allow a straight travel move to the first object point.
|
|
|
|
|
m_avoid_crossing_perimeters.disable_once();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this->m_label_objects.update(first_instance);
|
|
|
|
|
|
|
|
|
|
// We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature):
|
|
|
|
|
@@ -2515,6 +2542,7 @@ LayerResult GCodeGenerator::process_layer(
|
|
|
|
|
is_anything_overridden, false /* print_wipe_extrusions */);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// During layer change the starting position of next layer is now known.
|
|
|
|
|
// The solution is thus to emplace a temporary tag to the gcode, cache the postion and
|
|
|
|
|
// replace the tag later. The tag is Layer_Change_Travel, the cached position is
|
|
|
|
|
@@ -2582,6 +2610,7 @@ LayerResult GCodeGenerator::process_layer(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
boost::algorithm::replace_first(gcode, tag, layer_change_gcode);
|
|
|
|
|
|
|
|
|
|
BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
|
|
|
|
|
log_memory_info();
|
|
|
|
|
|
|
|
|
|
@@ -2640,7 +2669,6 @@ void GCodeGenerator::process_layer_single_object(
|
|
|
|
|
const PrintObject &print_object = print_instance.print_object;
|
|
|
|
|
const Print &print = *print_object.print();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (! print_wipe_extrusions && layer_to_print.support_layer != nullptr)
|
|
|
|
|
if (const SupportLayer &support_layer = *layer_to_print.support_layer; ! support_layer.support_fills.entities.empty()) {
|
|
|
|
|
ExtrusionRole role = support_layer.support_fills.role();
|
|
|
|
|
@@ -2814,11 +2842,8 @@ void GCodeGenerator::process_layer_single_object(
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void GCodeGenerator::apply_print_config(const PrintConfig &print_config)
|
|
|
|
|
{
|
|
|
|
|
m_writer.apply_print_config(print_config);
|
|
|
|
|
@@ -2862,7 +2887,6 @@ void GCodeGenerator::encode_full_config(const Print& print, std::vector<std::pai
|
|
|
|
|
void GCodeGenerator::set_extruders(const std::vector<unsigned int> &extruder_ids)
|
|
|
|
|
{
|
|
|
|
|
m_writer.set_extruders(extruder_ids);
|
|
|
|
|
|
|
|
|
|
m_wipe.init(this->config(), extruder_ids);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -2872,6 +2896,7 @@ void GCodeGenerator::set_origin(const Vec2d &pointf)
|
|
|
|
|
const auto offset = Point::new_scale(m_origin - pointf);
|
|
|
|
|
if (last_position.has_value())
|
|
|
|
|
*(this->last_position) += offset;
|
|
|
|
|
|
|
|
|
|
m_wipe.offset_path(offset);
|
|
|
|
|
m_origin = pointf;
|
|
|
|
|
}
|
|
|
|
|
@@ -2889,7 +2914,6 @@ std::string GCodeGenerator::preamble()
|
|
|
|
|
return gcode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// called by GCodeGenerator::process_layer()
|
|
|
|
|
std::string GCodeGenerator::change_layer(
|
|
|
|
|
coordf_t previous_layer_z,
|
|
|
|
|
@@ -2904,6 +2928,7 @@ std::string GCodeGenerator::change_layer(
|
|
|
|
|
if (m_writer.multiple_extruders) {
|
|
|
|
|
gcode += m_label_objects.maybe_change_instance(m_writer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!EXTRUDER_CONFIG(travel_ramping_lift) && EXTRUDER_CONFIG(retract_layer_change)) {
|
|
|
|
|
gcode += this->retract_and_wipe();
|
|
|
|
|
} else if (EXTRUDER_CONFIG(travel_ramping_lift) && !vase_mode){
|
|
|
|
|
@@ -2931,6 +2956,7 @@ std::string GCodeGenerator::change_layer(
|
|
|
|
|
|
|
|
|
|
// forget last wiping path as wiping after raising Z is pointless
|
|
|
|
|
m_wipe.reset_path();
|
|
|
|
|
|
|
|
|
|
return gcode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -2945,10 +2971,11 @@ static inline bool validate_smooth_path(const GCode::SmoothPath &smooth_path, bo
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
#endif //NDEBUG
|
|
|
|
|
|
|
|
|
|
static constexpr const double min_gcode_segment_length = 0.002;
|
|
|
|
|
|
|
|
|
|
std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
// Extrude all loops CCW.
|
|
|
|
|
//w38
|
|
|
|
|
//bool is_hole = loop_src.is_clockwise();
|
|
|
|
|
@@ -2966,7 +2993,6 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC
|
|
|
|
|
//w38
|
|
|
|
|
seam_point = m_seam_placer.place_seam(m_layer, new_loop_src, m_config.external_perimeters_first, seam_point);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Because the G-code export has 1um resolution, don't generate segments shorter than 1.5 microns,
|
|
|
|
|
// thus empty path segments will not be produced by G-code export.
|
|
|
|
|
//w38
|
|
|
|
|
@@ -2982,32 +3008,38 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC
|
|
|
|
|
|
|
|
|
|
if (smooth_path.empty())
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
assert(validate_smooth_path(smooth_path, ! m_enable_loop_clipping));
|
|
|
|
|
|
|
|
|
|
// Apply the small perimeter speed.
|
|
|
|
|
//w38
|
|
|
|
|
if (new_loop_src.paths.front().role().is_perimeter() && new_loop_src.length() <= SMALL_PERIMETER_LENGTH && speed == -1)
|
|
|
|
|
//w38//Y27
|
|
|
|
|
bool is_small_perimeter_length = false;
|
|
|
|
|
if (new_loop_src.paths.front().role().is_perimeter() && new_loop_src.length() <= SMALL_PERIMETER_LENGTH && speed == -1) {
|
|
|
|
|
speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed);
|
|
|
|
|
is_small_perimeter_length = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Extrude along the smooth path.
|
|
|
|
|
std::string gcode;
|
|
|
|
|
for (const GCode::SmoothPathElement &el : smooth_path)
|
|
|
|
|
//Y27
|
|
|
|
|
for (const GCode::SmoothPathElement &el : smooth_path) {
|
|
|
|
|
m_resonance_avoidance = !is_small_perimeter_length;
|
|
|
|
|
gcode += this->_extrude(el.path_attributes, el.path, description, speed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// reset acceleration
|
|
|
|
|
gcode += m_writer.set_print_acceleration(fast_round_up<unsigned int>(m_config.default_acceleration.value));
|
|
|
|
|
|
|
|
|
|
if (m_wipe.enabled()) {
|
|
|
|
|
|
|
|
|
|
// Wipe will hide the seam.
|
|
|
|
|
m_wipe.set_path(std::move(smooth_path));
|
|
|
|
|
}//w38
|
|
|
|
|
}
|
|
|
|
|
//w38
|
|
|
|
|
else if (new_loop_src.paths.back().role().is_external_perimeter() && m_layer != nullptr && m_config.perimeters.value > 1) {
|
|
|
|
|
|
|
|
|
|
// Only wipe inside if the wipe along the perimeter is disabled.
|
|
|
|
|
// Make a little move inwards before leaving loop.
|
|
|
|
|
if (std::optional<Point> pt = wipe_hide_seam(smooth_path, is_hole, scale_(EXTRUDER_CONFIG(nozzle_diameter))); pt) {
|
|
|
|
|
|
|
|
|
|
// Generate the seam hiding travel move.
|
|
|
|
|
gcode += m_writer.travel_to_xy(this->point_to_gcode(*pt), "move inwards before travel");
|
|
|
|
|
this->last_position = *pt;
|
|
|
|
|
@@ -3016,6 +3048,7 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC
|
|
|
|
|
|
|
|
|
|
return gcode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string GCodeGenerator::extrude_skirt(
|
|
|
|
|
const ExtrusionLoop &loop_src, const ExtrusionFlow &extrusion_flow_override,
|
|
|
|
|
const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed)
|
|
|
|
|
@@ -3024,13 +3057,16 @@ std::string GCodeGenerator::extrude_skirt(
|
|
|
|
|
Point seam_point = this->last_position.has_value() ? *this->last_position : Point::Zero();
|
|
|
|
|
GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit_split_with_seam(
|
|
|
|
|
loop_src, false, m_scaled_resolution, seam_point, scaled<double>(0.0015));
|
|
|
|
|
|
|
|
|
|
// Clip the path to avoid the extruder to get exactly on the first point of the loop;
|
|
|
|
|
// if polyline was shorter than the clipping distance we'd get a null polyline, so
|
|
|
|
|
// we discard it in that case.
|
|
|
|
|
if (m_enable_loop_clipping)
|
|
|
|
|
clip_end(smooth_path, scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER, scaled<double>(min_gcode_segment_length));
|
|
|
|
|
|
|
|
|
|
if (smooth_path.empty())
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
assert(validate_smooth_path(smooth_path, ! m_enable_loop_clipping));
|
|
|
|
|
|
|
|
|
|
// Extrude along the smooth path.
|
|
|
|
|
@@ -3041,8 +3077,10 @@ std::string GCodeGenerator::extrude_skirt(
|
|
|
|
|
el.path_attributes.height = extrusion_flow_override.height;
|
|
|
|
|
gcode += this->_extrude(el.path_attributes, el.path, description, speed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// reset acceleration
|
|
|
|
|
gcode += m_writer.set_print_acceleration(fast_round_up<unsigned int>(m_config.default_acceleration.value));
|
|
|
|
|
|
|
|
|
|
if (m_wipe.enabled())
|
|
|
|
|
// Wipe will hide the seam.
|
|
|
|
|
m_wipe.set_path(std::move(smooth_path));
|
|
|
|
|
@@ -3059,13 +3097,15 @@ std::string GCodeGenerator::extrude_multi_path(const ExtrusionMultiPath &multipa
|
|
|
|
|
}
|
|
|
|
|
#endif // NDEBUG
|
|
|
|
|
GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit(multipath, reverse, m_scaled_resolution);
|
|
|
|
|
// extrude along the path
|
|
|
|
|
|
|
|
|
|
// extrude along the path
|
|
|
|
|
std::string gcode;
|
|
|
|
|
for (GCode::SmoothPathElement &el : smooth_path)
|
|
|
|
|
gcode += this->_extrude(el.path_attributes, el.path, description, speed);
|
|
|
|
|
|
|
|
|
|
GCode::reverse(smooth_path);
|
|
|
|
|
m_wipe.set_path(std::move(smooth_path));
|
|
|
|
|
|
|
|
|
|
// reset acceleration
|
|
|
|
|
gcode += m_writer.set_print_acceleration((unsigned int)floor(m_config.default_acceleration.value + 0.5));
|
|
|
|
|
return gcode;
|
|
|
|
|
@@ -3214,6 +3254,7 @@ std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const
|
|
|
|
|
this->m_layer_change_used_external_mp = this->m_avoid_crossing_perimeters.use_external_mp_once;
|
|
|
|
|
this->m_layer_change_layer = this->layer();
|
|
|
|
|
this->m_layer_change_origin = this->origin();
|
|
|
|
|
|
|
|
|
|
double lift{
|
|
|
|
|
EXTRUDER_CONFIG(travel_ramping_lift) ? EXTRUDER_CONFIG(travel_max_lift) :
|
|
|
|
|
EXTRUDER_CONFIG(retract_lift)};
|
|
|
|
|
@@ -3230,6 +3271,7 @@ std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const
|
|
|
|
|
gcode += this->writer().get_travel_to_z_gcode(from_z + lift, "lift");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::string comment{"move to first layer point"};
|
|
|
|
|
|
|
|
|
|
gcode += insert_gcode();
|
|
|
|
|
@@ -3240,7 +3282,9 @@ std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const
|
|
|
|
|
this->last_position = point.head<2>();
|
|
|
|
|
this->writer().update_position(gcode_point);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_current_layer_first_position = gcode_point;
|
|
|
|
|
|
|
|
|
|
return gcode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -3257,6 +3301,7 @@ double cap_speed(
|
|
|
|
|
}
|
|
|
|
|
return speed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string GCodeGenerator::_extrude(
|
|
|
|
|
const ExtrusionAttributes &path_attr,
|
|
|
|
|
const Geometry::ArcWelder::Path &path,
|
|
|
|
|
@@ -3272,6 +3317,7 @@ std::string GCodeGenerator::_extrude(
|
|
|
|
|
if (m_writer.multiple_extruders && has_active_instance) {
|
|
|
|
|
gcode += m_label_objects.maybe_change_instance(m_writer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!m_current_layer_first_position) {
|
|
|
|
|
const Vec3crd point = to_3d(path.front().point, scaled(this->m_last_layer_z));
|
|
|
|
|
gcode += this->travel_to_first_position(point, unscaled(point.z()), path_attr.role, [&](){
|
|
|
|
|
@@ -3307,6 +3353,7 @@ std::string GCodeGenerator::_extrude(
|
|
|
|
|
} else {
|
|
|
|
|
this->m_already_unretracted = true;
|
|
|
|
|
gcode += "FIRST_UNRETRACT" + this->unretract();
|
|
|
|
|
|
|
|
|
|
//First unretract may or may not be removed thus we must start from E0.
|
|
|
|
|
gcode += this->writer().reset_e();
|
|
|
|
|
}
|
|
|
|
|
@@ -3409,7 +3456,6 @@ std::string GCodeGenerator::_extrude(
|
|
|
|
|
|
|
|
|
|
std::pair<float, float> dynamic_speed_and_fan_speed{-1, -1};
|
|
|
|
|
if (path_attr.overhang_attributes.has_value()) {
|
|
|
|
|
|
|
|
|
|
double external_perim_reference_speed = m_config.get_abs_value("external_perimeter_speed");
|
|
|
|
|
if (external_perim_reference_speed == 0)
|
|
|
|
|
external_perim_reference_speed = m_volumetric_speed / path_attr.mm3_per_mm;
|
|
|
|
|
@@ -3421,22 +3467,33 @@ std::string GCodeGenerator::_extrude(
|
|
|
|
|
external_perim_reference_speed, speed);
|
|
|
|
|
}
|
|
|
|
|
//Y27
|
|
|
|
|
bool dont_speed_down_by_overhang = true;
|
|
|
|
|
if (path_attr.role == ExtrusionRole::ExternalPerimeter && m_config.opt_bool("resonance_avoidance")) {
|
|
|
|
|
if (dynamic_speed_and_fan_speed.first > -1) {
|
|
|
|
|
if (speed != dynamic_speed_and_fan_speed.first) {
|
|
|
|
|
dont_speed_down_by_overhang = false;
|
|
|
|
|
speed = dynamic_speed_and_fan_speed.first;
|
|
|
|
|
double min_speed = cap_speed(speed, path_attr.mm3_per_mm, m_config, m_writer.extruder()->id());
|
|
|
|
|
if (min_speed > m_config.opt_float("max_resonance_avoidance_speed")) {
|
|
|
|
|
m_resonance_avoidance = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
speed = dynamic_speed_and_fan_speed.first;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// cap speed with max_volumetric_speed anyway (even if user is not using autospeed)
|
|
|
|
|
speed = cap_speed(speed, path_attr.mm3_per_mm, m_config, m_writer.extruder()->id());
|
|
|
|
|
//Y27
|
|
|
|
|
if (path_attr.role == ExtrusionRole::ExternalPerimeter && m_config.opt_bool("resonance_avoidance" ) && dont_speed_down_by_overhang) {
|
|
|
|
|
|
|
|
|
|
if (m_resonance_avoidance) {
|
|
|
|
|
if (speed <= m_config.opt_float("max_resonance_avoidance_speed")) {
|
|
|
|
|
speed = std::min(speed, m_config.opt_float("min_resonance_avoidance_speed"));
|
|
|
|
|
}
|
|
|
|
|
m_resonance_avoidance = true;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (dynamic_speed_and_fan_speed.first > -1) {
|
|
|
|
|
speed = dynamic_speed_and_fan_speed.first;
|
|
|
|
|
}
|
|
|
|
|
speed = cap_speed(speed, path_attr.mm3_per_mm, m_config, m_writer.extruder()->id());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double F = speed * 60; // convert mm/sec to mm/min
|
|
|
|
|
|
|
|
|
|
// extrude arc or line
|
|
|
|
|
@@ -3502,6 +3559,7 @@ std::string GCodeGenerator::_extrude(
|
|
|
|
|
gcode += m_writer.set_speed(F, "", cooling_marker_setspeed_comments);
|
|
|
|
|
if (dynamic_speed_and_fan_speed.second >= 0)
|
|
|
|
|
gcode += ";_SET_FAN_SPEED" + std::to_string(int(dynamic_speed_and_fan_speed.second)) + "\n";
|
|
|
|
|
|
|
|
|
|
std::string comment;
|
|
|
|
|
if (m_config.gcode_comments) {
|
|
|
|
|
comment = description;
|
|
|
|
|
@@ -3550,7 +3608,6 @@ std::string GCodeGenerator::_extrude(
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (m_enable_cooling_markers)
|
|
|
|
|
gcode += path_attr.role.is_bridge() ? ";_BRIDGE_FAN_END\n" : ";_EXTRUDE_END\n";
|
|
|
|
|
|
|
|
|
|
@@ -3561,13 +3618,13 @@ std::string GCodeGenerator::_extrude(
|
|
|
|
|
return gcode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::string GCodeGenerator::generate_travel_gcode(
|
|
|
|
|
const Points3& travel,
|
|
|
|
|
const std::string& comment,
|
|
|
|
|
const std::function<std::string()>& insert_gcode
|
|
|
|
|
) {
|
|
|
|
|
std::string gcode;
|
|
|
|
|
|
|
|
|
|
const unsigned acceleration =(unsigned)(m_config.travel_acceleration.value + 0.5);
|
|
|
|
|
|
|
|
|
|
if (travel.empty()) {
|
|
|
|
|
@@ -3576,7 +3633,6 @@ std::string GCodeGenerator::generate_travel_gcode(
|
|
|
|
|
|
|
|
|
|
// generate G-code for the travel move
|
|
|
|
|
// use G1 because we rely on paths being straight (G0 may make round paths)
|
|
|
|
|
|
|
|
|
|
gcode += this->m_writer.set_travel_acceleration(acceleration);
|
|
|
|
|
|
|
|
|
|
Vec3d previous_point{this->point_to_gcode(travel.front())};
|
|
|
|
|
@@ -3589,6 +3645,7 @@ std::string GCodeGenerator::generate_travel_gcode(
|
|
|
|
|
gcode += insert_gcode();
|
|
|
|
|
already_inserted = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gcode += this->m_writer.travel_to_xyz(previous_point, gcode_point, comment);
|
|
|
|
|
this->last_position = point.head<2>();
|
|
|
|
|
previous_point = gcode_point;
|
|
|
|
|
@@ -3690,7 +3747,6 @@ std::string GCodeGenerator::travel_to(
|
|
|
|
|
const std::string &comment,
|
|
|
|
|
const std::function<std::string()>& insert_gcode
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
|
|
// check whether a straight travel move would need retraction
|
|
|
|
|
|
|
|
|
|
bool could_be_wipe_disabled {false};
|
|
|
|
|
@@ -3735,6 +3791,7 @@ std::string GCodeGenerator::travel_to(
|
|
|
|
|
(upper_limit > 0 && initial_elevation > upper_limit)) {
|
|
|
|
|
can_be_flat = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Points3 travel = (
|
|
|
|
|
can_be_flat ?
|
|
|
|
|
GCode::Impl::Travels::generate_flat_travel(xy_path.points, initial_elevation) :
|
|
|
|
|
@@ -3807,6 +3864,7 @@ std::string GCodeGenerator::set_extruder(unsigned int extruder_id, double print_
|
|
|
|
|
if (!this->m_config.complete_objects.value) {
|
|
|
|
|
gcode += this->m_label_objects.maybe_stop_instance();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// prepend retraction on the current extruder
|
|
|
|
|
gcode += this->retract_and_wipe(true);
|
|
|
|
|
|
|
|
|
|
@@ -3976,7 +4034,6 @@ Point GCodeGenerator::gcode_to_point(const Vec2d &point) const
|
|
|
|
|
// This function may be called at the very start from toolchange G-code when the extruder is not assigned yet.
|
|
|
|
|
pt += m_config.extruder_offset.get_at(extruder->id());
|
|
|
|
|
return scaled<coord_t>(pt);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace Slic3r
|
|
|
|
|
|