diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 7fb70e7..60c0f9a 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -215,6 +215,7 @@ set(SLIC3R_SOURCES KDTreeIndirect.hpp Layer.cpp Layer.hpp + LayerRegion.hpp LayerRegion.cpp libslic3r.h "${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h" diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 0e1f2b1..b153578 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -20,6 +20,9 @@ //w21 #include "../ShortestPath.hpp" //w11 + +#include "LayerRegion.hpp" + #define NARROW_INFILL_AREA_THRESHOLD 3 #define NARROW_INFILL_AREA_THRESHOLD_MIN 0.5 namespace Slic3r { @@ -228,14 +231,14 @@ std::vector group_fills(const Layer &layer) fill.expolygons.emplace_back(std::move(fill.surface.expolygon)); //w21 fill.region_id_group.push_back(region_id); - fill.no_overlap_expolygons = layerm.fill_no_overlap_expolygons; + //fill.no_overlap_expolygons = layerm.fill_no_overlap_expolygons; } else { //w21 fill.expolygons.emplace_back(surface.expolygon); auto t = find(fill.region_id_group.begin(), fill.region_id_group.end(), region_id); if (t == fill.region_id_group.end()) { fill.region_id_group.push_back(region_id); - fill.no_overlap_expolygons = union_ex(fill.no_overlap_expolygons, layerm.fill_no_overlap_expolygons); + //fill.no_overlap_expolygons = union_ex(fill.no_overlap_expolygons, layerm.fill_no_overlap_expolygons); } } } diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 61dc5a7..ecfc9d6 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -86,6 +86,11 @@ const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/QIDI_Slicer_custom_gcode_per_print_z.xml"; const std::string CUT_INFORMATION_FILE = "Metadata/QIDI_Slicer_cut_information.xml"; +static constexpr const char *RELATIONSHIP_TAG = "Relationship"; + +static constexpr const char* TARGET_ATTR = "Target"; +static constexpr const char* RELS_TYPE_ATTR = "Type"; + static constexpr const char* MODEL_TAG = "model"; static constexpr const char* RESOURCES_TAG = "resources"; static constexpr const char* OBJECT_TAG = "object"; @@ -113,6 +118,7 @@ static constexpr const char* Z_ATTR = "z"; static constexpr const char* V1_ATTR = "v1"; static constexpr const char* V2_ATTR = "v2"; static constexpr const char* V3_ATTR = "v3"; +static constexpr const char* PPATH_ATTR = "p:path"; static constexpr const char* OBJECTID_ATTR = "objectid"; static constexpr const char* TRANSFORM_ATTR = "transform"; static constexpr const char* PRINTABLE_ATTR = "printable"; @@ -329,18 +335,20 @@ namespace Slic3r { class _3MF_Importer : public _3MF_Base { + typedef std::pair PathId; struct Component { - int object_id; + PathId object_id; + std::string path; Transform3d transform; - explicit Component(int object_id) + explicit Component(PathId object_id) : object_id(object_id) , transform(Transform3d::Identity()) { } - Component(int object_id, const Transform3d& transform) + Component(PathId object_id, const Transform3d &transform) : object_id(object_id) , transform(transform) { @@ -458,11 +466,11 @@ namespace Slic3r { }; // Map from a 1 based 3MF object ID to a 0 based ModelObject index inside m_model->objects. - typedef std::map IdToModelObjectMap; - typedef std::map IdToAliasesMap; + typedef std::map IdToModelObjectMap; + typedef std::map IdToAliasesMap; typedef std::vector InstancesList; typedef std::map IdToMetadataMap; - typedef std::map IdToGeometryMap; + typedef std::map IdToGeometryMap; typedef std::map> IdToLayerHeightsProfileMap; typedef std::map IdToLayerConfigRangesMap; typedef std::map IdToCutObjectInfoMap; @@ -503,6 +511,8 @@ namespace Slic3r { std::string m_curr_metadata_name; std::string m_curr_characters; std::string m_name; + std::string m_start_part_path; + std::string m_model_path; public: _3MF_Importer(); @@ -526,8 +536,9 @@ namespace Slic3r { } bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions); + bool _extract_relationships_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat); + bool _extract_model_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat); bool _is_svg_shape_file(const std::string &filename) const; - bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_cut_information_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions); void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions); @@ -540,6 +551,9 @@ namespace Slic3r { bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); void _extract_embossed_svg_shape_file(const std::string &filename, mz_zip_archive &archive, const mz_zip_archive_file_stat &stat); + // handlers to parse the .rels file + void _handle_start_relationships_element(const char* name, const char** attributes); + bool _handle_start_relationship(const char **attributes, unsigned int num_attributes); // handlers to parse the .model file void _handle_start_model_xml_element(const char* name, const char** attributes); void _handle_end_model_xml_element(const char* name); @@ -591,7 +605,7 @@ namespace Slic3r { bool _handle_start_text_configuration(const char** attributes, unsigned int num_attributes); bool _handle_start_shape_configuration(const char **attributes, unsigned int num_attributes); - bool _create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter); + bool _create_object_instance(PathId object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter); void _apply_transform(ModelInstance& instance, const Transform3d& transform); @@ -611,6 +625,8 @@ namespace Slic3r { bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions); + // callbacks to parse the .rels file + static void XMLCALL _handle_start_relationships_element(void *userData, const char *name, const char **attributes); // callbacks to parse the .model file static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes); static void XMLCALL _handle_end_model_xml_element(void* userData, const char* name); @@ -660,6 +676,7 @@ namespace Slic3r { m_sla_support_points.clear(); m_curr_metadata_name.clear(); m_curr_characters.clear(); + m_start_part_path = MODEL_FILE; // set default value for invalid .rel file clear_errors(); return _load_model_from_file(filename, model, config, config_substitutions); @@ -699,23 +716,28 @@ namespace Slic3r { m_name = boost::filesystem::path(filename).stem().string(); - // we first loop the entries to read from the archive the .model file only, in order to extract the version from it + int index = mz_zip_reader_locate_file(&archive, RELATIONSHIPS_FILE.c_str(), nullptr, 0); + if (index < 0 || !mz_zip_reader_file_stat(&archive, index, &stat)) + return false; + + mz_zip_archive_file_stat start_part_stat{std::numeric_limits::max()}; + m_model_path = MODEL_FILE; + _extract_relationships_from_archive(archive, stat); bool found_model = false; + // we first loop the entries to read from the .model files which are not root for (mz_uint i = 0; i < num_entries; ++i) { if (mz_zip_reader_file_stat(&archive, i, &stat)) { std::string name(stat.m_filename); std::replace(name.begin(), name.end(), '\\', '/'); if (boost::algorithm::iends_with(name, MODEL_EXTENSION)) { - if(found_model){ - close_zip_reader(&archive); - add_error("3mf contain multiple .model files and it is not supported yet."); - return false; + // valid model name -> extract model + m_model_path = "/" + name; + if (m_model_path == m_start_part_path) { + start_part_stat = stat; + continue; } - found_model = true; - try - { - // valid model name -> extract model + try { if (!_extract_model_from_archive(archive, stat)) { close_zip_reader(&archive); add_error("Archive does not contain a valid model"); @@ -728,9 +750,26 @@ namespace Slic3r { close_zip_reader(&archive); throw Slic3r::FileIOError(e.what()); } + found_model = true; } } } + // Read root model file + if (start_part_stat.m_file_index < num_entries) { + try { + m_model_path.clear(); + if (!_extract_model_from_archive(archive, start_part_stat)) { + close_zip_reader(&archive); + add_error("Archive does not contain a valid model"); + return false; + } + } catch (const std::exception &e) { + // ensure the zip archive is closed and rethrow the exception + close_zip_reader(&archive); + throw Slic3r::FileIOError(e.what()); + } + found_model = true; + } if (!found_model) { close_zip_reader(&archive); add_error("Not valid 3mf. There is missing .model file."); @@ -869,7 +908,7 @@ namespace Slic3r { ObjectMetadata::VolumeMetadataList volumes; ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr; - IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first); + IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first.second); if (obj_metadata != m_objects_metadata.end()) { // config data has been found, this model was saved using slic3r pe @@ -950,8 +989,55 @@ namespace Slic3r { } } -// // fixes the min z of the model if negative -// model.adjust_min_z(); + // We support our 3mf contains only configuration without mesh, + // others MUST contain mesh (triangles and vertices). + if (!m_qidislicer_generator_version.has_value() && model.objects.empty()) { + const std::string msg = (boost::format(_u8L("The 3MF file does not contain a valid mesh.\n\n\"%1%\"")) % filename).str(); + throw Slic3r::RuntimeError(msg); + } + + return true; + } + + bool _3MF_Importer::_extract_relationships_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat) + { + if (stat.m_uncomp_size == 0 || + stat.m_uncomp_size > 10000000 // Prevent overloading by big Relations file(>10MB). there is no reason to be soo big + ) { + add_error("Found invalid size"); + return false; + } + + _destroy_xml_parser(); + + m_xml_parser = XML_ParserCreate(nullptr); + if (m_xml_parser == nullptr) { + add_error("Unable to create parser"); + return false; + } + + XML_SetUserData(m_xml_parser, (void *) this); + XML_SetStartElementHandler(m_xml_parser, _handle_start_relationships_element); + + void *parser_buffer = XML_GetBuffer(m_xml_parser, (int) stat.m_uncomp_size); + if (parser_buffer == nullptr) { + add_error("Unable to create buffer"); + return false; + } + + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, (size_t) stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading config data to buffer"); + return false; + } + + if (!XML_ParseBuffer(m_xml_parser, (int) stat.m_uncomp_size, 1)) { + char error_buf[1024]; + ::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), + (int) XML_GetCurrentLineNumber(m_xml_parser)); + add_error(error_buf); + return false; + } return true; } @@ -1515,6 +1601,39 @@ namespace Slic3r { } } + void XMLCALL _3MF_Importer::_handle_start_relationships_element(void *userData, const char *name, const char **attributes) + { + _3MF_Importer *importer = (_3MF_Importer *) userData; + if (importer != nullptr) + importer->_handle_start_relationships_element(name, attributes); + } + + void _3MF_Importer::_handle_start_relationships_element(const char *name, const char **attributes) + { + if (m_xml_parser == nullptr) + return; + + bool res = true; + unsigned int num_attributes = (unsigned int) XML_GetSpecifiedAttributeCount(m_xml_parser); + + if (::strcmp(RELATIONSHIP_TAG, name) == 0) + res = _handle_start_relationship(attributes, num_attributes); + + m_curr_characters.clear(); + if (!res) + _stop_xml_parser(); + } + + bool _3MF_Importer::_handle_start_relationship(const char **attributes, unsigned int num_attributes) + { + std::string type = get_attribute_value_string(attributes, num_attributes, RELS_TYPE_ATTR); + // only exactly that string type mean root model file + if (type == "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel") { + std::string path = get_attribute_value_string(attributes, num_attributes, TARGET_ATTR); + m_start_part_path = path; + } + return true; + } void _3MF_Importer::_handle_start_model_xml_element(const char* name, const char** attributes) { if (m_xml_parser == nullptr) @@ -1654,6 +1773,8 @@ namespace Slic3r { bool _3MF_Importer::_handle_end_model() { + if (!m_model_path.empty()) + return true; // deletes all non-built or non-instanced objects for (const IdToModelObjectMap::value_type& object : m_objects) { if (object.second >= int(m_model->objects.size())) { @@ -1722,6 +1843,7 @@ namespace Slic3r { bool _3MF_Importer::_handle_end_object() { if (m_curr_object.object != nullptr) { + PathId object_id{m_model_path, m_curr_object.id}; if (m_curr_object.geometry.empty()) { // no geometry defined // remove the object from the model @@ -1729,26 +1851,26 @@ namespace Slic3r { if (m_curr_object.components.empty()) { // no components defined -> invalid object, delete it - IdToModelObjectMap::iterator object_item = m_objects.find(m_curr_object.id); + IdToModelObjectMap::iterator object_item = m_objects.find(object_id); if (object_item != m_objects.end()) m_objects.erase(object_item); - IdToAliasesMap::iterator alias_item = m_objects_aliases.find(m_curr_object.id); + IdToAliasesMap::iterator alias_item = m_objects_aliases.find(object_id); if (alias_item != m_objects_aliases.end()) m_objects_aliases.erase(alias_item); } else // adds components to aliases - m_objects_aliases.insert({ m_curr_object.id, m_curr_object.components }); + m_objects_aliases.insert({ object_id, m_curr_object.components }); } else { // geometry defined, store it for later use - m_geometries.insert({ m_curr_object.id, std::move(m_curr_object.geometry) }); + m_geometries.insert({ object_id, std::move(m_curr_object.geometry) }); // stores the object for later use - if (m_objects.find(m_curr_object.id) == m_objects.end()) { - m_objects.insert({ m_curr_object.id, m_curr_object.model_object_idx }); - m_objects_aliases.insert({ m_curr_object.id, { 1, Component(m_curr_object.id) } }); // aliases itself + if (m_objects.find(object_id) == m_objects.end()) { + m_objects.insert({ object_id, m_curr_object.model_object_idx }); + m_objects_aliases.insert({object_id, {1, Component(object_id)}}); // aliases itself } else { add_error("Found object with duplicate id"); @@ -1859,19 +1981,22 @@ namespace Slic3r { bool _3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes) { + std::string path = get_attribute_value_string(attributes, num_attributes, PPATH_ATTR); + if (path.empty()) path = m_model_path; int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); - IdToModelObjectMap::iterator object_item = m_objects.find(object_id); + PathId path_id { path, object_id }; + IdToModelObjectMap::iterator object_item = m_objects.find(path_id); if (object_item == m_objects.end()) { - IdToAliasesMap::iterator alias_item = m_objects_aliases.find(object_id); + IdToAliasesMap::iterator alias_item = m_objects_aliases.find(path_id); if (alias_item == m_objects_aliases.end()) { add_error("Found component with invalid object id"); return false; } } - m_curr_object.components.emplace_back(object_id, transform); + m_curr_object.components.emplace_back(path_id, transform); return true; } @@ -1905,9 +2030,11 @@ namespace Slic3r { int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); + std::string path = get_attribute_value_string(attributes, num_attributes, PPATH_ATTR); + if (path.empty()) path = m_model_path; int printable = get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR); - return _create_object_instance(object_id, transform, printable, 1); + return _create_object_instance({path, object_id}, transform, printable, 1); } bool _3MF_Importer::_handle_end_item() @@ -2063,7 +2190,7 @@ namespace Slic3r { return true; } - bool _3MF_Importer::_create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter) + bool _3MF_Importer::_create_object_instance(PathId object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter) { static const unsigned int MAX_RECURSIONS = 10; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 8acce2b..a2dc56a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -552,7 +552,6 @@ GCodeGenerator::GCodeGenerator(const Print* print) : m_brim_done(false), m_second_layer_things_done(false), m_silent_time_estimator_enabled(false), - m_current_instance({nullptr, -1}), m_print(print) {} void GCodeGenerator::do_export(Print* print, const char* path, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb) @@ -876,6 +875,30 @@ static inline GCode::SmoothPathCache smooth_path_interpolate_global(const Print& return out; } +static inline bool is_mk2_or_mk3(const std::string &printer_model) { + if (boost::starts_with(printer_model, "MK2")) { + return true; + } else if (boost::starts_with(printer_model, "MK3") && (printer_model.size() <= 3 || printer_model[3] != '.')) { + // Ignore MK3.5 and MK3.9. + return true; + } + + return false; +} + +static inline std::optional find_M84(const std::string &gcode) { + std::istringstream gcode_is(gcode); + std::string gcode_line; + while (std::getline(gcode_is, gcode_line)) { + boost::trim(gcode_line); + + if (gcode_line == "M84" || boost::starts_with(gcode_line, "M84 ") || boost::starts_with(gcode_line, "M84;")) { + return gcode_line; + } + } + + 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("binary_gcode")->value; @@ -1109,7 +1132,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail this->print_machine_envelope(file, print); // Label all objects so printer knows about them since the start. - m_label_objects.init(print); + m_label_objects.init(print.objects(), print.config().gcode_label_objects, print.config().gcode_flavor); //B41 // file.write(m_label_objects.all_objects_header()); // Update output variables after the extruders were initialized. @@ -1228,9 +1251,12 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail // Move to the origin position for the copy we're going to print. // This happens before Z goes down to layer 0 again, so that no collision happens hopefully. m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer - m_avoid_crossing_perimeters.use_external_mp_once(); + m_avoid_crossing_perimeters.use_external_mp_once = true; file.write(this->retract_and_wipe()); - file.write(this->travel_to(*this->last_position, Point(0, 0), ExtrusionRole::None, "move to origin position for next object")); + file.write(m_label_objects.maybe_stop_instance()); + const double last_z{this->writer().get_position().z()}; + file.write(this->writer().get_travel_to_z_gcode(last_z, "ensure z position")); + file.write(this->travel_to(*this->last_position, Point(0, 0), ExtrusionRole::None, "move to origin position for next object", [](){return "";})); m_enable_cooling_markers = true; // Disable motion planner when traveling to first object point. m_avoid_crossing_perimeters.disable_once(); @@ -1259,6 +1285,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. // All extrusion moves with the same top layer height are extruded uninterrupted. @@ -1313,6 +1340,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail // and export G-code into file. this->process_layers(print, tool_ordering, print_object_instances_ordering, layers_to_print, smooth_path_cache_global, file); + file.write(m_label_objects.maybe_stop_instance()); if (m_wipe_tower) // Purge the extruder, pull out the active filament. file.write(m_wipe_tower->finalize(*this)); @@ -1382,8 +1410,10 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail m_processor.get_binary_data() ); - if (!export_to_binary_gcode) + if (!export_to_binary_gcode) { + 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) @@ -1391,6 +1421,8 @@ 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()); } else { // if exporting gcode in ascii format, statistics export is done here @@ -1426,7 +1458,12 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail append_full_config(*m_print, full_config); if (!full_config.empty()) file.write(full_config); - file.write("; qidislicer_config = end\n"); + file.write("; qidislicer_config = end\n"); + } + + if (std::optional line_M84 = find_M84(print.config().end_gcode); + is_mk2_or_mk3(print.config().printer_model) && line_M84.has_value()) { + file.writeln(*line_M84); } } print.throw_if_canceled(); @@ -1502,12 +1539,13 @@ void GCodeGenerator::process_layers( }); // The pipeline is variable: The vase mode filter is optional. const auto spiral_vase = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [spiral_vase = this->m_spiral_vase.get()](LayerResult in) -> LayerResult { + [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); - return { spiral_vase->process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush}; + 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}; }); const auto pressure_equalizer = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [pressure_equalizer = this->m_pressure_equalizer.get()](LayerResult in) -> LayerResult { @@ -1596,11 +1634,12 @@ void GCodeGenerator::process_layers( }); // The pipeline is variable: The vase mode filter is optional. const auto spiral_vase = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [spiral_vase = this->m_spiral_vase.get()](LayerResult in)->LayerResult { + [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); - return { spiral_vase->process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush }; + 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 }; }); const auto pressure_equalizer = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [pressure_equalizer = this->m_pressure_equalizer.get()](LayerResult in) -> LayerResult { @@ -2107,18 +2146,61 @@ bool GCodeGenerator::line_distancer_is_required(const std::vector& } return false; } -std::string GCodeGenerator::get_layer_change_gcode(const Vec3d& from, const Vec3d& to, const unsigned extruder_id) { - const Polyline xy_path{ - this->gcode_to_point(from.head<2>()), - this->gcode_to_point(to.head<2>()) - }; +Polyline GCodeGenerator::get_layer_change_xy_path(const Vec3d &from, const Vec3d &to) { + bool could_be_wipe_disabled{false}; + const bool needs_retraction{true}; + + const Point saved_last_position{*this->last_position}; + const bool saved_use_external_mp{this->m_avoid_crossing_perimeters.use_external_mp_once}; + const Vec2d saved_origin{this->origin()}; + const Layer* saved_layer{this->layer()}; + + this->m_avoid_crossing_perimeters.use_external_mp_once = m_layer_change_used_external_mp; + if (this->m_layer_change_origin) { + this->m_origin = *this->m_layer_change_origin; + } + this->m_layer = m_layer_change_layer; + this->m_avoid_crossing_perimeters.init_layer(*this->m_layer); + + const Point start_point{this->gcode_to_point(from.head<2>())}; + const Point end_point{this->gcode_to_point(to.head<2>())}; + this->last_position = start_point; + + Polyline xy_path{ + this->generate_travel_xy_path(start_point, end_point, needs_retraction, could_be_wipe_disabled)}; + std::vector gcode_xy_path; + gcode_xy_path.reserve(xy_path.size()); + for (const Point &point : xy_path.points) { + gcode_xy_path.push_back(this->point_to_gcode(point)); + } + + this->last_position = saved_last_position; + this->m_avoid_crossing_perimeters.use_external_mp_once = saved_use_external_mp; + this->m_origin = saved_origin; + this->m_layer = saved_layer; + + Polyline result; + for (const Vec2d& point : gcode_xy_path) { + result.points.push_back(gcode_to_point(point)); + } + + return result; +} + +GCode::Impl::Travels::ElevatedTravelParams get_ramping_layer_change_params( + const Vec3d &from, + const Vec3d &to, + const Polyline &xy_path, + const FullPrintConfig &config, + const unsigned extruder_id, + const GCode::TravelObstacleTracker &obstacle_tracker +) { using namespace GCode::Impl::Travels; ElevatedTravelParams elevation_params{ - get_elevated_traval_params(xy_path, this->m_config, extruder_id, this->m_travel_obstacle_tracker)}; + get_elevated_traval_params(xy_path, config, extruder_id, obstacle_tracker)}; - const double initial_elevation = from.z(); const double z_change = to.z() - from.z(); elevation_params.lift_height = std::max(z_change, elevation_params.lift_height); @@ -2132,6 +2214,25 @@ std::string GCodeGenerator::get_layer_change_gcode(const Vec3d& from, const Vec3 elevation_params.slope_end = path_length; } + return elevation_params; +} + +std::string GCodeGenerator::get_ramping_layer_change_gcode(const Vec3d &from, const Vec3d &to, const unsigned extruder_id) { + const Polyline xy_path{this->get_layer_change_xy_path(from, to)}; + + const GCode::Impl::Travels::ElevatedTravelParams elevation_params{ + get_ramping_layer_change_params( + from, to, xy_path, m_config, extruder_id, m_travel_obstacle_tracker + )}; + return this->generate_ramping_layer_change_gcode(xy_path, from.z(), elevation_params); +} + +std::string GCodeGenerator::generate_ramping_layer_change_gcode( + const Polyline &xy_path, + const double initial_elevation, + const GCode::Impl::Travels::ElevatedTravelParams &elevation_params +) const { + using namespace GCode::Impl::Travels; const std::vector 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, @@ -2147,7 +2248,8 @@ std::string GCodeGenerator::get_layer_change_gcode(const Vec3d& from, const Vec3 Vec3d previous_point{this->point_to_gcode(travel.front())}; for (const Vec3crd& point : travel) { const Vec3d gcode_point{this->point_to_gcode(point)}; - travel_gcode += this->m_writer.get_travel_to_xyz_gcode(previous_point, gcode_point, "layer change"); + travel_gcode += this->m_writer + .get_travel_to_xyz_gcode(previous_point, gcode_point, "layer change"); previous_point = gcode_point; } return travel_gcode; @@ -2199,6 +2301,10 @@ LayerResult GCodeGenerator::process_layer( bool first_layer = layer.id() == 0; unsigned int first_extruder_id = layer_tools.extruders.front(); + const std::vector instances_to_print{sort_print_object_instances(layers, ordering, single_object_instance_idx)}; + const PrintInstance* first_instance{instances_to_print.empty() ? nullptr : &instances_to_print.front().print_object.instances()[instances_to_print.front().instance_id]}; + m_label_objects.update(first_instance); + //B36 m_writer.set_is_first_layer(first_layer); @@ -2347,6 +2453,10 @@ LayerResult GCodeGenerator::process_layer( 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 loops = loops_it->second; this->set_origin(0., 0.); m_avoid_crossing_perimeters.use_external_mp(); @@ -2368,6 +2478,10 @@ 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) @@ -2378,7 +2492,7 @@ LayerResult GCodeGenerator::process_layer( m_avoid_crossing_perimeters.disable_once(); } - std::vector instances_to_print = sort_print_object_instances(layers, ordering, single_object_instance_idx); + 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): bool is_anything_overridden = layer_tools.wiping_extrusions().is_anything_overridden(); @@ -2420,7 +2534,9 @@ LayerResult GCodeGenerator::process_layer( if (first_layer) { layer_change_gcode = ""; // Explicit for readability. } else if (do_ramping_layer_change) { - layer_change_gcode = this->get_layer_change_gcode(*m_previous_layer_last_position, *m_current_layer_first_position, *m_layer_change_extruder_id); + const Vec3d &from{*m_previous_layer_last_position}; + const Vec3d &to{*m_current_layer_first_position}; + layer_change_gcode = this->get_ramping_layer_change_gcode(from, to, *m_layer_change_extruder_id); } else { layer_change_gcode = this->writer().get_travel_to_z_gcode(print_z, "simple layer change"); } @@ -2451,7 +2567,7 @@ LayerResult GCodeGenerator::process_layer( const std::size_t end{end_tag_start + retraction_end_tag.size()}; gcode.replace(start, end - start, ""); - layer_change_gcode = this->get_layer_change_gcode(*m_previous_layer_last_position_before_wipe, *m_current_layer_first_position, *m_layer_change_extruder_id); + layer_change_gcode = this->get_ramping_layer_change_gcode(*m_previous_layer_last_position_before_wipe, *m_current_layer_first_position, *m_layer_change_extruder_id); removed_retraction = true; } @@ -2500,7 +2616,7 @@ void GCodeGenerator::process_layer_single_object( { bool first = true; // Delay layer initialization as many layers may not print with all extruders. - auto init_layer_delayed = [this, &print_instance, &layer_to_print, &first, &gcode]() { + auto init_layer_delayed = [this, &print_instance, &layer_to_print, &first]() { if (first) { first = false; const PrintObject &print_object = print_instance.print_object; @@ -2512,11 +2628,12 @@ void GCodeGenerator::process_layer_single_object( // When starting a new object, use the external motion planner for the first travel move. const Point &offset = print_object.instances()[print_instance.instance_id].shift; GCode::PrintObjectInstance next_instance = {&print_object, int(print_instance.instance_id)}; - if (m_current_instance != next_instance) - m_avoid_crossing_perimeters.use_external_mp_once(); + if (m_current_instance != next_instance) { + m_avoid_crossing_perimeters.use_external_mp_once = true; + } m_current_instance = next_instance; this->set_origin(unscale(offset)); - gcode += m_label_objects.start_object(print_instance.print_object.instances()[print_instance.instance_id], GCode::LabelObjects::IncludeName::No); + m_label_objects.update(&print_instance.print_object.instances()[print_instance.instance_id]); } }; @@ -2698,8 +2815,6 @@ void GCodeGenerator::process_layer_single_object( } } - if (! first) - gcode += m_label_objects.stop_object(print_instance.print_object.instances()[print_instance.instance_id]); } @@ -2786,6 +2901,9 @@ std::string GCodeGenerator::change_layer( // Increment a progress bar indicator. gcode += m_writer.update_progress(++ m_layer_index, m_layer_count); + 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){ @@ -2793,8 +2911,9 @@ std::string GCodeGenerator::change_layer( std::optional{to_3d(this->point_to_gcode(*this->last_position), previous_layer_z)} : std::nullopt; gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_Start); - gcode += this->retract_and_wipe(); + gcode += this->retract_and_wipe(false, false); gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_End); + gcode += m_writer.reset_e(); } Vec3d new_position = this->writer().get_position(); @@ -2868,7 +2987,7 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC if (m_wipe.enabled()) { // Wipe will hide the seam. - m_wipe.set_path(std::move(smooth_path), false); + m_wipe.set_path(std::move(smooth_path)); } else if (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. @@ -2912,7 +3031,7 @@ std::string GCodeGenerator::extrude_skirt( gcode += m_writer.set_print_acceleration(fast_round_up(m_config.default_acceleration.value)); if (m_wipe.enabled()) // Wipe will hide the seam. - m_wipe.set_path(std::move(smooth_path), false); + m_wipe.set_path(std::move(smooth_path)); return gcode; } @@ -2931,7 +3050,8 @@ std::string GCodeGenerator::extrude_multi_path(const ExtrusionMultiPath &multipa std::string gcode; for (GCode::SmoothPathElement &el : smooth_path) gcode += this->_extrude(el.path_attributes, el.path, description, speed); - m_wipe.set_path(std::move(smooth_path), true); + 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; @@ -3064,11 +3184,22 @@ void GCodeGenerator::GCodeOutputStream::write_format(const char* format, ...) va_end(args); } -std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const double from_z) { +std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const double from_z, const ExtrusionRole role, const std::function& insert_gcode) { std::string gcode; const Vec3d gcode_point = to_3d(this->point_to_gcode(point.head<2>()), unscaled(point.z())); + if (!EXTRUDER_CONFIG(travel_ramping_lift) && this->last_position) { + Vec3d writer_position{this->writer().get_position()}; + writer_position.z() = 0.0; // Endofrce z generation! + this->writer().update_position(writer_position); + gcode = this->travel_to( + *this->last_position, point.head<2>(), role, "travel to first layer point", insert_gcode + ); + } else { + 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)}; @@ -3079,19 +3210,22 @@ std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const lift = 0.0; } - if (EXTRUDER_CONFIG(retract_length) > 0 && (!this->last_position || (!EXTRUDER_CONFIG(travel_ramping_lift)))) { + if (EXTRUDER_CONFIG(retract_length) > 0 && !this->last_position) { if (!this->last_position || EXTRUDER_CONFIG(retract_before_travel) < (this->point_to_gcode(*this->last_position) - gcode_point.head<2>()).norm()) { gcode += this->writer().retract(); gcode += this->writer().get_travel_to_z_gcode(from_z + lift, "lift"); } } - this->last_position = point.head<2>(); - this->writer().update_position(gcode_point); + const std::string comment{"move to first layer point"}; - std::string comment{"move to first layer point"}; + gcode += insert_gcode(); gcode += this->writer().get_travel_to_xy_gcode(gcode_point.head<2>(), comment); gcode += this->writer().get_travel_to_z_gcode(gcode_point.z(), comment); + this->m_avoid_crossing_perimeters.reset_once_modifiers(); + this->last_position = point.head<2>(); + this->writer().update_position(gcode_point); + } m_current_layer_first_position = gcode_point; return gcode; } @@ -3118,15 +3252,22 @@ std::string GCodeGenerator::_extrude( std::string gcode; const std::string_view description_bridge = path_attr.role.is_bridge() ? " (bridge)"sv : ""sv; + const bool has_active_instance{m_label_objects.has_active_instance()}; + 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())); + gcode += this->travel_to_first_position(point, unscaled(point.z()), path_attr.role, [&](){ + return m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer); + }); } else { // go to first point of extrusion path if (!this->last_position) { const double z = this->m_last_layer_z; const std::string comment{"move to print after unknown position"}; gcode += this->retract_and_wipe(); + gcode += m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer); gcode += this->m_writer.travel_to_xy(this->point_to_gcode(path.front().point), comment); gcode += this->m_writer.get_travel_to_z_gcode(z, comment); } else if ( this->last_position != path.front().point) { @@ -3134,7 +3275,9 @@ std::string GCodeGenerator::_extrude( comment += description; comment += description_bridge; comment += " point"; - const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment)}; + const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment, [&](){ + return m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer); + })}; gcode += travel_gcode; } } @@ -3148,6 +3291,12 @@ 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(); + } + + if (m_writer.multiple_extruders && !has_active_instance) { + gcode += m_label_objects.maybe_change_instance(m_writer); } if (!m_pending_pre_extrusion_gcode.empty()) { @@ -3368,7 +3517,8 @@ std::string GCodeGenerator::_extrude( std::string GCodeGenerator::generate_travel_gcode( const Points3& travel, - const std::string& comment + const std::string& comment, + const std::function& insert_gcode ) { std::string gcode; const unsigned acceleration =(unsigned)(m_config.travel_acceleration.value + 0.5); @@ -3383,9 +3533,15 @@ std::string GCodeGenerator::generate_travel_gcode( gcode += this->m_writer.set_travel_acceleration(acceleration); Vec3d previous_point{this->point_to_gcode(travel.front())}; - for (const Vec3crd& point : travel) { + bool already_inserted{false}; + for (std::size_t i{0}; i < travel.size(); ++i) { + const Vec3crd& point{travel[i]}; const Vec3d gcode_point{this->point_to_gcode(point)}; + if (travel.size() - i <= 2 && !already_inserted) { + 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; @@ -3481,7 +3637,11 @@ Polyline GCodeGenerator::generate_travel_xy_path( // This method accepts &point in print coordinates. std::string GCodeGenerator::travel_to( - const Point &start_point, const Point &end_point, ExtrusionRole role, const std::string &comment + const Point &start_point, + const Point &end_point, + ExtrusionRole role, + const std::string &comment, + const std::function& insert_gcode ) { // check whether a straight travel move would need retraction @@ -3541,10 +3701,10 @@ std::string GCodeGenerator::travel_to( ) ); - return wipe_retract_gcode + generate_travel_gcode(travel, comment); + return wipe_retract_gcode + generate_travel_gcode(travel, comment, insert_gcode); } -std::string GCodeGenerator::retract_and_wipe(bool toolchange) +std::string GCodeGenerator::retract_and_wipe(bool toolchange, bool reset_e) { std::string gcode; @@ -3563,7 +3723,9 @@ std::string GCodeGenerator::retract_and_wipe(bool toolchange) length is honored in case wipe path was too short. */ gcode += toolchange ? m_writer.retract_for_toolchange() : m_writer.retract(); + if (reset_e) { gcode += m_writer.reset_e(); + } return gcode; } @@ -3594,8 +3756,12 @@ std::string GCodeGenerator::set_extruder(unsigned int extruder_id, double print_ return gcode; } + std::string gcode{}; + if (!this->m_config.complete_objects.value) { + gcode += this->m_label_objects.maybe_stop_instance(); + } // prepend retraction on the current extruder - std::string gcode = this->retract_and_wipe(true); + gcode += this->retract_and_wipe(true); // Always reset the extrusion path, even if the tool change retract is set to zero. m_wipe.reset_path(); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 8cdec7d..7e58fbb 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -96,7 +96,7 @@ struct PrintObjectInstance int instance_idx = -1; bool operator==(const PrintObjectInstance &other) const {return print_object == other.print_object && instance_idx == other.instance_idx; } - bool operator!=(const PrintObjectInstance &other) const { return *this == other; } + bool operator!=(const PrintObjectInstance &other) const { return !(*this == other); } }; } // namespace GCode @@ -210,8 +210,15 @@ private: static ObjectsLayerToPrint collect_layers_to_print(const PrintObject &object); static std::vector> collect_layers_to_print(const Print &print); + Polyline get_layer_change_xy_path(const Vec3d &from, const Vec3d &to); + + std::string get_ramping_layer_change_gcode(const Vec3d &from, const Vec3d &to, const unsigned extruder_id); /** @brief Generates ramping travel gcode for layer change. */ - std::string get_layer_change_gcode(const Vec3d& from, const Vec3d& to, const unsigned extruder_id); + std::string generate_ramping_layer_change_gcode( + const Polyline &xy_path, + const double initial_elevation, + const GCode::Impl::Travels::ElevatedTravelParams &elevation_params + ) const; LayerResult process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. @@ -302,7 +309,8 @@ private: std::string extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache); std::string generate_travel_gcode( const Points3& travel, - const std::string& comment + const std::string& comment, + const std::function& insert_gcode ); Polyline generate_travel_xy_path( const Point& start, @@ -314,10 +322,11 @@ private: const Point &start_point, const Point &end_point, ExtrusionRole role, - const std::string &comment + const std::string &comment, + const std::function& insert_gcode ); - std::string travel_to_first_position(const Vec3crd& point, const double from_z); + std::string travel_to_first_position(const Vec3crd& point, const double from_z, const ExtrusionRole role, const std::function& insert_gcode); bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None); //B41 @@ -328,7 +337,7 @@ private: int unique_id; }; std::unordered_map m_label_data; - std::string retract_and_wipe(bool toolchange = false); + std::string retract_and_wipe(bool toolchange = false, bool reset_e = true); std::string unretract() { return m_writer.unretract(); } std::string set_extruder(unsigned int extruder_id, double print_z); bool line_distancer_is_required(const std::vector& extruder_ids); @@ -419,6 +428,9 @@ private: // This needs to be populated during the layer processing! std::optional m_current_layer_first_position; std::optional m_layer_change_extruder_id; + bool m_layer_change_used_external_mp{false}; + const Layer* m_layer_change_layer{nullptr}; + std::optional m_layer_change_origin; bool m_already_unretracted{false}; std::unique_ptr m_cooling_buffer; diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 9f26105..136946d 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -1171,7 +1171,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCodeGenerator &gcodegen, cons { // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). // Otherwise perform the path planning in the coordinate system of the active object. - bool use_external = m_use_external_mp || m_use_external_mp_once; + bool use_external = m_use_external_mp || use_external_mp_once; Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); const Point start = *gcodegen.last_position + scaled_origin; const Point end = point + scaled_origin; diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index 5e6d83f..ea2737c 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -17,11 +17,10 @@ class AvoidCrossingPerimeters public: // Routing around the objects vs. inside a single object. void use_external_mp(bool use = true) { m_use_external_mp = use; }; - void use_external_mp_once() { m_use_external_mp_once = true; } - bool used_external_mp_once() { return m_use_external_mp_once; } + bool used_external_mp_once() { return use_external_mp_once; } void disable_once() { m_disabled_once = true; } bool disabled_once() const { return m_disabled_once; } - void reset_once_modifiers() { m_use_external_mp_once = false; m_disabled_once = false; } + void reset_once_modifiers() { use_external_mp_once = false; m_disabled_once = false; } void init_layer(const Layer &layer); @@ -50,10 +49,10 @@ public: } }; + // just for the next travel move + bool use_external_mp_once { false }; private: bool m_use_external_mp { false }; - // just for the next travel move - bool m_use_external_mp_once { false }; // this flag disables avoid_crossing_perimeters just for the next travel move // we enable it by default for the first travel move in print bool m_disabled_once { true }; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index e223850..7948afc 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -3806,7 +3806,13 @@ void GCodeProcessor::post_process() struct LineData { std::string line; - float time; + std::array(PrintEstimatedStatistics::ETimeMode::Count)> times{ 0.0f, 0.0f }; + }; + + enum ETimeMode + { + Normal = static_cast(PrintEstimatedStatistics::ETimeMode::Normal), + Stealth = static_cast(PrintEstimatedStatistics::ETimeMode::Stealth) }; #ifndef NDEBUG @@ -3836,10 +3842,10 @@ void GCodeProcessor::post_process() #endif // NDEBUG EWriteType m_write_type{ EWriteType::BySize }; - // Time machine containing g1 times cache - TimeMachine& m_machine; + // Time machines containing g1 times cache + const std::array(PrintEstimatedStatistics::ETimeMode::Count)>& m_machines; // Current time - float m_time{ 0.0f }; + std::array(PrintEstimatedStatistics::ETimeMode::Count)> m_times{ 0.0f, 0.0f }; // Current size in bytes size_t m_size{ 0 }; @@ -3855,11 +3861,12 @@ void GCodeProcessor::post_process() bgcode::binarize::Binarizer& m_binarizer; public: - ExportLines(bgcode::binarize::Binarizer& binarizer, EWriteType type, TimeMachine& machine) + ExportLines(bgcode::binarize::Binarizer& binarizer, EWriteType type, + const std::array(PrintEstimatedStatistics::ETimeMode::Count)>& machines) #ifndef NDEBUG - : m_statistics(*this), m_binarizer(binarizer), m_write_type(type), m_machine(machine) {} + : m_statistics(*this), m_binarizer(binarizer), m_write_type(type), m_machines(machines) {} #else - : m_binarizer(binarizer), m_write_type(type), m_machine(machine) {} + : m_binarizer(binarizer), m_write_type(type), m_machines(machines) {} #endif // NDEBUG // return: number of internal G1 lines (from G2/G3 splitting) processed @@ -3876,9 +3883,9 @@ void GCodeProcessor::post_process() else return ret; - auto init_it = m_machine.g1_times_cache.begin() + m_times_cache_id; + auto init_it = m_machines[Normal].g1_times_cache.begin() + m_times_cache_id; auto it = init_it; - while (it != m_machine.g1_times_cache.end() && it->id < g1_lines_counter) { + while (it != m_machines[Normal].g1_times_cache.end() && it->id < g1_lines_counter) { ++it; ++m_times_cache_id; } @@ -3888,7 +3895,7 @@ void GCodeProcessor::post_process() // search for internal G1 lines if (GCodeReader::GCodeLine::cmd_is(line, "G2") || GCodeReader::GCodeLine::cmd_is(line, "G3")) { - while (it != m_machine.g1_times_cache.end() && it->remaining_internal_g1_lines > 0) { + while (it != m_machines[Normal].g1_times_cache.end() && it->remaining_internal_g1_lines > 0) { ++it; ++m_times_cache_id; ++g1_lines_counter; @@ -3896,14 +3903,17 @@ void GCodeProcessor::post_process() } } - if (it != m_machine.g1_times_cache.end() && it->id == g1_lines_counter) - m_time = it->elapsed_time; + if (it != m_machines[Normal].g1_times_cache.end() && it->id == g1_lines_counter) { + m_times[Normal] = it->elapsed_time; + if (!m_machines[Stealth].g1_times_cache.empty()) + m_times[Stealth] = (m_machines[Stealth].g1_times_cache.begin() + std::distance(m_machines[Normal].g1_times_cache.begin(), it))->elapsed_time; + } return ret; } // add the given gcode line to the cache void append_line(const std::string& line) { - m_lines.push_back({ line, m_time }); + m_lines.push_back({ line, m_times }); #ifndef NDEBUG m_statistics.add_line(line.length()); #endif // NDEBUG @@ -3914,7 +3924,8 @@ void GCodeProcessor::post_process() } // Insert the gcode lines required by the command cmd by backtracing into the cache - void insert_lines(const Backtrace& backtrace, const std::string& cmd, std::function line_inserter, + void insert_lines(const Backtrace& backtrace, const std::string& cmd, + std::function&)> line_inserter, std::function line_replacer) { assert(!m_lines.empty()); const float time_step = backtrace.time_step(); @@ -3922,13 +3933,13 @@ void GCodeProcessor::post_process() float last_time_insertion = 0.0f; // used to avoid inserting two lines at the same time for (unsigned int i = 0; i < backtrace.steps; ++i) { const float backtrace_time_i = (i + 1) * time_step; - const float time_threshold_i = m_time - backtrace_time_i; + const float time_threshold_i = m_times[Normal] - backtrace_time_i; auto rev_it = m_lines.rbegin() + rev_it_dist; auto start_rev_it = rev_it; std::string curr_cmd = GCodeReader::GCodeLine::extract_cmd(rev_it->line); // backtrace into the cache to find the place where to insert the line - while (rev_it != m_lines.rend() && rev_it->time > time_threshold_i && curr_cmd != cmd && curr_cmd != "G28" && curr_cmd != "G29") { + while (rev_it != m_lines.rend() && rev_it->times[Normal] > time_threshold_i && curr_cmd != cmd && curr_cmd != "G28" && curr_cmd != "G29") { rev_it->line = line_replacer(rev_it->line); ++rev_it; if (rev_it != m_lines.rend()) @@ -3940,11 +3951,15 @@ void GCodeProcessor::post_process() break; // insert the line for the current step - if (rev_it != m_lines.rend() && rev_it != start_rev_it && rev_it->time != last_time_insertion) { - last_time_insertion = rev_it->time; - const std::string out_line = line_inserter(i + 1, last_time_insertion, m_time - last_time_insertion); + if (rev_it != m_lines.rend() && rev_it != start_rev_it && rev_it->times[Normal] != last_time_insertion) { + last_time_insertion = rev_it->times[Normal]; + std::vector time_diffs; + time_diffs.push_back(m_times[Normal] - last_time_insertion); + if (!m_machines[Stealth].g1_times_cache.empty()) + time_diffs.push_back(m_times[Stealth] - rev_it->times[Stealth]); + const std::string out_line = line_inserter(i + 1, time_diffs); rev_it_dist = std::distance(m_lines.rbegin(), rev_it) + 1; - m_lines.insert(rev_it.base(), { out_line, rev_it->time }); + m_lines.insert(rev_it.base(), { out_line, rev_it->times }); #ifndef NDEBUG m_statistics.add_line(out_line.length()); #endif // NDEBUG @@ -3970,7 +3985,7 @@ void GCodeProcessor::post_process() std::string out_string; if (!m_lines.empty()) { if (m_write_type == EWriteType::ByTime) { - while (m_lines.front().time < m_time - backtrace_time) { + while (m_lines.front().times[Normal] < m_times[Normal] - backtrace_time) { const LineData& data = m_lines.front(); out_string += data.line; m_size -= data.line.length(); @@ -4055,7 +4070,8 @@ void GCodeProcessor::post_process() } }; - ExportLines export_lines(m_binarizer, m_result.backtrace_enabled ? ExportLines::EWriteType::ByTime : ExportLines::EWriteType::BySize, m_time_processor.machines[0]); + ExportLines export_lines(m_binarizer, m_result.backtrace_enabled ? ExportLines::EWriteType::ByTime : ExportLines::EWriteType::BySize, + m_time_processor.machines); // replace placeholder lines with the proper final value // gcode_line is in/out parameter, to reduce expensive memory allocation @@ -4259,9 +4275,14 @@ void GCodeProcessor::post_process() } export_lines.insert_lines(backtrace, cmd, // line inserter - [tool_number, this](unsigned int id, float time, float time_diff) { - int temperature = int( m_layer_id != 1 ? m_extruder_temps_config[tool_number] : m_extruder_temps_first_layer_config[tool_number]); - const std::string out = "M104 T" + std::to_string(tool_number) + " P" + std::to_string(int(std::round(time_diff))) + " S" + std::to_string(temperature) + "\n"; + [tool_number, this](unsigned int id, const std::vector& time_diffs) { + const int temperature = int(m_layer_id != 1 ? m_extruder_temps_config[tool_number] : m_extruder_temps_first_layer_config[tool_number]); + std::string out = "M104.1 T" + std::to_string(tool_number); + if (time_diffs.size() > 0) + out += " P" + std::to_string(int(std::round(time_diffs[0]))); + if (time_diffs.size() > 1) + out += " Q" + std::to_string(int(std::round(time_diffs[1]))); + out += " S" + std::to_string(temperature) + "\n"; return out; }, // line replacer diff --git a/src/libslic3r/GCode/GCodeWriter.hpp b/src/libslic3r/GCode/GCodeWriter.hpp index ed0e826..22b592e 100644 --- a/src/libslic3r/GCode/GCodeWriter.hpp +++ b/src/libslic3r/GCode/GCodeWriter.hpp @@ -198,6 +198,8 @@ public: { return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS) }; } static Vec3d quantize(const Vec3d &pt) { return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS), quantize(pt.z(), XYZF_EXPORT_DIGITS) }; } + static Vec2d quantize(const Vec2f &pt) + { return { quantize(double(pt.x()), XYZF_EXPORT_DIGITS), quantize(double(pt.y()), XYZF_EXPORT_DIGITS) }; } void emit_axis(const char axis, const double v, size_t digits); diff --git a/src/libslic3r/GCode/LabelObjects.cpp b/src/libslic3r/GCode/LabelObjects.cpp index 0692ee9..9d1413f 100644 --- a/src/libslic3r/GCode/LabelObjects.cpp +++ b/src/libslic3r/GCode/LabelObjects.cpp @@ -1,10 +1,12 @@ #include "LabelObjects.hpp" #include "ClipperUtils.hpp" +#include "GCode/GCodeWriter.hpp" #include "Model.hpp" #include "Print.hpp" #include "TriangleMeshSlicer.hpp" +#include "boost/algorithm/string/replace.hpp" namespace Slic3r::GCode { @@ -39,10 +41,10 @@ Polygon instance_outline(const PrintInstance* pi) }; // anonymous namespace -void LabelObjects::init(const Print& print) +void LabelObjects::init(const SpanOfConstPtrs& objects, LabelObjectsStyle label_object_style, GCodeFlavor gcode_flavor) { - m_label_objects_style = print.config().gcode_label_objects; - m_flavor = print.config().gcode_flavor; + m_label_objects_style = label_object_style; + m_flavor = gcode_flavor; if (m_label_objects_style == LabelObjectsStyle::Disabled) return; @@ -51,7 +53,7 @@ void LabelObjects::init(const Print& print) // Iterate over all PrintObjects and their PrintInstances, collect PrintInstances which // belong to the same ModelObject. - for (const PrintObject* po : print.objects()) + for (const PrintObject* po : objects) for (const PrintInstance& pi : po->instances()) model_object_to_print_instances[pi.model_instance->get_object()].emplace_back(&pi); @@ -87,13 +89,69 @@ void LabelObjects::init(const Print& print) } } - m_label_data.emplace(pi, LabelData{name, unique_id}); + // Now calculate the polygon and center for Cancel Object (this is not always used). + Polygon outline = instance_outline(pi); + assert(! outline.empty()); + outline.douglas_peucker(50000.f); + Point center = outline.centroid(); + char buffer[64]; + std::snprintf(buffer, sizeof(buffer) - 1, "%.3f,%.3f", unscale(center[0]), unscale(center[1])); + std::string center_str(buffer); + std::string polygon_str = std::string("["); + for (const Point& point : outline) { + std::snprintf(buffer, sizeof(buffer) - 1, "[%.3f,%.3f],", unscale(point[0]), unscale(point[1])); + polygon_str += buffer; + } + polygon_str.pop_back(); + polygon_str += "]"; + + m_label_data.emplace_back(LabelData{pi, name, center_str, polygon_str, unique_id}); ++unique_id; } } } +bool LabelObjects::update(const PrintInstance *instance) { + if (this->last_operation_instance == instance) { + return false; + } + this->last_operation_instance = instance; + return true; +} +std::string LabelObjects::maybe_start_instance(GCodeWriter& writer) { + if (current_instance == nullptr && last_operation_instance != nullptr) { + current_instance = last_operation_instance; + + std::string result{this->start_object(*current_instance, LabelObjects::IncludeName::No)}; + result += writer.reset_e(true); + return result; + } + return ""; +} + +std::string LabelObjects::maybe_stop_instance() { + if (current_instance != nullptr) { + const std::string result{this->stop_object(*current_instance)}; + current_instance = nullptr; + return result; + } + return ""; +} + +std::string LabelObjects::maybe_change_instance(GCodeWriter& writer) { + if (last_operation_instance != current_instance) { + const std::string stop_instance_gcode{this->maybe_stop_instance()}; + // Be carefull with refactoring: this->maybe_stop_instance() + this->maybe_start_instance() + // may not be evaluated in order. The order is indeed undefined! + return stop_instance_gcode + this->maybe_start_instance(writer); + } + return ""; +} + +bool LabelObjects::has_active_instance() { + return this->current_instance != nullptr; +} std::string LabelObjects::all_objects_header() const { @@ -102,38 +160,34 @@ std::string LabelObjects::all_objects_header() const std::string out; - // Let's sort the values according to unique_id so they are in the same order in which they were added. - std::vector> label_data_sorted; - for (const auto& pi_and_label : m_label_data) - label_data_sorted.emplace_back(pi_and_label); - std::sort(label_data_sorted.begin(), label_data_sorted.end(), [](const auto& ld1, const auto& ld2) { return ld1.second.unique_id < ld2.second.unique_id; }); out += "\n"; - for (const auto& [print_instance, label] : label_data_sorted) { - if (m_label_objects_style == LabelObjectsStyle::Firmware && m_flavor == gcfKlipper) { - char buffer[64]; - out += "EXCLUDE_OBJECT_DEFINE NAME='" + label.name + "'"; - Polygon outline = instance_outline(print_instance); - assert(! outline.empty()); - outline.douglas_peucker(50000.f); - Point center = outline.centroid(); - std::snprintf(buffer, sizeof(buffer) - 1, " CENTER=%.3f,%.3f", unscale(center[0]), unscale(center[1])); - out += buffer + std::string(" POLYGON=["); - for (const Point& point : outline) { - std::snprintf(buffer, sizeof(buffer) - 1, "[%.3f,%.3f],", unscale(point[0]), unscale(point[1])); - out += buffer; - } - out.pop_back(); - out += "]\n"; - } else { - out += start_object(*print_instance, IncludeName::Yes); - out += stop_object(*print_instance); + for (const LabelData& label : m_label_data) { + if (m_label_objects_style == LabelObjectsStyle::Firmware && m_flavor == gcfKlipper) + out += "EXCLUDE_OBJECT_DEFINE NAME='" + label.name + "' CENTER=" + label.center + " POLYGON=" + label.polygon + "\n"; + else { + out += start_object(*label.pi, IncludeName::Yes); + out += stop_object(*label.pi); } } out += "\n"; return out; } +std::string LabelObjects::all_objects_header_singleline_json() const +{ + std::string out; + out = "{\"objects\":["; + for (size_t i=0; i -#include +#include + +#include "libslic3r/Print.hpp" namespace Slic3r { @@ -11,30 +13,49 @@ enum class LabelObjectsStyle; struct PrintInstance; class Print; +class GCodeWriter; namespace GCode { - -class LabelObjects { +class LabelObjects +{ public: + void init(const SpanOfConstPtrs& objects, LabelObjectsStyle label_object_style, GCodeFlavor gcode_flavor); + std::string all_objects_header() const; + std::string all_objects_header_singleline_json() const; + + bool update(const PrintInstance *instance); + + std::string maybe_start_instance(GCodeWriter& writer); + + std::string maybe_stop_instance(); + + std::string maybe_change_instance(GCodeWriter& writer); + + bool has_active_instance(); + +private: + struct LabelData + { + const PrintInstance* pi; + std::string name; + std::string center; + std::string polygon; + int unique_id; + }; enum class IncludeName { No, Yes }; - void init(const Print& print); - std::string all_objects_header() const; std::string start_object(const PrintInstance& print_instance, IncludeName include_name) const; std::string stop_object(const PrintInstance& print_instance) const; -private: - struct LabelData { - std::string name; - int unique_id; - }; + const PrintInstance* current_instance{nullptr}; + const PrintInstance* last_operation_instance{nullptr}; LabelObjectsStyle m_label_objects_style; GCodeFlavor m_flavor; - std::unordered_map m_label_data; + std::vector m_label_data; }; diff --git a/src/libslic3r/GCode/PressureEqualizer.cpp b/src/libslic3r/GCode/PressureEqualizer.cpp index 138200e..c82fa29 100644 --- a/src/libslic3r/GCode/PressureEqualizer.cpp +++ b/src/libslic3r/GCode/PressureEqualizer.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "../libslic3r.h" #include "../PrintConfig.hpp" @@ -27,6 +28,11 @@ static constexpr float max_segment_length = 5.f; // affect how distant will be propagated a flow rate adjustment. static constexpr int max_look_back_limit = 128; +// Max non-extruding XY distance (travel move) in mm between two continuous extrusions where we pretend +// it's all one continuous extrusion line. Above this distance, we assume extruder pressure hits 0 +// This exists because often there are tiny travel moves between stuff like infill. +// Lines where some extruder pressure will remain (so we should equalize between these small travels). +static constexpr double max_ignored_gap_between_extruding_segments = 3.; PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig &config) : m_use_relative_e_distances(config.use_relative_e_distances.value) { // Preallocate some data, so that output_buffer.data() will return an empty string. @@ -59,8 +65,8 @@ PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig &config) : m_use_ extrusion_rate_slope.positive = m_max_volumetric_extrusion_rate_slope_positive; } - // Don't regulate the pressure before and after gap-fill and ironing. - for (const GCodeExtrusionRole er : {GCodeExtrusionRole::GapFill, GCodeExtrusionRole::Ironing}) { + // Don't regulate the pressure before and after ironing. + for (const GCodeExtrusionRole er : {GCodeExtrusionRole::Ironing}) { m_max_volumetric_extrusion_rate_slopes[size_t(er)].negative = 0; m_max_volumetric_extrusion_rate_slopes[size_t(er)].positive = 0; } @@ -97,6 +103,72 @@ void PressureEqualizer::process_layer(const std::string &gcode) } assert(!this->opened_extrude_set_speed_block); } + // At this point, we have an entire layer of gcode lines loaded into m_gcode_lines. + // Now, we will split the mix of travels and extrusions into segments of continuous extrusions and process them. + // We skip over large travels, and pretend that small ones are part of a continuous extrusion segment. + for (auto current_extrusion_end_it = m_gcode_lines.cbegin(); current_extrusion_end_it != m_gcode_lines.cend();) { + // Find beginning of next extrusion segment from current position. + const auto current_extrusion_begin_it = std::find_if(current_extrusion_end_it, m_gcode_lines.cend(), [](const GCodeLine &line) { + return line.extruding(); + }); + + // We start with extrusion length of zero. + current_extrusion_end_it = current_extrusion_begin_it; + + // Inner loop extends the extrusion segment over small travel moves. + while (current_extrusion_end_it != m_gcode_lines.cend()) { + // Find the end of the current extrusion segment. + const auto travel_begin_it = std::find_if(std::next(current_extrusion_end_it), m_gcode_lines.cend(), [](const GCodeLine &line) { + return !line.extruding(); + }); + + current_extrusion_end_it = std::prev(travel_begin_it); + + const auto next_extrusion_segment_it = advance_segment_beyond_small_gap(current_extrusion_end_it); + if (std::distance(current_extrusion_end_it, next_extrusion_segment_it) > 0) { + // Extend the continuous line over the small gap. + current_extrusion_end_it = next_extrusion_segment_it; + continue; // Keep going, loop again to find the new end of extrusion segment. + } else { + break; // Gap to next extrude is too big, stop looking forward. We've found the end of this segment. + } + } + + // Now, run the pressure equalizer across the segment like a streamroller. + // It operates on a sliding window that moves forward across gcode line by line. + const std::ptrdiff_t current_extrusion_begin_idx = std::distance(m_gcode_lines.cbegin(), current_extrusion_begin_it); + for (auto current_line_it = current_extrusion_begin_it; current_line_it != current_extrusion_end_it; ++current_line_it) { + const std::ptrdiff_t current_line_idx = std::distance(m_gcode_lines.cbegin(), current_line_it); + + // Feed pressure equalizer past lines, going back to max_look_back_limit (or start of segment). + const size_t start_idx = size_t(std::max(current_extrusion_begin_idx, current_line_idx - max_look_back_limit)); + adjust_volumetric_rate(start_idx, size_t(current_line_idx)); + } + + // Current extrusion is all done processing so advance beyond it for the next loop. + if (current_extrusion_end_it != m_gcode_lines.cend()) + ++current_extrusion_end_it; + } +} + +PressureEqualizer::GCodeLinesConstIt PressureEqualizer::advance_segment_beyond_small_gap(const GCodeLinesConstIt &last_extruding_line_it) const { + // This should only be run on the last extruding line before a gap. + assert(last_extruding_line_it != m_gcode_lines.cend() && last_extruding_line_it->extruding()); + double travel_distance = 0.; + // Start at the beginning of a gap, advance till extrusion found or gap too big. + for (auto current_line_it = std::next(last_extruding_line_it); current_line_it != m_gcode_lines.cend(); ++current_line_it) { + // Started extruding again! Return segment extension. + if (current_line_it->extruding()) + return current_line_it; + + travel_distance += current_line_it->dist_xy(); + // Gap too big, don't extend segment. + if (travel_distance > max_ignored_gap_between_extruding_segments) + return last_extruding_line_it; + } + + // Looped until the end of the layer and couldn't extend extrusion. + return last_extruding_line_it; } LayerResult PressureEqualizer::process_layer(LayerResult &&input) @@ -391,7 +463,6 @@ bool PressureEqualizer::process_line(const char *line, const char *line_end, GCo buf.extruder_id = m_current_extruder; memcpy(buf.pos_end, m_current_pos, sizeof(float)*5); - adjust_volumetric_rate(); #ifdef PRESSURE_EQUALIZER_DEBUG ++line_idx; #endif @@ -506,16 +577,14 @@ void PressureEqualizer::output_gcode_line(const size_t line_idx) } } -void PressureEqualizer::adjust_volumetric_rate() +void PressureEqualizer::adjust_volumetric_rate(const size_t first_line_idx, const size_t last_line_idx) { - if (m_gcode_lines.size() < 2) + // Don't bother adjusting volumetric rate if there's no gcode to adjust. + if (last_line_idx <= first_line_idx || last_line_idx - first_line_idx < 2) return; - // Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes. - size_t fist_line_idx = size_t(std::max(0, int(m_gcode_lines.size()) - max_look_back_limit)); - const size_t last_line_idx = m_gcode_lines.size() - 1; size_t line_idx = last_line_idx; - if (line_idx == fist_line_idx || !m_gcode_lines[line_idx].extruding()) + if (line_idx == first_line_idx || !m_gcode_lines[line_idx].extruding()) // Nothing to do, the last move is not extruding. return; @@ -523,13 +592,13 @@ void PressureEqualizer::adjust_volumetric_rate() feedrate_per_extrusion_role.fill(std::numeric_limits::max()); feedrate_per_extrusion_role[int(m_gcode_lines[line_idx].extrusion_role)] = m_gcode_lines[line_idx].volumetric_extrusion_rate_start; - while (line_idx != fist_line_idx) { + while (line_idx != first_line_idx) { size_t idx_prev = line_idx - 1; - for (; !m_gcode_lines[idx_prev].extruding() && idx_prev != fist_line_idx; --idx_prev); + for (; !m_gcode_lines[idx_prev].extruding() && idx_prev != first_line_idx; --idx_prev); if (!m_gcode_lines[idx_prev].extruding()) break; - // Don't decelerate before ironing and gap-fill. - if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing || m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::GapFill) { + // Don't decelerate before ironing. + if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing) { line_idx = idx_prev; continue; } @@ -549,7 +618,8 @@ void PressureEqualizer::adjust_volumetric_rate() // Limit by the succeeding volumetric flow rate. rate_end = rate_succ; - if (!line.adjustable_flow || line.extrusion_role == GCodeExtrusionRole::ExternalPerimeter || line.extrusion_role == GCodeExtrusionRole::GapFill || line.extrusion_role == GCodeExtrusionRole::BridgeInfill || line.extrusion_role == GCodeExtrusionRole::Ironing) { + // Don't alter the flow rate for these extrusion types. + if (!line.adjustable_flow || line.extrusion_role == GCodeExtrusionRole::BridgeInfill || line.extrusion_role == GCodeExtrusionRole::Ironing) { rate_end = line.volumetric_extrusion_rate_end; } else if (line.volumetric_extrusion_rate_end > rate_end) { line.volumetric_extrusion_rate_end = rate_end; @@ -571,9 +641,8 @@ void PressureEqualizer::adjust_volumetric_rate() line.modified = true; } } -// feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_start : rate_start; - // Don't store feed rate for ironing and gap-fill. - if (line.extrusion_role != GCodeExtrusionRole::Ironing && line.extrusion_role != GCodeExtrusionRole::GapFill) + // Don't store feed rate for ironing. + if (line.extrusion_role != GCodeExtrusionRole::Ironing) feedrate_per_extrusion_role[iRole] = line.volumetric_extrusion_rate_start; } } @@ -587,8 +656,8 @@ void PressureEqualizer::adjust_volumetric_rate() for (; !m_gcode_lines[idx_next].extruding() && idx_next != last_line_idx; ++idx_next); if (!m_gcode_lines[idx_next].extruding()) break; - // Don't accelerate after ironing and gap-fill. - if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing || m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::GapFill) { + // Don't accelerate after ironing. + if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing) { line_idx = idx_next; continue; } @@ -603,7 +672,8 @@ void PressureEqualizer::adjust_volumetric_rate() continue; // The positive rate is unlimited or the rate for GCodeExtrusionRole iRole is unlimited. float rate_start = feedrate_per_extrusion_role[iRole]; - if (!line.adjustable_flow || line.extrusion_role == GCodeExtrusionRole::ExternalPerimeter || line.extrusion_role == GCodeExtrusionRole::GapFill || line.extrusion_role == GCodeExtrusionRole::BridgeInfill || line.extrusion_role == GCodeExtrusionRole::Ironing) { + // Don't alter the flow rate for these extrusion types. + if (!line.adjustable_flow || line.extrusion_role == GCodeExtrusionRole::BridgeInfill || line.extrusion_role == GCodeExtrusionRole::Ironing) { rate_start = line.volumetric_extrusion_rate_start; } else if (iRole == size_t(line.extrusion_role) && rate_prec < rate_start) rate_start = rate_prec; @@ -627,9 +697,8 @@ void PressureEqualizer::adjust_volumetric_rate() line.modified = true; } } -// feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_end : rate_end; - // Don't store feed rate for ironing and gap-fill. - if (line.extrusion_role != GCodeExtrusionRole::Ironing && line.extrusion_role != GCodeExtrusionRole::GapFill) + // Don't store feed rate for ironing + if (line.extrusion_role != GCodeExtrusionRole::Ironing) feedrate_per_extrusion_role[iRole] = line.volumetric_extrusion_rate_end; } } diff --git a/src/libslic3r/GCode/PressureEqualizer.hpp b/src/libslic3r/GCode/PressureEqualizer.hpp index a222089..e7ff680 100644 --- a/src/libslic3r/GCode/PressureEqualizer.hpp +++ b/src/libslic3r/GCode/PressureEqualizer.hpp @@ -169,6 +169,8 @@ private: bool extrude_end_tag = false; }; + using GCodeLines = std::vector; + using GCodeLinesConstIt = GCodeLines::const_iterator; // Output buffer will only grow. It will not be reallocated over and over. std::vector output_buffer; size_t output_buffer_length; @@ -182,9 +184,10 @@ private: bool process_line(const char *line, const char *line_end, GCodeLine &buf); void output_gcode_line(size_t line_idx); + GCodeLinesConstIt advance_segment_beyond_small_gap(const GCodeLinesConstIt &last_extruding_line_it) const; // Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes. // Then go forward and adjust the feedrate to decrease the slope of the extrusion rate changes. - void adjust_volumetric_rate(); + void adjust_volumetric_rate(size_t first_line_idx, size_t last_line_idx); // Push the text to the end of the output_buffer. inline void push_to_output(GCodeG1Formatter &formatter); diff --git a/src/libslic3r/GCode/SmoothPath.cpp b/src/libslic3r/GCode/SmoothPath.cpp index f27f1f2..f65d7b7 100644 --- a/src/libslic3r/GCode/SmoothPath.cpp +++ b/src/libslic3r/GCode/SmoothPath.cpp @@ -133,6 +133,11 @@ double clip_end(SmoothPath &path, double distance, double min_point_distance_thr return distance; } +void reverse(SmoothPath &path) { + std::reverse(path.begin(), path.end()); + for (SmoothPathElement &path_element : path) + Geometry::ArcWelder::reverse(path_element.path); +} void SmoothPathCache::interpolate_add(const ExtrusionPath &path, const InterpolationParameters ¶ms) { double tolerance = params.tolerance; diff --git a/src/libslic3r/GCode/SmoothPath.hpp b/src/libslic3r/GCode/SmoothPath.hpp index 83afd60..4e5e6cf 100644 --- a/src/libslic3r/GCode/SmoothPath.hpp +++ b/src/libslic3r/GCode/SmoothPath.hpp @@ -33,6 +33,7 @@ std::optional sample_path_point_at_distance_from_end(const SmoothPath &pa // rather discard such a degenerate segment. double clip_end(SmoothPath &path, double distance, double min_point_distance_threshold); +void reverse(SmoothPath &path); class SmoothPathCache { public: diff --git a/src/libslic3r/GCode/SpiralVase.cpp b/src/libslic3r/GCode/SpiralVase.cpp index c3caee2..0c53b78 100644 --- a/src/libslic3r/GCode/SpiralVase.cpp +++ b/src/libslic3r/GCode/SpiralVase.cpp @@ -1,10 +1,21 @@ #include "SpiralVase.hpp" #include "GCode.hpp" #include +#include +#include namespace Slic3r { -std::string SpiralVase::process_layer(const std::string &gcode) +static AABBTreeLines::LinesDistancer get_layer_distancer(const std::vector &layer_points) +{ + Linesf lines; + for (size_t idx = 1; idx < layer_points.size(); ++idx) + lines.emplace_back(layer_points[idx - 1].cast(), layer_points[idx].cast()); + + return AABBTreeLines::LinesDistancer{std::move(lines)}; +} + +std::string SpiralVase::process_layer(const std::string &gcode, bool last_layer) { /* This post-processor relies on several assumptions: - all layers are processed through it, including those that are not supposed @@ -22,8 +33,8 @@ std::string SpiralVase::process_layer(const std::string &gcode) } // Get total XY length for this layer by summing all extrusion moves. - float total_layer_length = 0; - float layer_height = 0; + float total_layer_length = 0.f; + float layer_height = 0.f; float z = 0.f; { @@ -49,15 +60,21 @@ std::string SpiralVase::process_layer(const std::string &gcode) // Remove layer height from initial Z. z -= layer_height; - std::string new_gcode; - //FIXME Tapering of the transition layer only works reliably with relative extruder distances. + // FIXME Tapering of the transition layer and smoothing only works reliably with relative extruder distances. // For absolute extruder distances it will be switched off. // Tapering the absolute extruder distances requires to process every extrusion value after the first transition // layer. - bool transition = m_transition_layer && m_config.use_relative_e_distances.value; - float layer_height_factor = layer_height / total_layer_length; + const bool transition_in = m_transition_layer && m_config.use_relative_e_distances.value; + const bool transition_out = last_layer && m_config.use_relative_e_distances.value; + const bool smooth_spiral = m_smooth_spiral && m_config.use_relative_e_distances.value; + + const AABBTreeLines::LinesDistancer previous_layer_distancer = get_layer_distancer(m_previous_layer); + Vec2f last_point = m_previous_layer.empty() ? Vec2f::Zero() : m_previous_layer.back(); float len = 0.f; - m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height_factor, transition, &len] + std::string new_gcode, transition_gcode; + std::vector current_layer; + m_reader.parse_buffer(gcode, [z, total_layer_length, layer_height, transition_in, transition_out, smooth_spiral, max_xy_smoothing = m_max_xy_smoothing, + &len, &last_point, &new_gcode, &transition_gcode, ¤t_layer, &previous_layer_distancer] (GCodeReader &reader, GCodeReader::GCodeLine line) { if (line.cmd_is("G1")) { if (line.has_z()) { @@ -66,16 +83,52 @@ std::string SpiralVase::process_layer(const std::string &gcode) line.set(reader, Z, z); new_gcode += line.raw() + '\n'; return; + } else if (line.has_x() || line.has_y()) { // Sometimes lines have X/Y but the move is to the last position. + if (const float dist_XY = line.dist_XY(reader); dist_XY > 0 && line.extruding(reader)) { // Exclude wipe and retract + len += dist_XY; + const float factor = len / total_layer_length; + if (transition_in) + // Transition layer, interpolate the amount of extrusion from zero to the final value. + line.set(reader, E, line.e() * factor, 5); + else if (transition_out) { + // We want the last layer to ramp down extrusion, but without changing z height! + // So clone the line before we mess with its Z and duplicate it into a new layer that ramps down E + // We add this new layer at the very end + GCodeReader::GCodeLine transition_line(line); + transition_line.set(reader, E, line.e() * (1.f - factor), 5); + transition_gcode += transition_line.raw() + '\n'; + } + + // This line is the core of Spiral Vase mode, ramp up the Z smoothly + line.set(reader, Z, z + factor * layer_height); + + bool emit_gcode_line = true; + if (smooth_spiral) { + // Now we also need to try to interpolate X and Y + Vec2f p(line.x(), line.y()); // Get current x/y coordinates + current_layer.emplace_back(p); // Store that point for later use on the next layer + + auto [nearest_distance, idx, nearest_pt] = previous_layer_distancer.distance_from_lines_extra(p.cast()); + if (nearest_distance < max_xy_smoothing) { + // Interpolate between the point on this layer and the point on the previous layer + Vec2f target = nearest_pt.cast() * (1.f - factor) + p * factor; + + // We will emit a new g-code line only when XYZ positions differ from the previous g-code line. + emit_gcode_line = GCodeFormatter::quantize(last_point) != GCodeFormatter::quantize(target); + + line.set(reader, X, target.x()); + line.set(reader, Y, target.y()); + // We need to figure out the distance of this new line! + float modified_dist_XY = (last_point - target).norm(); + // Scale the extrusion amount according to change in length + line.set(reader, E, line.e() * modified_dist_XY / dist_XY, 5); + last_point = target; } else { - float dist_XY = line.dist_XY(reader); - if (dist_XY > 0) { - // horizontal move - if (line.extruding(reader)) { - len += dist_XY; - line.set(reader, Z, z + len * layer_height_factor); - if (transition && line.has(E)) - // Transition layer, modulate the amount of extrusion from zero to the final value. - line.set(reader, E, line.value(E) * len / total_layer_length); + last_point = p; + } + } + + if (emit_gcode_line) new_gcode += line.raw() + '\n'; } return; @@ -84,14 +137,18 @@ std::string SpiralVase::process_layer(const std::string &gcode) cause a visible seam when loops are not aligned in XY; by skipping it we blend the first loop move in the XY plane (although the smoothness of such blend depend on how long the first segment is; maybe we should - enforce some minimum length?). */ + enforce some minimum length?). + When smooth_spiral is enabled, we're gonna end up exactly where the next layer should + start anyway, so we don't need the travel move */ } } - } new_gcode += line.raw() + '\n'; + if (transition_out) + transition_gcode += line.raw() + '\n'; }); - return new_gcode; + m_previous_layer = std::move(current_layer); + return new_gcode + transition_gcode; } } diff --git a/src/libslic3r/GCode/SpiralVase.hpp b/src/libslic3r/GCode/SpiralVase.hpp index fb461c2..ad4f40d 100644 --- a/src/libslic3r/GCode/SpiralVase.hpp +++ b/src/libslic3r/GCode/SpiralVase.hpp @@ -6,28 +6,38 @@ namespace Slic3r { -class SpiralVase { +class SpiralVase +{ public: - SpiralVase(const PrintConfig &config) : m_config(config) + SpiralVase() = delete; + + explicit SpiralVase(const PrintConfig &config) : m_config(config) { m_reader.z() = (float)m_config.z_offset; m_reader.apply_config(m_config); + const double max_nozzle_diameter = *std::max_element(config.nozzle_diameter.values.begin(), config.nozzle_diameter.values.end()); + m_max_xy_smoothing = float(2. * max_nozzle_diameter); }; - void enable(bool en) { - m_transition_layer = en && ! m_enabled; - m_enabled = en; + void enable(bool enable) + { + m_transition_layer = enable && !m_enabled; + m_enabled = enable; } - std::string process_layer(const std::string &gcode); + std::string process_layer(const std::string &gcode, bool last_layer); private: const PrintConfig &m_config; GCodeReader m_reader; + float m_max_xy_smoothing = 0.f; bool m_enabled = false; // First spiral vase layer. Layer height has to be ramped up from zero to the target layer height. bool m_transition_layer = false; + // Whether to interpolate XY coordinates with the previous layer. Results in no seam at layer changes + bool m_smooth_spiral = true; + std::vector m_previous_layer; }; } diff --git a/src/libslic3r/GCode/Wipe.cpp b/src/libslic3r/GCode/Wipe.cpp index c5c6be0..d68746b 100644 --- a/src/libslic3r/GCode/Wipe.cpp +++ b/src/libslic3r/GCode/Wipe.cpp @@ -32,27 +32,11 @@ void Wipe::init(const PrintConfig &config, const std::vector &extr this->enable(wipe_xy); } -void Wipe::set_path(SmoothPath &&path, bool reversed) -{ +void Wipe::set_path(SmoothPath &&path) { this->reset_path(); if (this->enabled() && ! path.empty()) { - if (coord_t wipe_len_max_scaled = scaled(m_wipe_len_max); reversed) { - m_path = std::move(path.back().path); - Geometry::ArcWelder::reverse(m_path); - int64_t len = Geometry::ArcWelder::estimate_path_length(m_path); - for (auto it = std::next(path.rbegin()); len < wipe_len_max_scaled && it != path.rend(); ++ it) { - if (it->path_attributes.role.is_bridge()) - break; // Do not perform a wipe on bridges. - assert(it->path.size() >= 2); - assert(m_path.back().point == it->path.back().point); - if (m_path.back().point != it->path.back().point) - // ExtrusionMultiPath is interrupted in some place. This should not really happen. - break; - len += Geometry::ArcWelder::estimate_path_length(it->path); - m_path.insert(m_path.end(), it->path.rbegin() + 1, it->path.rend()); - } - } else { + const coord_t wipe_len_max_scaled = scaled(m_wipe_len_max); m_path = std::move(path.front().path); int64_t len = Geometry::ArcWelder::estimate_path_length(m_path); for (auto it = std::next(path.begin()); len < wipe_len_max_scaled && it != path.end(); ++ it) { @@ -67,7 +51,6 @@ void Wipe::set_path(SmoothPath &&path, bool reversed) m_path.insert(m_path.end(), it->path.begin() + 1, it->path.end()); } } - } assert(m_path.empty() || m_path.size() > 1); } diff --git a/src/libslic3r/GCode/Wipe.hpp b/src/libslic3r/GCode/Wipe.hpp index 053b179..e65657e 100644 --- a/src/libslic3r/GCode/Wipe.hpp +++ b/src/libslic3r/GCode/Wipe.hpp @@ -42,7 +42,7 @@ public: if (this->enabled() && path.size() > 1) m_path = std::move(path); } - void set_path(SmoothPath &&path, bool reversed); + void set_path(SmoothPath &&path); void offset_path(const Point &v) { m_offset += v; } std::string wipe(GCodeGenerator &gcodegen, bool toolchange); diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 0f9c2c5..8e8785c 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -22,6 +22,16 @@ namespace Slic3r { +// Calculates length of extrusion line to extrude given volume +static float volume_to_length(float volume, float line_width, float layer_height) +{ + return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f)))); +} + +static float length_to_volume(float length, float line_width, float layer_height) +{ + return std::max(0.f, length * layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))); +} class WipeTowerWriter { public: @@ -102,6 +112,10 @@ public: return *this; } + WipeTowerWriter& switch_filament_monitoring(bool enable) { + m_gcode += std::string("G4 S0\n") + "M591 " + (enable ? "R" : "S0") + "\n"; + return *this; + } // Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various // filament loading and cooling moves from normal extrusion moves. Therefore the writer // is asked to suppres output of some lines, which look like extrusions. @@ -284,6 +298,24 @@ public: return extrude_explicit(end_point, y(), loading_dist, x_speed * 60.f, false, false); } + // Loads filament while also moving towards given point in x-axis. Unlike the previous function, this one respects + // both the loading_speed and x_speed. Can shorten the move. + WipeTowerWriter& load_move_x_advanced_there_and_back(float farthest_x, float e_dist, float e_speed, float x_speed) + { + float old_x = x(); + float time = std::abs(e_dist / e_speed); // time that the whole move must take + float x_max_dist = std::abs(farthest_x - x()); // max x-distance that we can travel + float x_dist = x_speed * time; // totel x-distance to travel during the move + int n = int(x_dist / (2*x_max_dist) + 1.f); // how many there and back moves should we do + float r = 2*n*x_max_dist / x_dist; // actual/required dist if the move is not shortened + + float end_point = x() + (farthest_x > x() ? 1.f : -1.f) * x_max_dist / r; + for (int i=0; i& bed_points = config.bed_shape.values; BoundingBoxf bb(bed_points); @@ -594,6 +630,7 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config) m_filpar[idx].is_soluble = config.wipe_tower_extruder == 0 ? config.filament_soluble.get_at(idx) : (idx != size_t(config.wipe_tower_extruder - 1)); m_filpar[idx].temperature = config.temperature.get_at(idx); m_filpar[idx].first_layer_temperature = config.first_layer_temperature.get_at(idx); + m_filpar[idx].filament_minimal_purge_on_wipe_tower = config.filament_minimal_purge_on_wipe_tower.get_at(idx); // If this is a single extruder MM printer, we will use all the SE-specific config values. // Otherwise, the defaults will be used to turn off the SE stuff. @@ -606,6 +643,8 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config) m_filpar[idx].cooling_moves = config.filament_cooling_moves.get_at(idx); m_filpar[idx].cooling_initial_speed = float(config.filament_cooling_initial_speed.get_at(idx)); m_filpar[idx].cooling_final_speed = float(config.filament_cooling_final_speed.get_at(idx)); + m_filpar[idx].filament_stamping_loading_speed = float(config.filament_stamping_loading_speed.get_at(idx)); + m_filpar[idx].filament_stamping_distance = float(config.filament_stamping_distance.get_at(idx)); } m_filpar[idx].filament_area = float((M_PI/4.f) * pow(config.filament_diameter.get_at(idx), 2)); // all extruders are assumed to have the same filament diameter at this point @@ -719,7 +758,7 @@ std::vector WipeTower::prime( toolchange_Wipe(writer, cleaning_box , 20.f); box_coordinates box = cleaning_box; box.translate(0.f, writer.y() - cleaning_box.ld.y() + m_perimeter_width); - toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[tools[idx_tool + 1]].first_layer_temperature); + toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[m_current_tool].first_layer_temperature, m_filpar[tools[idx_tool + 1]].first_layer_temperature); cleaning_box.translate(prime_section_width, 0.f); writer.travel(cleaning_box.ld, 7200); } @@ -766,7 +805,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) for (const auto &b : m_layer_info->tool_changes) if ( b.new_tool == tool ) { wipe_volume = b.wipe_volume; - wipe_area = b.required_depth * m_layer_info->extra_spacing; + wipe_area = b.required_depth; break; } } @@ -807,14 +846,15 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. if (tool != (unsigned int)-1){ // This is not the last change. toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, - is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature); + (is_first_layer() ? m_filpar[m_current_tool].first_layer_temperature : m_filpar[m_current_tool].temperature), + (is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature)); toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials. toolchange_Load(writer, cleaning_box); writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area. ++ m_num_tool_changes; } else - toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature); + toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature, m_filpar[m_current_tool].temperature); m_depth_traversed += wipe_area; @@ -841,13 +881,14 @@ void WipeTower::toolchange_Unload( WipeTowerWriter &writer, const box_coordinates &cleaning_box, const std::string& current_material, + const int old_temperature, const int new_temperature) { float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width; float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width; const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness - const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm + const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing_ramming; // spacing between lines in mm const Vec2f ramming_start_pos = Vec2f(xl, cleaning_box.ld.y() + m_depth_traversed + y_step/2.f); @@ -860,10 +901,14 @@ void WipeTower::toolchange_Unload( float e_done = 0; // measures E move done from each segment const bool do_ramming = m_semm || m_filpar[m_current_tool].multitool_ramming; + const bool cold_ramming = m_is_mk4mmu3; if (do_ramming) { writer.travel(ramming_start_pos); // move to starting position + if (! m_is_mk4mmu3) writer.disable_linear_advance(); + if (cold_ramming) + writer.set_extruder_temp(old_temperature - 20); } else writer.set_position(ramming_start_pos); @@ -884,7 +929,7 @@ void WipeTower::toolchange_Unload( if (tch.old_tool == m_current_tool) { sum_of_depths += tch.ramming_depth; float ramming_end_y = sum_of_depths; - ramming_end_y -= (y_step/m_extra_spacing-m_perimeter_width) / 2.f; // center of final ramming line + ramming_end_y -= (y_step/m_extra_spacing_ramming-m_perimeter_width) / 2.f; // center of final ramming line if ( (m_current_shape == SHAPE_REVERSED && ramming_end_y < sparse_beginning_y - 0.5f*m_perimeter_width ) || (m_current_shape == SHAPE_NORMAL && ramming_end_y > sparse_beginning_y + 0.5f*m_perimeter_width ) ) @@ -898,6 +943,10 @@ void WipeTower::toolchange_Unload( } } + if (m_is_mk4mmu3) { + writer.switch_filament_monitoring(false); + writer.wait(1.5f); + } // now the ramming itself: while (do_ramming && i < m_filpar[m_current_tool].ramming_speed.size()) @@ -938,31 +987,66 @@ void WipeTower::toolchange_Unload( .retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f) .resume_preview(); } + const int& number_of_cooling_moves = m_filpar[m_current_tool].cooling_moves; + const bool cooling_will_happen = m_semm && number_of_cooling_moves > 0; + bool change_temp_later = false; // Wipe tower should only change temperature with single extruder MM. Otherwise, all temperatures should // be already set and there is no need to change anything. Also, the temperature could be changed // for wrong extruder. if (m_semm) { - if (new_temperature != 0 && (new_temperature != m_old_temperature || is_first_layer()) ) { // Set the extruder temperature, but don't wait. + if (new_temperature != 0 && (new_temperature != m_old_temperature || is_first_layer() || cold_ramming) ) { // Set the extruder temperature, but don't wait. // If the required temperature is the same as last time, don't emit the M104 again (if user adjusted the value, it would be reset) // However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off). + if (cold_ramming && cooling_will_happen) + change_temp_later = true; + else writer.set_extruder_temp(new_temperature, false); m_old_temperature = new_temperature; } } // Cooling: - const int& number_of_moves = m_filpar[m_current_tool].cooling_moves; - if (m_semm && number_of_moves > 0) { + if (cooling_will_happen) { const float& initial_speed = m_filpar[m_current_tool].cooling_initial_speed; const float& final_speed = m_filpar[m_current_tool].cooling_final_speed; - float speed_inc = (final_speed - initial_speed) / (2.f * number_of_moves - 1.f); + float speed_inc = (final_speed - initial_speed) / (2.f * number_of_cooling_moves - 1.f); + + if (m_is_mk4mmu3) + writer.disable_linear_advance(); writer.suppress_preview() .travel(writer.x(), writer.y() + y_step); old_x = writer.x(); turning_point = xr-old_x > old_x-xl ? xr : xl; - for (int i=0; i0 && m_filpar[m_current_tool].filament_stamping_distance != 0) { + + // Stamping turning point shall be no farther than 20mm from the current nozzle position: + float stamping_turning_point = std::clamp(old_x + 20.f * (turning_point - old_x > 0.f ? 1.f : -1.f), xl, xr); + + // Only last 5mm will be done with the fast x travel. The point is to spread possible blobs + // along the whole wipe tower. + if (stamping_dist_e > 5) { + float cent = writer.x(); + writer.load_move_x_advanced(stamping_turning_point, (stamping_dist_e - 5), m_filpar[m_current_tool].filament_stamping_loading_speed, 200); + writer.load_move_x_advanced(cent, 5, m_filpar[m_current_tool].filament_stamping_loading_speed, m_travel_speed); + writer.travel(cent, writer.y()); + } else + writer.load_move_x_advanced_there_and_back(stamping_turning_point, stamping_dist_e, m_filpar[m_current_tool].filament_stamping_loading_speed, m_travel_speed); + + // Retract while the print head is stationary, so if there is a blob, it is not dragged along. + writer.retract(stamping_dist_e, m_filpar[m_current_tool].unloading_speed * 60.f); + } + + if (i == number_of_cooling_moves - 1 && change_temp_later) { + // If cold_ramming, the temperature change should be done before the last cooling move. + writer.set_extruder_temp(new_temperature, false); + } float speed = initial_speed + speed_inc * 2*i; writer.load_move_x_advanced(turning_point, m_cooling_tube_length, speed); speed += speed_inc; @@ -979,7 +1063,7 @@ void WipeTower::toolchange_Unload( // this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start: // the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material - Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width); + Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing_ramming-m_perimeter_width) / 2.f + m_perimeter_width); if (do_ramming) writer.travel(pos, 2400.f); else @@ -1004,6 +1088,8 @@ void WipeTower::toolchange_Change( //writer.append("[end_filament_gcode]\n"); writer.append("[toolchange_gcode_from_wipe_tower_generator]\n"); + if (m_is_mk4mmu3) + writer.switch_filament_monitoring(true); // Travel to where we assume we are. Custom toolchange or some special T code handling (parking extruder etc) // gcode could have left the extruder somewhere, we cannot just start extruding. We should also inform the // postprocessor that we absolutely want to have this in the gcode, even if it thought it is the same as before. @@ -1064,20 +1150,23 @@ void WipeTower::toolchange_Wipe( const float& xl = cleaning_box.ld.x(); const float& xr = cleaning_box.rd.x(); + writer.set_extrusion_flow(m_extrusion_flow * m_extra_flow); + const float line_width = m_perimeter_width * m_extra_flow; + writer.change_analyzer_line_width(line_width); // Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least // the ordered volume, even if it means violating the box. This can later be removed and simply // wipe until the end of the assigned area. - float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) * (is_first_layer() ? m_extra_spacing : 1.f); - float dy = (is_first_layer() ? 1.f : m_extra_spacing) * m_perimeter_width; // Don't use the extra spacing for the first layer. + float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) / m_extra_flow; + float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width; // Don't use the extra spacing for the first layer, but do use the spacing resulting from increased flow. // All the calculations in all other places take the spacing into account for all the layers. const float target_speed = is_first_layer() ? m_first_layer_speed * 60.f : m_infill_speed * 60.f; float wipe_speed = 0.33f * target_speed; - // if there is less than 2.5*m_perimeter_width to the edge, advance straightaway (there is likely a blob anyway) - if ((m_left_to_right ? xr-writer.x() : writer.x()-xl) < 2.5f*m_perimeter_width) { - writer.travel((m_left_to_right ? xr-m_perimeter_width : xl+m_perimeter_width),writer.y()+dy); + // if there is less than 2.5*line_width to the edge, advance straightaway (there is likely a blob anyway) + if ((m_left_to_right ? xr-writer.x() : writer.x()-xl) < 2.5f*line_width) { + writer.travel((m_left_to_right ? xr-line_width : xl+line_width),writer.y()+dy); m_left_to_right = !m_left_to_right; } @@ -1092,21 +1181,21 @@ void WipeTower::toolchange_Wipe( float traversed_x = writer.x(); if (m_left_to_right) - writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5f*m_perimeter_width), writer.y(), wipe_speed); + writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5f*line_width), writer.y(), wipe_speed); else - writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5f*m_perimeter_width), writer.y(), wipe_speed); + writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5f*line_width), writer.y(), wipe_speed); - if (writer.y()+float(EPSILON) > cleaning_box.lu.y()-0.5f*m_perimeter_width) + if (writer.y()+float(EPSILON) > cleaning_box.lu.y()-0.5f*line_width) break; // in case next line would not fit traversed_x -= writer.x(); x_to_wipe -= std::abs(traversed_x); if (x_to_wipe < WT_EPSILON) { - writer.travel(m_left_to_right ? xl + 1.5f*m_perimeter_width : xr - 1.5f*m_perimeter_width, writer.y(), 7200); + writer.travel(m_left_to_right ? xl + 1.5f*line_width : xr - 1.5f*line_width, writer.y(), 7200); break; } // stepping to the next line: - writer.extrude(writer.x() + (i % 4 == 0 ? -1.f : (i % 4 == 1 ? 1.f : 0.f)) * 1.5f*m_perimeter_width, writer.y() + dy); + writer.extrude(writer.x() + (i % 4 == 0 ? -1.f : (i % 4 == 1 ? 1.f : 0.f)) * 1.5f*line_width, writer.y() + dy); m_left_to_right = !m_left_to_right; } @@ -1120,6 +1209,7 @@ void WipeTower::toolchange_Wipe( m_left_to_right = !m_left_to_right; writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow. + writer.change_analyzer_line_width(m_perimeter_width); } @@ -1399,9 +1489,19 @@ std::vector> WipeTower::extract_wipe_volumes(const PrintConfi // Extract purging volumes for each extruder pair: std::vector> wipe_volumes; const unsigned int number_of_extruders = (unsigned int)(sqrt(wiping_matrix.size())+EPSILON); - for (unsigned int i = 0; i(wiping_matrix.begin()+i*number_of_extruders, wiping_matrix.begin()+(i+1)*number_of_extruders)); + // For SEMM printers, the project can be configured to use defaults from configuration, + // in which case the custom matrix shall be ignored. We will overwrite the values. + if (config.single_extruder_multi_material && ! config.wiping_volumes_use_custom_matrix) { + for (size_t i = 0; i < number_of_extruders; ++i) { + for (size_t j = 0; j < number_of_extruders; ++j) { + if (i != j) + wipe_volumes[i][j] = (i == j ? 0.f : config.multimaterial_purging.value * config.filament_purge_multiplier.get_at(j) / 100.f); + } + } + } // Also include filament_minimal_purge_on_wipe_tower. This is needed for the preview. for (unsigned int i = 0; i> WipeTower::extract_wipe_volumes(const PrintConfi return wipe_volumes; } +static float get_wipe_depth(float volume, float layer_height, float perimeter_width, float extra_flow, float extra_spacing, float width) +{ + float length_to_extrude = (volume_to_length(volume, perimeter_width, layer_height)) / extra_flow; + length_to_extrude = std::max(length_to_extrude,0.f); + + return (int(length_to_extrude / width) + 1) * perimeter_width * extra_spacing; +} // Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume) @@ -1426,22 +1533,17 @@ void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned in return; // this is an actual toolchange - let's calculate depth to reserve on the wipe tower - float depth = 0.f; float width = m_wipe_tower_width - 3*m_perimeter_width; float length_to_extrude = volume_to_length(0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f), m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator, layer_height_par); - depth = (int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator); - float ramming_depth = depth; - length_to_extrude = width*((length_to_extrude / width)-int(length_to_extrude / width)) - width; - float first_wipe_line = -length_to_extrude; - length_to_extrude += volume_to_length(wipe_volume, m_perimeter_width, layer_height_par); - length_to_extrude = std::max(length_to_extrude,0.f); + float ramming_depth = (int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator) * m_extra_spacing_ramming; + float first_wipe_line = - (width*((length_to_extrude / width)-int(length_to_extrude / width)) - width); - depth += (int(length_to_extrude / width) + 1) * m_perimeter_width; - depth *= m_extra_spacing; + float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height_par); + float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width); - m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, depth, ramming_depth, first_wipe_line, wipe_volume)); + m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume)); } @@ -1491,14 +1593,14 @@ void WipeTower::save_on_last_wipe() if (i == idx) { float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into - float length_to_save = finish_layer().total_extrusion_length_in_plane(); - float length_to_wipe = volume_to_length(toolchange.wipe_volume, - m_perimeter_width, m_layer_info->height) - toolchange.first_wipe_line - length_to_save; - length_to_wipe = std::max(length_to_wipe,0.f); - float depth_to_wipe = m_perimeter_width * (std::floor(length_to_wipe/width) + ( length_to_wipe > 0.f ? 1.f : 0.f ) ); + float volume_to_save = length_to_volume(finish_layer().total_extrusion_length_in_plane(), m_perimeter_width, m_layer_info->height); + float volume_left_to_wipe = std::max(m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower, toolchange.wipe_volume_total - volume_to_save); + float volume_we_need_depth_for = std::max(0.f, volume_left_to_wipe - length_to_volume(toolchange.first_wipe_line, m_perimeter_width*m_extra_flow, m_layer_info->height)); + float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width); - toolchange.required_depth = (toolchange.ramming_depth + depth_to_wipe) * m_extra_spacing; + toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe; + toolchange.wipe_volume = volume_left_to_wipe; } } } diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index f75acf8..2afdc54 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -1,6 +1,5 @@ -#ifndef WipeTower_ -#define WipeTower_ - +#ifndef slic3r_GCode_WipeTower_hpp_ +#define slic3r_GCode_WipeTower_hpp_ #include #include #include @@ -232,6 +231,8 @@ public: float unloading_speed = 0.f; float unloading_speed_start = 0.f; float delay = 0.f ; + float filament_stamping_loading_speed = 0.f; + float filament_stamping_distance = 0.f; int cooling_moves = 0; float cooling_initial_speed = 0.f; float cooling_final_speed = 0.f; @@ -243,6 +244,7 @@ public: float filament_area; bool multitool_ramming; float multitool_ramming_time = 0.f; + float filament_minimal_purge_on_wipe_tower = 0.f; }; private: @@ -260,6 +262,7 @@ private: bool m_semm = true; // Are we using a single extruder multimaterial printer? + bool m_is_mk4mmu3 = false; Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm. float m_wipe_tower_width; // Width of the wipe tower. float m_wipe_tower_depth = 0.f; // Depth of the wipe tower @@ -309,8 +312,6 @@ private: // State of the wipe tower generator. unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics. unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics. - ///unsigned int m_idx_tool_change_in_layer = 0; // Layer change counter in this layer. Counting up to m_max_color_changes. - bool m_print_brim = true; // A fill-in direction (positive Y, negative Y) alternates with each layer. wipe_shape m_current_shape = SHAPE_NORMAL; size_t m_current_tool = 0; @@ -319,7 +320,9 @@ private: float m_depth_traversed = 0.f; // Current y position at the wipe tower. bool m_current_layer_finished = false; bool m_left_to_right = true; - float m_extra_spacing = 1.f; + float m_extra_flow = 1.f; + float m_extra_spacing_wipe = 1.f; + float m_extra_spacing_ramming = 1.f; bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; } @@ -331,16 +334,10 @@ private: return layer_height * ( m_perimeter_width - layer_height * (1.f-float(M_PI)/4.f)) / filament_area(); } - // Calculates length of extrusion line to extrude given volume - float volume_to_length(float volume, float line_width, float layer_height) const { - return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f)))); - } // Calculates depth for all layers and propagates them downwards void plan_tower(); - // Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental - void make_wipe_tower_square(); // Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe void save_on_last_wipe(); @@ -355,19 +352,19 @@ private: float ramming_depth; float first_wipe_line; float wipe_volume; + float wipe_volume_total; ToolChange(size_t old, size_t newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f) - : old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth}, first_wipe_line{fwl}, wipe_volume{wv} {} + : old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth}, first_wipe_line{fwl}, wipe_volume{wv}, wipe_volume_total{wv} {} }; float z; // z position of the layer float height; // layer height float depth; // depth of the layer based on all layers above - float extra_spacing; float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; } std::vector tool_changes; WipeTowerInfo(float z_par, float layer_height_par) - : z{z_par}, height{layer_height_par}, depth{0}, extra_spacing{1.f} {} + : z{z_par}, height{layer_height_par}, depth{0} {} }; std::vector m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...)) @@ -390,6 +387,7 @@ private: WipeTowerWriter &writer, const box_coordinates &cleaning_box, const std::string& current_material, + const int old_temperature, const int new_temperature); void toolchange_Change( @@ -412,4 +410,4 @@ private: } // namespace Slic3r -#endif // WipeTowerQIDIMM_hpp_ +#endif // slic3r_GCode_WipeTower_hpp_ diff --git a/src/libslic3r/GCode/WipeTowerIntegration.cpp b/src/libslic3r/GCode/WipeTowerIntegration.cpp index 2021d2b..add8b3c 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.cpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.cpp @@ -58,13 +58,14 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip || will_go_down); // don't dig into the print if (should_travel_to_tower) { const Point xy_point = wipe_tower_point_to_object_point(gcodegen, start_pos); + gcode += gcodegen.m_label_objects.maybe_stop_instance(); gcode += gcodegen.retract_and_wipe(); - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; const std::string comment{"Travel to a Wipe Tower"}; if (gcodegen.m_current_layer_first_position) { if (gcodegen.last_position) { gcode += gcodegen.travel_to( - *gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment + *gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment, [](){return "";} ); } else { gcode += gcodegen.writer().travel_to_xy(gcodegen.point_to_gcode(xy_point), comment); @@ -72,7 +73,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip } } else { const Vec3crd point = to_3d(xy_point, scaled(z)); - gcode += gcodegen.travel_to_first_position(point, current_z); + gcode += gcodegen.travel_to_first_position(point, current_z, ExtrusionRole::Mixed, [](){return "";}); } gcode += gcodegen.unretract(); } else { @@ -93,12 +94,10 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines. toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z if (gcodegen.config().wipe_tower) { - if (tcr.priming) { - const double return_to_z{tcr.print_z + gcodegen.config().z_offset.value}; - deretraction_str += gcodegen.writer().get_travel_to_z_gcode(return_to_z, "set priming layer Z"); - } else { - deretraction_str += gcodegen.writer().travel_to_z(z, "restore layer Z"); - } + deretraction_str += gcodegen.writer().get_travel_to_z_gcode(z, "restore layer Z"); + Vec3d position{gcodegen.writer().get_position()}; + position.z() = z; + gcodegen.writer().update_position(position); deretraction_str += gcodegen.unretract(); } @@ -111,7 +110,10 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip boost::replace_first(tcr_rotated_gcode, "[deretraction_from_wipe_tower_generator]", deretraction_str); std::string tcr_gcode; unescape_string_cstyle(tcr_rotated_gcode, tcr_gcode); + if (gcodegen.config().default_acceleration > 0) + gcode += gcodegen.writer().set_print_acceleration(fast_round_up(gcodegen.config().wipe_tower_acceleration.value)); gcode += tcr_gcode; + gcode += gcodegen.writer().set_print_acceleration(fast_round_up(gcodegen.config().default_acceleration.value)); // A phony move to the end position at the wipe tower. gcodegen.writer().travel_to_xy(end_pos.cast()); @@ -136,7 +138,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip } // Let the planner know we are traveling between objects. - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; return gcode; } @@ -262,10 +264,11 @@ std::string WipeTowerIntegration::tool_change(GCodeGenerator &gcodegen, int extr std::string WipeTowerIntegration::finalize(GCodeGenerator &gcodegen) { std::string gcode; - if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_z) > EPSILON) + const double purge_z{m_final_purge.print_z + gcodegen.config().z_offset.value}; + if (std::abs(gcodegen.writer().get_position().z() - purge_z) > EPSILON) gcode += gcodegen.generate_travel_gcode( - {{gcodegen.last_position->x(), gcodegen.last_position->y(), scaled(m_final_purge.print_z)}}, - "move to safe place for purging" + {{gcodegen.last_position->x(), gcodegen.last_position->y(), scaled(purge_z)}}, + "move to safe place for purging", [](){return "";} ); gcode += append_tcr(gcodegen, m_final_purge, -1); return gcode; diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index db93720..9c9d2f7 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -620,8 +620,6 @@ void Layer::make_perimeters() std::vector layer_region_ids; std::vector> perimeter_and_gapfill_ranges; ExPolygons fill_expolygons; - //w21 - ExPolygons fill_no_overlap_expolygons; std::vector fill_expolygons_ranges; SurfacesPtr surfaces_to_merge; SurfacesPtr surfaces_to_merge_temp; @@ -652,8 +650,6 @@ void Layer::make_perimeters() fill_expolygons.clear(); fill_expolygons_ranges.clear(); surfaces_to_merge.clear(); - //w21 - //fill_no_overlap_expolygons.clear(); // find compatible regions layer_region_ids.clear(); @@ -694,8 +690,7 @@ void Layer::make_perimeters() } if (layer_region_ids.size() == 1) { // optimization - //w21 - (*layerm)->make_perimeters((*layerm)->slices(), perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges,(*layerm)->fill_no_overlap_expolygons); + (*layerm)->make_perimeters((*layerm)->slices(), perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges); this->sort_perimeters_into_islands((*layerm)->slices(), region_id, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids); } else { SurfaceCollection new_slices; @@ -730,24 +725,8 @@ void Layer::make_perimeters() } } // make perimeters - //w21 - ExPolygons fill_no_overlap; - layerm_config->make_perimeters(new_slices, perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges,fill_no_overlap); + layerm_config->make_perimeters(new_slices, perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges); this->sort_perimeters_into_islands(new_slices, region_id_config, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids); - //w21 - if (!new_slices.surfaces.empty()) { - for (size_t idx : layer_region_ids) { - // Separate the fill surfaces. - LayerRegion &layer = *m_regions[idx]; - ExPolygons expp = intersection_ex(new_slices.surfaces, layer.slices().surfaces); - layer.m_fill_expolygons = std::move(expp); - if (!layer.m_fill_expolygons.empty()) { - layer.m_fill_surfaces.set(std::move(layer.m_fill_expolygons), layer.slices().surfaces.front()); - layer.fill_no_overlap_expolygons = intersection_ex(layer.slices().surfaces, fill_no_overlap); - } - - } - } } } } diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 46eb346..dd0212c 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -7,6 +7,7 @@ #include "Flow.hpp" #include "SurfaceCollection.hpp" #include "ExtrusionEntityCollection.hpp" +#include "LayerRegion.hpp" #include @@ -29,41 +30,6 @@ namespace FillLightning { class Generator; }; -// Range of indices, providing support for range based loops. -template -class IndexRange -{ -public: - IndexRange(T ibegin, T iend) : m_begin(ibegin), m_end(iend) {} - IndexRange() = default; - - // Just a bare minimum functionality iterator required by range-for loop. - class Iterator { - public: - T operator*() const { return m_idx; } - bool operator!=(const Iterator &rhs) const { return m_idx != rhs.m_idx; } - void operator++() { ++ m_idx; } - private: - friend class IndexRange; - Iterator(T idx) : m_idx(idx) {} - T m_idx; - }; - - Iterator begin() const { assert(m_begin <= m_end); return Iterator(m_begin); }; - Iterator end() const { assert(m_begin <= m_end); return Iterator(m_end); }; - - bool empty() const { assert(m_begin <= m_end); return m_begin >= m_end; } - T size() const { assert(m_begin <= m_end); return m_end - m_begin; } - -private: - // Index of the first extrusion in LayerRegion. - T m_begin { 0 }; - // Index of the last extrusion in LayerRegion. - T m_end { 0 }; -}; - -using ExtrusionRange = IndexRange; -using ExPolygonRange = IndexRange; // Range of extrusions, referencing the source region by an index. class LayerExtrusionRange : public ExtrusionRange @@ -91,150 +57,6 @@ using LayerExtrusionRanges = std::vector; #endif // NDEBUG -class LayerRegion -{ -public: - [[nodiscard]] Layer* layer() { return m_layer; } - [[nodiscard]] const Layer* layer() const { return m_layer; } - [[nodiscard]] const PrintRegion& region() const { return *m_region; } - - // collection of surfaces generated by slicing the original geometry - // divided by type top/bottom/internal - [[nodiscard]] const SurfaceCollection& slices() const { return m_slices; } - - // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") - // and for re-starting of infills. - [[nodiscard]] const ExPolygons& fill_expolygons() const { return m_fill_expolygons; } - // and their bounding boxes - [[nodiscard]] const BoundingBoxes& fill_expolygons_bboxes() const { return m_fill_expolygons_bboxes; } - // Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands. - // Not used for a plain single material print with no infill modifiers. - [[nodiscard]] const ExPolygons& fill_expolygons_composite() const { return m_fill_expolygons_composite; } - // and their bounding boxes - [[nodiscard]] const BoundingBoxes& fill_expolygons_composite_bboxes() const { return m_fill_expolygons_composite_bboxes; } - - // collection of surfaces generated by slicing the original geometry - // divided by type top/bottom/internal - [[nodiscard]] const SurfaceCollection& fill_surfaces() const { return m_fill_surfaces; } - - // collection of extrusion paths/loops filling gaps - // These fills are generated by the perimeter generator. - // They are not printed on their own, but they are copied to this->fills during infill generation. - [[nodiscard]] const ExtrusionEntityCollection& thin_fills() const { return m_thin_fills; } - - // collection of polylines representing the unsupported bridge edges - [[nodiscard]] const Polylines& unsupported_bridge_edges() const { return m_unsupported_bridge_edges; } - - // ordered collection of extrusion paths/loops to build all perimeters - // (this collection contains only ExtrusionEntityCollection objects) - [[nodiscard]] const ExtrusionEntityCollection& perimeters() const { return m_perimeters; } - - // ordered collection of extrusion paths to fill surfaces - // (this collection contains only ExtrusionEntityCollection objects) - [[nodiscard]] const ExtrusionEntityCollection& fills() const { return m_fills; } - //w21 - ExPolygons fill_no_overlap_expolygons; - - Flow flow(FlowRole role) const; - Flow flow(FlowRole role, double layer_height) const; - Flow bridging_flow(FlowRole role, bool force_thick_bridges = false) const; - - void slices_to_fill_surfaces_clipped(); - void prepare_fill_surfaces(); - // Produce perimeter extrusions, gap fill extrusions and fill polygons for input slices. - void make_perimeters( - // Input slices for which the perimeters, gap fills and fill expolygons are to be generated. - const SurfaceCollection &slices, - // Ranges of perimeter extrusions and gap fill extrusions per suface, referencing - // newly created extrusions stored at this LayerRegion. - std::vector> &perimeter_and_gapfill_ranges, - // All fill areas produced for all input slices above. - ExPolygons &fill_expolygons, - // Ranges of fill areas above per input slice. - std::vector &fill_expolygons_ranges, - //w21 - ExPolygons &fill_no_overlap_expolygons); - void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered); - double infill_area_threshold() const; - // Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer. - void trim_surfaces(const Polygons &trimming_polygons); - // Single elephant foot compensation step, used by the elephant foor compensation at the 1st layer. - // Trim surfaces by trimming polygons (shrunk by an elephant foot compensation step), but don't shrink narrow parts so much that no perimeter would fit. - void elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons); - - void export_region_slices_to_svg(const char *path) const; - void export_region_fill_surfaces_to_svg(const char *path) const; - // Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. - void export_region_slices_to_svg_debug(const char *name) const; - void export_region_fill_surfaces_to_svg_debug(const char *name) const; - - // Is there any valid extrusion assigned to this LayerRegion? - bool has_extrusions() const { return ! this->perimeters().empty() || ! this->fills().empty(); } - -protected: - friend class Layer; - friend class PrintObject; - - LayerRegion(Layer *layer, const PrintRegion *region) : m_layer(layer), m_region(region) {} - ~LayerRegion() = default; - -private: - // Modifying m_slices - friend std::string fix_slicing_errors(LayerPtrs&, const std::function&); - template - friend void apply_mm_segmentation(PrintObject& print_object, ThrowOnCancel throw_on_cancel); - - Layer *m_layer; - const PrintRegion *m_region; - - // Backed up slices before they are split into top/bottom/internal. - // Only backed up for multi-region layers or layers with elephant foot compensation. - //FIXME Review whether not to simplify the code by keeping the raw_slices all the time. - ExPolygons m_raw_slices; - -//FIXME make m_slices public for unit tests -public: - // collection of surfaces generated by slicing the original geometry - // divided by type top/bottom/internal - SurfaceCollection m_slices; - -private: - // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") - // and for re-starting of infills. - ExPolygons m_fill_expolygons; - // and their bounding boxes - BoundingBoxes m_fill_expolygons_bboxes; - // Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands. - // Not used for a plain single material print with no infill modifiers. - ExPolygons m_fill_expolygons_composite; - // and their bounding boxes - BoundingBoxes m_fill_expolygons_composite_bboxes; - - // Collection of surfaces for infill generation, created by splitting m_slices by m_fill_expolygons. - SurfaceCollection m_fill_surfaces; - - // Collection of extrusion paths/loops filling gaps - // These fills are generated by the perimeter generator. - // They are not printed on their own, but they are copied to this->fills during infill generation. - ExtrusionEntityCollection m_thin_fills; - - // collection of polylines representing the unsupported bridge edges - Polylines m_unsupported_bridge_edges; - - // ordered collection of extrusion paths/loops to build all perimeters - // (this collection contains only ExtrusionEntityCollection objects) - ExtrusionEntityCollection m_perimeters; - - // ordered collection of extrusion paths to fill surfaces - // (this collection contains only ExtrusionEntityCollection objects) - ExtrusionEntityCollection m_fills; - - // collection of expolygons representing the bridged areas (thus not - // needing support material) -// Polygons bridged; - //w21 - ExPolygons m_fill_no_overlap_expolygons; -}; // LayerSlice contains one or more LayerIsland objects, // each LayerIsland containing a set of perimeter extrusions extruded with one particular PrintRegionConfig parameters diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index c296b01..53ac003 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -73,9 +73,7 @@ void LayerRegion::make_perimeters( // All fill areas produced for all input slices above. ExPolygons &fill_expolygons, // Ranges of fill areas above per input slice. - std::vector &fill_expolygons_ranges, - //w21 - ExPolygons &fill_no_overlap_expolygons) + std::vector &fill_expolygons_ranges) { m_perimeters.clear(); m_thin_fills.clear(); @@ -108,11 +106,8 @@ void LayerRegion::make_perimeters( // Cummulative sum of polygons over all the regions. const ExPolygons *lower_slices = this->layer()->lower_layer ? &this->layer()->lower_layer->lslices : nullptr; - //w16 - const ExPolygons *upper_slices = this->layer()->upper_layer ? &this->layer()->upper_layer->lslices : nullptr; // Cache for offsetted lower_slices Polygons lower_layer_polygons_cache; - Polygons upper_layer_polygons_cache; for (const Surface &surface : slices) { auto perimeters_begin = uint32_t(m_perimeters.size()); @@ -120,37 +115,16 @@ void LayerRegion::make_perimeters( auto fill_expolygons_begin = uint32_t(fill_expolygons.size()); if (this->layer()->object()->config().perimeter_generator.value == PerimeterGeneratorType::Arachne && !spiral_vase) - //w16 - if (this->layer()->object()->config().top_one_wall_type == TopOneWallType::Alltop) - PerimeterGenerator::process_with_one_wall_arachne( - // input: - params, - surface, - lower_slices, - //w16 - upper_slices, - lower_layer_polygons_cache, - upper_layer_polygons_cache, - // output: - m_perimeters, - m_thin_fills, - fill_expolygons, - //w21 - fill_no_overlap_expolygons); - else PerimeterGenerator::process_arachne( // input: params, surface, lower_slices, - upper_slices, lower_layer_polygons_cache, // output: m_perimeters, m_thin_fills, - fill_expolygons, - //w21 - fill_no_overlap_expolygons); + fill_expolygons); else PerimeterGenerator::process_classic( @@ -158,16 +132,11 @@ void LayerRegion::make_perimeters( params, surface, lower_slices, - //w16 - upper_slices, lower_layer_polygons_cache, - upper_layer_polygons_cache, // output: m_perimeters, m_thin_fills, - fill_expolygons, - //w21 - fill_no_overlap_expolygons); + fill_expolygons); perimeter_and_gapfill_ranges.emplace_back( ExtrusionRange{ perimeters_begin, uint32_t(m_perimeters.size()) }, ExtrusionRange{ gap_fills_begin, uint32_t(m_thin_fills.size()) }); @@ -197,61 +166,17 @@ static ExPolygons fill_surfaces_extract_expolygons(Surfaces &surfaces, std::init return out; } -// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params, -// detect bridges. -// Trim "shells" by the expanded bridges. -Surfaces expand_bridges_detect_orientations( - Surfaces &surfaces, - ExPolygons &shells, - const Algorithm::RegionExpansionParameters &expansion_params_into_solid_infill, - ExPolygons &sparse, - const Algorithm::RegionExpansionParameters &expansion_params_into_sparse_infill, - const float closing_radius) -{ - using namespace Slic3r::Algorithm; - - double thickness; - ExPolygons bridges_ex = fill_surfaces_extract_expolygons(surfaces, {stBottomBridge}, thickness); - if (bridges_ex.empty()) - return {}; - - // Calculate bridge anchors and their expansions in their respective shell region. - WaveSeeds bridge_anchors = wave_seeds(bridges_ex, shells, expansion_params_into_solid_infill.tiny_expansion, true); - std::vector bridge_expansions = propagate_waves_ex(bridge_anchors, shells, expansion_params_into_solid_infill); - bool expanded_into_shells = ! bridge_expansions.empty(); - bool expanded_into_sparse = false; - { - WaveSeeds bridge_anchors_sparse = wave_seeds(bridges_ex, sparse, expansion_params_into_sparse_infill.tiny_expansion, true); - std::vector bridge_expansions_sparse = propagate_waves_ex(bridge_anchors_sparse, sparse, expansion_params_into_sparse_infill); - if (! bridge_expansions_sparse.empty()) { - expanded_into_sparse = true; - for (WaveSeed &seed : bridge_anchors_sparse) - seed.boundary += uint32_t(shells.size()); - for (RegionExpansionEx &expansion : bridge_expansions_sparse) - expansion.boundary_id += uint32_t(shells.size()); - append(bridge_anchors, std::move(bridge_anchors_sparse)); - append(bridge_expansions, std::move(bridge_expansions_sparse)); - } - } // Cache for detecting bridge orientation and merging regions with overlapping expansions. struct Bridge { ExPolygon expolygon; uint32_t group_id; - std::vector::const_iterator bridge_expansion_begin; - double angle = -1; + std::vector::const_iterator bridge_expansion_begin; + std::optional angle{std::nullopt}; }; - std::vector bridges; - { - bridges.reserve(bridges_ex.size()); - uint32_t group_id = 0; - for (ExPolygon &ex : bridges_ex) - bridges.push_back({ std::move(ex), group_id ++, bridge_expansions.end() }); - bridges_ex.clear(); - } // Group the bridge surfaces by overlaps. - auto group_id = [&bridges](uint32_t src_id) { +uint32_t group_id(std::vector &bridges, uint32_t src_id) { uint32_t group_id = bridges[src_id].group_id; while (group_id != src_id) { src_id = group_id; @@ -261,107 +186,148 @@ Surfaces expand_bridges_detect_orientations( return group_id; }; +std::vector get_grouped_bridges( + ExPolygons&& bridge_expolygons, + const std::vector& bridge_expansions +) { + using namespace Algorithm; + + std::vector result; { - // Cache of bboxes per expansion boundary. - std::vector bboxes; + result.reserve(bridge_expansions.size()); + uint32_t group_id = 0; + using std::move_iterator; + for (ExPolygon& expolygon : bridge_expolygons) + result.push_back({ std::move(expolygon), group_id ++, bridge_expansions.end() }); + } // Detect overlaps of bridge anchors inside their respective shell regions. // bridge_expansions are sorted by boundary id and source id. - for (auto it = bridge_expansions.begin(); it != bridge_expansions.end();) { - // For each boundary region: - auto it_begin = it; - auto it_end = std::next(it_begin); - for (; it_end != bridge_expansions.end() && it_end->boundary_id == it_begin->boundary_id; ++ it_end) ; - bboxes.clear(); - bboxes.reserve(it_end - it_begin); - for (auto it2 = it_begin; it2 != it_end; ++ it2) - bboxes.emplace_back(get_extents(it2->expolygon.contour)); + for (auto expansion_iterator = bridge_expansions.begin(); expansion_iterator != bridge_expansions.end();) { + auto boundary_region_begin = expansion_iterator; + auto boundary_region_end = std::find_if( + next(expansion_iterator), + bridge_expansions.end(), + [&](const RegionExpansionEx& expansion){ + return expansion.boundary_id != expansion_iterator->boundary_id; + } + ); + + // Cache of bboxes per expansion boundary. + std::vector bounding_boxes; + bounding_boxes.reserve(std::distance(boundary_region_begin, boundary_region_end)); + std::transform( + boundary_region_begin, + boundary_region_end, + std::back_inserter(bounding_boxes), + [](const RegionExpansionEx& expansion){ + return get_extents(expansion.expolygon.contour); + } + ); // For each bridge anchor of the current source: - for (; it != it_end; ++ it) { - // A grup id for this bridge. - for (auto it2 = std::next(it); it2 != it_end; ++ it2) - if (it->src_id != it2->src_id && - bboxes[it - it_begin].overlap(bboxes[it2 - it_begin]) && + for (;expansion_iterator != boundary_region_end; ++expansion_iterator) { + auto candidate_iterator = std::next(expansion_iterator); + for (;candidate_iterator != boundary_region_end; ++candidate_iterator) { + const BoundingBox& current_bounding_box{ + bounding_boxes[expansion_iterator - boundary_region_begin] + }; + const BoundingBox& candidate_bounding_box{ + bounding_boxes[candidate_iterator - boundary_region_begin] + }; + if ( + expansion_iterator->src_id != candidate_iterator->src_id + && current_bounding_box.overlap(candidate_bounding_box) // One may ignore holes, they are irrelevant for intersection test. - ! intersection(it->expolygon.contour, it2->expolygon.contour).empty()) { + && !intersection(expansion_iterator->expolygon.contour, candidate_iterator->expolygon.contour).empty() + ) { // The two bridge regions intersect. Give them the same (lower) group id. - uint32_t id = group_id(it->src_id); - uint32_t id2 = group_id(it2->src_id); + uint32_t id = group_id(result, expansion_iterator->src_id); + uint32_t id2 = group_id(result, candidate_iterator->src_id); if (id < id2) - bridges[id2].group_id = id; + result[id2].group_id = id; else - bridges[id].group_id = id2; + result[id].group_id = id2; } } } } + return result; +} - // Detect bridge directions. - { - std::sort(bridge_anchors.begin(), bridge_anchors.end(), Algorithm::lower_by_src_and_boundary); +void detect_bridge_directions( + const Algorithm::WaveSeeds& bridge_anchors, + std::vector& bridges, + const std::vector& expansion_zones +) { + if (expansion_zones.empty()) { + throw std::runtime_error("At least one expansion zone must exist!"); + } auto it_bridge_anchor = bridge_anchors.begin(); - Lines lines; - Polygons anchor_areas; for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) { Bridge &bridge = bridges[bridge_id]; -// lines.clear(); - anchor_areas.clear(); + Polygons anchor_areas; int32_t last_anchor_id = -1; for (; it_bridge_anchor != bridge_anchors.end() && it_bridge_anchor->src == bridge_id; ++ it_bridge_anchor) { if (last_anchor_id != int(it_bridge_anchor->boundary)) { last_anchor_id = int(it_bridge_anchor->boundary); - append(anchor_areas, to_polygons(last_anchor_id < int32_t(shells.size()) ? shells[last_anchor_id] : sparse[last_anchor_id - int32_t(shells.size())])); + unsigned start_index{}; + unsigned end_index{}; + for (const ExpansionZone& expansion_zone: expansion_zones) { + end_index += expansion_zone.expolygons.size(); + if (last_anchor_id < static_cast(end_index)) { + append(anchor_areas, to_polygons(expansion_zone.expolygons[last_anchor_id - start_index])); + break; + } + start_index += expansion_zone.expolygons.size(); } -// if (Points &polyline = it_bridge_anchor->path; polyline.size() >= 2) { -// reserve_more_power_of_2(lines, polyline.size() - 1); -// for (size_t i = 1; i < polyline.size(); ++ i) -// lines.push_back({ polyline[i - 1], polyline[1] }); -// } } - lines = to_lines(diff_pl(to_polylines(bridge.expolygon), expand(anchor_areas, float(SCALED_EPSILON)))); + } + Lines lines{to_lines(diff_pl(to_polylines(bridge.expolygon), expand(anchor_areas, float(SCALED_EPSILON))))}; auto [bridging_dir, unsupported_dist] = detect_bridging_direction(lines, to_polygons(bridge.expolygon)); bridge.angle = M_PI + std::atan2(bridging_dir.y(), bridging_dir.x()); -#if 0 + if constexpr (false) { coordf_t stroke_width = scale_(0.06); BoundingBox bbox = get_extents(anchor_areas); bbox.merge(get_extents(bridge.expolygon)); bbox.offset(scale_(1.)); ::Slic3r::SVG - svg(debug_out_path(("bridge" + std::to_string(bridge.angle) + "_" /* + std::to_string(this->layer()->bottom_z())*/).c_str()), + svg(debug_out_path(("bridge" + std::to_string(*bridge.angle) + "_" /* + std::to_string(this->layer()->bottom_z())*/).c_str()), bbox); svg.draw(bridge.expolygon, "cyan"); svg.draw(lines, "green", stroke_width); svg.draw(anchor_areas, "red"); -#endif + } } } - // Merge the groups with the same group id, produce surfaces by merging source overhangs with their newly expanded anchors. - Surfaces out; - { - Polygons acc; - Surface templ{ stBottomBridge, {} }; - std::sort(bridge_expansions.begin(), bridge_expansions.end(), [](auto &l, auto &r) { - return l.src_id < r.src_id || (l.src_id == r.src_id && l.boundary_id < r.boundary_id); - }); +Surfaces merge_bridges( + std::vector& bridges, + const std::vector& bridge_expansions, + const float closing_radius +) { for (auto it = bridge_expansions.begin(); it != bridge_expansions.end(); ) { bridges[it->src_id].bridge_expansion_begin = it; uint32_t src_id = it->src_id; for (++ it; it != bridge_expansions.end() && it->src_id == src_id; ++ it) ; } - for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) - if (group_id(bridge_id) == bridge_id) { + Surfaces result; + for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) { + if (group_id(bridges, bridge_id) == bridge_id) { // Head of the group. - acc.clear(); + Polygons acc; for (uint32_t bridge_id2 = bridge_id; bridge_id2 < uint32_t(bridges.size()); ++ bridge_id2) - if (group_id(bridge_id2) == bridge_id) { + if (group_id(bridges, bridge_id2) == bridge_id) { append(acc, to_polygons(std::move(bridges[bridge_id2].expolygon))); auto it_bridge_expansion = bridges[bridge_id2].bridge_expansion_begin; assert(it_bridge_expansion == bridge_expansions.end() || it_bridge_expansion->src_id == bridge_id2); for (; it_bridge_expansion != bridge_expansions.end() && it_bridge_expansion->src_id == bridge_id2; ++ it_bridge_expansion) - append(acc, to_polygons(std::move(it_bridge_expansion->expolygon))); + append(acc, to_polygons(it_bridge_expansion->expolygon)); } //FIXME try to be smart and pick the best bridging angle for all? - templ.bridge_angle = bridges[bridge_id].angle; + if (!bridges[bridge_id].angle) { + assert(false && "Bridge angle must be pre-calculated!"); + } + Surface templ{ stBottomBridge, {} }; + templ.bridge_angle = bridges[bridge_id].angle ? *bridges[bridge_id].angle : -1; //NOTE: The current regularization of the shells can create small unasigned regions in the object (E.G. benchy) // without the following closing operation, those regions will stay unfilled and cause small holes in the expanded surface. // look for narrow_ensure_vertical_wall_thickness_region_radius filter. @@ -369,29 +335,105 @@ Surfaces expand_bridges_detect_orientations( // without safety offset, artifacts are generated (GH #2494) // union_safety_offset_ex(acc) for (ExPolygon &ex : final) - out.emplace_back(templ, std::move(ex)); + result.emplace_back(templ, std::move(ex)); } } + return result; +} + +struct ExpansionResult { + Algorithm::WaveSeeds anchors; + std::vector expansions; +}; + +ExpansionResult expand_expolygons( + const ExPolygons& expolygons, + std::vector& expansion_zones +) { + using namespace Algorithm; + WaveSeeds bridge_anchors; + std::vector bridge_expansions; + + unsigned processed_bridges_count = 0; + for (ExpansionZone& expansion_zone : expansion_zones) { + WaveSeeds seeds{wave_seeds( + expolygons, + expansion_zone.expolygons, + expansion_zone.parameters.tiny_expansion, + true + )}; + std::vector expansions{propagate_waves_ex( + seeds, + expansion_zone.expolygons, + expansion_zone.parameters + )}; + + for (WaveSeed &seed : seeds) + seed.boundary += processed_bridges_count; + for (RegionExpansionEx &expansion : expansions) + expansion.boundary_id += processed_bridges_count; + + expansion_zone.expanded_into = ! expansions.empty(); + + append(bridge_anchors, std::move(seeds)); + append(bridge_expansions, std::move(expansions)); + + processed_bridges_count += expansion_zone.expolygons.size(); + } + return {bridge_anchors, bridge_expansions}; +} + +// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params, +// detect bridges. +// Trim "shells" by the expanded bridges. +Surfaces expand_bridges_detect_orientations( + Surfaces &surfaces, + std::vector& expansion_zones, + const float closing_radius +) +{ + using namespace Slic3r::Algorithm; + + double thickness; + ExPolygons bridge_expolygons = fill_surfaces_extract_expolygons(surfaces, {stBottomBridge}, thickness); + if (bridge_expolygons.empty()) + return {}; + + // Calculate bridge anchors and their expansions in their respective shell region. + ExpansionResult expansion_result{expand_expolygons( + bridge_expolygons, + expansion_zones + )}; + + std::vector bridges{get_grouped_bridges( + std::move(bridge_expolygons), + expansion_result.expansions + )}; + bridge_expolygons.clear(); + + std::sort(expansion_result.anchors.begin(), expansion_result.anchors.end(), Algorithm::lower_by_src_and_boundary); + detect_bridge_directions(expansion_result.anchors, bridges, expansion_zones); + + // Merge the groups with the same group id, produce surfaces by merging source overhangs with their newly expanded anchors. + std::sort(expansion_result.expansions.begin(), expansion_result.expansions.end(), [](auto &l, auto &r) { + return l.src_id < r.src_id || (l.src_id == r.src_id && l.boundary_id < r.boundary_id); + }); + Surfaces out{merge_bridges(bridges, expansion_result.expansions, closing_radius)}; // Clip by the expanded bridges. - if (expanded_into_shells) - shells = diff_ex(shells, out); - if (expanded_into_sparse) - sparse = diff_ex(sparse, out); + for (ExpansionZone& expansion_zone : expansion_zones) + if (expansion_zone.expanded_into) + expansion_zone.expolygons = diff_ex(expansion_zone.expolygons, out); return out; } -// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params. -// Trim "shells" by the expanded bridges. -static Surfaces expand_merge_surfaces( +Surfaces expand_merge_surfaces( Surfaces &surfaces, SurfaceType surface_type, - ExPolygons &shells, - const Algorithm::RegionExpansionParameters &expansion_params_into_solid_infill, - ExPolygons &sparse, - const Algorithm::RegionExpansionParameters &expansion_params_into_sparse_infill, + std::vector& expansion_zones, const float closing_radius, - const double bridge_angle = -1.) + const double bridge_angle +) { using namespace Slic3r::Algorithm; @@ -400,17 +442,17 @@ static Surfaces expand_merge_surfaces( if (src.empty()) return {}; - std::vector expansions = propagate_waves(src, shells, expansion_params_into_solid_infill); - bool expanded_into_shells = !expansions.empty(); - bool expanded_into_sparse = false; - { - std::vector expansions2 = propagate_waves(src, sparse, expansion_params_into_sparse_infill); - if (! expansions2.empty()) { - expanded_into_sparse = true; - for (RegionExpansion &expansion : expansions2) - expansion.boundary_id += uint32_t(shells.size()); - append(expansions, std::move(expansions2)); - } + unsigned processed_expolygons_count = 0; + std::vector expansions; + for (ExpansionZone& expansion_zone : expansion_zones) { + std::vector zone_expansions = propagate_waves(src, expansion_zone.expolygons, expansion_zone.parameters); + expansion_zone.expanded_into = !zone_expansions.empty(); + + for (RegionExpansion &expansion : zone_expansions) + expansion.boundary_id += processed_expolygons_count; + + processed_expolygons_count += expansion_zone.expolygons.size(); + append(expansions, std::move(zone_expansions)); } std::vector expanded = merge_expansions_into_expolygons(std::move(src), std::move(expansions)); @@ -418,11 +460,10 @@ static Surfaces expand_merge_surfaces( // without the following closing operation, those regions will stay unfilled and cause small holes in the expanded surface. // look for narrow_ensure_vertical_wall_thickness_region_radius filter. expanded = closing_ex(expanded, closing_radius); - // Trim the shells by the expanded expolygons. - if (expanded_into_shells) - shells = diff_ex(shells, expanded); - if (expanded_into_sparse) - sparse = diff_ex(sparse, expanded); + // Trim the zones by the expanded expolygons. + for (ExpansionZone& expansion_zone : expansion_zones) + if (expansion_zone.expanded_into) + expansion_zone.expolygons = diff_ex(expansion_zone.expolygons, expanded); Surface templ{ surface_type, {} }; templ.bridge_angle = bridge_angle; @@ -471,16 +512,23 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly double layer_thickness; ExPolygons shells = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, { stInternalSolid }, layer_thickness)); ExPolygons sparse = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, { stInternal }, layer_thickness)); + ExPolygons top_expolygons = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, { stTop }, layer_thickness)); + const auto expansion_params_into_sparse_infill = RegionExpansionParameters::build(expansion_min, expansion_step, max_nr_expansion_steps); + const auto expansion_params_into_solid_infill = RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps); + + std::vector expansion_zones{ + ExpansionZone{std::move(shells), expansion_params_into_solid_infill}, + ExpansionZone{std::move(sparse), expansion_params_into_sparse_infill}, + ExpansionZone{std::move(top_expolygons), expansion_params_into_solid_infill}, + }; SurfaceCollection bridges; - const auto expansion_params_into_sparse_infill = RegionExpansionParameters::build(expansion_min, expansion_step, max_nr_expansion_steps); { BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z; const double custom_angle = this->region().config().bridge_angle.value; - const auto expansion_params_into_solid_infill = RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps); bridges.surfaces = custom_angle > 0 ? - expand_merge_surfaces(m_fill_surfaces.surfaces, stBottomBridge, shells, expansion_params_into_solid_infill, sparse, expansion_params_into_sparse_infill, closing_radius, Geometry::deg2rad(custom_angle)) : - expand_bridges_detect_orientations(m_fill_surfaces.surfaces, shells, expansion_params_into_solid_infill, sparse, expansion_params_into_sparse_infill, closing_radius); + expand_merge_surfaces(m_fill_surfaces.surfaces, stBottomBridge, expansion_zones, closing_radius, Geometry::deg2rad(custom_angle)) : + expand_bridges_detect_orientations(m_fill_surfaces.surfaces, expansion_zones, closing_radius); BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done"; #if 0 { @@ -490,25 +538,36 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly #endif } - Surfaces bottoms = expand_merge_surfaces(m_fill_surfaces.surfaces, stBottom, shells, - RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps), - sparse, expansion_params_into_sparse_infill, closing_radius); - Surfaces tops = expand_merge_surfaces(m_fill_surfaces.surfaces, stTop, shells, - RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps), - sparse, expansion_params_into_sparse_infill, closing_radius); + m_fill_surfaces.remove_types({ stTop }); + { + Surface top_templ(stTop, {}); + top_templ.thickness = layer_thickness; + m_fill_surfaces.append(std::move(expansion_zones.back().expolygons), top_templ); + } + + expansion_zones.pop_back(); + + expansion_zones.at(0).parameters = RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps); + Surfaces bottoms = expand_merge_surfaces(m_fill_surfaces.surfaces, stBottom, expansion_zones, closing_radius); + + expansion_zones.at(0).parameters = RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps); + Surfaces tops = expand_merge_surfaces(m_fill_surfaces.surfaces, stTop, expansion_zones, closing_radius); // m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternal, stInternalSolid }); m_fill_surfaces.clear(); - reserve_more(m_fill_surfaces.surfaces, shells.size() + sparse.size() + bridges.size() + bottoms.size() + tops.size()); + unsigned zones_expolygons_count = 0; + for (const ExpansionZone& zone : expansion_zones) + zones_expolygons_count += zone.expolygons.size(); + reserve_more(m_fill_surfaces.surfaces, zones_expolygons_count + bridges.size() + bottoms.size() + tops.size()); { Surface solid_templ(stInternalSolid, {}); solid_templ.thickness = layer_thickness; - m_fill_surfaces.append(std::move(shells), solid_templ); + m_fill_surfaces.append(std::move(expansion_zones[0].expolygons), solid_templ); } { Surface sparse_templ(stInternal, {}); sparse_templ.thickness = layer_thickness; - m_fill_surfaces.append(std::move(sparse), sparse_templ); + m_fill_surfaces.append(std::move(expansion_zones[1].expolygons), sparse_templ); } m_fill_surfaces.append(std::move(bridges.surfaces)); m_fill_surfaces.append(std::move(bottoms)); diff --git a/src/libslic3r/LayerRegion.hpp b/src/libslic3r/LayerRegion.hpp new file mode 100644 index 0000000..fa8bb8d --- /dev/null +++ b/src/libslic3r/LayerRegion.hpp @@ -0,0 +1,224 @@ +#ifndef slic3r_LayerRegion_hpp_ +#define slic3r_LayerRegion_hpp_ + +#include "BoundingBox.hpp" +#include "ExtrusionEntityCollection.hpp" +#include "SurfaceCollection.hpp" +#include "libslic3r/Algorithm/RegionExpansion.hpp" + + +namespace Slic3r { + +class Layer; +using LayerPtrs = std::vector; +class PrintRegion; + +// Range of indices, providing support for range based loops. +template +class IndexRange +{ +public: + IndexRange(T ibegin, T iend) : m_begin(ibegin), m_end(iend) {} + IndexRange() = default; + + // Just a bare minimum functionality iterator required by range-for loop. + class Iterator { + public: + T operator*() const { return m_idx; } + bool operator!=(const Iterator &rhs) const { return m_idx != rhs.m_idx; } + void operator++() { ++ m_idx; } + private: + friend class IndexRange; + Iterator(T idx) : m_idx(idx) {} + T m_idx; + }; + + Iterator begin() const { assert(m_begin <= m_end); return Iterator(m_begin); }; + Iterator end() const { assert(m_begin <= m_end); return Iterator(m_end); }; + + bool empty() const { assert(m_begin <= m_end); return m_begin >= m_end; } + T size() const { assert(m_begin <= m_end); return m_end - m_begin; } + +private: + // Index of the first extrusion in LayerRegion. + T m_begin { 0 }; + // Index of the last extrusion in LayerRegion. + T m_end { 0 }; +}; + +template class IndexRange; + +using ExtrusionRange = IndexRange; +using ExPolygonRange = IndexRange; + +class LayerRegion +{ +public: + [[nodiscard]] Layer* layer() { return m_layer; } + [[nodiscard]] const Layer* layer() const { return m_layer; } + [[nodiscard]] const PrintRegion& region() const { return *m_region; } + + // collection of surfaces generated by slicing the original geometry + // divided by type top/bottom/internal + [[nodiscard]] const SurfaceCollection& slices() const { return m_slices; } + + // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") + // and for re-starting of infills. + [[nodiscard]] const ExPolygons& fill_expolygons() const { return m_fill_expolygons; } + // and their bounding boxes + [[nodiscard]] const BoundingBoxes& fill_expolygons_bboxes() const { return m_fill_expolygons_bboxes; } + // Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands. + // Not used for a plain single material print with no infill modifiers. + [[nodiscard]] const ExPolygons& fill_expolygons_composite() const { return m_fill_expolygons_composite; } + // and their bounding boxes + [[nodiscard]] const BoundingBoxes& fill_expolygons_composite_bboxes() const { return m_fill_expolygons_composite_bboxes; } + + // collection of surfaces generated by slicing the original geometry + // divided by type top/bottom/internal + [[nodiscard]] const SurfaceCollection& fill_surfaces() const { return m_fill_surfaces; } + + // collection of extrusion paths/loops filling gaps + // These fills are generated by the perimeter generator. + // They are not printed on their own, but they are copied to this->fills during infill generation. + [[nodiscard]] const ExtrusionEntityCollection& thin_fills() const { return m_thin_fills; } + + // collection of polylines representing the unsupported bridge edges + [[nodiscard]] const Polylines& unsupported_bridge_edges() const { return m_unsupported_bridge_edges; } + + // ordered collection of extrusion paths/loops to build all perimeters + // (this collection contains only ExtrusionEntityCollection objects) + [[nodiscard]] const ExtrusionEntityCollection& perimeters() const { return m_perimeters; } + + // ordered collection of extrusion paths to fill surfaces + // (this collection contains only ExtrusionEntityCollection objects) + [[nodiscard]] const ExtrusionEntityCollection& fills() const { return m_fills; } + + Flow flow(FlowRole role) const; + Flow flow(FlowRole role, double layer_height) const; + Flow bridging_flow(FlowRole role, bool force_thick_bridges = false) const; + + void slices_to_fill_surfaces_clipped(); + void prepare_fill_surfaces(); + // Produce perimeter extrusions, gap fill extrusions and fill polygons for input slices. + void make_perimeters( + // Input slices for which the perimeters, gap fills and fill expolygons are to be generated. + const SurfaceCollection &slices, + // Ranges of perimeter extrusions and gap fill extrusions per suface, referencing + // newly created extrusions stored at this LayerRegion. + std::vector> &perimeter_and_gapfill_ranges, + // All fill areas produced for all input slices above. + ExPolygons &fill_expolygons, + // Ranges of fill areas above per input slice. + std::vector &fill_expolygons_ranges); + void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered); + double infill_area_threshold() const; + // Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer. + void trim_surfaces(const Polygons &trimming_polygons); + // Single elephant foot compensation step, used by the elephant foor compensation at the 1st layer. + // Trim surfaces by trimming polygons (shrunk by an elephant foot compensation step), but don't shrink narrow parts so much that no perimeter would fit. + void elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons); + + void export_region_slices_to_svg(const char *path) const; + void export_region_fill_surfaces_to_svg(const char *path) const; + // Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. + void export_region_slices_to_svg_debug(const char *name) const; + void export_region_fill_surfaces_to_svg_debug(const char *name) const; + + // Is there any valid extrusion assigned to this LayerRegion? + bool has_extrusions() const { return ! this->perimeters().empty() || ! this->fills().empty(); } + +protected: + friend class Layer; + friend class PrintObject; + + LayerRegion(Layer *layer, const PrintRegion *region) : m_layer(layer), m_region(region) {} + ~LayerRegion() = default; + +private: + // Modifying m_slices + friend std::string fix_slicing_errors(LayerPtrs&, const std::function&); + template + friend void apply_mm_segmentation(PrintObject& print_object, ThrowOnCancel throw_on_cancel); + + Layer *m_layer; + const PrintRegion *m_region; + + // Backed up slices before they are split into top/bottom/internal. + // Only backed up for multi-region layers or layers with elephant foot compensation. + //FIXME Review whether not to simplify the code by keeping the raw_slices all the time. + ExPolygons m_raw_slices; + +//FIXME make m_slices public for unit tests +public: + // collection of surfaces generated by slicing the original geometry + // divided by type top/bottom/internal + SurfaceCollection m_slices; + +private: + // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") + // and for re-starting of infills. + ExPolygons m_fill_expolygons; + // and their bounding boxes + BoundingBoxes m_fill_expolygons_bboxes; + // Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands. + // Not used for a plain single material print with no infill modifiers. + ExPolygons m_fill_expolygons_composite; + // and their bounding boxes + BoundingBoxes m_fill_expolygons_composite_bboxes; + + // Collection of surfaces for infill generation, created by splitting m_slices by m_fill_expolygons. + SurfaceCollection m_fill_surfaces; + + // Collection of extrusion paths/loops filling gaps + // These fills are generated by the perimeter generator. + // They are not printed on their own, but they are copied to this->fills during infill generation. + ExtrusionEntityCollection m_thin_fills; + + // collection of polylines representing the unsupported bridge edges + Polylines m_unsupported_bridge_edges; + + // ordered collection of extrusion paths/loops to build all perimeters + // (this collection contains only ExtrusionEntityCollection objects) + ExtrusionEntityCollection m_perimeters; + + // ordered collection of extrusion paths to fill surfaces + // (this collection contains only ExtrusionEntityCollection objects) + ExtrusionEntityCollection m_fills; + + // collection of expolygons representing the bridged areas (thus not + // needing support material) +// Polygons bridged; +}; + +struct ExpansionZone { + ExPolygons expolygons; + Algorithm::RegionExpansionParameters parameters; + bool expanded_into = false; +}; + +/** +* Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params, +* detect bridges. +* Trim "shells" by the expanded bridges. +*/ +Surfaces expand_bridges_detect_orientations( + Surfaces &surfaces, + std::vector& expansion_zones, + const float closing_radius +); + +/** +* Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params. +* Trim "shells" by the expanded bridges. +*/ +Surfaces expand_merge_surfaces( + Surfaces &surfaces, + SurfaceType surface_type, + std::vector& expansion_zones, + const float closing_radius, + const double bridge_angle = -1 +); + +} + +#endif // slic3r_LayerRegion_hpp_ diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 8d56348..ae98b1e 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -1069,39 +1069,6 @@ std::tuple, Polygons> generate_extra_perimeters_over return {extra_perims, diff(inset_overhang_area, inset_overhang_area_left_unfilled)}; } -//w16 -void PerimeterGenerator::add_infill_contour_for_arachne(ExPolygons infill_contour, - int loops, - coord_t ext_perimeter_spacing, - coord_t perimeter_spacing, - coord_t min_perimeter_infill_spacing, - coord_t spacing, - bool is_inner_part, - const Parameters ¶ms, - ExPolygons &infill_areas, - ExPolygons & out_fill_expolygons, - //w21 - ExPolygons & out_fill_no_overlap) -{ - if (offset_ex(infill_contour, -float(spacing / 2.)).empty()) { - infill_contour.clear(); - } - coord_t insert = (loops < 0) ? 0 : ext_perimeter_spacing; - if (is_inner_part || loops > 0) - insert = perimeter_spacing; - - insert = coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale(insert)))); - Polygons inner_pp; - for (ExPolygon &ex : infill_contour) - ex.simplify_p(params.scaled_resolution, &inner_pp); - ExPolygons inner_union = union_ex(inner_pp); - float offset1 = -min_perimeter_infill_spacing / 2.; - float offset2 = insert + min_perimeter_infill_spacing / 2.; - infill_areas = offset2_ex(inner_union, offset1, offset2); - append(out_fill_expolygons, offset2_ex(union_ex(inner_pp), float(-min_perimeter_infill_spacing / 2.), float(insert + min_perimeter_infill_spacing / 2.))); - //w21 - append(out_fill_no_overlap,offset2_ex(inner_union, float(-min_perimeter_infill_spacing / 2.), float(min_perimeter_infill_spacing / 2.))); -} // Thanks, Cura developers, for implementing an algorithm for generating perimeters with variable width (Arachne) that is based on the paper @@ -1111,7 +1078,6 @@ void PerimeterGenerator::process_arachne( const Parameters ¶ms, const Surface &surface, const ExPolygons *lower_slices, - const ExPolygons *upper_slices, // Cache: Polygons &lower_slices_polygons_cache, // Output: @@ -1120,9 +1086,7 @@ void PerimeterGenerator::process_arachne( // Gaps without the thin walls ExtrusionEntityCollection & /* out_gap_fill */, // Infills without the gap fills - ExPolygons &out_fill_expolygons, - //w21 - ExPolygons &out_fill_no_overlap) + ExPolygons &out_fill_expolygons) { // other perimeters coord_t perimeter_spacing = params.perimeter_flow.scaled_spacing(); @@ -1149,9 +1113,6 @@ void PerimeterGenerator::process_arachne( ExPolygons last = offset_ex(surface.expolygon.simplify_p(params.scaled_resolution), - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); Polygons last_p = to_polygons(last); - //w16 - if (upper_slices == nullptr && params.object_config.top_one_wall_type == TopOneWallType::Onlytopmost) - loop_number = 0; Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, params.layer_height, params.object_config, params.print_config); std::vector perimeters = wallToolPaths.getToolPaths(); @@ -1308,21 +1269,10 @@ void PerimeterGenerator::process_arachne( out_loops.append(extrusion_coll); ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour()); - //w17 - ExPolygons the_layer_surface = infill_contour; const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing; if (offset_ex(infill_contour, -float(spacing / 2.)).empty()) infill_contour.clear(); // Infill region is too small, so let's filter it out. - if (params.object_config.top_one_wall_type != TopOneWallType::Disable) { - coord_t perimeter_width = params.perimeter_flow.scaled_width(); - double min_width_top_surface = (params.object_config.top_area_threshold / 100) * - std::max(double(ext_perimeter_spacing / 4 + 10), double(perimeter_width / 4)); - infill_contour = offset2_ex(infill_contour, -min_width_top_surface, min_width_top_surface + perimeter_width); - ExPolygons surface_not_export_to_top = diff_ex(the_layer_surface, infill_contour); - } - // BBS: get real top surface - infill_contour = intersection_ex(infill_contour, the_layer_surface); // create one more offset to be used as boundary for fill // we offset by half the perimeter spacing (to get to the actual infill boundary) // and then we offset back and forth by half the infill spacing to only consider the @@ -1366,411 +1316,24 @@ void PerimeterGenerator::process_arachne( infill_areas = diff_ex(infill_areas, filled_area); } } - //w21 - append(out_fill_no_overlap, offset2_ex(union_ex(pp),float(-min_perimeter_infill_spacing / 2.), float( min_perimeter_infill_spacing / 2.))); - append(out_fill_expolygons, std::move(infill_areas)); -} -void PerimeterGenerator::process_with_one_wall_arachne( - // Inputs: - const Parameters ¶ms, - const Surface &surface, - const ExPolygons *lower_slices, - //w16 - const ExPolygons *upper_slices, - // Cache: - Polygons &lower_slices_polygons_cache, - Polygons &upper_slices_polygons_cache, - // Output: - // Loops with the external thin walls - ExtrusionEntityCollection &out_loops, - // Gaps without the thin walls - ExtrusionEntityCollection & /* out_gap_fill */, - // Infills without the gap fills - ExPolygons &out_fill_expolygons, - //w21 - ExPolygons &out_fill_no_overlap) -{ - // other perimeters - coord_t perimeter_spacing = params.perimeter_flow.scaled_spacing(); - // external perimeters - coord_t ext_perimeter_width = params.ext_perimeter_flow.scaled_width(); - coord_t ext_perimeter_spacing = params.ext_perimeter_flow.scaled_spacing(); - coord_t ext_perimeter_spacing2 = scaled(0.5f * (params.ext_perimeter_flow.spacing() + params.perimeter_flow.spacing())); - // solid infill - coord_t solid_infill_spacing = params.solid_infill_flow.scaled_spacing(); - - // prepare grown lower layer slices for overhang detection - if (params.config.overhangs && lower_slices != nullptr && lower_slices_polygons_cache.empty()) { - // We consider overhang any part where the entire nozzle diameter is not supported by the - // lower layer, so we take lower slices and offset them by half the nozzle diameter used - // in the current layer - double nozzle_diameter = params.print_config.nozzle_diameter.get_at(params.config.perimeter_extruder-1); - lower_slices_polygons_cache = offset(*lower_slices, float(scale_(+nozzle_diameter/2))); - } - if (params.config.overhangs && upper_slices != nullptr && upper_slices_polygons_cache.empty()) { - double upper_nozzle_diameter = params.print_config.nozzle_diameter.get_at(params.config.perimeter_extruder - 1); - upper_slices_polygons_cache = offset(*upper_slices, float(scale_(EPSILON))); - } - - - // we need to process each island separately because we might have different - // extra perimeters for each one - // detect how many perimeters must be generated for this island - int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops - ExPolygons last = offset_ex(surface.expolygon.simplify_p(params.scaled_resolution), - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); - Polygons last_p = to_polygons(last); - - int remain_loops = -1; - if (params.object_config.top_one_wall_type == TopOneWallType::Alltop) { - if (upper_slices != nullptr) - remain_loops = loop_number - 1; - - loop_number = 0; - } - - Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, params.layer_height, params.object_config, params.print_config); - std::vector perimeters = wallToolPaths.getToolPaths(); - loop_number = int(perimeters.size()) - 1; - - - - //w16 - ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour()); - ExPolygons inner_infill_contour; - - if (remain_loops >= 0) { - - ExPolygons the_layer_surface = infill_contour; - BoundingBox infill_contour_box = get_extents(infill_contour); - infill_contour_box.offset(SCALED_EPSILON); - Polygons upper_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(upper_slices_polygons_cache, - infill_contour_box); - - infill_contour = diff_ex(infill_contour, upper_polygons_series_clipped); - - coord_t perimeter_width = params.perimeter_flow.scaled_width(); - if (lower_slices != nullptr) { - BoundingBox infill_contour_box = get_extents(infill_contour); - infill_contour_box.offset(SCALED_EPSILON); - Polygons lower_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_slices_polygons_cache, - infill_contour_box); - - ExPolygons bridge_area = offset_ex(diff_ex(infill_contour, lower_polygons_series_clipped), - std::max(ext_perimeter_spacing, perimeter_width)); - infill_contour = diff_ex(infill_contour, bridge_area); - } - //w17 - // double min_width_top_surface = std::max(double(ext_perimeter_spacing / 4 + 10), double(perimeter_width / 4)); - double min_width_top_surface = (params.object_config.top_area_threshold / 100) * std::max(double(ext_perimeter_spacing / 4 + 10), double(perimeter_width / 4)); - infill_contour = offset2_ex(infill_contour, -min_width_top_surface, min_width_top_surface + perimeter_width); - - ExPolygons surface_not_export_to_top = diff_ex(the_layer_surface, infill_contour); - - infill_contour = intersection_ex(infill_contour, the_layer_surface); - Polygons surface_not_export_to_top_p = to_polygons(surface_not_export_to_top); - Arachne::WallToolPaths innerWallToolPaths(surface_not_export_to_top_p, perimeter_spacing, perimeter_spacing, - coord_t(remain_loops + 1), 0, params.layer_height, params.object_config, params.print_config); - - std::vector perimeters_inner = innerWallToolPaths.getToolPaths(); - remain_loops = int(perimeters_inner.size()) - 1; - if (!perimeters.empty()) { - for (int perimeter_idx = 0; perimeter_idx < perimeters_inner.size(); perimeter_idx++) { - if (perimeters_inner[perimeter_idx].empty()) - continue; - - for (Arachne::ExtrusionLine &wall : perimeters_inner[perimeter_idx]) { - wall.inset_idx++; - } - } - } - perimeters.insert(perimeters.end(), perimeters_inner.begin(), perimeters_inner.end()); - - inner_infill_contour = union_ex(innerWallToolPaths.getInnerContour()); - } - -#ifdef ARACHNE_DEBUG - { - static int iRun = 0; - export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour())); - } -#endif - - // All closed ExtrusionLine should have the same the first and the last point. - // But in rare cases, Arachne produce ExtrusionLine marked as closed but without - // equal the first and the last point. - assert([&perimeters = std::as_const(perimeters)]() -> bool { - for (const Arachne::VariableWidthLines &perimeter : perimeters) - for (const Arachne::ExtrusionLine &el : perimeter) - if (el.is_closed && el.junctions.front().p != el.junctions.back().p) - return false; - return true; - }()); - - int start_perimeter = int(perimeters.size()) - 1; - int end_perimeter = -1; - int direction = -1; - - if (params.config.external_perimeters_first) { - start_perimeter = 0; - end_perimeter = int(perimeters.size()); - direction = 1; - } - - std::vector all_extrusions; - for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) { - if (perimeters[perimeter_idx].empty()) - continue; - for (Arachne::ExtrusionLine &wall : perimeters[perimeter_idx]) - all_extrusions.emplace_back(&wall); - } - - // Find topological order with constraints from extrusions_constrains. - std::vector blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion. - std::vector> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion. - ankerl::unordered_dense::map map_extrusion_to_idx; - for (size_t idx = 0; idx < all_extrusions.size(); idx++) - map_extrusion_to_idx.emplace(all_extrusions[idx], idx); - - Arachne::WallToolPaths::ExtrusionLineSet extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, params.config.external_perimeters_first); - for (auto [before, after] : extrusions_constrains) { - auto after_it = map_extrusion_to_idx.find(after); - ++blocked[after_it->second]; - blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second); - } - - std::vector processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed. - Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position. - std::vector ordered_extrusions; // To store our result in. At the end we'll std::swap. - ordered_extrusions.reserve(all_extrusions.size()); - - while (ordered_extrusions.size() < all_extrusions.size()) { - size_t best_candidate = 0; - double best_distance_sqr = std::numeric_limits::max(); - bool is_best_closed = false; - - std::vector available_candidates; - for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) { - if (processed[candidate] || blocked[candidate]) - continue; // Not a valid candidate. - available_candidates.push_back(candidate); - } - - std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool { - return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed; - }); - - for (const size_t candidate_path_idx : available_candidates) { - auto& path = all_extrusions[candidate_path_idx]; - - if (path->junctions.empty()) { // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end. - if (best_distance_sqr == std::numeric_limits::max()) { - best_candidate = candidate_path_idx; - is_best_closed = path->is_closed; - } - continue; - } - - const Point candidate_position = path->junctions.front().p; - double distance_sqr = (current_position - candidate_position).cast().norm(); - if (distance_sqr < best_distance_sqr) { // Closer than the best candidate so far. - if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits::max()) || (!path->is_closed && !is_best_closed)) { - best_candidate = candidate_path_idx; - best_distance_sqr = distance_sqr; - is_best_closed = path->is_closed; - } - } - } - - auto &best_path = all_extrusions[best_candidate]; - ordered_extrusions.push_back({best_path, best_path->is_contour(), false}); - processed[best_candidate] = true; - for (size_t unlocked_idx : blocking[best_candidate]) - blocked[unlocked_idx]--; - - if (!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then. - if(best_path->is_closed) - current_position = best_path->junctions[0].p; //We end where we started. - else - current_position = best_path->junctions.back().p; //Pick the other end from where we started. - } - } - - if (params.layer_id > 0 && params.config.fuzzy_skin != FuzzySkinType::None) { - std::vector closed_loop_extrusions; - for (PerimeterGeneratorArachneExtrusion &extrusion : ordered_extrusions) - if (extrusion.extrusion->inset_idx == 0) { - if (extrusion.extrusion->is_closed && params.config.fuzzy_skin == FuzzySkinType::External) { - closed_loop_extrusions.emplace_back(&extrusion); - } else { - extrusion.fuzzify = true; - } - } - - if (params.config.fuzzy_skin == FuzzySkinType::External) { - ClipperLib_Z::Paths loops_paths; - loops_paths.reserve(closed_loop_extrusions.size()); - for (const auto &cl_extrusion : closed_loop_extrusions) { - assert(cl_extrusion->extrusion->junctions.front() == cl_extrusion->extrusion->junctions.back()); - size_t loop_idx = &cl_extrusion - &closed_loop_extrusions.front(); - ClipperLib_Z::Path loop_path; - loop_path.reserve(cl_extrusion->extrusion->junctions.size() - 1); - for (auto junction_it = cl_extrusion->extrusion->junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion->junctions.end()); ++junction_it) - loop_path.emplace_back(junction_it->p.x(), junction_it->p.y(), loop_idx); - loops_paths.emplace_back(loop_path); - } - - ClipperLib_Z::Clipper clipper; - clipper.AddPaths(loops_paths, ClipperLib_Z::ptSubject, true); - ClipperLib_Z::PolyTree loops_polytree; - clipper.Execute(ClipperLib_Z::ctUnion, loops_polytree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd); - - for (const ClipperLib_Z::PolyNode *child_node : loops_polytree.Childs) { - // The whole contour must have the same index. - coord_t polygon_idx = child_node->Contour.front().z(); - bool has_same_idx = std::all_of(child_node->Contour.begin(), child_node->Contour.end(), - [&polygon_idx](const ClipperLib_Z::IntPoint &point) -> bool { return polygon_idx == point.z(); }); - if (has_same_idx) - closed_loop_extrusions[polygon_idx]->fuzzify = true; - } - } - } - - if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(params, lower_slices_polygons_cache, ordered_extrusions); !extrusion_coll.empty()) - out_loops.append(extrusion_coll); - - - //w16 - if (remain_loops >= 0) { - const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing; - if (offset_ex(infill_contour, -float(spacing / 2.)).empty()) - infill_contour.clear(); - coord_t inset = (loop_number < 0) ? 0 : - (loop_number == 0) ? - // one loop - ext_perimeter_spacing : - // two or more loops? - perimeter_spacing; - - inset = coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale(inset)))); - Polygons pp; - for (ExPolygon &ex : infill_contour) - ex.simplify_p(params.scaled_resolution, &pp); - // collapse too narrow infill areas - const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); - // append infill areas to fill_surfaces - ExPolygons infill_areas = offset2_ex(union_ex(pp), float(-min_perimeter_infill_spacing / 2.), - float(inset + min_perimeter_infill_spacing / 2.)); - // ExPolygons infill_areas; - - if (lower_slices != nullptr && params.config.overhangs && params.config.extra_perimeters_on_overhangs && - params.config.perimeters > 0 && params.layer_id > params.object_config.raft_layers) { - // Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material - auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas, lower_slices_polygons_cache, - loop_number + 1, params.overhang_flow, - params.scaled_resolution, params.object_config, - params.print_config); - if (!extra_perimeters.empty()) { - ExtrusionEntityCollection &this_islands_perimeters = static_cast(*out_loops.entities.back()); - ExtrusionEntitiesPtr old_entities; - old_entities.swap(this_islands_perimeters.entities); - for (ExtrusionPaths &paths : extra_perimeters) - this_islands_perimeters.append(std::move(paths)); - append(this_islands_perimeters.entities, old_entities); - infill_areas = diff_ex(infill_areas, filled_area); - } - } - - inset = (loop_number < 0) ? 0 : - (loop_number == 0) ? - // one loop - ext_perimeter_spacing : - // two or more loops? - perimeter_spacing; - - inset = coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale(inset)))); - for (ExPolygon &ex : infill_contour) - ex.simplify_p(params.scaled_resolution, &pp); - // collapse too narrow infill areas - - - if (remain_loops >= 0) { - //w21 - add_infill_contour_for_arachne(infill_contour, loop_number, ext_perimeter_spacing, perimeter_spacing, - min_perimeter_infill_spacing, spacing, true, params, infill_areas, out_fill_expolygons,out_fill_no_overlap); - } - - - if (remain_loops >= 0) { - if (!inner_infill_contour.empty()) - //w21 - add_infill_contour_for_arachne(inner_infill_contour, remain_loops, ext_perimeter_spacing, perimeter_spacing, - min_perimeter_infill_spacing, spacing, true, params, infill_areas, out_fill_expolygons,out_fill_no_overlap); - } - //w21 - append(out_fill_no_overlap, - offset2_ex(union_ex(pp), float(-min_perimeter_infill_spacing / 2.), float(min_perimeter_infill_spacing / 2.))); - append(out_fill_expolygons, std::move(infill_areas)); - } else { - infill_contour = union_ex(wallToolPaths.getInnerContour()); - const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing; - if (offset_ex(infill_contour, -float(spacing / 2.)).empty()) - infill_contour.clear(); // Infill region is too small, so let's filter it out. - - coord_t inset = (loop_number < 0) ? 0 : (loop_number == 0) ? ext_perimeter_spacing : perimeter_spacing; - - inset = coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale(inset)))); - Polygons pp; - for (ExPolygon &ex : infill_contour) - ex.simplify_p(params.scaled_resolution, &pp); - // collapse too narrow infill areas - const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); - // append infill areas to fill_surfaces - ExPolygons infill_areas = offset2_ex(union_ex(pp), float(-min_perimeter_infill_spacing / 2.), - float(inset + min_perimeter_infill_spacing / 2.)); - - if (lower_slices != nullptr && params.config.overhangs && params.config.extra_perimeters_on_overhangs && - params.config.perimeters > 0 && params.layer_id > params.object_config.raft_layers) { - // Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material - auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas, lower_slices_polygons_cache, - loop_number + 1, params.overhang_flow, - params.scaled_resolution, params.object_config, - params.print_config); - if (!extra_perimeters.empty()) { - ExtrusionEntityCollection &this_islands_perimeters = static_cast(*out_loops.entities.back()); - ExtrusionEntitiesPtr old_entities; - old_entities.swap(this_islands_perimeters.entities); - for (ExtrusionPaths &paths : extra_perimeters) - this_islands_perimeters.append(std::move(paths)); - append(this_islands_perimeters.entities, old_entities); - infill_areas = diff_ex(infill_areas, filled_area); - } - } - //w21 - append(out_fill_no_overlap,offset2_ex(union_ex(pp), float(-min_perimeter_infill_spacing / 2.), float(min_perimeter_infill_spacing / 2.))); append(out_fill_expolygons, std::move(infill_areas)); } -} void PerimeterGenerator::process_classic( // Inputs: const Parameters ¶ms, const Surface &surface, const ExPolygons *lower_slices, - //w16 - const ExPolygons *upper_slices, // Cache: - Polygons &lower_layer_polygons_cache, - Polygons &upper_layer_polygons_cache, + Polygons &lower_slices_polygons_cache, // Output: // Loops with the external thin walls ExtrusionEntityCollection &out_loops, // Gaps without the thin walls ExtrusionEntityCollection &out_gap_fill, // Infills without the gap fills - ExPolygons &out_fill_expolygons, - //w21 - ExPolygons &out_fill_no_overlap) + ExPolygons &out_fill_expolygons) { // other perimeters coord_t perimeter_width = params.perimeter_flow.scaled_width(); @@ -1797,12 +1360,12 @@ void PerimeterGenerator::process_classic( bool has_gap_fill = params.config.gap_fill_enabled.value && params.config.gap_fill_speed.value > 0; // prepare grown lower layer slices for overhang detection - if (params.config.overhangs && lower_slices != nullptr && lower_layer_polygons_cache.empty()) { + if (params.config.overhangs && lower_slices != nullptr && lower_slices_polygons_cache.empty()) { // We consider overhang any part where the entire nozzle diameter is not supported by the // lower layer, so we take lower slices and offset them by half the nozzle diameter used // in the current layer double nozzle_diameter = params.print_config.nozzle_diameter.get_at(params.config.perimeter_extruder-1); - lower_layer_polygons_cache = offset(*lower_slices, float(scale_(+nozzle_diameter / 2))); + lower_slices_polygons_cache = offset(*lower_slices, float(scale_(+nozzle_diameter/2))); } // we need to process each island separately because we might have different @@ -1811,17 +1374,6 @@ void PerimeterGenerator::process_classic( int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops ExPolygons last = union_ex(surface.expolygon.simplify_p(params.scaled_resolution)); ExPolygons gaps; - //w16 - ExPolygons fill_clip; - ExPolygons top_fills; - //w16 - - if (params.config.overhangs && upper_slices != nullptr && upper_layer_polygons_cache.empty()) { - double upper_nozzle_diameter = params.print_config.nozzle_diameter.get_at(params.config.perimeter_extruder - 1); - upper_layer_polygons_cache = offset(*upper_slices, float(scale_(+upper_nozzle_diameter / 2))); - } - if (loop_number > 0 && params.object_config.top_one_wall_type != TopOneWallType::Disable && upper_slices == nullptr) - loop_number = 0; if (loop_number >= 0) { // In case no perimeters are to be generated, loop_number will equal to -1. std::vector contours(loop_number+1); // depth => loops @@ -1861,22 +1413,20 @@ void PerimeterGenerator::process_classic( //FIXME Is this offset correct if the line width of the inner perimeters differs // from the line width of the infill? coord_t distance = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing; - //w16 - //offsets = params.config.thin_walls ? + offsets = params.config.thin_walls ? // This path will ensure, that the perimeters do not overfill, as in - // qidi3d/Slic3r GH #32, but with the cost of rounding the perimeters + // prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters // excessively, creating gaps, which then need to be filled in by the not very // reliable gap fill algorithm. // Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than // the original. - //offset2_ex(last, - // - float(distance + min_spacing / 2. - 1.), - // float(min_spacing / 2. - 1.)) : + offset2_ex(last, + - float(distance + min_spacing / 2. - 1.), + float(min_spacing / 2. - 1.)) : // If "detect thin walls" is not enabled, this paths will be entered, which - // leads to overflows, as in qidi3d/Slic3r GH #32 - // offset_ex(last, - float(distance)); + // leads to overflows, as in prusa3d/Slic3r GH #32 + offset_ex(last, - float(distance)); // look for gaps - offsets = offset2_ex(last, -float(distance + min_spacing / 2. - 1.), float(min_spacing / 2. - 1.)); if (has_gap_fill) // not using safety offset here would "detect" very narrow gaps // (but still long enough to escape the area threshold) that gap fill @@ -1915,51 +1465,6 @@ void PerimeterGenerator::process_classic( } last = std::move(offsets); - //w16 - if (i == 0 && i != loop_number && params.object_config.top_one_wall_type == TopOneWallType::Alltop && - upper_slices != nullptr) { - coord_t offset_top_surface = scale_( - 1.5 * (params.config.perimeters.value == 0 ? - 0. : - unscaled(double(ext_perimeter_width + perimeter_spacing * int(int(params.config.perimeters.value) - int(1)))))); - if (offset_top_surface > 0.9 * (params.config.perimeters.value <= 1 ? 0. : (perimeter_spacing * (params.config.perimeters.value - 1)))) - offset_top_surface -= coord_t( - 0.9 * (params.config.perimeters.value <= 1 ? 0. : (perimeter_spacing * (params.config.perimeters.value - 1)))); - else - offset_top_surface = 0; - //w17 - double min_width_top_surface = (params.object_config.top_area_threshold/100 ) * - std::max(double(ext_perimeter_spacing / 2 + 10), 1.0 * (double(perimeter_width))); - BoundingBox last_box = get_extents(last); - last_box.offset(SCALED_EPSILON); - Polygons upper_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(upper_layer_polygons_cache, - last_box); - upper_polygons_series_clipped = offset(upper_polygons_series_clipped, min_width_top_surface); - fill_clip = offset_ex(last, -double(ext_perimeter_spacing)); - ExPolygons bridge_checker; - if (lower_slices != nullptr) { - Polygons lower_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_layer_polygons_cache, - last_box); - - double bridge_offset = std::max(double(ext_perimeter_spacing), (double(perimeter_width))); - bridge_checker = offset_ex(diff_ex(last, lower_polygons_series_clipped, ApplySafetyOffset::Yes), 1.5 * bridge_offset); - } - ExPolygons delete_bridge = diff_ex(last, bridge_checker, ApplySafetyOffset::Yes); - - ExPolygons top_polygons = diff_ex(delete_bridge, upper_polygons_series_clipped, ApplySafetyOffset::Yes); - ExPolygons temp_gap = diff_ex(top_polygons, fill_clip); - ExPolygons inner_polygons = diff_ex(last, - offset_ex(top_polygons, offset_top_surface + min_width_top_surface - - double(ext_perimeter_spacing / 2)), - ApplySafetyOffset::Yes); - top_polygons = diff_ex(fill_clip, inner_polygons, ApplySafetyOffset::Yes); - top_fills = union_ex(top_fills, top_polygons); - double infill_spacing_unscaled = params.config.infill_extrusion_width.value; // this->config->sparse_infill_line_width.value; - fill_clip = offset_ex(last, double(ext_perimeter_spacing / 2) - scale_(infill_spacing_unscaled / 2)); - last = intersection_ex(inner_polygons, last); - if (has_gap_fill) - last = union_ex(last, temp_gap); - } if (i == loop_number && (! has_gap_fill || params.config.fill_density.value == 0)) { @@ -2024,7 +1529,7 @@ void PerimeterGenerator::process_classic( } } // at this point, all loops should be in contours[0] - ExtrusionEntityCollection entities = traverse_loops_classic(params, lower_layer_polygons_cache, contours.front(), thin_walls); + ExtrusionEntityCollection entities = traverse_loops_classic(params, lower_slices_polygons_cache, contours.front(), thin_walls); // if brim will be printed, reverse the order of perimeters so that // we continue inwards after having finished the brim // TODO: add test for perimeter order @@ -2075,14 +1580,9 @@ void PerimeterGenerator::process_classic( ext_perimeter_spacing / 2 : // two or more loops? perimeter_spacing / 2; - //w21 - coord_t infill_peri_overlap = 0; // only apply infill overlap if we actually have one perimeter - //w21 - if (inset > 0) { - infill_peri_overlap = coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale( solid_infill_spacing / 2)))); - inset -= infill_peri_overlap; - } + if (inset > 0) + inset -= coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale(inset + solid_infill_spacing / 2)))); // simplify infill contours according to resolution Polygons pp; for (ExPolygon &ex : last) @@ -2096,34 +1596,12 @@ void PerimeterGenerator::process_classic( float(- inset - min_perimeter_infill_spacing / 2.), float(min_perimeter_infill_spacing / 2.)); - ExPolygons top_infill_exp = intersection_ex(fill_clip, offset_ex(top_fills, double(ext_perimeter_spacing / 2))); - //w21 - if (!top_fills.empty()) { - infill_areas = union_ex(infill_areas, offset_ex(top_infill_exp, double(infill_peri_overlap))); - } - append(out_fill_expolygons, std::move(top_infill_exp)); - //w21 - { - ExPolygons polyWithoutOverlap; - if (min_perimeter_infill_spacing / 2 > infill_peri_overlap) - polyWithoutOverlap = offset2_ex( - union_ex(pp), - float(-inset - min_perimeter_infill_spacing / 2.), - float(min_perimeter_infill_spacing / 2 - infill_peri_overlap)); - else - polyWithoutOverlap = offset_ex( - union_ex(pp), - double(-inset - infill_peri_overlap)); - if (!top_fills.empty()) - polyWithoutOverlap = union_ex(polyWithoutOverlap, top_infill_exp); - out_fill_no_overlap.insert(out_fill_no_overlap.end(), polyWithoutOverlap.begin(), polyWithoutOverlap.end()); - } if (lower_slices != nullptr && params.config.overhangs && params.config.extra_perimeters_on_overhangs && params.config.perimeters > 0 && params.layer_id > params.object_config.raft_layers) { // Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas, - lower_layer_polygons_cache, + lower_slices_polygons_cache, loop_number + 1, params.overhang_flow, params.scaled_resolution, params.object_config, params.print_config); diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index 038aabb..96ab5a1 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -70,28 +70,21 @@ void process_classic( const Parameters ¶ms, const Surface &surface, const ExPolygons *lower_slices, - //w16 - const ExPolygons *upper_slices, // Cache: - Polygons &lower_layer_polygons_cache, - Polygons &upper_layer_polygons_cache, + Polygons &lower_slices_polygons_cache, // Output: // Loops with the external thin walls ExtrusionEntityCollection &out_loops, // Gaps without the thin walls ExtrusionEntityCollection &out_gap_fill, // Infills without the gap fills - ExPolygons &out_fill_expolygons, - //w21 - ExPolygons &out_fill_no_overlap); + ExPolygons &out_fill_expolygons); void process_arachne( // Inputs: const Parameters ¶ms, const Surface & surface, const ExPolygons *lower_slices, - //w16 - const ExPolygons *upper_slices, // Cache: Polygons &lower_slices_polygons_cache, // Output: @@ -100,43 +93,7 @@ void process_arachne( // Gaps without the thin walls ExtrusionEntityCollection &out_gap_fill, // Infills without the gap fills - ExPolygons &out_fill_expolygons, - //w21 - ExPolygons &out_fill_no_overlap); - -void process_with_one_wall_arachne( - // Inputs: - const Parameters ¶ms, - const Surface &surface, - const ExPolygons *lower_slices, - //w16 - const ExPolygons *upper_slices, - // Cache: - Polygons &lower_slices_polygons_cache, - Polygons &upper_slices_polygons_cache, - // Output: - // Loops with the external thin walls - ExtrusionEntityCollection &out_loops, - // Gaps without the thin walls - ExtrusionEntityCollection &out_gap_fill, - // Infills without the gap fills - ExPolygons &out_fill_expolygons, - //w21 - ExPolygons &out_fill_no_overlap); - -//w16 -void add_infill_contour_for_arachne(ExPolygons infill_contour, - int loops, - coord_t ext_perimeter_spacing, - coord_t perimeter_spacing, - coord_t min_perimeter_infill_spacing, - coord_t spacing, - bool is_inner_part, - const Parameters ¶ms, - ExPolygons & infill_areas, - ExPolygons & out_fill_expolygons, - //w21 - ExPolygons & out_fill_no_overlap); + ExPolygons &out_fill_expolygons); ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, float tolerance, float merge_tolerance); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 6e77338..8ff2178 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -442,7 +442,7 @@ static std::vector s_Preset_print_options { "enable_dynamic_overhang_speeds", "overhang_speed_0", "overhang_speed_1", "overhang_speed_2", "overhang_speed_3", "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed", "bridge_speed", "gap_fill_speed", "gap_fill_enabled", "travel_speed", "travel_speed_z", "first_layer_speed", "first_layer_speed_over_raft", "perimeter_acceleration", "infill_acceleration", - "external_perimeter_acceleration", "top_solid_infill_acceleration", "solid_infill_acceleration", "travel_acceleration", + "external_perimeter_acceleration", "top_solid_infill_acceleration", "solid_infill_acceleration", "travel_acceleration", "wipe_tower_acceleration", "bridge_acceleration", "first_layer_acceleration", "first_layer_acceleration_over_raft", "default_acceleration", "skirts", "skirt_distance", "skirt_height", "draft_shield", "min_skirt_length", "brim_width", "brim_separation", "brim_type", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", "raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion", @@ -486,8 +486,8 @@ static std::vector s_Preset_print_options { static std::vector s_Preset_filament_options { "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed", "extrusion_multiplier", "filament_density", "filament_cost", "filament_spool_weight", "filament_loading_speed", "filament_loading_speed_start", "filament_load_time", - "filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves", - "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower", + "filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves", "filament_stamping_loading_speed", "filament_stamping_distance", + "filament_cooling_initial_speed", "filament_purge_multiplier", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower", "filament_multitool_ramming", "filament_multitool_ramming_volume", "filament_multitool_ramming_flow", "temperature", "idle_temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", @@ -536,8 +536,8 @@ static std::vector s_Preset_printer_options { "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", "color_change_gcode", "pause_print_gcode", "template_custom_gcode", "between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction", - "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height", - "default_print_profile", "inherits", + "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "multimaterial_purging", + "max_print_height", "default_print_profile", "inherits", "remaining_times", "silent_mode", "machine_limits_usage", "thumbnails", "thumbnails_format", //Y20 //B52 @@ -2276,6 +2276,13 @@ const std::string& ExtruderFilaments::get_preset_name_by_alias(const std::string return alias; } +void ExtruderFilaments::select_filament(size_t idx) +{ + assert(idx == size_t(-1) || idx < m_extr_filaments.size()); + // Check idx befor saving it's value to m_idx_selected. + // Invalidate m_idx_selected, if idx is out of range m_extr_filaments + m_idx_selected = (idx == size_t(-1) || idx < m_extr_filaments.size()) ? idx : size_t(-1); +} bool ExtruderFilaments::select_filament(const std::string &name_w_suffix, bool force/*= false*/) { std::string name = Preset::remove_suffix_modified(name_w_suffix); diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 83e4189..013800a 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -894,7 +894,7 @@ public: // Select filament by the full filament name, which contains name of filament, separator and name of selected preset // If full_name doesn't contain name of selected preset, then select first preset in the list for this filament bool select_filament(const std::string& name, bool force = false); - void select_filament(size_t idx) { m_idx_selected = idx; } + void select_filament(size_t idx); std::string get_selected_preset_name() const { return m_idx_selected == size_t(-1) ? std::string() : m_extr_filaments[m_idx_selected].preset->name; } const Preset* get_selected_preset() const { return m_idx_selected == size_t(-1) ? nullptr : m_extr_filaments[m_idx_selected].preset; } diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 32a18c6..3019d8b 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -32,8 +32,8 @@ namespace Slic3r { static std::vector s_project_options { "colorprint_heights", - "wiping_volumes_extruders", - "wiping_volumes_matrix" + "wiping_volumes_matrix", + "wiping_volumes_use_custom_matrix" }; const char *PresetBundle::QIDI_BUNDLE = "QIDITechnology"; @@ -1202,6 +1202,7 @@ ConfigSubstitutions PresetBundle::load_config_file_config_bundle( load_one(this->printers, tmp_bundle.printers, tmp_bundle.printers .get_selected_preset_name(), true); this->extruders_filaments.clear(); + this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments)); this->update_multi_material_filament_presets(); for (size_t i = 1; i < std::min(tmp_bundle.extruders_filaments.size(), this->extruders_filaments.size()); ++i) this->extruders_filaments[i].select_filament(load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.extruders_filaments[i].get_selected_preset_name(), false)); @@ -1691,6 +1692,7 @@ std::pair PresetBundle::load_configbundle( // Extruder_filaments have to be recreated with new loaded filaments this->extruders_filaments.clear(); + this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments)); this->update_multi_material_filament_presets(); for (size_t i = 0; i < std::min(this->extruders_filaments.size(), active_filaments.size()); ++ i) this->extruders_filaments[i].select_filament(filaments.find_preset(active_filaments[i], true)->name); @@ -1729,27 +1731,21 @@ void PresetBundle::update_multi_material_filament_presets() // Now verify if wiping_volumes_matrix has proper size (it is used to deduce number of extruders in wipe tower generator): std::vector old_matrix = this->project_config.option("wiping_volumes_matrix")->values; - size_t old_number_of_extruders = size_t(sqrt(old_matrix.size())+EPSILON); + size_t old_number_of_extruders = size_t(std::sqrt(old_matrix.size())+EPSILON); if (num_extruders != old_number_of_extruders) { - // First verify if purging volumes presets for each extruder matches number of extruders - std::vector& extruders = this->project_config.option("wiping_volumes_extruders")->values; - while (extruders.size() < 2*num_extruders) { - extruders.push_back(extruders.size()>1 ? extruders[0] : 50.); // copy the values from the first extruder - extruders.push_back(extruders.size()>1 ? extruders[1] : 50.); - } - while (extruders.size() > 2*num_extruders) { - extruders.pop_back(); - extruders.pop_back(); - } + // Extract the relevant config options, even values from possibly modified presets. + const double default_purge = static_cast(this->printers.get_edited_preset().config.option("multimaterial_purging"))->value; + const std::vector filament_purging_multipliers = get_config_options_for_current_filaments("filament_purge_multiplier"); std::vector new_matrix; - for (unsigned int i=0;iproject_config.option("wiping_volumes_matrix")->values = new_matrix; } diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index fc38ecf..1410277 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -58,6 +58,27 @@ public: void cache_extruder_filaments_names(); void reset_extruder_filaments(); + // Another hideous function related to current ExtruderFilaments hack. Returns a vector of values + // of a given config option for all currently used filaments. Modified value is returned for modified preset. + // Must be called with the vector ConfigOption type, e.g. ConfigOptionPercents. + template + auto get_config_options_for_current_filaments(const t_config_option_key& key) + { + decltype(T::values) out; + const Preset& edited_preset = this->filaments.get_edited_preset(); + for (const ExtruderFilaments& extr_filament : this->extruders_filaments) { + const Preset& selected_preset = *extr_filament.get_selected_preset(); + const Preset& preset = edited_preset.name == selected_preset.name ? edited_preset : selected_preset; + const T* co = preset.config.opt(key); + if (co) { + assert(co->values.size() == 1); + out.push_back(co->values.back()); + } else { + // Key is missing or type mismatch. + } + } + return out; + } PresetCollection& get_presets(Preset::Type preset_type); // The project configuration values are kept separated from the print/filament/printer preset, diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 68fb7c5..f14a440 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -159,6 +159,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "use_volumetric_e", "variable_layer_height", "wipe", + "wipe_tower_acceleration", //w15 "wipe_distance" }; @@ -208,9 +209,12 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "filament_unloading_speed_start" || opt_key == "filament_toolchange_delay" || opt_key == "filament_cooling_moves" + || opt_key == "filament_stamping_loading_speed" + || opt_key == "filament_stamping_distance" || opt_key == "filament_minimal_purge_on_wipe_tower" || opt_key == "filament_cooling_initial_speed" || opt_key == "filament_cooling_final_speed" + || opt_key == "filament_purge_multiplier" || opt_key == "filament_ramming_parameters" || opt_key == "filament_multitool_ramming" || opt_key == "filament_multitool_ramming_volume" @@ -228,13 +232,16 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "wipe_tower_cone_angle" || opt_key == "wipe_tower_bridging" || opt_key == "wipe_tower_extra_spacing" + || opt_key == "wipe_tower_extra_flow" || opt_key == "wipe_tower_no_sparse_layers" || opt_key == "wipe_tower_extruder" || opt_key == "wiping_volumes_matrix" + || opt_key == "wiping_volumes_use_custom_matrix" || opt_key == "parking_pos_retraction" || opt_key == "cooling_tube_retraction" || opt_key == "cooling_tube_length" || opt_key == "extra_loading_move" + || opt_key == "multimaterial_purging" || opt_key == "travel_speed" || opt_key == "travel_speed_z" || opt_key == "first_layer_speed" diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index a33aece..e87a254 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1117,6 +1117,22 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionFloats { 0. }); + def = this->add("filament_stamping_loading_speed", coFloats); + def->label = L("Stamping loading speed"); + def->tooltip = L("Speed used for stamping."); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloats { 20. }); + + def = this->add("filament_stamping_distance", coFloats); + def->label = L("Stamping distance measured from the center of the cooling tube"); + def->tooltip = L("If set to nonzero value, filament is moved toward the nozzle between the individual cooling moves (\"stamping\"). " + "This option configures how long this movement should be before the filament is retracted again."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloats { 0. }); def = this->add("filament_cooling_moves", coInts); def->label = L("Number of cooling moves"); def->tooltip = L("Filament is cooled by being moved back and forth in the " @@ -1153,6 +1169,15 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionFloats { 3.4 }); + def = this->add("filament_purge_multiplier", coPercents); + def->label = L("Purge volume multiplier"); + def->tooltip = L("Purging volume on the wipe tower is determined by 'multimaterial_purging' in Printer Settings. " + "This option allows to modify the volume on filament level. " + "Note that the project can override this by setting project-specific values."); + def->sidetext = L("%"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionPercents { 100 }); def = this->add("filament_load_time", coFloats); def->label = L("Filament load time"); def->tooltip = L("Time for the printer firmware (or the Multi Material Unit 2.0) to load a new filament during a tool change (when executing the T code). This time is added to the total print time by the G-code time estimator."); @@ -1614,6 +1639,14 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(0)); + def = this->add("wipe_tower_acceleration", coFloat); + def->label = L("Wipe tower"); + def->tooltip = L("This is the acceleration your printer will use for wipe tower. Set zero to disable " + "acceleration control for the wipe tower."); + def->sidetext = L("mm/s²"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0)); def = this->add("travel_acceleration", coFloat); def->label = L("Travel"); def->tooltip = L("This is the acceleration your printer will use for travel moves. Set zero to disable " @@ -2214,6 +2247,13 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(-2.)); + def = this->add("multimaterial_purging", coFloat); + def->label = L("Purging volume"); + def->tooltip = L("Determines purging volume on the wipe tower. This can be modified in Filament Settings " + "('filament_purge_multiplier') or overridden using project-specific settings."); + def->sidetext = L("mm³"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(140.)); def = this->add("perimeter_acceleration", coFloat); def->label = L("Perimeters"); def->tooltip = L("This is the acceleration your printer will use for perimeters. " @@ -3380,12 +3420,6 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); - def = this->add("wiping_volumes_extruders", coFloats); - def->label = L("Purging volumes - load/unload volumes"); - def->tooltip = L("This vector saves required volumes to change from/to each tool used on the " - "wipe tower. These values are used to simplify creation of the full purging " - "volumes below."); - def->set_default_value(new ConfigOptionFloats { 70., 70., 70., 70., 70., 70., 70., 70., 70., 70. }); def = this->add("wiping_volumes_matrix", coFloats); def->label = L("Purging volumes - matrix"); @@ -3397,6 +3431,10 @@ void PrintConfigDef::init_fff_params() 140., 140., 140., 0., 140., 140., 140., 140., 140., 0. }); + def = this->add("wiping_volumes_use_custom_matrix", coBool); + def->label = ""; + def->tooltip = ""; + def->set_default_value(new ConfigOptionBool{ false }); def = this->add("wipe_tower_x", coFloat); def->label = L("Position X"); def->tooltip = L("X coordinate of the left front corner of a wipe tower"); @@ -3452,6 +3490,15 @@ void PrintConfigDef::init_fff_params() def->max = 300.; def->set_default_value(new ConfigOptionPercent(100.)); + def = this->add("wipe_tower_extra_flow", coPercent); + def->label = L("Extra flow for purging"); + def->tooltip = L("Extra flow used for the purging lines on the wipe tower. This makes the purging lines thicker or narrower " + "than they normally would be. The spacing is adjusted automatically."); + def->sidetext = L("%"); + def->mode = comExpert; + def->min = 100.; + def->max = 300.; + def->set_default_value(new ConfigOptionPercent(100.)); def = this->add("wipe_into_infill", coBool); def->category = L("Wipe options"); def->label = L("Wipe into this object's infill"); @@ -4564,7 +4611,8 @@ static std::set PrintConfigDef_ignore = { "ensure_vertical_shell_thickness", // Disabled in 2.6.0-alpha6, this option is problematic "infill_only_where_needed", - "gcode_binary" // Introduced in 2.7.0-alpha1, removed in 2.7.1 (replaced by binary_gcode). + "gcode_binary", // Introduced in 2.7.0-alpha1, removed in 2.7.1 (replaced by binary_gcode). + "wiping_volumes_extruders" // Removed in 2.7.3-alpha1. }; void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) @@ -4695,6 +4743,25 @@ void PrintConfigDef::handle_legacy_composite(DynamicPrintConfig &config) config.set_key_value("thumbnails", new ConfigOptionString(thumbnails_str)); } } + if (config.has("wiping_volumes_matrix") && !config.has("wiping_volumes_use_custom_matrix")) { + // This is apparently some pre-2.7.3 config, where the wiping_volumes_matrix was always used. + // The 2.7.3 introduced an option to use defaults derived from config. In case the matrix + // contains only default values, switch it to default behaviour. The default values + // were zeros on the diagonal and 140 otherwise. + std::vector matrix = config.opt("wiping_volumes_matrix")->values; + int num_of_extruders = int(std::sqrt(matrix.size()) + 0.5); + int i = -1; + bool custom = false; + for (int j = 0; j < int(matrix.size()); ++j) { + if (j % num_of_extruders == 0) + ++i; + if (i != j % num_of_extruders && !is_approx(matrix[j], 140.)) { + custom = true; + break; + } + } + config.set_key_value("wiping_volumes_use_custom_matrix", new ConfigOptionBool(custom)); + } } const PrintConfigDef print_config_def; diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index ec0dc7a..88acd49 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -725,10 +725,13 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloats, filament_cooling_initial_speed)) ((ConfigOptionFloats, filament_minimal_purge_on_wipe_tower)) ((ConfigOptionFloats, filament_cooling_final_speed)) + ((ConfigOptionPercents, filament_purge_multiplier)) ((ConfigOptionStrings, filament_ramming_parameters)) ((ConfigOptionBools, filament_multitool_ramming)) ((ConfigOptionFloats, filament_multitool_ramming_volume)) ((ConfigOptionFloats, filament_multitool_ramming_flow)) + ((ConfigOptionFloats, filament_stamping_loading_speed)) + ((ConfigOptionFloats, filament_stamping_distance)) ((ConfigOptionBool, gcode_comments)) ((ConfigOptionEnum, gcode_flavor)) ((ConfigOptionEnum, gcode_label_objects)) @@ -781,6 +784,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionBool, remaining_times)) ((ConfigOptionBool, silent_mode)) ((ConfigOptionFloat, extra_loading_move)) + ((ConfigOptionFloat, multimaterial_purging)) ((ConfigOptionString, color_change_gcode)) ((ConfigOptionString, pause_print_gcode)) ((ConfigOptionString, template_custom_gcode)) @@ -893,6 +897,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloat, travel_acceleration)) ((ConfigOptionBools, wipe)) ((ConfigOptionBool, wipe_tower)) + ((ConfigOptionFloat, wipe_tower_acceleration)) ((ConfigOptionFloat, wipe_tower_x)) ((ConfigOptionFloat, wipe_tower_y)) ((ConfigOptionFloat, wipe_tower_width)) @@ -901,10 +906,11 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloat, wipe_tower_brim_width)) ((ConfigOptionFloat, wipe_tower_cone_angle)) ((ConfigOptionPercent, wipe_tower_extra_spacing)) + ((ConfigOptionPercent, wipe_tower_extra_flow)) ((ConfigOptionFloat, wipe_tower_bridging)) ((ConfigOptionInt, wipe_tower_extruder)) ((ConfigOptionFloats, wiping_volumes_matrix)) - ((ConfigOptionFloats, wiping_volumes_extruders)) + ((ConfigOptionBool, wiping_volumes_use_custom_matrix)) ((ConfigOptionFloat, z_offset)) ) diff --git a/src/platform/msw/QIDISlicer-gcodeviewer.rc.in b/src/platform/msw/QIDISlicer-gcodeviewer.rc.in index 3b08d4a..c63739d 100644 --- a/src/platform/msw/QIDISlicer-gcodeviewer.rc.in +++ b/src/platform/msw/QIDISlicer-gcodeviewer.rc.in @@ -12,7 +12,7 @@ PRODUCTVERSION @SLIC3R_RC_VERSION@ VALUE "ProductName", "@SLIC3R_APP_NAME@ G-code Viewer" VALUE "ProductVersion", "@SLIC3R_BUILD_ID@" VALUE "InternalName", "@SLIC3R_APP_NAME@ G-code Viewer" - VALUE "LegalCopyright", "Copyright \251 2016-2023 Prusa Research, \251 2011-2018 Alessandro Ranellucci" + VALUE "LegalCopyright", "Copyright \251 2016-2024 Prusa Research, \251 2011-2018 Alessandro Ranellucci" VALUE "OriginalFilename", "qidi-gcodeviewer.exe" } } diff --git a/src/platform/msw/QIDISlicer.rc.in b/src/platform/msw/QIDISlicer.rc.in index 5d3df8a..4daaf27 100644 --- a/src/platform/msw/QIDISlicer.rc.in +++ b/src/platform/msw/QIDISlicer.rc.in @@ -12,7 +12,7 @@ PRODUCTVERSION @SLIC3R_RC_VERSION@ VALUE "ProductName", "@SLIC3R_APP_NAME@" VALUE "ProductVersion", "@SLIC3R_BUILD_ID@" VALUE "InternalName", "@SLIC3R_APP_NAME@" - VALUE "LegalCopyright", "Copyright \251 2016-2023 Prusa Research, \251 2011-2018 Alessandro Ranellucci" + VALUE "LegalCopyright", "Copyright \251 2016-2024 Prusa Research, \251 2011-2018 Alessandro Ranellucci" VALUE "OriginalFilename", "qidi-slicer.exe" } } diff --git a/src/platform/osx/Info.plist.in b/src/platform/osx/Info.plist.in index 9db9dd1..57d5b54 100644 --- a/src/platform/osx/Info.plist.in +++ b/src/platform/osx/Info.plist.in @@ -5,7 +5,7 @@ CFBundleExecutable @SLIC3R_APP_KEY@ CFBundleGetInfoString - @SLIC3R_APP_NAME@ Copyright (C) 2011-2019 Alessandro Ranellucci, (C) 2016-2023 QIDI Reseach + @SLIC3R_APP_NAME@ Copyright (C) 2011-2019 Alessandro Ranellucci, (C) 2016-2024 QIDI Technology CFBundleIconFile QIDISlicer.icns CFBundleName diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index cbdf821..930dd63 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -60,7 +60,7 @@ CopyrightsDialog::CopyrightsDialog() m_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxSize(40 * em_unit(), 20 * em_unit()), wxHW_SCROLLBAR_AUTO); - wxFont font = get_default_font(this); + wxFont font = this->GetFont();// get_default_font(this); const int fs = font.GetPointSize(); const int fs2 = static_cast(1.2f*fs); int size[] = { fs, fs, fs, fs, fs2, fs2, fs2 }; @@ -289,7 +289,7 @@ AboutDialog::AboutDialog() "%6%" "

" "%7%
" - "%8% © 2016-2023 Prusa Research.
" + "%8% © 2016-2024 Prusa Research.
" "%9% © 2011-2018 Alessandro Ranellucci.
" "Slic3r %10% " "%11%." diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 5c12435..749d20b 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -287,7 +287,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) bool have_default_acceleration = config->opt_float("default_acceleration") > 0; for (auto el : { "perimeter_acceleration", "infill_acceleration", "top_solid_infill_acceleration", "solid_infill_acceleration", "external_perimeter_acceleration", - "bridge_acceleration", "first_layer_acceleration" }) + "bridge_acceleration", "first_layer_acceleration", "wipe_tower_acceleration"}) toggle_field(el, have_default_acceleration); bool have_skirt = config->opt_int("skirts") > 0; @@ -351,7 +351,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) bool have_wipe_tower = config->opt_bool("wipe_tower"); for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle", - "wipe_tower_extra_spacing", "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" }) + "wipe_tower_extra_spacing", "wipe_tower_extra_flow", "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" }) toggle_field(el, have_wipe_tower); bool have_non_zero_mmu_segmented_region_max_width = config->opt_float("mmu_segmented_region_max_width") > 0.; diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 1f53fd9..999b0a2 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1512,11 +1512,11 @@ PageDownloader::PageDownloader(ConfigWizard* parent) )); } -#ifdef __linux__ +#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) append_text(wxString::Format(_L( "On Linux systems the process of registration also creates desktop integration files for this version of application." ))); -#endif +#endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) box_allow_downloads->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->m_downloader->allow(event.IsChecked()); }); @@ -1575,8 +1575,8 @@ bool DownloaderUtils::Worker::perform_register(const std::string& path_override/ key_full = key_string; #elif __APPLE__ // Apple registers for custom url in info.plist thus it has to be already registered since build. - // The url will always trigger opening of qidislicer and we have to check that user has allowed it. (GUI_App::MacOpenURL is the triggered method) -#else + // The url will always trigger opening of prusaslicer and we have to check that user has allowed it. (GUI_App::MacOpenURL is the triggered method) +#elif defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) // the performation should be called later during desktop integration perform_registration_linux = true; #endif @@ -1594,7 +1594,7 @@ void DownloaderUtils::Worker::deregister() key_full = key_string; #elif __APPLE__ // TODO -#else +#elif defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) BOOST_LOG_TRIVIAL(debug) << "DesktopIntegrationDialog::undo_downloader_registration"; DesktopIntegrationDialog::undo_downloader_registration(); perform_registration_linux = false; @@ -2781,10 +2781,12 @@ bool ConfigWizard::priv::on_bnt_finish() { wxBusyCursor wait; +#if !defined(__linux__) || (defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)) if (!page_downloader->on_finish_downloader()) { index->go_to(page_downloader); return false; } +#endif /* If some printers were added/deleted, but related MaterialPage wasn't activated, * than last changes wouldn't be updated for filaments/materials. * SO, do that before check_and_install_missing_materials() @@ -3066,14 +3068,14 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese if ((check_unsaved_preset_changes = install_bundles.size() > 0)) header = _L_PLURAL("A new vendor was installed and one of its printers will be activated", "New vendors were installed and one of theirs printers will be activated", install_bundles.size()); -#ifdef __linux__ +#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) // Desktop integration on Linux BOOST_LOG_TRIVIAL(debug) << "ConfigWizard::priv::apply_config integrate_desktop" << page_welcome->integrate_desktop() << " perform_registration_linux " << page_downloader->m_downloader->get_perform_registration_linux(); if (page_welcome->integrate_desktop()) DesktopIntegrationDialog::perform_desktop_integration(); if (page_downloader->m_downloader->get_perform_registration_linux()) DesktopIntegrationDialog::perform_downloader_desktop_integration(); -#endif +#endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) // Decide whether to create snapshot based on run_reason and the reset profile checkbox bool snapshot = true; @@ -3412,7 +3414,9 @@ ConfigWizard::ConfigWizard(wxWindow *parent) p->add_page(p->page_update = new PageUpdate(this)); +#if !defined(__linux__) || (defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)) p->add_page(p->page_downloader = new PageDownloader(this)); +#endif p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this)); #ifdef _WIN32 p->add_page(p->page_files_association = new PageFilesAssociation(this)); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 3fa2ef4..9534496 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2989,13 +2989,7 @@ void GUI_App::MacOpenFiles(const wxArrayString &fileNames) void GUI_App::MacOpenURL(const wxString& url) { - if (app_config && !app_config->get_bool("downloader_url_registered")) - { - notification_manager()->push_notification(NotificationType::URLNotRegistered); - BOOST_LOG_TRIVIAL(error) << "Recieved command to open URL, but it is not allowed in app configuration. URL: " << url; - return; - } - start_download(boost::nowide::narrow(url)); + start_download(into_u8(url)); } #endif /* __APPLE */ @@ -3166,6 +3160,9 @@ void GUI_App::show_desktop_integration_dialog() void GUI_App::show_downloader_registration_dialog() { +#if defined(__linux__) && !defined(SLIC3R_DESKTOP_INTEGRATION) + return; +#endif InfoDialog msg(nullptr , format_wxstr(_L("Welcome to %1% version %2%."), SLIC3R_APP_NAME, SLIC3R_VERSION) , format_wxstr(_L( @@ -3177,10 +3174,10 @@ void GUI_App::show_downloader_registration_dialog() if (msg.ShowModal() == wxID_YES) { auto downloader_worker = new DownloaderUtils::Worker(nullptr); downloader_worker->perform_register(app_config->get("url_downloader_dest")); -#ifdef __linux__ +#if defined(__linux__) if (downloader_worker->get_perform_registration_linux()) DesktopIntegrationDialog::perform_downloader_desktop_integration(); -#endif // __linux__ +#endif //(__linux__) } else { app_config->set("downloader_url_registered", "0"); } @@ -3567,6 +3564,17 @@ void GUI_App::start_download(std::string url) BOOST_LOG_TRIVIAL(error) << "Could not start URL download: plater is nullptr."; return; } + // Windows register and deregister executable path to registry - cant get here when not registered + // Apple registers via info.plist attached to exectable - might get here + // Linux registers via desktop integration file - might get here only if such file was created outside Slicer. + // Desktop integration is limited with SLIC3R_DESKTOP_INTEGRATION (cmake option). +#if defined(__APPLE__) || (defined(__linux__) && !defined(SLIC3R_DESKTOP_INTEGRATION)) + if (app_config && app_config->get_bool("downloader_url_registered")) { + notification_manager()->push_notification(NotificationType::URLNotRegistered); + BOOST_LOG_TRIVIAL(error) << "Recieved command to open URL, but it is not allowed in app configuration. URL: " << url; + return; + } +#endif //defined(__APPLE__) || (defined(__linux__) && !defined(SLIC3R_DESKTOP_INTEGRATION)) //lets always init so if the download dest folder was changed, new dest is used boost::filesystem::path dest_folder(app_config->get("url_downloader_dest")); if (dest_folder.empty() || !boost::filesystem::is_directory(dest_folder)) { diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index eb4bb4e..c9d5882 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -179,7 +179,7 @@ void ImGuiWrapper::set_language(const std::string &language) 0, }; m_font_cjk = false; - if (lang == "cs" || lang == "pl" || lang == "hu") { + if (lang == "cs" || lang == "pl" || lang == "hu" || lang == "sl") { ranges = ranges_latin2; } else if (lang == "ru" || lang == "uk" || lang == "be") { ranges = ImGui::GetIO().Fonts->GetGlyphRangesCyrillic(); // Default + about 400 Cyrillic characters diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7a2f6ea..114beff 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -501,19 +501,23 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : sizer->Add(m_wiping_dialog_button, 0, wxALIGN_CENTER_VERTICAL); m_wiping_dialog_button->Bind(wxEVT_BUTTON, ([parent](wxCommandEvent& e) { - auto &project_config = wxGetApp().preset_bundle->project_config; + PresetBundle* preset_bundle = wxGetApp().preset_bundle; + DynamicPrintConfig& project_config = preset_bundle->project_config; + const bool use_custom_matrix = (project_config.option("wiping_volumes_use_custom_matrix"))->value; const std::vector &init_matrix = (project_config.option("wiping_volumes_matrix"))->values; - const std::vector &init_extruders = (project_config.option("wiping_volumes_extruders"))->values; const std::vector extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(); - WipingDialog dlg(parent, cast(init_matrix), cast(init_extruders), extruder_colours); + // Extract the relevant config options, even values from possibly modified presets. + const double default_purge = static_cast(preset_bundle->printers.get_edited_preset().config.option("multimaterial_purging"))->value; + std::vector filament_purging_multipliers = preset_bundle->get_config_options_for_current_filaments("filament_purge_multiplier"); + + WipingDialog dlg(parent, cast(init_matrix), extruder_colours, default_purge, filament_purging_multipliers, use_custom_matrix); if (dlg.ShowModal() == wxID_OK) { std::vector matrix = dlg.get_matrix(); - std::vector extruders = dlg.get_extruders(); (project_config.option("wiping_volumes_matrix"))->values = std::vector(matrix.begin(), matrix.end()); - (project_config.option("wiping_volumes_extruders"))->values = std::vector(extruders.begin(), extruders.end()); + (project_config.option("wiping_volumes_use_custom_matrix"))->value = dlg.get_use_custom_matrix(); // Update Project dirty state, update application title bar. wxGetApp().plater()->update_project_dirty_from_presets(); wxPostEvent(parent, SimpleEvent(EVT_SCHEDULE_BACKGROUND_PROCESS, parent)); @@ -946,7 +950,8 @@ Sidebar::Sidebar(Plater *parent) int bmp_px_cnt = 32; #endif //__APPLE__ ScalableBitmap bmp = ScalableBitmap(this, icon_name, bmp_px_cnt); - *btn = new ScalableButton(this, wxID_ANY, bmp, "", wxBU_EXACTFIT); + *btn = new ScalableButton(this, wxID_ANY, bmp, label, wxBU_EXACTFIT); + (*btn)->SetFont(wxGetApp().bold_font()); wxGetApp().SetWindowVariantForButton((*btn)); #ifdef _WIN32 @@ -964,9 +969,10 @@ Sidebar::Sidebar(Plater *parent) (*btn)->Hide(); }; - init_scalable_btn(&p->btn_send_gcode , "export_gcode", _L("Send to printer") + " " +GUI::shortkey_ctrl_prefix() + "Shift+G"); + init_scalable_btn(&p->btn_send_gcode, "export_gcode", _L("Send to printer"), _L("Send to printer") + " " + GUI::shortkey_ctrl_prefix() + "Shift+G"); // init_scalable_btn(&p->btn_eject_device, "eject_sd" , _L("Remove device ") + GUI::shortkey_ctrl_prefix() + "T"); - init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _L("Export to SD card / Flash drive") + " " + GUI::shortkey_ctrl_prefix() + "U"); + init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _L("Export"), _L("Export to SD card / Flash drive") + " " + GUI::shortkey_ctrl_prefix() + "U"); +//Y14 // regular buttons "Slice now" and "Export G-code" // const int scaled_height = p->btn_eject_device->GetBitmapHeight() + 4; @@ -2118,7 +2124,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) , config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({ "bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance", "brim_width", "brim_separation", "brim_type", "variable_layer_height", "nozzle_diameter", "single_extruder_multi_material", - "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_extruder", + "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_extra_flow", "wipe_tower_extruder", "extruder_colour", "filament_colour", "material_colour", "max_print_height", "printer_model", "printer_notes", "printer_technology", // These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor. "layer_height", "first_layer_height", "min_layer_height", "max_layer_height", diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 6ebef11..9929a60 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -19,9 +19,13 @@ #ifdef WIN32 #include #endif // WIN32 -#ifdef __linux__ +#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) #include "DesktopIntegrationDialog.hpp" -#endif //__linux__ +#endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) + +#if defined(__linux__) && !defined(SLIC3R_DESKTOP_INTEGRATION) +#include "NotificationManager.hpp" +#endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) namespace Slic3r { @@ -121,9 +125,11 @@ void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::strin if (wxGetApp().is_editor()) { auto app_config = get_app_config(); +#if !defined(__linux__) || (defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)) downloader->set_path_name(app_config->get("url_downloader_dest")); downloader->allow(!app_config->has("downloader_url_registered") || app_config->get_bool("downloader_url_registered")); +#endif for (const std::string& opt_key : {"suppress_hyperlinks", "downloader_url_registered"}) m_optgroup_other->set_value(opt_key, app_config->get_bool(opt_key)); @@ -627,14 +633,18 @@ void PreferencesDialog::build() // "If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks."), app_config->get_bool("suppress_hyperlinks")); +#if !defined(__linux__) || (defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)) append_bool_option(m_optgroup_other, "downloader_url_registered", L("Allow downloads from Printables.com"), L("If enabled, QIDISlicer will be allowed to download from Printables.com"), app_config->get_bool("downloader_url_registered")); +#endif activate_options_tab(m_optgroup_other); +#if !defined(__linux__) || (defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)) create_downloader_path_sizer(); +#endif create_settings_font_widget(); #if ENABLE_ENVIRONMENT_MAP @@ -739,16 +749,18 @@ void PreferencesDialog::update_ctrls_alignment() void PreferencesDialog::accept(wxEvent&) { +#if !defined(__linux__) || (defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)) if(wxGetApp().is_editor()) { if (const auto it = m_values.find("downloader_url_registered"); it != m_values.end()) downloader->allow(it->second == "1"); if (!downloader->on_finish()) return; -#ifdef __linux__ +#if defined(__linux__) if( downloader->get_perform_registration_linux()) DesktopIntegrationDialog::perform_downloader_desktop_integration(); -#endif // __linux__ +#endif } +#endif std::vector options_to_recreate_GUI = { "no_defaults", "tabs_as_menu", "sys_menu_enabled", "font_pt_size", "suppress_round_corners" }; diff --git a/src/slic3r/GUI/RammingChart.cpp b/src/slic3r/GUI/RammingChart.cpp index 244d83a..3f5decf 100644 --- a/src/slic3r/GUI/RammingChart.cpp +++ b/src/slic3r/GUI/RammingChart.cpp @@ -111,6 +111,7 @@ void Chart::mouse_right_button_clicked(wxMouseEvent& event) { void Chart::mouse_clicked(wxMouseEvent& event) { + m_uniform = (event.GetModifiers() == wxMOD_CONTROL); wxPoint point = event.GetPosition(); int button_index = which_button_is_clicked(point); if ( button_index != -1) { @@ -132,7 +133,13 @@ void Chart::mouse_moved(wxMouseEvent& event) { } int delta_x = pos.x - m_previous_mouse.x; int delta_y = pos.y - m_previous_mouse.y; - m_dragged->move(fixed_x?0:double(delta_x)/m_rect.GetWidth() * visible_area.m_width,-double(delta_y)/m_rect.GetHeight() * visible_area.m_height); + double new_y = m_dragged->get_pos().m_y - double(delta_y) / m_rect.GetHeight() * visible_area.m_height; + + if (m_uniform) + for (ButtonToDrag& b : m_buttons) + b.move(fixed_x?0:double(delta_x)/m_rect.GetWidth() * visible_area.m_width, new_y - b.get_pos().m_y); + else + m_dragged->move(fixed_x?0:double(delta_x)/m_rect.GetWidth() * visible_area.m_width, new_y - m_dragged->get_pos().m_y); m_previous_mouse = pos; recalculate_line(); } @@ -259,7 +266,7 @@ std::vector Chart::get_ramming_speed(float sampling) const { std::vector speeds_out; const int number_of_samples = std::round( visible_area.m_width / sampling); - if (number_of_samples>0) { + if (number_of_samples>0 && !m_line_to_draw.empty()) { const int dx = (m_line_to_draw.size()-1) / number_of_samples; for (int j=0;j0) for (const auto& pair : initial_buttons) @@ -27,7 +27,7 @@ public: recalculate_line(); } void set_xy_range(float x,float y) { - x = int(x/0.5) * 0.5; + x = int(x/0.25) * 0.25; if (x>=0) visible_area.SetRight(x); if (y>=0) visible_area.SetBottom(y); recalculate_line(); @@ -103,7 +103,6 @@ private: void recalculate_line(); - void recalculate_volume(); wxRect m_rect; // rectangle on screen the chart is mapped into (screen coordinates) @@ -114,6 +113,7 @@ private: ButtonToDrag* m_dragged = nullptr; float m_total_volume = 0.f; + bool m_uniform = false; // testing only }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index d66df5b..c79b3fa 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1615,6 +1615,7 @@ void TabPrint::build() optgroup->append_single_option_line("bridge_acceleration"); optgroup->append_single_option_line("first_layer_acceleration"); optgroup->append_single_option_line("first_layer_acceleration_over_raft"); + optgroup->append_single_option_line("wipe_tower_acceleration"); optgroup->append_single_option_line("travel_acceleration"); optgroup->append_single_option_line("default_acceleration"); @@ -1649,6 +1650,7 @@ void TabPrint::build() optgroup->append_single_option_line("wipe_tower_bridging"); optgroup->append_single_option_line("wipe_tower_cone_angle"); optgroup->append_single_option_line("wipe_tower_extra_spacing"); + optgroup->append_single_option_line("wipe_tower_extra_flow"); optgroup->append_single_option_line("wipe_tower_no_sparse_layers"); optgroup->append_single_option_line("single_extruder_multi_material_priming"); @@ -2284,6 +2286,9 @@ void TabFilament::build() optgroup->append_single_option_line("filament_cooling_moves"); optgroup->append_single_option_line("filament_cooling_initial_speed"); optgroup->append_single_option_line("filament_cooling_final_speed"); + optgroup->append_single_option_line("filament_stamping_loading_speed"); + optgroup->append_single_option_line("filament_stamping_distance"); + optgroup->append_single_option_line("filament_purge_multiplier"); create_line_with_widget(optgroup.get(), "filament_ramming_parameters", "", [this](wxWindow* parent) { auto ramming_dialog_btn = new wxButton(parent, wxID_ANY, _(L("Ramming settings"))+dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); @@ -3419,6 +3424,7 @@ void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/) optgroup->append_single_option_line("cooling_tube_length"); optgroup->append_single_option_line("parking_pos_retraction"); optgroup->append_single_option_line("extra_loading_move"); + optgroup->append_single_option_line("multimaterial_purging"); optgroup->append_single_option_line("high_current_on_filament_swap"); if (from_initial_build) page->clear(); diff --git a/src/slic3r/GUI/Widgets/DropDown.cpp b/src/slic3r/GUI/Widgets/DropDown.cpp index f59b91f..75a689b 100644 --- a/src/slic3r/GUI/Widgets/DropDown.cpp +++ b/src/slic3r/GUI/Widgets/DropDown.cpp @@ -516,7 +516,7 @@ void DropDown::mouseReleased(wxMouseEvent& event) if (HasCapture()) ReleaseMouse(); if (hover_item >= 0) { // not moved -#ifdef __WXOSX__ +#ifndef _WIN32 // To avoid cases, when some dialog appears after item selection, but DropDown is still shown Hide(); #endif diff --git a/src/slic3r/GUI/WipeTowerDialog.cpp b/src/slic3r/GUI/WipeTowerDialog.cpp index 6f5915d..f4fcd26 100644 --- a/src/slic3r/GUI/WipeTowerDialog.cpp +++ b/src/slic3r/GUI/WipeTowerDialog.cpp @@ -4,6 +4,7 @@ #include "BitmapCache.hpp" #include "GUI.hpp" #include "I18N.hpp" +#include "slic3r/GUI/format.hpp" #include "GUI_App.hpp" #include "MsgDialog.hpp" @@ -67,11 +68,6 @@ RammingDialog::RammingDialog(wxWindow* parent,const std::string& parameters) } -#ifdef _WIN32 -#define style wxSP_ARROW_KEYS | wxBORDER_SIMPLE -#else -#define style wxSP_ARROW_KEYS -#endif @@ -105,11 +101,17 @@ RammingPanel::RammingPanel(wxWindow* parent, const std::string& parameters) #endif sizer_chart->Add(m_chart, 0, wxALL, 5); - m_widget_time = new ::SpinInputDouble(this,"", wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH(), -1), style, 0., 5., 3., 0.5); +#ifdef _WIN32 + const long style = wxSP_ARROW_KEYS | wxBORDER_SIMPLE; +#else + const long style = wxSP_ARROW_KEYS; +#endif + + m_widget_time = new ::SpinInputDouble(this,"", wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH(), -1), style, 0., 5., 3., 0.25); m_widget_time->SetDigits(2); m_widget_volume = new ::SpinInput(this,"",wxEmptyString,wxDefaultPosition,wxSize(ITEM_WIDTH(), -1),style,0,10000,0); - m_widget_ramming_line_width_multiplicator = new ::SpinInput(this,"",wxEmptyString,wxDefaultPosition,wxSize(ITEM_WIDTH(), -1),style,10,200,100); - m_widget_ramming_step_multiplicator = new ::SpinInput(this,"",wxEmptyString,wxDefaultPosition,wxSize(ITEM_WIDTH(), -1),style,10,200,100); + m_widget_ramming_line_width_multiplicator = new ::SpinInput(this,"",wxEmptyString,wxDefaultPosition,wxSize(ITEM_WIDTH(), -1),style,10,300,100); + m_widget_ramming_step_multiplicator = new ::SpinInput(this,"",wxEmptyString,wxDefaultPosition,wxSize(ITEM_WIDTH(), -1),style,10,300,100); #ifdef _WIN32 update_ui(m_widget_time->GetText()); @@ -129,6 +131,14 @@ RammingPanel::RammingPanel(wxWindow* parent, const std::string& parameters) gsizer_param->Add(m_widget_ramming_line_width_multiplicator); gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Ramming line spacing")) + " (%):")), 0, wxALIGN_CENTER_VERTICAL); gsizer_param->Add(m_widget_ramming_step_multiplicator); + gsizer_param->AddSpacer(40); + gsizer_param->AddSpacer(40); + + std::string ctrl_str = shortkey_ctrl_prefix(); + if (! ctrl_str.empty() && ctrl_str.back() == '+') + ctrl_str.pop_back(); + // TRN: The placeholder expands to Ctrl or Cmd (on macOS). + gsizer_param->Add(new wxStaticText(this, wxID_ANY, format_wxstr(_L("For constant flow rate, hold %1% while dragging."), ctrl_str)), 0, wxALIGN_CENTER_VERTICAL); sizer_param->Add(gsizer_param, 0, wxTOP, scale(10)); @@ -176,25 +186,52 @@ std::string RammingPanel::get_parameters() } -// Parent dialog for purging volume adjustments - it fathers WipingPanel widget (that contains all controls) and a button to toggle simple/advanced mode: -WipingDialog::WipingDialog(wxWindow* parent, const std::vector& matrix, const std::vector& extruders, const std::vector& extruder_colours) +// Parent dialog for purging volume adjustments - it fathers WipingPanel widget (that contains all controls) and a button. +WipingDialog::WipingDialog(wxWindow* parent, const std::vector& matrix, const std::vector& extruder_colours, + double printer_purging_volume, const std::vector& filament_purging_multipliers, bool use_custom_matrix) : wxDialog(parent, wxID_ANY, _(L("Wipe tower - Purging volume adjustment")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE/* | wxRESIZE_BORDER*/) { SetFont(wxGetApp().normal_font()); update_ui(this); - auto widget_button = new wxButton(this,wxID_ANY,"-",wxPoint(0,0),wxDefaultSize); - update_ui(widget_button); - wxGetApp().SetWindowVariantForButton(widget_button); - m_panel_wiping = new WipingPanel(this,matrix,extruders, extruder_colours, widget_button); + m_widget_button = new wxButton(this,wxID_ANY,_L("Set values from configuration"), wxPoint(0, 0), wxDefaultSize); + update_ui(m_widget_button); + wxGetApp().SetWindowVariantForButton(m_widget_button); - auto main_sizer = new wxBoxSizer(wxVERTICAL); + m_radio_button1 = new wxRadioButton(this, wxID_ANY, _L("Use values from configuration")); + m_radio_button2 = new wxRadioButton(this, wxID_ANY, _L("Use custom project-specific settings")); + auto stb1 = new wxStaticBox(this, wxID_ANY, wxEmptyString); + auto stb2 = new wxStaticBox(this, wxID_ANY, wxEmptyString); + + m_panel_wiping = new WipingPanel(this, matrix, extruder_colours, filament_purging_multipliers, printer_purging_volume, m_widget_button); + + update_ui(m_radio_button1); + update_ui(m_radio_button2); + update_ui(stb1); + update_ui(stb2); + + auto heading_text = new wxStaticText(this, wxID_ANY, _L("The project uses single-extruder multimaterial printer with the wipe tower.\nThe volume of material used for purging can be configured here.") ,wxDefaultPosition, wxDefaultSize); + m_info_text1 = new wxStaticText(this, wxID_ANY, _L("Options 'multimaterial_purging' and 'filament_purge_multiplier' will be used.") ,wxDefaultPosition, wxDefaultSize); // set min sizer width according to extruders count - const auto sizer_width = (int)((sqrt(matrix.size()) + 2.8)*ITEM_WIDTH()); + const auto sizer_width = (int)((std::sqrt(matrix.size()) + 2.8)*ITEM_WIDTH()); + auto main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->SetMinSize(wxSize(sizer_width, -1)); - main_sizer->Add(m_panel_wiping, 0, wxEXPAND | wxALL, 5); - main_sizer->Add(widget_button, 0, wxALIGN_CENTER_HORIZONTAL | wxCENTER | wxBOTTOM, 5); + main_sizer->Add(heading_text, 0, wxALL, 10); + + main_sizer->Add(m_radio_button1, 0, wxALL, 10); + auto stb_sizer1 = new wxStaticBoxSizer(stb1, wxHORIZONTAL); + stb_sizer1->Add(m_info_text1, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 5); + main_sizer->Add(stb_sizer1, 0, wxALIGN_CENTER_HORIZONTAL | wxEXPAND | wxLEFT | wxRIGHT, 20); + + auto t = new wxStaticText(this, wxID_ANY, _L("(all values in mm³)"), wxDefaultPosition, wxDefaultSize); + + main_sizer->Add(m_radio_button2, 0, wxALL, 10); + auto stb_sizer2 = new wxStaticBoxSizer(stb2, wxVERTICAL); + stb_sizer2->Add(m_panel_wiping, 0, wxEXPAND | wxALL, 5); + stb_sizer2->Add(t, 0, wxALIGN_CENTER_HORIZONTAL | wxCENTER | wxBOTTOM, 5); + stb_sizer2->Add(m_widget_button, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10); + main_sizer->Add(stb_sizer2, 0, wxALIGN_CENTER_HORIZONTAL | wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 20); auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL); wxGetApp().SetWindowVariantForButton(buttons->GetAffirmativeButton()); wxGetApp().SetWindowVariantForButton(buttons->GetCancelButton()); @@ -209,32 +246,46 @@ WipingDialog::WipingDialog(wxWindow* parent, const std::vector& matrix, c this->Bind(wxEVT_BUTTON,[this](wxCommandEvent&) { // if OK button is clicked.. m_output_matrix = m_panel_wiping->read_matrix_values(); // ..query wiping panel and save returned values - m_output_extruders = m_panel_wiping->read_extruders_values(); // so they can be recovered later by calling get_...() EndModal(wxID_OK); },wxID_OK); + this->Bind(wxEVT_RADIOBUTTON, [this](wxCommandEvent&) { + enable_or_disable_panel(); + }); + + m_radio_button1->SetValue(! use_custom_matrix); + m_radio_button2->SetValue(use_custom_matrix); + enable_or_disable_panel(); this->Show(); } -// This function allows to "play" with sizers parameters (like align or border) -void WipingPanel::format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_sizer, const wxString& info, const wxString& table_title, int table_lshift/*=0*/) +// This function allows to "play" with sizrs parameters (like align or border) +void WipingPanel::format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_sizer, const wxString& table_title, int table_lshift/*=0*/) { - wxSize text_size = GetTextExtent(info); - auto info_str = new wxStaticText(page, wxID_ANY, info ,wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER); - info_str->Wrap(int(0.6*text_size.x)); - sizer->Add( info_str, 0, wxEXPAND); auto table_sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(table_sizer, 0, wxALIGN_CENTER | wxCENTER, table_lshift); - table_sizer->Add(new wxStaticText(page, wxID_ANY, table_title), 0, wxALIGN_CENTER | wxTOP, 50); - table_sizer->Add(grid_sizer, 0, wxALIGN_CENTER | wxTOP, 10); + table_sizer->Add(new wxStaticText(page, wxID_ANY, table_title), 0, wxALIGN_CENTER | wxTOP, 10); + table_sizer->Add(grid_sizer, 0, wxALIGN_CENTER | wxTOP | wxLEFT, 15); } -// This panel contains all control widgets for both simple and advanced mode (these reside in separate sizers) -WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, const std::vector& extruders, const std::vector& extruder_colours, wxButton* widget_button) +WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, const std::vector& extruder_colours, + const std::vector& filament_purging_multipliers, double printer_purging_volume, wxButton* widget_button) : wxPanel(parent,wxID_ANY, wxDefaultPosition, wxDefaultSize/*,wxBORDER_RAISED*/) { + m_filament_purging_multipliers = filament_purging_multipliers; + m_printer_purging_volume = printer_purging_volume; m_widget_button = widget_button; // pointer to the button in parent dialog - m_widget_button->Bind(wxEVT_BUTTON,[this](wxCommandEvent&){ toggle_advanced(true); }); + m_widget_button->Bind(wxEVT_BUTTON,[this](wxCommandEvent&){ + // Set the matrix to defaults. + for (size_t i = 0; i < m_number_of_extruders; ++i) { + for (size_t j = 0; j < m_number_of_extruders; ++j) { + if (i != j) { + double def_val = m_printer_purging_volume * m_filament_purging_multipliers[j] / 100.; + edit_boxes[j][i]->SetValue(wxString("") << int(def_val)); + } + } + } + }); m_number_of_extruders = (int)(sqrt(matrix.size())+0.001); @@ -244,18 +295,12 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, con m_colours.push_back(wxColor(rgb.r_uchar(), rgb.g_uchar(), rgb.b_uchar())); } - // Create two switched panels with their own sizers - m_sizer_simple = new wxBoxSizer(wxVERTICAL); m_sizer_advanced = new wxBoxSizer(wxVERTICAL); - m_page_simple = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); m_page_advanced = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - m_page_simple->SetSizer(m_sizer_simple); m_page_advanced->SetSizer(m_sizer_advanced); - update_ui(m_page_simple); update_ui(m_page_advanced); - auto gridsizer_simple = new wxGridSizer(3, 5, 10); m_gridsizer_advanced = new wxGridSizer(m_number_of_extruders+1, 5, 1); // First create controls for advanced mode and assign them to m_page_advanced: @@ -308,57 +353,15 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, con } // collect and format sizer - format_sizer(m_sizer_advanced, m_page_advanced, m_gridsizer_advanced, - _(L("Here you can adjust required purging volume (mm³) for any given pair of tools.")), - _(L("Extruder changed to"))); + format_sizer(m_sizer_advanced, m_page_advanced, m_gridsizer_advanced, _(L("Extruder changed to"))); - // Hide preview page before new page creating - // It allows to do that from a beginning of the main panel - m_page_advanced->Hide(); - - // Now the same for simple mode: - gridsizer_simple->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString("")), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); - gridsizer_simple->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString(_(L("unloaded")))), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); - gridsizer_simple->Add(new wxStaticText(m_page_simple,wxID_ANY,wxString(_(L("loaded")))), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); - - auto add_spin_ctrl = [this](std::vector<::SpinInput*>& vec, float initial) - { - ::SpinInput* spin_ctrl = new ::SpinInput(m_page_simple, "", wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH(), -1), style | wxALIGN_RIGHT, 0, 300, (int)initial); - update_ui(spin_ctrl); - vec.push_back(spin_ctrl); - - }; - - for (unsigned int i=0;iSetCanFocus(false); - w->SetBackgroundColour(m_colours[i]); - hsizer->Add(w, wxALIGN_CENTER_VERTICAL); - hsizer->AddSpacer(10); - hsizer->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString(_(L("Tool #"))) << i + 1 << ": "), 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); - - gridsizer_simple->Add(hsizer, 1, wxEXPAND); - gridsizer_simple->Add(m_old.back(),0); - gridsizer_simple->Add(m_new.back(),0); - } - - // collect and format sizer - format_sizer(m_sizer_simple, m_page_simple, gridsizer_simple, - _(L("Total purging volume is calculated by summing two values below, depending on which tools are loaded/unloaded.")), - _(L("Volume to purge (mm³) when the filament is being")), 50); m_sizer = new wxBoxSizer(wxVERTICAL); - m_sizer->Add(m_page_simple, 0, wxEXPAND | wxALL, 25); - m_sizer->Add(m_page_advanced, 0, wxEXPAND | wxALL, 25); + m_sizer->Add(m_page_advanced, 0, wxEXPAND | wxALL, 5); m_sizer->SetSizeHints(this); SetSizer(m_sizer); - toggle_advanced(); // to show/hide what is appropriate m_page_advanced->Bind(wxEVT_PAINT,[this](wxPaintEvent&) { wxPaintDC dc(m_page_advanced); @@ -368,6 +371,21 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, con int text_height = 0; dc.GetTextExtent(label,&text_width,&text_height); int xpos = m_gridsizer_advanced->GetPosition().x; + if (!m_page_advanced->IsEnabled()) { + dc.SetTextForeground(wxSystemSettings::GetColour( +#if defined (__linux__) && defined (__WXGTK2__) + wxSYS_COLOUR_BTNTEXT +#else + wxSYS_COLOUR_GRAYTEXT +#endif + )); + dc.DrawRotatedText(label, xpos - text_height, y_pos + text_width / 2.f, 90); +#ifdef _WIN32 + dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT)); + dc.DrawRotatedText(label, xpos - text_height-1, y_pos + text_width / 2.f+1, 90); +#endif + } + else dc.DrawRotatedText(label,xpos-text_height,y_pos + text_width/2.f,90); }); } @@ -377,8 +395,6 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, con // Reads values from the (advanced) wiping matrix: std::vector WipingPanel::read_matrix_values() { - if (!m_advanced) - fill_in_matrix(); std::vector output; for (unsigned int i=0;i WipingPanel::read_matrix_values() { return output; } -// Reads values from simple mode to save them for next time: -std::vector WipingPanel::read_extruders_values() { - std::vector output; - for (unsigned int i=0;iGetValue()); - output.push_back(m_new[i]->GetValue()); - } - return output; -} - -// This updates the "advanced" matrix based on values from "simple" mode -void WipingPanel::fill_in_matrix() { - for (unsigned i=0;iSetValue(wxString("")<< (m_old[i]->GetValue() + m_new[j]->GetValue())); - } - } -} - - - -// Function to check if simple and advanced settings are matching -bool WipingPanel::advanced_matches_simple() { - for (unsigned i=0;iGetValue() != (wxString("")<< (m_old[i]->GetValue() + m_new[j]->GetValue()))) - return false; - } - } - return true; -} - - -// Switches the dialog from simple to advanced mode and vice versa -void WipingPanel::toggle_advanced(bool user_action) { - if (m_advanced && !advanced_matches_simple() && user_action) { - if (MessageDialog(this, _L("Switching to simple settings will discard changes done in the advanced mode!\n\nDo you want to proceed?"), - _L("Warning"),wxYES_NO|wxICON_EXCLAMATION).ShowModal() != wxID_YES) - return; - } - if (user_action) - m_advanced = !m_advanced; // user demands a change -> toggle - else - m_advanced = !advanced_matches_simple(); // if called from constructor, show what is appropriate - - (m_advanced ? m_page_advanced : m_page_simple)->Show(); - (!m_advanced ? m_page_advanced : m_page_simple)->Hide(); - - m_widget_button->SetLabel(m_advanced ? _(L("Show simplified settings")) : _(L("Show advanced settings"))); - if (m_advanced) - if (user_action) fill_in_matrix(); // otherwise keep values loaded from config - - m_sizer->Layout(); - Refresh(); + +void WipingDialog::enable_or_disable_panel() +{ + bool enable = m_radio_button2->GetValue(); + m_info_text1->Enable(! enable); + m_widget_button->Enable(enable); + m_panel_wiping->Enable(enable); + m_panel_wiping->Refresh(); } diff --git a/src/slic3r/GUI/WipeTowerDialog.hpp b/src/slic3r/GUI/WipeTowerDialog.hpp index 46d4e5c..63ee2bc 100644 --- a/src/slic3r/GUI/WipeTowerDialog.hpp +++ b/src/slic3r/GUI/WipeTowerDialog.hpp @@ -47,29 +47,22 @@ private: class WipingPanel : public wxPanel { public: - WipingPanel(wxWindow* parent, const std::vector& matrix, const std::vector& extruders, const std::vector& extruder_colours, wxButton* widget_button); + WipingPanel(wxWindow* parent, const std::vector& matrix, const std::vector& extruder_colours, + const std::vector& filament_purging_multipliers, double printer_purging_volume, wxButton* widget_button); std::vector read_matrix_values(); - std::vector read_extruders_values(); - void toggle_advanced(bool user_action = false); - void format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_sizer, const wxString& info, const wxString& table_title, int table_lshift=0); + void format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_sizer, const wxString& table_title, int table_lshift=0); private: - void fill_in_matrix(); - bool advanced_matches_simple(); - - std::vector<::SpinInput*> m_old; - std::vector<::SpinInput*> m_new; std::vector> edit_boxes; std::vector m_colours; unsigned int m_number_of_extruders = 0; - bool m_advanced = false; - wxPanel* m_page_simple = nullptr; wxPanel* m_page_advanced = nullptr; wxBoxSizer* m_sizer = nullptr; - wxBoxSizer* m_sizer_simple = nullptr; wxBoxSizer* m_sizer_advanced = nullptr; wxGridSizer* m_gridsizer_advanced = nullptr; wxButton* m_widget_button = nullptr; + double m_printer_purging_volume; + std::vector m_filament_purging_multipliers; // In percents ! }; @@ -78,15 +71,20 @@ private: class WipingDialog : public wxDialog { public: - WipingDialog(wxWindow* parent, const std::vector& matrix, const std::vector& extruders, const std::vector& extruder_colours); + WipingDialog(wxWindow* parent, const std::vector& matrix, const std::vector& extruder_colours, + double printer_purging_volume, const std::vector& filament_purging_multipliers, bool use_custom_matrix); std::vector get_matrix() const { return m_output_matrix; } - std::vector get_extruders() const { return m_output_extruders; } + bool get_use_custom_matrix() const { return m_radio_button2->GetValue(); } private: + void enable_or_disable_panel(); WipingPanel* m_panel_wiping = nullptr; std::vector m_output_matrix; - std::vector m_output_extruders; + wxRadioButton* m_radio_button1 = nullptr; + wxRadioButton* m_radio_button2 = nullptr; + wxButton* m_widget_button = nullptr; + wxStaticText* m_info_text1 = nullptr; }; #endif // _WIPE_TOWER_DIALOG_H_ \ No newline at end of file diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index 8c40ac0..c73eee8 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -16,6 +16,7 @@ add_executable(${_TEST_NAME}_tests test_gcode_travels.cpp test_gcodefindreplace.cpp test_gcodewriter.cpp + test_cancel_object.cpp test_layers.cpp test_model.cpp test_multi.cpp diff --git a/tests/fff_print/test_cancel_object.cpp b/tests/fff_print/test_cancel_object.cpp new file mode 100644 index 0000000..3e0b0e0 --- /dev/null +++ b/tests/fff_print/test_cancel_object.cpp @@ -0,0 +1,200 @@ +#include +#include +#include + +#include "libslic3r/GCode.hpp" +#include "test_data.hpp" + +using namespace Slic3r; +using namespace Test; + +constexpr bool debug_files{false}; + +std::string remove_object(const std::string &gcode, const int id) { + std::string result{gcode}; + std::string start_token{"M486 S" + std::to_string(id) + "\n"}; + std::string end_token{"M486 S-1\n"}; + + std::size_t start{result.find(start_token)}; + + while (start != std::string::npos) { + std::size_t end_token_start{result.find(end_token, start)}; + std::size_t end{end_token_start + end_token.size()}; + result.replace(start, end - start, ""); + start = result.find(start_token); + } + return result; +} + +TEST_CASE("Remove object sanity check", "[CancelObject]") { + // clang-format off + const std::string gcode{ + "the\n" + "M486 S2\n" + "to delete\n" + "M486 S-1\n" + "kept\n" + "M486 S2\n" + "to also delete\n" + "M486 S-1\n" + "lines\n" + }; + // clang-format on + + const std::string result{remove_object(gcode, 2)}; + + // clang-format off + CHECK(result == std::string{ + "the\n" + "kept\n" + "lines\n" + }); + // clang-format on +} + +void check_retraction(const std::string &gcode, double offset = 0.0) { + GCodeReader parser; + std::map retracted; + unsigned count{0}; + std::set there_is_unretract; + int extruder_id{0}; + + parser.parse_buffer( + gcode, + [&](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + INFO("Line number: " + std::to_string(++count)); + INFO("Extruder id: " + std::to_string(extruder_id)); + if (!line.raw().empty() && line.raw().front() == 'T') { + extruder_id = std::stoi(std::string{line.raw().back()}); + } + if (line.dist_XY(self) < std::numeric_limits::epsilon()) { + if (line.has_e() && line.e() < 0) { + retracted[extruder_id] += line.e(); + } + if (line.has_e() && line.e() > 0) { + INFO("Line: " + line.raw()); + if (there_is_unretract.count(extruder_id) == 0) { + there_is_unretract.insert(extruder_id); + REQUIRE(retracted[extruder_id] + offset + line.e() == Approx(0.0)); + } else { + REQUIRE(retracted[extruder_id] + line.e() == Approx(0.0)); + } + retracted[extruder_id] = 0.0; + } + } + } + ); +} + +void add_object( + Model &model, const std::string &name, const int extruder, const Vec3d &offset = Vec3d::Zero() +) { + std::string extruder_id{std::to_string(extruder)}; + ModelObject *object = model.add_object(); + object->name = name; + ModelVolume *volume = object->add_volume(Test::mesh(Test::TestMesh::cube_20x20x20)); + volume->set_material_id("material" + extruder_id); + volume->translate(offset); + DynamicPrintConfig config; + config.set_deserialize_strict({ + {"extruder", extruder_id}, + }); + volume->config.assign_config(config); + object->add_instance(); + object->ensure_on_bed(); +} + +class CancelObjectFixture +{ +public: + CancelObjectFixture() { + config.set_deserialize_strict({ + {"gcode_flavor", "marlin2"}, + {"gcode_label_objects", "firmware"}, + {"gcode_comments", "1"}, + {"use_relative_e_distances", "1"}, + {"wipe", "0"}, + {"skirts", "0"}, + }); + + add_object(two_cubes, "no_offset_cube", 0); + add_object(two_cubes, "offset_cube", 0, {30.0, 0.0, 0.0}); + + add_object(multimaterial_cubes, "no_offset_cube", 1); + add_object(multimaterial_cubes, "offset_cube", 2, {30.0, 0.0, 0.0}); + + retract_length = config.option("retract_length")->get_at(0); + retract_length_toolchange = config.option("retract_length_toolchange") + ->get_at(0); + } + + DynamicPrintConfig config{Slic3r::DynamicPrintConfig::full_print_config()}; + + Model two_cubes; + Model multimaterial_cubes; + + double retract_length{}; + double retract_length_toolchange{}; +}; + +TEST_CASE_METHOD(CancelObjectFixture, "Single extruder", "[CancelObject]") { + Print print; + print.apply(two_cubes, config); + print.validate(); + const std::string gcode{Test::gcode(print)}; + + if constexpr (debug_files) { + std::ofstream output{"single_extruder_two.gcode"}; + output << gcode; + } + + SECTION("One remaining") { + const std::string removed_object_gcode{remove_object(gcode, 0)}; + REQUIRE(removed_object_gcode.find("M486 S1\n") != std::string::npos); + if constexpr (debug_files) { + std::ofstream output{"single_extruder_one.gcode"}; + output << removed_object_gcode; + } + + check_retraction(removed_object_gcode); + } + + SECTION("All cancelled") { + const std::string removed_all_gcode{remove_object(remove_object(gcode, 0), 1)}; + + // First retraction is not compensated - set offset. + check_retraction(removed_all_gcode, retract_length); + } +} + +TEST_CASE_METHOD(CancelObjectFixture, "Sequential print", "[CancelObject]") { + config.set_deserialize_strict({{"complete_objects", 1}}); + + Print print; + print.apply(two_cubes, config); + print.validate(); + const std::string gcode{Test::gcode(print)}; + + if constexpr (debug_files) { + std::ofstream output{"sequential_print_two.gcode"}; + output << gcode; + } + + SECTION("One remaining") { + const std::string removed_object_gcode{remove_object(gcode, 0)}; + REQUIRE(removed_object_gcode.find("M486 S1\n") != std::string::npos); + if constexpr (debug_files) { + std::ofstream output{"sequential_print_one.gcode"}; + output << removed_object_gcode; + } + + check_retraction(removed_object_gcode); + } + + SECTION("All cancelled") { + const std::string removed_all_gcode{remove_object(remove_object(gcode, 0), 1)}; + + // First retraction is not compensated - set offset. + check_retraction(removed_all_gcode, retract_length); + } +} diff --git a/tests/fff_print/test_gcode.cpp b/tests/fff_print/test_gcode.cpp index d71ffab..1263b95 100644 --- a/tests/fff_print/test_gcode.cpp +++ b/tests/fff_print/test_gcode.cpp @@ -138,11 +138,15 @@ TEST_CASE("Extrusion, travels, temeperatures", "[GCode]") { Model model; Test::init_print({TestMesh::cube_20x20x20}, print, model, config, false, 2); std::string gcode = Test::gcode(print); + if constexpr (debug_files) { + std::ofstream gcode_file{"sequential_print.gcode"}; + gcode_file << gcode; + } parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { INFO("Unexpected E argument"); CHECK(!line.has_e()); - if (line.has_z()) { + if (line.has_z() && std::abs(line.dist_Z(self)) > 0) { z_moves.emplace_back(line.z()); } if (line.has_x() || line.has_y()) { diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 4b76f5a..201b730 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -42,6 +42,7 @@ add_executable(${_TEST_NAME}_tests test_anyptr.cpp test_jump_point_search.cpp test_support_spots_generator.cpp + test_layer_region.cpp ../data/prusaparts.cpp ../data/prusaparts.hpp test_static_map.cpp diff --git a/tests/libslic3r/test_layer_region.cpp b/tests/libslic3r/test_layer_region.cpp new file mode 100644 index 0000000..0f6dfb7 --- /dev/null +++ b/tests/libslic3r/test_layer_region.cpp @@ -0,0 +1,148 @@ +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/SVG.hpp" +#include +#include + +using namespace Slic3r; +using namespace Slic3r::Algorithm; + +constexpr bool export_svgs = false; + +ExPolygon rectangle(const Point& origin, const int width, const int height) { + return { + origin, + origin + Point{width, 0}, + origin + Point{width, height}, + origin + Point{0, height}, + }; +} + +struct LayerRegionFixture { + Surfaces surfaces{ + Surface{ + stBottomBridge, + rectangle({scaled(-1.0), scaled(0.0)}, scaled(1.0), scaled(1.0)) + }, + Surface{ + stBottomBridge, + rectangle({scaled(0.0), scaled(0.0)}, scaled(1.0), scaled(1.0)) + }, + Surface{ + stBottomBridge, + rectangle({scaled(-3.0), scaled(0.0)}, scaled(1.0), scaled(1.0)) + } + }; + + ExPolygons shells{{ + rectangle({scaled(-1.0), scaled(1.0)}, scaled(3.0), scaled(1.0)) + }}; + ExPolygons sparse {{ + rectangle({scaled(-2.0), scaled(-1.0)}, scaled(1.0), scaled(3.0)) + }}; + + const float scaled_spacing{scaled(0.3)}; + + static constexpr const float expansion_step = scaled(0.1); + static constexpr const size_t max_nr_expansion_steps = 5; + const float closing_radius = 0.55f * 0.65f * 1.05f * scaled_spacing; + const int shells_expansion_depth = scaled(0.6); + const RegionExpansionParameters expansion_params_into_solid_infill = RegionExpansionParameters::build( + shells_expansion_depth, + expansion_step, + max_nr_expansion_steps + ); + const int sparse_expansion_depth = scaled(0.3); + const RegionExpansionParameters expansion_params_into_sparse_infill = RegionExpansionParameters::build( + sparse_expansion_depth, + expansion_step, + max_nr_expansion_steps + ); + + std::vector expansion_zones{ + ExpansionZone{ + std::move(shells), + expansion_params_into_solid_infill, + }, + ExpansionZone{ + std::move(sparse), + expansion_params_into_sparse_infill, + } + }; +}; + +TEST_CASE_METHOD(LayerRegionFixture, "test the surface expansion", "[LayerRegion]") { + const double custom_angle{1.234f}; + + const Surfaces result{expand_merge_surfaces( + surfaces, stBottomBridge, + expansion_zones, + closing_radius, + custom_angle + )}; + + if constexpr (export_svgs) { + SVG svg("general_expansion.svg", BoundingBox{ + Point{scaled(-3.0), scaled(-1.0)}, + Point{scaled(2.0), scaled(2.0)} + }); + + svg.draw(surfaces, "blue"); + svg.draw(expansion_zones[0].expolygons, "green"); + svg.draw(expansion_zones[1].expolygons, "red"); + svg.draw_outline(result, "black", "", scale_(0.01)); + } + + REQUIRE(result.size() == 2); + CHECK(result.at(0).bridge_angle == Approx(custom_angle)); + CHECK(result.at(1).bridge_angle == Approx(custom_angle)); + CHECK(result.at(0).expolygon.contour.size() == 22); + CHECK(result.at(1).expolygon.contour.size() == 14); + + // These lines in the polygons should correspond to the expansion depth. + CHECK(result.at(0).expolygon.contour.lines().at(2).length() == shells_expansion_depth); + CHECK(result.at(1).expolygon.contour.lines().at(7).length() == sparse_expansion_depth); + CHECK(result.at(1).expolygon.contour.lines().at(11).length() == sparse_expansion_depth); + + CHECK(intersection_ex({result.at(0).expolygon}, expansion_zones[0].expolygons).size() == 0); + CHECK(intersection_ex({result.at(0).expolygon}, expansion_zones[1].expolygons).size() == 0); + CHECK(intersection_ex({result.at(1).expolygon}, expansion_zones[0].expolygons).size() == 0); + CHECK(intersection_ex({result.at(1).expolygon}, expansion_zones[1].expolygons).size() == 0); +} + +TEST_CASE_METHOD(LayerRegionFixture, "test the bridge expansion with the bridge angle detection", "[LayerRegion]") { + Surfaces result{expand_bridges_detect_orientations( + surfaces, + expansion_zones, + closing_radius + )}; + + if constexpr (export_svgs) { + SVG svg("bridge_expansion.svg", BoundingBox{ + Point{scaled(-3.0), scaled(-1.0)}, + Point{scaled(2.0), scaled(2.0)} + }); + + svg.draw(surfaces, "blue"); + svg.draw(expansion_zones[0].expolygons, "green"); + svg.draw(expansion_zones[1].expolygons, "red"); + svg.draw_outline(result, "black", "", scale_(0.01)); + } + + REQUIRE(result.size() == 2); + CHECK(result.at(0).bridge_angle == Approx(1.5707963268)); + CHECK(std::fmod(result.at(1).bridge_angle, M_PI) == Approx(0.0)); + CHECK(result.at(0).expolygon.contour.size() == 22); + CHECK(result.at(1).expolygon.contour.size() == 14); + + // These lines in the polygons should correspond to the expansion depth. + CHECK(result.at(0).expolygon.contour.lines().at(2).length() == shells_expansion_depth); + CHECK(result.at(1).expolygon.contour.lines().at(7).length() == sparse_expansion_depth); + CHECK(result.at(1).expolygon.contour.lines().at(11).length() == sparse_expansion_depth); + + CHECK(intersection_ex({result.at(0).expolygon}, expansion_zones[0].expolygons).size() == 0); + CHECK(intersection_ex({result.at(0).expolygon}, expansion_zones[1].expolygons).size() == 0); + CHECK(intersection_ex({result.at(1).expolygon}, expansion_zones[0].expolygons).size() == 0); + CHECK(intersection_ex({result.at(1).expolygon}, expansion_zones[1].expolygons).size() == 0); +}