From 126534997a9620bc9f3efc497b9da1d283eebb7e Mon Sep 17 00:00:00 2001 From: QIDI TECH <893239786@qq.com> Date: Mon, 5 May 2025 19:52:57 +0800 Subject: [PATCH] update libslic3r --- src/libslic3r/AABBTreeLines.hpp | 1 - src/libslic3r/AppConfig.cpp | 131 +- src/libslic3r/AppConfig.hpp | 6 +- .../BeadingStrategy/BeadingStrategy.hpp | 2 + .../BeadingStrategyFactory.cpp | 7 + .../LimitedBeadingStrategy.cpp | 6 +- .../OuterWallContourStrategy.cpp | 83 + .../OuterWallContourStrategy.hpp | 29 + .../Arachne/SkeletalTrapezoidation.cpp | 38 +- .../Arachne/SkeletalTrapezoidation.hpp | 8 +- .../Arachne/SkeletalTrapezoidationEdge.hpp | 11 + .../Arachne/SkeletalTrapezoidationGraph.cpp | 15 +- src/libslic3r/Arachne/WallToolPaths.cpp | 93 +- src/libslic3r/Arachne/WallToolPaths.hpp | 15 + .../Arachne/utils/ExtrusionJunction.cpp | 5 +- .../Arachne/utils/ExtrusionJunction.hpp | 6 +- src/libslic3r/Arachne/utils/ExtrusionLine.cpp | 16 +- src/libslic3r/Arachne/utils/ExtrusionLine.hpp | 2 + src/libslic3r/Arrange.cpp | 106 +- src/libslic3r/Arrange.hpp | 3 +- src/libslic3r/BoundingBox.cpp | 7 + src/libslic3r/BoundingBox.hpp | 4 +- src/libslic3r/Brim.cpp | 54 +- src/libslic3r/BrimEarsPoint.hpp | 5 + src/libslic3r/BuildVolume.cpp | 202 +- src/libslic3r/BuildVolume.hpp | 49 +- src/libslic3r/CMakeLists.txt | 24 +- .../CSGMesh/PerformCSGMeshBooleans.hpp | 4 +- src/libslic3r/Calib.cpp | 14 +- src/libslic3r/Calib.hpp | 55 +- src/libslic3r/Clipper2Utils.cpp | 153 +- src/libslic3r/Clipper2Utils.hpp | 8 +- src/libslic3r/ClipperUtils.cpp | 7 + src/libslic3r/ClipperUtils.hpp | 3 + src/libslic3r/Color.cpp | 21 + src/libslic3r/Color.hpp | 4 +- src/libslic3r/Config.cpp | 185 +- src/libslic3r/Config.hpp | 433 ++- src/libslic3r/ElephantFootCompensation.cpp | 23 +- src/libslic3r/Emboss.cpp | 26 +- src/libslic3r/Emboss.hpp | 2 + src/libslic3r/ExPolygon.cpp | 76 + src/libslic3r/ExPolygon.hpp | 9 +- src/libslic3r/Extruder.cpp | 31 +- src/libslic3r/Extruder.hpp | 13 +- src/libslic3r/ExtrusionEntity.cpp | 18 +- src/libslic3r/ExtrusionEntity.hpp | 66 +- src/libslic3r/ExtrusionEntityCollection.cpp | 2 + src/libslic3r/ExtrusionEntityCollection.hpp | 8 +- src/libslic3r/FilamentGroup.cpp | 899 +++++ src/libslic3r/FilamentGroup.hpp | 200 ++ src/libslic3r/FilamentGroupUtils.cpp | 278 ++ src/libslic3r/FilamentGroupUtils.hpp | 89 + src/libslic3r/Fill/Fill.cpp | 256 +- src/libslic3r/Fill/Fill3DHoneycomb.hpp | 4 + src/libslic3r/Fill/FillAdaptive.hpp | 1 + src/libslic3r/Fill/FillBase.cpp | 24 +- src/libslic3r/Fill/FillBase.hpp | 17 +- src/libslic3r/Fill/FillConcentric.hpp | 1 + src/libslic3r/Fill/FillConcentricInternal.hpp | 1 + src/libslic3r/Fill/FillCrossHatch.hpp | 1 + src/libslic3r/Fill/FillFloatingConcentric.cpp | 888 +++++ src/libslic3r/Fill/FillFloatingConcentric.hpp | 77 + src/libslic3r/Fill/FillGyroid.hpp | 1 + src/libslic3r/Fill/FillHoneycomb.hpp | 1 + src/libslic3r/Fill/FillLightning.cpp | 1 + src/libslic3r/Fill/FillLightning.hpp | 1 + src/libslic3r/Fill/FillLine.hpp | 1 + src/libslic3r/Fill/FillPlanePath.hpp | 1 + src/libslic3r/Fill/FillRectilinear.cpp | 269 +- src/libslic3r/Fill/FillRectilinear.hpp | 34 +- .../Fill/Lightning/DistanceField.cpp | 4 +- src/libslic3r/Flow.cpp | 2 +- src/libslic3r/FlushVolCalc.cpp | 114 +- src/libslic3r/FlushVolCalc.hpp | 8 +- src/libslic3r/FlushVolPredictor.cpp | 45 +- src/libslic3r/FlushVolPredictor.hpp | 28 +- src/libslic3r/Format/AMF.cpp | 39 +- src/libslic3r/Format/OBJ.cpp | 44 +- src/libslic3r/Format/OBJ.hpp | 23 +- src/libslic3r/Format/STEP.cpp | 667 ++-- src/libslic3r/Format/STEP.hpp | 16 +- src/libslic3r/Format/qds_3mf.cpp | 351 +- src/libslic3r/Format/qds_3mf.hpp | 10 + src/libslic3r/Frustum.cpp | 92 +- src/libslic3r/Frustum.hpp | 32 +- src/libslic3r/GCode.cpp | 1871 +++++++--- src/libslic3r/GCode.hpp | 81 +- .../GCode/AvoidCrossingPerimeters.cpp | 33 +- src/libslic3r/GCode/ConflictChecker.cpp | 20 +- src/libslic3r/GCode/ConflictChecker.hpp | 3 +- src/libslic3r/GCode/CoolingBuffer.cpp | 857 +---- src/libslic3r/GCode/CoolingBuffer.hpp | 83 +- src/libslic3r/GCode/GCodeEditor.cpp | 636 ++++ src/libslic3r/GCode/GCodeEditor.hpp | 299 ++ src/libslic3r/GCode/GCodeProcessor.cpp | 2728 ++++++++++++--- src/libslic3r/GCode/GCodeProcessor.hpp | 421 ++- src/libslic3r/GCode/SeamPlacer.cpp | 110 +- src/libslic3r/GCode/SeamPlacer.hpp | 8 +- src/libslic3r/GCode/Smoothing.cpp | 224 ++ src/libslic3r/GCode/Smoothing.hpp | 103 + src/libslic3r/GCode/ThumbnailData.hpp | 3 + src/libslic3r/GCode/ToolOrderUtils.cpp | 759 ++++ src/libslic3r/GCode/ToolOrderUtils.hpp | 114 + src/libslic3r/GCode/ToolOrdering.cpp | 1014 ++++-- src/libslic3r/GCode/ToolOrdering.hpp | 86 +- src/libslic3r/GCode/WipeTower.cpp | 3054 ++++++++++++++++- src/libslic3r/GCode/WipeTower.hpp | 197 +- src/libslic3r/GCodeReader.hpp | 23 +- src/libslic3r/GCodeWriter.cpp | 292 +- src/libslic3r/GCodeWriter.hpp | 68 +- src/libslic3r/Geometry.cpp | 21 +- src/libslic3r/Geometry.hpp | 2 +- src/libslic3r/Geometry/VoronoiUtils.cpp | 2 + .../Interlocking/InterlockingGenerator.cpp | 333 ++ .../Interlocking/InterlockingGenerator.hpp | 172 + src/libslic3r/Interlocking/VoxelUtils.cpp | 219 ++ src/libslic3r/Interlocking/VoxelUtils.hpp | 212 ++ src/libslic3r/Layer.cpp | 192 +- src/libslic3r/Layer.hpp | 13 +- src/libslic3r/LayerRegion.cpp | 385 +-- src/libslic3r/MacUtils.hpp | 2 +- src/libslic3r/MacUtils.mm | 16 +- src/libslic3r/MeshBoolean.cpp | 69 +- src/libslic3r/Model.cpp | 233 +- src/libslic3r/Model.hpp | 51 +- src/libslic3r/ModelArrange.cpp | 17 +- src/libslic3r/MultiMaterialSegmentation.cpp | 26 +- src/libslic3r/MultiPoint.cpp | 22 + src/libslic3r/MultiPoint.hpp | 3 +- src/libslic3r/ObjColorUtils.cpp | 5 +- src/libslic3r/ObjColorUtils.hpp | 3 +- src/libslic3r/ParameterUtils.cpp | 32 + src/libslic3r/ParameterUtils.hpp | 2 + src/libslic3r/PerimeterGenerator.cpp | 496 ++- src/libslic3r/PerimeterGenerator.hpp | 15 +- src/libslic3r/PlaceholderParser.cpp | 349 +- src/libslic3r/Point.cpp | 43 + src/libslic3r/Point.hpp | 39 +- src/libslic3r/Polygon.cpp | 35 + src/libslic3r/Polygon.hpp | 4 + src/libslic3r/Polyline.cpp | 52 + src/libslic3r/Polyline.hpp | 4 + src/libslic3r/Preset.cpp | 440 ++- src/libslic3r/Preset.hpp | 24 +- src/libslic3r/PresetBundle.cpp | 754 +++- src/libslic3r/PresetBundle.hpp | 85 +- src/libslic3r/Print.cpp | 582 +++- src/libslic3r/Print.hpp | 170 +- src/libslic3r/PrintApply.cpp | 150 +- src/libslic3r/PrintBase.hpp | 5 +- src/libslic3r/PrintConfig.cpp | 2339 ++++++++++++- src/libslic3r/PrintConfig.hpp | 367 +- src/libslic3r/PrintObject.cpp | 757 ++-- src/libslic3r/PrintObjectSlice.cpp | 38 +- src/libslic3r/ProjectTask.hpp | 53 +- src/libslic3r/SLAPrint.cpp | 2 +- src/libslic3r/SLAPrint.hpp | 2 +- src/libslic3r/SVG.cpp | 360 +- src/libslic3r/SVG.hpp | 8 + src/libslic3r/Slicing.cpp | 28 +- src/libslic3r/Support/SupportCommon.cpp | 158 +- src/libslic3r/Support/SupportCommon.hpp | 74 + src/libslic3r/Support/SupportLayer.hpp | 2 + src/libslic3r/Support/SupportParameters.hpp | 102 +- src/libslic3r/Support/TreeModelVolumes.cpp | 15 +- src/libslic3r/Support/TreeSupport.cpp | 862 +++-- src/libslic3r/Support/TreeSupport.hpp | 4 +- src/libslic3r/Support/TreeSupport3D.cpp | 120 +- src/libslic3r/Support/TreeSupportCommon.hpp | 7 +- src/libslic3r/Surface.cpp | 1 + src/libslic3r/Surface.hpp | 6 +- src/libslic3r/TriangleMesh.cpp | 6 +- src/libslic3r/TriangleSelector.cpp | 92 +- src/libslic3r/TriangleSelector.hpp | 9 +- src/libslic3r/Utils.hpp | 40 +- src/libslic3r/VectorFormatter.hpp | 40 + src/libslic3r/libslic3r.h | 4 +- src/libslic3r/libslic3r_version.h.in | 1 + src/libslic3r/utils.cpp | 40 + 180 files changed, 24833 insertions(+), 5679 deletions(-) create mode 100644 src/libslic3r/Arachne/BeadingStrategy/OuterWallContourStrategy.cpp create mode 100644 src/libslic3r/Arachne/BeadingStrategy/OuterWallContourStrategy.hpp create mode 100644 src/libslic3r/FilamentGroup.cpp create mode 100644 src/libslic3r/FilamentGroup.hpp create mode 100644 src/libslic3r/FilamentGroupUtils.cpp create mode 100644 src/libslic3r/FilamentGroupUtils.hpp create mode 100644 src/libslic3r/Fill/FillFloatingConcentric.cpp create mode 100644 src/libslic3r/Fill/FillFloatingConcentric.hpp create mode 100644 src/libslic3r/GCode/GCodeEditor.cpp create mode 100644 src/libslic3r/GCode/GCodeEditor.hpp create mode 100644 src/libslic3r/GCode/Smoothing.cpp create mode 100644 src/libslic3r/GCode/Smoothing.hpp create mode 100644 src/libslic3r/GCode/ToolOrderUtils.cpp create mode 100644 src/libslic3r/GCode/ToolOrderUtils.hpp create mode 100644 src/libslic3r/Interlocking/InterlockingGenerator.cpp create mode 100644 src/libslic3r/Interlocking/InterlockingGenerator.hpp create mode 100644 src/libslic3r/Interlocking/VoxelUtils.cpp create mode 100644 src/libslic3r/Interlocking/VoxelUtils.hpp create mode 100644 src/libslic3r/VectorFormatter.hpp diff --git a/src/libslic3r/AABBTreeLines.hpp b/src/libslic3r/AABBTreeLines.hpp index 21678bf..aab7301 100644 --- a/src/libslic3r/AABBTreeLines.hpp +++ b/src/libslic3r/AABBTreeLines.hpp @@ -2,7 +2,6 @@ #define SRC_LIBSLIC3R_AABBTREELINES_HPP_ #include "Point.hpp" -#include "Utils.hpp" #include "libslic3r.h" #include "libslic3r/AABBTreeIndirect.hpp" #include "libslic3r/Line.hpp" diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 2704791..b8d44c0 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -131,7 +131,8 @@ void AppConfig::set_defaults() if (get("single_instance").empty()) set_bool("single_instance", false); - + if (get("import_3mf_as_project").empty()) + set_bool("import_3mf_as_project", true); #ifdef SUPPORT_REMEMBER_OUTPUT_PATH if (get("remember_output_path").empty()) set_bool("remember_output_path", true); @@ -169,6 +170,12 @@ void AppConfig::set_defaults() if (get("reverse_mouse_wheel_zoom").empty()) set_bool("reverse_mouse_wheel_zoom", false); #endif + if (get("enable_append_color_by_sync_ams").empty()) + set_bool("enable_append_color_by_sync_ams", true); + if (get("enable_merge_color_by_sync_ams").empty()) + set_bool("enable_merge_color_by_sync_ams", false); + if (get("ams_sync_match_full_use_color_dist").empty()) + set_bool("ams_sync_match_full_use_color_dist", false); if (get("zoom_to_mouse").empty()) set_bool("zoom_to_mouse", false); @@ -176,6 +183,8 @@ void AppConfig::set_defaults() set_bool("show_shells_in_preview", true); if (get("enable_lod").empty()) set_bool("enable_lod", true); + if (get("gamma_correct_in_import_obj").empty()) + set_bool("gamma_correct_in_import_obj", false); if (get("enable_opengl_multi_instance").empty()) set_bool("enable_opengl_multi_instance", true); if (get("import_single_svg_and_split").empty()) @@ -186,11 +195,26 @@ void AppConfig::set_defaults() set("grabber_size_factor", "1.0"); //#ifdef SUPPORT_SHOW_HINTS if (get("show_hints").empty()) - set_bool("show_hints", true); + set_bool("show_hints", false); //#endif if (get("enable_multi_machine").empty()) set_bool("enable_multi_machine", false); + if (get("prefer_to_use_dgpu").empty()) + set_bool("prefer_to_use_dgpu", false); + + if (get("msaa_type").empty()) + set("msaa_type", "X4"); + + if (get("enable_advanced_antialiasing").empty()) + set_bool("enable_advanced_antialiasing", false); + + if (get("gizmo_keep_screen_size").empty()) + set_bool("gizmo_keep_screen_size", true); + + if (get("show_3d_navigator").empty()) + set_bool("show_3d_navigator", true); + //w13 if (get("enable_seal").empty()) set_bool("enable_seal", true); @@ -262,13 +286,25 @@ void AppConfig::set_defaults() if (get("show_daily_tips").empty()) { set_bool("show_daily_tips", true); } - //true is auto calculate - if (get("auto_calculate").empty()) { - set_bool("auto_calculate", true); + + if (get("auto_calculate_flush").empty()){ + set("auto_calculate_flush","all"); } - if (get("auto_calculate_when_filament_change").empty()){ - set_bool("auto_calculate_when_filament_change", true); + if (get("enable_high_low_temp_mixed_printing").empty()){ + set_bool("enable_high_low_temp_mixed_printing", false); + } + + if (get("ignore_ext_filament_in_filament_map").empty()){ + set_bool("ignore_ext_filament_in_filament_map", false); + } + + if (get("pop_up_filament_map_dialog").empty()){ + set_bool("pop_up_filament_map_dialog", false); + } + + if (get("prefered_filament_map_mode").empty()){ + set("prefered_filament_map_mode",ConfigOptionEnum::get_enum_names()[FilamentMapMode::fmmAutoForFlush]); } if (get("show_home_page").empty()) { @@ -287,6 +323,10 @@ void AppConfig::set_defaults() set("units", "0"); } + if (get("auto_transfer_when_switch_preset").empty()) { + set("auto_transfer_when_switch_preset", "true"); + } + if (get("sync_user_preset").empty()) { set_bool("sync_user_preset", false); } @@ -407,6 +447,13 @@ void AppConfig::set_defaults() set_bool("is_split_compound", false); } + if (get("play_slicing_video").empty()) { + set_bool("play_slicing_video", true); + } + if (get("play_tpu_printing_video").empty()) { + set_bool("play_tpu_printing_video", true); + } + // 10 if (get("machine_list_net").empty()) set("machine_list_net", "0"); @@ -487,23 +534,34 @@ std::string AppConfig::load() std::string error_message; try { - ifs.open(AppConfig::loading_path()); + auto path = AppConfig::loading_path(); + ifs.open(path); + if (!ifs.is_open()) { + BOOST_LOG_TRIVIAL(info) << "AppConfig::load() open fail:" << AppConfig::loading_path(); + return "Line break format may be incorrect."; + } #ifdef WIN32 std::stringstream input_stream; input_stream << ifs.rdbuf(); std::string total_string = input_stream.str(); - size_t last_pos = total_string.find_last_of('}'); - std::string left_string = total_string.substr(0, last_pos+1); - //skip the "\n" - std::string right_string = total_string.substr(last_pos+2); + if (total_string.empty()) { + BOOST_LOG_TRIVIAL(info) << "AppConfig::load() read fail:" << AppConfig::loading_path(); + return "read fail."; + } else { + size_t last_pos = total_string.find_last_of('}'); + std::string left_string = total_string.substr(0, last_pos + 1); + // skip the "\n" + std::string right_string = total_string.substr(last_pos + 2); - std::string md5_str = appconfig_md5_hash_line({left_string.data()}); - // Verify the checksum of the config file without taking just for debugging purpose. - if (md5_str != right_string) - BOOST_LOG_TRIVIAL(info) << "The configuration file " << AppConfig::loading_path() << - " has a wrong MD5 checksum or the checksum is missing. This may indicate a file corruption or a harmless user edit."; - j = json::parse(left_string); + std::string md5_str = appconfig_md5_hash_line({left_string.data()}); + // Verify the checksum of the config file without taking just for debugging purpose. + if (md5_str != right_string) { + BOOST_LOG_TRIVIAL(info) << "The configuration file " << AppConfig::loading_path() + << " has a wrong MD5 checksum or the checksum is missing. This may indicate a file corruption or a harmless user edit."; + } + j = json::parse(left_string); + } #else ifs >> j; #endif @@ -603,6 +661,10 @@ std::string AppConfig::load() for (auto cali_it = calis_j["presets"].begin(); cali_it != calis_j["presets"].end(); cali_it++) { CaliPresetInfo preset_info; preset_info.tray_id = cali_it.value()["tray_id"].get(); + if (cali_it.value().contains("extruder_id")) + preset_info.extruder_id = cali_it.value()["extruder_id"].get(); + if (cali_it.value().contains("nozzle_volume_type")) + preset_info.nozzle_volume_type = NozzleVolumeType(cali_it.value()["nozzle_volume_type"].get()); preset_info.nozzle_diameter = cali_it.value()["nozzle_diameter"].get(); preset_info.filament_id = cali_it.value()["filament_id"].get(); preset_info.setting_id = cali_it.value()["setting_id"].get(); @@ -630,7 +692,7 @@ std::string AppConfig::load() if (iter.value().is_string()) m_storage[it.key()][iter.key()] = iter.value().get(); else { - BOOST_LOG_TRIVIAL(trace) << "load config warning..."; + BOOST_LOG_TRIVIAL(warning) << "load config warning..."; } } } @@ -720,6 +782,8 @@ void AppConfig::save() for (auto filament_preset : cali_info.selected_presets) { json preset_json; preset_json["tray_id"] = filament_preset.tray_id; + preset_json["extruder_id"] = filament_preset.extruder_id; + preset_json["nozzle_volume_type"] = int(filament_preset.nozzle_volume_type); preset_json["nozzle_diameter"] = filament_preset.nozzle_diameter; preset_json["filament_id"] = filament_preset.filament_id; preset_json["setting_id"] = filament_preset.setting_id; @@ -1112,7 +1176,8 @@ void AppConfig::set_recent_projects(const std::vector& recent_proje for (unsigned int i = 0; i < (unsigned int)recent_projects.size(); ++i) { auto n = std::to_string(i + 1); - if (n.length() == 1) n = "0" + n; + if (n.length() == 1) n = "00" + n; + else if (n.length() == 2) n = "0" + n; it->second[n] = recent_projects[i]; } } @@ -1295,6 +1360,32 @@ std::vector AppConfig::get_custom_color_from_config() return colors; } +void AppConfig::save_nozzle_volume_types_to_config(const std::string& printer_name, const std::string& nozzle_volume_types) +{ + if (!has_section("nozzle_volume_types")) { + std::map data; + data[printer_name] = nozzle_volume_types; + set_section("nozzle_volume_types", data); + } else { + auto data = get_section("nozzle_volume_types"); + auto data_modify = const_cast&>(data); + data_modify[printer_name] = nozzle_volume_types; + set_section("nozzle_volume_types", data_modify); + } +} + +std::string AppConfig::get_nozzle_volume_types_from_config(const std::string& printer_name) +{ + std::string nozzle_volume_types; + if (has_section("nozzle_volume_types")) { + auto data = get_section("nozzle_volume_types"); + if (data.find(printer_name) != data.end()) + nozzle_volume_types = data[printer_name]; + } + + return nozzle_volume_types; +} + void AppConfig::reset_selections() { auto it = m_storage.find("presets"); diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index fbcbced..469ddf7 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -203,6 +203,10 @@ public: void save_custom_color_to_config(const std::vector &colors); std::vector get_custom_color_from_config(); + + void save_nozzle_volume_types_to_config(const std::string& printer_name, const std::string& nozzle_volume_types); + std::string get_nozzle_volume_types_from_config(const std::string& printer_name); + // reset the current print / filament / printer selections, so that // the PresetBundle::load_selections(const AppConfig &config) call will select // the first non-default preset when called. @@ -256,7 +260,7 @@ public: private: template - bool get_3dmouse_device_numeric_value(const std::string &device_name, const char *parameter_name, T &out) const + bool get_3dmouse_device_numeric_value(const std::string &device_name, const char *parameter_name, T &out) const { std::string key = std::string("mouse_device:") + device_name; auto it = m_storage.find(key); diff --git a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp index 99e3823..9b9c89c 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp +++ b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp @@ -13,6 +13,8 @@ namespace Slic3r::Arachne template constexpr T pi_div(const T div) { return static_cast(M_PI) / div; } +constexpr int WallContourMarkedWidth = 0; +constexpr int FirstWallContourMarkedWidth = 1; /*! * Mostly virtual base class template. * diff --git a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.cpp b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.cpp index 4044c90..7a2fdac 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.cpp +++ b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.cpp @@ -8,6 +8,7 @@ #include "DistributedBeadingStrategy.hpp" #include "RedistributeBeadingStrategy.hpp" #include "OuterWallInsetBeadingStrategy.hpp" +#include "OuterWallContourStrategy.hpp" #include #include @@ -44,6 +45,12 @@ BeadingStrategyPtr BeadingStrategyFactory::makeStrategy( ret = std::make_unique(outer_wall_offset, std::move(ret)); } +// this beading strategy will cause junctions with different idx link together,to be fixed later +#if 0 + //Apply the OuterWallContourStrategy last, since that adds a 1-width marker wall to mark the boundary of first beading. + BOOST_LOG_TRIVIAL(debug) << "Applying the First Beading Contour Strategy."; + ret = std::make_unique(std::move(ret)); +#endif //Apply the LimitedBeadingStrategy last, since that adds a 0-width marker wall which other beading strategies shouldn't touch. BOOST_LOG_TRIVIAL(debug) << "Applying the Limited Beading meta-strategy with maximum bead count = " << max_bead_count << "."; ret = std::make_unique(max_bead_count, std::move(ret)); diff --git a/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp index 97d854b..9a31b3b 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp +++ b/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp @@ -48,7 +48,7 @@ LimitedBeadingStrategy::Beading LimitedBeadingStrategy::compute(coord_t thicknes const coord_t innermost_toolpath_location = ret.toolpath_locations[max_bead_count / 2 - 1]; const coord_t innermost_toolpath_width = ret.bead_widths[max_bead_count / 2 - 1]; ret.toolpath_locations.insert(ret.toolpath_locations.begin() + max_bead_count / 2, innermost_toolpath_location + innermost_toolpath_width / 2); - ret.bead_widths.insert(ret.bead_widths.begin() + max_bead_count / 2, 0); + ret.bead_widths.insert(ret.bead_widths.begin() + max_bead_count / 2, WallContourMarkedWidth); } return ret; } @@ -77,14 +77,14 @@ LimitedBeadingStrategy::Beading LimitedBeadingStrategy::compute(coord_t thicknes coord_t innermost_toolpath_location = ret.toolpath_locations[max_bead_count / 2 - 1]; coord_t innermost_toolpath_width = ret.bead_widths[max_bead_count / 2 - 1]; ret.toolpath_locations.insert(ret.toolpath_locations.begin() + max_bead_count / 2, innermost_toolpath_location + innermost_toolpath_width / 2); - ret.bead_widths.insert(ret.bead_widths.begin() + max_bead_count / 2, 0); + ret.bead_widths.insert(ret.bead_widths.begin() + max_bead_count / 2, WallContourMarkedWidth); //Symmetry on both sides. Symmetry is guaranteed since this code is stopped early if the bead_count <= max_bead_count, and never reaches this point then. const size_t opposite_bead = bead_count - (max_bead_count / 2 - 1); innermost_toolpath_location = ret.toolpath_locations[opposite_bead]; innermost_toolpath_width = ret.bead_widths[opposite_bead]; ret.toolpath_locations.insert(ret.toolpath_locations.begin() + opposite_bead, innermost_toolpath_location - innermost_toolpath_width / 2); - ret.bead_widths.insert(ret.bead_widths.begin() + opposite_bead, 0); + ret.bead_widths.insert(ret.bead_widths.begin() + opposite_bead, WallContourMarkedWidth); return ret; } diff --git a/src/libslic3r/Arachne/BeadingStrategy/OuterWallContourStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/OuterWallContourStrategy.cpp new file mode 100644 index 0000000..c082e0e --- /dev/null +++ b/src/libslic3r/Arachne/BeadingStrategy/OuterWallContourStrategy.cpp @@ -0,0 +1,83 @@ + +#include "OuterWallContourStrategy.hpp" +#include "Point.hpp" + +namespace Slic3r::Arachne +{ + + +OuterWallContourStrategy::OuterWallContourStrategy(BeadingStrategyPtr parent) + : BeadingStrategy(*parent) + , parent(std::move(parent)) +{ +} + +std::string OuterWallContourStrategy::toString() const +{ + return std::string("OuterWallContourStrategy+") + parent->toString(); +} + +coord_t OuterWallContourStrategy::getTransitioningLength(coord_t lower_bead_count) const +{ + return parent->getTransitioningLength(lower_bead_count); +} + +float OuterWallContourStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const +{ + return parent->getTransitionAnchorPos(lower_bead_count); +} + +std::vector OuterWallContourStrategy::getNonlinearThicknesses(coord_t lower_bead_count) const +{ + return parent->getNonlinearThicknesses(lower_bead_count); +} + + +coord_t OuterWallContourStrategy::getTransitionThickness(coord_t lower_bead_count) const +{ + if(lower_bead_count <= 1) + return parent->getTransitionThickness(lower_bead_count); + else if(lower_bead_count == 2 || lower_bead_count ==3) + return parent->getTransitionThickness(1); + return parent->getTransitionThickness(lower_bead_count-2); +} + + +coord_t OuterWallContourStrategy::getOptimalBeadCount(coord_t thickness) const +{ + coord_t parent_bead_count = parent->getOptimalBeadCount(thickness); + if(parent_bead_count <= 1) + return parent_bead_count; + return parent_bead_count + 2; +} + + +coord_t OuterWallContourStrategy::getOptimalThickness(coord_t bead_count) const +{ + if (bead_count <= 1) + return parent->getOptimalThickness(bead_count); + return parent->getOptimalThickness(bead_count - 2) + 2; +} + +BeadingStrategy::Beading OuterWallContourStrategy::compute(coord_t thickness, coord_t bead_count) const +{ + if (bead_count <= 1) + return parent->compute(thickness, bead_count); + + assert(bead_count >= 3); + Beading ret = parent->compute(thickness, bead_count - 2); + if(ret.toolpath_locations.size() == 1){ + return ret; + } + if(ret.toolpath_locations.size() > 0 ){ + assert(ret.bead_widths.size()>0); + double location = ret.toolpath_locations.front() + ret.bead_widths.front() / 2; + double location_reverse = ret.toolpath_locations.back() - ret.bead_widths.back() / 2; + ret.toolpath_locations.insert(ret.toolpath_locations.begin()+1, location); + ret.bead_widths.insert(ret.bead_widths.begin()+1, FirstWallContourMarkedWidth); + ret.toolpath_locations.insert((ret.toolpath_locations.rbegin()+1).base(), location_reverse); + ret.bead_widths.insert((ret.bead_widths.rbegin()).base(), FirstWallContourMarkedWidth); + } + return ret; +} +} // namespace Slic3r::Arachne \ No newline at end of file diff --git a/src/libslic3r/Arachne/BeadingStrategy/OuterWallContourStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/OuterWallContourStrategy.hpp new file mode 100644 index 0000000..1ecb4b4 --- /dev/null +++ b/src/libslic3r/Arachne/BeadingStrategy/OuterWallContourStrategy.hpp @@ -0,0 +1,29 @@ +#ifndef OUTER_WALL_CONTOUR_STRATEGY_H +#define OUTER_WALL_CONTOUR_STRATEGY_H + +#include "BeadingStrategy.hpp" +namespace Slic3r::Arachne +{ + +class OuterWallContourStrategy : public BeadingStrategy +{ +public: + OuterWallContourStrategy(BeadingStrategyPtr parent); + ~OuterWallContourStrategy() override = default; + + Beading compute(coord_t thickness, coord_t bead_count) const override; + coord_t getOptimalThickness(coord_t bead_count) const override; + coord_t getTransitionThickness(coord_t lower_bead_count) const override; + coord_t getOptimalBeadCount(coord_t thickness) const override; + std::string toString() const override; + + coord_t getTransitioningLength(coord_t lower_bead_count) const override; + float getTransitionAnchorPos(coord_t lower_bead_count) const override; + std::vector getNonlinearThicknesses(coord_t lower_bead_count) const override; + +protected: + const BeadingStrategyPtr parent; +}; + +} +#endif \ No newline at end of file diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp index 5d085f3..fa377f1 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp @@ -104,7 +104,7 @@ SkeletalTrapezoidation::node_t &SkeletalTrapezoidation::makeNode(const VD::verte } } -void SkeletalTrapezoidation::transferEdge(Point from, Point to, const VD::edge_type &vd_edge, edge_t *&prev_edge, Point &start_source_point, Point &end_source_point, const std::vector &segments) { +void SkeletalTrapezoidation::transferEdge(Point from, Point to, const VD::edge_type &vd_edge, edge_t *&prev_edge, Point &start_source_point, Point &end_source_point, const std::vector &segments, const bool hole_compensation_flag) { auto he_edge_it = vd_edge_to_he_edge.find(vd_edge.twin()); if (he_edge_it != vd_edge_to_he_edge.end()) { // Twin segment(s) have already been made @@ -128,7 +128,7 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, const VD::edge_t edge->twin = twin; twin->twin = edge; edge->from->incident_edge = edge; - + edge->data.setHoleCompensationFlag(hole_compensation_flag); if (prev_edge) { edge->prev = prev_edge; @@ -192,7 +192,8 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, const VD::edge_t edge->from = v0; edge->to = v1; edge->from->incident_edge = edge; - + edge->data.setHoleCompensationFlag(hole_compensation_flag); + if (prev_edge) { edge->prev = prev_edge; @@ -373,13 +374,16 @@ bool SkeletalTrapezoidation::computePointCellRange(const VD::cell_type &cell, Po SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const BeadingStrategy& beading_strategy, double transitioning_angle, coord_t discretization_step_size, coord_t transition_filter_dist, coord_t allowed_filter_deviation, - coord_t beading_propagation_transition_dist + coord_t beading_propagation_transition_dist, bool enable_hole_compensation, + const std::vector& hole_indices ): transitioning_angle(transitioning_angle), discretization_step_size(discretization_step_size), transition_filter_dist(transition_filter_dist), allowed_filter_deviation(allowed_filter_deviation), beading_propagation_transition_dist(beading_propagation_transition_dist), - beading_strategy(beading_strategy) + beading_strategy(beading_strategy), + enable_hole_compensation(enable_hole_compensation), + hole_indices(hole_indices) { constructFromPolygons(polys); } @@ -390,6 +394,8 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) this->outline = polys; #endif + std::set hole_indices_(this->hole_indices.begin(), this->hole_indices.end()); + // Check self intersections. assert([&polys]() -> bool { EdgeGrid::Grid grid; @@ -436,10 +442,15 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) const VD::edge_type *ending_voronoi_edge = nullptr; // Compute and store result in above variables + bool apply_hole_compensation = this->enable_hole_compensation; + if (cell.contains_point()) { const bool keep_going = computePointCellRange(cell, start_source_point, end_source_point, starting_voronoi_edge, ending_voronoi_edge, segments); if (!keep_going) continue; + + const PolygonsPointIndex source_point_idx = Geometry::VoronoiUtils::get_source_point_index(cell, segments.begin(), segments.end()); + apply_hole_compensation &= hole_indices_.find(source_point_idx.poly_idx) != hole_indices_.end(); } else { assert(cell.contains_segment()); Geometry::SegmentCellRange cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, segments.cbegin(), segments.cend()); @@ -448,6 +459,9 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) end_source_point = cell_range.segment_end_point; starting_voronoi_edge = cell_range.edge_begin; ending_voronoi_edge = cell_range.edge_end; + + const Segment& source_segment = Geometry::VoronoiUtils::get_source_segment(cell, segments.cbegin(), segments.cend()); + apply_hole_compensation &= hole_indices_.find(source_segment.poly_idx) != hole_indices_.end(); } if (!starting_voronoi_edge || !ending_voronoi_edge) { @@ -458,8 +472,8 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) // Copy start to end edge to graph assert(Geometry::VoronoiUtils::is_in_range(*starting_voronoi_edge)); edge_t *prev_edge = nullptr; - transferEdge(start_source_point, Geometry::VoronoiUtils::to_point(starting_voronoi_edge->vertex1()).cast(), *starting_voronoi_edge, prev_edge, start_source_point, end_source_point, segments); - node_t *starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()]; + transferEdge(start_source_point, Geometry::VoronoiUtils::to_point(starting_voronoi_edge->vertex1()).cast(), *starting_voronoi_edge, prev_edge, start_source_point, end_source_point, segments,apply_hole_compensation); + node_t *starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()]; starting_node->data.distance_to_boundary = 0; constexpr bool is_next_to_start_or_end = true; @@ -470,11 +484,11 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) Point v1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex0()).cast(); Point v2 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()).cast(); - transferEdge(v1, v2, *vd_edge, prev_edge, start_source_point, end_source_point, segments); + transferEdge(v1, v2, *vd_edge, prev_edge, start_source_point, end_source_point, segments,apply_hole_compensation); graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_voronoi_edge); } - transferEdge(Geometry::VoronoiUtils::to_point(ending_voronoi_edge->vertex0()).cast(), end_source_point, *ending_voronoi_edge, prev_edge, start_source_point, end_source_point, segments); + transferEdge(Geometry::VoronoiUtils::to_point(ending_voronoi_edge->vertex0()).cast(), end_source_point, *ending_voronoi_edge, prev_edge, start_source_point, end_source_point, segments, apply_hole_compensation); prev_edge->to->data.distance_to_boundary = 0; } @@ -1775,6 +1789,8 @@ void SkeletalTrapezoidation::generateJunctions(ptr_vector_t& continue; } + bool apply_hole_compensation = edge->data.getHoleCompensationFlag(); + Beading* beading = &getOrCreateBeading(edge->to, node_beadings)->beading; edge_junctions.emplace_back(std::make_shared()); edge_.data.setExtrusionJunctions(edge_junctions.back()); // initialization @@ -1828,7 +1844,7 @@ void SkeletalTrapezoidation::generateJunctions(ptr_vector_t& { // Snap to start node if it is really close, in order to be able to see 3-way intersection later on more robustly junction = a; } - ret.emplace_back(junction, beading->bead_widths[junction_idx], junction_idx); + ret.emplace_back(ExtrusionJunction(junction, beading->bead_widths[junction_idx], junction_idx, apply_hole_compensation)); } } } @@ -2113,7 +2129,7 @@ void SkeletalTrapezoidation::generateLocalMaximaSingleBeads() constexpr coord_t n_segments = 6; for (coord_t segment = 0; segment < n_segments; segment++) { float a = 2.0 * M_PI / n_segments * segment; - line.junctions.emplace_back(node.p + Point(r * cos(a), r * sin(a)), width, inset_index); + line.junctions.emplace_back(ExtrusionJunction(node.p + Point(r * cos(a), r * sin(a)), width, inset_index, false)); } } } diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp index 75ff940..043c83c 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp @@ -60,6 +60,8 @@ class SkeletalTrapezoidation template using ptr_vector_t = std::vector>; + bool enable_hole_compensation; + std::vector hole_indices; double transitioning_angle; //!< How pointy a region should be before we apply the method. Equals 180* - limit_bisector_angle coord_t discretization_step_size; //!< approximate size of segments when parabolic VD edges get discretized (and vertex-vertex edges) coord_t transition_filter_dist; //!< Filter transition mids (i.e. anchors) closer together than this @@ -108,7 +110,9 @@ public: , coord_t discretization_step_size , coord_t transition_filter_dist , coord_t allowed_filter_deviation - , coord_t beading_propagation_transition_dist); + , coord_t beading_propagation_transition_dist + , bool enable_hole_compensation + , const std::vector& hole_indices); /*! * A skeletal graph through the polygons that we need to fill with beads. @@ -180,7 +184,7 @@ protected: * Transfer an edge from the VD to the HE and perform discretization of parabolic edges (and vertex-vertex edges) * \p prev_edge serves as input and output. May be null as input. */ - void transferEdge(Point from, Point to, const VD::edge_type &vd_edge, edge_t *&prev_edge, Point &start_source_point, Point &end_source_point, const std::vector &segments); + void transferEdge(Point from, Point to, const VD::edge_type &vd_edge, edge_t *&prev_edge, Point &start_source_point, Point &end_source_point, const std::vector &segments, const bool hole_compensation_flag); /*! * Discretize a Voronoi edge that represents the medial axis of a vertex- diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidationEdge.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidationEdge.hpp index e0d3fe8..8f6562f 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidationEdge.hpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidationEdge.hpp @@ -110,7 +110,18 @@ public: return extrusion_junctions.lock(); } + void setHoleCompensationFlag(bool enabled) + { + apply_hole_compensation = enabled; + } + + bool getHoleCompensationFlag() const + { + return apply_hole_compensation; + } + private: + bool apply_hole_compensation{ false }; Central is_central; //! whether the edge is significant; whether the source segments have a sharp angle; -1 is unknown std::weak_ptr> transitions; diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp index 4ef96ed..61d9a24 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp @@ -324,12 +324,14 @@ void SkeletalTrapezoidationGraph::makeRib(edge_t*& prev_edge, Point start_source nodes.emplace_front(SkeletalTrapezoidationJoint(), p); node_t* node = &nodes.front(); node->data.distance_to_boundary = 0; - + edges.emplace_front(SkeletalTrapezoidationEdge(SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD)); edge_t* forth_edge = &edges.front(); + forth_edge->data.setHoleCompensationFlag(prev_edge->data.getHoleCompensationFlag()); edges.emplace_front(SkeletalTrapezoidationEdge(SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD)); edge_t* back_edge = &edges.front(); - + back_edge->data.setHoleCompensationFlag(prev_edge->data.getHoleCompensationFlag()); + prev_edge->next = forth_edge; forth_edge->prev = prev_edge; forth_edge->from = prev_edge->to; @@ -339,7 +341,7 @@ void SkeletalTrapezoidationGraph::makeRib(edge_t*& prev_edge, Point start_source back_edge->from = node; back_edge->to = prev_edge->to; node->incident_edge = back_edge; - + prev_edge = back_edge; } @@ -352,6 +354,8 @@ std::pairp; + bool apply_hole_compensation = edge.data.getHoleCompensationFlag(); + const Line source_segment = getSource(edge); Point px; source_segment.distance_to_squared(p, &px); @@ -372,6 +376,11 @@ std::pairdata.setHoleCompensationFlag(apply_hole_compensation); + second->data.setHoleCompensationFlag(apply_hole_compensation); + outward_edge->data.setHoleCompensationFlag(apply_hole_compensation); + inward_edge->data.setHoleCompensationFlag(apply_hole_compensation); + if (edge_before) { edge_before->next = first; diff --git a/src/libslic3r/Arachne/WallToolPaths.cpp b/src/libslic3r/Arachne/WallToolPaths.cpp index 13bc901..927792b 100644 --- a/src/libslic3r/Arachne/WallToolPaths.cpp +++ b/src/libslic3r/Arachne/WallToolPaths.cpp @@ -41,6 +41,12 @@ WallToolPaths::WallToolPaths(const Polygons& outline, const coord_t bead_width_0 { } +void WallToolPaths::EnableHoleCompensation(bool enable_, const std::vector& hole_indices_) +{ + enable_hole_compensation = enable_; + hole_indices = hole_indices_; +} + void simplify(Polygon &thiss, const int64_t smallest_line_segment_squared, const int64_t allowed_error_distance_squared) { if (thiss.size() < 3) { @@ -437,6 +443,13 @@ const std::vector &WallToolPaths::generate() if (this->inset_count < 1) return toolpaths; + size_t original_outline_size = outline.size(); + bool outline_size_change = false; + // Lambda for checking size changes + auto update_outline_size_change = [original_outline_size, &outline_size_change](const Polygons& polys) { + outline_size_change |= (original_outline_size != polys.size()); + }; + const coord_t smallest_segment = Slic3r::Arachne::meshfix_maximum_resolution; const coord_t allowed_distance = Slic3r::Arachne::meshfix_maximum_deviation; const coord_t epsilon_offset = (allowed_distance / 2) - 1; @@ -446,26 +459,36 @@ const std::vector &WallToolPaths::generate() // Simplify outline for boost::voronoi consumption. Absolutely no self intersections or near-self intersections allowed: // TODO: Open question: Does this indeed fix all (or all-but-one-in-a-million) cases for manifold but otherwise possibly complex polygons? Polygons prepared_outline = offset(offset(offset(outline, -epsilon_offset), epsilon_offset * 2), -epsilon_offset); - simplify(prepared_outline, smallest_segment, allowed_distance); - fixSelfIntersections(epsilon_offset, prepared_outline); - removeDegenerateVerts(prepared_outline); - removeColinearEdges(prepared_outline, 0.005); + update_outline_size_change(prepared_outline); + + // Helper function for applying a sequence of operations with size change tracking + auto process_with_size_check = [&](auto&& operation) { + operation(); + update_outline_size_change(prepared_outline); + }; + + process_with_size_check([&] { simplify(prepared_outline, smallest_segment, allowed_distance);}); + process_with_size_check([&] { fixSelfIntersections(epsilon_offset, prepared_outline); }); + process_with_size_check([&] { removeDegenerateVerts(prepared_outline); }); + process_with_size_check([&] { removeColinearEdges(prepared_outline, 0.005); }); // Removing collinear edges may introduce self intersections, so we need to fix them again - fixSelfIntersections(epsilon_offset, prepared_outline); - removeDegenerateVerts(prepared_outline); - removeSmallAreas(prepared_outline, small_area_length * small_area_length, false); + process_with_size_check([&] { fixSelfIntersections(epsilon_offset, prepared_outline); }); + process_with_size_check([&] { removeDegenerateVerts(prepared_outline); }); + process_with_size_check([&] { removeSmallAreas(prepared_outline, small_area_length * small_area_length, false); }); // The functions above could produce intersecting polygons that could cause a crash inside Arachne. // Applying Clipper union should be enough to get rid of this issue. // Clipper union also fixed an issue in Arachne that in post-processing Voronoi diagram, some edges // didn't have twin edges. (a non-planar Voronoi diagram probably caused this). prepared_outline = union_(prepared_outline); - + update_outline_size_change(prepared_outline); if (area(prepared_outline) <= 0) { assert(toolpaths.empty()); return toolpaths; } + bool apply_hole_compensation = this->enable_hole_compensation && !outline_size_change; + const float external_perimeter_extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(unscale(bead_width_0), float(this->layer_height)); const float perimeter_extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(unscale(bead_width_x), float(this->layer_height)); @@ -501,7 +524,9 @@ const std::vector &WallToolPaths::generate() discretization_step_size, transition_filter_dist, allowed_filter_deviation, - wall_transition_length + wall_transition_length, + apply_hole_compensation, + hole_indices ); wall_maker.generateToolpaths(toolpaths); @@ -674,27 +699,36 @@ const std::vector &WallToolPaths::getToolPaths() void WallToolPaths::separateOutInnerContour() { + enum PathType{ + ActualPath, + WallContour, + FirstWallContour + }; //We'll remove all 0-width paths from the original toolpaths and store them separately as polygons. std::vector actual_toolpaths; actual_toolpaths.reserve(toolpaths.size()); //A bit too much, but the correct order of magnitude. - std::vector contour_paths; - contour_paths.reserve(toolpaths.size() / inset_count); + std::vector wall_contour_paths; + wall_contour_paths.reserve(toolpaths.size() / inset_count); + std::vector first_wall_contour_paths; inner_contour.clear(); + first_wall_contour.clear(); for (const VariableWidthLines &inset : toolpaths) { if (inset.empty()) continue; - bool is_contour = false; + PathType type; for (const ExtrusionLine &line : inset) { for (const ExtrusionJunction &j : line) { - if (j.w == 0) - is_contour = true; + if (j.w == Arachne::WallContourMarkedWidth) + type = WallContour; + else if(j.w == Arachne::FirstWallContourMarkedWidth) + type = FirstWallContour; else - is_contour = false; + type = ActualPath; break; } } - if (is_contour) { + if (type==WallContour) { #ifdef DEBUG for (const ExtrusionLine &line : inset) for (const ExtrusionJunction &j : line) @@ -706,7 +740,16 @@ void WallToolPaths::separateOutInnerContour() else if (line.is_closed) // sometimes an very small even polygonal wall is not stitched into a polygon inner_contour.emplace_back(line.toPolygon()); } - } else { + } + else if (type == FirstWallContour){ + for (const ExtrusionLine &line : inset) { + if (line.is_odd) + continue; + else if (line.is_closed) + first_wall_contour.emplace_back(line.toPolygon()); + } + } + else { actual_toolpaths.emplace_back(inset); } } @@ -720,7 +763,7 @@ void WallToolPaths::separateOutInnerContour() //To get a correct shape, we need to make the outside contour positive and any holes inside negative. //This can be done by applying the even-odd rule to the shape. This rule is not sensitive to the winding order of the polygon. //The even-odd rule would be incorrect if the polygon self-intersects, but that should never be generated by the skeletal trapezoidation. - inner_contour = union_(inner_contour, ClipperLib::PolyFillType::pftEvenOdd); + first_wall_contour = union_(first_wall_contour, ClipperLib::PolyFillType::pftEvenOdd); } const Polygons& WallToolPaths::getInnerContour() @@ -736,6 +779,20 @@ const Polygons& WallToolPaths::getInnerContour() return inner_contour; } +Polygons EmptyPolygons; +const Polygons& WallToolPaths::getFirstWallContour() +{ + if (!toolpaths_generated && inset_count > 0) + { + generate(); + } + else if(inset_count == 0) + { + return EmptyPolygons; + } + return first_wall_contour; +} + bool WallToolPaths::removeEmptyToolPaths(std::vector &toolpaths) { toolpaths.erase(std::remove_if(toolpaths.begin(), toolpaths.end(), [](const VariableWidthLines& lines) diff --git a/src/libslic3r/Arachne/WallToolPaths.hpp b/src/libslic3r/Arachne/WallToolPaths.hpp index 6bb1153..dece5db 100644 --- a/src/libslic3r/Arachne/WallToolPaths.hpp +++ b/src/libslic3r/Arachne/WallToolPaths.hpp @@ -44,6 +44,8 @@ public: */ WallToolPaths(const Polygons& outline, coord_t bead_width_0, coord_t bead_width_x, size_t inset_count, coord_t wall_0_inset, coordf_t layer_height, const WallToolPathsParams ¶ms); + void EnableHoleCompensation(bool enable, const std::vector& hole_indices_); + /*! /*! * Generates the Toolpaths * \return A reference to the newly create ToolPaths @@ -77,6 +79,15 @@ public: */ const Polygons& getInnerContour(); + /** + * @brief Get the contour of outer wall path + * + * Attention! The function is not completed now! + * + * @return + */ + const Polygons& getFirstWallContour(); + /*! * Removes empty paths from the toolpaths * \param toolpaths the VariableWidthPaths generated with \p generate() @@ -130,7 +141,11 @@ private: bool toolpaths_generated; // toolpaths; // hole_indices; }; } // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/utils/ExtrusionJunction.cpp b/src/libslic3r/Arachne/utils/ExtrusionJunction.cpp index 3cdfa0d..f8063f0 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionJunction.cpp +++ b/src/libslic3r/Arachne/utils/ExtrusionJunction.cpp @@ -10,9 +10,10 @@ bool ExtrusionJunction::operator ==(const ExtrusionJunction& other) const { return p == other.p && w == other.w - && perimeter_index == other.perimeter_index; + && perimeter_index == other.perimeter_index + && hole_compensation_flag == other.hole_compensation_flag; } -ExtrusionJunction::ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index) : p(p), w(w), perimeter_index(perimeter_index) {} +ExtrusionJunction::ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index, const bool compensation_flag) : p(p), w(w), perimeter_index(perimeter_index),hole_compensation_flag(compensation_flag) {} } diff --git a/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp b/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp index 1465251..c2a5fb4 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp +++ b/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp @@ -18,6 +18,10 @@ namespace Slic3r::Arachne */ struct ExtrusionJunction { + /*! + * whether the junction is generated from a hole that needs compensation + */ + bool hole_compensation_flag; /*! * The position of the centreline of the path when it reaches this junction. * This is the position that should end up in the g-code eventually. @@ -37,7 +41,7 @@ struct ExtrusionJunction */ size_t perimeter_index; - ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index); + ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index, const bool hole_compensation); bool operator==(const ExtrusionJunction& other) const; }; diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp index afb35a4..797e8d3 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp @@ -147,7 +147,7 @@ void ExtrusionLine::simplify(const int64_t smallest_line_segment_squared, const else { // New point seems like a valid one. - const ExtrusionJunction new_to_add = ExtrusionJunction(intersection_point, current.w, current.perimeter_index); + const ExtrusionJunction new_to_add = ExtrusionJunction(intersection_point, current.w, current.perimeter_index, current.hole_compensation_flag); // If there was a previous point added, remove it. if(!new_junctions.empty()) { @@ -232,6 +232,20 @@ int64_t ExtrusionLine::calculateExtrusionAreaDeviationError(ExtrusionJunction A, } } +bool ExtrusionLine::shouldApplyHoleCompensation(const double threshold) const +{ + int64_t total_length = 0; + int64_t marked_length = 0; + for (size_t idx = 1; idx < junctions.size(); ++idx) { + int64_t length = (junctions[idx].p - junctions[idx - 1].p).cast().norm(); + total_length += length; + int marked_rate = (int)(junctions[idx].hole_compensation_flag) + (int)(junctions[idx - 1].hole_compensation_flag); + marked_length += length * marked_rate / 2; + } + double rate = (double)(marked_length) / (double)(total_length); + return rate > threshold; +} + bool ExtrusionLine::is_contour() const { if (!this->is_closed) diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp index 83b0984..0e3002c 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp @@ -190,6 +190,8 @@ struct ExtrusionLine * */ static int64_t calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C, coord_t& weighted_average_width); + bool shouldApplyHoleCompensation(const double threshold = 0.8) const; + bool is_contour() const; double area() const; diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 2ecb379..187acdb 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -192,6 +192,7 @@ void fill_config(PConf& pcfg, const ArrangeParams ¶ms) { // Allow parallel execution. pcfg.parallel = params.parallel; + pcfg.save_svg = params.save_svg; // QDS: excluded regions in QDS bed for (auto& poly : params.excluded_regions) @@ -548,27 +549,36 @@ protected: score += height_score / valid_items_cnt; } - std::set extruder_ids; + std::map extruder_id_types; + std::set tpu_extruder_ids; for (int i = 0; i < m_items.size(); i++) { Item& p = m_items[i]; if (p.is_virt_object) continue; - extruder_ids.insert(p.extrude_ids.begin(),p.extrude_ids.end()); + extruder_id_types.insert(p.extrude_id_filament_types.begin(), p.extrude_id_filament_types.end()); + for (auto id_type : p.extrude_id_filament_types) { + if (id_type.second == "TPU") tpu_extruder_ids.insert(id_type.first); + } + } + for (auto id_type : item.extrude_id_filament_types) { + if (id_type.second == "TPU") tpu_extruder_ids.insert(id_type.first); } + // do not allow more than 1 TPU extruder on same plate + if (tpu_extruder_ids.size() > 1) score += LARGE_COST_TO_REJECT; // add a large cost if not multi materials on same plate is not allowed - if (!params.allow_multi_materials_on_same_plate) { + else if (!params.allow_multi_materials_on_same_plate) { // it's the first object, which can be multi-color - bool first_object = extruder_ids.empty(); + bool first_object = extruder_id_types.empty(); // the two objects (previously packed items and the current item) are considered having same color if either one's colors are a subset of the other - std::set item_extruder_ids(item.extrude_ids.begin(), item.extrude_ids.end()); - bool same_color_with_previous_items = std::includes(extruder_ids.begin(), extruder_ids.end(), item_extruder_ids.begin(), item_extruder_ids.end()); + bool same_color_with_previous_items = std::includes(extruder_id_types.begin(), extruder_id_types.end(), item.extrude_id_filament_types.begin(), + item.extrude_id_filament_types.end()); if (!(first_object || same_color_with_previous_items)) score += LARGE_COST_TO_REJECT * 1.3; } // for layered printing, we want extruder change as few as possible // this has very weak effect, CAN NOT use a large weight - int last_extruder_cnt = extruder_ids.size(); - extruder_ids.insert(item.extrude_ids.begin(), item.extrude_ids.end()); - int new_extruder_cnt= extruder_ids.size(); + int last_extruder_cnt = extruder_id_types.size(); + extruder_id_types.insert(item.extrude_id_filament_types.begin(), item.extrude_id_filament_types.end()); + int new_extruder_cnt = extruder_id_types.size(); if (!params.is_seq_print) { score += 1 * (new_extruder_cnt-last_extruder_cnt); } @@ -682,25 +692,29 @@ public: if (stopcond) m_pck.stopCondition(stopcond); + m_pconf.progressFunc = [](const std::string& name) { BOOST_LOG_TRIVIAL(debug) << "arrange progress in NFP: " + name; }; + m_pconf.sortfunc= [¶ms](Item& i1, Item& i2) { int p1 = i1.priority(), p2 = i2.priority(); if (p1 != p2) return p1 > p2; if (params.is_seq_print) { - return i1.bed_temp != i2.bed_temp ? (i1.bed_temp > i2.bed_temp) : - (i1.height != i2.height ? (i1.height < i2.height) : (i1.area() > i2.area())); + return i1.bed_temp != i2.bed_temp ? (i1.bed_temp > i2.bed_temp) : + i1.height != i2.height ? (i1.height < i2.height) : + std::abs(i1.area() / i2.area() - 1) > 0.2 ? (i1.area() > i2.area()) : + i1.extrude_id_filament_types.begin()->first < i2.extrude_id_filament_types.begin()->first; } else { // single color objects first, then objects with more colors - if (i1.extrude_ids.size() != i2.extrude_ids.size()) { - if (i1.extrude_ids.size() == 1 || i2.extrude_ids.size() == 1) - return i1.extrude_ids.size() == 1; + if (i1.extrude_id_filament_types.size() != i2.extrude_id_filament_types.size()) { + if (i1.extrude_id_filament_types.size() == 1 || i2.extrude_id_filament_types.size() == 1) + return i1.extrude_id_filament_types.size() == 1; else - return i1.extrude_ids.size() > i2.extrude_ids.size(); + return i1.extrude_id_filament_types.size() > i2.extrude_id_filament_types.size(); } else return i1.bed_temp != i2.bed_temp ? (i1.bed_temp > i2.bed_temp) : - i1.extrude_ids != i2.extrude_ids ? (i1.extrude_ids.front() < i2.extrude_ids.front()) : + i1.extrude_id_filament_types != i2.extrude_id_filament_types ? (i1.extrude_id_filament_types.begin()->first < i2.extrude_id_filament_types.begin()->first) : std::abs(i1.height/params.printable_height - i2.height/params.printable_height)>0.05 ? i1.height > i2.height: (i1.area() > i2.area()); } @@ -857,37 +871,28 @@ void _arrange( std::function progressfn, std::function stopfn) { - // Integer ceiling the min distance from the bed perimeters - coord_t md = params.min_obj_distance; - md = md / 2; - - auto corrected_bin = bin; - //sl::offset(corrected_bin, md); - ArrangeParams mod_params = params; - mod_params.min_obj_distance = 0; // items are already inflated - - AutoArranger arranger{corrected_bin, mod_params, progressfn, stopfn}; - - remove_large_items(excludes, corrected_bin); - - // If there is something on the plate - if (!excludes.empty()) arranger.preload(excludes); - - std::vector> inp; - inp.reserve(shapes.size() + excludes.size()); - for (auto &itm : shapes ) inp.emplace_back(itm); - for (auto &itm : excludes) inp.emplace_back(itm); + ArrangeParams mod_params = params; + mod_params.min_obj_distance = 0; // items are already inflated // Use the minimum bounding box rotation as a starting point. // TODO: This only works for convex hull. If we ever switch to concave // polygon nesting, a convex hull needs to be calculated. if (params.align_to_y_axis) { for (auto &itm : shapes) { - auto angle = min_area_boundingbox_rotation(itm.transformedShape()); - itm.rotate(angle + PI / 2); + itm.allowed_rotations = {0.0}; + // only rotate the object if its long axis is significanly larger than its short axis (more than 10%) + try { + auto bbox = minAreaBoundingBox, boost::rational>(itm.transformedShape()); + auto w = bbox.width(), h = bbox.height(); + if (w > h * 1.1 || h > w * 1.1) { + itm.allowed_rotations = {bbox.angleToX() + PI / 2, 0.0}; + } + } catch (const std::exception &e) { + // min_area_boundingbox_rotation may throw exception of dividing 0 if the object is already perfectly aligned to X + BOOST_LOG_TRIVIAL(error) << "arranging min_area_boundingbox_rotation fails, msg=" << e.what(); + } } - } - else if (params.allow_rotations) { + } else if (params.allow_rotations) { for (auto &itm : shapes) { auto angle = min_area_boundingbox_rotation(itm.transformedShape()); BOOST_LOG_TRIVIAL(debug) << itm.name << " min_area_boundingbox_rotation=" << angle << ", original angle=" << itm.rotation(); @@ -901,9 +906,27 @@ void _arrange( itm.rotate(fit_into_box_rotation(itm.transformedShape(), bin)); } } + itm.allowed_rotations = {0., PI / 4., PI / 2, 3. * PI / 4.}; } } + // Integer ceiling the min distance from the bed perimeters + coord_t md = params.min_obj_distance / 2; + + auto corrected_bin = bin; + //sl::offset(corrected_bin, md); + + AutoArranger arranger{corrected_bin, mod_params, progressfn, stopfn}; + + // If there is something on the plate + if (!excludes.empty()) arranger.preload(excludes); + + std::vector> inp; + inp.reserve(shapes.size() + excludes.size()); + for (auto &itm : shapes ) inp.emplace_back(itm); + for (auto &itm : excludes) inp.emplace_back(itm); + + arranger(inp.begin(), inp.end()); for (Item &itm : inp) itm.inflation(0); } @@ -969,12 +992,13 @@ static void process_arrangeable(const ArrangePolygon &arrpoly, item.binId(arrpoly.bed_idx); item.priority(arrpoly.priority); item.itemId(arrpoly.itemid); - item.extrude_ids = arrpoly.extrude_ids; + item.extrude_id_filament_types = arrpoly.extrude_id_filament_types; item.height = arrpoly.height; item.name = arrpoly.name; //QDS: add virtual object logic item.is_virt_object = arrpoly.is_virt_object; item.is_wipe_tower = arrpoly.is_wipe_tower; + item.is_extrusion_cali_object = arrpoly.is_extrusion_cali_object; item.bed_temp = arrpoly.first_bed_temp; item.print_temp = arrpoly.print_temp; item.vitrify_temp = arrpoly.vitrify_temp; diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index f239288..20fd64f 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -61,7 +61,7 @@ struct ArrangePolygon { //QDS: add row/col for sudoku-style layout int row{0}; int col{0}; - std::vector extrude_ids{}; /// extruder_id for least extruder switch + std::map extrude_id_filament_types; /// extruder_id for least extruder switch int filament_temp_type{ -1 }; int bed_temp{0}; ///bed temperature for different material judge int print_temp{0}; ///print temperature for different material judge @@ -126,6 +126,7 @@ struct ArrangeParams { bool avoid_extrusion_cali_region = true; bool is_seq_print = false; bool align_to_y_axis = false; + bool save_svg = false; float bed_shrink_x = 0.1; float bed_shrink_y = 0.1; float brim_skirt_distance = 0; diff --git a/src/libslic3r/BoundingBox.cpp b/src/libslic3r/BoundingBox.cpp index d066ca6..e3d2320 100644 --- a/src/libslic3r/BoundingBox.cpp +++ b/src/libslic3r/BoundingBox.cpp @@ -53,6 +53,13 @@ BoundingBox BoundingBox::rotated(double angle, const Point ¢er) const return out; } +BoundingBox BoundingBox::scaled(double factor) const +{ + BoundingBox out(*this); + out.scale(factor); + return out; +} + template void BoundingBoxBase::scale(double factor) { diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index c2a84bb..3fe8e02 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -211,7 +211,9 @@ public: BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase(pmin, pmax) {} BoundingBox(const Points &points) : BoundingBoxBase(points) {} - BoundingBox inflated(coordf_t delta) const throw() { BoundingBox out(*this); out.offset(delta); return out; } + BoundingBox inflated(coordf_t delta) const noexcept { BoundingBox out(*this); out.offset(delta); return out; } + + BoundingBox scaled(double factor) const; friend BoundingBox get_extents_rotated(const Points &points, double angle); }; diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index 3829634..1c160b9 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -863,9 +863,6 @@ static ExPolygons outer_inner_brim_area(const Print& print, brimToWrite.insert({ objectWithExtruder.first, {true,true} }); ExPolygons objectIslands; - auto bedPoly = Model::getBedPolygon(); - auto bedExPoly = diff_ex((offset(bedPoly, scale_(30.), jtRound, SCALED_RESOLUTION)), { bedPoly }); - auto save_polygon_if_is_inner_island = [](const Polygons& holes_area, const Polygon& contour, int& hole_index) { for (size_t i = 0; i < holes_area.size(); i++) { Polygons contour_polys; @@ -1004,7 +1001,7 @@ static ExPolygons outer_inner_brim_area(const Print& print, if (!has_outer_brim) append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset), ex_poly_holes_reversed)); if (!has_inner_brim && !has_outer_brim) - append(no_brim_area_object, offset_ex(ex_poly_holes_reversed, -no_brim_offset)); + append(no_brim_area_object, diff_ex(ex_poly_holes_reversed, offset_ex(ex_poly_holes_reversed, -no_brim_offset))); append(holes_object, ex_poly_holes_reversed); } } @@ -1050,18 +1047,53 @@ static ExPolygons outer_inner_brim_area(const Print& print, } } } - if (!bedExPoly.empty()){ - no_brim_area.push_back(bedExPoly.front()); + + int extruder_nums = print.config().nozzle_diameter.values.size(); + std::vector extruder_unprintable_area; + if (extruder_nums == 1) + extruder_unprintable_area.emplace_back(Polygons{Model::getBedPolygon()}); + else { + extruder_unprintable_area = print.get_extruder_printable_polygons(); } - no_brim_area = offset2_ex(no_brim_area, scaled_flow_width, -scaled_flow_width); // connect scattered small areas to prevent generating very small brims + std::vector filament_map = print.get_filament_maps(); + + if (print.has_wipe_tower() && !print.get_fake_wipe_tower().outer_wall.empty()) { + ExPolygons expolyFromLines{}; + for (auto polyline : print.get_fake_wipe_tower().outer_wall.begin()->second) { + polyline.remove_duplicate_points(); + expolyFromLines.emplace_back(polyline.points); + expolyFromLines.back().translate(Point(scale_(print.get_fake_wipe_tower().pos[0]), scale_(print.get_fake_wipe_tower().pos[1]))); + } + expolygons_append(no_brim_area, expolyFromLines); + } + + std::vector extruder_no_brim_area_cache(extruder_nums, no_brim_area); + for (int extruder_id = 0; extruder_id < extruder_nums; ++extruder_id) { + auto bedPoly = extruder_unprintable_area[extruder_id]; + auto bedExPoly = diff_ex((offset(bedPoly, scale_(30.), jtRound, SCALED_RESOLUTION)), {bedPoly}); + if (!bedExPoly.empty()) { + extruder_no_brim_area_cache[extruder_id].push_back(bedExPoly.front()); + } + extruder_no_brim_area_cache[extruder_id] = offset2_ex(extruder_no_brim_area_cache[extruder_id], scaled_flow_width, -scaled_flow_width); // connect scattered small areas to prevent generating very small brims + } + for (const PrintObject* object : print.objects()) { - if (brimAreaMap.find(object->id()) != brimAreaMap.end()) - { - brimAreaMap[object->id()] = diff_ex(brimAreaMap[object->id()], no_brim_area); + ExPolygons extruder_no_brim_area = no_brim_area; + auto iter = std::find_if(objPrintVec.begin(), objPrintVec.end(), [object](const std::pair& item) { + return item.first == object->id(); + }); + + if (iter != objPrintVec.end()) { + int extruder_id = filament_map[iter->second - 1] - 1; + extruder_no_brim_area = extruder_no_brim_area_cache[extruder_id]; + } + + if (brimAreaMap.find(object->id()) != brimAreaMap.end()) { + brimAreaMap[object->id()] = diff_ex(brimAreaMap[object->id()], extruder_no_brim_area); } if (supportBrimAreaMap.find(object->id()) != supportBrimAreaMap.end()) - supportBrimAreaMap[object->id()] = diff_ex(supportBrimAreaMap[object->id()], no_brim_area); + supportBrimAreaMap[object->id()] = diff_ex(supportBrimAreaMap[object->id()], extruder_no_brim_area); } brim_area.clear(); diff --git a/src/libslic3r/BrimEarsPoint.hpp b/src/libslic3r/BrimEarsPoint.hpp index d768d21..a859d54 100644 --- a/src/libslic3r/BrimEarsPoint.hpp +++ b/src/libslic3r/BrimEarsPoint.hpp @@ -42,6 +42,11 @@ struct BrimPoint return result.cast(); } + void set_transform(const Transform3d& trsf) + { + pos = transform(trsf); + } + bool operator==(const BrimPoint &sp) const { float rdiff = std::abs(head_front_radius - sp.head_front_radius); diff --git a/src/libslic3r/BuildVolume.cpp b/src/libslic3r/BuildVolume.cpp index f6d7c93..36dec02 100644 --- a/src/libslic3r/BuildVolume.cpp +++ b/src/libslic3r/BuildVolume.cpp @@ -9,9 +9,11 @@ namespace Slic3r { -BuildVolume::BuildVolume(const std::vector &printable_area, const double printable_height) : m_bed_shape(printable_area), m_max_print_height(printable_height) +BuildVolume::BuildVolume(const std::vector &printable_area, const double printable_height, const std::vector> &extruder_areas, const std::vector& extruder_printable_heights) + : m_bed_shape(printable_area), m_max_print_height(printable_height), m_extruder_shapes(extruder_areas), m_extruder_printable_height(extruder_printable_heights) { assert(printable_height >= 0); + //assert(extruder_printable_heights.size() == extruder_areas.size()); m_polygon = Polygon::new_scale(printable_area); @@ -76,6 +78,100 @@ BuildVolume::BuildVolume(const std::vector &printable_area, const double m_top_bottom_convex_hull_decomposition_bed = convex_decomposition(m_convex_hull, BedEpsilon); } + if (m_extruder_shapes.size() > 0) + { + m_shared_volume.data[0] = m_bboxf.min.x(); + m_shared_volume.data[1] = m_bboxf.min.y(); + m_shared_volume.data[2] = m_bboxf.max.x(); + m_shared_volume.data[3] = m_bboxf.max.y(); + m_shared_volume.zs[1] = m_bboxf.max.z(); + for (unsigned int index = 0; index < m_extruder_shapes.size(); index++) + { + std::vector& extruder_shape = m_extruder_shapes[index]; + BuildExtruderVolume extruder_volume; + + if (extruder_shape.empty()) + { + //should not happen + BOOST_LOG_TRIVIAL(warning) << boost::format("Found invalid extruder_printable_area of index %1%")%index; + assert(false); + m_extruder_shapes.clear(); + return; + } + + if ((extruder_shape == printable_area)&&(extruder_printable_heights[index] == printable_height)) { + extruder_volume.same_with_bed = true; + extruder_volume.type = m_type; + extruder_volume.bbox = m_bbox; + extruder_volume.bboxf = m_bboxf; + extruder_volume.circle = m_circle; + } + else { + Polygon poly = Polygon::new_scale(extruder_shape); + + double poly_area = poly.area(); + extruder_volume.bbox = get_extents(poly); + BoundingBoxf temp_bboxf = get_extents(extruder_shape); + extruder_volume.bboxf = BoundingBoxf3{ to_3d(temp_bboxf.min, 0.), to_3d(temp_bboxf.max, extruder_printable_heights[index]) }; + + if (extruder_shape.size() >= 4 && std::abs((poly_area - double(extruder_volume.bbox.size().x()) * double(extruder_volume.bbox.size().y()))) < sqr(SCALED_EPSILON)) + { + extruder_volume.type = Type::Rectangle; + extruder_volume.circle.center = 0.5 * (extruder_volume.bbox.min.cast() + extruder_volume.bbox.max.cast()); + extruder_volume.circle.radius = 0.5 * extruder_volume.bbox.size().cast().norm(); + } + else if (extruder_shape.size() > 3) { + extruder_volume.circle = Geometry::circle_ransac(extruder_shape); + bool is_circle = true; + + Vec2d prev = extruder_shape.back(); + for (const Vec2d &p : extruder_shape) { + if (// Polygon vertices must lie very close the circle. + std::abs((p - extruder_volume.circle.center).norm() - extruder_volume.circle.radius) > 0.005 || + // Midpoints of polygon edges must not undercat more than 3mm. This corresponds to 72 edges per circle generated by BedShapePanel::update_shape(). + extruder_volume.circle.radius - (0.5 * (prev + p) -extruder_volume.circle.center).norm() > 3.) { + is_circle = false; + break; + } + prev = p; + } + if (is_circle) { + extruder_volume.type = Type::Circle; + extruder_volume.circle.center = scaled(extruder_volume.circle.center); + extruder_volume.circle.radius = scaled(extruder_volume.circle.radius); + } + } + + if (m_type == Type::Invalid) { + //not supported currently, use the same as bed + extruder_volume.same_with_bed = true; + extruder_volume.type = m_type; + extruder_volume.bbox = m_bbox; + extruder_volume.bboxf = m_bboxf; + extruder_volume.circle = m_circle; + } + //always ignore z + extruder_volume.bboxf.min.z() = -std::numeric_limits::max(); + } + m_extruder_volumes.push_back(std::move(extruder_volume)); + + if (m_shared_volume.data[0] < extruder_volume.bboxf.min.x()) + m_shared_volume.data[0] = extruder_volume.bboxf.min.x(); + if (m_shared_volume.data[1] < extruder_volume.bboxf.min.y()) + m_shared_volume.data[1] = extruder_volume.bboxf.min.y(); + if (m_shared_volume.data[2] > extruder_volume.bboxf.max.x()) + m_shared_volume.data[2] = extruder_volume.bboxf.max.x(); + if (m_shared_volume.data[3] > extruder_volume.bboxf.max.y()) + m_shared_volume.data[3] = extruder_volume.bboxf.max.y(); + if (m_shared_volume.zs[1] > extruder_volume.bboxf.max.z()) + m_shared_volume.zs[1] = extruder_volume.bboxf.max.z(); + } + + m_shared_volume.type = static_cast(m_type); + m_shared_volume.zs[0] = 0.f; + //m_shared_volume.zs[1] = printable_height; + } + BOOST_LOG_TRIVIAL(debug) << "BuildVolume printable_area clasified as: " << this->type_name(); } @@ -196,7 +292,7 @@ BuildVolume::ObjectState object_state_templ(const indexed_triangle_set &its, con bool outside = false; static constexpr const auto world_min_z = float(-BuildVolume::SceneEpsilon); - if (may_be_below_bed) + if (may_be_below_bed) { // Slower test, needs to clip the object edges with the print bed plane. // 1) Allocate transformed vertices with their position with respect to print bed surface. @@ -252,7 +348,7 @@ BuildVolume::ObjectState object_state_templ(const indexed_triangle_set &its, con } } } - else + else { // Much simpler and faster code, not clipping the object with the print bed. assert(! may_be_below_bed); @@ -289,14 +385,14 @@ BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set& i case Type::Circle: { Geometry::Circlef circle { unscaled(m_circle.center), unscaled(m_circle.radius + SceneEpsilon) }; - return m_max_print_height == 0.0 ? + return m_max_print_height == 0.0 ? object_state_templ(its, trafo, may_be_below_bed, [circle](const Vec3f &pt) { return circle.contains(to_2d(pt)); }) : object_state_templ(its, trafo, may_be_below_bed, [circle, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && circle.contains(to_2d(pt)); }); } case Type::Convex: //FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently. case Type::Custom: - return m_max_print_height == 0.0 ? + return m_max_print_height == 0.0 ? object_state_templ(its, trafo, may_be_below_bed, [this](const Vec3f &pt) { return Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast()); }) : object_state_templ(its, trafo, may_be_below_bed, [this, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast()); }); case Type::Invalid: @@ -314,10 +410,100 @@ BuildVolume::ObjectState BuildVolume::volume_state_bbox(const BoundingBoxf3& vol if (ignore_bottom) build_volume.min.z() = -std::numeric_limits::max(); return build_volume.max.z() <= - SceneEpsilon ? ObjectState::Below : - build_volume.contains(volume_bbox) ? ObjectState::Inside : + build_volume.contains(volume_bbox) ? ObjectState::Inside : build_volume.intersects(volume_bbox) ? ObjectState::Colliding : ObjectState::Outside; } +const BuildVolume::BuildExtruderVolume& BuildVolume::get_extruder_area_volume(int index) const +{ + assert(index >= 0 && index < m_extruder_volumes.size()); + return m_extruder_volumes[index]; +} + +BuildVolume::ObjectState BuildVolume::check_object_state_with_extruder_area(const indexed_triangle_set &its, const Transform3f &trafo, int index) const +{ + const BuildExtruderVolume& extruder_volume = get_extruder_area_volume(index); + ObjectState return_state = ObjectState::Inside; + + if (!extruder_volume.same_with_bed) { + switch (extruder_volume.type) { + case Type::Rectangle: + { + BoundingBox3Base build_volume = extruder_volume.bboxf.inflated(SceneEpsilon); + if (m_max_print_height == 0.0) + build_volume.max.z() = std::numeric_limits::max(); + BoundingBox3Base build_volumef(build_volume.min.cast(), build_volume.max.cast()); + + return_state = object_state_templ(its, trafo, false, [build_volumef](const Vec3f &pt) { return build_volumef.contains(pt); }); + break; + } + case Type::Circle: + { + Geometry::Circlef circle { unscaled(extruder_volume.circle.center), unscaled(extruder_volume.circle.radius + SceneEpsilon) }; + return_state = (m_max_print_height == 0.0) ? + object_state_templ(its, trafo, false, [circle](const Vec3f &pt) { return circle.contains(to_2d(pt)); }) : + object_state_templ(its, trafo, false, [circle, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && circle.contains(to_2d(pt)); }); + break; + } + case Type::Invalid: + default: + break; + } + } + + if (return_state != ObjectState::Inside) + return_state = ObjectState::Limited; + + return return_state; +} + +BuildVolume::ObjectState BuildVolume::check_object_state_with_extruder_areas(const indexed_triangle_set &its, const Transform3f &trafo, std::vector& inside_extruders) const +{ + ObjectState result = ObjectState::Inside; + int extruder_area_count = get_extruder_area_count(); + inside_extruders.resize(extruder_area_count, true); + for (int index = 0; index < extruder_area_count; index++) + { + ObjectState state = check_object_state_with_extruder_area(its, trafo, index); + + if (state == ObjectState::Limited) { + inside_extruders[index] = false; + result = ObjectState::Limited; + } + } + + return result; +} + +BuildVolume::ObjectState BuildVolume::check_volume_bbox_state_with_extruder_area(const BoundingBoxf3& volume_bbox, int index) const +{ + const BuildExtruderVolume& extruder_volume = get_extruder_area_volume(index); + BoundingBox3Base extruder_bbox = extruder_volume.bboxf.inflated(SceneEpsilon); + if (extruder_volume.same_with_bed || extruder_bbox.contains(volume_bbox)) + return ObjectState::Inside; + else + return ObjectState::Limited; +} + +BuildVolume::ObjectState BuildVolume::check_volume_bbox_state_with_extruder_areas(const BoundingBoxf3& volume_bbox, std::vector& inside_extruders) const +{ + ObjectState result = ObjectState::Inside; + int extruder_area_count = get_extruder_area_count(); + inside_extruders.resize(extruder_area_count, true); + for (int index = 0; index < extruder_area_count; index++) + { + ObjectState state = check_volume_bbox_state_with_extruder_area(volume_bbox, index); + + if (state == ObjectState::Limited) { + inside_extruders[index] = false; + result = ObjectState::Limited; + } + } + + return result; +} + + bool BuildVolume::all_paths_inside(const GCodeProcessorResult& paths, const BoundingBoxf3& paths_bbox, bool ignore_bottom) const { auto move_valid = [](const GCodeProcessorResult::MoveVertex &move) { @@ -340,7 +526,7 @@ bool BuildVolume::all_paths_inside(const GCodeProcessorResult& paths, const Boun const Vec2f c = unscaled(m_circle.center); const float r = unscaled(m_circle.radius) + epsilon; const float r2 = sqr(r); - return m_max_print_height == 0.0 ? + return m_max_print_height == 0.0 ? std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, c, r2](const GCodeProcessorResult::MoveVertex &move) { return ! move_valid(move) || (to_2d(move.position) - c).squaredNorm() <= r2; }) : std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, c, r2, z = m_max_print_height + epsilon](const GCodeProcessorResult::MoveVertex& move) @@ -350,7 +536,7 @@ bool BuildVolume::all_paths_inside(const GCodeProcessorResult& paths, const Boun //FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently. case Type::Custom: return m_max_print_height == 0.0 ? - std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, this](const GCodeProcessorResult::MoveVertex &move) + std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, this](const GCodeProcessorResult::MoveVertex &move) { return ! move_valid(move) || Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(move.position).cast()); }) : std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, this, z = m_max_print_height + epsilon](const GCodeProcessorResult::MoveVertex &move) { return ! move_valid(move) || (Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(move.position).cast()) && move.position.z() <= z); }); diff --git a/src/libslic3r/BuildVolume.hpp b/src/libslic3r/BuildVolume.hpp index 18471f1..f65ae49 100644 --- a/src/libslic3r/BuildVolume.hpp +++ b/src/libslic3r/BuildVolume.hpp @@ -17,10 +17,10 @@ struct GCodeProcessorResult; class BuildVolume { public: - enum class Type : unsigned char + enum class Type : char { // Not set yet or undefined. - Invalid, + Invalid = -1, // Rectangular print bed. Most common, cheap to work with. Rectangle, // Circular print bed. Common on detals, cheap to work with. @@ -31,15 +31,40 @@ public: Custom }; + struct BuildExtruderVolume { + bool same_with_bed{false}; + Type type{Type::Invalid}; + BoundingBox bbox; + BoundingBoxf3 bboxf; + Geometry::Circled circle; + }; + + struct BuildSharedVolume + { + // see: Bed3D::EShapeType + int type{ 0 }; + // data contains: + // Rectangle: + // [0] = min.x, [1] = min.y, [2] = max.x, [3] = max.y + // Circle: + // [0] = center.x, [1] = center.y, [3] = radius + std::array data; + // [0] = min z, [1] = max z + std::array zs; + }; + // Initialized to empty, all zeros, Invalid. BuildVolume() {} // Initialize from PrintConfig::printable_area and PrintConfig::printable_height - BuildVolume(const std::vector &printable_area, const double printable_height); + BuildVolume(const std::vector &printable_area, const double printable_height, const std::vector> &extruder_areas, const std::vector& extruder_printable_heights); // Source data, unscaled coordinates. const std::vector& printable_area() const { return m_bed_shape; } double printable_height() const { return m_max_print_height; } - + const std::vector>& extruder_areas() const { return m_extruder_shapes; } + const std::vector& extruder_heights() const { return m_extruder_printable_height; } + const BuildSharedVolume& get_shared_volume() const { return m_shared_volume; } + // Derived data Type type() const { return m_type; } // Format the type for console output. @@ -70,9 +95,11 @@ public: Colliding, // Outside of the build volume means the object is ignored: Not printed and no error is shown. Outside, - // Completely below the print bed. The same as Outside, but an object with one printable part below the print bed + // Completely below the print bed. The same as Outside, but an object with one printable part below the print bed // and at least one part above the print bed is still printable. Below, + //in Limited area + Limited }; // 1) Tests called on the plater. @@ -95,11 +122,23 @@ public: // Called on initial G-code preview on OpenGL vertex buffer interleaved normals and vertices. bool all_paths_inside_vertices_and_normals_interleaved(const std::vector& paths, const Eigen::AlignedBox& bbox, bool ignore_bottom = true) const; + int get_extruder_area_count() const { return m_extruder_volumes.size(); } + const BuildExtruderVolume& get_extruder_area_volume(int index) const; + ObjectState check_object_state_with_extruder_area(const indexed_triangle_set &its, const Transform3f &trafo, int index) const; + ObjectState check_object_state_with_extruder_areas(const indexed_triangle_set &its, const Transform3f &trafo, std::vector& inside_extruders) const; + ObjectState check_volume_bbox_state_with_extruder_area(const BoundingBoxf3& volume_bbox, int index) const; + ObjectState check_volume_bbox_state_with_extruder_areas(const BoundingBoxf3& volume_bbox, std::vector& inside_extruders) const; + private: // Source definition of the print bed geometry (PrintConfig::printable_area) std::vector m_bed_shape; + //QDS: extruder shapes + std::vector> m_extruder_shapes; //original data from config + std::vector m_extruder_volumes; + BuildSharedVolume m_shared_volume; //used for rendering // Source definition of the print volume height (PrintConfig::printable_height) double m_max_print_height { 0.f }; + std::vector m_extruder_printable_height; // Derived values. Type m_type { Type::Invalid }; diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 847b884..84a5cb7 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -112,6 +112,8 @@ set(lisbslic3r_sources Fill/Lightning/TreeNode.hpp Fill/FillRectilinear.cpp Fill/FillRectilinear.hpp + Fill/FillFloatingConcentric.hpp + Fill/FillFloatingConcentric.cpp Flow.cpp Flow.hpp Frustum.cpp @@ -139,8 +141,8 @@ set(lisbslic3r_sources Format/svg.cpp GCode/ThumbnailData.cpp GCode/ThumbnailData.hpp - GCode/CoolingBuffer.cpp - GCode/CoolingBuffer.hpp + GCode/GCodeEditor.cpp + GCode/GCodeEditor.hpp GCode/PostProcessor.cpp GCode/PostProcessor.hpp # GCode/PressureEqualizer.cpp @@ -163,6 +165,10 @@ set(lisbslic3r_sources GCode/AvoidCrossingPerimeters.hpp GCode/ConflictChecker.cpp GCode/ConflictChecker.hpp + GCode/Smoothing.cpp + GCode/Smoothing.hpp + GCode/CoolingBuffer.cpp + GCode/CoolingBuffer.hpp GCode.cpp GCode.hpp GCodeReader.cpp @@ -397,6 +403,8 @@ set(lisbslic3r_sources Arachne/BeadingStrategy/RedistributeBeadingStrategy.cpp Arachne/BeadingStrategy/WideningBeadingStrategy.hpp Arachne/BeadingStrategy/WideningBeadingStrategy.cpp + Arachne/BeadingStrategy/OuterWallContourStrategy.hpp + Arachne/BeadingStrategy/OuterWallContourStrategy.cpp Arachne/utils/ExtrusionJunction.hpp Arachne/utils/ExtrusionJunction.cpp Arachne/utils/ExtrusionLine.hpp @@ -428,6 +436,18 @@ set(lisbslic3r_sources ClipperZUtils.hpp FlushVolPredictor.hpp FlushVolPredictor.cpp + FilamentGroup.hpp + FilamentGroup.cpp + FilamentGroupUtils.hpp + FilamentGroupUtils.cpp + GCode/ToolOrderUtils.hpp + GCode/ToolOrderUtils.cpp + FlushVolPredictor.hpp + FlushVolPredictor.cpp + Interlocking/InterlockingGenerator.hpp + Interlocking/InterlockingGenerator.cpp + Interlocking/VoxelUtils.hpp + Interlocking/VoxelUtils.cpp GCode/Thumbnails.cpp GCode/Thumbnails.hpp ) diff --git a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp index 7e861cd..a235c70 100644 --- a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp +++ b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp @@ -286,12 +286,12 @@ std::tuple check_csgmesh_booleans(const Range return; } - if (!MeshBoolean::cgal::does_bound_a_volume(*m)) { + /*if (!MeshBoolean::cgal::does_bound_a_volume(*m)) {//has crash problem BOOST_LOG_TRIVIAL(info) << "check_csgmesh_booleans fails! mesh "<("filament_max_volumetric_speed")->get_at(0); - Flow pattern_line = Flow(line_width, layer_height, config.option("nozzle_diameter")->get_at(0)); - auto pa_speed = std::min(std::max(general_suggested_min_speed, config.option("outer_wall_speed")->value), + double filament_max_volumetric_speed = config.option("filament_max_volumetric_speed")->get_at(filament_idx); + Flow pattern_line = Flow(line_width, layer_height, config.option("nozzle_diameter")->get_at(extruder_id)); + auto pa_speed = std::min(std::max(general_suggested_min_speed, config.option("outer_wall_speed")->get_at(extruder_id)), filament_max_volumetric_speed / pattern_line.mm3_per_mm()); return std::floor(pa_speed); @@ -206,9 +206,9 @@ double CalibPressureAdvance::get_distance(Vec2d from, Vec2d to) const { return s std::string CalibPressureAdvance::draw_line(GCodeWriter &writer, Vec2d to_pt, double line_width, double layer_height, double speed, const std::string &comment) { - const double e_per_mm = CalibPressureAdvance::e_per_mm(line_width, layer_height, m_config.option("nozzle_diameter")->get_at(0), + const double e_per_mm = CalibPressureAdvance::e_per_mm(line_width, layer_height, m_config.option("nozzle_diameter")->get_at(0), m_config.option("filament_diameter")->get_at(0), - m_config.option("filament_flow_ratio")->get_at(0)); + m_config.option("filament_flow_ratio")->get_at(0)); const double length = get_distance(Vec2d(m_last_pos.x(), m_last_pos.y()), to_pt); auto dE = e_per_mm * length; @@ -553,7 +553,7 @@ void CalibPressureAdvancePattern::generate_custom_gcodes(const DynamicPrintConfi if (i == 1) { gcode << m_writer.set_pressure_advance(m_params.start); - double number_e_per_mm = e_per_mm(line_width(), height_layer(), m_config.option("nozzle_diameter")->get_at(0), + double number_e_per_mm = e_per_mm(line_width(), height_layer(), m_config.option("nozzle_diameter")->get_at(0), m_config.option("filament_diameter")->get_at(0), m_config.option("filament_flow_ratio")->get_at(0)); diff --git a/src/libslic3r/Calib.hpp b/src/libslic3r/Calib.hpp index 772df9a..488354d 100644 --- a/src/libslic3r/Calib.hpp +++ b/src/libslic3r/Calib.hpp @@ -22,7 +22,8 @@ enum class CalibMode : int { Calib_Retraction_tower, //w29 Calib_Flow_Rate_Coarse, - Calib_Flow_Rate_Fine + Calib_Flow_Rate_Fine, + Calib_Test_Model }; enum class CalibState { @@ -38,6 +39,7 @@ enum class CalibState { struct Calib_Params { Calib_Params() : mode(CalibMode::Calib_None){} + int extruder_id = 0; double start, end, step; bool print_numbers = false; CalibMode mode; @@ -53,8 +55,13 @@ class X1CCalibInfos public: struct X1CCalibInfo { + int extruder_id = 0; int tray_id; + int ams_id = 0; + int slot_id = 0; int bed_temp; + ExtruderType extruder_type{ExtruderType::etDirectDrive}; + NozzleVolumeType nozzle_volume_type = NozzleVolumeType::nvtStandard; int nozzle_temp; float nozzle_diameter; std::string filament_id; @@ -64,12 +71,15 @@ public: }; std::vector calib_datas; + CalibMode cali_mode{ CalibMode::Calib_None }; }; class CaliPresetInfo { public: int tray_id; + int extruder_id; + NozzleVolumeType nozzle_volume_type; float nozzle_diameter; std::string filament_id; std::string setting_id; @@ -78,6 +88,8 @@ public: CaliPresetInfo &operator=(const CaliPresetInfo &other) { this->tray_id = other.tray_id; + this->extruder_id = other.extruder_id; + this->nozzle_volume_type = other.nozzle_volume_type; this->nozzle_diameter = other.nozzle_diameter; this->filament_id = other.filament_id; this->setting_id = other.setting_id; @@ -103,7 +115,11 @@ public: CALI_RESULT_PROBLEM = 1, CALI_RESULT_FAILED = 2, }; - int tray_id; + int extruder_id = 0; + NozzleVolumeType nozzle_volume_type; + int tray_id = 0; + int ams_id = 0; + int slot_id = 0; int cali_idx = -1; float nozzle_diameter; std::string filament_id; @@ -116,12 +132,33 @@ public: struct PACalibIndexInfo { - int tray_id; - int cali_idx; + int extruder_id = 0; + NozzleVolumeType nozzle_volume_type; + int tray_id = 0; + int ams_id = 0; + int slot_id = 0; + int cali_idx = -1; // -1 means default float nozzle_diameter; std::string filament_id; }; +struct PACalibExtruderInfo +{ + int extruder_id = 0; + NozzleVolumeType nozzle_volume_type; + float nozzle_diameter; + std::string filament_id = ""; + bool use_extruder_id{true}; + bool use_nozzle_volume_type{true}; +}; + +struct PACalibTabInfo +{ + float pa_calib_tab_nozzle_dia; + int extruder_id; + NozzleVolumeType nozzle_volume_type; +}; + class FlowRatioCalibResult { public: @@ -147,7 +184,7 @@ struct DrawBoxOptArgs class CalibPressureAdvance { public: - static float find_optimal_PA_speed(const DynamicPrintConfig &config, double line_width, double layer_height, int filament_idx = 0); + static float find_optimal_PA_speed(const DynamicPrintConfig &config, double line_width, double layer_height, int extruder_id = 0, int filament_idx = 0); protected: CalibPressureAdvance() = default; @@ -226,7 +263,9 @@ private: struct SuggestedConfigCalibPAPattern { - const std::vector> float_pairs{{"initial_layer_print_height", 0.25}, {"layer_height", 0.2}, {"initial_layer_speed", 30}}; + const std::vector> float_pairs{{"initial_layer_print_height", 0.25}, {"layer_height", 0.2}}; + + const std::vector>> floats_pairs{{"initial_layer_speed", {30}}}; const std::vector> nozzle_ratio_pairs{{"line_width", 112.5}, {"initial_layer_line_width", 140}}; @@ -256,8 +295,8 @@ public: Vec3d get_start_offset(); protected: - double speed_first_layer() const { return m_config.option("initial_layer_speed")->value; }; - double speed_perimeter() const { return m_config.option("outer_wall_speed")->value; }; + double speed_first_layer() const { return m_config.option("initial_layer_speed")->get_at(m_params.extruder_id); }; + double speed_perimeter() const { return m_config.option("outer_wall_speed")->get_at(m_params.extruder_id); }; double line_width_first_layer() const { return m_config.get_abs_value("initial_layer_line_width"); }; double line_width() const { return m_config.get_abs_value("line_width"); }; int wall_count() const { return m_config.option("wall_loops")->value; }; diff --git a/src/libslic3r/Clipper2Utils.cpp b/src/libslic3r/Clipper2Utils.cpp index 1469d90..417bab2 100644 --- a/src/libslic3r/Clipper2Utils.cpp +++ b/src/libslic3r/Clipper2Utils.cpp @@ -1,4 +1,6 @@ #include "Clipper2Utils.hpp" +#include "libslic3r.h" +#include "clipper2/clipper.h" namespace Slic3r { @@ -33,6 +35,87 @@ Clipper2Lib::Paths64 Slic3rPoints_to_Paths64(const std::vector& in) return out; } +Points Path64ToPoints(const Clipper2Lib::Path64& path64) +{ + Points points; + points.reserve(path64.size()); + for (const Clipper2Lib::Point64 &point64 : path64) points.emplace_back(std::move(Slic3r::Point(point64.x, point64.y))); + return points; +} + +static ExPolygons PolyTreeToExPolygons(Clipper2Lib::PolyTree64 &&polytree) +{ + struct Inner + { + static void PolyTreeToExPolygonsRecursive(Clipper2Lib::PolyTree64 &&polynode, ExPolygons *expolygons) + { + size_t cnt = expolygons->size(); + expolygons->resize(cnt + 1); + (*expolygons)[cnt].contour.points = Path64ToPoints(polynode.Polygon()); + + (*expolygons)[cnt].holes.resize(polynode.Count()); + for (int i = 0; i < polynode.Count(); ++i) { + (*expolygons)[cnt].holes[i].points = Path64ToPoints(polynode[i]->Polygon()); + // Add outer polygons contained by (nested within) holes. + for (int j = 0; j < polynode[i]->Count(); ++j) PolyTreeToExPolygonsRecursive(std::move(*polynode[i]->Child(j)), expolygons); + } + } + + static size_t PolyTreeCountExPolygons(const Clipper2Lib::PolyPath64& polynode) + { + size_t cnt = 1; + for (size_t i = 0; i < polynode.Count(); ++i) { + for (size_t j = 0; j < polynode.Child(i)->Count(); ++j) cnt += PolyTreeCountExPolygons(*polynode.Child(i)->Child(j)); + } + return cnt; + } + }; + + ExPolygons retval; + size_t cnt = 0; + for (int i = 0; i < polytree.Count(); ++i) cnt += Inner::PolyTreeCountExPolygons(*polytree[i]); + retval.reserve(cnt); + for (int i = 0; i < polytree.Count(); ++i) Inner::PolyTreeToExPolygonsRecursive(std::move(*polytree[i]), &retval); + return retval; +} + +void SimplifyPolyTree(const Clipper2Lib::PolyPath64 &polytree, double epsilon, Clipper2Lib::PolyPath64 &result) +{ + for (const auto &child : polytree) { + Clipper2Lib::PolyPath64 *newchild = result.AddChild(Clipper2Lib::SimplifyPath(child->Polygon(), epsilon)); + SimplifyPolyTree(*child, epsilon, *newchild); + } +} + +Clipper2Lib::Paths64 Slic3rPolygons_to_Paths64(const Polygons &in) +{ + Clipper2Lib::Paths64 out; + out.reserve(in.size()); + for (const Polygon &poly : in) { + Clipper2Lib::Path64 path; + path.reserve(poly.points.size()); + for (const Slic3r::Point &point : poly.points) path.emplace_back(std::move(Clipper2Lib::Point64(point.x(), point.y()))); + out.emplace_back(std::move(path)); + } + return out; +} + +Clipper2Lib::Paths64 Slic3rExPolygons_to_Paths64(const ExPolygons& in) +{ + Clipper2Lib::Paths64 out; + out.reserve(in.size()); + for (const ExPolygon& expolygon : in) { + for (size_t i = 0; i < expolygon.num_contours(); i++) { + const auto &poly = expolygon.contour_or_hole(i); + Clipper2Lib::Path64 path; + path.reserve(poly.points.size()); + for (const Slic3r::Point &point : poly.points) path.emplace_back(std::move(Clipper2Lib::Point64(point.x(), point.y()))); + out.emplace_back(std::move(path)); + } + } + return out; +} + Polylines _clipper2_pl_open(Clipper2Lib::ClipType clipType, const Slic3r::Polylines& subject, const Slic3r::Polygons& clip) { Clipper2Lib::Clipper64 c; @@ -57,4 +140,72 @@ Slic3r::Polylines intersection_pl_2(const Slic3r::Polylines& subject, const Slic Slic3r::Polylines diff_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip) { return _clipper2_pl_open(Clipper2Lib::ClipType::Difference, subject, clip); } -} \ No newline at end of file +ExPolygons union_ex_2(const Polygons& polygons) +{ + Clipper2Lib::Clipper64 c; + c.AddSubject(Slic3rPolygons_to_Paths64(polygons)); + + Clipper2Lib::ClipType ct = Clipper2Lib::ClipType::Union; + Clipper2Lib::FillRule fr = Clipper2Lib::FillRule::NonZero; + Clipper2Lib::PolyTree64 solution; + c.Execute(ct, fr, solution); + + ExPolygons results = PolyTreeToExPolygons(std::move(solution)); + + return results; +} + +ExPolygons union_ex_2(const ExPolygons &expolygons) +{ + Clipper2Lib::Clipper64 c; + c.AddSubject(Slic3rExPolygons_to_Paths64(expolygons)); + + Clipper2Lib::ClipType ct = Clipper2Lib::ClipType::Union; + Clipper2Lib::FillRule fr = Clipper2Lib::FillRule::NonZero; + Clipper2Lib::PolyTree64 solution; + c.Execute(ct, fr, solution); + + ExPolygons results = PolyTreeToExPolygons(std::move(solution)); + + return results; +} + +// 对 ExPolygons 进行偏移 +ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta) +{ + Clipper2Lib::Paths64 subject = Slic3rExPolygons_to_Paths64(expolygons); + Clipper2Lib::ClipperOffset offsetter; + offsetter.AddPaths(subject, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); + Clipper2Lib::PolyPath64 polytree; + offsetter.Execute(delta, polytree); + ExPolygons results = PolyTreeToExPolygons(std::move(polytree)); + + return results; +} + +ExPolygons offset2_ex_2(const ExPolygons& expolygons, double delta1, double delta2) +{ + // 1st offset + Clipper2Lib::Paths64 subject = Slic3rExPolygons_to_Paths64(expolygons); + Clipper2Lib::ClipperOffset offsetter; + offsetter.AddPaths(subject, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); + Clipper2Lib::PolyPath64 polytree; + offsetter.Execute(delta1, polytree); + + // simplify the result + Clipper2Lib::PolyPath64 polytree2; + SimplifyPolyTree(polytree, SCALED_EPSILON, polytree2); + + // 2nd offset + offsetter.Clear(); + offsetter.AddPaths(Clipper2Lib::PolyTreeToPaths64(polytree2), Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); + polytree.Clear(); + offsetter.Execute(delta2, polytree); + + // convert back to expolygons + ExPolygons results = PolyTreeToExPolygons(std::move(polytree)); + + return results; +} + +} // namespace Slic3r \ No newline at end of file diff --git a/src/libslic3r/Clipper2Utils.hpp b/src/libslic3r/Clipper2Utils.hpp index f694217..402d43c 100644 --- a/src/libslic3r/Clipper2Utils.hpp +++ b/src/libslic3r/Clipper2Utils.hpp @@ -1,8 +1,7 @@ #ifndef slic3r_Clipper2Utils_hpp_ #define slic3r_Clipper2Utils_hpp_ -#include "libslic3r.h" -#include "clipper2/clipper.h" +#include "ExPolygon.hpp" #include "Polygon.hpp" #include "Polyline.hpp" @@ -10,7 +9,10 @@ namespace Slic3r { Slic3r::Polylines intersection_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip); Slic3r::Polylines diff_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip); - +ExPolygons union_ex_2(const Polygons &expolygons); +ExPolygons union_ex_2(const ExPolygons &expolygons); +ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta); +ExPolygons offset2_ex_2(const ExPolygons &expolygons, double delta1, double delta2); } #endif diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 13b7290..64029a3 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -830,6 +830,13 @@ Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons& poly1, const Slic3r::ExPol return union_ex(expolys); } +Slic3r::ExPolygons xor_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset) { + return _clipper_ex(ClipperLib::ctXor, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonProvider(clip), do_safety_offset); +} +Slic3r::ExPolygons xor_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { + return _clipper_ex(ClipperLib::ctXor, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); +} + template Polylines _clipper_pl_open(ClipperLib::ClipType clipType, PathsProvider1 &&subject, PathsProvider2 &&clip) { diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index cb2b8ee..2377349 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -563,6 +563,9 @@ Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons& poly1, const Slic3r::ExPol ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject); ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject); +Slic3r::ExPolygons xor_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons xor_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); + Slic3r::Polygons union_pt_chained_outside_in(const Slic3r::Polygons &subject); ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes); diff --git a/src/libslic3r/Color.cpp b/src/libslic3r/Color.cpp index 654b248..3775362 100644 --- a/src/libslic3r/Color.cpp +++ b/src/libslic3r/Color.cpp @@ -235,6 +235,27 @@ ColorRGBA ColorRGBA::operator * (float value) const return ret; } +void ColorRGBA::gamma_correct() { + auto coe = 1 / 2.2f; + for (int i = 0; i < 4; i++) { + m_data[i] = std::pow(m_data[i], coe); + } +} + +void ColorRGBA::gamma_correct(RGBA &color) +{ + auto coe = 1 / 2.2f; + for (int i = 0; i < 4; i++) { + color[i] = std::pow(color[i], coe); + } +} + +float ColorRGBA::gamma_correct(float value) +{ + auto coe = 1 / 2.2f; + return std::pow(value, coe); +} + ColorRGB operator * (float value, const ColorRGB& other) { return other * value; } ColorRGBA operator * (float value, const ColorRGBA& other) { return other * value; } diff --git a/src/libslic3r/Color.hpp b/src/libslic3r/Color.hpp index a622a84..111bbbf 100644 --- a/src/libslic3r/Color.hpp +++ b/src/libslic3r/Color.hpp @@ -108,7 +108,9 @@ public: void g(float g) { m_data[1] = std::clamp(g, 0.0f, 1.0f); } void b(float b) { m_data[2] = std::clamp(b, 0.0f, 1.0f); } void a(float a) { m_data[3] = std::clamp(a, 0.0f, 1.0f); } - + void gamma_correct(); + static void gamma_correct(RGBA& color); + static float gamma_correct(float value); void set(unsigned int comp, float value) { assert(0 <= comp && comp <= 3); m_data[comp] = std::clamp(value, 0.0f, 1.0f); diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index c132db3..73dc62a 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -451,6 +451,19 @@ void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys if (my_opt == nullptr) { // opt_key does not exist in this ConfigBase and it cannot be created, because it is not defined by this->def(). // This is only possible if other is of DynamicConfig type. + if (auto n = opt_key.find('#'); n != std::string::npos) { + auto opt_key2 = opt_key.substr(0, n); + auto my_opt2 = dynamic_cast(this->option(opt_key2)); + auto other_opt = other.option(opt_key2); + if (my_opt2 == nullptr && other_opt) + my_opt2 = dynamic_cast(this->option(opt_key2, true)); + if (my_opt2) { + int index = std::atoi(opt_key.c_str() + n + 1); + if (other_opt) + my_opt2->set_at(other_opt, index, index); + continue; + } + } if (ignore_nonexistent) continue; throw UnknownOptionException(opt_key); @@ -650,6 +663,32 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con return success; } +double ConfigBase::get_abs_value_at(const t_config_option_key &opt_key, size_t index) const +{ + const ConfigOption *raw_opt = this->option(opt_key); + assert(raw_opt != nullptr); + if (raw_opt->type() == coFloats) { + return static_cast(raw_opt)->get_at(index); + } + if (raw_opt->type() == coFloatsOrPercents) { + const ConfigDef *def = this->def(); + if (def == nullptr) throw NoDefinitionException(opt_key); + const ConfigOptionDef *opt_def = def->get(opt_key); + assert(opt_def != nullptr); + + if (opt_def->ratio_over.empty()) { + return 0; + } else { + const ConfigOption *ratio_opt = this->option(opt_def->ratio_over); + assert(ratio_opt->type() == coFloats); + const ConfigOptionFloats *ratio_values = static_cast(ratio_opt); + return static_cast(raw_opt)->get_at(index).get_abs_value(ratio_values->get_at(index)); + } + } + + throw ConfigurationError("ConfigBase::get_abs_value_at(): Not a valid option type for get_abs_value_at()"); +} + // Return an absolute value of a possibly relative config variable. // For example, return absolute infill extrusion width, either from an absolute value, or relative to the layer height. double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const @@ -672,7 +711,8 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const return opt_def->ratio_over.empty() ? 0. : static_cast(raw_opt)->get_abs_value(this->get_abs_value(opt_def->ratio_over)); } - throw ConfigurationError("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()"); + std::string err_info = "ConfigBase::get_abs_value(): Not a valid option type for get_abs_value(), parameter : " + opt_key; + throw ConfigurationError(err_info); } // Return an absolute value of a possibly relative config variable. @@ -754,6 +794,10 @@ ConfigSubstitutions ConfigBase::load_from_json(const std::string &file, ForwardC int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContext& substitution_context, bool load_inherits_to_config, std::map& key_values, std::string& reason) { + if (!boost::filesystem::exists(file)) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format("%1% file not exist.") % file; + return -1; + } json j; std::list different_settings_append; std::string new_support_style; @@ -763,6 +807,50 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex CNumericLocalesSetter locales_setter; + std::function parse_str_arr = [&parse_str_arr](const json::const_iterator& it, const char single_sep,const char array_sep,const bool escape_string_style,std::string& value_str)->bool { + // must have consistent type name + std::string consistent_type; + for (auto iter = it.value().begin(); iter != it.value().end(); ++iter) { + if (consistent_type.empty()) + consistent_type = iter.value().type_name(); + else { + if (consistent_type != iter.value().type_name()) + return false; + } + } + + bool first = true; + for (auto iter = it.value().begin(); iter != it.value().end(); iter++) { + if (iter.value().is_array()) { + if (!first) + value_str += array_sep; + else + first = false; + bool success = parse_str_arr(iter, single_sep, array_sep,escape_string_style, value_str); + if (!success) + return false; + } + else if (iter.value().is_string()) { + if (!first) + value_str += single_sep; + else + first = false; + if (!escape_string_style) + value_str += iter.value(); + else { + value_str += "\""; + value_str += escape_string_cstyle(iter.value()); + value_str += "\""; + } + } + else { + //should not happen + return false; + } + } + return true; + }; + try { boost::nowide::ifstream ifs(file); ifs >> j; @@ -779,7 +867,7 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex key_values.emplace(QDT_JSON_KEY_VERSION, it.value()); } else if (boost::iequals(it.key(), QDT_JSON_KEY_IS_CUSTOM)) { - key_values.emplace(QDT_JSON_KEY_IS_CUSTOM, it.value()); + //skip it } else if (boost::iequals(it.key(), QDT_JSON_KEY_NAME)) { key_values.emplace(QDT_JSON_KEY_NAME, it.value()); @@ -845,8 +933,7 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex substitution_context.unrecogized_keys.push_back(opt_key_src); continue; } - bool valid = true, first = true, use_comma = true; - //bool test2 = (it.key() == std::string("filament_end_gcode")); + bool valid = true, first = true; const ConfigOptionDef* optdef = config_def->get(opt_key); if (optdef == nullptr) { // If we didn't find an option, look for any other option having this as an alias. @@ -863,35 +950,30 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex } } - if (optdef && optdef->type == coStrings) { - use_comma = false; - } - for (auto iter = it.value().begin(); iter != it.value().end(); iter++) { - if (iter.value().is_string()) { - if (!first) { - if (use_comma) - value_str += ","; - else - value_str += ";"; - } - else - first = false; - - if (use_comma) - value_str += iter.value(); - else { - value_str += "\""; - value_str += escape_string_cstyle(iter.value()); - value_str += "\""; - } - } - else { - //should not happen - BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": parse "<type) + { + case coStrings: + escape_string_type = true; + single_sep = ';'; + break; + case coPointsGroups: + single_sep = '#'; + break; + default: break; } } + + // QDS: we only support 2 depth array + valid = parse_str_arr(it, single_sep, array_sep,escape_string_type, value_str); + if (!valid) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << file << " error, invalid json array for " << it.key(); + break; + } if (valid) this->set_deserialize(opt_key, value_str, substitution_context); } @@ -1340,15 +1422,13 @@ ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, Fo } //QDS: add json support -void ConfigBase::save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version, const std::string is_custom) const +void ConfigBase::save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version) const { json j; //record the headers j[QDT_JSON_KEY_VERSION] = version; j[QDT_JSON_KEY_NAME] = name; j[QDT_JSON_KEY_FROM] = from; - if (!is_custom.empty()) - j[QDT_JSON_KEY_IS_CUSTOM] = is_custom; //record all the key-values for (const std::string &opt_key : this->keys()) @@ -1374,14 +1454,14 @@ void ConfigBase::save_to_json(const std::string &file, const std::string &name, j[opt_key] = opt->serialize(); } else { - const ConfigOptionVectorBase *vec = static_cast(opt); + const ConfigOptionVectorBase* vec = static_cast(opt); //if (!vec->empty()) std::vector string_values = vec->vserialize(); /*for (int i = 0; i < string_values.size(); i++) { - std::string string_value = escape_string_cstyle(string_values[i]); - j[opt_key][i] = string_value; + std::string string_value = escape_string_cstyle(string_values[i]); + j[opt_key][i] = string_value; }*/ json j_array(string_values); @@ -1704,6 +1784,39 @@ t_config_option_keys DynamicConfig::equal(const DynamicConfig &other) const return equal; } +double& DynamicConfig::opt_float(const t_config_option_key &opt_key, unsigned int idx) +{ + if (ConfigOptionFloats *opt_floats = dynamic_cast(this->option(opt_key))) { + return opt_floats->get_at(idx); + } else { + ConfigOptionFloatsNullable *opt_floats_nullable = dynamic_cast(this->option(opt_key)); + assert(opt_floats_nullable != nullptr); + return opt_floats_nullable->get_at(idx); + } +} +const double& DynamicConfig::opt_float(const t_config_option_key &opt_key, unsigned int idx) const +{ + if (const ConfigOptionFloats *opt_floats = dynamic_cast(this->option(opt_key))) { + return opt_floats->get_at(idx); + } else if (const ConfigOptionFloatsNullable *opt_floats_nullable = dynamic_cast(this->option(opt_key))) { + return opt_floats_nullable->get_at(idx); + } else { + assert(false); + return 0; + } +} + +bool DynamicConfig::opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { + if (const ConfigOptionBools *opts = dynamic_cast(this->option(opt_key))) { + return opts->get_at(idx) != 0; + } + else { + const ConfigOptionBoolsNullable *opt_s = dynamic_cast(this->option(opt_key)); + assert(opt_s != nullptr); + return opt_s->get_at(idx) != 0; + } +} + } #include diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 39efa53..7422f37 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -28,11 +28,11 @@ namespace Slic3r { struct FloatOrPercent { - double value; - bool percent; + double value = 0; + bool percent = false; FloatOrPercent() {} - FloatOrPercent(double value_, bool percent_) : value(value_), percent(percent_) {} + FloatOrPercent(double value_, bool percent_) : value(value_), percent(percent_) { } double get_abs_value(double ratio_over) const { return this->percent ? (ratio_over * this->value / 100) : this->value; } @@ -196,6 +196,8 @@ enum ConfigOptionType { coEnum = 9, // QDS: vector of enums coEnums = coEnum + coVectorType, + coPointsGroups = 10 + coVectorType, + coIntsGroups = 11 + coVectorType }; enum ConfigOptionMode { @@ -290,7 +292,7 @@ public: return *this != *rhs; } // Apply an override option, possibly a nullable one. - virtual bool apply_override(const ConfigOption *rhs) { + virtual bool apply_override(const ConfigOption *rhs, std::vector& default_index) { if (*this == *rhs) return false; *this = *rhs; @@ -349,6 +351,14 @@ public: // Set a single vector item from either a scalar option or the first value of a vector option.vector of ConfigOptions. // This function is useful to split values from multiple extrder / filament settings into separate configurations. virtual void set_at(const ConfigOption *rhs, size_t i, size_t j) = 0; + //QDS + virtual void set_at_to_nil(size_t i) = 0; + virtual void append(const ConfigOption *rhs) = 0; + virtual void set(const ConfigOption* rhs, size_t start, size_t len) = 0; + virtual void set_with_restore(const ConfigOptionVectorBase* rhs, std::vector& restore_index, int stride) = 0; + virtual void set_only_diff(const ConfigOptionVectorBase* rhs, std::vector& diff_index, int stride) = 0; + virtual void set_to_index(const ConfigOptionVectorBase* rhs, std::vector& dest_index, int stride) = 0; + virtual void set_with_nil(const ConfigOptionVectorBase* rhs, const ConfigOptionVectorBase* inherits, int stride) = 0; // Resize the vector of values, copy the newly added values from opt_default if provided. virtual void resize(size_t n, const ConfigOption *opt_default = nullptr) = 0; // Clear the values vector. @@ -436,6 +446,155 @@ public: throw ConfigurationError("ConfigOptionVector::set_at(): Assigning an incompatible type"); } + //QDS + virtual void set_at_to_nil(size_t i) override {} + + void append(const ConfigOption *rhs) override + { + if (rhs->type() == this->type()) { + // Assign the first value of the rhs vector. + auto other = static_cast*>(rhs); + if (other->values.empty()) + throw ConfigurationError("ConfigOptionVector::append(): append an empty vector"); + this->values.insert(this->values.end(), other->values.begin(), other->values.end()); + } else if (rhs->type() == this->scalar_type()) + this->values.push_back(static_cast*>(rhs)->value); + else + throw ConfigurationError("ConfigOptionVector::append(): append an incompatible type"); + } + + // Set a single vector item from a range of another vector option + // This function is useful to split values from multiple extrder / filament settings into separate configurations. + void set(const ConfigOption* rhs, size_t start, size_t len) override + { + // It is expected that the vector value has at least one value, which is the default, if not overwritten. + assert(!this->values.empty()); + T v = this->values.front(); + this->values.resize(len, v); + if (rhs->type() == this->type()) { + // Assign the first value of the rhs vector. + auto other = static_cast*>(rhs); + if (other->values.size() < (start+len)) + throw ConfigurationError("ConfigOptionVector::set_with(): Assigning from an vector with invalid size"); + for (size_t i = 0; i < len; i++) + this->values[i] = other->get_at(start+i); + } + else + throw ConfigurationError("ConfigOptionVector::set_with(): Assigning an incompatible type"); + } + + //set a item related with extruder variants when loading config from 3mf, restore the non change values to system config + //rhs: item from systemconfig(inherits) + //keep_index: which index in this vector need to be restored + virtual void set_with_restore(const ConfigOptionVectorBase* rhs, std::vector& restore_index, int stride) override + { + if (rhs->type() == this->type()) { + //backup original ones + std::vector backup_values = this->values; + // Assign the first value of the rhs vector. + auto other = static_cast*>(rhs); + this->values = other->values; + + if (other->values.size() != (restore_index.size()*stride)) + throw ConfigurationError("ConfigOptionVector::set_with_restore(): Assigning from an vector with invalid restore_index size"); + + for (size_t i = 0; i < restore_index.size(); i++) { + if (restore_index[i] != -1) { + for (size_t j = 0; j < stride; j++) + this->values[i * stride +j] = backup_values[restore_index[i] * stride +j]; + } + } + } + else + throw ConfigurationError("ConfigOptionVector::set_with_keep(): Assigning an incompatible type"); + } + + //set a item related with extruder variants when loading user config, only set the different value of some extruder + //rhs: item from user config + //diff_index: which index in this vector need to be set + virtual void set_only_diff(const ConfigOptionVectorBase* rhs, std::vector& diff_index, int stride) override + { + if (rhs->type() == this->type()) { + // Assign the first value of the rhs vector. + auto other = static_cast*>(rhs); + + if (this->values.size() != (diff_index.size()*stride)) + throw ConfigurationError("ConfigOptionVector::set_only_diff(): Assigning from an vector with invalid diff_index size"); + + for (size_t i = 0; i < diff_index.size(); i++) { + if (diff_index[i] != -1) { + for (size_t j = 0; j < stride; j++) + { + if (!other->is_nil(diff_index[i])) + this->values[i * stride +j] = other->values[diff_index[i] * stride +j]; + } + } + } + } + else + throw ConfigurationError("ConfigOptionVector::set_only_diff(): Assigning an incompatible type"); + } + + //set a item related with extruder variants when apply static config with dynamic config + //rhs: item from dynamic config + //dest_index: which index in this vector need to be used + virtual void set_to_index(const ConfigOptionVectorBase* rhs, std::vector& dest_index, int stride) override + { + if (rhs->type() == this->type()) { + // Assign the first value of the rhs vector. + auto other = static_cast*>(rhs); + T v = other->values.front(); + this->values.resize(dest_index.size(), v); + + for (size_t i = 0; i < dest_index.size(); i++) { + for (size_t j = 0; j < stride; j++) + { + if (!other->is_nil(dest_index[i])) + this->values[i * stride +j] = other->values[dest_index[i] * stride +j]; + } + } + } + else + throw ConfigurationError("ConfigOptionVector::set_with_index(): Assigning an incompatible type"); + } + + //set a item related with extruder variants when saving user config, set the non-diff value of some extruder to nill + //this item has different value with inherit config + //rhs: item from userconfig + //inherits: item from inherit config + virtual void set_with_nil(const ConfigOptionVectorBase* rhs, const ConfigOptionVectorBase* inherits, int stride) override + { + if ((rhs->type() == this->type()) && (inherits->type() == this->type())) { + auto rhs_opt = static_cast*>(rhs); + auto inherits_opt = static_cast*>(inherits); + + if (inherits->size() != rhs->size()) + throw ConfigurationError("ConfigOptionVector::set_with_nil(): rhs size different with inherits size"); + + this->values.resize(inherits->size(), this->values.front()); + + for (size_t i = 0; i < inherits_opt->size(); i= i+stride) { + bool set_nil = true; + for (size_t j = 0; j < stride; j++) { + if (inherits_opt->values[i +j] != rhs_opt->values[i +j]) { + set_nil = false; + break; + } + } + + for (size_t j = 0; j < stride; j++) { + if (set_nil) { + this->set_at_to_nil(i +j); + } + else + this->values[i +j] = rhs_opt->values[i +j]; + } + } + } + else + throw ConfigurationError("ConfigOptionVector::set_with_nil(): Assigning an incompatible type"); + } + const T& get_at(size_t i) const { assert(! this->values.empty()); @@ -505,9 +664,10 @@ public: // Is this option overridden by another option? // An option overrides another option if it is not nil and not equal. + // assume lhs is not nullable even it is nullable bool overriden_by(const ConfigOption *rhs) const override { - if (this->nullable()) - throw ConfigurationError("Cannot override a nullable ConfigOption."); + /*if (this->nullable()) + throw ConfigurationError("Cannot override a nullable ConfigOption.");*/ if (rhs->type() != this->type()) throw ConfigurationError("ConfigOptionVector.overriden_by() applied to different types."); auto rhs_vec = static_cast*>(rhs); @@ -525,9 +685,10 @@ public: return false; } // Apply an override option, possibly a nullable one. - bool apply_override(const ConfigOption *rhs) override { - if (this->nullable()) - throw ConfigurationError("Cannot override a nullable ConfigOption."); + // assume lhs is not nullable even it is nullable + bool apply_override(const ConfigOption *rhs, std::vector& default_index) override { + //if (this->nullable()) + // throw ConfigurationError("Cannot override a nullable ConfigOption."); if (rhs->type() != this->type()) throw ConfigurationError("ConfigOptionVector.apply_override() applied to different types."); auto rhs_vec = static_cast*>(rhs); @@ -544,19 +705,26 @@ public: if (cnt < 1) return false; + std::vector default_value = this->values; + if (this->values.empty()) this->values.resize(rhs_vec->size()); else this->values.resize(rhs_vec->size(), this->values.front()); - bool modified = false; - auto default_value = this->values[0]; + assert(default_index.size() == rhs_vec->size()); + + bool modified = false; + for (size_t i = 0; i < rhs_vec->size(); ++i) { if (!rhs_vec->is_nil(i)) { this->values[i] = rhs_vec->values[i]; modified = true; } else { - this->values[i] = default_value; + if ((i < default_index.size()) && (default_index[i] - 1 < default_value.size())) + this->values[i] = default_value[default_index[i] - 1]; + else + this->values[i] = default_value[0]; } } return modified; @@ -634,6 +802,11 @@ public: // A scalar is nil, or all values of a vector are nil. bool is_nil() const override { for (auto v : this->values) if (! std::isnan(v)) return false; return true; } bool is_nil(size_t idx) const override { return std::isnan(this->values[idx]); } + virtual void set_at_to_nil(size_t i) override + { + assert(nullable() && (i < this->values.size())); + this->values[i] = nil_value(); + } std::string serialize() const override { @@ -795,6 +968,11 @@ public: // A scalar is nil, or all values of a vector are nil. bool is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; } bool is_nil(size_t idx) const override { return this->values[idx] == nil_value(); } + virtual void set_at_to_nil(size_t i) override + { + assert(nullable() && (i < this->values.size())); + this->values[i] = nil_value(); + } std::string serialize() const override { @@ -1373,6 +1551,210 @@ private: template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; +class ConfigOptionPointsGroups :public ConfigOptionVector +{ +public: + ConfigOptionPointsGroups() :ConfigOptionVector() {} + explicit ConfigOptionPointsGroups(std::initializer_list il) :ConfigOptionVector(std::move(il)) {} + explicit ConfigOptionPointsGroups(const std::vector& values) :ConfigOptionVector(values) {} + + static ConfigOptionType static_type() { return coPointsGroups; } + ConfigOptionType type()const override { return static_type(); } + ConfigOption* clone()const override { return new ConfigOptionPointsGroups(*this); } + ConfigOptionPointsGroups& operator=(const ConfigOption* opt) { this->set(opt); return *this; } + bool operator == (const ConfigOptionPointsGroups& rhs)const throw() { return this->values == rhs.values; } + bool operator == (const ConfigOption& rhs) const override { + if (rhs.type() != this->type()) + throw ConfigurationError("ConfigOptionPointsGroupsTempl: Comparing incompatible types"); + assert(dynamic_cast*>(&rhs)); + + return this->values == static_cast*>(&rhs)->values; + } + bool nullable() const override { return false; } + bool is_nil(size_t) const override { return false; } + + std::string serialize()const override + { + std::ostringstream ss; + for (auto iter = this->values.begin(); iter != this->values.end(); ++iter) { + if (iter != this->values.begin()) + ss << "#"; + serialize_single_value(ss, *iter); + } + + return ss.str(); + } + + std::vector vserialize()const override + { + std::vectorret; + for (const auto& points : this->values) { + std::ostringstream ss; + serialize_single_value(ss, points); + ret.emplace_back(ss.str()); + } + return ret; + } + + bool deserialize(const std::string& str, bool append = false) override + { + if (!append) + this->values.clear(); + std::istringstream is(str); + std::string group_str; + while (std::getline(is, group_str, '#')) { + Vec2ds group; + std::istringstream iss(group_str); + std::string point_str; + while (std::getline(iss, point_str, ',')) { + Vec2d point(Vec2d::Zero()); + std::istringstream iss(point_str); + std::string coord_str; + if (std::getline(iss, coord_str, 'x')) { + std::istringstream(coord_str) >> point(0); + if (std::getline(iss, coord_str, 'x')) { + std::istringstream(coord_str) >> point(1); + } + } + group.push_back(point); + } + this->values.emplace_back(std::move(group)); + } + return true; + } + std::vector vserialize_single(int idx) const + { + std::vectorret; + assert(idx < this->size()); + for (auto iter = values[idx].begin(); iter != values[idx].end(); ++iter) { + std::ostringstream ss; + ss << (*iter)(0); + ss << "x"; + ss << (*iter)(1); + ret.emplace_back(ss.str()); + } + return ret; + } +protected: + void serialize_single_value(std::ostringstream& ss, const Vec2ds& v) const { + for (auto iter = v.begin(); iter != v.end(); ++iter) { + if (iter - v.begin() != 0) + ss << ","; + ss << (*iter)(0); + ss << "x"; + ss << (*iter)(1); + } + } +private: + friend class cereal::access; + template void serialize(Archive& ar) { ar(cereal::base_class(this)); } +}; + +class ConfigOptionIntsGroups : public ConfigOptionVector> +{ +public: + ConfigOptionIntsGroups() : ConfigOptionVector>() {} + explicit ConfigOptionIntsGroups(std::initializer_list> il) : ConfigOptionVector>(std::move(il)) {} + explicit ConfigOptionIntsGroups(const std::vector> &values) : ConfigOptionVector>(values) {} + + static ConfigOptionType static_type() { return coIntsGroups; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption *clone() const override { return new ConfigOptionIntsGroups(*this); } + ConfigOptionIntsGroups &operator=(const ConfigOption *opt) + { + this->set(opt); + return *this; + } + bool operator==(const ConfigOptionIntsGroups &rhs) const throw() { return this->values == rhs.values; } + bool operator==(const ConfigOption &rhs) const override + { + if (rhs.type() != this->type()) throw ConfigurationError("ConfigConfigOptionIntsGroups: Comparing incompatible types"); + assert(dynamic_cast> *>(&rhs)); + + return this->values == static_cast> *>(&rhs)->values; + } + bool operator<(const ConfigOptionIntsGroups &rhs) const throw() { + bool is_lower = true; + for (size_t i = 0; i < values.size(); ++i) { + if (this->values[i] == rhs.values[i]) + continue; + + return (this->values[i] < rhs.values[i]); + } + return is_lower; + } + bool nullable() const override { return false; } + bool is_nil(size_t) const override { return false; } + + std::string serialize() const override + { + std::ostringstream ss; + for (auto iter = this->values.begin(); iter != this->values.end(); ++iter) { + if (iter != this->values.begin()) + ss << "#"; + serialize_single_value(ss, *iter); + } + + return ss.str(); + } + + std::vector vserialize() const override + { + std::vector ret; + for (const auto &value : this->values) { + std::ostringstream ss; + serialize_single_value(ss, value); + ret.emplace_back(ss.str()); + } + return ret; + } + + bool deserialize(const std::string &str, bool append = false) override + { + if (!append) this->values.clear(); + std::istringstream is(str); + std::string group_str; + while (std::getline(is, group_str, '#')) { + std::vector group_values; + std::istringstream iss(group_str); + std::string value_str; + while (std::getline(iss, value_str, ',')) { + int value; + std::istringstream(value_str) >> value; + group_values.push_back(value); + } + this->values.emplace_back(std::move(group_values)); + } + return true; + } + std::vector vserialize_single(int idx) const + { + std::vector ret; + assert(idx < this->size()); + for (auto iter = values[idx].begin(); iter != values[idx].end(); ++iter) { + std::ostringstream ss; + ss << (*iter); + ret.emplace_back(ss.str()); + } + return ret; + } + +protected: + void serialize_single_value(std::ostringstream &ss, const std::vector &v) const + { + for (auto iter = v.begin(); iter != v.end(); ++iter) { + if (iter - v.begin() != 0) + ss << ","; + ss << (*iter); + } + } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class(this)); } +}; + + class ConfigOptionBool : public ConfigOptionSingle { public: @@ -1435,6 +1817,11 @@ public: // A scalar is nil, or all values of a vector are nil. bool is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; } bool is_nil(size_t idx) const override { return this->values[idx] == nil_value(); } + virtual void set_at_to_nil(size_t i) override + { + assert(nullable() && (i < this->values.size())); + this->values[i] = nil_value(); + } bool& get_at(size_t i) { assert(! this->values.empty()); @@ -1821,7 +2208,8 @@ public: case coEnum: { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; } // QDS case coEnums: { auto opt = new ConfigOptionEnumsGeneric(this->enum_keys_map); archive(*opt); return opt; } - default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key); + case coIntsGroups: { auto opt = new ConfigOptionIntsGroups(); archive(*opt); return opt; } + default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key); } } } @@ -1856,7 +2244,8 @@ public: case coEnum: archive(*static_cast(opt)); break; // QDS case coEnums: archive(*static_cast(opt)); break; - default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key); + case coIntsGroups: archive(*static_cast(opt)); break; + default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key); } } // Make the compiler happy, shut up the warnings. @@ -2153,6 +2542,7 @@ public: void set_deserialize_strict(std::initializer_list items) { ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; this->set_deserialize(items, ctxt); } + double get_abs_value_at(const t_config_option_key &opt_key, size_t index) const; double get_abs_value(const t_config_option_key &opt_key) const; double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const; void setenv_() const; @@ -2173,7 +2563,7 @@ public: void save(const std::string &file) const; //QDS: add json support - void save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version, const std::string is_custom = "") const; + void save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version) const; // Set all the nullable values to nils. void null_nullables(); @@ -2327,13 +2717,17 @@ public: double& opt_float(const t_config_option_key &opt_key) { return this->option(opt_key)->value; } const double& opt_float(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key))->value; } - double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } - const double& opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } + double & opt_float(const t_config_option_key &opt_key, unsigned int idx); + const double & opt_float(const t_config_option_key &opt_key, unsigned int idx) const; + double & opt_float_nullable(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } + const double & opt_float_nullable(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } int& opt_int(const t_config_option_key &opt_key) { return this->option(opt_key)->value; } int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key))->value; } int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } + int& opt_int_nullable(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx);} + const int & opt_int_nullable(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx);} // In ConfigManipulation::toggle_print_fff_options, it is called on option with type ConfigOptionEnumGeneric* and also ConfigOptionEnum*. // Thus the virtual method getInt() is used to retrieve the enum value. @@ -2341,9 +2735,12 @@ public: ENUM opt_enum(const t_config_option_key &opt_key) const { return static_cast(this->option(opt_key)->getInt()); } // QDS int opt_enum(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } + int opt_enum_nullable(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } + bool opt_bool(const t_config_option_key &opt_key) const { return this->option(opt_key)->value != 0; } - bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option(opt_key)->get_at(idx) != 0; } + bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const; + bool opt_bool_nullable(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx);} // Command line processing bool read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys = nullptr); diff --git a/src/libslic3r/ElephantFootCompensation.cpp b/src/libslic3r/ElephantFootCompensation.cpp index 0adff1b..c746a1e 100644 --- a/src/libslic3r/ElephantFootCompensation.cpp +++ b/src/libslic3r/ElephantFootCompensation.cpp @@ -564,17 +564,16 @@ ExPolygon elephant_foot_compensation(const ExPolygon &input_expoly, double min_c else { EdgeGrid::Grid grid; - ExPolygon simplified = input_expoly.simplify(SCALED_EPSILON).front(); - assert(validate_expoly_orientation(simplified)); - BoundingBox bbox = get_extents(simplified.contour); + assert(validate_expoly_orientation(input_expoly)); + BoundingBox bbox = get_extents(input_expoly.contour); bbox.offset(SCALED_EPSILON); grid.set_bbox(bbox); - grid.create(simplified, coord_t(0.7 * search_radius)); + grid.create(input_expoly, coord_t(0.7 * search_radius)); std::vector> deltas; - deltas.reserve(simplified.holes.size() + 1); - ExPolygon resampled(simplified); + deltas.reserve(input_expoly.holes.size() + 1); + ExPolygon resampled(input_expoly); double resample_interval = scale_(0.5); - for (size_t idx_contour = 0; idx_contour <= simplified.holes.size(); ++ idx_contour) { + for (size_t idx_contour = 0; idx_contour <= input_expoly.holes.size(); ++ idx_contour) { Polygon &poly = (idx_contour == 0) ? resampled.contour : resampled.holes[idx_contour - 1]; std::vector resampled_point_parameters; poly.points = resample_polygon(poly.points, resample_interval, resampled_point_parameters); @@ -628,8 +627,9 @@ ExPolygon elephant_foot_compensation(const ExPolygon &input, const Flow &exter ExPolygons elephant_foot_compensation(const ExPolygons &input, const Flow &external_perimeter_flow, const double compensation) { ExPolygons out; - out.reserve(input.size()); - for (const ExPolygon &expoly : input) + ExPolygons simplified_exps = expolygons_simplify(input, SCALED_EPSILON); + out.reserve(simplified_exps.size()); + for (const ExPolygon &expoly : simplified_exps) out.emplace_back(elephant_foot_compensation(expoly, external_perimeter_flow, compensation)); return out; } @@ -637,8 +637,9 @@ ExPolygons elephant_foot_compensation(const ExPolygons &input, const Flow &exter ExPolygons elephant_foot_compensation(const ExPolygons &input, double min_contour_width, const double compensation) { ExPolygons out; - out.reserve(input.size()); - for (const ExPolygon &expoly : input) + ExPolygons simplified_exps = expolygons_simplify(input, SCALED_EPSILON); + out.reserve(simplified_exps.size()); + for (const ExPolygon &expoly : simplified_exps) out.emplace_back(elephant_foot_compensation(expoly, min_contour_width, compensation)); return out; } diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 333db06..509adb6 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -1270,6 +1270,16 @@ ExPolygons Slic3r::union_with_delta(EmbossShape &shape, float delta, unsigned ma return shape.final_shape.expolygons; } +HealedExPolygons Emboss::union_with_delta(ExPolygons expoly, float delta, unsigned max_heal_iteration) +{ + ExPolygons expolygons; + expolygons_append(expolygons, offset_ex(expoly, delta)); + ExPolygons result = union_ex(expolygons); + result = offset_ex(result, -delta); + bool is_healed = heal_expolygons(result, max_heal_iteration); + return {result, is_healed}; +} + void Slic3r::translate(ExPolygonsWithIds &expolygons_with_ids, const Point &p) { for (ExPolygonsWithId &expolygons_with_id : expolygons_with_ids) @@ -1490,8 +1500,8 @@ void add_quad(uint32_t i1, // bottom indices uint32_t i1_ = i1 + count_point; uint32_t i2_ = i2 + count_point; - result.indices.emplace_back(i2, i2_, i1); - result.indices.emplace_back(i1_, i1, i2_); + result.add_indice(i2, i2_, i1,true); + result.add_indice(i1_, i1, i2_, true); }; indexed_triangle_set polygons2model_unique( @@ -1522,12 +1532,11 @@ indexed_triangle_set polygons2model_unique( result.indices.reserve(shape_triangles.size() * 2 + points.size() * 2); // top triangles - change to CCW for (const Vec3i32 &t : shape_triangles) - result.indices.emplace_back(t.x(), t.z(), t.y()); + result.add_indice(t.x(), t.z(), t.y(), true); // bottom triangles - use CW for (const Vec3i32 &t : shape_triangles) - result.indices.emplace_back(t.x() + count_point, - t.y() + count_point, - t.z() + count_point); + result.add_indice(t.x() + count_point, + t.y() + count_point, t.z() + count_point, true); // quads around - zig zag by triangles size_t polygon_offset = 0; @@ -1593,11 +1602,10 @@ indexed_triangle_set polygons2model_duplicit( result.indices.reserve(shape_triangles.size() * 2 + points.size() * 2); // top triangles - change to CCW for (const Vec3i32 &t : shape_triangles) - result.indices.emplace_back(t.x(), t.z(), t.y()); + result.add_indice(t.x(), t.z(), t.y(),true); // bottom triangles - use CW for (const Vec3i32 &t : shape_triangles) - result.indices.emplace_back(t.x() + count_point, t.y() + count_point, - t.z() + count_point); + result.add_indice(t.x() + count_point, t.y() + count_point, t.z() + count_point, true); // quads around - zig zag by triangles size_t polygon_offset = 0; diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index d0ea17d..fdcdf87 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -155,6 +155,8 @@ namespace Emboss HealedExPolygons text2shapes (FontFileWithCache &font, const char *text, const FontProp &font_prop, const std::function &was_canceled = []() {return false;}); ExPolygonsWithIds text2vshapes(FontFileWithCache &font, const std::wstring& text, const FontProp &font_prop, const std::function& was_canceled = []() {return false;}); + HealedExPolygons union_with_delta(ExPolygons expoly, float delta, unsigned max_heal_iteration); + const unsigned ENTER_UNICODE = static_cast('\n'); /// Sum of character '\n' unsigned get_count_lines(const std::wstring &ws); diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 5a6556b..1746dbb 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace Slic3r { @@ -146,6 +147,13 @@ Point ExPolygon::point_projection(const Point &point) const } } +void ExPolygon::symmetric_y(const coord_t &y_axis) +{ + this->contour.symmetric_y(y_axis); + for (Polygon &hole : holes) + hole.symmetric_y(y_axis); +} + bool ExPolygon::overlaps(const ExPolygon &other) const { if (this->empty() || other.empty()) @@ -370,6 +378,39 @@ void ExPolygon::medial_axis(double min_width, double max_width, Polylines* polyl polylines->emplace_back(pl.points); } +ExPolygons ExPolygon::split_expoly_with_holes(coord_t gap_width, const ExPolygons& collision) const +{ + ExPolygons sub_overhangs; + Polygon max_hole; + coordf_t max_area = 0; + bool is_collided = false; + for (const auto &hole : this->holes) { + if (!is_collided && Slic3r::overlaps({ExPolygon(hole)}, collision)) { + max_area = abs(hole.area()); + max_hole = hole; + is_collided = true; + } else if (is_collided && Slic3r::overlaps({ExPolygon(hole)}, collision) && abs(hole.area()) > max_area) { + max_area = abs(hole.area()); + max_hole = hole; + } else if (!is_collided && !Slic3r::overlaps({ExPolygon(hole)}, collision) && abs(hole.area()) > max_area) { + max_area = abs(hole.area()); + max_hole = hole; + } + } + Point cent; + if (max_hole.size() > 0) { + auto overhang_bbx = get_extents(*this); + cent = max_hole.centroid(); + append(sub_overhangs, intersection_ex(ExPolygon(BoundingBox(overhang_bbx.min, Point(cent.x() - gap_width, cent.y() - gap_width)).polygon()), *this)); + append(sub_overhangs, intersection_ex(ExPolygon(BoundingBox(Point(cent.x() + gap_width, cent.y() + gap_width), overhang_bbx.max).polygon()), *this)); + append(sub_overhangs, + intersection_ex(ExPolygon(BoundingBox(Point(overhang_bbx.min(0), cent.y() + gap_width), Point(cent.x() - gap_width, overhang_bbx.max(1))).polygon()), *this)); + append(sub_overhangs, + intersection_ex(ExPolygon(BoundingBox(Point(cent.x() + gap_width, overhang_bbx.min(1)), Point(overhang_bbx.max(0), cent.y() - gap_width)).polygon()), *this)); + } + return sub_overhangs; +} + Lines ExPolygon::lines() const { Lines lines = this->contour.lines(); @@ -380,6 +421,41 @@ Lines ExPolygon::lines() const return lines; } +bool ExPolygon::remove_colinear_points() { + bool removed = this->contour.remove_colinear_points(); + if (contour.size() < 3) { + contour.points.clear(); + holes.clear(); + return true; + } + for (Polygon &hole : this->holes) + removed |= hole.remove_colinear_points(); + return removed; +} + +double get_expolygons_area(const ExPolygons& expolys) +{ + return std::accumulate(expolys.begin(), expolys.end(), (double)(0), [](double val, const ExPolygon& expoly) { + return val + expoly.area(); + }); +} + +bool is_narrow_expolygon(const ExPolygon& expolygon, double min_width, double min_area, double remain_area_ratio_thres) +{ + double original_area = expolygon.area(); + if (original_area < min_area) + return true; + + ExPolygons offsets = offset_ex(expolygon, -min_width / 2); + if (offsets.empty()) + return true; + + if (get_expolygons_area(offsets) / (original_area + EPSILON) < remain_area_ratio_thres) + return true; + return false; +} + + // Do expolygons match? If they match, they must have the same topology, // however their contours may be rotated. bool expolygons_match(const ExPolygon &l, const ExPolygon &r) diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 12d5db1..cc02160 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -56,7 +56,7 @@ public: bool on_boundary(const Point &point, double eps) const; // Projection of a point onto the polygon. Point point_projection(const Point &point) const; - + void symmetric_y(const coord_t &y_axis); // Does this expolygon overlap another expolygon? // Either the ExPolygons intersect, or one is fully inside the other, // and it is not inside a hole of the other expolygon. @@ -76,10 +76,14 @@ public: { Polylines out; this->medial_axis(min_width, max_width, &out); return out; } Lines lines() const; + bool remove_colinear_points(); + // Number of contours (outer contour with holes). size_t num_contours() const { return this->holes.size() + 1; } Polygon& contour_or_hole(size_t idx) { return (idx == 0) ? this->contour : this->holes[idx - 1]; } const Polygon& contour_or_hole(size_t idx) const { return (idx == 0) ? this->contour : this->holes[idx - 1]; } + //split expolygon-support with holes to help remove + ExPolygons split_expoly_with_holes(coord_t gap_width, const ExPolygons& collision) const; }; inline bool operator==(const ExPolygon &lhs, const ExPolygon &rhs) { return lhs.contour == rhs.contour && lhs.holes == rhs.holes; } @@ -452,6 +456,9 @@ inline ExPolygons expolygons_simplify(const ExPolygons &expolys, double toleranc return out; } +double get_expolygons_area(const ExPolygons& expolys); +bool is_narrow_expolygon(const ExPolygon& expolygon, double min_width, double min_area = scale_(1) * scale_(1), double remain_area_ratio_thres = 0.1); + // Do expolygons match? If they match, they must have the same topology, // however their contours may be rotated. bool expolygons_match(const ExPolygon &l, const ExPolygon &r); diff --git a/src/libslic3r/Extruder.cpp b/src/libslic3r/Extruder.cpp index 72e21f1..a9725c8 100644 --- a/src/libslic3r/Extruder.cpp +++ b/src/libslic3r/Extruder.cpp @@ -3,8 +3,8 @@ namespace Slic3r { -double Extruder::m_share_E = 0.; -double Extruder::m_share_retracted = 0.; +std::vector Extruder::m_share_E = {0.,0.}; +std::vector Extruder::m_share_retracted = {0.,0.}; Extruder::Extruder(unsigned int id, GCodeConfig *config, bool share_extruder) : m_id(id), @@ -18,16 +18,25 @@ Extruder::Extruder(unsigned int id, GCodeConfig *config, bool share_extruder) : m_e_per_mm3 /= this->filament_crossection(); } +unsigned int Extruder::extruder_id() const +{ + assert(m_config); + if (m_id < m_config->filament_map.size()) { + return m_config->filament_map.get_at(m_id) - 1; + } + return 0; +} + double Extruder::extrude(double dE) { // QDS if (m_share_extruder) { if (m_config->use_relative_e_distances) - m_share_E = 0.; - m_share_E += dE; + m_share_E[extruder_id()] = 0.; + m_share_E[extruder_id()] += dE; m_absolute_E += dE; if (dE < 0.) - m_share_retracted -= dE; + m_share_retracted[extruder_id()] -= dE; } else { // in case of relative E distances we always reset to 0 before any output if (m_config->use_relative_e_distances) @@ -52,12 +61,12 @@ double Extruder::retract(double length, double restart_extra) // QDS if (m_share_extruder) { if (m_config->use_relative_e_distances) - m_share_E = 0.; - double to_retract = std::max(0., length - m_share_retracted); + m_share_E[extruder_id()] = 0.; + double to_retract = std::max(0., length - m_share_retracted[extruder_id()]); if (to_retract > 0.) { - m_share_E -= to_retract; + m_share_E[extruder_id()] -= to_retract; m_absolute_E -= to_retract; - m_share_retracted += to_retract; + m_share_retracted[extruder_id()] += to_retract; } return to_retract; } else { @@ -79,9 +88,9 @@ double Extruder::unretract() { // QDS if (m_share_extruder) { - double dE = m_share_retracted; + double dE = m_share_retracted[extruder_id()]; this->extrude(dE); - m_share_retracted = 0.; + m_share_retracted[extruder_id()] = 0.; return dE; } else { double dE = m_retracted + m_restart_extra; diff --git a/src/libslic3r/Extruder.hpp b/src/libslic3r/Extruder.hpp index d3ffdba..2a94da1 100644 --- a/src/libslic3r/Extruder.hpp +++ b/src/libslic3r/Extruder.hpp @@ -17,8 +17,8 @@ public: void reset() { // QDS if (m_share_extruder) { - m_share_E = 0.; - m_share_retracted = 0.; + m_share_E = { 0.,0.}; + m_share_retracted = { 0.,0. }; } else { m_E = 0; m_retracted = 0; @@ -30,11 +30,12 @@ public: unsigned int id() const { return m_id; } + unsigned int extruder_id() const; double extrude(double dE); double retract(double length, double restart_extra); double unretract(); - double E() const { return m_share_extruder ? m_share_E : m_E; } - void reset_E() { m_E = 0.; m_share_E = 0.; } + double E() const { return m_share_extruder ? m_share_E[extruder_id()] : m_E; } + void reset_E() { m_E = 0.; m_share_E[extruder_id()] = 0.; } double e_per_mm(double mm3_per_mm) const { return mm3_per_mm * m_e_per_mm3; } double e_per_mm3() const { return m_e_per_mm3; } // Used filament volume in mm^3. @@ -77,8 +78,8 @@ private: // QDS. // Create shared E and retraction data for single extruder multi-material machine bool m_share_extruder; - static double m_share_E; - static double m_share_retracted; + static std::vector m_share_E; + static std::vector m_share_retracted; }; // Sort Extruder objects by the extruder id by default. diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index 4dc516c..cabc1f4 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -14,6 +14,7 @@ namespace Slic3r { static const double slope_path_ratio = 0.3; static const double slope_inner_outer_wall_gap = 0.4; +static const int overhang_threshold = 1; void ExtrusionPath::intersect_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const { @@ -69,7 +70,8 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale //1.9.7.52 bool ExtrusionPath::can_merge(const ExtrusionPath& other) { - return curve_degree==other.curve_degree && + return overhang_degree == other.overhang_degree && + curve_degree==other.curve_degree && mm3_per_mm == other.mm3_per_mm && width == other.width && height == other.height && @@ -418,6 +420,17 @@ bool ExtrusionLoop::has_overhang_point(const Point &point) const return false; } +bool ExtrusionLoop::has_overhang_paths() const +{ + for (const ExtrusionPath &path : this->paths) { + if (is_bridge(path.role())) + return true; + if (path.overhang_degree >= overhang_threshold) + return true; + } + return false; +} + void ExtrusionLoop::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const { for (const ExtrusionPath &path : this->paths) @@ -607,6 +620,7 @@ std::string ExtrusionEntity::role_to_string(ExtrusionRole role) case erExternalPerimeter : return L("Outer wall"); case erOverhangPerimeter : return L("Overhang wall"); case erInternalInfill : return L("Sparse infill"); + case erFloatingVerticalShell : return L("Floating vertical shell"); case erSolidInfill : return L("Internal solid infill"); case erTopSolidInfill : return L("Top surface"); case erBottomSurface : return L("Bottom surface"); @@ -637,6 +651,8 @@ ExtrusionRole ExtrusionEntity::string_to_role(const std::string_view role) return erOverhangPerimeter; else if (role == L("Sparse infill")) return erInternalInfill; + else if (role == L("Floating vertical shell")) + return erFloatingVerticalShell; else if (role == L("Internal solid infill")) return erSolidInfill; else if (role == L("Top surface")) diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 54999d1..04677e6 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -2,6 +2,7 @@ #define slic3r_ExtrusionEntity_hpp_ #include "libslic3r.h" +#include "BoundingBox.hpp" #include "Polygon.hpp" #include "Polyline.hpp" @@ -16,6 +17,30 @@ using ExPolygons = std::vector; class ExtrusionEntityCollection; class Extruder; + +struct NodeContour +{ + Points pts; //for lines contour + std::vector widths; + bool is_loop; +}; + +struct LoopNode +{ + //store outer wall and mark if it's loop + NodeContour node_contour; + int node_id; + int loop_id = 0; + BoundingBox bbox; + int merged_id = -1; + + //upper loop info + std::vector upper_node_id; + + //lower loop info + std::vector lower_node_id; +}; + // Each ExtrusionRole value identifies a distinct set of { extruder, speed } enum ExtrusionRole : uint8_t { erNone, @@ -24,6 +49,7 @@ enum ExtrusionRole : uint8_t { erOverhangPerimeter, erInternalInfill, erSolidInfill, + erFloatingVerticalShell, erTopSolidInfill, erBottomSurface, erIroning, @@ -42,6 +68,12 @@ enum ExtrusionRole : uint8_t { erCount }; +enum CustomizeFlag : uint8_t { + cfNone, + cfCircleCompensation, // shaft hole tolerance compensation + cfFloatingVerticalShell +}; + // Special flags describing loop enum ExtrusionLoopRole { elrDefault = 1 << 0, @@ -67,6 +99,7 @@ inline bool is_infill(ExtrusionRole role) return role == erBridgeInfill || role == erInternalInfill || role == erSolidInfill + || role == erFloatingVerticalShell || role == erTopSolidInfill || role == erBottomSurface || role == erIroning; @@ -81,6 +114,7 @@ inline bool is_solid_infill(ExtrusionRole role) { return role == erBridgeInfill || role == erSolidInfill + || role == erFloatingVerticalShell || role == erTopSolidInfill || role == erBottomSurface || role == erIroning; @@ -94,6 +128,12 @@ inline bool is_bridge(ExtrusionRole role) { class ExtrusionEntity { public: + ExtrusionEntity() = default; + ExtrusionEntity(const ExtrusionEntity &rhs) { m_customize_flag = rhs.m_customize_flag; }; + ExtrusionEntity(ExtrusionEntity &&rhs) { m_customize_flag = rhs.m_customize_flag; }; + ExtrusionEntity &operator=(const ExtrusionEntity &rhs) { m_customize_flag = rhs.m_customize_flag; return *this; } + ExtrusionEntity &operator=(ExtrusionEntity &&rhs) { m_customize_flag = rhs.m_customize_flag; return *this; } + virtual ExtrusionRole role() const = 0; virtual bool is_collection() const { return false; } virtual bool is_loop() const { return false; } @@ -129,6 +169,16 @@ public: static std::string role_to_string(ExtrusionRole role); static ExtrusionRole string_to_role(const std::string_view role); + + virtual CustomizeFlag get_customize_flag() const { return m_customize_flag; }; + virtual void set_customize_flag(CustomizeFlag flag) { m_customize_flag = flag; }; + + virtual int get_cooling_node() const { return m_cooling_node; }; + virtual void set_cooling_node(int id) { m_cooling_node = id; }; + +protected: + CustomizeFlag m_customize_flag{CustomizeFlag::cfNone}; + int m_cooling_node{ -1 }; }; typedef std::vector ExtrusionEntitiesPtr; @@ -153,7 +203,8 @@ public: ExtrusionPath(double overhang_degree, int curve_degree, ExtrusionRole role, double mm3_per_mm, float width, float height) : overhang_degree(overhang_degree), curve_degree(curve_degree), mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {} ExtrusionPath(const ExtrusionPath &rhs) - : polyline(rhs.polyline) + : ExtrusionEntity(rhs) + , polyline(rhs.polyline) , overhang_degree(rhs.overhang_degree) , curve_degree(rhs.curve_degree) , mm3_per_mm(rhs.mm3_per_mm) @@ -165,7 +216,8 @@ public: , m_no_extrusion(rhs.m_no_extrusion) {} ExtrusionPath(ExtrusionPath &&rhs) - : polyline(std::move(rhs.polyline)) + : ExtrusionEntity(rhs) + , polyline(std::move(rhs.polyline)) , overhang_degree(rhs.overhang_degree) , curve_degree(rhs.curve_degree) , mm3_per_mm(rhs.mm3_per_mm) @@ -177,7 +229,8 @@ public: , m_no_extrusion(rhs.m_no_extrusion) {} ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) - : polyline(polyline) + : ExtrusionEntity(rhs) + , polyline(polyline) , overhang_degree(rhs.overhang_degree) , curve_degree(rhs.curve_degree) , mm3_per_mm(rhs.mm3_per_mm) @@ -189,7 +242,8 @@ public: , m_no_extrusion(rhs.m_no_extrusion) {} ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs) - : polyline(std::move(polyline)) + : ExtrusionEntity(rhs) + , polyline(std::move(polyline)) , overhang_degree(rhs.overhang_degree) , curve_degree(rhs.curve_degree) , mm3_per_mm(rhs.mm3_per_mm) @@ -202,6 +256,7 @@ public: {} ExtrusionPath& operator=(const ExtrusionPath& rhs) { + ExtrusionEntity::operator=(rhs); m_can_reverse = rhs.m_can_reverse; m_role = rhs.m_role; m_no_extrusion = rhs.m_no_extrusion; @@ -215,6 +270,7 @@ public: return *this; } ExtrusionPath& operator=(ExtrusionPath&& rhs) { + ExtrusionEntity::operator=(rhs); m_can_reverse = rhs.m_can_reverse; m_role = rhs.m_role; m_no_extrusion = rhs.m_no_extrusion; @@ -417,6 +473,7 @@ public: ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : m_loop_role(role) {} ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role) {} ExtrusionLoop(ExtrusionPaths &&paths, ExtrusionLoopRole role = elrDefault) : paths(std::move(paths)), m_loop_role(role) {} + ExtrusionLoop(ExtrusionPaths &&paths, ExtrusionLoopRole role, CustomizeFlag flag) : paths(std::move(paths)), m_loop_role(role) { m_customize_flag = flag; } ExtrusionLoop(const ExtrusionPath &path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role) { this->paths.push_back(path); } ExtrusionLoop(const ExtrusionPath &&path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role) @@ -448,6 +505,7 @@ public: // Test, whether the point is extruded by a bridging flow. // This used to be used to avoid placing seams on overhangs, but now the EdgeGrid is used instead. bool has_overhang_point(const Point &point) const; + bool has_overhang_paths() const; ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); } ExtrusionLoopRole loop_role() const { return m_loop_role; } void set_loop_role(ExtrusionLoopRole role) { m_loop_role = role; } diff --git a/src/libslic3r/ExtrusionEntityCollection.cpp b/src/libslic3r/ExtrusionEntityCollection.cpp index ed6af12..1224d07 100644 --- a/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/src/libslic3r/ExtrusionEntityCollection.cpp @@ -13,6 +13,8 @@ void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities, auto last = extrusion_entities.end(); extrusion_entities.erase( std::remove_if(first, last, [&role](const ExtrusionEntity* ee) { + if((ee->role() == erSupportTransition && role ==erSupportMaterial)) + return false; return ee->role() != role; }), last); } diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index 413834d..dec981e 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -31,9 +31,12 @@ public: ExtrusionEntitiesPtr entities; // we own these entities bool no_sort; + + std::pair loop_node_range; ExtrusionEntityCollection(): no_sort(false) {} - ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : no_sort(other.no_sort), is_reverse(other.is_reverse) { this->append(other.entities); } - ExtrusionEntityCollection(ExtrusionEntityCollection &&other) : entities(std::move(other.entities)), no_sort(other.no_sort), is_reverse(other.is_reverse) {} + ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : no_sort(other.no_sort), is_reverse(other.is_reverse), loop_node_range(other.loop_node_range) { this->append(other.entities); } + ExtrusionEntityCollection(ExtrusionEntityCollection &&other) + : entities(std::move(other.entities)), no_sort(other.no_sort), is_reverse(other.is_reverse), loop_node_range(other.loop_node_range) {} explicit ExtrusionEntityCollection(const ExtrusionPaths &paths); ExtrusionEntityCollection& operator=(const ExtrusionEntityCollection &other); ExtrusionEntityCollection& operator=(ExtrusionEntityCollection &&other) @@ -41,6 +44,7 @@ public: this->entities = std::move(other.entities); this->no_sort = other.no_sort; is_reverse = other.is_reverse; + loop_node_range = other.loop_node_range; return *this; } ~ExtrusionEntityCollection() { clear(); } diff --git a/src/libslic3r/FilamentGroup.cpp b/src/libslic3r/FilamentGroup.cpp new file mode 100644 index 0000000..2a9b5b6 --- /dev/null +++ b/src/libslic3r/FilamentGroup.cpp @@ -0,0 +1,899 @@ +#include "FilamentGroup.hpp" +#include "GCode/ToolOrderUtils.hpp" +#include "FlushVolPredictor.hpp" +#include +#include +#include +#include + +namespace Slic3r +{ + using namespace FilamentGroupUtils; + // clear the array and heap,save the groups in heap to the array + static void change_memoryed_heaps_to_arrays(MemoryedGroupHeap& heap,const int total_filament_num,const std::vector& used_filaments, std::vector>& arrs) + { + // switch the label idx + arrs.clear(); + while (!heap.empty()) { + auto top = heap.top(); + heap.pop(); + std::vector labels_tmp(total_filament_num, 0); + for (size_t idx = 0; idx < top.group.size(); ++idx) + labels_tmp[used_filaments[idx]] = top.group[idx]; + arrs.emplace_back(std::move(labels_tmp)); + } + } + + static std::unordered_map get_merged_filament_map(const std::unordered_map>& merged_filaments) + { + std::unordered_map filament_merge_map; + for (auto elem : merged_filaments) { + for (auto f : elem.second) { + //traverse filaments in merged group + filament_merge_map[f] = elem.first; + } + } + return filament_merge_map; + } + + + std::vector calc_filament_group_for_tpu(const std::set& tpu_filaments, const int filament_nums, const int master_extruder_id) + { + std::vector ret(filament_nums); + for (size_t fidx = 0; fidx < filament_nums; ++fidx) { + if (tpu_filaments.count(fidx)) + ret[fidx] = master_extruder_id; + else + ret[fidx] = 1 - master_extruder_id; + } + return ret; + } + + bool can_swap_groups(const int extruder_id_0, const std::set& group_0, const int extruder_id_1, const std::set& group_1, const FilamentGroupContext& ctx) + { + std::vector>extruder_unprintables(2); + { + std::vector> unprintable_filaments = ctx.model_info.unprintable_filaments; + if (unprintable_filaments.size() > 1) + remove_intersection(unprintable_filaments[0], unprintable_filaments[1]); + + std::map>unplaceable_limts; + for (auto& group_id : { extruder_id_0,extruder_id_1 }) + for (auto f : unprintable_filaments[group_id]) + unplaceable_limts[f].emplace_back(group_id); + + for (auto& elem : unplaceable_limts) + sort_remove_duplicates(elem.second); + + for (auto& elem : unplaceable_limts) { + for (auto& eid : elem.second) { + if (eid == extruder_id_0) { + extruder_unprintables[0].insert(elem.first); + } + if (eid == extruder_id_1) { + extruder_unprintables[1].insert(elem.first); + } + } + } + } + + // check printable limits + for (auto fid : group_0) { + if (extruder_unprintables[1].count(fid) > 0) + return false; + } + + for (auto fid : group_1) { + if (extruder_unprintables[0].count(fid) > 0) + return false; + } + + // check extruder capacity ,if result before exchange meets the constraints and the result after exchange does not meet the constraints, return false + if (ctx.machine_info.max_group_size[extruder_id_0] >= group_0.size() && ctx.machine_info.max_group_size[extruder_id_1] >= group_1.size() && (ctx.machine_info.max_group_size[extruder_id_0] < group_1.size() || ctx.machine_info.max_group_size[extruder_id_1] < group_0.size())) + return false; + + return true; + } + + + // only support extruder nums with 2, try to swap the master extruder id with the other extruder id + std::vector optimize_group_for_master_extruder(const std::vector& used_filaments,const FilamentGroupContext& ctx, std::vector& filament_map) + { + std::vector ret = filament_map; + std::unordered_map> groups; + for (size_t idx = 0; idx < used_filaments.size(); ++idx) { + int filament_id = used_filaments[idx]; + int group_id = ret[filament_id]; + groups[group_id].insert(filament_id); + } + + int none_master_extruder_id = 1 - ctx.machine_info.master_extruder_id; + assert(0 <= none_master_extruder_id && none_master_extruder_id <= 1); + + if (can_swap_groups(none_master_extruder_id, groups[none_master_extruder_id], ctx.machine_info.master_extruder_id, groups[ctx.machine_info.master_extruder_id], ctx) + && groups[none_master_extruder_id].size()>groups[ctx.machine_info.master_extruder_id].size()) { + for (auto fid : groups[none_master_extruder_id]) + ret[fid] = ctx.machine_info.master_extruder_id; + for (auto fid : groups[ctx.machine_info.master_extruder_id]) + ret[fid] = none_master_extruder_id; + } + return ret; + } + + /** + * @brief Select the group that best fit the filaments in AMS + * + * Calculate the total color distance between the grouping results and the AMS filaments through + * minimum cost maximum flow. Only those with a distance difference within the threshold are + * considered valid. + * + * @param map_lists Group list with similar flush count + * @param used_filaments Idx of used filaments + * @param used_filament_info Information of filaments used + * @param machine_filament_info Information of filaments loaded in printer + * @param color_threshold Threshold for considering colors to be similar + * @return The group that best fits the filament distribution in AMS + */ + std::vector select_best_group_for_ams(const std::vector>& map_lists, + const std::vector& used_filaments, + const std::vector& used_filament_info, + const std::vector>& machine_filament_info_, + const double color_threshold) + { + using namespace FlushPredict; + + const int fail_cost = 9999; + + // these code is to make we machine filament info size is 2 + std::vector> machine_filament_info = machine_filament_info_; + machine_filament_info.resize(2); + + int best_cost = std::numeric_limits::max(); + std::vectorbest_map; + + for (auto& map : map_lists) { + std::vector> group_filaments(2); + std::vector>group_colors(2); + + for (size_t i = 0; i < used_filaments.size(); ++i) { + int target_group = map[used_filaments[i]] == 0 ? 0 : 1; + group_colors[target_group].emplace_back(used_filament_info[i].color); + group_filaments[target_group].emplace_back(i); + } + + int group_cost = 0; + for (size_t i = 0; i < 2; ++i) { + if (group_colors[i].empty()) + continue; + if (machine_filament_info[i].empty()) { + group_cost += group_colors.size() * fail_cost; + continue; + } + std::vector>distance_matrix(group_colors[i].size(), std::vector(machine_filament_info[i].size())); + + // calculate color distance matrix + for (size_t src = 0; src < group_colors[i].size(); ++src) { + for (size_t dst = 0; dst < machine_filament_info[i].size(); ++dst) { + distance_matrix[src][dst] = calc_color_distance( + RGBColor(group_colors[i][src].r, group_colors[i][src].g, group_colors[i][src].b), + RGBColor(machine_filament_info[i][dst].color.r, machine_filament_info[i][dst].color.g, machine_filament_info[i][dst].color.b) + ); + } + } + + // get min cost by min cost max flow + std::vectorl_nodes(group_colors[i].size()), r_nodes(machine_filament_info[i].size()); + std::iota(l_nodes.begin(), l_nodes.end(), 0); + std::iota(r_nodes.begin(), r_nodes.end(), 0); + + std::unordered_map>unlink_limits; + for (size_t from = 0; from < group_filaments[i].size(); ++from) { + for (size_t to = 0; to < machine_filament_info[i].size(); ++to) { + if (used_filament_info[group_filaments[i][from]].type != machine_filament_info[i][to].type || + used_filament_info[group_filaments[i][from]].is_support != machine_filament_info[i][to].is_support) { + unlink_limits[from].emplace_back(to); + } + } + } + + MatchModeGroupSolver mcmf(distance_matrix, l_nodes, r_nodes, std::vector(r_nodes.size(), l_nodes.size()), unlink_limits); + auto ams_map = mcmf.solve(); + + for (size_t idx = 0; idx < ams_map.size(); ++idx) { + if (ams_map[idx] == MaxFlowGraph::INVALID_ID || distance_matrix[idx][ams_map[idx]] > color_threshold) { + group_cost += fail_cost; + } + else { + group_cost += distance_matrix[idx][ams_map[idx]]; + } + } + } + + if (best_map.empty() || group_cost < best_cost) { + best_cost = group_cost; + best_map = map; + } + } + + return best_map; + } + + + void FilamentGroupUtils::update_memoryed_groups(const MemoryedGroup& item, const double gap_threshold, MemoryedGroupHeap& groups) + { + auto emplace_if_accepatle = [gap_threshold](MemoryedGroupHeap& heap, const MemoryedGroup& elem, const MemoryedGroup& best) { + if (best.cost == 0) { + if (std::abs(elem.cost - best.cost) <= ABSOLUTE_FLUSH_GAP_TOLERANCE) + heap.push(elem); + return; + } + double gap_rate = (double)std::abs(elem.cost - best.cost) / (double)best.cost; + if (gap_rate < gap_threshold) + heap.push(elem); + }; + + if (groups.empty()) { + groups.push(item); + } + else { + auto top = groups.top(); + // we only memory items with the highest prefer level + if (top.prefer_level > item.prefer_level) + return; + else if (top.prefer_level == item.prefer_level) { + if (top.cost <= item.cost) { + emplace_if_accepatle(groups, item, top); + } + // find a group with lower cost, rebuild the heap + else { + MemoryedGroupHeap new_heap; + new_heap.push(item); + while (!groups.empty()) { + auto top = groups.top(); + groups.pop(); + emplace_if_accepatle(new_heap, top, item); + } + groups = std::move(new_heap); + } + } + // find a group with the higher prefer level, rebuild the heap + else { + groups = MemoryedGroupHeap(); + groups.push(item); + } + } + } + + std::vector collect_sorted_used_filaments(const std::vector>& layer_filaments) + { + std::setused_filaments_set; + for (const auto& lf : layer_filaments) + for (const auto& f : lf) + used_filaments_set.insert(f); + std::vectorused_filaments(used_filaments_set.begin(), used_filaments_set.end()); + std::sort(used_filaments.begin(), used_filaments.end()); + return used_filaments; + } + + FlushDistanceEvaluator::FlushDistanceEvaluator(const FlushMatrix& flush_matrix, const std::vector& used_filaments, const std::vector>& layer_filaments, double p) + { + //calc pair counts + std::vector>count_matrix(used_filaments.size(), std::vector(used_filaments.size())); + for (const auto& lf : layer_filaments) { + for (auto iter = lf.begin(); iter != lf.end(); ++iter) { + auto id_iter1 = std::find(used_filaments.begin(), used_filaments.end(), *iter); + if (id_iter1 == used_filaments.end()) + continue; + auto idx1 = id_iter1 - used_filaments.begin(); + for (auto niter = std::next(iter); niter != lf.end(); ++niter) { + auto id_iter2 = std::find(used_filaments.begin(), used_filaments.end(), *niter); + if (id_iter2 == used_filaments.end()) + continue; + auto idx2 = id_iter2 - used_filaments.begin(); + count_matrix[idx1][idx2] += 1; + count_matrix[idx2][idx1] += 1; + } + } + } + + m_distance_matrix.resize(used_filaments.size(), std::vector(used_filaments.size())); + + for (size_t i = 0; i < used_filaments.size(); ++i) { + for (size_t j = 0; j < used_filaments.size(); ++j) { + if (i == j) + m_distance_matrix[i][j] = 0; + else { + //TODO: check m_flush_matrix + float max_val = std::max(flush_matrix[used_filaments[i]][used_filaments[j]], flush_matrix[used_filaments[j]][used_filaments[i]]); + float min_val = std::min(flush_matrix[used_filaments[i]][used_filaments[j]], flush_matrix[used_filaments[j]][used_filaments[i]]); + m_distance_matrix[i][j] = (max_val * p + min_val * (1 - p)) * count_matrix[i][j]; + } + } + } + } + + double FlushDistanceEvaluator::get_distance(int idx_a, int idx_b) const + { + assert(0 <= idx_a && idx_a < m_distance_matrix.size()); + assert(0 <= idx_b && idx_b < m_distance_matrix.size()); + + return m_distance_matrix[idx_a][idx_b]; + } + + std::vector KMediods2::cluster_small_data(const std::map& unplaceable_limits, const std::vector& group_size) + { + std::vectorlabels(m_elem_count, -1); + std::vectornew_group_size = group_size; + + for (auto& [elem, center] : unplaceable_limits) { + if (labels[elem] == -1) { + int gid = 1 - center; + labels[elem] = gid; + new_group_size[gid] -= 1; + } + } + + for (auto& label : labels) { + if (label == -1) { + int gid = -1; + for (size_t idx = 0; idx < new_group_size.size(); ++idx) { + if (new_group_size[idx] > 0) { + gid = idx; + break; + } + } + if (gid != -1) { + label = gid; + new_group_size[gid] -= 1; + } + else { + label = m_default_group_id; + } + } + } + + return labels; + } + + std::vector KMediods2::assign_cluster_label(const std::vector& center, const std::map& unplaceable_limtis, const std::vector& group_size, const FGStrategy& strategy) + { + struct Comp { + bool operator()(const std::pair& a, const std::pair& b) { + return a.second > b.second; + } + }; + + std::vector>groups(2); + std::vectornew_max_group_size = group_size; + // store filament idx and distance gap between center 0 and center 1 + std::priority_queue, std::vector>, Comp>min_heap; + + for (int i = 0; i < m_elem_count; ++i) { + if (auto it = unplaceable_limtis.find(i); it != unplaceable_limtis.end()) { + int gid = it->second; + assert(gid == 0 || gid == 1); + groups[1 - gid].insert(i); // insert to group + new_max_group_size[1 - gid] = std::max(new_max_group_size[1 - gid] - 1, 0); // decrease group_size + continue; + } + int distance_to_0 = m_evaluator->get_distance(i, center[0]); + int distance_to_1 = m_evaluator->get_distance(i, center[1]); + min_heap.push({ i,distance_to_0 - distance_to_1 }); + } + + bool have_enough_size = (min_heap.size() <= (new_max_group_size[0] + new_max_group_size[1])); + + if (have_enough_size || strategy == FGStrategy::BestFit) { + while (!min_heap.empty()) { + auto top = min_heap.top(); + min_heap.pop(); + if (groups[0].size() < new_max_group_size[0] && (top.second <= 0 || groups[1].size() >= new_max_group_size[1])) + groups[0].insert(top.first); + else if (groups[1].size() < new_max_group_size[1] && (top.second > 0 || groups[0].size() >= new_max_group_size[0])) + groups[1].insert(top.first); + else { + if (top.second <= 0) + groups[0].insert(top.first); + else + groups[1].insert(top.first); + } + } + } + else { + while (!min_heap.empty()) { + auto top = min_heap.top(); + min_heap.pop(); + if (top.second <= 0) + groups[0].insert(top.first); + else + groups[1].insert(top.first); + } + } + + std::vectorlabels(m_elem_count); + for (auto& f : groups[0]) + labels[f] = 0; + for (auto& f : groups[1]) + labels[f] = 1; + + return labels; + } + + int KMediods2::calc_cost(const std::vector& labels, const std::vector& medoids) + { + int total_cost = 0; + for (int i = 0; i < m_elem_count; ++i) + total_cost += m_evaluator->get_distance(i, medoids[labels[i]]); + return total_cost; + } + + void KMediods2::do_clustering(const FGStrategy& g_strategy, int timeout_ms) + { + FlushTimeMachine T; + T.time_machine_start(); + + if (m_elem_count < m_k) { + m_cluster_labels = cluster_small_data(m_unplaceable_limits, m_max_cluster_size); + { + std::vectorcluster_center(m_k, -1); + for (size_t idx = 0; idx < m_cluster_labels.size(); ++idx) { + if (cluster_center[m_cluster_labels[idx]] == -1) + cluster_center[m_cluster_labels[idx]] = idx; + } + MemoryedGroup g(m_cluster_labels, calc_cost(m_cluster_labels, cluster_center), 1); + update_memoryed_groups(g, memory_threshold, memoryed_groups); + } + return; + } + + std::vectorbest_labels; + int best_cost = std::numeric_limits::max(); + + for (int center_0 = 0; center_0 < m_elem_count; ++center_0) { + if (auto iter = m_unplaceable_limits.find(center_0); iter != m_unplaceable_limits.end() && iter->second == 0) + continue; + for (int center_1 = 0; center_1 < m_elem_count; ++center_1) { + if (center_0 == center_1) + continue; + if (auto iter = m_unplaceable_limits.find(center_1); iter != m_unplaceable_limits.end() && iter->second == 1) + continue; + + std::vectornew_centers = { center_0,center_1 }; + std::vectornew_labels = assign_cluster_label(new_centers, m_unplaceable_limits, m_max_cluster_size, g_strategy); + + int new_cost = calc_cost(new_labels, new_centers); + if (new_cost < best_cost) { + best_cost = new_cost; + best_labels = new_labels; + } + + { + MemoryedGroup g(new_labels,new_cost,1); + update_memoryed_groups(g, memory_threshold, memoryed_groups); + } + + if (T.time_machine_end() > timeout_ms) + break; + } + if (T.time_machine_end() > timeout_ms) + break; + } + this->m_cluster_labels = best_labels; + } + + std::vector FilamentGroup::calc_min_flush_group(int* cost) + { + auto used_filaments = collect_sorted_used_filaments(ctx.model_info.layer_filaments); + int used_filament_num = used_filaments.size(); + + if (used_filament_num < 10) + return calc_min_flush_group_by_enum(used_filaments, cost); + else + return calc_min_flush_group_by_pam2(used_filaments, cost, 500); + } + + std::unordered_map> FilamentGroup::try_merge_filaments() + { + std::unordered_map>merged_filaments; + + std::unordered_map> merge_filament_map; + + auto unprintable_stat_to_str = [unprintable_filaments = this->ctx.model_info.unprintable_filaments](int idx) { + std::string str; + for (size_t eid = 0; eid < unprintable_filaments.size(); ++eid) { + if (unprintable_filaments[eid].count(idx)) { + if (eid > 0) + str += ','; + str += std::to_string(idx); + } + } + return str; + }; + + for (size_t idx = 0; idx < ctx.model_info.filament_ids.size(); ++idx) { + std::string id = ctx.model_info.filament_ids[idx]; + Color color = ctx.model_info.filament_info[idx].color; + std::string unprintable_str = unprintable_stat_to_str(idx); + + std::string key = id + "," + color.to_hex_str(true) + "," + unprintable_str; + merge_filament_map[key].push_back(idx); + } + + for (auto& elem : merge_filament_map) { + if (elem.second.size() > 1) { + merged_filaments[elem.second.front()] = elem.second; + } + } + return merged_filaments; + } + + std::vector FilamentGroup::seperate_merged_filaments(const std::vector& filament_map, const std::unordered_map>& merged_filaments) + { + std::vector ret_map = filament_map; + for (auto& elem : merged_filaments) { + int src = elem.first; + for (auto f : elem.second) { + ret_map[f] = ret_map[src]; + } + } + return ret_map; + } + + void FilamentGroup::rebuild_context(const std::unordered_map>& merged_filaments) + { + if (merged_filaments.empty()) + return; + + FilamentGroupContext new_ctx = ctx; + + std::unordered_map filament_merge_map = get_merged_filament_map(merged_filaments); + + // modify layer filaments + for (auto& layer_filament : new_ctx.model_info.layer_filaments) { + for (auto& f : layer_filament) { + if (auto iter = filament_merge_map.find((int)(f)); iter != filament_merge_map.end()) { + f = iter->second; + } + } + } + + for (auto& unprintables : new_ctx.model_info.unprintable_filaments) { + std::set new_unprintables; + for (auto f : unprintables) { + if (auto iter = filament_merge_map.find((int)(f)); iter != filament_merge_map.end()) { + new_unprintables.insert(iter->second); + } + else { + new_unprintables.insert(f); + } + } + } + + ctx = new_ctx; + return; + } + + + + std::vector FilamentGroup::calc_filament_group(int* cost) + { + try { + if (FGMode::MatchMode == ctx.group_info.mode) + return calc_filament_group_for_match(cost); + } + catch (const FilamentGroupException& e) { + } + + auto merged_map = try_merge_filaments(); + rebuild_context(merged_map); + auto filamnet_map = calc_filament_group_for_flush(cost); + return seperate_merged_filaments(filamnet_map, merged_map); + } + + std::vector FilamentGroup::calc_filament_group_for_match(int* cost) + { + using namespace FlushPredict; + + auto used_filaments = collect_sorted_used_filaments(ctx.model_info.layer_filaments); + std::vector used_filament_list; + for (auto f : used_filaments) + used_filament_list.emplace_back(ctx.model_info.filament_info[f]); + + std::vector machine_filament_list; + std::map> machine_filament_set; + for (size_t eid = 0; eid < ctx.machine_info.machine_filament_info.size();++eid) { + for (auto& filament : ctx.machine_info.machine_filament_info[eid]) { + machine_filament_set[filament].insert(machine_filament_list.size()); + machine_filament_list.emplace_back(filament); + } + } + + if (machine_filament_list.empty()) + throw FilamentGroupException(FilamentGroupException::EmptyAmsFilaments,"Empty ams filament in For-Match mode."); + + std::map unprintable_limit_indices; // key stores filament idx in used_filament, value stores unprintable extruder + extract_unprintable_limit_indices(ctx.model_info.unprintable_filaments, used_filaments, unprintable_limit_indices); + + std::vector> color_dist_matrix(used_filament_list.size(), std::vector(machine_filament_list.size())); + for (size_t i = 0; i < used_filament_list.size(); ++i) { + for (size_t j = 0; j < machine_filament_list.size(); ++j) { + color_dist_matrix[i][j] = calc_color_distance( + RGBColor(used_filament_list[i].color.r, used_filament_list[i].color.g, used_filament_list[i].color.b), + RGBColor(machine_filament_list[j].color.r, machine_filament_list[j].color.g, machine_filament_list[j].color.b) + ); + } + } + + std::vectorl_nodes(used_filaments.size()); + std::iota(l_nodes.begin(), l_nodes.end(), 0); + std::vectorr_nodes(machine_filament_list.size()); + std::iota(r_nodes.begin(), r_nodes.end(), 0); + std::vectormachine_filament_capacity(machine_filament_list.size(),l_nodes.size()); + std::vectorextruder_filament_count(2, 0); + + auto is_extruder_filament_compatible = [&unprintable_limit_indices](int filament_idx, int extruder_id) { + auto iter = unprintable_limit_indices.find(filament_idx); + if (iter != unprintable_limit_indices.end() && iter->second == extruder_id) + return false; + return true; + }; + + auto build_unlink_limits = [](const std::vector& l_nodes, const std::vector& r_nodes, const std::function& can_link) { + std::unordered_map> unlink_limits; + for (size_t i = 0; i < l_nodes.size(); ++i) { + std::vector unlink_filaments; + for (size_t j = 0; j < r_nodes.size(); ++j) { + if (!can_link(l_nodes[i], r_nodes[j])) + unlink_filaments.emplace_back(j); + } + if (!unlink_filaments.empty()) + unlink_limits.emplace(i, std::move(unlink_filaments)); + } + return unlink_limits; + }; + + auto optimize_map_to_machine_filament = [&](const std::vector& map_to_machine_filament, const std::vector& l_nodes, const std::vector& r_nodes, std::vector& filament_map, bool consider_capacity) { + std::vector ungrouped_filaments; + std::vector filaments_to_optimize; + + auto map_filament_to_machine_filament = [&](int filament_idx, int machine_filament_idx) { + auto& machine_filament = machine_filament_list[machine_filament_idx]; + machine_filament_capacity[machine_filament_idx] = std::max(0, machine_filament_capacity[machine_filament_idx] - 1); // decrease machine filament capacity + filament_map[used_filaments[filament_idx]] = machine_filament.extruder_id; // set extruder id to filament map + extruder_filament_count[machine_filament.extruder_id] += 1; // increase filament count in extruder + }; + auto unmap_filament_to_machine_filament = [&](int filament_idx, int machine_filament_idx) { + auto& machine_filament = machine_filament_list[machine_filament_idx]; + machine_filament_capacity[machine_filament_idx] += 1; // increase machine filament capacity + extruder_filament_count[machine_filament.extruder_id] -= 1; // increase filament count in extruder + }; + + for (size_t idx = 0; idx < map_to_machine_filament.size(); ++idx) { + if (map_to_machine_filament[idx] == MaxFlowGraph::INVALID_ID) { + ungrouped_filaments.emplace_back(l_nodes[idx]); + continue; + } + int used_filament_idx = l_nodes[idx]; + int machine_filament_idx = r_nodes[map_to_machine_filament[idx]]; + auto& machine_filament = machine_filament_list[machine_filament_idx]; + if (machine_filament_set[machine_filament].size() > 1 && unprintable_limit_indices.count(used_filament_idx) == 0) + filaments_to_optimize.emplace_back(idx); + + map_filament_to_machine_filament(used_filament_idx, machine_filament_idx); + } + // try to optimize the result + for (auto idx : filaments_to_optimize) { + int filament_idx = l_nodes[idx]; + int old_machine_filament_idx = r_nodes[map_to_machine_filament[idx]]; + auto& old_machine_filament = machine_filament_list[old_machine_filament_idx]; + + int curr_gap = std::abs(extruder_filament_count[0] - extruder_filament_count[1]); + unmap_filament_to_machine_filament(filament_idx, old_machine_filament_idx); + + auto optional_filaments = machine_filament_set[old_machine_filament]; + auto iter = optional_filaments.begin(); + for (; iter != optional_filaments.end(); ++iter) { + int new_extruder_id = machine_filament_list[*iter].extruder_id; + int new_gap = std::abs(extruder_filament_count[new_extruder_id] + 1 - extruder_filament_count[1 - new_extruder_id]); + if (new_gap < curr_gap && (!consider_capacity || machine_filament_capacity[*iter] > 0)) { + map_filament_to_machine_filament(filament_idx, *iter); + break; + } + } + + if (iter == optional_filaments.end()) + map_filament_to_machine_filament(filament_idx, old_machine_filament_idx); + } + return ungrouped_filaments; + }; + + std::vector group(ctx.group_info.total_filament_num, ctx.machine_info.master_extruder_id); + std::vector ungrouped_filaments; + + auto unlink_limits_full = build_unlink_limits(l_nodes, r_nodes, [&used_filament_list, &machine_filament_list, is_extruder_filament_compatible](int used_filament_idx, int machine_filament_idx) { + return used_filament_list[used_filament_idx].type == machine_filament_list[machine_filament_idx].type && + used_filament_list[used_filament_idx].is_support == machine_filament_list[machine_filament_idx].is_support && + is_extruder_filament_compatible(used_filament_idx, machine_filament_list[machine_filament_idx].extruder_id); + }); + + { + MatchModeGroupSolver s(color_dist_matrix, l_nodes, r_nodes, machine_filament_capacity, unlink_limits_full); + ungrouped_filaments = optimize_map_to_machine_filament(s.solve(), l_nodes, r_nodes,group,false); + if (ungrouped_filaments.empty()) + return group; + } + + // additionally remove type limits + { + l_nodes = ungrouped_filaments; + auto unlink_limits = build_unlink_limits(l_nodes, r_nodes, [&machine_filament_list, is_extruder_filament_compatible](int used_filament_idx, int machine_filament_idx) { + return is_extruder_filament_compatible(used_filament_idx, machine_filament_list[machine_filament_idx].extruder_id); + }); + + MatchModeGroupSolver s(color_dist_matrix, l_nodes, r_nodes, machine_filament_capacity, unlink_limits); + ungrouped_filaments = optimize_map_to_machine_filament(s.solve(), l_nodes, r_nodes, group,false); + if (ungrouped_filaments.empty()) + return group; + } + + // remove all limits + { + l_nodes = ungrouped_filaments; + MatchModeGroupSolver s(color_dist_matrix, l_nodes, r_nodes, machine_filament_capacity, {}); + auto ret = optimize_map_to_machine_filament(s.solve(), l_nodes, r_nodes, group,false); + for (size_t idx = 0; idx < ret.size(); ++idx) { + if (ret[idx] == MaxFlowGraph::INVALID_ID) + assert(false); + else + group[used_filaments[l_nodes[idx]]] = machine_filament_list[r_nodes[ret[idx]]].extruder_id; + } + } + + return group; + } + + std::vector FilamentGroup::calc_filament_group_for_flush(int* cost) + { + auto used_filaments = collect_sorted_used_filaments(ctx.model_info.layer_filaments); + + std::vector ret = calc_min_flush_group(cost); + std::vector> memoryed_maps = this->m_memoryed_groups; + memoryed_maps.insert(memoryed_maps.begin(), ret); + + std::vector optimized_ret = optimize_group_for_master_extruder(used_filaments, ctx, ret); + if (optimized_ret != ret) + memoryed_maps.insert(memoryed_maps.begin(), optimized_ret); + + std::vector used_filament_info; + for (auto f : used_filaments) { + used_filament_info.emplace_back(ctx.model_info.filament_info[f]); + } + + ret = select_best_group_for_ams(memoryed_maps, used_filaments, used_filament_info, ctx.machine_info.machine_filament_info); + return ret; + } + + + // sorted used_filaments + std::vector FilamentGroup::calc_min_flush_group_by_enum(const std::vector& used_filaments, int* cost) + { + static constexpr int UNPLACEABLE_LIMIT_REWARD = 100; // reward value if the group result follows the unprintable limit + static constexpr int MAX_SIZE_LIMIT_REWARD = 10; // reward value if the group result follows the max size per extruder + static constexpr int BEST_FIT_LIMIT_REWARD = 1; // reward value if the group result try to fill the max size per extruder + + MemoryedGroupHeap memoryed_groups; + + auto bit_count_one = [](uint64_t n) + { + int count = 0; + while (n != 0) + { + n &= n - 1; + count++; + } + return count; + }; + + std::mapunplaceable_limit_indices; + extract_unprintable_limit_indices(ctx.model_info.unprintable_filaments, used_filaments, unplaceable_limit_indices); + + int used_filament_num = used_filaments.size(); + uint64_t max_group_num = (static_cast(1) << used_filament_num); + + int best_cost = std::numeric_limits::max(); + std::vectorbest_label; + int best_prefer_level = 0; + + for (uint64_t i = 0; i < max_group_num; ++i) { + std::vector>groups(2); + for (int j = 0; j < used_filament_num; ++j) { + if (i & (static_cast(1) << j)) + groups[1].insert(j); + else + groups[0].insert(j); + } + + int prefer_level = 0; + + if (check_printable(groups, unplaceable_limit_indices)) + prefer_level += UNPLACEABLE_LIMIT_REWARD; + if (groups[0].size() <= ctx.machine_info.max_group_size[0] && groups[1].size() <= ctx.machine_info.max_group_size[1]) + prefer_level += MAX_SIZE_LIMIT_REWARD; + if (FGStrategy::BestFit == ctx.group_info.strategy && groups[0].size() >= ctx.machine_info.max_group_size[0] && groups[1].size() >= ctx.machine_info.max_group_size[1]) + prefer_level += BEST_FIT_LIMIT_REWARD; + + std::vectorfilament_maps(used_filament_num); + for (int i = 0; i < used_filament_num; ++i) { + if (groups[0].find(i) != groups[0].end()) + filament_maps[i] = 0; + if (groups[1].find(i) != groups[1].end()) + filament_maps[i] = 1; + } + + int total_cost = reorder_filaments_for_minimum_flush_volume( + used_filaments, + filament_maps, + ctx.model_info.layer_filaments, + ctx.model_info.flush_matrix, + get_custom_seq, + nullptr + ); + + if (prefer_level > best_prefer_level || (prefer_level == best_prefer_level && total_cost < best_cost)) { + best_prefer_level = prefer_level; + best_cost = total_cost; + best_label = filament_maps; + } + + { + MemoryedGroup mg(filament_maps, total_cost, prefer_level); + update_memoryed_groups(mg, ctx.group_info.max_gap_threshold, memoryed_groups); + } + } + + if (cost) + *cost = best_cost; + + std::vector filament_labels(ctx.group_info.total_filament_num, 0); + for (size_t i = 0; i < best_label.size(); ++i) + filament_labels[used_filaments[i]] = best_label[i]; + + + change_memoryed_heaps_to_arrays(memoryed_groups, ctx.group_info.total_filament_num, used_filaments, m_memoryed_groups); + + return filament_labels; + } + + // sorted used_filaments + std::vector FilamentGroup::calc_min_flush_group_by_pam2(const std::vector& used_filaments, int* cost, int timeout_ms) + { + std::vectorfilament_labels_ret(ctx.group_info.total_filament_num, ctx.machine_info.master_extruder_id); + + std::mapunplaceable_limits; + extract_unprintable_limit_indices(ctx.model_info.unprintable_filaments, used_filaments, unplaceable_limits); + + auto distance_evaluator = std::make_shared(ctx.model_info.flush_matrix[0], used_filaments, ctx.model_info.layer_filaments); + KMediods2 PAM((int)used_filaments.size(), distance_evaluator, ctx.machine_info.master_extruder_id); + PAM.set_max_cluster_size(ctx.machine_info.max_group_size); + PAM.set_unplaceable_limits(unplaceable_limits); + PAM.set_memory_threshold(ctx.group_info.max_gap_threshold); + PAM.do_clustering(ctx.group_info.strategy, timeout_ms); + + std::vectorfilament_labels = PAM.get_cluster_labels(); + + { + auto memoryed_groups = PAM.get_memoryed_groups(); + change_memoryed_heaps_to_arrays(memoryed_groups, ctx.group_info.total_filament_num, used_filaments, m_memoryed_groups); + } + + if (cost) + *cost = reorder_filaments_for_minimum_flush_volume(used_filaments, filament_labels, ctx.model_info.layer_filaments, ctx.model_info.flush_matrix, std::nullopt, nullptr); + + for (int i = 0; i < filament_labels.size(); ++i) + filament_labels_ret[used_filaments[i]] = filament_labels[i]; + return filament_labels_ret; + } + +} + + diff --git a/src/libslic3r/FilamentGroup.hpp b/src/libslic3r/FilamentGroup.hpp new file mode 100644 index 0000000..ce1aedf --- /dev/null +++ b/src/libslic3r/FilamentGroup.hpp @@ -0,0 +1,200 @@ +#ifndef FILAMENT_GROUP_HPP +#define FILAMENT_GROUP_HPP + +#include +#include +#include +#include +#include +#include +#include +#include "GCode/ToolOrderUtils.hpp" +#include "FilamentGroupUtils.hpp" + +const static int DEFAULT_CLUSTER_SIZE = 16; + +const static int ABSOLUTE_FLUSH_GAP_TOLERANCE = 5; + +namespace Slic3r +{ + std::vectorcollect_sorted_used_filaments(const std::vector>& layer_filaments); + + enum FGStrategy { + BestCost, + BestFit + }; + + enum FGMode { + FlushMode, + MatchMode + }; + + namespace FilamentGroupUtils + { + struct FlushTimeMachine + { + private: + std::chrono::high_resolution_clock::time_point start; + + public: + void time_machine_start() + { + start = std::chrono::high_resolution_clock::now(); + } + + int time_machine_end() + { + auto end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end - start); + return duration.count(); + } + }; + + struct MemoryedGroup { + MemoryedGroup() = default; + MemoryedGroup(const std::vector& group_, const int cost_, const int prefer_level_) :group(group_), cost(cost_), prefer_level(prefer_level_) {} + bool operator>(const MemoryedGroup& other) const { + return prefer_level < other.prefer_level || (prefer_level == other.prefer_level && cost > other.cost); + } + + int cost{ 0 }; + int prefer_level{ 0 }; + std::vectorgroup; + }; + + using MemoryedGroupHeap = std::priority_queue, std::greater>; + + void update_memoryed_groups(const MemoryedGroup& item,const double gap_threshold, MemoryedGroupHeap& groups); + } + + struct FilamentGroupContext + { + struct ModelInfo { + std::vector flush_matrix; + std::vector> layer_filaments; + std::vector filament_info; + std::vector filament_ids; + std::vector> unprintable_filaments; + } model_info; + + struct GroupInfo { + int total_filament_num; + double max_gap_threshold; + FGMode mode; + FGStrategy strategy; + bool ignore_ext_filament; //wai gua filament + } group_info; + + struct MachineInfo { + std::vector max_group_size; + std::vector> machine_filament_info; + std::vector, int>> extruder_group_size; + int master_extruder_id; + } machine_info; + }; + + std::vector select_best_group_for_ams(const std::vector>& map_lists, + const std::vector& used_filaments, + const std::vector& used_filament_info, + const std::vector>& machine_filament_info, + const double color_delta_threshold = 20); + + std::vector optimize_group_for_master_extruder(const std::vector& used_filaments, const FilamentGroupContext& ctx, const std::vector& filament_map); + + bool can_swap_groups(const int extruder_id_0, const std::set& group_0, const int extruder_id_1, const std::set& group_1, const FilamentGroupContext& ctx); + + std::vector calc_filament_group_for_tpu(const std::set& tpu_filaments, const int filament_nums, const int master_extruder_id); + + class FlushDistanceEvaluator + { + public: + FlushDistanceEvaluator(const FlushMatrix& flush_matrix,const std::vector&used_filaments,const std::vector>& layer_filaments, double p = 0.65); + ~FlushDistanceEvaluator() = default; + double get_distance(int idx_a, int idx_b) const; + private: + std::vector>m_distance_matrix; + + }; + + class FilamentGroup + { + using MemoryedGroup = FilamentGroupUtils::MemoryedGroup; + using MemoryedGroupHeap = FilamentGroupUtils::MemoryedGroupHeap; + public: + explicit FilamentGroup(const FilamentGroupContext& ctx_) :ctx(ctx_) {} + public: + std::vector calc_filament_group(int * cost = nullptr); + std::vector> get_memoryed_groups()const { return m_memoryed_groups; } + + public: + std::vector calc_filament_group_for_match(int* cost = nullptr); + std::vector calc_filament_group_for_flush(int* cost = nullptr); + + private: + std::vector calc_min_flush_group(int* cost = nullptr); + std::vector calc_min_flush_group_by_enum(const std::vector& used_filaments, int* cost = nullptr); + std::vector calc_min_flush_group_by_pam2(const std::vector& used_filaments, int* cost = nullptr, int timeout_ms = 300); + + std::unordered_map> try_merge_filaments(); + void rebuild_context(const std::unordered_map>& merged_filaments); + std::vector seperate_merged_filaments(const std::vector& filament_map, const std::unordered_map>& merged_filaments ); + + private: + FilamentGroupContext ctx; + std::vector> m_memoryed_groups; + + public: + std::optional&)>> get_custom_seq; + }; + + + class KMediods2 + { + using MemoryedGroupHeap = FilamentGroupUtils::MemoryedGroupHeap; + using MemoryedGroup = FilamentGroupUtils::MemoryedGroup; + + enum INIT_TYPE + { + Random = 0, + Farthest + }; + public: + KMediods2(const int elem_count, const std::shared_ptr& evaluator, int default_group_id = 0) : + m_evaluator{ evaluator }, + m_elem_count{ elem_count }, + m_default_group_id{ default_group_id } + { + m_max_cluster_size = std::vector(m_k, DEFAULT_CLUSTER_SIZE); + } + + // set max group size + void set_max_cluster_size(const std::vector& group_size) { m_max_cluster_size = group_size; } + + // key stores elem idx, value stores the cluster id that elem cnanot be placed + void set_unplaceable_limits(const std::map& placeable_limits) { m_unplaceable_limits = placeable_limits; } + + void do_clustering(const FGStrategy& g_strategy,int timeout_ms = 100); + + void set_memory_threshold(double threshold) { memory_threshold = threshold; } + MemoryedGroupHeap get_memoryed_groups()const { return memoryed_groups; } + + std::vectorget_cluster_labels()const { return m_cluster_labels; } + + private: + std::vectorcluster_small_data(const std::map& unplaceable_limits, const std::vector& group_size); + std::vectorassign_cluster_label(const std::vector& center, const std::map& unplaceable_limits, const std::vector& group_size, const FGStrategy& strategy); + int calc_cost(const std::vector& labels, const std::vector& medoids); + protected: + FilamentGroupUtils::MemoryedGroupHeap memoryed_groups; + std::shared_ptr m_evaluator; + std::mapm_unplaceable_limits; + std::vectorm_cluster_labels; + std::vectorm_max_cluster_size; + + const int m_k = 2; + int m_elem_count; + int m_default_group_id{ 0 }; + double memory_threshold{ 0 }; + }; +} +#endif // !FILAMENT_GROUP_HPP diff --git a/src/libslic3r/FilamentGroupUtils.cpp b/src/libslic3r/FilamentGroupUtils.cpp new file mode 100644 index 0000000..1b16a13 --- /dev/null +++ b/src/libslic3r/FilamentGroupUtils.cpp @@ -0,0 +1,278 @@ +#include "FilamentGroupUtils.hpp" +#include +#include + +namespace Slic3r +{ +namespace FilamentGroupUtils +{ + Color::Color(const std::string& hexstr) { + if (hexstr.empty() || (hexstr.length() != 9 && hexstr.length() != 7) || hexstr[0] != '#') + { + assert(false); + r = 0, g = 0, b = 0, a = 255; + return; + } + + auto hexToByte = [](const std::string& hex)->unsigned char + { + unsigned int byte; + std::istringstream(hex) >> std::hex >> byte; + return static_cast(byte); + }; + r = hexToByte(hexstr.substr(1, 2)); + g = hexToByte(hexstr.substr(3, 2)); + b = hexToByte(hexstr.substr(5, 2)); + if (hexstr.size() == 9) + a = hexToByte(hexstr.substr(7, 2)); + } + + bool Color::operator<(const Color& other) const + { + if (r != other.r) return r < other.r; + if (g != other.g) return g < other.g; + if (b != other.b) return b < other.b; + return a < other.a; + } + + bool Color::operator==(const Color& other) const + { + return r == other.r && g == other.g && b == other.b && a == other.a; + + } + + bool Color::operator!=(const Color& other) const + { + return r != other.r || g != other.g || b != other.b || a != other.a; + } + + std::string Color::to_hex_str(bool include_alpha) const { + std::ostringstream oss; + oss << "#" << std::hex << std::setfill('0') + << std::setw(2) << static_cast(r) + << std::setw(2) << static_cast(g) + << std::setw(2) << static_cast(b); + + if (include_alpha) { + oss << std::setw(2) << static_cast(a); + } + return oss.str(); + } + + + bool MachineFilamentInfo::operator<(const MachineFilamentInfo& other) const + { + if (color != other.color) return color < other.color; + if (type != other.type) return type < other.type; + return is_support calc_max_group_size(const std::vector>& ams_counts, bool ignore_ext_filament) { + // add default value to 2 + std::vectorgroup_size(2, 0); + for (size_t idx = 0; idx < ams_counts.size(); ++idx) { + const auto& ams_count = ams_counts[idx]; + for (auto iter = ams_count.begin(); iter != ams_count.end(); ++iter) { + group_size[idx] += iter->first * iter->second; + } + } + + for (size_t idx = 0; idx < group_size.size(); ++idx) { + if (!ignore_ext_filament && group_size[idx] == 0) + group_size[idx] = 1; + } + return group_size; + } + + + static std::vector> build_full_machine_filaments(const std::vector>& filament_configs) + { + auto extract_filament_type = [](const std::string& s)->std::string { + std::regex r1(R"(^Sup.(\w+)$)"); + std::regex r2(R"(^(\w+)-S$)"); + + std::smatch m; + if (std::regex_match(s, m, r1)) + return m[1].str(); + if (std::regex_match(s, m, r2)) + return m[1].str(); + return s; + }; + + // change filament type to type format in preset + // defualt size set to 2 + std::vector> machine_filaments(2); + for (size_t idx = 0; idx < filament_configs.size(); ++idx) { + auto& arr = filament_configs[idx]; + for (auto& item : arr) { + MachineFilamentInfo temp; + std::string type; + std::string color; + std::string tray_name; + bool is_support_filament = false; + + if (auto color_ptr = item.option("filament_colour"); color_ptr) + color = color_ptr->get_at(0); + if (auto type_ptr = item.option("filament_type"); type_ptr) { + type = type_ptr->get_at(0); + type = extract_filament_type(type); + } + if (auto tray_ptr = item.option("tray_name"); tray_ptr) + tray_name = tray_ptr->get_at(0); + if (auto support_ptr = item.option("filament_is_support"); support_ptr) + is_support_filament = support_ptr->get_at(0); + + if (color.empty() || type.empty() || tray_name.empty()) + continue; + + temp.color = Color(color); + temp.type =type; + temp.extruder_id = idx; + temp.is_extended = tray_name == "Ext"; // hard-coded ext flag + temp.is_support = is_support_filament; + machine_filaments[idx].emplace_back(std::move(temp)); + } + } + return machine_filaments; + } + + std::vector> build_machine_filaments(const std::vector>& filament_configs, const std::vector>& ams_counts, bool ignore_ext_filament) + { + std::vector> ret(2); + std::vector ams_size(2, 0); + std::vector> full_machine_filaments = build_full_machine_filaments(filament_configs); + assert(full_machine_filaments.size() == 2); + for (size_t idx = 0; idx < std::min(ams_counts.size(),ams_size.size()); ++idx) { + const auto& ams_count = ams_counts[idx]; + for (auto iter = ams_count.begin(); iter != ams_count.end(); ++iter) { + ams_size[idx] += iter->first * iter->second; + } + } + + assert(full_machine_filaments.size() == ams_size.size()); + for (size_t idx = 0; idx < std::min(ams_size.size(), full_machine_filaments.size()); ++idx) { + std::vector tmp; + for (size_t j = 0; j < full_machine_filaments[idx].size(); ++j) { + auto& machine_filament = full_machine_filaments[idx][j]; + if (!machine_filament.is_extended) + tmp.emplace_back(machine_filament); + } + + // if do not have valid ams filament, try to use ext filament + if (tmp.empty() && !ignore_ext_filament) { + for (size_t j = 0; j < full_machine_filaments[idx].size(); ++j) { + auto& machine_filament = full_machine_filaments[idx][j]; + if (machine_filament.is_extended) + tmp.emplace_back(machine_filament); + } + } + + ret[idx] = std::move(tmp); + } + return ret; + } + + bool collect_unprintable_limits(const std::vector>& physical_unprintables, const std::vector>& geometric_unprintables, std::vector>& unprintable_limits) + { + unprintable_limits.clear(); + unprintable_limits.resize(2); + // resize unprintables to 2 + auto resized_physical_unprintables = physical_unprintables; + resized_physical_unprintables.resize(2); + auto resized_geometric_unprintables = geometric_unprintables; + resized_geometric_unprintables.resize(2); + + bool conflict = false; + conflict |= remove_intersection(resized_physical_unprintables[0], resized_physical_unprintables[1]); + conflict |= remove_intersection(resized_geometric_unprintables[0], resized_geometric_unprintables[1]); + + std::mapfilament_unprintable_exts; + for (auto& ext_unprintables : { resized_physical_unprintables,resized_geometric_unprintables }) { + for (int eid = 0; eid < ext_unprintables.size(); ++eid) { + for (int fid : ext_unprintables[eid]) { + if (auto iter = filament_unprintable_exts.find(fid); iter != filament_unprintable_exts.end() && iter->second != eid) + conflict = true; + else + filament_unprintable_exts[fid] = eid; + } + } + } + for (auto& elem : filament_unprintable_exts) + unprintable_limits[elem.second].insert(elem.first); + + return !conflict; + } + + bool remove_intersection(std::set& a, std::set& b) { + std::vectorintersection; + std::set_intersection(a.begin(), a.end(), b.begin(), b.end(), std::back_inserter(intersection)); + bool have_intersection = !intersection.empty(); + for (auto& item : intersection) { + a.erase(item); + b.erase(item); + } + return have_intersection; + } + + void extract_indices(const std::vector& used_filaments, const std::vector>& unprintable_elems, std::vector>& unprintable_idxs) + { + std::vector>(unprintable_elems.size()).swap(unprintable_idxs); + for (size_t gid = 0; gid < unprintable_elems.size(); ++gid) { + for (auto& f : unprintable_elems[gid]) { + auto iter = std::find(used_filaments.begin(), used_filaments.end(), (unsigned)f); + if (iter != used_filaments.end()) + unprintable_idxs[gid].insert(iter - used_filaments.begin()); + } + } + } + + void extract_unprintable_limit_indices(const std::vector>& unprintable_elems, const std::vector& used_filaments, std::map& unplaceable_limits) + { + unplaceable_limits.clear(); + // map the unprintable filaments to idx of used filaments , if not used ,just ignore + std::vector> unprintable_idxs; + extract_indices(used_filaments, unprintable_elems, unprintable_idxs); + if (unprintable_idxs.size() > 1) + remove_intersection(unprintable_idxs[0], unprintable_idxs[1]); + + for (size_t idx = 0; idx < unprintable_idxs.size(); ++idx) { + for (auto f : unprintable_idxs[idx]) + if (unplaceable_limits.count(f) == 0) + unplaceable_limits[f] = idx; + } + } + + + void extract_unprintable_limit_indices(const std::vector>& unprintable_elems, const std::vector& used_filaments, std::unordered_map>& unplaceable_limits) + { + unplaceable_limits.clear(); + std::vector>unprintable_idxs; + // map the unprintable filaments to idx of used filaments , if not used ,just ignore + extract_indices(used_filaments, unprintable_elems, unprintable_idxs); + // remove elems that cannot be printed in both extruder + if (unprintable_idxs.size() > 1) + remove_intersection(unprintable_idxs[0], unprintable_idxs[1]); + + for (size_t group_id = 0; group_id < unprintable_idxs.size(); ++group_id) + for (auto f : unprintable_idxs[group_id]) + unplaceable_limits[f].emplace_back(group_id); + + for (auto& elem : unplaceable_limits) + sort_remove_duplicates(elem.second); + } + + bool check_printable(const std::vector>& groups, const std::map& unprintable) + { + for (size_t i = 0; i < groups.size(); ++i) { + auto& group = groups[i]; + for (auto& filament : group) { + if (auto iter = unprintable.find(filament); iter != unprintable.end() && i == iter->second) + return false; + } + } + return true; + } +} +} \ No newline at end of file diff --git a/src/libslic3r/FilamentGroupUtils.hpp b/src/libslic3r/FilamentGroupUtils.hpp new file mode 100644 index 0000000..71839c2 --- /dev/null +++ b/src/libslic3r/FilamentGroupUtils.hpp @@ -0,0 +1,89 @@ +#ifndef FILAMENT_GROUP_UTILS_HPP +#define FILAMENT_GROUP_UTILS_HPP + +#include +#include +#include +#include + +#include "PrintConfig.hpp" + +namespace Slic3r +{ + namespace FilamentGroupUtils + { + struct Color + { + unsigned char r = 0; + unsigned char g = 0; + unsigned char b = 0; + unsigned char a = 255; + Color(unsigned char r_ = 0, unsigned char g_ = 0, unsigned char b_ = 0, unsigned a_ = 255) :r(r_), g(g_), b(b_), a(a_) {} + Color(const std::string& hexstr); + bool operator<(const Color& other) const; + bool operator==(const Color& other) const; + bool operator!=(const Color& other) const; + std::string to_hex_str(bool include_alpha = false) const; + }; + + + struct FilamentInfo { + Color color; + std::string type; + bool is_support; + }; + + struct MachineFilamentInfo: public FilamentInfo { + int extruder_id; + bool is_extended; + bool operator<(const MachineFilamentInfo& other) const; + }; + + + class FilamentGroupException: public std::exception { + public: + enum ErrorCode { + EmptyAmsFilaments, + ConflictLimits, + Unknown + }; + + private: + ErrorCode code_; + std::string message_; + + public: + FilamentGroupException(ErrorCode code, const std::string& message) + : code_(code), message_(message) {} + + ErrorCode code() const noexcept { + return code_; + } + + const char* what() const noexcept override { + return message_.c_str(); + } + }; + + std::vector calc_max_group_size(const std::vector>& ams_counts,bool ignore_ext_filament); + + std::vector> build_machine_filaments(const std::vector>& filament_configs, const std::vector>& ams_counts, bool ignore_ext_filament); + + bool collect_unprintable_limits(const std::vector>& physical_unprintables, const std::vector>& geometric_unprintables, std::vector>& unprintable_limits); + + bool remove_intersection(std::set& a, std::set& b); + + void extract_indices(const std::vector& used_filaments, const std::vector>& unprintable_elems, std::vector>& unprintable_idxs); + + void extract_unprintable_limit_indices(const std::vector>& unprintable_elems, const std::vector& used_filaments, std::map& unplaceable_limits); + + void extract_unprintable_limit_indices(const std::vector>& unprintable_elems, const std::vector& used_filaments, std::unordered_map>& unplaceable_limits); + + bool check_printable(const std::vector>& groups, const std::map& unprintable); + } + + +} + + +#endif \ No newline at end of file diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index f801ff1..8566608 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -14,6 +14,7 @@ #include "FillLightning.hpp" #include "FillConcentricInternal.hpp" #include "FillConcentric.hpp" +#include "FillFloatingConcentric.hpp" #define NARROW_INFILL_AREA_THRESHOLD 3 @@ -62,13 +63,17 @@ struct SurfaceFillParams float sparse_infill_speed = 0; float top_surface_speed = 0; float solid_infill_speed = 0; + float infill_shift_step = 0;// param for cross zag + float infill_rotate_step = 0; // param for zig zag to get cross texture + + bool symmetric_infill_y_axis = false; bool operator<(const SurfaceFillParams &rhs) const { #define RETURN_COMPARE_NON_EQUAL(KEY) if (this->KEY < rhs.KEY) return true; if (this->KEY > rhs.KEY) return false; #define RETURN_COMPARE_NON_EQUAL_TYPED(TYPE, KEY) if (TYPE(this->KEY) < TYPE(rhs.KEY)) return true; if (TYPE(this->KEY) > TYPE(rhs.KEY)) return false; // Sort first by decreasing bridging angle, so that the bridges are processed with priority when trimming one layer by the other. - if (this->bridge_angle > rhs.bridge_angle) return true; + if (this->bridge_angle > rhs.bridge_angle) return true; if (this->bridge_angle < rhs.bridge_angle) return false; RETURN_COMPARE_NON_EQUAL(extruder); @@ -88,6 +93,9 @@ struct SurfaceFillParams RETURN_COMPARE_NON_EQUAL(sparse_infill_speed); RETURN_COMPARE_NON_EQUAL(top_surface_speed); RETURN_COMPARE_NON_EQUAL(solid_infill_speed); + RETURN_COMPARE_NON_EQUAL(infill_shift_step); + RETURN_COMPARE_NON_EQUAL(infill_rotate_step); + RETURN_COMPARE_NON_EQUAL(symmetric_infill_y_axis); return false; } @@ -108,7 +116,10 @@ struct SurfaceFillParams this->extrusion_role == rhs.extrusion_role && this->sparse_infill_speed == rhs.sparse_infill_speed && this->top_surface_speed == rhs.top_surface_speed && - this->solid_infill_speed == rhs.solid_infill_speed; + this->solid_infill_speed == rhs.solid_infill_speed && + this->infill_shift_step == rhs.infill_shift_step && + this->infill_rotate_step == rhs.infill_rotate_step && + this->symmetric_infill_y_axis == rhs.symmetric_infill_y_axis; } }; @@ -124,16 +135,6 @@ struct SurfaceFill { ExPolygons no_overlap_expolygons; }; -// QDS: used to judge whether the internal solid infill area is narrow -static bool is_narrow_infill_area(const ExPolygon& expolygon) -{ - ExPolygons offsets = offset_ex(expolygon, -scale_(NARROW_INFILL_AREA_THRESHOLD)); - if (offsets.empty()) - return true; - - return false; -} - std::vector group_fills(const Layer &layer) { std::vector surface_fills; @@ -157,11 +158,20 @@ std::vector group_fills(const Layer &layer) params.extruder = layerm.region().extruder(extrusion_role); params.pattern = region_config.sparse_infill_pattern.value; params.density = float(region_config.sparse_infill_density); + if (params.pattern == ipCrossZag){ + params.infill_shift_step = scale_(region_config.infill_shift_step); + params.symmetric_infill_y_axis = region_config.symmetric_infill_y_axis; + }else if (params.pattern == ipZigZag){ + params.infill_rotate_step = region_config.infill_rotate_step * M_PI / 360; + params.symmetric_infill_y_axis = region_config.symmetric_infill_y_axis; + } if (surface.is_solid()) { params.density = 100.f; //FIXME for non-thick bridges, shall we allow a bottom surface pattern? - if (surface.is_solid_infill()) + if (surface.is_floating_vertical_shell()) + params.pattern = InfillPattern::ipFloatingConcentric; + else if (surface.is_solid_infill()) params.pattern = region_config.internal_solid_infill_pattern.value; else if (surface.is_external() && !is_bridge) params.pattern = surface.is_top() ? region_config.top_surface_pattern.value : region_config.bottom_surface_pattern.value; @@ -175,11 +185,11 @@ std::vector group_fills(const Layer &layer) is_bridge ? erBridgeInfill : (surface.is_solid() ? - (surface.is_top() ? erTopSolidInfill : (surface.is_bottom()? erBottomSurface : erSolidInfill)) : - erInternalInfill); + (surface.is_top() ? erTopSolidInfill : (surface.is_bottom()? erBottomSurface : surface.is_floating_vertical_shell()?erFloatingVerticalShell:erSolidInfill)) : + erInternalInfill); params.bridge_angle = float(surface.bridge_angle); params.angle = float(Geometry::deg2rad(region_config.infill_direction.value)); - + // Calculate the actual flow we'll be using for this infill. params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern); params.flow = params.bridge ? @@ -189,11 +199,13 @@ std::vector group_fills(const Layer &layer) //QDS: record speed params if (!params.bridge) { if (params.extrusion_role == erInternalInfill) - params.sparse_infill_speed = region_config.sparse_infill_speed; + params.sparse_infill_speed = region_config.sparse_infill_speed.get_at(layer.get_extruder_id(params.extruder)); else if (params.extrusion_role == erTopSolidInfill) - params.top_surface_speed = region_config.top_surface_speed; + params.top_surface_speed = region_config.top_surface_speed.get_at(layer.get_extruder_id(params.extruder)); else if (params.extrusion_role == erSolidInfill) - params.solid_infill_speed = region_config.internal_solid_infill_speed; + params.solid_infill_speed = region_config.internal_solid_infill_speed.get_at(layer.get_extruder_id(params.extruder)); + else if (params.extrusion_role == erFloatingVerticalShell) + params.solid_infill_speed = region_config.bridge_speed.get_at(layer.get_extruder_id(params.extruder)); } // Calculate flow spacing for infill pattern generation. if (surface.is_solid() || is_bridge) { @@ -333,7 +345,7 @@ std::vector group_fills(const Layer &layer) params.angle = float(Geometry::deg2rad(layerm.region().config().infill_direction.value)); // calculate the actual flow we'll be using for this infill params.flow = layerm.flow(frSolidInfill); - params.spacing = params.flow.spacing(); + params.spacing = params.flow.spacing(); surface_fills.emplace_back(params); surface_fills.back().surface.surface_type = stInternalSolid; surface_fills.back().surface.thickness = layer.height; @@ -347,44 +359,93 @@ std::vector group_fills(const Layer &layer) // QDS: detect narrow internal solid infill area and use ipConcentricInternal pattern instead if (layer.object()->config().detect_narrow_internal_solid_infill) { + const coordf_t narrow_threshold = scale_(NARROW_INFILL_AREA_THRESHOLD) * 2; + ExPolygons lower_internal_areas; + BoundingBox lower_internal_bbox; + if (layer.lower_layer) { + for (auto layerm : layer.lower_layer->regions()) { + auto internal_surfaces = layerm->fill_surfaces.filter_by_types({ stInternal,stInternalVoid }); + for (auto surface : internal_surfaces) + lower_internal_areas.push_back(surface->expolygon); + } + lower_internal_bbox = get_extents(lower_internal_areas); + } size_t surface_fills_size = surface_fills.size(); for (size_t i = 0; i < surface_fills_size; i++) { if (surface_fills[i].surface.surface_type != stInternalSolid) continue; size_t expolygons_size = surface_fills[i].expolygons.size(); - std::vector narrow_expolygons_index; - narrow_expolygons_index.reserve(expolygons_size); + std::vector narrow_expoly_idx; + std::vector narrow_floating_expoly_idx; + std::vector use_floating_filler; // QDS: get the index list of narrow expolygon - for (size_t j = 0; j < expolygons_size; j++) - if (is_narrow_infill_area(surface_fills[i].expolygons[j])) - narrow_expolygons_index.push_back(j); + for (size_t j = 0; j < expolygons_size; j++) { + auto bbox = get_extents(surface_fills[i].expolygons[j]); + auto clipped_internals = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_internal_areas, bbox.inflated(scale_(2))); // expand a little + auto clipped_internal_bbox = get_extents(clipped_internals); + if (is_narrow_expolygon(surface_fills[i].expolygons[j], narrow_threshold)) { + if (!clipped_internals.empty() && bbox.overlap(clipped_internal_bbox) && !intersection_ex(offset_ex(surface_fills[i].expolygons[j],SCALED_EPSILON), clipped_internals).empty()) { + narrow_floating_expoly_idx.emplace_back(j); + } + else { + narrow_expoly_idx.emplace_back(j); + } + } + } - if (narrow_expolygons_index.size() == 0) { + if (narrow_expoly_idx.empty() && narrow_floating_expoly_idx.empty()) { // QDS: has no narrow expolygon continue; } - else if (narrow_expolygons_index.size() == expolygons_size) { - // QDS: all expolygons are narrow, directly change the fill pattern + else if (narrow_floating_expoly_idx.size() == expolygons_size) { + surface_fills[i].params.pattern = ipFloatingConcentric; + surface_fills[i].params.extrusion_role = erFloatingVerticalShell; + surface_fills[i].surface.surface_type = stFloatingVerticalShell; + } + else if (narrow_expoly_idx.size() == expolygons_size) { surface_fills[i].params.pattern = ipConcentricInternal; } else { // QDS: some expolygons are narrow, spilit surface_fills[i] and rearrange the expolygons - params = surface_fills[i].params; - params.pattern = ipConcentricInternal; - surface_fills.emplace_back(params); - surface_fills.back().region_id = surface_fills[i].region_id; - surface_fills.back().surface.surface_type = stInternalSolid; - surface_fills.back().surface.thickness = surface_fills[i].surface.thickness; - surface_fills.back().region_id_group = surface_fills[i].region_id_group; - surface_fills.back().no_overlap_expolygons = surface_fills[i].no_overlap_expolygons; - for (size_t j = 0; j < narrow_expolygons_index.size(); j++) { - // QDS: move the narrow expolygons to new surface_fills.back(); - surface_fills.back().expolygons.emplace_back(std::move(surface_fills[i].expolygons[narrow_expolygons_index[j]])); + if (!narrow_expoly_idx.empty()) { + params = surface_fills[i].params; + params.pattern = ipConcentricInternal; + surface_fills.emplace_back(params); + surface_fills.back().region_id = surface_fills[i].region_id; + surface_fills.back().surface.surface_type = stInternalSolid; + surface_fills.back().surface.thickness = surface_fills[i].surface.thickness; + surface_fills.back().region_id_group = surface_fills[i].region_id_group; + surface_fills.back().no_overlap_expolygons = surface_fills[i].no_overlap_expolygons; + for (size_t j = 0; j < narrow_expoly_idx.size(); j++) { + // QDS: move the narrow expolygons to new surface_fills.back(); + surface_fills.back().expolygons.emplace_back(std::move(surface_fills[i].expolygons[narrow_expoly_idx[j]])); + } } - for (int j = narrow_expolygons_index.size() - 1; j >= 0; j--) { + + if (!narrow_floating_expoly_idx.empty()) { + params = surface_fills[i].params; + params.pattern = ipFloatingConcentric; + params.extrusion_role = erFloatingVerticalShell; + surface_fills.emplace_back(params); + surface_fills.back().region_id = surface_fills[i].region_id; + surface_fills.back().surface.surface_type = stFloatingVerticalShell; + surface_fills.back().surface.thickness = surface_fills[i].surface.thickness; + surface_fills.back().region_id_group = surface_fills[i].region_id_group; + surface_fills.back().no_overlap_expolygons = surface_fills[i].no_overlap_expolygons; + for (size_t j = 0; j < narrow_floating_expoly_idx.size(); j++) { + // QDS: move the narrow expolygons to new surface_fills.back(); + surface_fills.back().expolygons.emplace_back(std::move(surface_fills[i].expolygons[narrow_floating_expoly_idx[j]])); + } + } + + std::vector to_be_delete = narrow_floating_expoly_idx; + to_be_delete.insert(to_be_delete.end(), narrow_expoly_idx.begin(), narrow_expoly_idx.end()); + std::sort(to_be_delete.begin(), to_be_delete.end()); + + for (int j = to_be_delete.size() - 1; j >= 0; j--) { // QDS: delete the narrow expolygons from old surface_fills - surface_fills[i].expolygons.erase(surface_fills[i].expolygons.begin() + narrow_expolygons_index[j]); + surface_fills[i].expolygons.erase(surface_fills[i].expolygons.begin() + to_be_delete[j]); } } } @@ -410,7 +471,7 @@ void export_group_fills_to_svg(const char *path, const std::vector for (const auto &expoly : fill.expolygons) svg.draw(expoly, surface_type_to_color_name(fill.surface.surface_type), transparency); export_surface_type_legend_to_svg(svg, legend_pos); - svg.Close(); + svg.Close(); } #endif @@ -420,7 +481,6 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: for (LayerRegion *layerm : m_regions) layerm->fills.clear(); - #ifdef SLIC3R_DEBUG_SLICE_PROCESSING // this->export_region_fill_surfaces_to_svg_debug("10_fill-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ @@ -444,7 +504,12 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: f->z = this->print_z; f->angle = surface_fill.params.angle; f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; - + if (surface_fill.params.pattern == ipZigZag) { + if (f->layer_id % 2 == 0) + f->angle -= surface_fill.params.infill_rotate_step * (f->layer_id / 2); + else + f->angle += surface_fill.params.infill_rotate_step * (f->layer_id / 2); + } if (surface_fill.params.pattern == ipConcentricInternal) { FillConcentricInternal *fill_concentric = dynamic_cast(f.get()); assert(fill_concentric != nullptr); @@ -455,9 +520,49 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: assert(fill_concentric != nullptr); fill_concentric->print_config = &this->object()->print()->config(); fill_concentric->print_object_config = &this->object()->config(); - } else if (surface_fill.params.pattern == ipLightning) + } else if (surface_fill.params.pattern == ipLightning){ dynamic_cast(f.get())->generator = lightning_generator; + } + else if (surface_fill.params.pattern == ipFloatingConcentric) { + FillFloatingConcentric* fill_contour = dynamic_cast(f.get()); + assert(fill_contour != nullptr); + ExPolygons lower_unsuporrt_expolys; + Polygons lower_sparse_polys; + if (lower_layer) { + for (LayerRegion* layerm : lower_layer->regions()) { + auto surfaces = layerm->fill_surfaces.filter_by_types({ stInternal,stInternalVoid }); + ExPolygons sexpolys; + for (auto surface : surfaces) { + sexpolys.push_back(surface->expolygon); + } + sexpolys = union_ex(sexpolys); + lower_unsuporrt_expolys = union_ex(lower_unsuporrt_expolys, sexpolys); + } + lower_unsuporrt_expolys = shrink_ex(lower_unsuporrt_expolys, SCALED_EPSILON); + std::vector lower_fills = group_fills(*lower_layer); + bool detect_lower_sparse_lines = true; + for (auto& fill : lower_fills) { + if (fill.params.pattern == ipAdaptiveCubic || fill.params.pattern == ipLightning || fill.params.pattern == ipSupportCubic) { + detect_lower_sparse_lines = false; + break; + } + } + if (detect_lower_sparse_lines) { + float internal_infill_width = 0; + for (auto layerm : lower_layer->regions()) + internal_infill_width += layerm->flow(frInfill).scaled_width(); + internal_infill_width /= lower_layer->m_regions.size(); + Polylines lower_sparse_lines = lower_layer->generate_sparse_infill_polylines_for_anchoring(nullptr, nullptr, nullptr); + lower_sparse_polys = offset(lower_sparse_lines, internal_infill_width / 2); + lower_sparse_polys = union_(lower_sparse_polys); + } + } + fill_contour->lower_sparse_polys = lower_sparse_polys; + fill_contour->lower_layer_unsupport_areas = lower_unsuporrt_expolys; + fill_contour->print_config = &this->object()->print()->config(); + fill_contour->print_object_config = &this->object()->config(); + } // calculate flow spacing for infill pattern generation bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.bridge; double link_max_length = 0.; @@ -483,7 +588,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: params.anchor_length = surface_fill.params.anchor_length; params.anchor_length_max = surface_fill.params.anchor_length_max; params.resolution = resolution; - params.use_arachne = surface_fill.params.pattern == ipConcentric; + params.use_arachne = surface_fill.params.pattern == ipConcentric || surface_fill.params.pattern == ipFloatingConcentric; params.layer_height = m_regions[surface_fill.region_id]->layer()->height; // QDS @@ -491,11 +596,30 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: params.extrusion_role = surface_fill.params.extrusion_role; params.using_internal_flow = using_internal_flow; params.no_extrusion_overlap = surface_fill.params.overlap; - if (surface_fill.params.pattern == ipGrid) + if (surface_fill.params.pattern == ipCrossZag) { + if (f->layer_id % 2 == 0) { + params.horiz_move -= surface_fill.params.infill_shift_step * (f->layer_id / 2); + } else { + params.horiz_move += surface_fill.params.infill_shift_step * (f->layer_id / 2); + } + + params.symmetric_infill_y_axis = surface_fill.params.symmetric_infill_y_axis; + + } else if( surface_fill.params.pattern == ipZigZag ) { + params.symmetric_infill_y_axis = surface_fill.params.symmetric_infill_y_axis; + } + + if (surface_fill.params.pattern == ipGrid || surface_fill.params.pattern == ipFloatingConcentric) params.can_reverse = false; LayerRegion* layerm = this->m_regions[surface_fill.region_id]; for (ExPolygon& expoly : surface_fill.expolygons) { - f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes); + + f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes); + if (params.symmetric_infill_y_axis) { + params.symmetric_y_axis = f->extended_object_bounding_box().center().x(); + expoly.symmetric_y(params.symmetric_y_axis); + } + // Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon. f->spacing = surface_fill.params.spacing; surface_fill.surface.expolygon = std::move(expoly); @@ -543,23 +667,25 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc //case ipEnsuring: continue; break; case ipLightning: case ipAdaptiveCubic: - case ipSupportCubic: - case ipRectilinear: - case ipMonotonic: - case ipAlignedRectilinear: - case ipGrid: - case ipTriangles: - case ipStars: - case ipCubic: - case ipLine: - case ipConcentric: - case ipHoneycomb: - case ip3DHoneycomb: - case ipGyroid: - case ipHilbertCurve: - case ipArchimedeanChords: - case ipOctagramSpiral: break; - } + case ipSupportCubic: + case ipRectilinear: + case ipMonotonic: + case ipAlignedRectilinear: + case ipGrid: + case ipTriangles: + case ipStars: + case ipCubic: + case ipLine: + case ipConcentric: + case ipHoneycomb: + case ip3DHoneycomb: + case ipGyroid: + case ipHilbertCurve: + case ipArchimedeanChords: + case ipOctagramSpiral: + case ipZigZag: + case ipCrossZag: break; + } // Create the filler object. std::unique_ptr f = std::unique_ptr(Fill::new_from_type(surface_fill.params.pattern)); diff --git a/src/libslic3r/Fill/Fill3DHoneycomb.hpp b/src/libslic3r/Fill/Fill3DHoneycomb.hpp index f7a27cf..ccb37a3 100644 --- a/src/libslic3r/Fill/Fill3DHoneycomb.hpp +++ b/src/libslic3r/Fill/Fill3DHoneycomb.hpp @@ -15,6 +15,10 @@ public: Fill* clone() const override { return new Fill3DHoneycomb(*this); }; ~Fill3DHoneycomb() override {} + // require bridge flow since most of this pattern hangs in air + bool use_bridge_flow() const override { return true; } + bool is_self_crossing() override { return false; } + protected: void _fill_surface_single( const FillParams ¶ms, diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 0578cc3..0132ffa 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -71,6 +71,7 @@ protected: // may not be optimal as the internal infill lines may get extruded before the long infill // lines to which the short infill lines are supposed to anchor. bool no_sort() const override { return false; } + bool is_self_crossing() override { return true; } }; } // namespace FillAdaptive diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 6065b40..bf692be 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -24,6 +24,7 @@ // QDS: new infill pattern header #include "FillConcentricInternal.hpp" #include "FillCrossHatch.hpp" +#include "FillFloatingConcentric.hpp" // #define INFILL_DEBUG_OUTPUT @@ -56,6 +57,9 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipConcentricInternal: return new FillConcentricInternal(); // QDS: for bottom and top surface only case ipMonotonicLine: return new FillMonotonicLineWGapFill(); + case ipZigZag: return new FillZigZag(); + case ipCrossZag: return new FillCrossZag(); + case ipFloatingConcentric: return new FillFloatingConcentric(); default: throw Slic3r::InvalidArgument("unknown type"); } } @@ -176,8 +180,8 @@ coord_t Fill::_adjust_solid_spacing(const coord_t width, const coord_t distance) assert(distance > 0); // floor(width / distance) const auto number_of_intervals = coord_t((width - EPSILON) / distance); - coord_t distance_new = (number_of_intervals == 0) ? - distance : + coord_t distance_new = (number_of_intervals == 0) ? + distance : coord_t((width - EPSILON) / number_of_intervals); const coordf_t factor = coordf_t(distance_new) / coordf_t(distance); assert(factor > 1. - 1e-5); @@ -203,8 +207,8 @@ std::pair Fill::_infill_direction(const Surface *surface) const // Bounding box is the bounding box of a perl object Slic3r::Print::Object (c++ object Slic3r::PrintObject) // The bounding box is only undefined in unit tests. - Point out_shift = empty(this->bounding_box) ? - surface->expolygon.contour.bounding_box().center() : + Point out_shift = empty(this->bounding_box) ? + surface->expolygon.contour.bounding_box().center() : this->bounding_box.center(); #if 0 @@ -1480,6 +1484,18 @@ BoundaryInfillGraph create_boundary_infill_graph(const Polylines &infill_ordered return out; } +// The extended bounding box of the whole object that covers any rotation of every layer. +BoundingBox Fill::extended_object_bounding_box() const +{ + BoundingBox out = bounding_box; + out.merge(Point(out.min.y(), out.min.x())); + out.merge(Point(out.max.y(), out.max.x())); + + // The bounding box is scaled by sqrt(2.) to ensure that the bounding box + // covers any possible rotations. + return out.scaled(sqrt(2.)); +} + void Fill::connect_infill(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms) { assert(! infill_ordered.empty()); diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 211c5fb..7582ad2 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -76,6 +76,10 @@ struct FillParams float no_extrusion_overlap{ 0.0 }; bool dont_sort{ false }; // do not sort the lines, just simply connect them bool can_reverse{true}; + + float horiz_move{0.0}; //move infill to get cross zag pattern + bool symmetric_infill_y_axis{false}; + coord_t symmetric_y_axis{0}; }; static_assert(IsTriviallyCopyable::value, "FillParams class is not POD (and it should be - see constructor)."); @@ -116,13 +120,18 @@ public: static bool use_bridge_flow(const InfillPattern type); void set_bounding_box(const Slic3r::BoundingBox &bbox) { bounding_box = bbox; } - + BoundingBox extended_object_bounding_box() const; // Use bridge flow for the fill? virtual bool use_bridge_flow() const { return false; } // Do not sort the fill lines to optimize the print head path? virtual bool no_sort() const { return false; } + virtual bool is_self_crossing() = 0; + + // Return true if infill has a consistent pattern between layers. + virtual bool has_consistent_pattern() const { return false; } + // Perform the fill. virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); virtual ThickPolylines fill_surface_arachne(const Surface* surface, const FillParams& params); @@ -148,11 +157,11 @@ protected: // The expolygon may be modified by the method to avoid a copy. virtual void _fill_surface_single( - const FillParams & /* params */, + const FillParams & /* params */, unsigned int /* thickness_layers */, - const std::pair & /* direction */, + const std::pair & /* direction */, ExPolygon /* expolygon */, - Polylines & /* polylines_out */) {}; + Polylines & /* polylines_out */) {} // Used for concentric infill to generate ThickPolylines using Arachne. virtual void _fill_surface_single(const FillParams& params, diff --git a/src/libslic3r/Fill/FillConcentric.hpp b/src/libslic3r/Fill/FillConcentric.hpp index 2e63dcd..cdbd152 100644 --- a/src/libslic3r/Fill/FillConcentric.hpp +++ b/src/libslic3r/Fill/FillConcentric.hpp @@ -9,6 +9,7 @@ class FillConcentric : public Fill { public: ~FillConcentric() override = default; + bool is_self_crossing() override { return false; } protected: Fill* clone() const override { return new FillConcentric(*this); }; diff --git a/src/libslic3r/Fill/FillConcentricInternal.hpp b/src/libslic3r/Fill/FillConcentricInternal.hpp index 9c8442e..5d38b66 100644 --- a/src/libslic3r/Fill/FillConcentricInternal.hpp +++ b/src/libslic3r/Fill/FillConcentricInternal.hpp @@ -10,6 +10,7 @@ class FillConcentricInternal : public Fill public: ~FillConcentricInternal() override = default; void fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out) override; + bool is_self_crossing() override { return false; } protected: Fill* clone() const override { return new FillConcentricInternal(*this); }; diff --git a/src/libslic3r/Fill/FillCrossHatch.hpp b/src/libslic3r/Fill/FillCrossHatch.hpp index 859d5bd..34c4611 100644 --- a/src/libslic3r/Fill/FillCrossHatch.hpp +++ b/src/libslic3r/Fill/FillCrossHatch.hpp @@ -14,6 +14,7 @@ class FillCrossHatch : public Fill public: Fill *clone() const override { return new FillCrossHatch(*this); }; ~FillCrossHatch() override {} + bool is_self_crossing() override { return false; } protected: void _fill_surface_single( diff --git a/src/libslic3r/Fill/FillFloatingConcentric.cpp b/src/libslic3r/Fill/FillFloatingConcentric.cpp new file mode 100644 index 0000000..738af19 --- /dev/null +++ b/src/libslic3r/Fill/FillFloatingConcentric.cpp @@ -0,0 +1,888 @@ +#include "../ClipperUtils.hpp" +#include "../Clipper2Utils.hpp" +#include "../ClipperZUtils.hpp" +#include "../ExPolygon.hpp" +#include "../Surface.hpp" +#include "../VariableWidth.hpp" +#include "../format.hpp" +#include "FillFloatingConcentric.hpp" +#include + +namespace Slic3r { + +using ZPath = ClipperLib_Z::Path; +using ZPaths = ClipperLib_Z::Paths; + +FloatingPolyline FloatingPolyline::rebase_at(size_t idx) +{ + if (!this->is_closed()) + return {}; + + FloatingPolyline ret = *this; + static_cast(ret) = Polyline::rebase_at(idx); + size_t n = this->points.size(); + ret.is_floating.resize(n); + for (size_t j = 0; j < n - 1; ++j) { + ret.is_floating[j] = this->is_floating[(idx + j) % (n-1)]; + } + ret.is_floating.emplace_back(ret.is_floating.front()); + return ret; +} + +FloatingThickPolyline FloatingThickPolyline::rebase_at(size_t idx) +{ + if (!this->is_closed()) + return {}; + FloatingThickPolyline ret = *this; + static_cast(ret) = ThickPolyline::rebase_at(idx); + size_t n = this->points.size(); + ret.is_floating.resize(n); + for (size_t j = 0; j < n - 1; ++j) { + ret.is_floating[j] = this->is_floating[(idx + j) % (n - 1)]; + } + ret.is_floating.emplace_back(ret.is_floating.front()); + return ret; +} + +FloatingThicklines FloatingThickPolyline::floating_thicklines() const +{ + FloatingThicklines lines; + if (this->points.size() >= 2) { + lines.reserve(this->points.size() - 1); + for (size_t i = 0; i + 1 < this->points.size(); ++i) + lines.emplace_back(this->points[i], this->points[i + 1], this->width[2 * i], this->width[2 * i + 1], this->is_floating[i], this->is_floating[i + 1]); + } + return lines; +} + + +//QDS: new function to filter width to avoid too fragmented segments +static ExtrusionPaths floating_thick_polyline_to_extrusion_paths(const FloatingThickPolyline& floating_polyline, ExtrusionRole role, const Flow& flow, const float tolerance) +{ + ExtrusionPaths paths; + ExtrusionPath path(role); + FloatingThicklines lines = floating_polyline.floating_thicklines(); + + size_t start_index = 0; + double max_width, min_width; + + auto set_flow_for_path = [&flow](ExtrusionPath& path, double width) { + Flow new_flow = flow.with_width(unscale(width) + flow.height() * float(1. - 0.25 * PI)); + path.mm3_per_mm = new_flow.mm3_per_mm(); + path.width = new_flow.width(); + path.height = new_flow.height(); + }; + + auto append_path_and_reset = [set_flow_for_path, role, &paths](double& length, double& sum, ExtrusionPath& path){ + length = sum = 0; + paths.emplace_back(std::move(path)); + path = ExtrusionPath(role); + }; + + for (int i = 0; i < (int)lines.size(); ++i) { + const FloatingThickline& line = lines[i]; + + if (i == 0) { + max_width = min_width = line.a_width; + } + + const coordf_t line_len = line.length(); + if (line_len < SCALED_EPSILON) continue; + + double thickness_delta = std::max(fabs(max_width - line.b_width), fabs(min_width - line.b_width)); + //QDS: has large difference in width + if (thickness_delta > tolerance) { + //QDS: 1 generate path from start_index to i(not included) + if (start_index != i) { + path = ExtrusionPath(role); + double length = 0, sum = 0; + bool is_floating = false; + for (int idx = start_index; idx < i; idx++) { + bool curr_floating = lines[idx].is_a_floating && lines[idx].is_b_floating; + if (curr_floating != is_floating && length != 0) { + path.polyline.append(lines[idx].a); + if (is_floating) + path.set_customize_flag(CustomizeFlag::cfFloatingVerticalShell); + set_flow_for_path(path, sum / length); + append_path_and_reset(length, sum, path); + } + is_floating = curr_floating; + + double line_length = lines[idx].length(); + length += line_length; + sum += line_length * (lines[idx].a_width + lines[idx].b_width) * 0.5; + path.polyline.append(lines[idx].a); + } + path.polyline.append(lines[i].a); + if (length > SCALED_EPSILON) { + if (lines[i].is_a_floating && lines[i].is_b_floating) + path.set_customize_flag(CustomizeFlag::cfFloatingVerticalShell); + set_flow_for_path(path, sum / length); + paths.emplace_back(std::move(path)); + } + } + + start_index = i; + max_width = line.a_width; + min_width = line.a_width; + + //QDS: 2 handle the i-th segment + thickness_delta = fabs(line.a_width - line.b_width); + if (thickness_delta > tolerance) { + const unsigned int segments = (unsigned int)ceil(thickness_delta / tolerance); + const coordf_t seg_len = line_len / segments; + Points pp; + std::vector width; + { + pp.push_back(line.a); + width.push_back(line.a_width); + for (size_t j = 1; j < segments; ++j) { + pp.push_back((line.a.cast() + (line.b - line.a).cast().normalized() * (j * seg_len)).cast()); + + coordf_t w = line.a_width + (j * seg_len) * (line.b_width - line.a_width) / line_len; + width.push_back(w); + width.push_back(w); + } + pp.push_back(line.b); + width.push_back(line.b_width); + + assert(pp.size() == segments + 1u); + assert(width.size() == segments * 2); + } + + // delete this line and insert new ones + lines.erase(lines.begin() + i); + for (size_t j = 0; j < segments; ++j) { + FloatingThickline new_line(pp[j], pp[j + 1],width[2 * j],width[2 * j+1], line.is_a_floating,line.is_b_floating); + lines.insert(lines.begin() + i + j, new_line); + } + --i; + continue; + } + } + //QDS: just update the max and min width and continue + else { + max_width = std::max(max_width, std::max(line.a_width, line.b_width)); + min_width = std::min(min_width, std::min(line.a_width, line.b_width)); + } + } + //QDS: handle the remaining segment + size_t final_size = lines.size(); + if (start_index < final_size) { + path = ExtrusionPath(role); + double length = 0, sum = 0; + bool is_floating = false; + for (int idx = start_index; idx < final_size; idx++) { + bool curr_floating = lines[idx].is_a_floating && lines[idx].is_b_floating; + if (curr_floating!= is_floating && length != 0) { + path.polyline.append(lines[idx].a); + if(is_floating) + path.set_customize_flag(CustomizeFlag::cfFloatingVerticalShell); + set_flow_for_path(path, sum / length); + append_path_and_reset(length, sum, path); + } + is_floating = curr_floating; + double line_length = lines[idx].length(); + length += line_length; + sum += line_length * (lines[idx].a_width + lines[idx].b_width) * 0.5; + path.polyline.append(lines[idx].a); + + } + path.polyline.append(lines[final_size - 1].b); + if (length > SCALED_EPSILON) { + if (lines[final_size - 1].is_a_floating && lines[final_size - 1].is_b_floating) + path.set_customize_flag(CustomizeFlag::cfFloatingVerticalShell); + set_flow_for_path(path, sum / length); + paths.emplace_back(std::move(path)); + } + } + + return paths; +} + +double interpolate_width(const ZPath& path, + const ThickPolyline& line, + const int subject_idx_range, + const int default_width, + size_t idx) +{ + int prev_idx = idx; + while (prev_idx >= 0 && (path[prev_idx].z() < 0 || path[prev_idx].z() >= subject_idx_range)) + --prev_idx; + + int next_idx = idx; + while (next_idx < path.size() && (path[next_idx].z() < 0 || path[next_idx].z() >= subject_idx_range)) + ++next_idx; + + double width_prev; + double width_next; + if (prev_idx < 0) { + width_prev = default_width; + } + else { + size_t prev_z_idx = path[prev_idx].z(); + width_prev = line.get_width_at(prev_z_idx); + } + + if (next_idx >= path.size()) { + width_next = default_width; + } + else { + size_t next_z_idx = path[next_idx].z(); + width_next = line.get_width_at(next_z_idx); + } + Point prev(path[prev_idx].x(), path[prev_idx].y()); + Point next(path[next_idx].x(), path[next_idx].y()); + Point curr(path[idx].x(), path[idx].y()); + double d_total = (next - prev).cast().norm(); + double d_curr = (curr - prev).cast().norm(); + double t = (d_total > 0) ? (d_curr / d_total) : 0.0; + return (1 - t) * width_prev + t * width_next; +} + +FloatingThickPolyline merge_lines(ZPaths lines, const std::vector& mark_flags, const ThickPolyline& line, const int subject_idx_range ,const int default_width) +{ + using PathFlag = std::vector; + using PathFlags = std::vector; + + std::vectorused(lines.size(), false); + ZPaths merged_paths; + PathFlags merged_marks; + + auto update_path_flag = [](PathFlag& mark_flags, const ZPath& path, bool mark) { + for (auto p : path) + mark_flags.emplace_back(mark); + }; + + std::unordered_map> start_z_map; + std::unordered_map> end_z_map; + + for (size_t idx = 0; idx < lines.size(); ++idx) { + if (lines[idx].empty()) { + used[idx] = true; + continue; + } + start_z_map[lines[idx].front().z()].insert(idx); + end_z_map[lines[idx].back().z()].insert(idx); + } + + auto remove_from_map = [&start_z_map, &end_z_map, &lines](size_t idx) { + if (lines[idx].empty()) + return; + int64_t start_z = lines[idx].front().z(); + int64_t end_z = lines[idx].back().z(); + start_z_map[start_z].erase(idx); + if (start_z_map[start_z].empty()) + start_z_map.erase(start_z); + end_z_map[end_z].erase(idx); + if (end_z_map[end_z].empty()) + end_z_map.erase(end_z); + }; + + for (size_t idx = 0; idx < lines.size(); ++idx) { + if (used[idx]) + continue; + ZPath curr_path = lines[idx]; + PathFlag curr_mark; + update_path_flag(curr_mark, curr_path, mark_flags[idx]); + used[idx] = true; + remove_from_map(idx); + + bool merged; + do { + merged = false; + int64_t curr_end = curr_path.back().z(); + int64_t curr_start = curr_path.front().z(); + + // search after + { + if (auto start_iter = start_z_map.find(curr_end);start_iter != start_z_map.end()) { + size_t j = *start_iter->second.begin(); + remove_from_map(j); + curr_path.insert(curr_path.end(), lines[j].begin(), lines[j].end()); + update_path_flag(curr_mark, lines[j], mark_flags[j]); + used[j] = true; + merged = true; + } + else if (auto end_iter = end_z_map.find(curr_end); end_iter != end_z_map.end()) { + size_t j = *end_iter->second.begin(); + remove_from_map(j); + std::reverse(lines[j].begin(), lines[j].end()); + curr_path.insert(curr_path.end(), lines[j].begin(), lines[j].end()); + update_path_flag(curr_mark, lines[j], mark_flags[j]); + used[j] = true; + merged = true; + } + } + + if (merged) + continue; + + //search before + { + if (auto end_iter = end_z_map.find(curr_start);end_iter != end_z_map.end()) { + size_t j = *end_iter->second.begin(); + remove_from_map(j); + ZPath new_path = lines[j]; + PathFlag new_mark; + update_path_flag(new_mark, new_path, mark_flags[j]); + + new_path.insert(new_path.end(), curr_path.begin(), curr_path.end()); + new_mark.insert(new_mark.end(), curr_mark.begin(), curr_mark.end()); + curr_path = std::move(new_path); + curr_mark = std::move(new_mark); + used[j] = true; + merged = true; + } + else if (auto start_iter = start_z_map.find(curr_start); start_iter != start_z_map.end()) { + size_t j = *start_iter->second.begin(); + remove_from_map(j); + ZPath new_path = lines[j]; + std::reverse(new_path.begin(), new_path.end()); + PathFlag new_mark; + update_path_flag(new_mark, new_path, mark_flags[j]); + + new_path.insert(new_path.end(), curr_path.begin(), curr_path.end()); + new_mark.insert(new_mark.end(), curr_mark.begin(), curr_mark.end()); + curr_path = std::move(new_path); + curr_mark = std::move(new_mark); + used[j] = true; + merged = true; + } + } + + } while (merged); + + merged_paths.emplace_back(curr_path); + merged_marks.emplace_back(curr_mark); + } + + assert(merged_marks.size() == 1); + + FloatingThickPolyline res; + + auto& valid_path = merged_paths.front(); + auto& valid_mark = merged_marks.front(); + + for (size_t idx = 0; idx < valid_path.size(); ++idx) { + int zvalue = valid_path[idx].z(); + res.points.emplace_back(valid_path[idx].x(), valid_path[idx].y()); + res.is_floating.emplace_back(valid_mark[idx]); + if (0 <= zvalue && zvalue < subject_idx_range) { + res.width.emplace_back(line.get_width_at(prev_idx_modulo(zvalue, line.points))); + res.width.emplace_back(line.get_width_at(zvalue)); + } + else { + double width = interpolate_width(valid_path, line, subject_idx_range, default_width, idx); + res.width.emplace_back(width); + res.width.emplace_back(width); + } + } + res.width = std::vector(res.width.begin() + 1, res.width.end()-1); + assert(res.width.size() == 2 * res.points.size() - 2); + + return res; +} + +FloatingThickPolyline detect_floating_line(const ThickPolyline& line, const ExPolygons& floating_areas, const double default_width ,bool force_no_detect) +{ + { + Polyline polyline = line; + auto bbox_line = get_extents(polyline); + auto bbox_area = get_extents(floating_areas); + if (force_no_detect || !bbox_area.overlap(bbox_line) || intersection_pl(polyline, floating_areas).empty()) { + FloatingThickPolyline res; + res.width = line.width; + res.points = line.points; + res.is_floating.resize(res.points.size(), false); + return res; + } + } + + using ZPoint = ClipperLib_Z::IntPoint; + auto hash_function = [](const int a1, const int b1, const int a2, const int b2)->int32_t { + int32_t hash_val = 1000 * (a1 * 13 + b1) + (a2 * 17 + b2) + 1; + hash_val &= 0x7fffffff; + return hash_val; + }; + + int idx = 0; + int subject_idx_range; + ZPaths subject_paths; + { + subject_paths.emplace_back(); + for (auto p : line) + subject_paths.back().emplace_back(p.x(), p.y(), idx++); + } + + subject_idx_range = idx; + ZPaths clip_paths; + { + Polygons floating_polygons = to_polygons(floating_areas); + for (auto& poly : floating_polygons) { + clip_paths.emplace_back(); + for (const auto& p : poly) + clip_paths.back().emplace_back(p.x(), p.y(), idx++); + } + } + + ClipperLib_Z::ZFillCallback z_filler = [hash_function, subject_idx_range](const ZPoint& e1_a, const ZPoint& e1_b, const ZPoint& e2_a, const ZPoint& e2_b, ZPoint& d) { + if (e1_a.z() < subject_idx_range && e1_b.z() < subject_idx_range && e2_a.z() < subject_idx_range && e2_b.z() < subject_idx_range) { + BOOST_LOG_TRIVIAL(error) << Slic3r::format("ZFiller: both point in subject : %d, %d, %d, %d ", e1_a.z(), e1_b.z(), e2_a.z(), e2_b.z()); + } + if (e1_a.z() >= subject_idx_range && e1_b.z() >= subject_idx_range && e2_a.z() >= subject_idx_range && e2_b.z() >= subject_idx_range) { + BOOST_LOG_TRIVIAL(error) << Slic3r::format("ZFiller: both point in clip : %d, %d, %d, %d ", e1_a.z(), e1_b.z(), e2_a.z(), e2_b.z()); + } + if (e1_a.z() < 0 || e1_b.z() < 0 || e2_a.z() < 0 || e2_b.z() < 0) + BOOST_LOG_TRIVIAL(error) << Slic3r::format("ZFiller: Encounter negative z : %d, %d, %d, %d ", e1_a.z(), e1_b.z(), e2_a.z(), e2_b.z()); + if (e1_a.z() == e1_b.z() || e2_a.z() == e2_b.z()) { + BOOST_LOG_TRIVIAL(error) << Slic3r::format("ZFiller: Encounter same z in one line : %d, %d, %d, %d ", e1_a.z(), e1_b.z(), e2_a.z(), e2_b.z()); + } + if (e1_a.z() == e1_b.z() && e1_b.z() == e2_a.z() && e2_a.z() == e2_b.z()) { + BOOST_LOG_TRIVIAL(error) << Slic3r::format("ZFiller: Encounter same z in both line : %d, %d, %d, %d ", e1_a.z(), e1_b.z(), e2_a.z(), e2_b.z()); + // the intersect is generated by two lines in subject + d.z() = e1_a.z(); + return; + } // the intersect is generate by two line from subject and clip + d.z() = -hash_function(e1_a.z(), e1_b.z(), e2_a.z(), e2_b.z()); + if (d.z() >= 0) + BOOST_LOG_TRIVIAL(error) << Slic3r::format("ZFiller: hash function generate postive value : %d", d.z()); + }; + + + ZPaths intersect_out; + { + ClipperLib_Z::Clipper c; + ClipperLib_Z::PolyTree polytree; + c.ZFillFunction(z_filler); + c.AddPaths(subject_paths, ClipperLib_Z::ptSubject, false); + c.AddPaths(clip_paths, ClipperLib_Z::ptClip, true); + c.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero); + ClipperLib_Z::PolyTreeToPaths(std::move(polytree), intersect_out); + } + + ZPaths diff_out; + { + ClipperLib_Z::Clipper c; + ClipperLib_Z::PolyTree polytree; + c.ZFillFunction(z_filler); + c.AddPaths(subject_paths, ClipperLib_Z::ptSubject, false); + c.AddPaths(clip_paths, ClipperLib_Z::ptClip, true); + c.Execute(ClipperLib_Z::ctDifference, polytree, ClipperLib_Z::pftNonZero); + ClipperLib_Z::PolyTreeToPaths(std::move(polytree), diff_out); + } + + + ZPaths to_merge = diff_out; + to_merge.insert(to_merge.end(), intersect_out.begin(), intersect_out.end()); + std::vectorfloating_flags(to_merge.size(), false); + for (size_t idx = diff_out.size(); idx < diff_out.size() + intersect_out.size(); ++idx) + floating_flags[idx] = true; + + for (size_t idx = 0; idx < to_merge.size(); ++idx) { + for (auto iter = to_merge[idx].begin(); iter != to_merge[idx].end();++iter) { + if (iter->z() >= subject_idx_range) { + BOOST_LOG_TRIVIAL(error) << Slic3r::format("ZFiller: encounter idx from clip: %d",iter->z()); + } + } + } + + return merge_lines(to_merge,floating_flags,line,subject_idx_range,default_width); +} + +int start_none_floating_idx(int idx, const std::vector& none_floating_count) +{ + int backtrace_idx = idx - none_floating_count[idx] + 1; + if (backtrace_idx >= 0) + return backtrace_idx; + else + return none_floating_count.size() + backtrace_idx; +} + +template +void get_none_floating_prefix(const PointContainer& container, const ExPolygons& floating_areas, const Polygons& sparse_polys, std::vector& none_floating_length, std::vector& none_floating_count) +{ + std::vector(container.points.size(), 0).swap(none_floating_length); + std::vector(container.points.size(), 0).swap(none_floating_count); + + std::vector floating_bboxs; + for (size_t idx = 0; idx < floating_areas.size(); ++idx) + floating_bboxs.emplace_back(get_extents(floating_areas[idx])); + std::vector sparse_bboxs; + for (size_t idx = 0; idx < sparse_polys.size(); ++idx) + sparse_bboxs.emplace_back(get_extents(sparse_polys[idx])); + + auto point_in_floating_area = [&floating_bboxs, &sparse_bboxs, &floating_areas, &sparse_polys](const Point& p)->bool { + for (size_t idx = 0; idx < sparse_polys.size(); ++idx) { + if (!sparse_bboxs[idx].contains(p)) + continue; + if (sparse_polys[idx].contains(p)) + return false; + } + for (size_t idx = 0; idx < floating_areas.size(); ++idx) { + if (!floating_bboxs[idx].contains(p)) + continue; + if (floating_areas[idx].contains(p)) + return true; + } + + return false; + }; + + for (size_t idx = 0; idx < container.points.size(); ++idx) { + const Point& p = container.points[idx]; + if (!point_in_floating_area(p)) { + if (idx == 0) + none_floating_count[idx] = 1; + else + none_floating_count[idx] = none_floating_count[idx - 1] + 1; + if (none_floating_count[idx] > 1) + none_floating_length[idx] = none_floating_length[idx - 1] + ((Point)(container.points[prev_idx_modulo(idx, container.points)] - p)).cast().norm(); + else + none_floating_length[idx] = 0; + } + else { + none_floating_length[idx] = 0; + none_floating_count[idx] = 0; + } + } + + if (none_floating_count.back() > 0) { + for (size_t idx = 0; idx < container.points.size(); ++idx) { + if (none_floating_count[idx] == 0) + break; + none_floating_count[idx] = none_floating_count[prev_idx_modulo(idx, container.points)] + 1; + none_floating_length[idx] = none_floating_length[prev_idx_modulo(idx, container.points)] + ((Point)(container.points[prev_idx_modulo(idx, container.points)] - container.points[idx])).cast().norm(); + } + } +} + +template +int get_best_loop_start(const PointContainer& container, const ExPolygons& floating_areas, const Polygons& sparse_polys) { + std::vector none_floating_length; + std::vector none_floating_count; + + BoundingBox floating_bbox = get_extents(floating_areas); + BoundingBox poly_bbox(container.points); + + if (!poly_bbox.overlap(floating_bbox)) + return 0; + + Polygons clipped_sparse_polys = ClipperUtils::clip_clipper_polygons_with_subject_bbox(sparse_polys, poly_bbox); + get_none_floating_prefix(container, floating_areas, clipped_sparse_polys, none_floating_length, none_floating_count); + int best_idx = std::distance(none_floating_length.begin(), std::max_element(none_floating_length.begin(), none_floating_length.end())); + return start_none_floating_idx(best_idx, none_floating_count); +} + +template +std::vector get_loop_start_candidates(const PointContainer& container, const ExPolygons& floating_areas, const Polygons& sparse_polys) +{ + std::vector none_floating_length; + std::vector none_floating_count; + + BoundingBox floating_bbox = get_extents(floating_areas); + BoundingBox poly_bbox(container.points); + std::vector candidate_list; + + if (!poly_bbox.overlap(floating_bbox)) { + candidate_list.resize(container.points.size()); + std::iota(candidate_list.begin(), candidate_list.end(), 0); + return candidate_list; + } + Polygons clipped_sparse_polys = ClipperUtils::clip_clipper_polygons_with_subject_bbox(sparse_polys, poly_bbox); + get_none_floating_prefix(container, floating_areas, clipped_sparse_polys, none_floating_length, none_floating_count); + for (size_t idx = 0; idx < none_floating_length.size(); ++idx) { + if (none_floating_length[idx] > 0) + candidate_list.emplace_back(start_none_floating_idx(idx, none_floating_count)); + } + return candidate_list; +} + + +void smooth_floating_line(FloatingThickPolyline& line,coord_t max_gap_threshold, coord_t min_floating_threshold) +{ + if (line.empty()) + return; + struct LineParts { + int start; + int end; + bool is_floating; + }; + + auto build_line_parts = [&](const FloatingThickPolyline& line)->std::vector { + std::vector line_parts; + bool current_val = line.is_floating.front(); + int start = 0; + for (size_t idx = 1; idx < line.is_floating.size(); ++idx) { + if (line.is_floating[idx] != current_val) { + line_parts.push_back({ start,(int)(idx - 1),current_val }); + current_val = line.is_floating[idx]; + start = idx; + } + } + line_parts.push_back({ start,(int)(line.is_floating.size() - 1),current_val }); + return line_parts; + }; + + std::vector distance_prefix(line.points.size(),0); + for (size_t idx = 0; idx < line.points.size();++idx) { + if (idx == 0) + distance_prefix[idx] = 0; + else { + distance_prefix[idx] = distance_prefix[idx - 1] + (line.points[idx] - line.points[idx - 1]).cast().norm(); + } + } + { + // remove too small gaps + std::vector line_parts = build_line_parts(line); + std::vector> gaps_to_merge; + + for (size_t i = 1; i + 1 < line_parts.size(); ++i) { + const auto& curr = line_parts[i]; + if (!curr.is_floating) { + const auto& prev = line_parts[i - 1]; + const auto& next = line_parts[i + 1]; + if (prev.is_floating && next.is_floating) { + double total_length = distance_prefix[next.start] - distance_prefix[prev.end]; + if (total_length < max_gap_threshold) { + gaps_to_merge.emplace_back(curr.start, curr.end); + } + } + } + } + + for (const auto& gap : gaps_to_merge) { + for (int i = gap.first; i <= gap.second; ++i) { + line.is_floating[i] = true; + } + } + } + + { + std::vector line_parts = build_line_parts(line); + std::vector> segments_to_remove; + + for (auto& part : line_parts) { + if (part.is_floating && distance_prefix[part.end] - distance_prefix[part.start] < min_floating_threshold) { + segments_to_remove.emplace_back(part.start, part.end); + } + } + + for (const auto& seg : segments_to_remove) { + for (int i = seg.first; i <= seg.second; ++i) { + line.is_floating[i] = false; + } + } + } +} + +// nearest neibour排序,但是取点时,只能取get_loop_start_candidates得到的点 +FloatingThickPolylines FillFloatingConcentric::resplit_order_loops(Point curr_point, std::vector all_extrusions, const ExPolygons& floating_areas, const Polygons& sparse_polys, const coord_t default_width) +{ + FloatingThickPolylines result; + + for (size_t idx = 0; idx < all_extrusions.size(); ++idx) { + if (all_extrusions[idx]->empty()) + continue; + ThickPolyline thick_polyline = Arachne::to_thick_polyline(*all_extrusions[idx]); + FloatingThickPolyline thick_line_with_floating = detect_floating_line(thick_polyline, floating_areas, default_width, !print_object_config->detect_floating_vertical_shell.value); + smooth_floating_line(thick_line_with_floating, scale_(2), scale_(2)); + int split_idx = 0; + if (!floating_areas.empty() && all_extrusions[idx]->is_closed && thick_line_with_floating.points.front() == thick_line_with_floating.points.back()) { + if (idx == 0) + split_idx = get_best_loop_start(thick_line_with_floating, floating_areas,sparse_polys); + else { + auto candidates = get_loop_start_candidates(thick_line_with_floating, floating_areas,sparse_polys); + double min_dist = std::numeric_limits::max(); + for (auto candidate : candidates) { + double dist = (curr_point - thick_line_with_floating.points[candidate]).cast().norm(); + if (min_dist > dist) { + min_dist = dist; + split_idx = candidate; + } + } + } + FloatingThickPolyline new_line = thick_line_with_floating.rebase_at(split_idx); + assert(new_line.width.size() == 2 * new_line.points.size() - 2); + result.emplace_back(thick_line_with_floating.rebase_at(split_idx)); + } + else { + assert(thick_line_with_floating.width.size() == 2 * thick_line_with_floating.points.size() - 2); + result.emplace_back(thick_line_with_floating); + } + curr_point = result.back().last_point(); + } + return result; +} + +#if 0 +Polylines FillFloatingConcentric::resplit_order_loops(Point curr_point, Polygons loops, const ExPolygons& floating_areas) +{ + Polylines result; + for (size_t idx = 0; idx < loops.size(); ++idx) { + const Polygon& loop = loops[idx]; + int split_idx = 0; + if (!floating_areas.empty()) { + if (idx == 0) + split_idx = get_best_loop_start(loop, floating_areas); + else { + auto candidates = get_loop_start_candidates(loop, floating_areas); + double min_dist = std::numeric_limits::max(); + for (auto candidate : candidates) { + double dist = (curr_point - loop.points[candidate]).cast().norm(); + if (min_dist > dist) { + min_dist = dist; + split_idx = candidate; + } + } + } + result.emplace_back(loop.split_at_index(split_idx)); + } + else { + result.emplace_back(loop.split_at_index(curr_point.nearest_point_index(loop.points))); + } + curr_point = result.back().last_point(); + } + return result; +}; + +void FillFloatingConcentric::_fill_surface_single( + const FillParams& params, + unsigned int thickness_layers, + const std::pair& direction, + ExPolygon expolygon, + FloatingLines& polylines_out +) +{ + auto expoly_bbox = get_extents(expolygon); + coord_t min_spacing = scale_(this->spacing); + coord_t distance = coord_t(min_spacing / params.density); + distance = this->_adjust_solid_spacing(expoly_bbox.size()(0), distance); + this->spacing = unscale(distance); + + Polygons loops = to_polygons(expolygon); + ExPolygons offseted_expolys{ std::move(expolygon) }; + while (!offseted_expolys.empty()) { + offseted_expolys = offset2_ex(offseted_expolys, -(distance + min_spacing / 2), min_spacing / 2); + append(loops, to_polygons(offseted_expolys)); + } + // generate paths from outermost to the inner most + loops = union_pt_chained_outside_in(loops); + + auto reordered_polylines = resplit_order_loops({ 0,0 }, loops, lower_layer_unsupport_areas); + size_t i = polylines_out.size(); + for (auto& polyline : reordered_polylines) { + polylines_out.emplace_back(std::move(polyline)); + } + + size_t j = polylines_out.size(); + for (; i < polylines_out.size(); ++i) { + polylines_out[i].clip_end(this->loop_clipping); + if (polylines_out[i].is_valid()) { + if (j < i) + polylines_out[j] = std::move(polylines_out[i]); + ++j; + } + } + if (j < polylines_out.size()) + polylines_out.erase(polylines_out.begin() + j, polylines_out.end()); +} +#endif + +void FillFloatingConcentric::_fill_surface_single(const FillParams& params, + unsigned int thickness_layers, + const std::pair& direction, + ExPolygon expolygon, + FloatingThickPolylines& thick_polylines_out) +{ + Point bbox_size = expolygon.contour.bounding_box().size(); + coord_t min_spacing = params.flow.scaled_spacing(); + + coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / min_spacing + 1; + Polygons polygons = to_polygons(expolygon); + + double min_nozzle_diameter = *std::min_element(print_config->nozzle_diameter.values.begin(), print_config->nozzle_diameter.values.end()); + Arachne::WallToolPathsParams input_params; + input_params.min_bead_width = 0.85 * min_nozzle_diameter; + input_params.min_feature_size = 0.25 * min_nozzle_diameter; + input_params.wall_transition_length = 0.4; + input_params.wall_transition_angle = 10; + input_params.wall_transition_filter_deviation = 0.25 * min_nozzle_diameter; + input_params.wall_distribution_count = 1; + + Arachne::WallToolPaths wallToolPaths(polygons, min_spacing, min_spacing, loops_count, 0, params.layer_height, input_params); + + std::vector loops = wallToolPaths.getToolPaths(); + std::vector all_extrusions; + for (Arachne::VariableWidthLines& loop : loops) { + if (loop.empty()) + continue; + for (const Arachne::ExtrusionLine& wall : loop) + all_extrusions.emplace_back(&wall); + } + + // Split paths using a nearest neighbor search. + size_t firts_poly_idx = thick_polylines_out.size(); + auto thick_polylines = resplit_order_loops({ 0,0 }, all_extrusions, this->lower_layer_unsupport_areas, this->lower_sparse_polys,min_spacing); + append(thick_polylines_out, thick_polylines); + + + // clip the paths to prevent the extruder from getting exactly on the first point of the loop + // Keep valid paths only. + size_t j = firts_poly_idx; + for (size_t i = firts_poly_idx; i < thick_polylines_out.size(); ++i) { + thick_polylines_out[i].clip_end(this->loop_clipping); + if (thick_polylines_out[i].is_valid()) { + if (j < i) + thick_polylines_out[j] = std::move(thick_polylines_out[i]); + ++j; + } + } + if (j < thick_polylines_out.size()) + thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end()); +} + + +FloatingThickPolylines FillFloatingConcentric::fill_surface_arachne_floating(const Surface* surface, const FillParams& params) +{ + // Create the infills for each of the regions. + FloatingThickPolylines floating_thick_polylines_out; + for (ExPolygon& expoly : no_overlap_expolygons) + _fill_surface_single(params, surface->thickness_layers, _infill_direction(surface), std::move(expoly), floating_thick_polylines_out); + return floating_thick_polylines_out; +} + +void FillFloatingConcentric::fill_surface_extrusion(const Surface* surface, const FillParams& params, ExtrusionEntitiesPtr& out) +{ + FloatingThickPolylines floating_lines = this->fill_surface_arachne_floating(surface, params); + if (floating_lines.empty()) + return; + Flow new_flow = params.flow.with_spacing(this->spacing); + double flow_mm3_per_mm = new_flow.mm3_per_mm(); + double flow_width = new_flow.width(); + + ExtrusionEntityCollection* ecc = new ExtrusionEntityCollection(); + ecc->no_sort = true; + out.push_back(ecc); + size_t idx = ecc->entities.size(); + + const float tolerance = float(scale_(0.05)); + for (const auto& line : floating_lines) { + ExtrusionPaths paths = floating_thick_polyline_to_extrusion_paths(line, params.extrusion_role, new_flow, tolerance); + // Append paths to collection. + assert(!paths.empty()); + if (!paths.empty()) { + if (paths.front().first_point() == paths.back().last_point()) + ecc->entities.emplace_back(new ExtrusionLoop(std::move(paths))); + else { + for (ExtrusionPath& path : paths) { + assert(!path.empty()); + ecc->entities.emplace_back(new ExtrusionPath(std::move(path))); + } + } + } + } +} + + +} // namespace Slic3r diff --git a/src/libslic3r/Fill/FillFloatingConcentric.hpp b/src/libslic3r/Fill/FillFloatingConcentric.hpp new file mode 100644 index 0000000..bb993ba --- /dev/null +++ b/src/libslic3r/Fill/FillFloatingConcentric.hpp @@ -0,0 +1,77 @@ +#ifndef SLIC3R_FillFloatingConcentric_HPP +#define SLIC3R_FillFloatingConcentric_HPP + +#include "FillBase.hpp" +#include "FillConcentric.hpp" +#include "Arachne/WallToolPaths.hpp" + +namespace Slic3r{ + struct FloatingThickline : public ThickLine + { + FloatingThickline(const Point& a, const Point& b, double wa, double wb, bool a_floating, bool b_floating) :ThickLine(a, b, wa, wb) + { + is_a_floating = a_floating; + is_b_floating = b_floating; + } + bool is_a_floating; + bool is_b_floating; + }; + using FloatingThicklines = std::vector; + + struct FloatingPolyline : public Polyline + { + std::vector is_floating; + FloatingPolyline rebase_at(size_t idx); + }; + using FloatingPolylines = std::vector; + + struct FloatingThickPolyline :public ThickPolyline + { + std::vector is_floating; + FloatingThickPolyline rebase_at(size_t idx); + FloatingThicklines floating_thicklines()const; + }; + using FloatingThickPolylines = std::vector; + + class FillFloatingConcentric : public FillConcentric + { + public: + ~FillFloatingConcentric() override = default; + ExPolygons lower_layer_unsupport_areas; + Polygons lower_sparse_polys; + + protected: + Fill* clone() const override { return new FillFloatingConcentric(*this); } +#if 0 + void _fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon expolygon, + FloatingLines &polylines_out) ; +#endif + + void _fill_surface_single(const FillParams& params, + unsigned int thickness_layers, + const std::pair& direction, + ExPolygon expolygon, + FloatingThickPolylines& thick_polylines_out); + + FloatingThickPolylines fill_surface_arachne_floating(const Surface* surface, const FillParams& params); + + void fill_surface_extrusion(const Surface* surface, const FillParams& params, ExtrusionEntitiesPtr& out); + + FloatingThickPolylines resplit_order_loops(Point curr_point, std::vector all_extrusions, const ExPolygons& floating_areas, const Polygons& sparse_polys, const coord_t default_width); +#if 0 + Polylines resplit_order_loops(Point curr_point, Polygons loops, const ExPolygons& floating_areas); +#endif + + friend class Layer; + }; +} + + + + + +#endif \ No newline at end of file diff --git a/src/libslic3r/Fill/FillGyroid.hpp b/src/libslic3r/Fill/FillGyroid.hpp index ac66dfc..751a7c2 100644 --- a/src/libslic3r/Fill/FillGyroid.hpp +++ b/src/libslic3r/Fill/FillGyroid.hpp @@ -15,6 +15,7 @@ public: // require bridge flow since most of this pattern hangs in air bool use_bridge_flow() const override { return false; } + bool is_self_crossing() override { return false; } // Correction applied to regular infill angle to maximize printing // speed in default configuration (degrees) diff --git a/src/libslic3r/Fill/FillHoneycomb.hpp b/src/libslic3r/Fill/FillHoneycomb.hpp index 707e976..e8f806a 100644 --- a/src/libslic3r/Fill/FillHoneycomb.hpp +++ b/src/libslic3r/Fill/FillHoneycomb.hpp @@ -13,6 +13,7 @@ class FillHoneycomb : public Fill { public: ~FillHoneycomb() override {} + bool is_self_crossing() override { return false; } protected: Fill* clone() const override { return new FillHoneycomb(*this); }; diff --git a/src/libslic3r/Fill/FillLightning.cpp b/src/libslic3r/Fill/FillLightning.cpp index dd2189e..cc2ea33 100644 --- a/src/libslic3r/Fill/FillLightning.cpp +++ b/src/libslic3r/Fill/FillLightning.cpp @@ -13,6 +13,7 @@ void Filler::_fill_surface_single( ExPolygon expolygon, Polylines &polylines_out) { + if (!generator) return; const Layer &layer = generator->getTreesForLayer(this->layer_id); Polylines fill_lines = layer.convertToLines(to_polygons(expolygon), scaled(0.5 * this->spacing - this->overlap)); diff --git a/src/libslic3r/Fill/FillLightning.hpp b/src/libslic3r/Fill/FillLightning.hpp index 6e67278..74aa159 100644 --- a/src/libslic3r/Fill/FillLightning.hpp +++ b/src/libslic3r/Fill/FillLightning.hpp @@ -20,6 +20,7 @@ class Filler : public Slic3r::Fill { public: ~Filler() override = default; + bool is_self_crossing() override { return false; } Generator *generator { nullptr }; protected: diff --git a/src/libslic3r/Fill/FillLine.hpp b/src/libslic3r/Fill/FillLine.hpp index 9bf2b97..b1ddc73 100644 --- a/src/libslic3r/Fill/FillLine.hpp +++ b/src/libslic3r/Fill/FillLine.hpp @@ -14,6 +14,7 @@ class FillLine : public Fill public: Fill* clone() const override { return new FillLine(*this); }; ~FillLine() override = default; + bool is_self_crossing() override { return false; } protected: void _fill_surface_single( diff --git a/src/libslic3r/Fill/FillPlanePath.hpp b/src/libslic3r/Fill/FillPlanePath.hpp index f05f320..1371e85 100644 --- a/src/libslic3r/Fill/FillPlanePath.hpp +++ b/src/libslic3r/Fill/FillPlanePath.hpp @@ -37,6 +37,7 @@ class FillPlanePath : public Fill { public: ~FillPlanePath() override = default; + bool is_self_crossing() override { return false; } protected: void _fill_surface_single( diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index 6199b5e..7e746de 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -124,6 +125,8 @@ struct SegmentIntersection // y position of the intersection, rational number. int64_t pos_p { 0 }; uint32_t pos_q { 1 }; + // the index fo the prev point in the contour + size_t prev_idx{0}; coord_t pos() const { // Division rounds both positive and negative down to zero. @@ -133,7 +136,7 @@ struct SegmentIntersection p -= int64_t(pos_q>>1); else p += int64_t(pos_q>>1); - return coord_t(p / int64_t(pos_q)); + return coord_t(p / int64_t(pos_q)); } // Left vertical line / contour intersection point. @@ -249,7 +252,7 @@ struct SegmentIntersection int vertical_down(Side side) const { return side == Side::Left ? this->left_vertical_down() : this->right_vertical_down(); } int vertical_outside(Side side) const { return side == Side::Left ? this->left_vertical_outside() : this->right_vertical_outside(); } // Returns -1 if there is no link up. - int vertical_up() const { + int vertical_up() const { return this->has_left_vertical_up() ? this->left_vertical_up() : this->right_vertical_up(); } LinkQuality vertical_up_quality() const { @@ -320,7 +323,7 @@ struct SegmentIntersection } } - bool operator==(const SegmentIntersection &other) const + bool operator==(const SegmentIntersection &other) const { assert(pos_q > 0); assert(other.pos_q > 0); @@ -363,6 +366,66 @@ struct SegmentedIntersectionLine std::vector intersections; }; +static void adjust_sort_for_segment_intersections(std::vector &intersections) +{ + using IntersectionType = SegmentIntersection::SegmentIntersectionType; + std::stack stack; + bool has_out_low = false; + auto is_valid_type = [&stack, &has_out_low](IntersectionType type) { + if (stack.empty()) { + return type == IntersectionType::OUTER_LOW; + } else { + auto top_type = stack.top(); + switch (type) { + case SegmentIntersection::OUTER_LOW: return false; + case SegmentIntersection::OUTER_HIGH: return top_type == IntersectionType::OUTER_LOW; + case SegmentIntersection::INNER_LOW: return top_type != IntersectionType::OUTER_HIGH; + case SegmentIntersection::INNER_HIGH: return top_type == IntersectionType::INNER_LOW; + default: break; + } + return true; + } + }; + + std::vector visited(intersections.size(), false); + std::vector index_group; + for (size_t i = 0; i < intersections.size();) { + if (is_valid_type(intersections[i].type)) { + index_group.clear(); + // std::fill() + if (intersections[i].type == SegmentIntersection::OUTER_LOW || intersections[i].type == SegmentIntersection::INNER_LOW) { + stack.push(intersections[i].type); + } else if (intersections[i].type == SegmentIntersection::OUTER_HIGH || intersections[i].type == SegmentIntersection::INNER_HIGH) { + stack.pop(); + } + ++i; + } else { + visited[i] = true; + for (size_t j = i + 1; j < intersections.size(); ++j) { + if (!visited[j] && abs(intersections[j].pos() - intersections[i].pos()) < scale_(EPSILON)) { index_group.push_back(j); } + } + + if (!index_group.empty()) { + int swap_index = -1; + for (auto index : index_group) { + if (!visited[index]) { + swap_index = index; + visited[index] = true; + break; + } + } + + if (swap_index != -1) { + std::swap(intersections[i], intersections[swap_index]); + continue; + } + } + + ++i; + } + } +} + static SegmentIntersection phony_outer_intersection(SegmentIntersection::SegmentIntersectionType type, coord_t pos) { assert(type == SegmentIntersection::OUTER_LOW || type == SegmentIntersection::OUTER_HIGH); @@ -412,7 +475,7 @@ public: //assert(aoffset1 < 0); assert(aoffset2 <= 0); // assert(aoffset2 == 0 || aoffset2 < aoffset1); -// bool sticks_removed = +// bool sticks_removed = remove_sticks(polygons_src); // if (sticks_removed) BOOST_LOG_TRIVIAL(error) << "Sticks removed!"; polygons_outer = aoffset1 == 0 ? to_polygons(polygons_src) : offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, miterLimit); @@ -452,7 +515,7 @@ public: // Any contour with offset2 bool is_contour_inner(size_t idx) const { return idx >= n_contours_outer; } - const Polygon& contour(size_t idx) const + const Polygon& contour(size_t idx) const { return is_contour_outer(idx) ? polygons_outer[idx] : polygons_inner[idx - n_contours_outer]; } Polygon& contour(size_t idx) @@ -460,11 +523,11 @@ public: bool is_contour_ccw(size_t idx) const { return polygons_ccw[idx]; } - BoundingBox bounding_box_src() const + BoundingBox bounding_box_src() const { return get_extents(polygons_src); } - BoundingBox bounding_box_outer() const + BoundingBox bounding_box_outer() const { return get_extents(polygons_outer); } - BoundingBox bounding_box_inner() const + BoundingBox bounding_box_inner() const { return get_extents(polygons_inner); } #ifdef SLIC3R_DEBUG @@ -543,16 +606,16 @@ static inline bool intersection_on_prev_next_vertical_line_valid( } static inline bool intersection_on_prev_vertical_line_valid( - const std::vector &segs, - size_t iVerticalLine, + const std::vector &segs, + size_t iVerticalLine, size_t iIntersection) { return intersection_on_prev_next_vertical_line_valid(segs, iVerticalLine, iIntersection, SegmentIntersection::Side::Left); } static inline bool intersection_on_next_vertical_line_valid( - const std::vector &segs, - size_t iVerticalLine, + const std::vector &segs, + size_t iVerticalLine, size_t iIntersection) { return intersection_on_prev_next_vertical_line_valid(segs, iVerticalLine, iIntersection, SegmentIntersection::Side::Right); @@ -560,7 +623,7 @@ static inline bool intersection_on_next_vertical_line_valid( // Measure an Euclidian length of a perimeter segment when going from iIntersection to iIntersection2. static inline coordf_t measure_perimeter_horizontal_segment_length( - const ExPolygonWithOffset &poly_with_offset, + const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t iVerticalLine, size_t iIntersection, @@ -798,6 +861,7 @@ static std::vector slice_region_by_vertical_lines(con SegmentIntersection is; is.iContour = iContour; is.iSegment = iSegment; + is.prev_idx = iPrev; assert(l <= this_x); assert(r >= this_x); // Calculate the intersection position in y axis. x is known. @@ -839,6 +903,12 @@ static std::vector slice_region_by_vertical_lines(con // +-1 to take rounding into account. assert(is.pos() + 1 >= std::min(p1.y(), p2.y())); assert(is.pos() <= std::max(p1.y(), p2.y()) + 1); + //QDS: check segment intersection type + const coord_t dir = p2.x() - p1.x(); + const bool low = dir > 0; + is.type = poly_with_offset.is_contour_outer(iContour) ? + (low ? SegmentIntersection::OUTER_LOW : SegmentIntersection::OUTER_HIGH) : + (low ? SegmentIntersection::INNER_LOW : SegmentIntersection::INNER_HIGH); segs[i].intersections.push_back(is); } } @@ -848,7 +918,14 @@ static std::vector slice_region_by_vertical_lines(con for (size_t i_seg = 0; i_seg < segs.size(); ++ i_seg) { SegmentedIntersectionLine &sil = segs[i_seg]; // Sort the intersection points using exact rational arithmetic. - std::sort(sil.intersections.begin(), sil.intersections.end()); + //QDS: if the LOW and HIGH has seam y pos, LOW should be first + std::sort(sil.intersections.begin(), sil.intersections.end(), [](const SegmentIntersection &l, const SegmentIntersection &r) { + if (l.pos() == r.pos() && l.iContour == r.iContour) + return l.type < r.type; + + return l.pos() < r.pos(); + }); + adjust_sort_for_segment_intersections(sil.intersections); // Assign the intersection types, remove duplicate or overlapping intersection points. // When a loop vertex touches a vertical line, intersection point is generated for both segments. // If such two segments are oriented equally, then one of them is removed. @@ -858,16 +935,11 @@ static std::vector slice_region_by_vertical_lines(con size_t j = 0; for (size_t i = 0; i < sil.intersections.size(); ++ i) { // What is the orientation of the segment at the intersection point? - SegmentIntersection &is = sil.intersections[i]; + SegmentIntersection &is = sil.intersections[i]; const size_t iContour = is.iContour; - const Points &contour = poly_with_offset.contour(iContour).points; const size_t iSegment = is.iSegment; - const size_t iPrev = prev_idx_modulo(iSegment, contour); - const coord_t dir = contour[iSegment].x() - contour[iPrev].x(); - const bool low = dir > 0; - is.type = poly_with_offset.is_contour_outer(iContour) ? - (low ? SegmentIntersection::OUTER_LOW : SegmentIntersection::OUTER_HIGH) : - (low ? SegmentIntersection::INNER_LOW : SegmentIntersection::INNER_HIGH); + const size_t iPrev = is.prev_idx; + const bool low = is.type == SegmentIntersection::OUTER_LOW || is.type == SegmentIntersection::INNER_LOW; bool take_next = true; if (j > 0) { SegmentIntersection &is2 = sil.intersections[j - 1]; @@ -876,7 +948,7 @@ static std::vector slice_region_by_vertical_lines(con if (is.pos_p == is2.pos_p) { // Two successive segments meet exactly at the vertical line. // Verify that the segments of sil.intersections[i] and sil.intersections[j-1] are adjoint. - assert(iSegment == prev_idx_modulo(is2.iSegment, contour) || is2.iSegment == iPrev); + assert(iSegment == is2.prev_idx || is2.iSegment == iPrev); assert(is.type == is2.type); // Two successive segments of the same direction (both to the right or both to the left) // meet exactly at the vertical line. @@ -986,7 +1058,6 @@ static std::vector slice_region_by_vertical_lines(con throw; } #endif //INFILL_DEBUG_OUTPUT - return segs; } @@ -1125,7 +1196,7 @@ static void connect_segment_intersections_by_contours( assert(inext >= 0); itsct.prev_on_contour = iprev; - itsct.prev_on_contour_type = same_prev ? + itsct.prev_on_contour_type = same_prev ? (iprev < i_intersection ? SegmentIntersection::LinkType::Down : SegmentIntersection::LinkType::Up) : SegmentIntersection::LinkType::Horizontal; itsct.next_on_contour = inext; @@ -1182,7 +1253,7 @@ static void connect_segment_intersections_by_contours( } else if (link_max_length > 0) { // Measure length of the links. if (itsct.prev_on_contour_quality == SegmentIntersection::LinkQuality::Valid && - (same_prev ? + (same_prev ? measure_perimeter_segment_on_vertical_line_length(poly_with_offset, segs, i_vline, iprev, i_intersection, forward) : measure_perimeter_horizontal_segment_length(poly_with_offset, segs, i_vline - 1, iprev, i_intersection)) > link_max_length) itsct.prev_on_contour_quality = SegmentIntersection::LinkQuality::TooLong; @@ -1351,8 +1422,11 @@ static SegmentIntersection& end_of_vertical_run(SegmentedIntersectionLine &il, S return const_cast(end_of_vertical_run(std::as_const(il), std::as_const(start))); } -static void traverse_graph_generate_polylines( - const ExPolygonWithOffset& poly_with_offset, const FillParams& params, const coord_t link_max_length, std::vector& segs, Polylines& polylines_out) +static void traverse_graph_generate_polylines(const ExPolygonWithOffset &poly_with_offset, + const FillParams ¶ms, + std::vector &segs, + const bool consistent_pattern, + Polylines &polylines_out) { // For each outer only chords, measure their maximum distance to the bow of the outer contour. // Mark an outer only chord as consumed, if the distance is low. @@ -1386,34 +1460,28 @@ static void traverse_graph_generate_polylines( pointLast = polylines_out.back().points.back(); for (;;) { if (i_intersection == -1) { - // The path has been interrupted. Find a next starting point, closest to the previous extruder position. - coordf_t dist2min = std::numeric_limits().max(); - for (int i_vline2 = 0; i_vline2 < int(segs.size()); ++ i_vline2) { + // The path has been interrupted. Find a next starting point. + for (int i_vline2 = 0; i_vline2 < int(segs.size()); ++i_vline2) { const SegmentedIntersectionLine &vline = segs[i_vline2]; - if (! vline.intersections.empty()) { + if (!vline.intersections.empty()) { assert(vline.intersections.size() > 1); // Even number of intersections with the loops. assert((vline.intersections.size() & 1) == 0); assert(vline.intersections.front().type == SegmentIntersection::OUTER_LOW); - for (int i = 0; i < int(vline.intersections.size()); ++ i) { - const SegmentIntersection& intrsctn = vline.intersections[i]; + + // For infill that needs to be consistent between layers (like Zig Zag), + // we are switching between forward and backward passes based on the line index. + const bool forward_pass = !consistent_pattern || (i_vline2 % 2 == 0); + for (int i = 0; i < int(vline.intersections.size()); ++i) { + const int intrsctn_idx = forward_pass ? i : int(vline.intersections.size()) - i - 1; + const SegmentIntersection &intrsctn = vline.intersections[intrsctn_idx]; if (intrsctn.is_outer()) { - assert(intrsctn.is_low() || i > 0); - bool consumed = intrsctn.is_low() ? - intrsctn.consumed_vertical_up : - vline.intersections[i - 1].consumed_vertical_up; - if (! consumed) { - coordf_t dist2 = sqr(coordf_t(pointLast(0) - vline.pos)) + sqr(coordf_t(pointLast(1) - intrsctn.pos())); - if (dist2 < dist2min) { - dist2min = dist2; - i_vline = i_vline2; - i_intersection = i; - //FIXME We are taking the first left point always. Verify, that the caller chains the paths - // by a shortest distance, while reversing the paths if needed. - //if (polylines_out.empty()) - // Initial state, take the first line, which is the first from the left. - goto found; - } + assert(intrsctn.is_low() || intrsctn_idx > 0); + const bool consumed = intrsctn.is_low() ? intrsctn.consumed_vertical_up : vline.intersections[intrsctn_idx - 1].consumed_vertical_up; + if (!consumed) { + i_vline = i_vline2; + i_intersection = intrsctn_idx; + goto found; } } } @@ -1486,9 +1554,13 @@ static void traverse_graph_generate_polylines( // 1) Find possible connection points on the previous / next vertical line. int i_prev = it->left_horizontal(); int i_next = it->right_horizontal(); - bool intersection_prev_valid = intersection_on_prev_vertical_line_valid(segs, i_vline, i_intersection); + + // To ensure pattern consistency between layers for Zig Zag infill, we always + // try to connect to the next vertical line and never to the previous vertical line. + bool intersection_prev_valid = intersection_on_prev_vertical_line_valid(segs, i_vline, i_intersection) && !consistent_pattern; bool intersection_next_valid = intersection_on_next_vertical_line_valid(segs, i_vline, i_intersection); bool intersection_horizontal_valid = intersection_prev_valid || intersection_next_valid; + // Mark both the left and right connecting segment as consumed, because one cannot go to this intersection point as it has been consumed. if (i_prev != -1) segs[i_vline - 1].intersections[i_prev].consumed_perimeter_right = true; @@ -1523,9 +1595,9 @@ static void traverse_graph_generate_polylines( // Try to connect to a previous or next point on the same vertical line. int i_vertical = it->vertical_outside(); - auto vertical_link_quality = (i_vertical == -1 || vline.intersections[i_vertical + (going_up ? 0 : -1)].consumed_vertical_up) ? + auto vertical_link_quality = (i_vertical == -1 || vline.intersections[i_vertical + (going_up ? 0 : -1)].consumed_vertical_up) ? SegmentIntersection::LinkQuality::Invalid : it->vertical_outside_quality(); -#if 0 +#if 0 if (vertical_link_quality == SegmentIntersection::LinkQuality::Valid || // Follow the link if there is no horizontal link available. (! intersection_horizontal_valid && vertical_link_quality != SegmentIntersection::LinkQuality::Invalid)) { @@ -1632,7 +1704,7 @@ struct MonotonicRegionLink { MonotonicRegion *region; bool flipped; - // Distance of right side of this region to left side of the next region, if the "flipped" flag of this region and the next region + // Distance of right side of this region to left side of the next region, if the "flipped" flag of this region and the next region // is applied as defined. AntPath *next; // Distance of right side of this region to left side of the next region, if the "flipped" flag of this region and the next region @@ -1646,10 +1718,10 @@ class AntPathMatrix { public: AntPathMatrix( - const std::vector ®ions, - const ExPolygonWithOffset &poly_with_offset, + const std::vector ®ions, + const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, - const float initial_pheromone) : + const float initial_pheromone) : m_regions(regions), m_poly_with_offset(poly_with_offset), m_segs(segs), @@ -2018,7 +2090,7 @@ static float montonous_region_path_length(const MonotonicRegion ®ion, bool di break; assert(it->iContour == vline.intersections[inext].iContour); it = vline.intersections.data() + inext; - } + } } else { // Going down. assert(it->is_high()); @@ -2035,7 +2107,7 @@ static float montonous_region_path_length(const MonotonicRegion ®ion, bool di break; assert(it->iContour == vline.intersections[inext].iContour); it = vline.intersections.data() + inext; - } + } } if (i_vline == region.right.vline) @@ -2047,7 +2119,7 @@ static float montonous_region_path_length(const MonotonicRegion ®ion, bool di // Find the end of the next overlapping vertical segment. const SegmentedIntersectionLine &vline_right = segs[i_vline + 1]; - const SegmentIntersection *right = going_up ? + const SegmentIntersection *right = going_up ? &vertical_run_top(vline_right, vline_right.intersections[iright]) : &vertical_run_bottom(vline_right, vline_right.intersections[iright]); i_intersection = int(right - vline_right.intersections.data()); @@ -2120,6 +2192,8 @@ static void connect_monotonic_regions(std::vector ®ions, con for (;;) { MapType key(rbegin, nullptr); auto it = std::lower_bound(map_intersection_to_region_start.begin(), map_intersection_to_region_start.end(), key); + if (it == map_intersection_to_region_start.end() || it->first != key.first) + break; assert(it != map_intersection_to_region_start.end() && it->first == key.first); it->second->left_neighbors.emplace_back(®ion); SegmentIntersection *rnext = &vertical_run_top(vline_right, *rbegin); @@ -2292,7 +2366,7 @@ static std::vector chain_monotonic_regions( } else { if (regions_in_queue[iprev]) assert(left_neighbors_unprocessed[iprev] == 1); - else + else assert(left_neighbors_unprocessed[iprev] > 1); ++ num_predecessors_unprocessed; } @@ -2384,7 +2458,7 @@ static std::vector chain_monotonic_regions( total_length += next_region->length(next_dir) + path_matrix(*path_end.region, path_end.flipped, *next_region, next_dir).length; path_end = { next_region, next_dir }; assert(left_neighbors_unprocessed[next_region - regions.data()] == 1); - left_neighbors_unprocessed[next_region - regions.data()] = 0; + left_neighbors_unprocessed[next_region - regions.data()] = 0; } // Set an initial pheromone value to 10% of the greedy path's value. @@ -2411,7 +2485,7 @@ static std::vector chain_monotonic_regions( for (int round = 0; round < num_rounds && num_rounds_no_change < num_rounds_no_change_exit; ++ round) { bool improved = false; - for (int ant = 0; ant < num_ants; ++ ant) + for (int ant = 0; ant < num_ants; ++ ant) { // Find a new path following the pheromones deposited by the previous ants. print_ant("Round %1% ant %2%", round, ant); @@ -2430,10 +2504,10 @@ static std::vector chain_monotonic_regions( assert(left_neighbors_unprocessed[path.back().region - regions.data()] == 0); assert(validate_unprocessed()); print_ant("\tRegion (%1%:%2%,%3%) (%4%:%5%,%6%)", - path.back().region->left.vline, + path.back().region->left.vline, path.back().flipped ? path.back().region->left.high : path.back().region->left.low, path.back().flipped ? path.back().region->left.low : path.back().region->left.high, - path.back().region->right.vline, + path.back().region->right.vline, path.back().flipped == path.back().region->flips ? path.back().region->right.high : path.back().region->right.low, path.back().flipped == path.back().region->flips ? path.back().region->right.low : path.back().region->right.high); @@ -2512,10 +2586,10 @@ static std::vector chain_monotonic_regions( assert(left_neighbors_unprocessed[next_region - regions.data()] == 1); left_neighbors_unprocessed[next_region - regions.data()] = 0; print_ant("\tRegion (%1%:%2%,%3%) (%4%:%5%,%6%) length to prev %7%", - next_region->left.vline, + next_region->left.vline, next_dir ? next_region->left.high : next_region->left.low, next_dir ? next_region->left.low : next_region->left.high, - next_region->right.vline, + next_region->right.vline, next_dir == next_region->flips ? next_region->right.high : next_region->right.low, next_dir == next_region->flips ? next_region->right.low : next_region->right.high, take_path->link->length); @@ -2542,7 +2616,7 @@ static std::vector chain_monotonic_regions( assert(! path.empty()); float path_length = std::accumulate(path.begin(), path.end() - 1, path.back().region->length(path.back().flipped), - [&path_matrix](const float l, const MonotonicRegionLink &r) { + [&path_matrix](const float l, const MonotonicRegionLink &r) { const MonotonicRegionLink &next = *(&r + 1); return l + r.region->length(r.flipped) + path_matrix(*r.region, r.flipped, *next.region, next.flipped).length; }); @@ -2672,7 +2746,7 @@ static void polylines_from_paths(const std::vector &path, c assert(it->iContour == vline.intersections[inext].iContour); emit_perimeter_segment_on_vertical_line(poly_with_offset, segs, i_vline, it->iContour, it - vline.intersections.data(), inext, *polyline, it->has_left_vertical_up()); it = vline.intersections.data() + inext; - } + } } else { // Going down. assert(it->is_high()); @@ -2691,7 +2765,7 @@ static void polylines_from_paths(const std::vector &path, c assert(it->iContour == vline.intersections[inext].iContour); emit_perimeter_segment_on_vertical_line(poly_with_offset, segs, i_vline, it->iContour, it - vline.intersections.data(), inext, *polyline, it->has_right_vertical_down()); it = vline.intersections.data() + inext; - } + } } if (i_vline == region.right.vline) @@ -2703,7 +2777,7 @@ static void polylines_from_paths(const std::vector &path, c // Find the end of the next overlapping vertical segment. const SegmentedIntersectionLine &vline_right = segs[i_vline + 1]; - const SegmentIntersection *right = going_up ? + const SegmentIntersection *right = going_up ? &vertical_run_top(vline_right, vline_right.intersections[iright]) : &vertical_run_bottom(vline_right, vline_right.intersections[iright]); i_intersection = int(right - vline_right.intersections.data()); @@ -2736,6 +2810,17 @@ static void polylines_from_paths(const std::vector &path, c } } +// The extended bounding box of the whole object that covers any rotation of every layer. +BoundingBox FillRectilinear::extended_object_bounding_box() const { + BoundingBox out = this->bounding_box; + out.merge(Point(out.min.y(), out.min.x())); + out.merge(Point(out.max.y(), out.max.x())); + + // The bounding box is scaled by sqrt(2.) to ensure that the bounding box + // covers any possible rotations. + return out.scaled(sqrt(2.)); +} + bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillParams ¶ms, float angleBase, float pattern_shift, Polylines &polylines_out) { // At the end, only the new polylines will be rotated back. @@ -2755,8 +2840,8 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa // On the polygons of poly_with_offset, the infill lines will be connected. ExPolygonWithOffset poly_with_offset( - surface->expolygon, - - rotate_vector.first, + surface->expolygon, + - rotate_vector.first, float(scale_(this->overlap - (0.5 - INFILL_OVERLAP_OVER_SPACING) * this->spacing)), float(scale_(this->overlap - 0.5f * this->spacing))); if (poly_with_offset.n_contours_inner == 0) { @@ -2765,11 +2850,14 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa return true; } - BoundingBox bounding_box = poly_with_offset.bounding_box_src(); + // For infill that needs to be consistent between layers (like Zig Zag), + // we use bounding box of whole object to match vertical lines between layers. + BoundingBox bounding_box_src = poly_with_offset.bounding_box_src(); + BoundingBox bounding_box = this->has_consistent_pattern() ? this->extended_object_bounding_box() : bounding_box_src; // define flow spacing according to requested density if (params.full_infill() && !params.dont_adjust) { - line_spacing = this->_adjust_solid_spacing(bounding_box.size()(0), line_spacing); + line_spacing = this->_adjust_solid_spacing(bounding_box_src.size().x(), line_spacing); this->spacing = unscale(line_spacing); } else { // extend bounding box so that our pattern will be aligned with other layers @@ -2779,8 +2867,8 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa coord_t pattern_shift_scaled = coord_t(scale_(pattern_shift)) % line_spacing; refpt.x() -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); bounding_box.merge(align_to_grid( - bounding_box.min, - Point(line_spacing, line_spacing), + bounding_box.min, + Point(line_spacing, line_spacing), refpt)); } @@ -2791,6 +2879,14 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa if (params.full_infill()) x0 += (line_spacing + coord_t(SCALED_EPSILON)) / 2; + int gap_line = params.horiz_move / line_spacing; + if (gap_line % 2 == 0) { + x0 += params.horiz_move - gap_line * line_spacing; + } else { + x0 += params.horiz_move - (gap_line - 1) * line_spacing; + n_vlines += 1; + } + #ifdef SLIC3R_DEBUG static int iRun = 0; BoundingBox bbox_svg = poly_with_offset.bounding_box_outer(); @@ -2802,7 +2898,6 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa } iRun ++; #endif /* SLIC3R_DEBUG */ - std::vector segs = slice_region_by_vertical_lines(poly_with_offset, n_vlines, x0, line_spacing); // Connect by horizontal / vertical links, classify the links based on link_max_length as too long. connect_segment_intersections_by_contours(poly_with_offset, segs, params, link_max_length); @@ -2847,8 +2942,9 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa std::vector path = chain_monotonic_regions(regions, poly_with_offset, segs, rng); polylines_from_paths(path, poly_with_offset, segs, polylines_out); } - } else - traverse_graph_generate_polylines(poly_with_offset, params, this->link_max_length, segs, polylines_out); + } else { + traverse_graph_generate_polylines(poly_with_offset, params, segs, this->has_consistent_pattern(), polylines_out); + } #ifdef SLIC3R_DEBUG { @@ -2875,6 +2971,11 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa //FIXME rather simplify the paths to avoid very short edges? //assert(! it->has_duplicate_points()); it->remove_duplicate_points(); + + //get origin direction infill + if (params.symmetric_infill_y_axis) { + it->symmetric_y(params.symmetric_y_axis); + } } #ifdef SLIC3R_DEBUG @@ -3028,11 +3129,11 @@ Polylines FillCubic::fill_surface(const Surface *surface, const FillParams ¶ Polylines polylines_out; coordf_t dx = sqrt(0.5) * z; if (! this->fill_surface_by_multilines( - surface, params, + surface, params, { { 0.f, float(dx) }, { float(M_PI / 3.), - float(dx) }, { float(M_PI * 2. / 3.), float(dx) } }, polylines_out)) BOOST_LOG_TRIVIAL(error) << "FillCubic::fill_surface() failed to fill a region."; - return polylines_out; + return polylines_out; } Polylines FillSupportBase::fill_surface(const Surface *surface, const FillParams ¶ms) @@ -3068,7 +3169,7 @@ Points sample_grid_pattern(const ExPolygon& expolygon, coord_t spacing, const Bo { ExPolygonWithOffset poly_with_offset(expolygon, 0, 0, 0); std::vector segs = slice_region_by_vertical_lines( - poly_with_offset, + poly_with_offset, (global_bounding_box.max.x() - global_bounding_box.min.x() + spacing - 1) / spacing, global_bounding_box.min.x(), spacing); @@ -3210,7 +3311,7 @@ void FillMonotonicLineWGapFill::fill_surface_by_lines(const Surface* surface, co // On the polygons of poly_with_offset, the infill lines will be connected. ExPolygonWithOffset poly_with_offset( surface->expolygon, - - rotate_vector.first, + - rotate_vector.first, float(scale_(0 - (0.5 - INFILL_OVERLAP_OVER_SPACING) * params.flow.spacing())), float(scale_(0 - 0.5f * params.flow.spacing()))); if (poly_with_offset.n_contours_inner == 0) { diff --git a/src/libslic3r/Fill/FillRectilinear.hpp b/src/libslic3r/Fill/FillRectilinear.hpp index cb9d748..c7d6da9 100644 --- a/src/libslic3r/Fill/FillRectilinear.hpp +++ b/src/libslic3r/Fill/FillRectilinear.hpp @@ -15,6 +15,7 @@ public: Fill* clone() const override { return new FillRectilinear(*this); } ~FillRectilinear() override = default; Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + bool is_self_crossing() override { return false; } protected: // Fill by single directional lines, interconnect the lines along perimeters. @@ -27,6 +28,9 @@ protected: float pattern_shift; }; bool fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list &sweep_params, Polylines &polylines_out); + + // The extended bounding box of the whole object that covers any rotation of every layer. + BoundingBox extended_object_bounding_box() const; }; class FillAlignedRectilinear : public FillRectilinear @@ -64,6 +68,7 @@ public: Fill* clone() const override { return new FillGrid(*this); } ~FillGrid() override = default; Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + bool is_self_crossing() override { return true; } protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. @@ -76,6 +81,7 @@ public: Fill* clone() const override { return new FillTriangles(*this); } ~FillTriangles() override = default; Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + bool is_self_crossing() override { return true; } protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. @@ -88,6 +94,7 @@ public: Fill* clone() const override { return new FillStars(*this); } ~FillStars() override = default; Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + bool is_self_crossing() override { return true; } protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. @@ -100,6 +107,7 @@ public: Fill* clone() const override { return new FillCubic(*this); } ~FillCubic() override = default; Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + bool is_self_crossing() override { return true; } protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. @@ -123,6 +131,7 @@ class FillMonotonicLineWGapFill : public Fill public: ~FillMonotonicLineWGapFill() override = default; void fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out) override; + bool is_self_crossing() override { return false; } protected: Fill* clone() const override { return new FillMonotonicLineWGapFill(*this); }; @@ -132,9 +141,28 @@ private: void fill_surface_by_lines(const Surface* surface, const FillParams& params, Polylines& polylines_out); }; -Points sample_grid_pattern(const ExPolygon& expolygon, coord_t spacing, const BoundingBox& global_bounding_box); -Points sample_grid_pattern(const ExPolygons& expolygons, coord_t spacing, const BoundingBox& global_bounding_box); -Points sample_grid_pattern(const Polygons& polygons, coord_t spacing, const BoundingBox& global_bounding_box); +class FillZigZag : public FillRectilinear +{ +public: + Fill* clone() const override { return new FillZigZag(*this); } + ~FillZigZag() override = default; + + bool has_consistent_pattern() const override { return true; } +}; + +class FillCrossZag : public FillRectilinear +{ +public: + Fill *clone() const override { return new FillCrossZag(*this); } + ~FillCrossZag() override = default; + + bool has_consistent_pattern() const override { return true; } +}; + + +Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing, const BoundingBox &global_bounding_box); +Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing, const BoundingBox &global_bounding_box); +Points sample_grid_pattern(const Polygons &polygons, coord_t spacing, const BoundingBox &global_bounding_box); } // namespace Slic3r diff --git a/src/libslic3r/Fill/Lightning/DistanceField.cpp b/src/libslic3r/Fill/Lightning/DistanceField.cpp index 65da94b..b0a702a 100644 --- a/src/libslic3r/Fill/Lightning/DistanceField.cpp +++ b/src/libslic3r/Fill/Lightning/DistanceField.cpp @@ -4,6 +4,7 @@ #include "DistanceField.hpp" //Class we're implementing. #include "../FillRectilinear.hpp" #include "../../ClipperUtils.hpp" +#include "../../Clipper2Utils.hpp" #include @@ -43,7 +44,8 @@ DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outl m_supporting_radius2 = Slic3r::sqr(int64_t(radius)); // Sample source polygons with a regular grid sampling pattern. const BoundingBox overhang_bbox = get_extents(current_overhang); - ExPolygons expolys = offset2_ex(union_ex(current_overhang), -m_cell_size / 2, m_cell_size / 2); // remove dangling lines which causes sample_grid_pattern crash (fails the OUTER_LOW assertions) + // remove dangling lines which causes sample_grid_pattern crash (fails the OUTER_LOW assertions) + ExPolygons expolys = offset2_ex_2(union_ex_2(current_overhang), -m_cell_size / 2, m_cell_size / 2); for (const ExPolygon &expoly : expolys) { const Points sampled_points = sample_grid_pattern(expoly, m_cell_size, overhang_bbox); const size_t unsupported_points_prev_size = m_unsupported_points.size(); diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index cb1acfe..6eb1ce3 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -100,7 +100,7 @@ double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionFloat if (opt->value == 0.) { // If user left option to 0, calculate a sane default width. - auto opt_nozzle_diameters = config.option("nozzle_diameter"); + auto opt_nozzle_diameters = config.option("nozzle_diameter"); if (opt_nozzle_diameters == nullptr) throw_on_missing_variable(opt_key, "nozzle_diameter"); return auto_extrusion_width(opt_key_to_flow_role(opt_key), float(opt_nozzle_diameters->get_at(first_printing_extruder))); diff --git a/src/libslic3r/FlushVolCalc.cpp b/src/libslic3r/FlushVolCalc.cpp index 4705cb5..122223c 100644 --- a/src/libslic3r/FlushVolCalc.cpp +++ b/src/libslic3r/FlushVolCalc.cpp @@ -7,10 +7,10 @@ namespace Slic3r { -const int g_min_flush_volume_from_support = 420.f; +const int g_min_flush_volume_from_support = 700; const int g_flush_volume_to_support = 230; -const int g_max_flush_volume = 800; +const int g_max_flush_volume = 900; static float to_radians(float degree) { @@ -39,60 +39,72 @@ static float DeltaHS_QDS(float h1, float s1, float v1, float h2, float s2, float return std::min(1.2f, dxy); } -FlushVolCalculator::FlushVolCalculator(int min, int max, float multiplier) +FlushVolCalculator::FlushVolCalculator(int min, int max, bool is_multi_extruder, NozzleVolumeType volume_type, float multiplier) :m_min_flush_vol(min), m_max_flush_vol(max), m_multiplier(multiplier) { + if (!is_multi_extruder) { + m_machine_type = FlushPredict::Standard; + return; + } + + if (volume_type == NozzleVolumeType::nvtHighFlow) + m_machine_type = FlushPredict::DualHighFlow; + else + m_machine_type = FlushPredict::DualStandard; +} + +bool FlushVolCalculator::get_flush_vol_from_data(unsigned char src_r, unsigned char src_g, unsigned char src_b, + unsigned char dst_r, unsigned char dst_g, unsigned char dst_b, float& flush) +{ + GenericFlushPredictor pd(m_machine_type); + FlushPredict::RGBColor src(src_r, src_g, src_b); + FlushPredict::RGBColor dst(dst_r, dst_g, dst_b); + + return pd.predict(src, dst, flush); } int FlushVolCalculator::calc_flush_vol_rgb(unsigned char src_r, unsigned char src_g, unsigned char src_b, unsigned char dst_r, unsigned char dst_g, unsigned char dst_b) { - auto& pd = FlushVolPredictor::get_instance(); - float ret_flush_volume = 0; - FlushPredict::RGBColor src(src_r, src_g, src_b); - FlushPredict::RGBColor dst(dst_r, dst_g, dst_b); - bool success = pd.predict(src, dst, ret_flush_volume); - // if we could find the color pair from dataset, we need to recalculate - if (!success) { - float src_r_f, src_g_f, src_b_f, dst_r_f, dst_g_f, dst_b_f; - float from_hsv_h, from_hsv_s, from_hsv_v; - float to_hsv_h, to_hsv_s, to_hsv_v; + float flush_volume; + if(m_machine_type == FlushPredict::Standard && get_flush_vol_from_data(src_r, src_g, src_b, dst_r, dst_g, dst_b, flush_volume)) + return flush_volume; + float src_r_f, src_g_f, src_b_f, dst_r_f, dst_g_f, dst_b_f; + float from_hsv_h, from_hsv_s, from_hsv_v; + float to_hsv_h, to_hsv_s, to_hsv_v; - src_r_f = (float)src_r / 255.f; - src_g_f = (float)src_g / 255.f; - src_b_f = (float)src_b / 255.f; - dst_r_f = (float)dst_r / 255.f; - dst_g_f = (float)dst_g / 255.f; - dst_b_f = (float)dst_b / 255.f; + src_r_f = (float)src_r / 255.f; + src_g_f = (float)src_g / 255.f; + src_b_f = (float)src_b / 255.f; + dst_r_f = (float)dst_r / 255.f; + dst_g_f = (float)dst_g / 255.f; + dst_b_f = (float)dst_b / 255.f; - // Calculate color distance in HSV color space - RGB2HSV(src_r_f, src_g_f,src_b_f, &from_hsv_h, &from_hsv_s, &from_hsv_v); - RGB2HSV(dst_r_f, dst_g_f, dst_b_f, &to_hsv_h, &to_hsv_s, &to_hsv_v); - float hs_dist = DeltaHS_QDS(from_hsv_h, from_hsv_s, from_hsv_v, to_hsv_h, to_hsv_s, to_hsv_v); + // Calculate color distance in HSV color space + RGB2HSV(src_r_f, src_g_f, src_b_f, &from_hsv_h, &from_hsv_s, &from_hsv_v); + RGB2HSV(dst_r_f, dst_g_f, dst_b_f, &to_hsv_h, &to_hsv_s, &to_hsv_v); + float hs_dist = DeltaHS_QDS(from_hsv_h, from_hsv_s, from_hsv_v, to_hsv_h, to_hsv_s, to_hsv_v); - // 1. Color difference is more obvious if the dest color has high luminance - // 2. Color difference is more obvious if the source color has low luminance - float from_lumi = get_luminance(src_r_f, src_g_f, src_b_f); - float to_lumi = get_luminance(dst_r_f, dst_g_f, dst_b_f); - float lumi_flush = 0.f; - if (to_lumi >= from_lumi) { - lumi_flush = std::pow(to_lumi - from_lumi, 0.7f) * 560.f; - } - else { - lumi_flush = (from_lumi - to_lumi) * 80.f; - - float inter_hsv_v = 0.67 * to_hsv_v + 0.33 * from_hsv_v; - hs_dist = std::min(inter_hsv_v, hs_dist); - } - float hs_flush = 230.f * hs_dist; - - float flush_volume = calc_triangle_3rd_edge(hs_flush, lumi_flush, 120.f); - flush_volume = std::max(flush_volume, 60.f); - - ret_flush_volume = flush_volume; + // 1. Color difference is more obvious if the dest color has high luminance + // 2. Color difference is more obvious if the source color has low luminance + float from_lumi = get_luminance(src_r_f, src_g_f, src_b_f); + float to_lumi = get_luminance(dst_r_f, dst_g_f, dst_b_f); + float lumi_flush = 0.f; + if (to_lumi >= from_lumi) { + lumi_flush = std::pow(to_lumi - from_lumi, 0.7f) * 560.f; } + else { + lumi_flush = (from_lumi - to_lumi) * 80.f; - return ret_flush_volume; + float inter_hsv_v = 0.67 * to_hsv_v + 0.33 * from_hsv_v; + hs_dist = std::min(inter_hsv_v, hs_dist); + } + float hs_flush = 230.f * hs_dist; + + flush_volume = calc_triangle_3rd_edge(hs_flush, lumi_flush, 120.f); + flush_volume = std::max(flush_volume, 60.f); + + return flush_volume; } @@ -107,7 +119,19 @@ int FlushVolCalculator::calc_flush_vol(unsigned char src_a, unsigned char src_r, dst_r = dst_g = dst_b = 255; } - float flush_volume = calc_flush_vol_rgb(src_r, src_g, src_b, dst_r, dst_g, dst_b); + float flush_volume; + if(m_machine_type != FlushPredict::Standard && get_flush_vol_from_data(src_r, src_g, src_b, dst_r, dst_g, dst_b, flush_volume)) + return std::min((int)flush_volume, m_max_flush_vol); + + + flush_volume = calc_flush_vol_rgb(src_r, src_g, src_b, dst_r, dst_g, dst_b); + + constexpr float dark_color_thres = 180.f/255.f; + constexpr float light_color_thres = 75.f/255.f; + bool is_from_dark = get_luminance(src_r, src_g, src_b) > dark_color_thres; + bool is_to_light = get_luminance(dst_r, dst_g, dst_b) < light_color_thres; + if (m_machine_type != FlushPredict::Standard && is_from_dark && is_to_light) + flush_volume *= 1.3; flush_volume += m_min_flush_vol; return std::min((int)flush_volume, m_max_flush_vol); diff --git a/src/libslic3r/FlushVolCalc.hpp b/src/libslic3r/FlushVolCalc.hpp index ed9da60..eaa86ba 100644 --- a/src/libslic3r/FlushVolCalc.hpp +++ b/src/libslic3r/FlushVolCalc.hpp @@ -2,8 +2,8 @@ #define slic3r_FlushVolCalc_hpp_ #include "libslic3r.h" -#include "Config.hpp" #include "FlushVolPredictor.hpp" +#include "PrintConfig.hpp" namespace Slic3r { @@ -15,7 +15,7 @@ extern const int g_max_flush_volume; class FlushVolCalculator { public: - FlushVolCalculator(int min, int max, float multiplier = 1.0f); + FlushVolCalculator(int min, int max, bool is_multi_extruder, NozzleVolumeType volume_type, float multiplier = 1.0f); ~FlushVolCalculator() { } @@ -27,10 +27,14 @@ public: int calc_flush_vol_rgb(unsigned char src_r,unsigned char src_g,unsigned char src_b, unsigned char dst_r, unsigned char dst_g, unsigned char dst_b); + bool get_flush_vol_from_data(unsigned char src_r, unsigned char src_g, unsigned char src_b, + unsigned char dst_r, unsigned char dst_g, unsigned char dst_b, float& flush); + private: int m_min_flush_vol; int m_max_flush_vol; float m_multiplier; + FlushPredict::FlushMachineType m_machine_type; }; diff --git a/src/libslic3r/FlushVolPredictor.cpp b/src/libslic3r/FlushVolPredictor.cpp index d980554..6edb053 100644 --- a/src/libslic3r/FlushVolPredictor.cpp +++ b/src/libslic3r/FlushVolPredictor.cpp @@ -167,6 +167,20 @@ namespace FlushPredict } +class FlushVolPredictor +{ + using RGB = FlushPredict::RGBColor; +public: + bool predict(const RGB& from,const RGB& to , float& flush); + FlushVolPredictor(const std::string& data_file); + FlushVolPredictor() = default; +private: + uint64_t generate_hash_key(const RGB& from, const RGB& to); + std::unordered_map m_flush_map; + std::vector m_colors; + bool m_valid{ false }; +}; + uint64_t FlushVolPredictor::generate_hash_key(const RGB& from, const RGB& to) { uint64_t key = 0; @@ -284,9 +298,32 @@ bool FlushVolPredictor::predict(const RGB& from, const RGB& to, float& flush) return true; } -FlushVolPredictor& FlushVolPredictor::get_instance() + +static std::unordered_map predictor_instances; + +GenericFlushPredictor::GenericFlushPredictor(const MachineType& type) { - static std::string prefix = Slic3r::resources_dir(); - static FlushVolPredictor instance(prefix + "/flush/flush_data.txt"); - return instance; + auto iter = predictor_instances.find(type); + if (iter != predictor_instances.end()) + predictor = &iter->second; + else { + std::string path = Slic3r::resources_dir(); + if (type == MachineType::DualHighFlow) + path += "/flush/flush_data_dual_highflow.txt"; + else if (type == MachineType::DualStandard) + path += "/flush/flush_data_dual_standard.txt"; + else + path += "/flush/flush_data_standard.txt"; + predictor_instances[type] = FlushVolPredictor(path); + + predictor = &predictor_instances[type]; + } +} + + +bool GenericFlushPredictor::predict(const RGB& from, const RGB& to, float& flush) +{ + if (!predictor) + return false; + return predictor->predict(from, to, flush); } diff --git a/src/libslic3r/FlushVolPredictor.hpp b/src/libslic3r/FlushVolPredictor.hpp index 378a608..3d03766 100644 --- a/src/libslic3r/FlushVolPredictor.hpp +++ b/src/libslic3r/FlushVolPredictor.hpp @@ -8,6 +8,14 @@ namespace FlushPredict { + enum FlushMachineType + { + Standard, + DualStandard, + DualHighFlow + }; + + struct RGBColor { unsigned char r{ 0 }; @@ -35,25 +43,17 @@ namespace FlushPredict } +class FlushVolPredictor; -// Singleton pattern -class FlushVolPredictor +class GenericFlushPredictor { using RGB = FlushPredict::RGBColor; + using MachineType = FlushPredict::FlushMachineType; public: - bool predict(const RGB& from,const RGB& to , float& flush); - static FlushVolPredictor& get_instance(); + explicit GenericFlushPredictor(const MachineType& type); + bool predict(const RGB& from, const RGB& to, float& flush); private: - FlushVolPredictor(const std::string& data_file); - FlushVolPredictor(const FlushVolPredictor&) = delete; - FlushVolPredictor& operator=(const FlushVolPredictor&) = delete; - ~FlushVolPredictor() = default; - - uint64_t generate_hash_key(const RGB& from, const RGB& to); -private: - std::unordered_map m_flush_map; - std::vector m_colors; - bool m_valid; + FlushVolPredictor* predictor{ nullptr }; }; diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index aaac86c..08a2b88 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -66,7 +66,7 @@ struct AMFParserContext { AMFParserContext(XML_Parser parser, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model) : m_parser(parser), - m_model(*model), + m_model(*model), m_config(config), m_config_substitutions(config_substitutions) { @@ -112,7 +112,7 @@ struct AMFParserContext static void XMLCALL characters(void *userData, const XML_Char *s, int len) { AMFParserContext *ctx = (AMFParserContext*)userData; - ctx->characters(s, len); + ctx->characters(s, len); } static const char* get_attribute(const char **atts, const char *id) { @@ -326,7 +326,7 @@ void AMFParserContext::startElement(const char *name, const char **atts) this->stop(); else { m_object_instances_map[object_id].instances.push_back(AMFParserContext::Instance()); - m_instance = &m_object_instances_map[object_id].instances.back(); + m_instance = &m_object_instances_map[object_id].instances.back(); node_type_new = NODE_TYPE_INSTANCE; } } @@ -347,7 +347,7 @@ void AMFParserContext::startElement(const char *name, const char **atts) else { // It means that data was saved in old version (2.2.0 and older) of PrusaSlicer - // read old data ... + // read old data ... std::string gcode = get_attribute(atts, "gcode"); // ... and interpret them to the new data CustomGCode::Type type= gcode == "M600" ? CustomGCode::ColorChange : @@ -379,7 +379,7 @@ void AMFParserContext::startElement(const char *name, const char **atts) else if (m_path[2] == NODE_TYPE_INSTANCE) { assert(m_instance); if (strcmp(name, "deltax") == 0) - node_type_new = NODE_TYPE_DELTAX; + node_type_new = NODE_TYPE_DELTAX; else if (strcmp(name, "deltay") == 0) node_type_new = NODE_TYPE_DELTAY; else if (strcmp(name, "deltaz") == 0) @@ -415,7 +415,7 @@ void AMFParserContext::startElement(const char *name, const char **atts) case 4: if (m_path[3] == NODE_TYPE_VERTICES) { if (strcmp(name, "vertex") == 0) - node_type_new = NODE_TYPE_VERTEX; + node_type_new = NODE_TYPE_VERTEX; } else if (m_path[3] == NODE_TYPE_VOLUME) { if (strcmp(name, "metadata") == 0) { const char *type = get_attribute(atts, "type"); @@ -436,7 +436,7 @@ void AMFParserContext::startElement(const char *name, const char **atts) case 5: if (strcmp(name, "coordinates") == 0) { if (m_path[4] == NODE_TYPE_VERTEX) { - node_type_new = NODE_TYPE_COORDINATES; + node_type_new = NODE_TYPE_COORDINATES; } else this->stop(); } else if (name[0] == 'v' && name[1] >= '1' && name[1] <= '3' && name[2] == 0) { @@ -663,7 +663,7 @@ void AMFParserContext::endElement(const char * /* name */) m_volume->source.volume_idx = (int)m_model.objects.back()->volumes.size() - 1; m_volume->center_geometry_after_creation(); } else - // pass false if the mesh offset has been already taken from the data + // pass false if the mesh offset has been already taken from the data m_volume->center_geometry_after_creation(m_volume->source.input_file.empty()); m_volume->calculate_convex_hull(); @@ -778,7 +778,7 @@ void AMFParserContext::endElement(const char * /* name */) } m_object->sla_points_status = sla::PointsStatus::UserModified; } - else if (m_path.size() == 5 && m_path[1] == NODE_TYPE_OBJECT && m_path[3] == NODE_TYPE_RANGE && + else if (m_path.size() == 5 && m_path[1] == NODE_TYPE_OBJECT && m_path[3] == NODE_TYPE_RANGE && m_object && strcmp(opt_key, "layer_height_range") == 0) { // Parse object's layer_height_range, a semicolon separated doubles. char* p = m_value[1].data(); @@ -824,7 +824,7 @@ void AMFParserContext::endElement(const char * /* name */) m_volume->source.is_converted_from_meters = m_value[1] == "1"; } } - } + } else */if (m_path.size() == 3) { if (m_path[1] == NODE_TYPE_MATERIAL) { if (m_material) @@ -930,8 +930,9 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, ConfigSubstitut if (result) ctx.endDocument(); - - *use_inches = ctx.m_use_inches; + if (use_inches) { + *use_inches = ctx.m_use_inches; + } for (ModelObject* o : model->objects) { @@ -1009,7 +1010,9 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi } ctx.endDocument(); - *use_inches = ctx.m_use_inches; + if (use_inches) { + *use_inches = ctx.m_use_inches; + } //if (check_version && (ctx.m_version > VERSION_AMF_COMPATIBLE)) //{ // std::string msg = _(L("The selected amf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible.")); @@ -1326,7 +1329,7 @@ bool load_amf(const char *path, DynamicPrintConfig *config, ConfigSubstitutionCo for (const CustomGCode::Item& code : model->custom_gcode_per_print_z.gcodes) { pt::ptree& code_tree = main_tree.add("code", ""); - // store custom_gcode_per_print_z gcodes information + // store custom_gcode_per_print_z gcodes information code_tree.put(".print_z" , code.print_z ); code_tree.put(".type" , static_cast(code.type)); code_tree.put(".extruder" , code.extruder ); @@ -1337,14 +1340,14 @@ bool load_amf(const char *path, DynamicPrintConfig *config, ConfigSubstitutionCo std::string gcode = //code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") : code.type == CustomGCode::PausePrint ? config->opt_string("machine_pause_gcode") : code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") : - code.type == CustomGCode::ToolChange ? "tool_change" : code.extra; + code.type == CustomGCode::ToolChange ? "tool_change" : code.extra; code_tree.put(".gcode" , gcode ); } pt::ptree& mode_tree = main_tree.add("mode", ""); - // store mode of a custom_gcode_per_print_z - mode_tree.put(".value", - model->custom_gcode_per_print_z.mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode : + // store mode of a custom_gcode_per_print_z + mode_tree.put(".value", + model->custom_gcode_per_print_z.mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode : model->custom_gcode_per_print_z.mode == CustomGCode::Mode::MultiAsSingle ? CustomGCode::MultiAsSingleMode : CustomGCode::MultiExtruderMode); diff --git a/src/libslic3r/Format/OBJ.cpp b/src/libslic3r/Format/OBJ.cpp index 634d29d..d54a66f 100644 --- a/src/libslic3r/Format/OBJ.cpp +++ b/src/libslic3r/Format/OBJ.cpp @@ -22,7 +22,7 @@ namespace Slic3r { -bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::string &message) +bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo &obj_info, std::string &message, bool gamma_correct) { if (meshptr == nullptr) return false; @@ -115,8 +115,13 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s size_t j = i * OBJ_VERTEX_LENGTH; its.vertices.emplace_back(data.coordinates[j], data.coordinates[j + 1], data.coordinates[j + 2]); if (data.has_vertex_color) { - RGBA color{std::clamp(data.coordinates[j + 3], 0.f, 1.f), std::clamp(data.coordinates[j + 4], 0.f, 1.f), std::clamp(data.coordinates[j + 5], 0.f, 1.f), - std::clamp(data.coordinates[j + 6], 0.f, 1.f)}; + RGBA color{data.coordinates[j + 3], data.coordinates[j + 4], data.coordinates[j + 5],data.coordinates[j + 6]}; + if (gamma_correct) { + ColorRGBA::gamma_correct(color); + } + for (int i = 0; i < color.size(); i++) { + color[i] = std::clamp(color[i], 0.f, 1.f); + } obj_info.vertex_colors.emplace_back(color); } } @@ -147,24 +152,18 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s its.indices.emplace_back(indices[0], indices[1], indices[2]); int face_index =its.indices.size() - 1; RGBA face_color; - auto set_face_color = [&uvs, &data, &mtl_data, &obj_info, &face_color](int face_index, const std::string mtl_name) { + auto set_face_color = [&uvs, &data, &mtl_data, &obj_info, &face_color, &gamma_correct](int face_index, const std::string mtl_name) { if (mtl_data.new_mtl_unmap.find(mtl_name) != mtl_data.new_mtl_unmap.end()) { - bool is_merge_ka_kd = true; - for (size_t n = 0; n < 3; n++) { - if (float(mtl_data.new_mtl_unmap[mtl_name]->Ka[n] + mtl_data.new_mtl_unmap[mtl_name]->Kd[n]) > 1.0) { - is_merge_ka_kd=false; - break; + for (size_t n = 0; n < 3; n++) {//0.1 is light ambient + float object_ka = 0.f; + if (mtl_data.new_mtl_unmap[mtl_name]->Ka[n] > 0.01 && mtl_data.new_mtl_unmap[mtl_name]->Ka[n] < 0.99) { + object_ka = mtl_data.new_mtl_unmap[mtl_name]->Ka[n] * 0.1; } + auto value = object_ka + float(mtl_data.new_mtl_unmap[mtl_name]->Kd[n]); + float temp = gamma_correct ? ColorRGBA::gamma_correct(value) : value; + face_color[n] = std::clamp(temp, 0.f, 1.f); } - for (size_t n = 0; n < 3; n++) { - if (is_merge_ka_kd) { - face_color[n] = std::clamp(float(mtl_data.new_mtl_unmap[mtl_name]->Ka[n] + mtl_data.new_mtl_unmap[mtl_name]->Kd[n]), 0.f, 1.f); - } - else { - face_color[n] = std::clamp(float(mtl_data.new_mtl_unmap[mtl_name]->Kd[n]), 0.f, 1.f); - } - } - face_color[3] = mtl_data.new_mtl_unmap[mtl_name]->Tr; // alpha + face_color[3] = gamma_correct ? ColorRGBA::gamma_correct(mtl_data.new_mtl_unmap[mtl_name]->Tr) : mtl_data.new_mtl_unmap[mtl_name]->Tr; // alpha if (mtl_data.new_mtl_unmap[mtl_name]->map_Kd.size() > 0) { auto png_name = mtl_data.new_mtl_unmap[mtl_name]->map_Kd; obj_info.has_uv_png = true; @@ -180,6 +179,11 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s } obj_info.face_colors.emplace_back(face_color); } + else { + if (obj_info.lost_material_name.empty()) { + obj_info.lost_material_name = mtl_name; + } + } }; auto set_face_color_by_mtl = [&data, &set_face_color](int face_index) { if (data.usemtls.size() == 1) { @@ -218,11 +222,11 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s return true; } -bool load_obj(const char *path, Model *model, ObjInfo& obj_info, std::string &message, const char *object_name_in) +bool load_obj(const char *path, Model *model, ObjInfo& obj_info, std::string &message, const char *object_name_in,bool gamma_correct) { TriangleMesh mesh; - bool ret = load_obj(path, &mesh, obj_info, message); + bool ret = load_obj(path, &mesh, obj_info, message, gamma_correct); if (ret) { std::string object_name; diff --git a/src/libslic3r/Format/OBJ.hpp b/src/libslic3r/Format/OBJ.hpp index 7fccb6f..a1dd206 100644 --- a/src/libslic3r/Format/OBJ.hpp +++ b/src/libslic3r/Format/OBJ.hpp @@ -8,13 +8,12 @@ namespace Slic3r { class TriangleMesh; class Model; class ModelObject; -typedef std::function &input_colors, bool is_single_color, std::vector &filament_ids, unsigned char &first_extruder_id, - std::string& ml_region, std::string& ml_name, std::string& ml_id)> ObjImportColorFn; // Load an OBJ file into a provided model. struct ObjInfo { std::vector vertex_colors; std::vector face_colors; bool is_single_mtl{false}; + std::string lost_material_name{""}; std::vector> uvs; std::string obj_dircetory; std::map pngs; @@ -25,8 +24,24 @@ struct ObjInfo { std::string ml_name; std::string ml_id; }; -extern bool load_obj(const char *path, TriangleMesh *mesh, ObjInfo &vertex_colors, std::string &message); -extern bool load_obj(const char *path, Model *model, ObjInfo &vertex_colors, std::string &message, const char *object_name = nullptr); +struct ObjDialogInOut +{ // input:colors array + std::vector input_colors; + bool is_single_color{false}; + // colors array output: + std::vector filament_ids; + unsigned char first_extruder_id; + bool deal_vertex_color; + Model * model{nullptr}; + // ml + std::string ml_region; + std::string ml_name; + std::string ml_id; + std::string lost_material_name{""}; +}; +typedef std::function ObjImportColorFn; +extern bool load_obj(const char *path, TriangleMesh *mesh, ObjInfo &vertex_colors, std::string &message, bool gamma_correct =false); +extern bool load_obj(const char *path, Model *model, ObjInfo &vertex_colors, std::string &message, const char *object_name = nullptr, bool gamma_correct =false); extern bool store_obj(const char *path, TriangleMesh *mesh); extern bool store_obj(const char *path, ModelObject *model); diff --git a/src/libslic3r/Format/STEP.cpp b/src/libslic3r/Format/STEP.cpp index fe7b932..e057345 100644 --- a/src/libslic3r/Format/STEP.cpp +++ b/src/libslic3r/Format/STEP.cpp @@ -1,6 +1,7 @@ #include "../libslic3r.h" #include "../Model.hpp" #include "../TriangleMesh.hpp" +#include "libslic3r/Thread.hpp" #include "STEP.hpp" @@ -225,189 +226,189 @@ static void getNamedSolids(const TopLoc_Location& location, } } -bool load_step(const char *path, Model *model, bool& is_cancel, - double linear_defletion/*=0.003*/, - double angle_defletion/*= 0.5*/, - bool isSplitCompound, - ImportStepProgressFn stepFn, StepIsUtf8Fn isUtf8Fn, long& mesh_face_num) -{ - bool cb_cancel = false; - if (stepFn) { - stepFn(LOAD_STEP_STAGE_READ_FILE, 0, 1, cb_cancel); - is_cancel = cb_cancel; - if (cb_cancel) { - return false; - } - } - - if (!StepPreProcessor::isUtf8File(path) && isUtf8Fn) - isUtf8Fn(false); - std::string file_after_preprocess = std::string(path); - - std::vector namedSolids; - Handle(TDocStd_Document) document; - Handle(XCAFApp_Application) application = XCAFApp_Application::GetApplication(); - application->NewDocument(file_after_preprocess.c_str(), document); - STEPCAFControl_Reader reader; - reader.SetNameMode(true); - //QDS: Todo, read file is slow which cause the progress_bar no update and gui no response - IFSelect_ReturnStatus stat = reader.ReadFile(file_after_preprocess.c_str()); - if (stat != IFSelect_RetDone || !reader.Transfer(document)) { - application->Close(document); - throw std::logic_error{ std::string{"Could not read '"} + path + "'" }; - return false; - } - Handle(XCAFDoc_ShapeTool) shapeTool = XCAFDoc_DocumentTool::ShapeTool(document->Main()); - TDF_LabelSequence topLevelShapes; - shapeTool->GetFreeShapes(topLevelShapes); - - unsigned int id{1}; - Standard_Integer topShapeLength = topLevelShapes.Length() + 1; - auto stage_unit2 = topShapeLength / LOAD_STEP_STAGE_UNIT_NUM + 1; - - for (Standard_Integer iLabel = 1; iLabel < topShapeLength; ++iLabel) { - if (stepFn) { - if ((iLabel % stage_unit2) == 0) { - stepFn(LOAD_STEP_STAGE_GET_SOLID, iLabel, topShapeLength, cb_cancel); - is_cancel = cb_cancel; - } - if (cb_cancel) { - shapeTool.reset(nullptr); - application->Close(document); - return false; - } - } - getNamedSolids(TopLoc_Location{}, "", id, shapeTool, topLevelShapes.Value(iLabel), namedSolids, isSplitCompound); - } - - std::vector stl; - stl.resize(namedSolids.size()); - tbb::parallel_for(tbb::blocked_range(0, namedSolids.size()), [&](const tbb::blocked_range &range) { - for (size_t i = range.begin(); i < range.end(); i++) { - BRepMesh_IncrementalMesh mesh(namedSolids[i].solid, linear_defletion, false, angle_defletion, true); - // QDS: calculate total number of the nodes and triangles - int aNbNodes = 0; - int aNbTriangles = 0; - for (TopExp_Explorer anExpSF(namedSolids[i].solid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) { - TopLoc_Location aLoc; - Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(anExpSF.Current()), aLoc); - if (!aTriangulation.IsNull()) { - aNbNodes += aTriangulation->NbNodes(); - aNbTriangles += aTriangulation->NbTriangles(); - } - } - - if (aNbTriangles == 0 || aNbNodes == 0) - // QDS: No triangulation on the shape. - continue; - - stl[i].stats.type = inmemory; - stl[i].stats.number_of_facets = (uint32_t) aNbTriangles; - stl[i].stats.original_num_facets = stl[i].stats.number_of_facets; - stl_allocate(&stl[i]); - - std::vector points; - points.reserve(aNbNodes); - // QDS: count faces missing triangulation - Standard_Integer aNbFacesNoTri = 0; - // QDS: fill temporary triangulation - Standard_Integer aNodeOffset = 0; - Standard_Integer aTriangleOffet = 0; - for (TopExp_Explorer anExpSF(namedSolids[i].solid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) { - const TopoDS_Shape &aFace = anExpSF.Current(); - TopLoc_Location aLoc; - Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(aFace), aLoc); - if (aTriangulation.IsNull()) { - ++aNbFacesNoTri; - continue; - } - // QDS: copy nodes - gp_Trsf aTrsf = aLoc.Transformation(); - for (Standard_Integer aNodeIter = 1; aNodeIter <= aTriangulation->NbNodes(); ++aNodeIter) { - gp_Pnt aPnt = aTriangulation->Node(aNodeIter); - aPnt.Transform(aTrsf); - points.emplace_back(std::move(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z()))); - } - // QDS: copy triangles - const TopAbs_Orientation anOrientation = anExpSF.Current().Orientation(); - Standard_Integer anId[3] = {}; - for (Standard_Integer aTriIter = 1; aTriIter <= aTriangulation->NbTriangles(); ++aTriIter) { - Poly_Triangle aTri = aTriangulation->Triangle(aTriIter); - - aTri.Get(anId[0], anId[1], anId[2]); - if (anOrientation == TopAbs_REVERSED) - std::swap(anId[1], anId[2]); - // QDS: save triangles facets - stl_facet facet; - facet.vertex[0] = points[anId[0] + aNodeOffset - 1].cast(); - facet.vertex[1] = points[anId[1] + aNodeOffset - 1].cast(); - facet.vertex[2] = points[anId[2] + aNodeOffset - 1].cast(); - facet.extra[0] = 0; - facet.extra[1] = 0; - stl_normal normal; - stl_calculate_normal(normal, &facet); - stl_normalize_vector(normal); - facet.normal = normal; - stl[i].facet_start[aTriangleOffet + aTriIter - 1] = facet; - } - - aNodeOffset += aTriangulation->NbNodes(); - aTriangleOffet += aTriangulation->NbTriangles(); - } - } - }); - - if (mesh_face_num != -1) { - for (size_t i = 0; i < stl.size(); i++) { - // Test for overflow - mesh_face_num += stl[i].stats.number_of_facets; - } - return true; - } - - ModelObject *new_object = model->add_object(); - const char * last_slash = strrchr(path, DIR_SEPARATOR); - new_object->name.assign((last_slash == nullptr) ? path : last_slash + 1); - new_object->input_file = path; - - auto stage_unit3 = stl.size() / LOAD_STEP_STAGE_UNIT_NUM + 1; - for (size_t i = 0; i < stl.size(); i++) { - if (stepFn) { - if ((i % stage_unit3) == 0) { - stepFn(LOAD_STEP_STAGE_GET_MESH, i, stl.size(), cb_cancel); - is_cancel = cb_cancel; - } - if (cb_cancel) { - model->delete_object(new_object); - shapeTool.reset(nullptr); - application->Close(document); - return false; - } - } - - //QDS: maybe mesh is empty from step file. Don't add - if (stl[i].stats.number_of_facets > 0) { - TriangleMesh triangle_mesh; - triangle_mesh.from_stl(stl[i]); - ModelVolume* new_volume = new_object->add_volume(std::move(triangle_mesh)); - new_volume->name = namedSolids[i].name; - new_volume->source.input_file = path; - new_volume->source.object_idx = (int)model->objects.size() - 1; - new_volume->source.volume_idx = (int)new_object->volumes.size() - 1; - } - } - - shapeTool.reset(nullptr); - application->Close(document); - - //QDS: no valid shape from the step, delete the new object as well - if (new_object->volumes.size() == 0) { - model->delete_object(new_object); - return false; - } - - return true; -} +//bool load_step(const char *path, Model *model, bool& is_cancel, +// double linear_defletion/*=0.003*/, +// double angle_defletion/*= 0.5*/, +// bool isSplitCompound, +// ImportStepProgressFn stepFn, StepIsUtf8Fn isUtf8Fn, long& mesh_face_num) +//{ +// bool cb_cancel = false; +// if (stepFn) { +// stepFn(LOAD_STEP_STAGE_READ_FILE, 0, 1, cb_cancel); +// is_cancel = cb_cancel; +// if (cb_cancel) { +// return false; +// } +// } +// +// if (!StepPreProcessor::isUtf8File(path) && isUtf8Fn) +// isUtf8Fn(false); +// std::string file_after_preprocess = std::string(path); +// +// std::vector namedSolids; +// Handle(TDocStd_Document) document; +// Handle(XCAFApp_Application) application = XCAFApp_Application::GetApplication(); +// application->NewDocument(file_after_preprocess.c_str(), document); +// STEPCAFControl_Reader reader; +// reader.SetNameMode(true); +// //QDS: Todo, read file is slow which cause the progress_bar no update and gui no response +// IFSelect_ReturnStatus stat = reader.ReadFile(file_after_preprocess.c_str()); +// if (stat != IFSelect_RetDone || !reader.Transfer(document)) { +// application->Close(document); +// throw std::logic_error{ std::string{"Could not read '"} + path + "'" }; +// return false; +// } +// Handle(XCAFDoc_ShapeTool) shapeTool = XCAFDoc_DocumentTool::ShapeTool(document->Main()); +// TDF_LabelSequence topLevelShapes; +// shapeTool->GetFreeShapes(topLevelShapes); +// +// unsigned int id{1}; +// Standard_Integer topShapeLength = topLevelShapes.Length() + 1; +// auto stage_unit2 = topShapeLength / LOAD_STEP_STAGE_UNIT_NUM + 1; +// +// for (Standard_Integer iLabel = 1; iLabel < topShapeLength; ++iLabel) { +// if (stepFn) { +// if ((iLabel % stage_unit2) == 0) { +// stepFn(LOAD_STEP_STAGE_GET_SOLID, iLabel, topShapeLength, cb_cancel); +// is_cancel = cb_cancel; +// } +// if (cb_cancel) { +// shapeTool.reset(nullptr); +// application->Close(document); +// return false; +// } +// } +// getNamedSolids(TopLoc_Location{}, "", id, shapeTool, topLevelShapes.Value(iLabel), namedSolids, isSplitCompound); +// } +// +// std::vector stl; +// stl.resize(namedSolids.size()); +// tbb::parallel_for(tbb::blocked_range(0, namedSolids.size()), [&](const tbb::blocked_range &range) { +// for (size_t i = range.begin(); i < range.end(); i++) { +// BRepMesh_IncrementalMesh mesh(namedSolids[i].solid, linear_defletion, false, angle_defletion, true); +// // QDS: calculate total number of the nodes and triangles +// int aNbNodes = 0; +// int aNbTriangles = 0; +// for (TopExp_Explorer anExpSF(namedSolids[i].solid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) { +// TopLoc_Location aLoc; +// Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(anExpSF.Current()), aLoc); +// if (!aTriangulation.IsNull()) { +// aNbNodes += aTriangulation->NbNodes(); +// aNbTriangles += aTriangulation->NbTriangles(); +// } +// } +// +// if (aNbTriangles == 0 || aNbNodes == 0) +// // QDS: No triangulation on the shape. +// continue; +// +// stl[i].stats.type = inmemory; +// stl[i].stats.number_of_facets = (uint32_t) aNbTriangles; +// stl[i].stats.original_num_facets = stl[i].stats.number_of_facets; +// stl_allocate(&stl[i]); +// +// std::vector points; +// points.reserve(aNbNodes); +// // QDS: count faces missing triangulation +// Standard_Integer aNbFacesNoTri = 0; +// // QDS: fill temporary triangulation +// Standard_Integer aNodeOffset = 0; +// Standard_Integer aTriangleOffet = 0; +// for (TopExp_Explorer anExpSF(namedSolids[i].solid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) { +// const TopoDS_Shape &aFace = anExpSF.Current(); +// TopLoc_Location aLoc; +// Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(aFace), aLoc); +// if (aTriangulation.IsNull()) { +// ++aNbFacesNoTri; +// continue; +// } +// // QDS: copy nodes +// gp_Trsf aTrsf = aLoc.Transformation(); +// for (Standard_Integer aNodeIter = 1; aNodeIter <= aTriangulation->NbNodes(); ++aNodeIter) { +// gp_Pnt aPnt = aTriangulation->Node(aNodeIter); +// aPnt.Transform(aTrsf); +// points.emplace_back(std::move(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z()))); +// } +// // QDS: copy triangles +// const TopAbs_Orientation anOrientation = anExpSF.Current().Orientation(); +// Standard_Integer anId[3] = {}; +// for (Standard_Integer aTriIter = 1; aTriIter <= aTriangulation->NbTriangles(); ++aTriIter) { +// Poly_Triangle aTri = aTriangulation->Triangle(aTriIter); +// +// aTri.Get(anId[0], anId[1], anId[2]); +// if (anOrientation == TopAbs_REVERSED) +// std::swap(anId[1], anId[2]); +// // QDS: save triangles facets +// stl_facet facet; +// facet.vertex[0] = points[anId[0] + aNodeOffset - 1].cast(); +// facet.vertex[1] = points[anId[1] + aNodeOffset - 1].cast(); +// facet.vertex[2] = points[anId[2] + aNodeOffset - 1].cast(); +// facet.extra[0] = 0; +// facet.extra[1] = 0; +// stl_normal normal; +// stl_calculate_normal(normal, &facet); +// stl_normalize_vector(normal); +// facet.normal = normal; +// stl[i].facet_start[aTriangleOffet + aTriIter - 1] = facet; +// } +// +// aNodeOffset += aTriangulation->NbNodes(); +// aTriangleOffet += aTriangulation->NbTriangles(); +// } +// } +// }); +// +// if (mesh_face_num != -1) { +// for (size_t i = 0; i < stl.size(); i++) { +// // Test for overflow +// mesh_face_num += stl[i].stats.number_of_facets; +// } +// return true; +// } +// +// ModelObject *new_object = model->add_object(); +// const char * last_slash = strrchr(path, DIR_SEPARATOR); +// new_object->name.assign((last_slash == nullptr) ? path : last_slash + 1); +// new_object->input_file = path; +// +// auto stage_unit3 = stl.size() / LOAD_STEP_STAGE_UNIT_NUM + 1; +// for (size_t i = 0; i < stl.size(); i++) { +// if (stepFn) { +// if ((i % stage_unit3) == 0) { +// stepFn(LOAD_STEP_STAGE_GET_MESH, i, stl.size(), cb_cancel); +// is_cancel = cb_cancel; +// } +// if (cb_cancel) { +// model->delete_object(new_object); +// shapeTool.reset(nullptr); +// application->Close(document); +// return false; +// } +// } +// +// //QDS: maybe mesh is empty from step file. Don't add +// if (stl[i].stats.number_of_facets > 0) { +// TriangleMesh triangle_mesh; +// triangle_mesh.from_stl(stl[i]); +// ModelVolume* new_volume = new_object->add_volume(std::move(triangle_mesh)); +// new_volume->name = namedSolids[i].name; +// new_volume->source.input_file = path; +// new_volume->source.object_idx = (int)model->objects.size() - 1; +// new_volume->source.volume_idx = (int)new_object->volumes.size() - 1; +// } +// } +// +// shapeTool.reset(nullptr); +// application->Close(document); +// +// //QDS: no valid shape from the step, delete the new object as well +// if (new_object->volumes.size() == 0) { +// model->delete_object(new_object); +// return false; +// } +// +// return true; +//} Step::Step(fs::path path, ImportStepProgressFn stepFn, StepIsUtf8Fn isUtf8Fn): m_stepFn(stepFn), @@ -425,30 +426,257 @@ Step::Step(std::string path, ImportStepProgressFn stepFn, StepIsUtf8Fn isUtf8Fn) m_app->NewDocument(TCollection_ExtendedString("BinXCAF"), m_doc); } -bool Step::load() +Step::~Step() +{ + m_app->Close(m_doc); +} + +void Step::update_process(int load_stage, int current, int total, bool& cancel) +{ + if (m_stepFn) { + m_stepFn(load_stage, current, total, cancel); + } +} + +Step::Step_Status Step::load() { if (!StepPreProcessor::isUtf8File(m_path.c_str()) && m_utf8Fn) { m_utf8Fn(false); - return false; + return Step_Status::LOAD_ERROR; + } + std::atomic stop_load_flag = false; + Handle(StepProgressIncdicator) incdicator = new StepProgressIncdicator(stop_load_flag); + bool task_result = false; + bool cb_cancel = false; + int progress = 0; + bool load_result = false; + auto task = new boost::thread(Slic3r::create_thread([&]() -> void { + + STEPCAFControl_Reader reader; + reader.SetNameMode(true); + IFSelect_ReturnStatus stat = reader.ReadFile(m_path.c_str()); + if (cb_cancel) return; + progress = 3; + if (stat != IFSelect_RetDone || !reader.Transfer(m_doc, incdicator->Start())) { + load_result = false; + task_result = true; + return; + } + if (cb_cancel) return; + progress = 6; + m_shape_tool = XCAFDoc_DocumentTool::ShapeTool(m_doc->Main()); + TDF_LabelSequence topLevelShapes; + m_shape_tool->GetFreeShapes(topLevelShapes); + unsigned int id{ 1 }; + Standard_Integer topShapeLength = topLevelShapes.Length() + 1; + for (Standard_Integer iLabel = 1; iLabel < topShapeLength; ++iLabel) { + if (cb_cancel) return; + getNamedSolids(TopLoc_Location{}, "", id, m_shape_tool, topLevelShapes.Value(iLabel), m_name_solids); + } + progress = 10; + load_result = true; + task_result = true; + })); + while (!task_result) { + boost::this_thread::sleep_for(boost::chrono::milliseconds(200)); + update_process(LOAD_STEP_STAGE_READ_FILE, progress, 10, cb_cancel); + if (cb_cancel) { + stop_load_flag.store(true); + if (task) { + if (task->joinable()) { + task->join(); + delete task; + } + } + return Step_Status::CANCEL; + } + } + if (task){ + if (task->joinable()) { + task->join(); + delete task; + } + } + if (load_result) { + return Step_Status::LOAD_SUCCESS; + }else { + return Step_Status::LOAD_ERROR; + } + +} + +Step::Step_Status Step::mesh(Model* model, + bool& is_cancel, + bool isSplitCompound, + double linear_defletion/*=0.003*/, + double angle_defletion/*= 0.5*/) + +{ + bool task_result = false; + bool cb_cancel = false; + float progress = .0; + std::atomic meshed_solid_num = 0; + std::vector namedSolids; + float progress_2 = .0; + ModelObject* new_object = model->add_object(); + const char* last_slash = strrchr(m_path.c_str(), DIR_SEPARATOR); + new_object->name.assign((last_slash == nullptr) ? m_path.c_str() : last_slash + 1); + new_object->input_file = m_path.c_str(); + + auto task = new boost::thread(Slic3r::create_thread([&]() -> void { + TDF_LabelSequence topLevelShapes; + m_shape_tool->GetFreeShapes(topLevelShapes); + unsigned int id{ 1 }; + Standard_Integer topShapeLength = topLevelShapes.Length() + 1; + + for (Standard_Integer iLabel = 1; iLabel < topShapeLength; ++iLabel) { + progress = static_cast(iLabel) / (topShapeLength-1); + if (cb_cancel) { + return; + } + getNamedSolids(TopLoc_Location{}, "", id, m_shape_tool, topLevelShapes.Value(iLabel), namedSolids, isSplitCompound); + } + + std::vector stl; + stl.resize(namedSolids.size()); + tbb::parallel_for(tbb::blocked_range(0, namedSolids.size()), [&](const tbb::blocked_range& range) { + for (size_t i = range.begin(); i < range.end(); i++) { + BRepMesh_IncrementalMesh mesh(namedSolids[i].solid, linear_defletion, false, angle_defletion, true); + // QDS: calculate total number of the nodes and triangles + int aNbNodes = 0; + int aNbTriangles = 0; + for (TopExp_Explorer anExpSF(namedSolids[i].solid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) { + TopLoc_Location aLoc; + Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(anExpSF.Current()), aLoc); + if (!aTriangulation.IsNull()) { + aNbNodes += aTriangulation->NbNodes(); + aNbTriangles += aTriangulation->NbTriangles(); + } + } + + if (aNbTriangles == 0 || aNbNodes == 0) + // QDS: No triangulation on the shape. + continue; + + stl[i].stats.type = inmemory; + stl[i].stats.number_of_facets = (uint32_t)aNbTriangles; + stl[i].stats.original_num_facets = stl[i].stats.number_of_facets; + stl_allocate(&stl[i]); + + std::vector points; + points.reserve(aNbNodes); + // QDS: count faces missing triangulation + Standard_Integer aNbFacesNoTri = 0; + // QDS: fill temporary triangulation + Standard_Integer aNodeOffset = 0; + Standard_Integer aTriangleOffet = 0; + for (TopExp_Explorer anExpSF(namedSolids[i].solid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) { + const TopoDS_Shape& aFace = anExpSF.Current(); + TopLoc_Location aLoc; + Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(aFace), aLoc); + if (aTriangulation.IsNull()) { + ++aNbFacesNoTri; + continue; + } + // QDS: copy nodes + gp_Trsf aTrsf = aLoc.Transformation(); + for (Standard_Integer aNodeIter = 1; aNodeIter <= aTriangulation->NbNodes(); ++aNodeIter) { + gp_Pnt aPnt = aTriangulation->Node(aNodeIter); + aPnt.Transform(aTrsf); + points.emplace_back(std::move(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z()))); + } + // QDS: copy triangles + const TopAbs_Orientation anOrientation = anExpSF.Current().Orientation(); + Standard_Integer anId[3] = {}; + for (Standard_Integer aTriIter = 1; aTriIter <= aTriangulation->NbTriangles(); ++aTriIter) { + Poly_Triangle aTri = aTriangulation->Triangle(aTriIter); + + aTri.Get(anId[0], anId[1], anId[2]); + if (anOrientation == TopAbs_REVERSED) + std::swap(anId[1], anId[2]); + // QDS: save triangles facets + stl_facet facet; + facet.vertex[0] = points[anId[0] + aNodeOffset - 1].cast(); + facet.vertex[1] = points[anId[1] + aNodeOffset - 1].cast(); + facet.vertex[2] = points[anId[2] + aNodeOffset - 1].cast(); + facet.extra[0] = 0; + facet.extra[1] = 0; + stl_normal normal; + stl_calculate_normal(normal, &facet); + stl_normalize_vector(normal); + facet.normal = normal; + stl[i].facet_start[aTriangleOffet + aTriIter - 1] = facet; + } + + aNodeOffset += aTriangulation->NbNodes(); + aTriangleOffet += aTriangulation->NbTriangles(); + } + meshed_solid_num.fetch_add(1, std::memory_order_relaxed); + } + }); + + + for (size_t i = 0; i < stl.size(); i++) { + progress_2 = static_cast(i) / stl.size(); + if (cb_cancel) + return; + + //QDS: maybe mesh is empty from step file. Don't add + if (stl[i].stats.number_of_facets > 0) { + TriangleMesh triangle_mesh; + triangle_mesh.from_stl(stl[i]); + ModelVolume* new_volume = new_object->add_volume(std::move(triangle_mesh)); + new_volume->name = namedSolids[i].name; + new_volume->source.input_file = m_path.c_str(); + new_volume->source.object_idx = (int)model->objects.size() - 1; + new_volume->source.volume_idx = (int)new_object->volumes.size() - 1; + } + } + task_result = true; + })); + + while (!task_result) { + boost::this_thread::sleep_for(boost::chrono::milliseconds(300)); + if (progress_2 > 0) { + // third progress + update_process(LOAD_STEP_STAGE_GET_MESH, static_cast(progress_2 * 100), 100, cb_cancel); + }else { + if (meshed_solid_num.load()) { + // second progress + int meshed_solid = meshed_solid_num.load(); + update_process(LOAD_STEP_STAGE_GET_SOLID, static_cast((float)meshed_solid / namedSolids.size() * 10) + 10, 20, cb_cancel); + } else { + if (progress > 0) { + // first progress + update_process(LOAD_STEP_STAGE_GET_SOLID, static_cast(progress * 10), 20, cb_cancel); + } + } + } + + + if (cb_cancel) { + if (task) { + if (task->joinable()) { + task->join(); + delete task; + } + } + return Step_Status::CANCEL; + } + } + if (task) { + if (task->joinable()) { + task->join(); + delete task; + } } - STEPCAFControl_Reader reader; - reader.SetNameMode(true); - IFSelect_ReturnStatus stat = reader.ReadFile(m_path.c_str()); - if (stat != IFSelect_RetDone || !reader.Transfer(m_doc)) { - m_app->Close(m_doc); - return false; + //QDS: no valid shape from the step, delete the new object as well + if (new_object->volumes.size() == 0) { + model->delete_object(new_object); + return Step_Status::MESH_ERROR; } - m_shape_tool = XCAFDoc_DocumentTool::ShapeTool(m_doc->Main()); - TDF_LabelSequence topLevelShapes; - m_shape_tool->GetFreeShapes(topLevelShapes); - unsigned int id{ 1 }; - Standard_Integer topShapeLength = topLevelShapes.Length() + 1; - for (Standard_Integer iLabel = 1; iLabel < topShapeLength; ++iLabel) { - getNamedSolids(TopLoc_Location{}, "", id, m_shape_tool, topLevelShapes.Value(iLabel), m_name_solids); - } - - return true; + return Step_Status::MESH_SUCCESS; } void Step::clean_mesh_data() @@ -461,25 +689,30 @@ void Step::clean_mesh_data() unsigned int Step::get_triangle_num(double linear_defletion, double angle_defletion) { unsigned int tri_num = 0; - Handle(StepProgressIncdicator) progress = new StepProgressIncdicator(m_stop_mesh); - clean_mesh_data(); - IMeshTools_Parameters param; - param.Deflection = linear_defletion; - param.Angle = angle_defletion; - param.InParallel = true; - for (int i = 0; i < m_name_solids.size(); ++i) { - BRepMesh_IncrementalMesh mesh(m_name_solids[i].solid, param, progress->Start()); - for (TopExp_Explorer anExpSF(m_name_solids[i].solid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) { - TopLoc_Location aLoc; - Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(anExpSF.Current()), aLoc); - if (!aTriangulation.IsNull()) { - tri_num += aTriangulation->NbTriangles(); + try { + Handle(StepProgressIncdicator) progress = new StepProgressIncdicator(m_stop_mesh); + clean_mesh_data(); + IMeshTools_Parameters param; + param.Deflection = linear_defletion; + param.Angle = angle_defletion; + param.InParallel = true; + for (int i = 0; i < m_name_solids.size(); ++i) { + BRepMesh_IncrementalMesh mesh(m_name_solids[i].solid, param, progress->Start()); + for (TopExp_Explorer anExpSF(m_name_solids[i].solid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) { + TopLoc_Location aLoc; + Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(anExpSF.Current()), aLoc); + if (!aTriangulation.IsNull()) { + tri_num += aTriangulation->NbTriangles(); + } + } + if (m_stop_mesh.load()) { + return 0; } } - if (m_stop_mesh.load()) { - return 0; - } + } catch(Exception e) { + return 0; } + return tri_num; } diff --git a/src/libslic3r/Format/STEP.hpp b/src/libslic3r/Format/STEP.hpp index 227a387..171c690 100644 --- a/src/libslic3r/Format/STEP.hpp +++ b/src/libslic3r/Format/STEP.hpp @@ -89,14 +89,28 @@ private: class Step { public: +enum class Step_Status { + LOAD_SUCCESS, + LOAD_ERROR, + CANCEL, + MESH_SUCCESS, + MESH_ERROR +}; Step(fs::path path, ImportStepProgressFn stepFn = nullptr, StepIsUtf8Fn isUtf8Fn = nullptr); Step(std::string path, ImportStepProgressFn stepFn = nullptr, StepIsUtf8Fn isUtf8Fn = nullptr); - bool load(); + ~Step(); + Step_Status load(); unsigned int get_triangle_num(double linear_defletion, double angle_defletion); unsigned int get_triangle_num_tbb(double linear_defletion, double angle_defletion); void clean_mesh_data(); + Step_Status mesh(Model* model, + bool& is_cancel, + bool isSplitCompound, + double linear_defletion = 0.003, + double angle_defletion = 0.5); std::atomic m_stop_mesh; + void update_process(int load_stage, int current, int total, bool& cancel); private: std::string m_path; ImportStepProgressFn m_stepFn; diff --git a/src/libslic3r/Format/qds_3mf.cpp b/src/libslic3r/Format/qds_3mf.cpp index ea19628..d007e65 100644 --- a/src/libslic3r/Format/qds_3mf.cpp +++ b/src/libslic3r/Format/qds_3mf.cpp @@ -213,6 +213,8 @@ static constexpr const char *FILAMENT_COLOR_TAG = "color"; static constexpr const char *FILAMENT_USED_M_TAG = "used_m"; static constexpr const char *FILAMENT_USED_G_TAG = "used_g"; static constexpr const char *FILAMENT_TRAY_INFO_ID_TAG = "tray_info_idx"; +static constexpr const char *LAYER_FILAMENT_LISTS_TAG = "layer_filament_lists"; +static constexpr const char *LAYER_FILAMENT_LIST_TAG = "layer_filament_list"; static constexpr const char* CONFIG_TAG = "config"; @@ -295,6 +297,9 @@ static constexpr const char* FIRST_LAYER_PRINT_SEQUENCE_ATTR = "first_layer_prin static constexpr const char* OTHER_LAYERS_PRINT_SEQUENCE_ATTR = "other_layers_print_sequence"; static constexpr const char* OTHER_LAYERS_PRINT_SEQUENCE_NUMS_ATTR = "other_layers_print_sequence_nums"; static constexpr const char* SPIRAL_VASE_MODE = "spiral_mode"; +static constexpr const char* FILAMENT_MAP_MODE_ATTR = "filament_map_mode"; +static constexpr const char* FILAMENT_MAP_ATTR = "filament_maps"; +static constexpr const char* LIMIT_FILAMENT_MAP_ATTR = "limit_filament_maps"; static constexpr const char* GCODE_FILE_ATTR = "gcode_file"; static constexpr const char* THUMBNAIL_FILE_ATTR = "thumbnail_file"; static constexpr const char* NO_LIGHT_THUMBNAIL_FILE_ATTR = "thumbnail_no_light_file"; @@ -309,11 +314,13 @@ static constexpr const char* PLATERID_ATTR = "plater_id"; static constexpr const char* PLATER_NAME_ATTR = "plater_name"; static constexpr const char* PLATE_IDX_ATTR = "index"; static constexpr const char* PRINTER_MODEL_ID_ATTR = "printer_model_id"; +static constexpr const char* EXTRUDER_TYPE_ATTR = "extruder_type"; +static constexpr const char* NOZZLE_VOLUME_TYPE_ATTR = "nozzle_volume_type"; +static constexpr const char* NOZZLE_TYPE_ATTR = "nozzle_types"; static constexpr const char* NOZZLE_DIAMETERS_ATTR = "nozzle_diameters"; static constexpr const char* SLICE_PREDICTION_ATTR = "prediction"; static constexpr const char* SLICE_WEIGHT_ATTR = "weight"; static constexpr const char* TIMELAPSE_TYPE_ATTR = "timelapse_type"; -static constexpr const char* TIMELAPSE_ERROR_CODE_ATTR = "timelapse_error_code"; static constexpr const char* OUTSIDE_ATTR = "outside"; static constexpr const char* SUPPORT_USED_ATTR = "support_used"; static constexpr const char* LABEL_OBJECT_ENABLED_ATTR = "label_object_enabled"; @@ -470,6 +477,16 @@ void add_vec3(std::stringstream &stream, const Slic3r::Vec3f &tr) } } +template +void add_vector(std::stringstream &stream, const std::vector &values) +{ + for (size_t i = 0; i < values.size(); ++i) { + stream << values[i]; + if (i != (values.size() - 1)) + stream << " "; + } +} + Slic3r::Vec3f get_vec3_from_string(const std::string &pos_str) { Slic3r::Vec3f pos(0, 0, 0); @@ -1049,7 +1066,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) //QDS: add plate data related logic // add backup & restore logic bool load_model_from_file(const std::string& filename, Model& model, PlateDataPtrs& plate_data_list, std::vector& project_presets, DynamicPrintConfig& config, - ConfigSubstitutionContext& config_substitutions, LoadStrategy strategy, bool& is_qdt_3mf, Semver& file_version, Import3mfProgressFn proFn = nullptr, QDTProject *project = nullptr, int plate_id = 0); + ConfigSubstitutionContext& config_substitutions, LoadStrategy strategy, bool* is_qdt_3mf, Semver& file_version, Import3mfProgressFn proFn = nullptr, QDTProject *project = nullptr, int plate_id = 0); bool get_thumbnail(const std::string &filename, std::string &data); bool load_gcode_3mf_from_stream(std::istream & data, Model& model, PlateDataPtrs& plate_data_list, DynamicPrintConfig& config, Semver& file_version); unsigned int version() const { return m_version; } @@ -1256,7 +1273,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) //QDS: add plate data related logic // add backup & restore logic bool _QDS_3MF_Importer::load_model_from_file(const std::string& filename, Model& model, PlateDataPtrs& plate_data_list, std::vector& project_presets, DynamicPrintConfig& config, - ConfigSubstitutionContext& config_substitutions, LoadStrategy strategy, bool& is_qdt_3mf, Semver& file_version, Import3mfProgressFn proFn, QDTProject *project, int plate_id) + ConfigSubstitutionContext& config_substitutions, LoadStrategy strategy, bool* is_qdt_3mf, Semver& file_version, Import3mfProgressFn proFn, QDTProject *project, int plate_id) { m_version = 0; m_fdm_supports_painting_version = 0; @@ -1309,7 +1326,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) m_backup_path = model.get_backup_path(); } bool result = _load_model_from_file(filename, model, plate_data_list, project_presets, config, config_substitutions, proFn, project, plate_id); - is_qdt_3mf = m_is_qdt_3mf; + if (is_qdt_3mf) { + *is_qdt_3mf = m_is_qdt_3mf; + } if (m_qidislicer_generator_version) file_version = *m_qidislicer_generator_version; // save for restore @@ -1459,6 +1478,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) add_error("Archive does not contain a valid model config"); return false; } + } else if (_is_svg_shape_file(name)) { + _extract_embossed_svg_shape_file(name, archive, stat); } else if (boost::algorithm::iequals(name, SLICE_INFO_CONFIG_FILE)) { m_parsing_slice_info = true; @@ -1831,8 +1852,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) add_error("Archive does not contain a valid model config"); return false; } - } else if (_is_svg_shape_file(name)) { - _extract_embossed_svg_shape_file(name, archive, stat); } else if (!dont_load_config && boost::algorithm::iequals(name, SLICE_INFO_CONFIG_FILE)) { m_parsing_slice_info = true; @@ -4064,6 +4083,36 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) //std::string type = qds_get_attribute_value_string(attributes, num_attributes, TYPE_ATTR); std::string key = qds_get_attribute_value_string(attributes, num_attributes, KEY_ATTR); std::string value = qds_get_attribute_value_string(attributes, num_attributes, VALUE_ATTR); + if (key.empty()) + return true; + + auto get_vector_from_string = [](const std::string& str) -> std::vector { + std::stringstream stream(str); + int value; + std::vector results; + while (stream >> value) { + results.push_back(value); + } + return results; + }; + + auto get_vector_array_from_string = [get_vector_from_string](const std::string &str) -> std::vector> { + std::vector sub_strs; + size_t pos = 0; + size_t found = 0; + while ((found = str.find('#', pos)) != std::string::npos) { + std::string sub_str = str.substr(pos, found - pos); + sub_strs.push_back(sub_str); + pos = found + 1; + } + + std::vector> results; + for (std::string sub_str : sub_strs) { + results.emplace_back(get_vector_from_string(sub_str)); + } + + return results; + }; if ((m_curr_plater == nullptr)&&!m_parsing_slice_info) { @@ -4106,25 +4155,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) m_curr_plater->config.set_key_value("print_sequence", new ConfigOptionEnum(print_sequence)); } else if (key == FIRST_LAYER_PRINT_SEQUENCE_ATTR) { - auto get_vector_from_string = [](const std::string &str) -> std::vector { - std::stringstream stream(str); - int value; - std::vector results; - while (stream >> value) { - results.push_back(value); - } - return results; - }; m_curr_plater->config.set_key_value("first_layer_print_sequence", new ConfigOptionInts(get_vector_from_string(value))); } else if (key == OTHER_LAYERS_PRINT_SEQUENCE_ATTR) { - auto get_vector_from_string = [](const std::string &str) -> std::vector { - std::stringstream stream(str); - int value; - std::vector results; - while (stream >> value) { results.push_back(value); } - return results; - }; m_curr_plater->config.set_key_value("other_layers_print_sequence", new ConfigOptionInts(get_vector_from_string(value))); } else if (key == OTHER_LAYERS_PRINT_SEQUENCE_NUMS_ATTR) { @@ -4135,6 +4168,26 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) std::istringstream(value) >> std::boolalpha >> spiral_mode; m_curr_plater->config.set_key_value("spiral_mode", new ConfigOptionBool(spiral_mode)); } + else if (key == FILAMENT_MAP_MODE_ATTR) + { + FilamentMapMode map_mode = FilamentMapMode::fmmAutoForFlush; + // handle old versions, only load manual params + if (value != "Auto") { + ConfigOptionEnum::from_string(value, map_mode); + m_curr_plater->config.set_key_value("filament_map_mode", new ConfigOptionEnum(map_mode)); + } + } + else if (key == FILAMENT_MAP_ATTR) { + if (m_curr_plater){ + auto filament_map = get_vector_from_string(value); + for (size_t idx = 0; idx < filament_map.size(); ++idx) { + if (filament_map[idx] < 1) { + filament_map[idx] = 1; + } + } + m_curr_plater->config.set_key_value("filament_map", new ConfigOptionInts(filament_map)); + } + } else if (key == GCODE_FILE_ATTR) { m_curr_plater->gcode_file = value; @@ -4662,10 +4715,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) if (triangle_mesh.volume() < 0) triangle_mesh.flip_triangles(); - bool is_text = !volume_data->text_info.m_text.empty(); - bool modify_to_center_geometry = is_text ? false : true;//text do not modify_to_center_geometry - volume = object.add_volume(std::move(triangle_mesh), ModelVolumeType::MODEL_PART, modify_to_center_geometry); - + bool is_text = !volume_data->text_info.m_text.empty(); + bool modify_to_center_geometry = is_text ? false : true;//text do not modify_to_center_geometry + volume = object.add_volume(std::move(triangle_mesh), ModelVolumeType::MODEL_PART, modify_to_center_geometry); + if (shared_mesh_id != -1) //for some cases the shared mesh is in other plate and not loaded in cli slicing //we need to use the first one in the same plate instead @@ -6183,51 +6236,52 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add thumbnail file to archive\n"); } - if (generate_small_thumbnail && thumbnail_data.is_valid()) { - //generate small size of thumbnail - std::vector small_pixels; - small_pixels.resize(PLATE_THUMBNAIL_SMALL_WIDTH * PLATE_THUMBNAIL_SMALL_HEIGHT * 4); - /* step width and step height */ - int sw = thumbnail_data.width / PLATE_THUMBNAIL_SMALL_WIDTH; - int sh = thumbnail_data.height / PLATE_THUMBNAIL_SMALL_HEIGHT; - int clampped_width = sw * PLATE_THUMBNAIL_SMALL_WIDTH; - int clampped_height = sh * PLATE_THUMBNAIL_SMALL_HEIGHT; + //y60 + // if (generate_small_thumbnail && thumbnail_data.is_valid()) { + // //generate small size of thumbnail + // std::vector small_pixels; + // small_pixels.resize(PLATE_THUMBNAIL_SMALL_WIDTH * PLATE_THUMBNAIL_SMALL_HEIGHT * 4); + // /* step width and step height */ + // int sw = thumbnail_data.width / PLATE_THUMBNAIL_SMALL_WIDTH; + // int sh = thumbnail_data.height / PLATE_THUMBNAIL_SMALL_HEIGHT; + // int clampped_width = sw * PLATE_THUMBNAIL_SMALL_WIDTH; + // int clampped_height = sh * PLATE_THUMBNAIL_SMALL_HEIGHT; - for (int i = 0; i < clampped_height; i += sh) { - for (int j = 0; j < clampped_width; j += sw) { - int r = 0, g = 0, b = 0, a = 0; - for (int m = 0; m < sh; m++) { - for (int n = 0; n < sw; n++) { - r += (int)thumbnail_data.pixels[4 * ((i + m) * thumbnail_data.width + j + n) + 0]; - g += (int)thumbnail_data.pixels[4 * ((i + m) * thumbnail_data.width + j + n) + 1]; - b += (int)thumbnail_data.pixels[4 * ((i + m) * thumbnail_data.width + j + n) + 2]; - a += (int)thumbnail_data.pixels[4 * ((i + m) * thumbnail_data.width + j + n) + 3]; - } - } - r = std::clamp(0, r / sw / sh, 255); - g = std::clamp(0, g / sw / sh, 255); - b = std::clamp(0, b / sw / sh, 255); - a = std::clamp(0, a / sw / sh, 255); - small_pixels[4 * (i / sw * PLATE_THUMBNAIL_SMALL_WIDTH + j / sh) + 0] = (unsigned char)r; - small_pixels[4 * (i / sw * PLATE_THUMBNAIL_SMALL_WIDTH + j / sh) + 1] = (unsigned char)g; - small_pixels[4 * (i / sw * PLATE_THUMBNAIL_SMALL_WIDTH + j / sh) + 2] = (unsigned char)b; - small_pixels[4 * (i / sw * PLATE_THUMBNAIL_SMALL_WIDTH + j / sh) + 3] = (unsigned char)a; - //memcpy((void*)&small_pixels[4*(i / sw * PLATE_THUMBNAIL_SMALL_WIDTH + j / sh)], thumbnail_data.pixels.data() + 4*(i * thumbnail_data.width + j), 4); - } - } - size_t small_png_size = 0; - void* small_png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)small_pixels.data(), PLATE_THUMBNAIL_SMALL_WIDTH, PLATE_THUMBNAIL_SMALL_HEIGHT, 4, &small_png_size, MZ_DEFAULT_COMPRESSION, 1); - if (png_data != nullptr) { - std::string thumbnail_name = (boost::format("%1%_%2%_small.png") % local_path % (index + 1)).str(); - res = mz_zip_writer_add_mem(&archive, thumbnail_name.c_str(), (const void*)small_png_data, small_png_size, MZ_NO_COMPRESSION); - mz_free(small_png_data); - } - - if (!res) { - add_error("Unable to add small thumbnail file to archive"); - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add small thumbnail file to archive\n"); - } - } + // for (int i = 0; i < clampped_height; i += sh) { + // for (int j = 0; j < clampped_width; j += sw) { + // int r = 0, g = 0, b = 0, a = 0; + // for (int m = 0; m < sh; m++) { + // for (int n = 0; n < sw; n++) { + // r += (int)thumbnail_data.pixels[4 * ((i + m) * thumbnail_data.width + j + n) + 0]; + // g += (int)thumbnail_data.pixels[4 * ((i + m) * thumbnail_data.width + j + n) + 1]; + // b += (int)thumbnail_data.pixels[4 * ((i + m) * thumbnail_data.width + j + n) + 2]; + // a += (int)thumbnail_data.pixels[4 * ((i + m) * thumbnail_data.width + j + n) + 3]; + // } + // r = std::clamp(0, r / sw / sh, 255); + // g = std::clamp(0, g / sw / sh, 255); + // b = std::clamp(0, b / sw / sh, 255); + // a = std::clamp(0, a / sw / sh, 255); + // small_pixels[4 * (i / sw * PLATE_THUMBNAIL_SMALL_WIDTH + j / sh) + 0] = (unsigned char)r; + // small_pixels[4 * (i / sw * PLATE_THUMBNAIL_SMALL_WIDTH + j / sh) + 1] = (unsigned char)g; + // small_pixels[4 * (i / sw * PLATE_THUMBNAIL_SMALL_WIDTH + j / sh) + 2] = (unsigned char)b; + // small_pixels[4 * (i / sw * PLATE_THUMBNAIL_SMALL_WIDTH + j / sh) + 3] = (unsigned char)a; + // //memcpy((void*)&small_pixels[4*(i / sw * PLATE_THUMBNAIL_SMALL_WIDTH + j / sh)], thumbnail_data.pixels.data() + 4*(i * thumbnail_data.width + j), 4); + // } + // } + // size_t small_png_size = 0; + // void* small_png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)small_pixels.data(), PLATE_THUMBNAIL_SMALL_WIDTH, PLATE_THUMBNAIL_SMALL_HEIGHT, 4, &small_png_size, MZ_DEFAULT_COMPRESSION, 1); + // if (png_data != nullptr) { + // std::string thumbnail_name = (boost::format("%1%_%2%_small.png") % local_path % (index + 1)).str(); + // res = mz_zip_writer_add_mem(&archive, thumbnail_name.c_str(), (const void*)small_png_data, small_png_size, MZ_NO_COMPRESSION); + // mz_free(small_png_data); + // } + + // if (!res) { + // add_error("Unable to add small thumbnail file to archive"); + // BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add small thumbnail file to archive\n"); + // } + // } + // } return res; } @@ -6474,6 +6528,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) BOOST_LOG_TRIVIAL(info) << "saved mk_version " << model.mk_version; } + /*for ml*/ if (model.mk_name.empty() && !model.makerlab_name.empty()) { metadata_item_map[QDT_MAKERLAB_NAME] = model.makerlab_name; @@ -7065,40 +7120,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) return true; } - bool _QDS_3MF_Exporter::_add_brim_ear_points_file_to_archive(mz_zip_archive& archive, Model& model) - { - std::string out = ""; - char buffer[1024]; - - unsigned int count = 0; - for (const ModelObject* object : model.objects) { - ++count; - const BrimPoints& brim_points = object->brim_points; - if (!brim_points.empty()) { - sprintf(buffer, "object_id=%d|", count); - out += buffer; - - // Store the layer height profile as a single space separated list. - for (size_t i = 0; i < brim_points.size(); ++i) { - sprintf(buffer, (i==0 ? "%f %f %f %f" : " %f %f %f %f"), brim_points[i].pos(0), brim_points[i].pos(1), brim_points[i].pos(2), brim_points[i].head_front_radius); - out += buffer; - } - out += "\n"; - } - } - - if (!out.empty()) { - // Adds version header at the beginning: - out = std::string("brim_points_format_version=") + std::to_string(brim_points_format_version) + std::string("\n") + out; - - if (!mz_zip_writer_add_mem(&archive, BRIM_EAR_POINTS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - add_error("Unable to add brim ear points file to archive"); - return false; - } - } - return true; - } - bool _QDS_3MF_Exporter::_add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model) { std::string out = ""; @@ -7158,6 +7179,40 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) return true; } + bool _QDS_3MF_Exporter::_add_brim_ear_points_file_to_archive(mz_zip_archive& archive, Model& model) + { + std::string out = ""; + char buffer[1024]; + + unsigned int count = 0; + for (const ModelObject* object : model.objects) { + ++count; + const BrimPoints& brim_points = object->brim_points; + if (!brim_points.empty()) { + sprintf(buffer, "object_id=%d|", count); + out += buffer; + + // Store the layer height profile as a single space separated list. + for (size_t i = 0; i < brim_points.size(); ++i) { + sprintf(buffer, (i==0 ? "%f %f %f %f" : " %f %f %f %f"), brim_points[i].pos(0), brim_points[i].pos(1), brim_points[i].pos(2), brim_points[i].head_front_radius); + out += buffer; + } + out += "\n"; + } + } + + if (!out.empty()) { + // Adds version header at the beginning: + out = std::string("brim_points_format_version=") + std::to_string(brim_points_format_version) + std::string("\n") + out; + + if (!mz_zip_writer_add_mem(&archive, BRIM_EAR_POINTS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add brim ear points file to archive"); + return false; + } + } + return true; + } + /* bool _QDS_3MF_Exporter::_add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model) { @@ -7513,7 +7568,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) stream << "\"/>\n"; } - ConfigOptionInts *other_layers_print_sequence_opt = plate_data->config.option("other_layers_print_sequence"); if (other_layers_print_sequence_opt != nullptr) { stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << OTHER_LAYERS_PRINT_SEQUENCE_ATTR << "\" " << VALUE_ATTR << "=\""; @@ -7535,6 +7589,25 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) if (spiral_mode_opt) stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << SPIRAL_VASE_MODE << "\" " << VALUE_ATTR << "=\"" << spiral_mode_opt->getBool() << "\"/>\n"; + //filament map related + ConfigOption* filament_map_mode_opt = plate_data->config.option("filament_map_mode"); + t_config_enum_names filament_map_mode_names = ConfigOptionEnum::get_enum_names(); + if (filament_map_mode_opt != nullptr && filament_map_mode_names.size() > filament_map_mode_opt->getInt()) + stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << FILAMENT_MAP_MODE_ATTR << "\" " << VALUE_ATTR << "=\"" << filament_map_mode_names[filament_map_mode_opt->getInt()] << "\"/>\n"; + + ConfigOptionInts* filament_maps_opt = plate_data->config.option("filament_map"); + // filament map override global settings only when group mode overrides the global settings + if (filament_map_mode_opt !=nullptr && filament_maps_opt != nullptr) { + stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << FILAMENT_MAP_ATTR << "\" " << VALUE_ATTR << "=\""; + const std::vector& values = filament_maps_opt->values; + for (int i = 0; i < values.size(); ++i) { + stream << values[i]; + if (i != (values.size() - 1)) + stream << " "; + } + stream << "\"/>\n"; + } + if (save_gcode) stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << GCODE_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << xml_escape(plate_data->gcode_file) << "\"/>\n"; if (!plate_data->gcode_file.empty()) { @@ -7594,9 +7667,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) } else if (obj){ inst = obj->instances[inst_id]; - if (use_loaded_id && (inst->loaded_id > 0)) - { - //1.9.5 + if (use_loaded_id && (inst->loaded_id > 0)) { identify_id = inst->loaded_id; if (identify_id & 0xFF000000) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", identify_id %1%, too big, limit the high bits to 0\n") % identify_id; @@ -7763,16 +7834,45 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) break; } } + + std::vector extruder_types = config.option("extruder_type")->values; + std::vector nozzle_volume_types = config.option("nozzle_volume_type")->values; + + stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << EXTRUDER_TYPE_ATTR << "\" " << VALUE_ATTR << "=\""; + add_vector(stream, extruder_types); + stream << "\"/>\n"; + + stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << NOZZLE_VOLUME_TYPE_ATTR << "\" " << VALUE_ATTR << "=\""; + add_vector(stream, nozzle_volume_types); + stream << "\"/>\n"; + + auto* nozzle_diameter_option = dynamic_cast(config.option("nozzle_diameter")); + std::string nozzle_diameters_str; + if (nozzle_diameter_option) + nozzle_diameters_str = nozzle_diameter_option->serialize(); + stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PRINTER_MODEL_ID_ATTR << "\" " << VALUE_ATTR << "=\"" << plate_data->printer_model_id << "\"/>\n"; - stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << NOZZLE_DIAMETERS_ATTR << "\" " << VALUE_ATTR << "=\"" << plate_data->nozzle_diameters << "\"/>\n"; + stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << NOZZLE_DIAMETERS_ATTR << "\" " << VALUE_ATTR << "=\"" << nozzle_diameters_str << "\"/>\n"; stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << TIMELAPSE_TYPE_ATTR << "\" " << VALUE_ATTR << "=\"" << timelapse_type << "\"/>\n"; - //stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << TIMELAPSE_ERROR_CODE_ATTR << "\" " << VALUE_ATTR << "=\"" << plate_data->timelapse_warning_code << "\"/>\n"; stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << SLICE_PREDICTION_ATTR << "\" " << VALUE_ATTR << "=\"" << plate_data->get_gcode_prediction_str() << "\"/>\n"; stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << SLICE_WEIGHT_ATTR << "\" " << VALUE_ATTR << "=\"" << plate_data->get_gcode_weight_str() << "\"/>\n"; stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << OUTSIDE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->toolpath_outside << "\"/>\n"; stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << SUPPORT_USED_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->is_support_used << "\"/>\n"; stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << LABEL_OBJECT_ENABLED_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->is_label_object_enabled << "\"/>\n"; + std::vector filament_maps = plate_data->filament_maps; + if (filament_maps.empty()) + filament_maps = config.option("filament_map")->values; + stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << FILAMENT_MAP_ATTR << "\" " << VALUE_ATTR << "=\""; + add_vector(stream, filament_maps); + stream << "\"/>\n"; + + if (plate_data->limit_filament_maps.size() > 0) { + stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << LIMIT_FILAMENT_MAP_ATTR << "\" " << VALUE_ATTR << "=\""; + add_vector(stream, plate_data->limit_filament_maps); + stream << "\"/>\n"; + } + for (auto it = plate_data->objects_and_instances.begin(); it != plate_data->objects_and_instances.end(); it++) { int obj_id = it->first; @@ -7791,18 +7891,15 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) continue; } inst = obj->instances[inst_id]; - //1.9.5 if (!inst->printable) continue; - if (m_use_loaded_id && (inst->loaded_id > 0)) - { + if (m_use_loaded_id && (inst->loaded_id > 0)) { identify_id = inst->loaded_id; if (identify_id & 0xFF000000) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", identify_id %1%, too big, limit the high bits to 0\n") % identify_id; identify_id = identify_id & 0x00FFFFFF; } } - else identify_id = inst->id().id; bool skipped = std::find(plate_data->skipped_objects.begin(), plate_data->skipped_objects.end(), identify_id) != @@ -7825,6 +7922,30 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) for (auto it = plate_data->warnings.begin(); it != plate_data->warnings.end(); it++) { stream << " <" << SLICE_WARNING_TAG << " msg=\"" << it->msg << "\" level=\"" << std::to_string(it->level) << "\" error_code =\"" << it->error_code << "\" />\n"; } + + if (!plate_data->layer_filaments.empty()) { + stream << " <" << LAYER_FILAMENT_LISTS_TAG << ">\n"; + for (auto iter = plate_data->layer_filaments.begin(); iter != plate_data->layer_filaments.end(); ++iter) { + // key + std::vector sequence = iter->first; + std::stringstream key_stream; + add_vector(key_stream, sequence); + + // value + std::vector> ranges = iter->second; + std::stringstream value_stream; + for (size_t i = 0; i < ranges.size(); ++i) { + value_stream << ranges[i].first; + value_stream << " "; + value_stream << ranges[i].second; + if (i != (ranges.size() - 1)) value_stream << ","; + } + + stream << " <" << LAYER_FILAMENT_LIST_TAG << " filament_list=\"" << key_stream.str() << "\" layer_ranges=\"" << value_stream.str() << "\" />\n"; + } + stream << " \n"; + } + stream << " \n"; } } @@ -8404,7 +8525,7 @@ bool load_qds_3mf(const char* path, DynamicPrintConfig* config, ConfigSubstituti // All import should use "C" locales for number formatting. CNumericLocalesSetter locales_setter; _QDS_3MF_Importer importer; - bool res = importer.load_model_from_file(path, *model, *plate_data_list, *project_presets, *config, *config_substitutions, strategy, *is_qdt_3mf, *file_version, proFn, project, plate_id); + bool res = importer.load_model_from_file(path, *model, *plate_data_list, *project_presets, *config, *config_substitutions, strategy, is_qdt_3mf, *file_version, proFn, project, plate_id); importer.log_errors(); //QDS: remove legacy project logic currently //handle_legacy_project_loaded(importer.version(), *config); diff --git a/src/libslic3r/Format/qds_3mf.hpp b/src/libslic3r/Format/qds_3mf.hpp index 037fb65..9a981a2 100644 --- a/src/libslic3r/Format/qds_3mf.hpp +++ b/src/libslic3r/Format/qds_3mf.hpp @@ -94,6 +94,16 @@ struct PlateData bool toolpath_outside {false}; bool is_label_object_enabled {false}; int timelapse_warning_code = 0; // 1<<0 sprial vase, 1<<1 by object + std::vector filament_maps; // 1 base + using LayerFilaments = std::unordered_map, std::vector>, GCodeProcessorResult::FilamentSequenceHash>; + LayerFilaments layer_filaments; + + // Hexadecimal number, + // the 0th digit corresponds to extruder 1 + // the 1th digit corresponds to extruder 2 + // ... and so on. + // 0 means can be print on this extruder, 1 means cannot + std::vector limit_filament_maps; std::vector warnings; diff --git a/src/libslic3r/Frustum.cpp b/src/libslic3r/Frustum.cpp index 10fae91..fcf278a 100644 --- a/src/libslic3r/Frustum.cpp +++ b/src/libslic3r/Frustum.cpp @@ -1,18 +1,76 @@ #include "Frustum.hpp" #include namespace Slic3r { + +void Frustum::Plane::set_abcd(float a, float b, float c, float d) +{ + m_abcd[0] = a; + m_abcd[1] = b; + m_abcd[2] = c; + m_abcd[3] = d; +} + +const Vec4f& Frustum::Plane::get_abcd() const +{ + return m_abcd; +} + +void Frustum::Plane::normailze() +{ + float mag; + mag = sqrt(m_abcd[0] * m_abcd[0] + m_abcd[1] * m_abcd[1] + m_abcd[2] * m_abcd[2]); + m_abcd[0] = m_abcd[0] / mag; + m_abcd[1] = m_abcd[1] / mag; + m_abcd[2] = m_abcd[2] / mag; + m_abcd[3] = m_abcd[3] / mag; +} + +float Frustum::Plane::distance(const Vec3f& pt) const +{ + float result = 0.0f; + for (int i = 0; i < 3; ++i) { + result += pt[i] * m_abcd[i]; + } + + result += m_abcd[3]; + + return result; +} + Frustum::Plane::PlaneIntersects Frustum::Plane::intersects(const BoundingBoxf3 &box) const { - Vec3f center = ((box.min + box.max) * 0.5f).cast(); - Vec3f extent = ((box.max - box.min) * 0.5f).cast(); - float d = distance(center); - float r = fabsf(extent.x() * normal_.x()) + fabsf(extent.y() * normal_.y()) + fabsf(extent.z() * normal_.z()); - if (d == r) { - return Plane::Intersects_Tangent; - } else if (std::abs(d) < r) { - return Plane::Intersects_Cross; + // see https://cgvr.cs.uni-bremen.de/teaching/cg_literatur/lighthouse3d_view_frustum_culling/index.html + + Vec3d positive_v = box.min; + if (m_abcd[0] > 0.f) + positive_v[0] = box.max.x(); + if (m_abcd[1] > 0.f) + positive_v[1] = box.max.y(); + if (m_abcd[2] > 0.f) + positive_v[2] = box.max.z(); + + float dis_positive = distance(positive_v.cast()); + if (dis_positive < 0.f) + { + return Frustum::Plane::PlaneIntersects::Intersects_Back; } - return (d > 0.0f) ? Plane::Intersects_Front : Plane::Intersects_Back; + + Vec3d negitive_v = box.max; + if (m_abcd[0] > 0.f) + negitive_v[0] = box.min.x(); + if (m_abcd[1] > 0.f) + negitive_v[1] = box.min.y(); + if (m_abcd[2] > 0.f) + negitive_v[2] = box.min.z(); + + float dis_negitive = distance(negitive_v.cast()); + + if (dis_negitive < 0.f) + { + return Frustum::Plane::PlaneIntersects::Intersects_Cross; + } + + return Frustum::Plane::PlaneIntersects::Intersects_Front; } Frustum::Plane::PlaneIntersects Frustum::Plane::intersects(const Vec3f &p0) const { @@ -51,19 +109,15 @@ Frustum::Plane::PlaneIntersects Frustum::Plane::intersects(const Vec3f &p0, cons return Plane::Intersects_Tangent; } -bool Frustum::intersects(const BoundingBoxf3 &box, bool is_perspective) const +bool Frustum::intersects(const BoundingBoxf3 &box) const { - if (is_perspective) { - for (auto &plane : planes) { - if (plane.intersects(box) == Plane::Intersects_Back) { - return false; - } + for (auto& plane : planes) { + const auto rt = plane.intersects(box); + if (Frustum::Plane::Intersects_Back == rt) { + return false; } } - // check box intersects - if (!bbox.intersects(box)) { - return false; - } + return true; } diff --git a/src/libslic3r/Frustum.hpp b/src/libslic3r/Frustum.hpp index 26b12e1..296333c 100644 --- a/src/libslic3r/Frustum.hpp +++ b/src/libslic3r/Frustum.hpp @@ -13,17 +13,13 @@ public: class Plane { public: enum PlaneIntersects { Intersects_Cross = 0, Intersects_Tangent = 1, Intersects_Front = 2, Intersects_Back = 3 }; - void set(const Vec3f &n, const Vec3f &pt) - { - normal_ = n.normalized(); - center_ = pt; - d_ = -normal_.dot(pt); - } - float distance(const Vec3f &pt) const { return normal_.dot(pt) + d_; } + void set_abcd(float a, float b, float c, float d); + const Vec4f& get_abcd() const; + + void normailze(); + float distance(const Vec3f& pt) const; - inline const Vec3f &getNormal() const { return normal_; } - const Vec3f & getCenter() const { return center_; } Plane::PlaneIntersects intersects(const BoundingBoxf3 &box) const; //// check intersect with point (world space) Plane::PlaneIntersects intersects(const Vec3f &p0) const; @@ -32,12 +28,10 @@ public: // check intersect with triangle (world space) Plane::PlaneIntersects intersects(const Vec3f &p0, const Vec3f &p1, const Vec3f &p2) const; private: - Vec3f normal_; - Vec3f center_; - float d_ = 0; + Vec4f m_abcd; }; - bool intersects(const BoundingBoxf3 &box, bool is_perspective) const; + bool intersects(const BoundingBoxf3 &box) const; // check intersect with point (world space) bool intersects(const Vec3f &p0) const; // check intersect with line segment (world space) @@ -46,18 +40,6 @@ public: bool intersects(const Vec3f &p0, const Vec3f &p1, const Vec3f &p2) const; Plane planes[6]; - /* corners[0]: nearTopLeft; - * corners[1]: nearTopRight; - * corners[2]: nearBottomLeft; - * corners[3]: nearBottomRight; - * corners[4]: farTopLeft; - * corners[5]: farTopRight; - * corners[6]: farBottomLeft; - * corners[7]: farBottomRight; - */ - Vec3f corners[8]; - - BoundingBoxf3 bbox; }; enum FrustumClipMask { diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index aa5dbf0..8a47872 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -69,6 +69,8 @@ using namespace std::literals::string_view_literals; #endif #include +#include +#include namespace Slic3r { @@ -76,19 +78,18 @@ namespace Slic3r { //! return same string #define L(s) (s) #define _(s) Slic3r::I18N::translate(s) +#define _L(s) Slic3r::I18N::translate(s) static const float g_min_purge_volume = 100.f; static const float g_purge_volume_one_time = 135.f; static const int g_max_flush_count = 4; static const size_t g_max_label_object = 64; -//1.9.5 static const double smooth_speed_step = 10; static const double not_split_length = scale_(1.0); Vec2d travel_point_1; Vec2d travel_point_2; Vec2d travel_point_3; - static std::vector get_path_of_change_filament(const Print& print) { // give safe value in case there is no start_end_points in config @@ -270,7 +271,7 @@ static std::vector get_path_of_change_filament(const Print& print) if (gcodegen.config().standby_temperature_delta.value != 0) { // we assume that heating is always slower than cooling, so no need to block gcode += gcodegen.writer().set_temperature - (this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, gcodegen.writer().extruder()->id()); + (this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, gcodegen.writer().filament()->id()); } return gcode; @@ -279,7 +280,7 @@ static std::vector get_path_of_change_filament(const Print& print) std::string OozePrevention::post_toolchange(GCode& gcodegen) { return (gcodegen.config().standby_temperature_delta.value != 0) ? - gcodegen.writer().set_temperature(this->_get_temp(gcodegen), true, gcodegen.writer().extruder()->id()) : + gcodegen.writer().set_temperature(this->_get_temp(gcodegen), true, gcodegen.writer().filament()->id()) : std::string(); } @@ -287,8 +288,70 @@ static std::vector get_path_of_change_filament(const Print& print) OozePrevention::_get_temp(GCode& gcodegen) { return (gcodegen.layer() != NULL && gcodegen.layer()->id() == 0) - ? gcodegen.config().nozzle_temperature_initial_layer.get_at(gcodegen.writer().extruder()->id()) - : gcodegen.config().nozzle_temperature.get_at(gcodegen.writer().extruder()->id()); + ? gcodegen.config().nozzle_temperature_initial_layer.get_at(gcodegen.writer().filament()->id()) + : gcodegen.config().nozzle_temperature.get_at(gcodegen.writer().filament()->id()); + } + + std::string transform_gcode(const std::string &gcode, Vec2f pos, const Vec2f &translation, float angle) + { + Vec2f extruder_offset(0, 0); + std::istringstream gcode_str(gcode); + std::string gcode_out; + std::string line; + Vec2f transformed_pos = pos; + Vec2f old_pos(-1000.1f, -1000.1f); + + while (gcode_str) { + std::getline(gcode_str, line); // we read the gcode line by line + + if (line.find("G1 ") == 0) { + bool never_skip = false; + auto it = line.find(WipeTower::never_skip_tag()); + if (it != std::string::npos) { + // remove the tag and remember we saw it + never_skip = true; + line.erase(it, it + WipeTower::never_skip_tag().size()); + } + std::ostringstream line_out; + std::istringstream line_str(line); + line_str >> std::noskipws; // don't skip whitespace + char ch = 0; + while (line_str >> ch) { + if (ch == 'X' || ch == 'Y') + line_str >> (ch == 'X' ? pos.x() : pos.y()); + else + line_out << ch; + } + + transformed_pos = Eigen::Rotation2Df(angle) * pos + translation; + + if (transformed_pos != old_pos || never_skip) { + line = line_out.str(); + std::ostringstream oss; + oss << std::fixed << std::setprecision(3) << "G1 "; + if (transformed_pos.x() != old_pos.x() || never_skip) oss << " X" << transformed_pos.x() - extruder_offset.x(); + if (transformed_pos.y() != old_pos.y() || never_skip) oss << " Y" << transformed_pos.y() - extruder_offset.y(); + oss << " "; + line.replace(line.find("G1 "), 3, oss.str()); + old_pos = transformed_pos; + } + } + + gcode_out += line + "\n"; + } + return gcode_out; + } + + float get_wipe_avoid_pos_x(const Vec2f &wt_min, const Vec2f &wt_max, float offset) + { + float left = 100, right = 250; + float default_value = 110.f; + float a = 0.f, b = 0.f; + a = wt_max.x() + offset; + b = wt_min.x() - offset; + if (a > left && a < right) return a; + if (b > left && b < right) return b; + return default_value; } std::string Wipe::wipe(GCode& gcodegen, bool toolchange, bool is_last) @@ -298,24 +361,25 @@ static std::vector get_path_of_change_filament(const Print& print) /* Reduce feedrate a bit; travel speed is often too high to move on existing material. Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */ //OrcaSlicer - double cur_speed = gcodegen.writer().get_current_speed(); + double cur_speed = gcodegen.writer().get_current_speed(); double wipe_speed = gcodegen.config().role_base_wipe_speed && cur_speed > EPSILON ? cur_speed / 60 : - gcodegen.writer().config.travel_speed.value * gcodegen.config().wipe_speed.value / 100; + gcodegen.writer().config.travel_speed.get_at(get_extruder_index(gcodegen.writer().config, gcodegen.writer().filament()->id())) * gcodegen.config().wipe_speed.value / 100; + if (toolchange) { wipe_speed = gcodegen.m_print->config().prime_tower_max_speed.value; } // get the retraction length double length = toolchange - ? gcodegen.writer().extruder()->retract_length_toolchange() - : gcodegen.writer().extruder()->retraction_length(); + ? gcodegen.writer().filament()->retract_length_toolchange() + : gcodegen.writer().filament()->retraction_length(); // Shorten the retraction length by the amount already retracted before wipe. - length *= (1. - gcodegen.writer().extruder()->retract_before_wipe()); + length *= (1. - gcodegen.writer().filament()->retract_before_wipe()); if (length >= 0) { /* Calculate how long we need to travel in order to consume the required amount of retraction. In other words, how far do we move in XY at wipe_speed for the time needed to consume retraction_length at retraction_speed? */ // QDS - double wipe_dist = scale_(gcodegen.config().wipe_distance.get_at(gcodegen.writer().extruder()->id())); + double wipe_dist = scale_(gcodegen.config().wipe_distance.get_at(gcodegen.writer().filament()->id())); /* Take the stored wipe path and replace first point with the current actual position (they might be different, for example, in case of loop clipping). */ @@ -373,11 +437,150 @@ static std::vector get_path_of_change_filament(const Print& print) return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1))); } - std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const + // set volumetric speed of outer wall ,ignore per obejct & region ,just use default setting + static float get_outer_wall_volumetric_speed(const FullPrintConfig& config, const Print& print, int filament_id, int extruder_id) { + float outer_wall_volumetric_speed = 0; + float filament_max_volumetric_speed = config.filament_max_volumetric_speed.get_at(filament_id); + float outer_wall_line_width = print.default_region_config().outer_wall_line_width.value; + if (outer_wall_line_width == 0.0) { + float default_line_width = print.default_object_config().line_width.value; + outer_wall_line_width = default_line_width == 0.0 ? config.filament_diameter.get_at(filament_id) : default_line_width; + } + Flow outer_wall_flow = Flow(outer_wall_line_width, config.layer_height, config.nozzle_diameter.get_at(extruder_id)); + float outer_wall_speed = print.default_region_config().outer_wall_speed.get_at(extruder_id); + outer_wall_volumetric_speed = outer_wall_speed * outer_wall_flow.mm3_per_mm(); + if (outer_wall_volumetric_speed > filament_max_volumetric_speed) + outer_wall_volumetric_speed = filament_max_volumetric_speed; + return outer_wall_volumetric_speed; + } + // QDS + // start_pos refers to the last position before the wipe_tower. + // end_pos refers to the wipe tower's start_pos. + // using the print coordinate system + Polyline WipeTowerIntegration::generate_path_to_wipe_tower(const Point& start_pos,const Point &end_pos , const BoundingBox& avoid_polygon , const BoundingBox& printer_bbx) const { - if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool) + Polyline res; + coord_t alpha = scaled(2.f); // offset distance + BoundingBox avoid_polygon_inner = avoid_polygon; + avoid_polygon_inner.offset(alpha); + coord_t width = avoid_polygon_inner.max[0] - avoid_polygon_inner.min[0]; + Polygon bed_polygon = printer_bbx.polygon(); + Vec2f v(1, 0); // the first print direction of end_pos. + if (abs(end_pos[0] - avoid_polygon_inner.min[0]) < width / 2) v = -v; // judge whether the wipe tower's infill goes to the left or right. + // Judge whether the avoid_polygon_inner is outside the printer_bbx. + // If so, do nothing and just go directly to the end_pos. + bool is_bbx_in_bed = true; + Points avoid_points = avoid_polygon_inner.polygon().points; + for (auto &wipe_tower_bbx_p : avoid_points) { + if (ClipperLib::PointInPolygon(wipe_tower_bbx_p, bed_polygon.points) != 1) { + is_bbx_in_bed = false; + break; + } + } + if (!is_bbx_in_bed) { + res.points.push_back(end_pos); + return res; + } + // Ray-Line Segment Intersection + auto ray_intersetion_line = [](const Vec2d &a, const Vec2d &v1, const Vec2d &b, const Vec2d &c) -> std::pair { + const Vec2d v2 = c - b; + double denom = cross2(v1, v2); + if (fabs(denom) < EPSILON) return {false, Point(0, 0)}; + const Vec2d v12 = (a - b); + double nume_a = cross2(v2, v12); + double nume_b = cross2(v1, v12); + double t1 = nume_a / denom; + double t2 = nume_b / denom; + if (t1 >= 0 && t2 >= 0 && t2 <= 1.) { + // Get the intersection point. + Vec2d res = a + t1 * v1; + return std::pair(true, scaled(res)); + } + return std::pair(false, {0, 0}); + }; + struct Inter_info + { + int inter_idx0 = -1; + Point inter_p; + }; + auto calc_path_len = [](Points &points, Inter_info &beg_info, Inter_info &end_info, bool is_add) -> std::pair, double> { + int beg = is_add ? (beg_info.inter_idx0 + 1) % points.size() : beg_info.inter_idx0; + int end = is_add ? end_info.inter_idx0 : (end_info.inter_idx0 + 1) % points.size(); + int i = beg; + double len = 0; + std::vector path; + path.push_back(beg_info.inter_p); + len += (unscale(beg_info.inter_p) - unscale(points[beg])).squaredNorm(); + while (i != end) { + int ni = is_add ? (i + 1) % points.size() : (i - 1 + points.size()) % points.size(); + auto a = unscale(points[i]); + auto b = unscale(points[ni]); + len += (a - b).squaredNorm(); + path.push_back(points[i]); + i = ni; + } + path.push_back(points[end]); + path.push_back(end_info.inter_p); + len += (unscale(end_info.inter_p) - unscale(points[end])).squaredNorm(); + return {path, len}; + }; + // calculate the intersection point of end_pos along vector v with the avoid_polygon. + // store in inter_info. + // represent this intersection by 'p'. + Inter_info inter_info; + for (size_t i = 0; i < avoid_points.size(); i++) { + const auto &a = avoid_points[i]; + const auto &b = avoid_points[(i + 1) % avoid_points.size()]; + auto [is_inter, inter_p] = ray_intersetion_line(unscale(end_pos), v.cast(), unscale(a), unscale(b)); + if (is_inter) { + inter_info.inter_idx0 = i; + inter_info.inter_p = inter_p; + break; + } + } + if (inter_info.inter_idx0 == -1) { + res.points.push_back(end_pos); + return res; + } + // calculate the other intersection of start_to_p with the avoid_polygon. + // represent this intersection by 'p_'. + Inter_info inter_info2; + Linef start_to_p(unscale(start_pos), unscale(inter_info.inter_p)); + for (size_t i = 0; i < avoid_points.size(); i++) { + if (i == inter_info.inter_idx0) continue; + Vec2d a = unscale(avoid_points[i]); + Vec2d b = unscale(avoid_points[(i + 1) % avoid_points.size()]); + Linef tower_edge(a, b); + Vec2d inter; + if (line_alg::intersection(start_to_p, tower_edge, &inter)) { + inter_info2.inter_p = scaled(inter); + inter_info2.inter_idx0 = i; + break; + } + } + // if p_ does not exist, go directly to p. + // else p travels along the shorter path on the wipe_tower_offset_polygon to p_ + if (inter_info2.inter_idx0 == -1) { + res.points.push_back(inter_info.inter_p); + } else { + std::vector path; + auto [path1, len1] = calc_path_len(avoid_points, inter_info2, inter_info, true); + auto [path2, len2] = calc_path_len(avoid_points, inter_info2, inter_info, false); + path = len1 < len2 ? path1 : path2; + for (size_t i = 0; i < path.size(); i++) { + res.points.push_back(path[i]); + } + } + res.points.push_back(end_pos); + return res; + } + + std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_filament_id, double z) const + { + if (new_filament_id != -1 && new_filament_id != tcr.new_tool) throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); + int new_extruder_id = get_extruder_index(*m_print_config, new_filament_id); std::string gcode; // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines) @@ -386,18 +589,22 @@ static std::vector get_path_of_change_filament(const Print& print) auto transform_wt_pt = [&alpha, this](const Vec2f& pt) -> Vec2f { Vec2f out = Eigen::Rotation2Df(alpha) * pt; - out += m_wipe_tower_pos; + out += m_wipe_tower_pos + m_rib_offset; return out; }; Vec2f start_pos = tcr.start_pos; Vec2f end_pos = tcr.end_pos; + Vec2f tool_change_start_pos = start_pos; + if (tcr.is_tool_change) + tool_change_start_pos = tcr.tool_change_start_pos; if (! tcr.priming) { start_pos = transform_wt_pt(start_pos); end_pos = transform_wt_pt(end_pos); + tool_change_start_pos = transform_wt_pt(tool_change_start_pos); } - Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos; + Vec2f wipe_tower_offset = (tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos) + m_rib_offset; float wipe_tower_rotation = tcr.priming ? 0.f : alpha; std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); @@ -434,32 +641,66 @@ static std::vector get_path_of_change_filament(const Print& print) // Process the end filament gcode. std::string end_filament_gcode_str; - if (gcodegen.writer().extruder() != nullptr) { + if (gcodegen.writer().filament() != nullptr) { // Process the custom filament_end_gcode in case of single_extruder_multi_material. - unsigned int old_extruder_id = gcodegen.writer().extruder()->id(); - const std::string& filament_end_gcode = gcodegen.config().filament_end_gcode.get_at(old_extruder_id); - if (gcodegen.writer().extruder() != nullptr && !filament_end_gcode.empty()) { - end_filament_gcode_str = gcodegen.placeholder_parser_process("filament_end_gcode", filament_end_gcode, old_extruder_id); + unsigned int old_filament_id = gcodegen.writer().filament()->id(); + const std::string& filament_end_gcode = gcodegen.config().filament_end_gcode.get_at(old_filament_id); + if (gcodegen.writer().filament() != nullptr && !filament_end_gcode.empty()) { + DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); + end_filament_gcode_str = gcodegen.placeholder_parser_process("filament_end_gcode", filament_end_gcode, old_filament_id, &config); check_add_eol(end_filament_gcode_str); } } + //QDS: increase toolchange count gcodegen.m_toolchange_count++; + std::string toolchange_gcode_str; + + ZHopType z_hope_type = ZHopType(gcodegen.config().z_hop_types.get_at(gcodegen.writer().filament()->id())); + LiftType auto_lift_type = LiftType::NormalLift; + if (z_hope_type == ZHopType::zhtAuto || z_hope_type == ZHopType::zhtSpiral || z_hope_type == ZHopType::zhtSlope) + auto_lift_type = LiftType::SpiralLift; + // QDS: should be placed before toolchange parsing - std::string toolchange_retract_str = gcodegen.retract(true, false); + std::string toolchange_retract_str = gcodegen.retract(true, false, auto_lift_type, true); check_add_eol(toolchange_retract_str); // Process the custom change_filament_gcode. If it is empty, provide a simple Tn command to change the filament. // Otherwise, leave control to the user completely. - std::string toolchange_gcode_str; - const std::string& change_filament_gcode = gcodegen.config().change_filament_gcode.value; -// m_max_layer_z = std::max(m_max_layer_z, tcr.print_z); + std::string change_filament_gcode = gcodegen.config().change_filament_gcode.value; + + bool is_used_travel_avoid_perimeter = gcodegen.m_config.prime_tower_skip_points.value; + + // add nozzle change gcode into change filament gcode + std::string nozzle_change_gcode_trans; + if (!tcr.nozzle_change_result.gcode.empty() && (gcodegen.config().nozzle_diameter.size() > 1)) { + // move to start_pos before nozzle change + std::string start_pos_str; + start_pos_str = gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(tcr.nozzle_change_result.start_pos) + plate_origin_2d), erMixed, + "Move to nozzle change start pos"); + check_add_eol(start_pos_str); + nozzle_change_gcode_trans += start_pos_str; + nozzle_change_gcode_trans += gcodegen.unretract(); + nozzle_change_gcode_trans += transform_gcode(tcr.nozzle_change_result.gcode, tcr.nozzle_change_result.start_pos, wipe_tower_offset, wipe_tower_rotation); + gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(tcr.nozzle_change_result.end_pos) + plate_origin_2d)); + gcodegen.m_wipe.reset_path(); + for (const Vec2f& wipe_pt : tcr.nozzle_change_result.wipe_path) + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt) + plate_origin_2d)); + nozzle_change_gcode_trans += gcodegen.retract(true, false, auto_lift_type, true); + end_filament_gcode_str = nozzle_change_gcode_trans + end_filament_gcode_str; + } + + end_filament_gcode_str = toolchange_retract_str + end_filament_gcode_str; + if (! change_filament_gcode.empty()) { DynamicConfig config; - int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1; - config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); - config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id)); + int old_filament_id = gcodegen.writer().filament() ? (int)gcodegen.writer().filament()->id() : -1; + int old_extruder_id = gcodegen.writer().filament() ? (int)gcodegen.writer().filament()->extruder_id() : -1; + + config.set_key_value("previous_extruder", new ConfigOptionInt(old_filament_id)); + config.set_key_value("next_extruder", new ConfigOptionInt(new_filament_id)); config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z)); config.set_key_value("toolchange_z", new ConfigOptionFloat(z)); @@ -468,22 +709,34 @@ static std::vector get_path_of_change_filament(const Print& print) { GCodeWriter& gcode_writer = gcodegen.m_writer; FullPrintConfig& full_config = gcodegen.m_config; - float old_retract_length = gcode_writer.extruder() != nullptr ? full_config.retraction_length.get_at(previous_extruder_id) : 0; - float new_retract_length = full_config.retraction_length.get_at(new_extruder_id); - float old_retract_length_toolchange = gcode_writer.extruder() != nullptr ? full_config.retract_length_toolchange.get_at(previous_extruder_id) : 0; - float new_retract_length_toolchange = full_config.retract_length_toolchange.get_at(new_extruder_id); - int old_filament_temp = gcode_writer.extruder() != nullptr ? (gcodegen.on_first_layer()? full_config.nozzle_temperature_initial_layer.get_at(previous_extruder_id) : full_config.nozzle_temperature.get_at(previous_extruder_id)) : 210; - int new_filament_temp = gcodegen.on_first_layer() ? full_config.nozzle_temperature_initial_layer.get_at(new_extruder_id) : full_config.nozzle_temperature.get_at(new_extruder_id); + + // set volumetric speed of outer wall ,ignore per obejct,just use default setting + float outer_wall_volumetric_speed = get_outer_wall_volumetric_speed(full_config, *gcodegen.m_print, new_filament_id, gcodegen.get_extruder_id(new_filament_id)); + config.set_key_value("outer_wall_volumetric_speed", new ConfigOptionFloat(outer_wall_volumetric_speed)); + + float old_retract_length = (old_filament_id != -1) ? full_config.retraction_length.get_at(old_filament_id) : 0; + float new_retract_length = full_config.retraction_length.get_at(new_filament_id); + float old_retract_length_toolchange = (old_filament_id != -1) ? full_config.retract_length_toolchange.get_at(old_filament_id) : 0; + float new_retract_length_toolchange = full_config.retract_length_toolchange.get_at(new_filament_id); + int old_filament_temp = (old_filament_id != -1) ? (gcodegen.on_first_layer()? full_config.nozzle_temperature_initial_layer.get_at(old_filament_id) : full_config.nozzle_temperature.get_at(old_filament_id)) : 210; + int new_filament_temp = gcodegen.on_first_layer() ? full_config.nozzle_temperature_initial_layer.get_at(new_filament_id) : full_config.nozzle_temperature.get_at(new_filament_id); Vec3d nozzle_pos = gcode_writer.get_position(); float purge_volume = tcr.purge_volume < EPSILON ? 0 : std::max(tcr.purge_volume, g_min_purge_volume); - float filament_area = float((M_PI / 4.f) * pow(full_config.filament_diameter.get_at(new_extruder_id), 2)); + float filament_area = float((M_PI / 4.f) * pow(full_config.filament_diameter.get_at(new_filament_id), 2)); float purge_length = purge_volume / filament_area; - int old_filament_e_feedrate = gcode_writer.extruder() != nullptr ? (int)(60.0 * full_config.filament_max_volumetric_speed.get_at(previous_extruder_id) / filament_area) : 200; + int old_filament_e_feedrate = (old_filament_id != -1) ? (int)(60.0 * full_config.filament_max_volumetric_speed.get_at(old_filament_id) / filament_area) : 200; old_filament_e_feedrate = old_filament_e_feedrate == 0 ? 100 : old_filament_e_feedrate; - int new_filament_e_feedrate = (int)(60.0 * full_config.filament_max_volumetric_speed.get_at(new_extruder_id) / filament_area); + int new_filament_e_feedrate = (int)(60.0 * full_config.filament_max_volumetric_speed.get_at(new_filament_id) / filament_area); new_filament_e_feedrate = new_filament_e_feedrate == 0 ? 100 : new_filament_e_feedrate; + float wipe_avoid_pos_x = 0.f; + { + //set wipe_avoid_pos_x + Vec2f box_min = transform_wt_pt(m_wipe_tower_bbx.min.cast()); + Vec2f box_max = transform_wt_pt(m_wipe_tower_bbx.max.cast()); + wipe_avoid_pos_x = get_wipe_avoid_pos_x(box_min, box_max, 3.f); + } config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z)); config.set_key_value("relative_e_axis", new ConfigOptionBool(full_config.use_relative_e_distances)); @@ -497,8 +750,8 @@ static std::vector get_path_of_change_filament(const Print& print) config.set_key_value("new_retract_length_toolchange", new ConfigOptionFloat(new_retract_length_toolchange)); config.set_key_value("old_filament_temp", new ConfigOptionInt(old_filament_temp)); config.set_key_value("new_filament_temp", new ConfigOptionInt(new_filament_temp)); - config.set_key_value("x_after_toolchange", new ConfigOptionFloat(start_pos(0))); - config.set_key_value("y_after_toolchange", new ConfigOptionFloat(start_pos(1))); + config.set_key_value("x_after_toolchange", new ConfigOptionFloat(tool_change_start_pos(0))); + config.set_key_value("y_after_toolchange", new ConfigOptionFloat(tool_change_start_pos(1))); config.set_key_value("z_after_toolchange", new ConfigOptionFloat(nozzle_pos(2))); config.set_key_value("first_flush_volume", new ConfigOptionFloat(purge_length / 2.f)); config.set_key_value("second_flush_volume", new ConfigOptionFloat(purge_length / 2.f)); @@ -512,10 +765,10 @@ static std::vector get_path_of_change_filament(const Print& print) config.set_key_value("travel_point_3_y", new ConfigOptionFloat(float(travel_point_3.y()))); config.set_key_value("flush_length", new ConfigOptionFloat(purge_length)); + config.set_key_value("wipe_avoid_perimeter", new ConfigOptionBool(is_used_travel_avoid_perimeter)); + config.set_key_value("wipe_avoid_pos_x", new ConfigOptionFloat(wipe_avoid_pos_x)); int flush_count = std::min(g_max_flush_count, (int)std::round(purge_volume / g_purge_volume_one_time)); - - //1.9.7.52 // handle cases for very small purge if (flush_count == 0 && purge_volume > 0) flush_count += 1; @@ -533,11 +786,10 @@ static std::vector get_path_of_change_filament(const Print& print) config.set_key_value(key_value, new ConfigOptionFloat(0.f)); } } - toolchange_gcode_str = gcodegen.placeholder_parser_process("change_filament_gcode", change_filament_gcode, new_extruder_id, &config); + toolchange_gcode_str = gcodegen.placeholder_parser_process("change_filament_gcode", change_filament_gcode, new_filament_id, &config); + check_add_eol(toolchange_gcode_str); - // retract before toolchange - toolchange_gcode_str = toolchange_retract_str + toolchange_gcode_str; //QDS { //QDS: current position and fan_speed is unclear after interting change_filament_gcode @@ -554,54 +806,92 @@ static std::vector get_path_of_change_filament(const Print& print) } // move to start_pos for wiping after toolchange - std::string start_pos_str; - start_pos_str = gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d), erMixed, "Move to start pos"); - check_add_eol(start_pos_str); - toolchange_gcode_str += start_pos_str; - - // unretract before wiping - toolchange_gcode_str += gcodegen.unretract(); - check_add_eol(toolchange_gcode_str); + if (!is_used_travel_avoid_perimeter) { + std::string start_pos_str = gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, tool_change_start_pos + plate_origin_2d), erMixed, "Move to start pos"); + check_add_eol(start_pos_str); + toolchange_gcode_str += start_pos_str; + } else { + // QDS:change travel_path + Vec3f gcode_last_pos; + GCodeProcessor::get_last_position_from_gcode(toolchange_gcode_str, gcode_last_pos); + Vec2f gcode_last_pos2d{gcode_last_pos[0], gcode_last_pos[1]}; + Point gcode_last_pos2d_object = gcodegen.gcode_to_point(gcode_last_pos2d.cast() + plate_origin_2d.cast()); + Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, tool_change_start_pos + plate_origin_2d); + BoundingBox avoid_bbx, printer_bbx; + { + //set printer_bbx + Pointfs bed_pointsf = gcodegen.m_config.printable_area.values; + Points bed_points; + for (auto p : bed_pointsf) { + bed_points.push_back(wipe_tower_point_to_object_point(gcodegen, p.cast() + plate_origin_2d)); + } + printer_bbx = BoundingBox(bed_points); + } + { + //set avoid_bbx + avoid_bbx = scaled(m_wipe_tower_bbx); + Polygon avoid_points = avoid_bbx.polygon(); + for (auto& p : avoid_points.points) { + Vec2f pp = transform_wt_pt(unscale(p).cast()); + p = wipe_tower_point_to_object_point(gcodegen, pp + plate_origin_2d); + } + avoid_bbx = BoundingBox(avoid_points.points); + } + std::string travel_to_wipe_tower_gcode; + Polyline travel_polyline = generate_path_to_wipe_tower(gcode_last_pos2d_object, start_wipe_pos, avoid_bbx, printer_bbx); + for (const auto &p : travel_polyline.points) { + travel_to_wipe_tower_gcode += gcodegen.travel_to(p, erMixed, "Move to start pos"); + check_add_eol(travel_to_wipe_tower_gcode); + } + toolchange_gcode_str += travel_to_wipe_tower_gcode; + gcodegen.set_last_pos(start_wipe_pos); + } } std::string toolchange_command; - if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))) - toolchange_command = gcodegen.writer().toolchange(new_extruder_id); - if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id)) + if (tcr.priming || (new_filament_id >= 0 && gcodegen.writer().need_toolchange(new_filament_id))) + toolchange_command = gcodegen.writer().toolchange(new_filament_id); + if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_filament_id)) toolchange_gcode_str += toolchange_command; else { // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code. } - gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); - gcodegen.placeholder_parser().set("retraction_distance_when_cut", gcodegen.m_config.retraction_distances_when_cut.get_at(new_extruder_id)); - gcodegen.placeholder_parser().set("long_retraction_when_cut", gcodegen.m_config.long_retractions_when_cut.get_at(new_extruder_id)); + // do unretract after setting current extruder_id + std::string toolchange_unretract_str = gcodegen.unretract(); + check_add_eol(toolchange_unretract_str); + + gcodegen.placeholder_parser().set("current_extruder", new_filament_id); + gcodegen.placeholder_parser().set("retraction_distance_when_cut", gcodegen.m_config.retraction_distances_when_cut.get_at(new_filament_id)); + gcodegen.placeholder_parser().set("long_retraction_when_cut", gcodegen.m_config.long_retractions_when_cut.get_at(new_filament_id)); // Process the start filament gcode. std::string start_filament_gcode_str; - const std::string& filament_start_gcode = gcodegen.config().filament_start_gcode.get_at(new_extruder_id); + const std::string &filament_start_gcode = gcodegen.config().filament_start_gcode.get_at(new_filament_id); if (!filament_start_gcode.empty()) { // Process the filament_start_gcode for the active filament only. DynamicConfig config; - config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id)); - start_filament_gcode_str = gcodegen.placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_extruder_id, &config); + config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_filament_id)); + start_filament_gcode_str = gcodegen.placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config); check_add_eol(start_filament_gcode_str); } + start_filament_gcode_str = start_filament_gcode_str + toolchange_unretract_str; + // Insert the end filament, toolchange, and start filament gcode into the generated gcode. DynamicConfig config; config.set_key_value("filament_end_gcode", new ConfigOptionString(end_filament_gcode_str)); config.set_key_value("change_filament_gcode", new ConfigOptionString(toolchange_gcode_str)); config.set_key_value("filament_start_gcode", new ConfigOptionString(start_filament_gcode_str)); - std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config); + std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_filament_id, &config); unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode); gcode += tcr_gcode; check_add_eol(toolchange_gcode_str); //OrcaSlicer: set new PA for new filament. QDS: never use for QIDI Printer //w30 - if ( gcodegen.config().enable_pressure_advance.get_at(new_extruder_id)) - gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_extruder_id)); + if (gcodegen.config().enable_pressure_advance.get_at(new_filament_id)) + gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_filament_id)); // A phony move to the end position at the wipe tower. gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast()); @@ -616,7 +906,8 @@ static std::vector get_path_of_change_filament(const Print& print) // Prepare a future wipe. gcodegen.m_wipe.reset_path(); for (const Vec2f& wipe_pt : tcr.wipe_path) - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt))); + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt) + plate_origin_2d)); + gcode += gcodegen.retract(true, false, auto_lift_type, true); } // Let the planner know we are traveling between objects. @@ -647,7 +938,8 @@ static std::vector get_path_of_change_filament(const Print& print) // All G1 commands should be translated and rotated. X and Y coords are // only pushed to the output when they differ from last time. // WT generator can override this by appending the never_skip_tag - if (line.find("G1 ") == 0) { + if (line.find("G1 ") == 0 || line.find("G2 ") == 0 || line.find("G3 ") == 0) { + std::string cur_gcode_start = line.find("G1 ") == 0 ? "G1 " : (line.find("G2 ") == 0 ? "G2 " : "G3 "); bool never_skip = false; auto it = line.find(WipeTower::never_skip_tag()); if (it != std::string::npos) { @@ -671,13 +963,13 @@ static std::vector get_path_of_change_filament(const Print& print) if (transformed_pos != old_pos || never_skip) { line = line_out.str(); std::ostringstream oss; - oss << std::fixed << std::setprecision(3) << "G1 "; + oss << std::fixed << std::setprecision(3) << cur_gcode_start; if (transformed_pos.x() != old_pos.x() || never_skip) oss << " X" << transformed_pos.x() - extruder_offset.x(); if (transformed_pos.y() != old_pos.y() || never_skip) oss << " Y" << transformed_pos.y() - extruder_offset.y(); oss << " "; - line.replace(line.find("G1 "), 3, oss.str()); + line.replace(line.find(cur_gcode_start), 3, oss.str()); old_pos = transformed_pos; } } @@ -700,6 +992,9 @@ static std::vector get_path_of_change_filament(const Print& print) gcode_out += oss.str(); } } + old_pos = Vec2f{-1000.1f, -1000.1f}; + pos = tcr.tool_change_start_pos; + transformed_pos = pos; } } return gcode_out; @@ -796,8 +1091,8 @@ static std::vector get_path_of_change_filament(const Print& print) const std::vector ColorPrintColors::Colors = { "#C0392B", "#E67E22", "#F1C40F", "#27AE60", "#1ABC9C", "#2980B9", "#9B59B6" }; -#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id()) - +#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.filament()->extruder_id()) +#define FILAMENT_CONFIG(OPT) m_config.OPT.get_at(m_writer.filament()->id()) // Collect pairs of object_layer + support_layer sorted by print_z. // object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON. std::vector GCode::collect_layers_to_print(const PrintObject& object) @@ -1129,6 +1424,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu BOOST_LOG_TRIVIAL(info) << boost::format("Will export G-code to %1% soon")%path; GCodeProcessor::s_IsQDTPrinter = print->is_QDT_Printer(); + m_writer.set_is_qdt_printer(print->is_QDT_Printer()); print->set_started(psGCodeExport); @@ -1214,14 +1510,17 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu if (m_config.printer_structure.value == PrinterStructure::psI3 && print->config().print_sequence == PrintSequence::ByObject) { m_timelapse_warning_code += (1 << 1); } + if (m_config.timelapse_type.value == TimelapseType::tlSmooth && !m_config.enable_prime_tower.value) { + m_timelapse_warning_code += (1 << 2); + } m_processor.result().timelapse_warning_code = m_timelapse_warning_code; m_processor.result().support_traditional_timelapse = m_support_traditional_timelapse; bool activate_long_retraction_when_cut = false; - for (const auto& extruder : m_writer.extruders()) + for (const auto& filament : m_writer.extruders()) activate_long_retraction_when_cut |= ( - m_config.long_retractions_when_cut.get_at(extruder.id()) - && m_config.retraction_distances_when_cut.get_at(extruder.id()) > 0 + m_config.long_retractions_when_cut.get_at(filament.id()) + && m_config.retraction_distances_when_cut.get_at(filament.id()) > 0 ); m_processor.result().long_retraction_when_cut = activate_long_retraction_when_cut; @@ -1239,6 +1538,16 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu m_processor.result().filament_printable_reuslt = FilamentPrintableResult(conflict_filament, bed_type_to_gcode_string(m_config.curr_bed_type)); } m_processor.set_filaments(m_writer.extruders()); + // check gcode is valid in multi_extruder printabele area + int extruder_size = m_print->config().nozzle_diameter.values.size(); + if (extruder_size > 1) { + std::vector extruder_unprintable_polys = m_print->get_extruder_unprintable_polygons(); + m_processor.check_multi_extruder_gcode_valid(extruder_unprintable_polys, + m_print->get_extruder_printable_height(), + m_print->get_filament_maps(), + m_print->get_physical_unprintable_filaments(m_print->get_slice_used_filaments(false))); + } + m_processor.finalize(true); // DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); DoExport::update_print_estimated_stats(m_processor, m_writer.extruders(), print->m_print_statistics); @@ -1273,7 +1582,6 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu // free functions called by GCode::_do_export() namespace DoExport { - //1.9.7.52 static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled,const std::vector& filaments) { silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlinLegacy || config.gcode_flavor == gcfMarlinFirmware) @@ -1281,7 +1589,6 @@ namespace DoExport { processor.reset(); processor.apply_config(config); processor.enable_stealth_time_estimator(silent_time_estimator_enabled); - //1.9.7.52 processor.set_filaments(filaments); } @@ -1299,7 +1606,7 @@ namespace DoExport { // QDS: remove small small_perimeter_speed config, and will absolutely // remove related code if no other issue in the coming release. //region.config().get_abs_value("small_perimeter_speed") == 0 || - region.config().outer_wall_speed.value == 0 || + region.config().outer_wall_speed.get_at(cur_extruder_index()) == 0 || region.config().get_abs_value("bridge_speed") == 0) mm3_per_mm.push_back(layerm->perimeters.min_mm3_per_mm()); if (region.config().get_abs_value("sparse_infill_speed") == 0 || @@ -1356,8 +1663,9 @@ namespace DoExport { if (! skirt_points.empty()) { Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points); Polygons skirts; - for (unsigned int extruder_id : print.extruders()) { - const Vec2d &extruder_offset = print.config().extruder_offset.get_at(extruder_id); + auto filament_extruder_map = print.config().filament_map.values; // 1 based idxs + for (unsigned int filament_id : print.extruders()) { + const Vec2d& extruder_offset = print.config().extruder_offset.get_at(filament_extruder_map[filament_id] - 1); Polygon s(outer_skirt); s.translate(Point::new_scale(-extruder_offset(0), -extruder_offset(1))); skirts.emplace_back(std::move(s)); @@ -1378,7 +1686,7 @@ namespace DoExport { } //QDS: add plate id for thumbnail generate param - /*template + template static void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, int plate_id, const std::vector &sizes, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled) { // Write thumbnails using base64 encoding @@ -1419,7 +1727,7 @@ namespace DoExport { throw_if_canceled(); } } - }*/ + } // Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section. static std::string update_print_stats_and_format_filament_stats( @@ -1560,8 +1868,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato { PROFILE_FUNC(); + m_print = &print; + // modifies m_silent_time_estimator_enabled - //1.9.7.52 DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled, m_writer.extruders()); // resets analyzer's tracking data m_last_height = 0.f; @@ -1711,8 +2020,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato error_str += GCodeThumbnails::get_error_string(errors); throw Slic3r::ExportError(error_str); } - - if (!thumbnails.empty()) + //y60 + if (!thumbnails.empty() && !m_config.is_support_3mf) GCodeThumbnails::export_thumbnails_to_file( thumbnail_cb, print.get_plate_index(), thumbnails, [&file](const char* sz) { file.write(sz); }, [&print]() { print.throw_if_canceled(); }); @@ -1768,51 +2077,39 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato ToolOrdering tool_ordering; unsigned int initial_extruder_id = (unsigned int)-1; //QDS: first non-support filament extruder - unsigned int initial_non_support_extruder_id; + unsigned int initial_non_support_extruder_id = (unsigned int) -1; unsigned int final_extruder_id = (unsigned int)-1; bool has_wipe_tower = false; + print.m_statistics_by_extruder_count.clear(); + std::vector first_filaments; + std::vector first_non_support_filaments; std::vector print_object_instances_ordering; std::vector::const_iterator print_object_instance_sequential_active; + std::vector::const_iterator first_has_extrude_print_object; + //resize + first_non_support_filaments.resize(print.config().nozzle_diameter.size(), -1); + first_filaments.resize(print.config().nozzle_diameter.size(), -1); + if (print.config().print_sequence == PrintSequence::ByObject) { // Order object instances for sequential print. print_object_instances_ordering = sort_object_instances_by_model_order(print); // print_object_instances_ordering = sort_object_instances_by_max_z(print); // Find the 1st printing object, find its tool ordering and the initial extruder ID. print_object_instance_sequential_active = print_object_instances_ordering.begin(); + first_has_extrude_print_object = print_object_instance_sequential_active; + bool find_fist_non_support_filament = false; for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++ print_object_instance_sequential_active) { tool_ordering = ToolOrdering(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id); - if ((initial_extruder_id = tool_ordering.first_extruder()) != static_cast(-1)) { - //QDS: try to find the non-support filament extruder if is multi color and initial_extruder is support filament - initial_non_support_extruder_id = initial_extruder_id; - if (tool_ordering.all_extruders().size() > 1 && print.config().filament_is_support.get_at(initial_extruder_id)) { - bool has_non_support_filament = false; - for (unsigned int extruder : tool_ordering.all_extruders()) { - if (!print.config().filament_is_support.get_at(extruder)) { - has_non_support_filament = true; - break; - } - } - //QDS: find the non-support filament extruder of object - if (has_non_support_filament) { - bool find_initial_non_support_filament = false; - for (LayerTools layer_tools : tool_ordering.layer_tools()) { - if (!layer_tools.has_object) - continue; - for (unsigned int extruder : layer_tools.extruders) { - if (print.config().filament_is_support.get_at(extruder)) - continue; - initial_non_support_extruder_id = extruder; - find_initial_non_support_filament = true; - break; - } - if (find_initial_non_support_filament) - break; - } - } + tool_ordering.sort_and_build_data(*(*print_object_instance_sequential_active)->print_object,initial_extruder_id); + if (!find_fist_non_support_filament && tool_ordering.first_extruder() != (unsigned int) -1) { + //QDS: try to find the non-support filament extruder if is multi color and initial_extruder is support filament + if (initial_extruder_id == (unsigned int) -1) { + initial_extruder_id = tool_ordering.first_extruder(); + first_has_extrude_print_object = print_object_instance_sequential_active; } - break; + find_fist_non_support_filament = tool_ordering.cal_non_support_filaments(print.config(), initial_non_support_extruder_id, first_non_support_filaments, first_filaments); } } if (initial_extruder_id == static_cast(-1)) @@ -1827,6 +2124,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Find tool ordering for all the objects at once, and the initial extruder ID. // If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it. tool_ordering = print.tool_ordering(); + tool_ordering.cal_most_used_extruder(print.config()); tool_ordering.assign_custom_gcodes(print); if (tool_ordering.all_extruders().empty()) // No object to print was found, cancel the G-code export. @@ -1844,34 +2142,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato #endif //QDS: try to find the non-support filament extruder if is multi color and initial_extruder is support filament if (initial_extruder_id != static_cast(-1)) { - initial_non_support_extruder_id = initial_extruder_id; - if (tool_ordering.all_extruders().size() > 1 && print.config().filament_is_support.get_at(initial_extruder_id)) { - bool has_non_support_filament = false; - for (unsigned int extruder : tool_ordering.all_extruders()) { - if (!print.config().filament_is_support.get_at(extruder)) { - has_non_support_filament = true; - break; - } - } - //QDS: find the non-support filament extruder of object - if (has_non_support_filament){ - bool find_initial_non_support_filament = false; - for (LayerTools layer_tools : tool_ordering.layer_tools()) { - if (!layer_tools.has_object) - continue; - for (unsigned int extruder : layer_tools.extruders) { - if (print.config().filament_is_support.get_at(extruder)) - continue; - initial_non_support_extruder_id = extruder; - find_initial_non_support_filament = true; - break; - } - - if (find_initial_non_support_filament) - break; - } - } - } + // QDS: try to find the non-support filament extruder if is multi color and initial_extruder is support filament + // check if has non support filaments + tool_ordering.cal_non_support_filaments(print.config(), initial_non_support_extruder_id, first_non_support_filaments, first_filaments); } // In non-sequential print, the printing extruders may have been modified by the extruder switches stored in Model::custom_gcode_per_print_z. @@ -1885,13 +2158,20 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato initial_extruder_id = 0; initial_non_support_extruder_id = 0; } + + //could not find non support filmanet, use fisrt print filament + if (initial_non_support_extruder_id == (unsigned int) -1) + initial_non_support_extruder_id = initial_extruder_id; + print.throw_if_canceled(); - m_cooling_buffer = make_unique(*this); - m_cooling_buffer->set_current_extruder(initial_extruder_id); + m_gcode_editer = make_unique(*this); + m_gcode_editer->set_current_extruder(initial_extruder_id); + + int extruder_id = get_extruder_id(initial_extruder_id); // Emit machine envelope limits for the Marlin firmware. - this->print_machine_envelope(file, print); + this->print_machine_envelope(file, print, initial_extruder_id); //w21 // Disable fan. @@ -1902,9 +2182,26 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato //} // Let the start-up script prime the 1st printing tool. + + auto match_physical_extruder_for_each_filament = [](std::vector &filaments, const FullPrintConfig &config) { + // match the filament to the physical extruder + std::vector physicial_first_filaments; + physicial_first_filaments.resize(filaments.size()); + for (int extruder_id = 0; extruder_id < filaments.size(); extruder_id++) { + physicial_first_filaments[config.physical_extruder_map.get_at(extruder_id)] = filaments[extruder_id]; + } + filaments = physicial_first_filaments; + }; + match_physical_extruder_for_each_filament(first_filaments, m_config); + m_placeholder_parser.set("first_tools", new ConfigOptionInts(first_filaments)); + m_placeholder_parser.set("first_filaments", new ConfigOptionInts(first_filaments)); m_placeholder_parser.set("initial_tool", initial_extruder_id); m_placeholder_parser.set("initial_extruder", initial_extruder_id); //QDS + match_physical_extruder_for_each_filament(first_non_support_filaments, m_config); + + m_placeholder_parser.set("first_non_support_tools", new ConfigOptionInts(first_non_support_filaments)); + m_placeholder_parser.set("first_non_support_filaments", new ConfigOptionInts(first_non_support_filaments)); m_placeholder_parser.set("initial_no_support_tool", initial_non_support_extruder_id); m_placeholder_parser.set("initial_no_support_extruder", initial_non_support_extruder_id); m_placeholder_parser.set("current_extruder", initial_extruder_id); @@ -1912,8 +2209,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato m_placeholder_parser.set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(initial_extruder_id)); m_placeholder_parser.set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(initial_extruder_id)); - m_placeholder_parser.set("retraction_distances_when_cut", new ConfigOptionFloats(m_config.retraction_distances_when_cut)); - m_placeholder_parser.set("long_retractions_when_cut",new ConfigOptionBools(m_config.long_retractions_when_cut)); + m_placeholder_parser.set("retraction_distances_when_cut", new ConfigOptionFloatsNullable(m_config.retraction_distances_when_cut)); + m_placeholder_parser.set("long_retractions_when_cut",new ConfigOptionBoolsNullable(m_config.long_retractions_when_cut)); //Set variable for total layer count so it can be used in custom gcode. m_placeholder_parser.set("total_layer_count", m_layer_count); // Useful for sequential prints. @@ -1938,20 +2235,35 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // therefore it does NOT encompass the initial purge line. // It does NOT encompass MMU/MMU2 starting (wipe) areas. - - - //w24 - //auto pts = std::make_unique(); - //pts->values.reserve(print.first_layer_convex_hull().size()); - //for (const Point &pt : print.first_layer_convex_hull().points) - // pts->values.emplace_back(unscale(pt)); + // auto pts = std::make_unique(); + // pts->values.reserve(print.first_layer_convex_hull().size()); + // for (const Point &pt : print.first_layer_convex_hull().points) + // pts->values.emplace_back(unscale(pt)); - //BoundingBoxf bbox = first_layer_projection(print); - //BoundingBoxf bbox_without_plate_offset{ - // {bbox.min.x() - plate_offset.x(),bbox.min.y() - plate_offset.y()}, - // {bbox.max.x() - plate_offset.x(),bbox.max.y() - plate_offset.y()} - //}; + // BoundingBoxf bbox = first_layer_projection(print); + // BoundingBoxf bbox_without_plate_offset{ + // {bbox.min.x() - plate_offset.x(),bbox.min.y() - plate_offset.y()}, + // {bbox.max.x() - plate_offset.x(),bbox.max.y() - plate_offset.y()} + // }; + + // if (print.calib_params().mode == CalibMode::Calib_PA_Line) { + // Pointfs bedfs = print.config().printable_area.values; + // BoundingBoxf bed_bbox = BoundingBoxf(bedfs); + // pts->values.clear(); + // for (const Vec2d &pt : bedfs) + // pts->values.emplace_back(pt); + // m_placeholder_parser.set("first_layer_print_convex_hull", pts.release()); + // m_placeholder_parser.set("first_layer_print_min", new ConfigOptionFloats({bed_bbox.min.x(), bed_bbox.min.y()})); + // m_placeholder_parser.set("first_layer_print_max", new ConfigOptionFloats({bed_bbox.max.x(), bed_bbox.max.y()})); + // m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({bed_bbox.size().x(), bed_bbox.size().y()})); + // } + // else { + // m_placeholder_parser.set("first_layer_print_convex_hull", pts.release()); + // m_placeholder_parser.set("first_layer_print_min", new ConfigOptionFloats({bbox_without_plate_offset.min.x(), bbox_without_plate_offset.min.y()})); + // m_placeholder_parser.set("first_layer_print_max", new ConfigOptionFloats({bbox_without_plate_offset.max.x(), bbox_without_plate_offset.max.y()})); + // m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({bbox.size().x(), bbox.size().y()})); + // } auto pts = std::make_unique(); BoundingBoxf bbox; pts->values.reserve(print.first_layer_convex_hull().size()); @@ -2002,25 +2314,38 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato int max_chamber_temp = 0; { - int curr_bed_type = m_config.curr_bed_type.getInt(); + BedType curr_bed_type = m_config.curr_bed_type; for (const auto& extruder : m_writer.extruders()) max_chamber_temp = std::max(max_chamber_temp, m_config.chamber_temperatures.get_at(extruder.id())); + int min_temperature_vitrification = std::numeric_limits::max(); + for (const auto& extruder : m_writer.extruders()) + min_temperature_vitrification = std::min(min_temperature_vitrification, m_config.temperature_vitrification.get_at(extruder.id())); + + std::string first_layer_bed_temp_str; const ConfigOptionInts* first_bed_temp_opt = m_config.option(get_bed_temp_1st_layer_key((BedType)curr_bed_type)); const ConfigOptionInts* bed_temp_opt = m_config.option(get_bed_temp_key((BedType)curr_bed_type)); + int target_bed_temp = 0; + if (m_config.bed_temperature_formula == BedTempFormula::btfHighestTemp) + target_bed_temp = get_highest_bed_temperature(true, print); + else + target_bed_temp = get_bed_temperature(initial_extruder_id, true, curr_bed_type); + m_placeholder_parser.set("qdt_bed_temperature_gcode", new ConfigOptionBool(false)); m_placeholder_parser.set("bed_temperature_initial_layer", new ConfigOptionInts(*first_bed_temp_opt)); m_placeholder_parser.set("bed_temperature", new ConfigOptionInts(*bed_temp_opt)); - m_placeholder_parser.set("bed_temperature_initial_layer_single", new ConfigOptionInt(first_bed_temp_opt->get_at(initial_extruder_id))); + m_placeholder_parser.set("bed_temperature_initial_layer_single", new ConfigOptionInt(target_bed_temp)); m_placeholder_parser.set("bed_temperature_initial_layer_vector", new ConfigOptionString("")); - m_placeholder_parser.set("chamber_temperature", new ConfigOptionInts({max_chamber_temp})); + m_placeholder_parser.set("chamber_temperature", new ConfigOptionInts({ max_chamber_temp })); m_placeholder_parser.set("overall_chamber_temperature", new ConfigOptionInt(max_chamber_temp)); + m_placeholder_parser.set("enable_high_low_temp_mix", new ConfigOptionBool(!print.need_check_multi_filaments_compatibility())); + m_placeholder_parser.set("min_vitrification_temperature", new ConfigOptionInt(min_temperature_vitrification)); - //support variables `first_layer_temperature` and `first_layer_bed_temperature` + //support variables `first_layer_temperature` and `first_layer_bed_temperature` m_placeholder_parser.set("first_layer_bed_temperature", new ConfigOptionInts(*first_bed_temp_opt)); - m_placeholder_parser.set("first_layer_temperature", new ConfigOptionInts(m_config.nozzle_temperature_initial_layer)); + m_placeholder_parser.set("first_layer_temperature", new ConfigOptionIntsNullable(m_config.nozzle_temperature_initial_layer)); m_placeholder_parser.set("max_print_height", new ConfigOptionInt(m_config.printable_height)); m_placeholder_parser.set("z_offset", new ConfigOptionFloat(0.0f)); m_placeholder_parser.set("plate_name", new ConfigOptionString(print.get_plate_name())); @@ -2030,22 +2355,15 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato during_print_exhaust_fan_speed_num.reserve(m_config.during_print_exhaust_fan_speed.size()); for (const auto& item : m_config.during_print_exhaust_fan_speed.values) during_print_exhaust_fan_speed_num.emplace_back((int)(item / 100.0 * 255)); - m_placeholder_parser.set("during_print_exhaust_fan_speed_num",new ConfigOptionInts(during_print_exhaust_fan_speed_num)); + m_placeholder_parser.set("during_print_exhaust_fan_speed_num", new ConfigOptionInts(during_print_exhaust_fan_speed_num)); + //QDS: calculate the volumetric speed of outer wall. Ignore pre-object setting and multi-filament, and just use the default setting - { - float filament_max_volumetric_speed = m_config.option("filament_max_volumetric_speed")->get_at(initial_non_support_extruder_id); - float outer_wall_line_width = print.default_region_config().outer_wall_line_width.value; - if (outer_wall_line_width == 0.0) { - float default_line_width = print.default_object_config().line_width.value; - outer_wall_line_width = default_line_width == 0.0 ? m_config.nozzle_diameter.get_at(initial_non_support_extruder_id) : default_line_width; - } - Flow outer_wall_flow = Flow(outer_wall_line_width, m_config.layer_height, m_config.nozzle_diameter.get_at(initial_non_support_extruder_id)); - float outer_wall_speed = print.default_region_config().outer_wall_speed.value; - float outer_wall_volumetric_speed = outer_wall_speed * outer_wall_flow.mm3_per_mm(); - if (outer_wall_volumetric_speed > filament_max_volumetric_speed) - outer_wall_volumetric_speed = filament_max_volumetric_speed; - m_placeholder_parser.set("outer_wall_volumetric_speed", new ConfigOptionFloat(outer_wall_volumetric_speed)); - } + float outer_wall_volumetric_speed = get_outer_wall_volumetric_speed(m_config, print, initial_non_support_extruder_id, get_extruder_id(initial_non_support_extruder_id)); + m_placeholder_parser.set("outer_wall_volumetric_speed", new ConfigOptionFloat(outer_wall_volumetric_speed)); + + auto first_layer_filaments = print.get_slice_used_filaments(true); + bool has_tpu_in_first_layer = std::any_of(first_layer_filaments.begin(), first_layer_filaments.end(), [&](unsigned int idx) { return m_config.filament_type.values[idx] == "TPU"; }); + m_placeholder_parser.set("has_tpu_in_first_layer", new ConfigOptionBool(has_tpu_in_first_layer)); if (print.calib_params().mode == CalibMode::Calib_PA_Line) { m_placeholder_parser.set("scan_first_layer", new ConfigOptionBool(false)); @@ -2067,10 +2385,19 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // adds tag for processor file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); - + + //w44 + if (m_config.support_box_temp_control) { + auto get_all_extruders = tool_ordering.all_extruders(); + std::string box_control_str = generate_box_temp_command(get_all_extruders, m_config); + file.writeln(box_control_str); + } + + file.write("\n"); // Write the custom start G-code file.writeln(machine_start_gcode); - + //QDS: mark machine start gcode + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::MachineStartGCodeEnd).c_str()); //w21 if (print.config().close_fan_the_first_x_layers.get_at(initial_extruder_id)) { file.write(m_writer.set_fan(0)); @@ -2081,6 +2408,17 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato m_writer.set_current_position_clear(false); m_start_gcode_filament = GCodeProcessor::get_gcode_last_filament(machine_start_gcode); + m_writer.init_extruder(initial_non_support_extruder_id); + // add the missing filament start gcode in machine start gcode + { + DynamicConfig config; + config.set_key_value("filament_extruder_id", new ConfigOptionInt((int)(initial_non_support_extruder_id))); + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + std::string filament_start_gcode = this->placeholder_parser_process("filament_start_gcode", print.config().filament_start_gcode.values.at(initial_non_support_extruder_id), initial_non_support_extruder_id,&config); + file.writeln(filament_start_gcode); + // mark the first filament used in print + file.write_format(";VT%d\n", initial_extruder_id); + } // Process filament-specific gcode. /* if (has_wipe_tower) { @@ -2142,30 +2480,41 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato } if (this->m_objsWithBrim.empty() && this->m_objSupportsWithBrim.empty()) m_brim_done = true; + std::vector travel_accelerations; + for (auto value : m_config.travel_acceleration.values) { + travel_accelerations.emplace_back((unsigned int) floor(value + 0.5)); + } + std::vector first_layer_travel_accelerations; + for (auto value : m_config.initial_layer_travel_acceleration.values) { + first_layer_travel_accelerations.emplace_back((unsigned int) floor(value + 0.5)); + } + m_writer.set_travel_acceleration(travel_accelerations); + m_writer.set_first_layer_travel_acceleration(first_layer_travel_accelerations); + //29 // OrcaSlicer: calib //if (print.calib_params().mode == CalibMode::Calib_PA_Line) { //std::string gcode; - //if ((m_config.default_acceleration.value > 0 && m_config.outer_wall_acceleration.value > 0)) { - // gcode += m_writer.set_acceleration((unsigned int) floor(m_config.outer_wall_acceleration.value + 0.5)); - //} + // if ((m_config.default_acceleration.get_at(cur_extruder_index()) > 0 && m_config.outer_wall_acceleration.get_at(cur_extruder_index()) > 0)) { + // m_writer.set_acceleration((unsigned int) floor(m_config.outer_wall_acceleration.get_at(cur_extruder_index()) + 0.5)); + // } //if (m_config.default_jerk.value > 0 && !this->is_QDT_Printer()) { // double jerk = m_config.outer_wall_jerk.value; // gcode += m_writer.set_jerk_xy(jerk); //} - //CalibPressureAdvanceLine pa_test(this); - //double filament_max_volumetric_speed = m_config.option("filament_max_volumetric_speed")->get_at(initial_extruder_id); - //Flow pattern_line = Flow(pa_test.line_width(), 0.2, m_config.nozzle_diameter.get_at(0)); - //auto fast_speed = std::min(print.default_region_config().outer_wall_speed.value, filament_max_volumetric_speed / pattern_line.mm3_per_mm()); - //auto slow_speed = fast_speed / 4; /*std::max(20.0, fast_speed / 10.0);*/ - //pa_test.set_speed(fast_speed, slow_speed); - //pa_test.draw_numbers() = print.calib_params().print_numbers; - //auto params = print.calib_params(); - //gcode += pa_test.generate_test(params.start, params.step, std::llround(std::ceil((params.end - params.start) / params.step)) + 1); + // CalibPressureAdvanceLine pa_test(this); + // double filament_max_volumetric_speed = m_config.option("filament_max_volumetric_speed")->get_at(initial_extruder_id); + // Flow pattern_line = Flow(pa_test.line_width(), 0.2, m_config.nozzle_diameter.get_at(0)); + // auto fast_speed = std::min(print.default_region_config().outer_wall_speed.get_at(cur_extruder_index()), filament_max_volumetric_speed / pattern_line.mm3_per_mm()); + // auto slow_speed = fast_speed / 4; /*std::max(20.0, fast_speed / 10.0);*/ + // pa_test.set_speed(fast_speed, slow_speed); + // pa_test.draw_numbers() = print.calib_params().print_numbers; + // auto params = print.calib_params(); + // gcode += pa_test.generate_test(params.start, params.step, std::llround(std::ceil((params.end - params.start) / params.step)) + 1); - //file.write(gcode); + // file.write(gcode); //} //else { @@ -2177,11 +2526,13 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Do all objects for each layer. if (print.config().print_sequence == PrintSequence::ByObject && !has_wipe_tower) { size_t finished_objects = 0; + print_object_instance_sequential_active = first_has_extrude_print_object; const PrintObject *prev_object = (*print_object_instance_sequential_active)->print_object; for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++print_object_instance_sequential_active) { const PrintObject &object = *(*print_object_instance_sequential_active)->print_object; if (&object != prev_object || tool_ordering.first_extruder() != final_extruder_id) { tool_ordering = ToolOrdering(object, final_extruder_id); + tool_ordering.sort_and_build_data(object, final_extruder_id); unsigned int new_extruder_id = tool_ordering.first_extruder(); if (new_extruder_id == (unsigned int) -1) // Skip this object. @@ -2198,7 +2549,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato if (finished_objects > 0) { // 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_enable_cooling_markers = false; // we're not filtering these moves through GCodeEditor m_avoid_crossing_perimeters.use_external_mp_once(); // QDS. change tool before moving to origin point. if (m_writer.need_toolchange(initial_extruder_id)) { @@ -2226,13 +2577,25 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato file.writeln(printing_by_object_gcode); } // Reset the cooling buffer internal state (the current position, feed rate, accelerations). - m_cooling_buffer->reset(this->writer().get_position()); - m_cooling_buffer->set_current_extruder(initial_extruder_id); + m_gcode_editer->reset(this->writer().get_position()); + m_gcode_editer->set_current_extruder(initial_extruder_id); // Process all layers of a single object instance (sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. + tool_ordering.cal_most_used_extruder(print.config()); this->process_layers(print, tool_ordering, collect_layers_to_print(object), *print_object_instance_sequential_active - object.instances().data(), file, prime_extruder); + { + // save the flush statitics stored in tool ordering by object + print.m_statistics_by_extruder_count.stats_by_single_extruder += tool_ordering.get_filament_change_stats(ToolOrdering::FilamentChangeMode::SingleExt); + print.m_statistics_by_extruder_count.stats_by_multi_extruder_best += tool_ordering.get_filament_change_stats(ToolOrdering::FilamentChangeMode::MultiExtBest); + print.m_statistics_by_extruder_count.stats_by_multi_extruder_curr += tool_ordering.get_filament_change_stats(ToolOrdering::FilamentChangeMode::MultiExtCurr); + // save sorted filament sequences + const auto& layer_tools = tool_ordering.layer_tools(); + for (const auto& lt : layer_tools) + m_sorted_layer_filaments.emplace_back(lt.extruders); + } + // QDS: close powerlost recovery //w30 //{ @@ -2258,6 +2621,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato if (has_wipe_tower && !layers_to_print.empty()) { m_wipe_tower.reset(new WipeTowerIntegration(print.config(), print.get_plate_index(), print.get_plate_origin(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get())); + m_wipe_tower->set_wipe_tower_depth(print.get_wipe_tower_depth()); + m_wipe_tower->set_wipe_tower_bbx(print.get_wipe_tower_bbx()); + m_wipe_tower->set_rib_offset(print.get_rib_offset()); // QDS // file.write(m_writer.travel_to_z(initial_layer_print_height + m_config.z_offset.value, "Move to the first layer height")); file.write(m_writer.travel_to_z(initial_layer_print_height, "Move to the first layer height")); @@ -2305,6 +2671,17 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. this->process_layers(print, tool_ordering, print_object_instances_ordering, layers_to_print, file); + { + //save the flush statitics stored in tool ordering + print.m_statistics_by_extruder_count.stats_by_single_extruder = tool_ordering.get_filament_change_stats(ToolOrdering::FilamentChangeMode::SingleExt); + print.m_statistics_by_extruder_count.stats_by_multi_extruder_best = tool_ordering.get_filament_change_stats(ToolOrdering::FilamentChangeMode::MultiExtBest); + print.m_statistics_by_extruder_count.stats_by_multi_extruder_curr = tool_ordering.get_filament_change_stats(ToolOrdering::FilamentChangeMode::MultiExtCurr); + // save sorted filament sequences + const auto& layer_tools = tool_ordering.layer_tools(); + for (const auto& lt : layer_tools) + m_sorted_layer_filaments.emplace_back(lt.extruders); + } + // QDS: close powerlost recovery //w30 //{ @@ -2346,6 +2723,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // adds tag for processor file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); + //QDS: mark machine end gcode + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::MachineEndGCodeStart).c_str()); + // Process filament-specific gcode in extruder order. { DynamicConfig config; @@ -2356,7 +2736,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); if (print.config().single_extruder_multi_material) { // Process the filament_end_gcode for the active filament only. - int extruder_id = m_writer.extruder()->id(); + int extruder_id = m_writer.filament()->id(); config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id)); file.writeln(this->placeholder_parser_process("filament_end_gcode", print.config().filament_end_gcode.get_at(extruder_id), extruder_id, &config)); } else { @@ -2366,7 +2746,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato file.writeln(this->placeholder_parser_process("filament_end_gcode", end_gcode, extruder_id, &config)); } } - file.writeln(this->placeholder_parser_process("machine_end_gcode", print.config().machine_end_gcode, m_writer.extruder()->id(), &config)); + file.writeln(this->placeholder_parser_process("machine_end_gcode", print.config().machine_end_gcode, m_writer.filament()->id(), &config)); } file.write(m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100% file.write(m_writer.postamble()); @@ -2455,6 +2835,76 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato print.throw_if_canceled(); } + +//w44 +std::string GCode::generate_box_temp_command(const std::vector& all_extruders, FullPrintConfig m_config) { + std::array vt_values{}; + std::vector temp_parts; + + for (unsigned int extruder : all_extruders) { + if (extruder < 16) { + vt_values[extruder] = m_config.box_temperature.get_at(extruder); + } + } + + for (int i = 0; i < 16; ++i) { + if (vt_values[i] != 0) { + temp_parts.push_back("VT" + std::to_string(i) + "=" + std::to_string(vt_values[i])); + } + } + + if (temp_parts.empty()) { + return ""; + } + else { + return "DISABLE_BOX_HEATER\nBOX_TEMP_SET " + std::accumulate( + std::next(temp_parts.begin()), temp_parts.end(), + temp_parts[0], + [](const std::string& a, const std::string& b) { + return a + " " + b; + } + ); + } +} +// export info requested for filament change +void GCode::export_layer_filaments(GCodeProcessorResult* result) +{ + if (result == nullptr) + return; + + const std::vectorfilament_map = m_config.filament_map.values; // 1 based + std::vectorprev_filament(m_config.nozzle_diameter.size(), -1); + for (size_t idx = 0; idx < m_sorted_layer_filaments.size(); ++idx) { + for (auto f : m_sorted_layer_filaments[idx]) { + int extruder_idx = filament_map[f] - 1; + if (prev_filament[extruder_idx] != -1 && f != prev_filament[extruder_idx]) { + std::pair from_to_pair = { prev_filament[extruder_idx],f }; + auto iter = result->filament_change_count_map.find(from_to_pair); + if (iter == result->filament_change_count_map.end()) + result->filament_change_count_map.emplace(from_to_pair, 1); + else + iter->second += 1; + } + prev_filament[extruder_idx] = f; + } + + // now we do not need sorted data, so we sort the filaments in id order + auto layer_filaments = m_sorted_layer_filaments[idx]; + std::sort(layer_filaments.begin(), layer_filaments.end()); + auto iter = result->layer_filaments.find(layer_filaments); + if (iter == result->layer_filaments.end()) { + result->layer_filaments[layer_filaments].emplace_back(idx, idx); + } + else { + // if layer id is sequential, expand the range + if (iter->second.back().second == idx - 1) + iter->second.back().second = idx; + else + iter->second.emplace_back(idx, idx); + } + } +} + //QDS void GCode::check_placeholder_parser_failed() { @@ -2483,6 +2933,21 @@ void GCode::check_placeholder_parser_failed() } } +size_t GCode::cur_extruder_index() const +{ + //TODO: check if the function is duplicated + //just return m_writer.filament()->extruder_id() + return get_extruder_id(m_writer.filament()->id()); +} + +size_t GCode::get_extruder_id(unsigned int filament_id) const +{ + if (m_print) { + return m_print->get_extruder_id(filament_id); + } + return 0; +} + // Process all layers of all objects (non-sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. @@ -2493,8 +2958,17 @@ void GCode::process_layers( const std::vector>> &layers_to_print, GCodeOutputStream &output_stream) { - // The pipeline is variable: The vase mode filter is optional. + //QDS: get object label id size_t layer_to_print_idx = 0; + std::vector object_label; + + for (const PrintInstance *instance : print_object_instances_ordering) + object_label.push_back(instance->model_instance->get_labeled_id()); + + std::vector layers_results; + layers_results.resize(layers_to_print.size()); + + // The pipeline is variable: The vase mode filter is optional. const auto generator = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print, &layer_to_print_idx](tbb::flow_control& fc) -> GCode::LayerResult { if (layer_to_print_idx == layers_to_print.size()) { @@ -2509,7 +2983,9 @@ void GCode::process_layers( //QDS check_placeholder_parser_failed(); print.throw_if_canceled(); - return this->process_layer(print, layer.second, layer_tools, &layer == &layers_to_print.back(), &print_object_instances_ordering, size_t(-1)); + GCode::LayerResult res = this->process_layer(print, layer.second, layer_tools, &layer == &layers_to_print.back(), &print_object_instances_ordering, tool_ordering.get_most_used_extruder(), size_t(-1)); + res.gcode_store_pos = layer_to_print_idx - 1; + return std::move(res); } }); if (m_spiral_vase) { @@ -2521,21 +2997,90 @@ void GCode::process_layers( slic3r_tbb_filtermode::serial_in_order, [&spiral_mode = *this->m_spiral_vase.get(), & layers_to_print](GCode::LayerResult in) -> GCode::LayerResult { spiral_mode.enable(in.spiral_vase_enable); bool last_layer = in.layer_id == layers_to_print.size() - 1; - return { spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush}; + return {spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush, in.gcode_store_pos}; }); - const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in) -> std::string { - return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); - }); - const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&output_stream](std::string s) { output_stream.write(s); } - ); + std::vector> layers_extruder_adjustments(layers_to_print.size()); + + const auto parsing = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments, object_label](GCode::LayerResult in) -> GCode::LayerResult{ + //record gcode + in.gcode = gcode_editer.process_layer(std::move(in.gcode), in.layer_id, layers_extruder_adjustments[in.gcode_store_pos], object_label, in.cooling_buffer_flush, false); + return std::move(in); + }); + + //step2: cooling + std::vector> layers_wall_collection(layers_to_print.size()); + + CoolingBuffer cooling_processor; + + const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&cooling_processor, &layers_extruder_adjustments](GCode::LayerResult in) -> GCode::LayerResult { + in.layer_time = cooling_processor.calculate_layer_slowdown(layers_extruder_adjustments[in.gcode_store_pos]); + return std::move(in); + }); + + // step 4.1: record node date + SmoothCalculator smooth_calculator(object_label.size()); + + const auto build_node = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&smooth_calculator, &layers_wall_collection, &layers_extruder_adjustments, object_label, &layers_results](GCode::LayerResult in){ + smooth_calculator.build_node(layers_wall_collection[in.gcode_store_pos], object_label, layers_extruder_adjustments[in.gcode_store_pos]); + layers_results[in.gcode_store_pos] = std::move(in); + return; + }); + + // step 5: rewite + const auto write_gocde= tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments](GCode::LayerResult in) -> std::string { + return gcode_editer.write_layer_gcode(std::move(in.gcode), in.layer_id, in.layer_time, layers_extruder_adjustments[in.gcode_store_pos]); + }); + + std::vector gcode_res; + + // QDS: apply new feedrate of outwall and recalculate layer time + int layer_idx = 0; + const auto calculate_layer_time= tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [&layer_idx, &smooth_calculator, &layers_extruder_adjustments, &gcode_res](tbb::flow_control& fc) -> GCode::LayerResult { + if(layer_idx == gcode_res.size()){ + fc.stop(); + return{}; + }else{ + if (layer_idx > 0){ + gcode_res[layer_idx].layer_time = smooth_calculator.recaculate_layer_time(layer_idx, layers_extruder_adjustments[gcode_res[layer_idx].gcode_store_pos]); + } + return gcode_res[layer_idx++]; + } + }); + + + const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&output_stream](std::string s) { output_stream.write(s); }); + + // QDS: apply cooling // The pipeline elements are joined using const references, thus no copying is performed. if (m_spiral_vase) - tbb::parallel_pipeline(12, generator & spiral_mode & cooling & output); - else - tbb::parallel_pipeline(12, generator & cooling & output); + tbb::parallel_pipeline(12, generator & spiral_mode & parsing & cooling & write_gocde & output); + else if (!m_config.z_direction_outwall_speed_continuous) + tbb::parallel_pipeline(12, generator & parsing & cooling & write_gocde & output); + else { + tbb::parallel_pipeline(12, generator & parsing & cooling & build_node); + std::string message; + message = _L("Smoothing z direction speed"); + m_print->set_status(85, message); + //append data + for (const LayerResult &res : layers_results) { + //remove empty gcode layer caused by support independent layers + if (res.cooling_buffer_flush) { + smooth_calculator.append_data(layers_wall_collection[res.gcode_store_pos]); + gcode_res.push_back(std::move(res)); + } + } + + smooth_calculator.smooth_layer_speed(); + message = _L("Exporting G-code"); + m_print->set_status(90, message); + tbb::parallel_pipeline(12, calculate_layer_time & write_gocde & output); + } } // Process all layers of a single object instance (sequential mode) with a parallel pipeline: @@ -2550,8 +3095,21 @@ void GCode::process_layers( // QDS const bool prime_extruder) { + // the pipeline should be + // generatotr + (spira) + parse + (cooling) + (smoothing) + rewrite + // rewrite pipeline to get better schu + + // QDS: get object label id + size_t layer_to_print_idx = 0; + std::vector object_label; + for (LayerToPrint layer : layers_to_print) + object_label.push_back(layer.original_object->instances()[single_object_idx].model_instance->get_labeled_id()); + + std::vector layers_results; + layers_results.resize(layers_to_print.size()); + + //step 1: generator // The pipeline is variable: The vase mode filter is optional. - size_t layer_to_print_idx = 0; const auto generator = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [this, &print, &tool_ordering, &layers_to_print, &layer_to_print_idx, single_object_idx, prime_extruder](tbb::flow_control& fc) -> GCode::LayerResult { if (layer_to_print_idx == layers_to_print.size()) { @@ -2563,7 +3121,9 @@ void GCode::process_layers( //QDS check_placeholder_parser_failed(); print.throw_if_canceled(); - return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, single_object_idx, prime_extruder); + GCode::LayerResult res = this->process_layer(print, {std::move(layer)}, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, tool_ordering.get_most_used_extruder(), single_object_idx, prime_extruder); + res.gcode_store_pos = layer_to_print_idx - 1; + return std::move(res); } }); if (m_spiral_vase) { @@ -2575,27 +3135,98 @@ void GCode::process_layers( slic3r_tbb_filtermode::serial_in_order, [&spiral_mode = *this->m_spiral_vase.get(), &layers_to_print](GCode::LayerResult in) -> GCode::LayerResult { spiral_mode.enable(in.spiral_vase_enable); bool last_layer = in.layer_id == layers_to_print.size() - 1; - return { spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush }; + return {spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush, in.gcode_store_pos}; }); - const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in)->std::string { - return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); - }); - const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&output_stream](std::string s) { output_stream.write(s); } - ); + //QDS: get objects and nodes info, for better arrange + const ConstPrintObjectPtrsAdaptor &objects = print.objects(); + + // step 2: parse + std::vector> layers_extruder_adjustments(layers_to_print.size()); + + const auto parsing = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments, object_label](GCode::LayerResult in) -> GCode::LayerResult{ + //record gcode + in.gcode = gcode_editer.process_layer(std::move(in.gcode), in.layer_id, layers_extruder_adjustments[in.gcode_store_pos], object_label, in.cooling_buffer_flush, false); + return std::move(in); + }); + + // step 3: cooling + std::vector> layers_wall_collection(layers_to_print.size()); + CoolingBuffer cooling_processor; + + const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&cooling_processor, &layers_extruder_adjustments](GCode::LayerResult in) -> GCode::LayerResult { + in.layer_time = cooling_processor.calculate_layer_slowdown(layers_extruder_adjustments[in.gcode_store_pos]); + return std::move(in); + }); + + // step 4.1: record node date + SmoothCalculator smooth_calculator(object_label.size()); + + const auto build_node = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&smooth_calculator, &layers_wall_collection, &layers_extruder_adjustments, object_label, &layers_results](GCode::LayerResult in){ + smooth_calculator.build_node(layers_wall_collection[in.gcode_store_pos], object_label, layers_extruder_adjustments[in.gcode_store_pos]); + layers_results[in.gcode_store_pos] = std::move(in); + return; + }); + + // step 5: rewite + const auto write_gocde= tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments](GCode::LayerResult in) -> std::string { + return gcode_editer.write_layer_gcode(std::move(in.gcode), in.layer_id, in.layer_time, layers_extruder_adjustments[in.gcode_store_pos]); + }); + + std::vector gcode_res; + + // QDS: apply new feedrate of outwall and recalculate layer time + int layer_idx = 0; + //restart pipeline + const auto calculate_layer_time = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [&layer_idx, &gcode_res, &smooth_calculator, &layers_extruder_adjustments](tbb::flow_control& fc) -> GCode::LayerResult { + if(layer_idx == gcode_res.size()){ + fc.stop(); + return{}; + }else{ + if (layer_idx > 0) { + gcode_res[layer_idx].layer_time = smooth_calculator.recaculate_layer_time(layer_idx, layers_extruder_adjustments[gcode_res[layer_idx].gcode_store_pos]); + } + return gcode_res[layer_idx++]; + } + }); + + + const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&output_stream](std::string s) { output_stream.write(s); }); + + // QDS: apply cooling // The pipeline elements are joined using const references, thus no copying is performed. if (m_spiral_vase) - tbb::parallel_pipeline(12, generator & spiral_mode & cooling & output); - else - tbb::parallel_pipeline(12, generator & cooling & output); + tbb::parallel_pipeline(12, generator & spiral_mode & parsing & cooling & write_gocde & output); + else if (!m_config.z_direction_outwall_speed_continuous) + tbb::parallel_pipeline(12, generator & parsing & cooling & write_gocde & output); + else { + tbb::parallel_pipeline(12, generator & parsing & cooling & build_node); + // step 4.2: smoothing + // break pipeline and do z smoothing + // append data + for (const LayerResult &res : layers_results) { + // remove empty gcode layer caused by support independent layers + if (res.cooling_buffer_flush) { + smooth_calculator.append_data(layers_wall_collection[res.gcode_store_pos]); + gcode_res.push_back(res); + } + } + + smooth_calculator.smooth_layer_speed(); + + tbb::parallel_pipeline(12, calculate_layer_time & write_gocde & output); + } } -std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) +std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_filament_id, const DynamicConfig *config_override) { try { - return m_placeholder_parser.process(templ, current_extruder_id, config_override, &m_placeholder_parser_context); + return m_placeholder_parser.process(templ, current_filament_id, config_override, &m_placeholder_parser_context); } catch (std::runtime_error &err) { // Collect the names of failed template substitutions for error reporting. auto it = m_placeholder_parser_failed_templates.find(name); @@ -2677,19 +3308,20 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc // Print the machine envelope G-code for the Marlin firmware based on the "machine_max_xxx" parameters. // Do not process this piece of G-code by the time estimator, it already knows the values through another sources. -void GCode::print_machine_envelope(GCodeOutputStream &file, Print &print) +void GCode::print_machine_envelope(GCodeOutputStream &file, Print &print, int extruder_id) { + int matched_machine_limit_idx = get_extruder_id(extruder_id) * 2; if (print.config().gcode_flavor.value == gcfMarlinLegacy || print.config().gcode_flavor.value == gcfMarlinFirmware) { file.write_format("M201 X%d Y%d Z%d E%d\n", - int(print.config().machine_max_acceleration_x.values.front() + 0.5), - int(print.config().machine_max_acceleration_y.values.front() + 0.5), - int(print.config().machine_max_acceleration_z.values.front() + 0.5), - int(print.config().machine_max_acceleration_e.values.front() + 0.5)); + int(print.config().machine_max_acceleration_x.values[matched_machine_limit_idx] + 0.5), + int(print.config().machine_max_acceleration_y.values[matched_machine_limit_idx] + 0.5), + int(print.config().machine_max_acceleration_z.values[matched_machine_limit_idx] + 0.5), + int(print.config().machine_max_acceleration_e.values[matched_machine_limit_idx] + 0.5)); file.write_format("M203 X%d Y%d Z%d E%d\n", - int(print.config().machine_max_speed_x.values.front() + 0.5), - int(print.config().machine_max_speed_y.values.front() + 0.5), - int(print.config().machine_max_speed_z.values.front() + 0.5), - int(print.config().machine_max_speed_e.values.front() + 0.5)); + int(print.config().machine_max_speed_x.values[matched_machine_limit_idx] + 0.5), + int(print.config().machine_max_speed_y.values[matched_machine_limit_idx] + 0.5), + int(print.config().machine_max_speed_z.values[matched_machine_limit_idx] + 0.5), + int(print.config().machine_max_speed_e.values[matched_machine_limit_idx] + 0.5)); // Now M204 - acceleration. This one is quite hairy thanks to how Marlin guys care about // Legacy Marlin should export travel acceleration the same as printing acceleration. @@ -2724,6 +3356,15 @@ int GCode::get_bed_temperature(const int extruder_id, const bool is_first_layer, return bed_temp_opt->get_at(extruder_id); } +int GCode::get_highest_bed_temperature(const bool is_first_layer, const Print& print) const +{ + auto bed_type = m_config.curr_bed_type; + int bed_temp = 0; + for (auto fidx : print.get_slice_used_filaments(is_first_layer)) { + bed_temp = std::max(bed_temp, get_bed_temperature(fidx, is_first_layer, bed_type)); + } + return bed_temp; +} // Write 1st layer bed temperatures into the G-code. // Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. @@ -2734,8 +3375,13 @@ void GCode::_print_first_layer_bed_temperature(GCodeOutputStream &file, Print &p // Initial bed temperature based on the first extruder. // QDS std::vector temps_per_bed; - int bed_temp = get_bed_temperature(first_printing_extruder_id, true, print.config().curr_bed_type); - + int bed_temp = 0; + if (m_config.bed_temperature_formula.value == BedTempFormula::btfHighestTemp) { + bed_temp = get_highest_bed_temperature(true, print); + } + else { + bed_temp = get_bed_temperature(first_printing_extruder_id, true, print.config().curr_bed_type); + } // Is the bed temperature set by the provided custom G-code? int temp_by_gcode = -1; bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, false, temp_by_gcode); @@ -3028,10 +3674,8 @@ namespace Skirt { inline std::string get_instance_name(const PrintObject* object, size_t inst_id) { auto obj_name = object->model_object()->name; - - // replace special char in obj_name with '-' - const std::string banned = "\b\t\n\v\f\r \"#%&\'*-./:;<>\\"; - std::replace_if(obj_name.begin(), obj_name.end(), [&banned](char c) { return banned.find(c) != std::string::npos; }, '_'); + // replace space in obj_name with '-' + std::replace(obj_name.begin(), obj_name.end(), ' ', '_'); return (boost::format("%1%_id_%2%_copy_%3%") % obj_name % object->get_klipper_object_id() % inst_id).str(); } @@ -3053,6 +3697,7 @@ GCode::LayerResult GCode::process_layer( const bool last_layer, // Pairs of PrintObject index and its instance index. const std::vector *ordering, + const int most_used_extruder, // If set to size_t(-1), then print all copies of all objects. // Otherwise print a single copy of a single object. const size_t single_object_instance_idx, @@ -3144,61 +3789,50 @@ GCode::LayerResult GCode::process_layer( config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); gcode += this->placeholder_parser_process("before_layer_change_gcode", - print.config().before_layer_change_gcode.value, m_writer.extruder()->id(), &config) + print.config().before_layer_change_gcode.value, m_writer.filament()->id(), &config) + "\n"; } PrinterStructure printer_structure = m_config.printer_structure.value; + PrintSequence print_sequence = m_config.print_sequence; + bool sequence_by_layer = print_sequence == PrintSequence::ByLayer; + bool is_i3_printer = printer_structure == PrinterStructure::psI3; + bool is_multi_extruder = m_config.nozzle_diameter.size() > 1; + bool need_insert_timelapse_gcode_for_traditional = false; - if (printer_structure == PrinterStructure::psI3 && - !m_spiral_vase && - (!m_wipe_tower || !m_wipe_tower->enable_timelapse_print()) && - print.config().print_sequence == PrintSequence::ByLayer) { - need_insert_timelapse_gcode_for_traditional = true; + if (!m_wipe_tower || !m_wipe_tower->enable_timelapse_print()) { + need_insert_timelapse_gcode_for_traditional = ((is_i3_printer && !m_spiral_vase)|| is_multi_extruder); } + bool has_insert_timelapse_gcode = false; bool has_wipe_tower = (layer_tools.has_wipe_tower && m_wipe_tower); - auto insert_timelapse_gcode = [this, print_z, &print]() -> std::string { - std::string gcode_res; - if (!print.config().time_lapse_gcode.value.empty()) { - DynamicConfig config; - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); - gcode_res = this->placeholder_parser_process("timelapse_gcode", print.config().time_lapse_gcode.value, m_writer.extruder()->id(), &config) + "\n"; - } - return gcode_res; - }; + int physical_extruder_id = print.config().physical_extruder_map.get_at(most_used_extruder); + + ZHopType z_hope_type = ZHopType(FILAMENT_CONFIG(z_hop_types)); + LiftType auto_lift_type = LiftType::NormalLift; + if (z_hope_type == ZHopType::zhtAuto || z_hope_type == ZHopType::zhtSpiral || z_hope_type == ZHopType::zhtSlope) + auto_lift_type = LiftType::SpiralLift; // QDS: don't use lazy_raise when enable spiral vase gcode += this->change_layer(print_z); // this will increase m_layer_index m_layer = &layer; m_object_layer_over_raft = false; - if (printer_structure == PrinterStructure::psI3 && !need_insert_timelapse_gcode_for_traditional && !m_spiral_vase && print.config().print_sequence == PrintSequence::ByLayer) { - std::string timepals_gcode = insert_timelapse_gcode(); - gcode += timepals_gcode; - m_writer.set_current_position_clear(false); - //QDS: check whether custom gcode changes the z position. Update if changed - double temp_z_after_timepals_gcode; - if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_timepals_gcode)) { - Vec3d pos = m_writer.get_position(); - pos(2) = temp_z_after_timepals_gcode; - m_writer.set_position(pos); - } - } if (! print.config().layer_change_gcode.value.empty()) { DynamicConfig config; + config.set_key_value("most_used_physical_extruder_id", new ConfigOptionInt(physical_extruder_id)); config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); gcode += this->placeholder_parser_process("layer_change_gcode", - print.config().layer_change_gcode.value, m_writer.extruder()->id(), &config) + print.config().layer_change_gcode.value, m_writer.filament()->id(), &config) + "\n"; config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); } //QDS: set layer time fan speed after layer change gcode gcode += ";_SET_FAN_SPEED_CHANGING_LAYER\n"; + m_writer.set_first_layer(this->on_first_layer()); + if (print.calib_mode() == CalibMode::Calib_PA_Tower) { //w29 //gcode += writer().set_pressure_advance(print.calib_params().start + static_cast(print_z) * print.calib_params().step); @@ -3209,16 +3843,16 @@ GCode::LayerResult GCode::process_layer( } else if (print.calib_mode() == CalibMode::Calib_Vol_speed_Tower) { auto _speed = print.calib_params().start + print_z * print.calib_params().step; - m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloat(std::round(_speed))); + m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloatsNullable({ std::round(_speed) })); } else if (print.calib_mode() == CalibMode::Calib_VFA_Tower) { auto _speed = print.calib_params().start + std::floor(print_z / 5.0) * print.calib_params().step; - m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloat(std::round(_speed))); + m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloatsNullable({ std::round(_speed) })); } else if (print.calib_mode() == CalibMode::Calib_Retraction_tower) { auto _length = print.calib_params().start + std::floor(std::max(0.0, print_z - 0.4)) * print.calib_params().step; DynamicConfig _cfg; - _cfg.set_key_value("retraction_length", new ConfigOptionFloats{_length}); + _cfg.set_key_value("retraction_length", new ConfigOptionFloatsNullable{_length}); writer().config.apply(_cfg); sprintf(buf, "; Calib_Retraction_tower: Z_HEIGHT: %g, length:%g\n", print_z, _length); gcode += buf; @@ -3227,16 +3861,16 @@ GCode::LayerResult GCode::process_layer( //QDS if (first_layer) { //QDS: set first layer global acceleration - if (m_config.default_acceleration.value > 0 && m_config.initial_layer_acceleration.value > 0) { - double acceleration = m_config.initial_layer_acceleration.value; - gcode += m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); + if (m_config.default_acceleration.get_at(cur_extruder_index()) > 0 && m_config.initial_layer_acceleration.get_at(cur_extruder_index()) > 0) { + double acceleration = m_config.initial_layer_acceleration.get_at(cur_extruder_index()); + m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); } //w30 if (m_config.default_jerk.value > 0 && m_config.initial_layer_jerk.value > 0 && this->is_QDT_Printer()) gcode += m_writer.set_jerk_xy(m_config.initial_layer_jerk.value); } - if (! first_layer && ! m_second_layer_things_done) { + if (!first_layer && !m_second_layer_things_done) { //QDS: open powerlost recovery { //w30 @@ -3255,9 +3889,9 @@ GCode::LayerResult GCode::process_layer( } //QDS: reset acceleration at sencond layer - if (m_config.default_acceleration.value > 0 && m_config.initial_layer_acceleration.value > 0) { - double acceleration = m_config.default_acceleration.value; - gcode += m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); + if (m_config.default_acceleration.get_at(cur_extruder_index()) > 0 && m_config.initial_layer_acceleration.get_at(cur_extruder_index()) > 0) { + double acceleration = m_config.default_acceleration.get_at(cur_extruder_index()); + m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); } //w30 if (m_config.default_jerk.value > 0 && m_config.initial_layer_jerk.value > 0 && this->is_QDT_Printer()) @@ -3265,8 +3899,8 @@ GCode::LayerResult GCode::process_layer( // Transition from 1st to 2nd layer. Adjust nozzle temperatures as prescribed by the nozzle dependent // nozzle_temperature_initial_layer vs. temperature settings. - for (const Extruder &extruder : m_writer.extruders()) { - if (print.config().single_extruder_multi_material.value && extruder.id() != m_writer.extruder()->id()) + for (const Extruder& extruder : m_writer.extruders()) { + if (print.config().single_extruder_multi_material.value && extruder.id() != m_writer.filament()->id()) // In single extruder multi material mode, set the temperature for the current extruder only. continue; int temperature = print.config().nozzle_temperature.get_at(extruder.id()); @@ -3275,7 +3909,11 @@ GCode::LayerResult GCode::process_layer( } // QDS - int bed_temp = get_bed_temperature(first_extruder_id, false, print.config().curr_bed_type); + int bed_temp = 0; + if (m_config.bed_temperature_formula == BedTempFormula::btfHighestTemp) + bed_temp = get_highest_bed_temperature(false,print); + else + bed_temp = get_bed_temperature(first_extruder_id, false, m_config.curr_bed_type); gcode += m_writer.set_bed_temperature(bed_temp); //w32 @@ -3298,7 +3936,7 @@ GCode::LayerResult GCode::process_layer( if (single_object_instance_idx == size_t(-1)) { // Normal (non-sequential) print. - gcode += ProcessLayer::emit_custom_gcode_per_print_z(*this, layer_tools.custom_gcode, m_writer.extruder()->id(), first_extruder_id, print.config()); + gcode += ProcessLayer::emit_custom_gcode_per_print_z(*this, layer_tools.custom_gcode, m_writer.filament()->id(), first_extruder_id, print.config()); } // Extrude skirt at the print_z of the raft layers and normal object layers // not at the print_z of the interlaced support material layers. @@ -3308,7 +3946,7 @@ GCode::LayerResult GCode::process_layer( // QDS: get next extruder according to flush and soluble auto get_next_extruder = [&](int current_extruder,const std::vector&extruders) { - std::vector flush_matrix(cast(m_config.flush_volumes_matrix.values)); + std::vector flush_matrix(cast(get_flush_volumes_matrix(m_config.flush_volumes_matrix.values, 0, m_config.nozzle_diameter.values.size()))); const unsigned int number_of_extruders = (unsigned int)(sqrt(flush_matrix.size()) + EPSILON); // Extract purging volumes for each extruder pair: std::vector> wipe_volumes; @@ -3404,6 +4042,15 @@ GCode::LayerResult GCode::process_layer( break; } } + if (print.config().filament_is_support.get_at(dontcare_extruder)) { + // The last extruder printed on the previous layer extrudes support filament. + // Try to find a non-support extruder on the same layer. + for (unsigned int extruder_id : layer_tools.extruders) + if (!print.config().filament_is_support.get_at(extruder_id)) { + dontcare_extruder = extruder_id; + break; + } + } if (support_dontcare) support_extruder = dontcare_extruder; if (interface_dontcare) @@ -3468,7 +4115,8 @@ GCode::LayerResult GCode::process_layer( // The process is almost the same for perimeters and infills - we will do it in a cycle that repeats twice: std::vector printing_extruders; for (const ObjectByExtruder::Island::Region::Type entity_type : { ObjectByExtruder::Island::Region::INFILL, ObjectByExtruder::Island::Region::PERIMETERS }) { - for (const ExtrusionEntity *ee : (entity_type == ObjectByExtruder::Island::Region::INFILL) ? layerm->fills.entities : layerm->perimeters.entities) { + bool is_infill = entity_type == ObjectByExtruder::Island::Region::INFILL; + for (const ExtrusionEntity *ee : is_infill ? layerm->fills.entities : layerm->perimeters.entities) { // extrusions represents infill or perimeter extrusions of a single island. assert(dynamic_cast(ee) != nullptr); const auto *extrusions = static_cast(ee); @@ -3531,6 +4179,94 @@ GCode::LayerResult GCode::process_layer( } } // for objects + std::map> filament_to_print_instances; + { + for (unsigned int filament_id : layer_tools.extruders) { + auto objects_by_extruder_it = by_extruder.find(filament_id); + if (objects_by_extruder_it == by_extruder.end()) continue; + + bool has_prime_tower = print.config().enable_prime_tower && print.extruders().size() > 1 && + (print.config().print_sequence == PrintSequence::ByLayer || + (print.config().print_sequence == PrintSequence::ByObject && print.objects().size() == 1)); + if (has_prime_tower) { + int plate_idx = print.get_plate_index(); + Point wt_pos(print.config().wipe_tower_x.get_at(plate_idx), print.config().wipe_tower_y.get_at(plate_idx)); + + std::vector &objects_by_extruder = objects_by_extruder_it->second; + std::vector print_objects; + for (int obj_idx = 0; obj_idx < objects_by_extruder.size(); obj_idx++) { + auto &object_by_extruder = objects_by_extruder[obj_idx]; + if (object_by_extruder.islands.empty() && (object_by_extruder.support == nullptr || object_by_extruder.support->empty())) continue; + + print_objects.push_back(print.get_object(obj_idx)); + } + + std::vector new_ordering = chain_print_object_instances(print_objects, &wt_pos); + std::reverse(new_ordering.begin(), new_ordering.end()); + filament_to_print_instances[filament_id] = sort_print_object_instances(objects_by_extruder_it->second, layers, &new_ordering, single_object_instance_idx); + } else { + filament_to_print_instances[filament_id] = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx); + } + } + } + + std::set layer_object_label_ids; + for (auto iter = filament_to_print_instances.begin(); iter != filament_to_print_instances.end(); ++iter) { + for (const InstanceToPrint &instance : iter->second) { + layer_object_label_ids.insert(instance.label_object_id); + } + } + + auto insert_timelapse_gcode = [this, print_z, &print, &physical_extruder_id, &layer_object_label_ids]() -> std::string { + std::string timepals_gcode; + if (!print.config().time_lapse_gcode.value.empty()) { + DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + config.set_key_value("most_used_physical_extruder_id", new ConfigOptionInt(physical_extruder_id)); + timepals_gcode = this->placeholder_parser_process("timelapse_gcode", print.config().time_lapse_gcode.value, m_writer.filament()->id(), &config) + "\n"; + } + m_writer.set_current_position_clear(false); + + double temp_z_after_tool_change; + if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_tool_change)) { + Vec3d pos = m_writer.get_position(); + pos(2) = temp_z_after_tool_change; + m_writer.set_position(pos); + } + + // (layer_object_label_ids.size() < 64) this restriction comes from _encode_label_ids_to_base64() + if (print.is_QDT_Printer() && + (print.num_object_instances() <= g_max_label_object) && // Don't support too many objects on one plate + (print.num_object_instances() > 1) && // Don't support skipping single object + (print.calib_params().mode == CalibMode::Calib_None)) { + std::ostringstream oss; + for (auto it = layer_object_label_ids.begin(); it != layer_object_label_ids.end(); ++it) { + if (it != layer_object_label_ids.begin()) oss << ","; + oss << *it; + } + + //w44 + /*std::string start_str = std::string("; object ids of layer ") + std::to_string(m_layer_index + 1) + (" start: ") + oss.str() + "\n"; + start_str += "M624 " + _encode_label_ids_to_base64(std::vector(layer_object_label_ids.begin(), layer_object_label_ids.end())) + "\n"; + + std::string end_str = std::string("; object ids of this layer") + std::to_string(m_layer_index + 1) + (" end: ") + oss.str() + "\n"; + end_str += "M625\n"; + + timepals_gcode = start_str + timepals_gcode + end_str; */ + } + + return timepals_gcode; + }; + + if (!need_insert_timelapse_gcode_for_traditional) { // Equivalent to the timelapse gcode placed in layer_change_gcode + if (FILAMENT_CONFIG(retract_when_changing_layer)) { + gcode += this->retract(false, false, auto_lift_type, true); + } + gcode += insert_timelapse_gcode(); + } + if (m_wipe_tower) m_wipe_tower->set_is_first_print(true); @@ -3540,24 +4276,33 @@ GCode::LayerResult GCode::process_layer( if (has_wipe_tower) { if (!m_wipe_tower->is_empty_wipe_tower_gcode(*this, extruder_id, extruder_id == layer_tools.extruders.back())) { if (need_insert_timelapse_gcode_for_traditional && !has_insert_timelapse_gcode) { - gcode += this->retract(false, false, LiftType::NormalLift); - m_writer.add_object_change_labels(gcode); - - std::string timepals_gcode = insert_timelapse_gcode(); - gcode += timepals_gcode; - m_writer.set_current_position_clear(false); - //QDS: check whether custom gcode changes the z position. Update if changed - double temp_z_after_timepals_gcode; - if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_timepals_gcode)) { - Vec3d pos = m_writer.get_position(); - pos(2) = temp_z_after_timepals_gcode; - m_writer.set_position(pos); + bool should_insert = true; + if (m_config.nozzle_diameter.values.size() == 2){ + if (!writer().filament() || get_extruder_id(writer().filament()->id()) != most_used_extruder) { + should_insert = false; + } + } + + if (should_insert) { + gcode += this->retract(false, false, auto_lift_type, true); + m_writer.add_object_change_labels(gcode); + + gcode += insert_timelapse_gcode(); + has_insert_timelapse_gcode = true; } - has_insert_timelapse_gcode = true; } gcode += m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back()); } } else { + if (m_writer.need_toolchange(extruder_id) && + m_config.nozzle_diameter.values.size() == 2 && writer().filament() && + (get_extruder_id(writer().filament()->id()) == most_used_extruder)) { + gcode += this->retract(false, false, auto_lift_type, true); + m_writer.add_object_change_labels(gcode); + + gcode += insert_timelapse_gcode(); + has_insert_timelapse_gcode = true; + } gcode += this->set_extruder(extruder_id, print_z); } @@ -3579,7 +4324,7 @@ GCode::LayerResult GCode::process_layer( path.mm3_per_mm = mm3_per_mm; } //FIXME using the support_speed of the 1st object printed. - gcode += this->extrude_loop(loop, "skirt", m_config.support_speed.value); + gcode += this->extrude_loop(loop, "skirt", m_config.support_speed.get_at(cur_extruder_index())); } m_avoid_crossing_perimeters.use_external_mp(false); // Allow a straight travel move to the first object point if this is the first layer (but don't in next layers). @@ -3587,37 +4332,7 @@ GCode::LayerResult GCode::process_layer( m_avoid_crossing_perimeters.disable_once(); } - auto objects_by_extruder_it = by_extruder.find(extruder_id); - if (objects_by_extruder_it == by_extruder.end()) - continue; - - // QDS: ordering instances by extruder - std::vector instances_to_print; - bool has_prime_tower = print.config().enable_prime_tower - && print.extruders().size() > 1 - && (print.config().print_sequence == PrintSequence::ByLayer - || (print.config().print_sequence == PrintSequence::ByObject && print.objects().size() == 1)); - if (has_prime_tower) { - int plate_idx = print.get_plate_index(); - Point wt_pos(print.config().wipe_tower_x.get_at(plate_idx), print.config().wipe_tower_y.get_at(plate_idx)); - - std::vector& objects_by_extruder = objects_by_extruder_it->second; - std::vector print_objects; - for (int obj_idx = 0; obj_idx < objects_by_extruder.size(); obj_idx++) { - auto& object_by_extruder = objects_by_extruder[obj_idx]; - if (object_by_extruder.islands.empty() && (object_by_extruder.support == nullptr || object_by_extruder.support->empty())) - continue; - - print_objects.push_back(print.get_object(obj_idx)); - } - - std::vector new_ordering = chain_print_object_instances(print_objects, &wt_pos); - std::reverse(new_ordering.begin(), new_ordering.end()); - instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, &new_ordering, single_object_instance_idx); - } - else { - instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx); - } + std::vector &instances_to_print = filament_to_print_instances[extruder_id]; // QDS if (print.has_skirt() && print.config().print_sequence == PrintSequence::ByObject && prime_extruder && first_layer && extruder_id == first_extruder_id) { @@ -3634,7 +4349,7 @@ GCode::LayerResult GCode::process_layer( set_origin(unscaled(offset)); for (ExtrusionEntity* ee : layer.object()->object_skirt().entities) //FIXME using the support_speed of the 1st object printed. - gcode += this->extrude_entity(*ee, "skirt", m_config.support_speed.value); + gcode += this->extrude_entity(*ee, "skirt", m_config.support_speed.get_at(cur_extruder_index())); } } @@ -3655,6 +4370,8 @@ GCode::LayerResult GCode::process_layer( if (m_config.reduce_crossing_wall) m_avoid_crossing_perimeters.init_layer(*m_layer); + //QDS: label object id, prepare for cooling + gcode += "; OBJECT_ID: " + std::to_string(instance_to_print.label_object_id) + "\n"; std::string temp_start_str; if (m_enable_label_object) { std::string start_str = std::string("; start printing object, unique label id: ") + std::to_string(instance_to_print.label_object_id) + "\n"; @@ -3695,7 +4412,7 @@ GCode::LayerResult GCode::process_layer( this->set_origin(0., 0.); m_avoid_crossing_perimeters.use_external_mp(); for (const ExtrusionEntity* ee : print.m_supportBrimMap.at(instance_to_print.print_object.id()).entities) { - gcode += this->extrude_entity(*ee, "brim", m_config.support_speed.value); + gcode += this->extrude_entity(*ee, "brim", m_config.support_speed.get_at(cur_extruder_index())); } m_avoid_crossing_perimeters.use_external_mp(false); // Allow a straight travel move to the first object point. @@ -3736,7 +4453,7 @@ GCode::LayerResult GCode::process_layer( this->set_origin(0., 0.); m_avoid_crossing_perimeters.use_external_mp(); for (const ExtrusionEntity* ee : print.m_brimMap.at(instance_to_print.print_object.id()).entities) { - gcode += this->extrude_entity(*ee, "brim", m_config.support_speed.value); + gcode += this->extrude_entity(*ee, "brim", m_config.support_speed.get_at(cur_extruder_index())); } m_avoid_crossing_perimeters.use_external_mp(false); // Allow a straight travel move to the first object point. @@ -3765,8 +4482,9 @@ GCode::LayerResult GCode::process_layer( //QDS: for first layer, we always print wall firstly to get better bed adhesive force //This behaviour is same with cura if (is_infill_first && !first_layer) { - if (!has_wipe_tower && need_insert_timelapse_gcode_for_traditional && !has_insert_timelapse_gcode && has_infill(by_region_specific)) { - gcode += this->retract(false, false, LiftType::NormalLift); + if (!has_wipe_tower && need_insert_timelapse_gcode_for_traditional && printer_structure == PrinterStructure::psI3 + && !has_insert_timelapse_gcode && has_infill(by_region_specific)) { + gcode += this->retract(false, false, auto_lift_type, true); if (!temp_start_str.empty() && m_writer.empty_object_start_str()) { std::string end_str = std::string("; stop printing object, unique label id: ") + std::to_string(instance_to_print.label_object_id) + "\n"; //w30 @@ -3775,16 +4493,7 @@ GCode::LayerResult GCode::process_layer( gcode += end_str; } - std::string timepals_gcode = insert_timelapse_gcode(); - gcode += timepals_gcode; - m_writer.set_current_position_clear(false); - //QDS: check whether custom gcode changes the z position. Update if changed - double temp_z_after_timepals_gcode; - if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_timepals_gcode)) { - Vec3d pos = m_writer.get_position(); - pos(2) = temp_z_after_timepals_gcode; - m_writer.set_position(pos); - } + gcode += insert_timelapse_gcode(); if (!temp_start_str.empty() && m_writer.empty_object_start_str()) gcode += temp_start_str; @@ -3795,8 +4504,9 @@ GCode::LayerResult GCode::process_layer( gcode += this->extrude_perimeters(print, by_region_specific); } else { gcode += this->extrude_perimeters(print, by_region_specific); - if (!has_wipe_tower && need_insert_timelapse_gcode_for_traditional && !has_insert_timelapse_gcode && has_infill(by_region_specific)) { - gcode += this->retract(false, false, LiftType::NormalLift); + if (!has_wipe_tower && need_insert_timelapse_gcode_for_traditional && printer_structure == PrinterStructure::psI3 + && !has_insert_timelapse_gcode && has_infill(by_region_specific)) { + gcode += this->retract(false, false, auto_lift_type, true); if (!temp_start_str.empty() && m_writer.empty_object_start_str()) { std::string end_str = std::string("; stop printing object, unique label id: ") + std::to_string(instance_to_print.label_object_id) + "\n"; //w30 @@ -3805,16 +4515,7 @@ GCode::LayerResult GCode::process_layer( gcode += end_str; } - std::string timepals_gcode = insert_timelapse_gcode(); - gcode += timepals_gcode; - m_writer.set_current_position_clear(false); - //QDS: check whether custom gcode changes the z position. Update if changed - double temp_z_after_timepals_gcode; - if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_timepals_gcode)) { - Vec3d pos = m_writer.get_position(); - pos(2) = temp_z_after_timepals_gcode; - m_writer.set_position(pos); - } + gcode += insert_timelapse_gcode(); if (!temp_start_str.empty() && m_writer.empty_object_start_str()) gcode += temp_start_str; @@ -3864,8 +4565,8 @@ GCode::LayerResult GCode::process_layer( gcode = m_spiral_vase->process_layer(std::move(gcode)); // Apply cooling logic; this may alter speeds. - if (m_cooling_buffer) - gcode = m_cooling_buffer->process_layer(std::move(gcode), layer.id(), + if (m_gcode_editer) + gcode = m_gcode_editer->process_layer(std::move(gcode), layer.id(), // Flush the cooling buffer at each object layer or possibly at the last layer, even if it contains just supports (This should not happen). object_layer || last_layer); @@ -3883,23 +4584,26 @@ GCode::LayerResult GCode::process_layer( BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << log_memory_info(); - if (!has_wipe_tower && need_insert_timelapse_gcode_for_traditional && !has_insert_timelapse_gcode) { - if (m_support_traditional_timelapse) + if (need_insert_timelapse_gcode_for_traditional && !has_insert_timelapse_gcode) { + // The traditional model of thin-walled object will have flaws for I3 + if (m_support_traditional_timelapse + && printer_structure == PrinterStructure::psI3 + && m_config.timelapse_type.value == TimelapseType::tlTraditional) m_support_traditional_timelapse = false; - gcode += this->retract(false, false, LiftType::NormalLift); + // The traditional model will have flaws for multi_extruder when switching extruder + if (m_config.nozzle_diameter.values.size() == 2 + && m_support_traditional_timelapse + && m_config.timelapse_type.value == TimelapseType::tlTraditional + && (writer().filament() && get_extruder_id(writer().filament()->id()) != most_used_extruder)) { + m_support_traditional_timelapse = false; + } + if (FILAMENT_CONFIG(retract_when_changing_layer)) { + gcode += this->retract(false, false, auto_lift_type, true); + } m_writer.add_object_change_labels(gcode); - std::string timepals_gcode = insert_timelapse_gcode(); - gcode += timepals_gcode; - m_writer.set_current_position_clear(false); - //QDS: check whether custom gcode changes the z position. Update if changed - double temp_z_after_timepals_gcode; - if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_timepals_gcode)) { - Vec3d pos = m_writer.get_position(); - pos(2) = temp_z_after_timepals_gcode; - m_writer.set_position(pos); - } + gcode += insert_timelapse_gcode(); } result.gcode = std::move(gcode); @@ -3987,10 +4691,10 @@ std::string GCode::change_layer(coordf_t print_z) //QDS //coordf_t z = print_z + m_config.z_offset.value; // in unscaled coordinates coordf_t z = print_z; // in unscaled coordinates - if (EXTRUDER_CONFIG(retract_when_changing_layer) && m_writer.will_move_z(z)) { - LiftType lift_type = this->to_lift_type(ZHopType(EXTRUDER_CONFIG(z_hop_types))); + if (FILAMENT_CONFIG(retract_when_changing_layer) && m_writer.will_move_z(z)) { + LiftType lift_type = this->to_lift_type(ZHopType(FILAMENT_CONFIG(z_hop_types))); //QDS: force to use SpiralLift when change layer if lift type is auto - gcode += this->retract(false, false, ZHopType(EXTRUDER_CONFIG(z_hop_types)) == ZHopType::zhtAuto ? LiftType::SpiralLift : lift_type); + gcode += this->retract(false, false, ZHopType(FILAMENT_CONFIG(z_hop_types)) == ZHopType::zhtAuto ? LiftType::SpiralLift : lift_type); } m_writer.add_object_change_labels(gcode); @@ -4055,7 +4759,6 @@ static bool has_overhang_path_on_slope(const ExtrusionLoop &loop, double slope_l return false; } -//1.9.5 static std::map overhang_speed_key_map = { {1, "overhang_1_4_speed"}, @@ -4068,51 +4771,53 @@ static std::map overhang_speed_key_map = double GCode::get_path_speed(const ExtrusionPath &path) { - double min_speed = double(m_config.slow_down_min_speed.get_at(m_writer.extruder()->id())); + double min_speed = double(m_config.slow_down_min_speed.get_at(m_writer.filament()->id())); // set speed double speed = 0; if (path.role() == erPerimeter) { - speed = m_config.get_abs_value("inner_wall_speed"); - if (m_config.enable_overhang_speed.value) { + speed = m_config.inner_wall_speed.get_at(cur_extruder_index()); + if (m_config.enable_overhang_speed.get_at(cur_extruder_index())) { double new_speed = 0; new_speed = get_overhang_degree_corr_speed(speed, path.overhang_degree); speed = new_speed == 0.0 ? speed : new_speed; } } else if (path.role() == erExternalPerimeter) { - speed = m_config.get_abs_value("outer_wall_speed"); - if (m_config.enable_overhang_speed.value) { + speed = m_config.outer_wall_speed.get_at(cur_extruder_index()); + if (m_config.enable_overhang_speed.get_at(cur_extruder_index())) { double new_speed = 0; new_speed = get_overhang_degree_corr_speed(speed, path.overhang_degree); speed = new_speed == 0.0 ? speed : new_speed; } - // 1.9.5 - } else if (path.role() == erOverhangPerimeter && path.overhang_degree == 5) - speed = m_config.get_abs_value("overhang_totally_speed"); + } + else if (path.role() == erOverhangPerimeter && path.overhang_degree == 5) + speed = m_config.overhang_totally_speed.get_at(cur_extruder_index()); else if (path.role() == erOverhangPerimeter || path.role() == erBridgeInfill || path.role() == erSupportTransition) { - speed = m_config.get_abs_value("bridge_speed"); + speed = m_config.bridge_speed.get_at(cur_extruder_index()); } auto _mm3_per_mm = path.mm3_per_mm * double(m_curr_print->calib_mode() == CalibMode::Calib_Flow_Rate ? this->config().print_flow_ratio.value : 1); // QDS: if not set the speed, then use the filament_max_volumetric_speed directly + double filament_max_volumetric_speed = FILAMENT_CONFIG(filament_max_volumetric_speed); if (speed == 0) { if (_mm3_per_mm > 0) - speed = EXTRUDER_CONFIG(filament_max_volumetric_speed) / _mm3_per_mm; + speed = filament_max_volumetric_speed / _mm3_per_mm; else - speed = EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm; + speed = filament_max_volumetric_speed / path.mm3_per_mm; } if (this->on_first_layer()) { // QDS: for solid infill of initial layer, speed can be higher as long as // wall lines have be attached - if (path.role() != erBottomSurface) speed = m_config.get_abs_value("initial_layer_speed"); + if (path.role() != erBottomSurface) speed = m_config.initial_layer_speed.get_at(cur_extruder_index()); } - if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) { - double extrude_speed = EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm; - if (_mm3_per_mm > 0) extrude_speed = EXTRUDER_CONFIG(filament_max_volumetric_speed) / _mm3_per_mm; + if (filament_max_volumetric_speed > 0) { + double extrude_speed = filament_max_volumetric_speed / path.mm3_per_mm; + if (_mm3_per_mm > 0) extrude_speed = filament_max_volumetric_speed / _mm3_per_mm; // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) speed = std::min(speed, extrude_speed); } + return speed; } @@ -4123,21 +4828,22 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // extrude all loops ccw bool was_clockwise = loop.make_counter_clockwise(); - bool is_hole = loop.loop_role() == elrPerimeterHole; + bool is_hole = loop.loop_role() & elrPerimeterHole; // find the point of the loop that is closest to the current extruder position // or randomize if requested Point last_pos = this->last_pos(); + bool satisfy_scarf_seam_angle_threshold = false; if (!m_config.spiral_mode && description == "perimeter") { assert(m_layer != nullptr); bool is_outer_wall_first = m_config.wall_sequence == WallSequence::OuterInner; - m_seam_placer.place_seam(m_layer, loop, is_outer_wall_first, this->last_pos()); + m_seam_placer.place_seam(m_layer, loop, is_outer_wall_first, this->last_pos(), satisfy_scarf_seam_angle_threshold); } else loop.split_at(last_pos, false); // QDS: not apply on fist layer, too small E has stick issue with hotend plate - int filament_scarf_type = EXTRUDER_CONFIG(filament_scarf_seam_type); - bool enable_seam_slope = (filament_scarf_type == int(SeamScarfType::External) && !is_hole) || - filament_scarf_type == int(SeamScarfType::All) && + int filament_scarf_type = FILAMENT_CONFIG(filament_scarf_seam_type); + bool enable_seam_slope = ((filament_scarf_type == int(SeamScarfType::External) && !is_hole) || + filament_scarf_type == int(SeamScarfType::All)) && !m_config.spiral_mode && (loop.role() == erExternalPerimeter || (loop.role() == erPerimeter && m_config.seam_slope_inner_walls)) && @@ -4145,8 +4851,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou if (enable_seam_slope && m_config.seam_slope_conditional.value) { //QDS: the seam has been decide, only check the seam position angle - const auto nozzle_diameter = EXTRUDER_CONFIG(nozzle_diameter); - enable_seam_slope = loop.check_seam_point_angle(m_config.scarf_angle_threshold.value * M_PI / 180.0, nozzle_diameter); + enable_seam_slope = satisfy_scarf_seam_angle_threshold; } // clip the path to avoid the extruder to get exactly on the first point of the loop; @@ -4156,13 +4861,17 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou const double clip_length = m_enable_loop_clipping && !enable_seam_slope ? seam_gap : 0; // get paths ExtrusionPaths paths; + bool set_holes_and_compensation_speed = loop.get_customize_flag() && !loop.has_overhang_paths(); + if (set_holes_and_compensation_speed && m_config.apply_scarf_seam_on_circles.value) { + enable_seam_slope = true; + } loop.clip_end(clip_length, &paths); if (paths.empty()) return ""; double small_peri_speed=-1; // apply the small perimeter speed - if (speed==-1 && loop.length() <= SMALL_PERIMETER_LENGTH(m_config.small_perimeter_threshold.value)) - small_peri_speed = m_config.small_perimeter_speed.get_abs_value(m_config.outer_wall_speed); + if (speed==-1 && loop.length() <= SMALL_PERIMETER_LENGTH(m_config.small_perimeter_threshold.get_at(cur_extruder_index()))) + small_peri_speed = m_config.small_perimeter_speed.get_at(cur_extruder_index()).get_abs_value(m_config.outer_wall_speed.get_at(cur_extruder_index())); // extrude along the path std::string gcode; @@ -4184,15 +4893,15 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou if (enable_seam_slope) { // Create seam slope double start_slope_ratio; - if (EXTRUDER_CONFIG(filament_scarf_height).percent) - start_slope_ratio = EXTRUDER_CONFIG(filament_scarf_height).value / 100; + if (FILAMENT_CONFIG(filament_scarf_height).percent) + start_slope_ratio = FILAMENT_CONFIG(filament_scarf_height).value / 100; else { - start_slope_ratio = EXTRUDER_CONFIG(filament_scarf_height).value / paths.front().height; + start_slope_ratio = FILAMENT_CONFIG(filament_scarf_height).value / paths.front().height; } - float slope_gap = EXTRUDER_CONFIG(filament_scarf_gap).get_abs_value(scale_(EXTRUDER_CONFIG(nozzle_diameter))); + float slope_gap = FILAMENT_CONFIG(filament_scarf_gap).get_abs_value(scale_(EXTRUDER_CONFIG(nozzle_diameter))); - double scarf_seam_length = EXTRUDER_CONFIG(filament_scarf_length); + double scarf_seam_length = FILAMENT_CONFIG(filament_scarf_length); double loop_length = 0.; for (const auto &path : paths) { @@ -4221,8 +4930,8 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // Then extrude it for (const auto &p : new_loop.get_all_paths()) { //w16 - m_resonance_avoidance = m_config.resonance_avoidance; - gcode += this->_extrude(*p, description, speed_for_path(*p, m_resonance_avoidance)); + m_resonance_avoidance = m_config.resonance_avoidance.get_at(cur_extruder_index()); + gcode += this->_extrude(*p, description, speed_for_path(*p, m_resonance_avoidance), set_holes_and_compensation_speed); } set_last_scarf_seam_flag(true); @@ -4246,10 +4955,11 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // QDS: smooth speed of discontinuity areas if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && (loop.role() == erExternalPerimeter || loop.role() == erPerimeter)) smooth_speed_discontinuity_area(paths); + for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) { //w16 - m_resonance_avoidance = m_config.resonance_avoidance; - gcode += this->_extrude(*path, description, speed_for_path(*path, m_resonance_avoidance)); + m_resonance_avoidance = m_config.resonance_avoidance.get_at(cur_extruder_index()); + gcode += this->_extrude(*path, description, speed_for_path(*path, m_resonance_avoidance), set_holes_and_compensation_speed); } set_last_scarf_seam_flag(false); } @@ -4257,7 +4967,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou //QDS: don't reset acceleration when printing first layer. During first layer, acceleration is always same value. if (!this->on_first_layer()) { // reset acceleration - gcode += m_writer.set_acceleration((unsigned int) (m_config.default_acceleration.value + 0.5)); + m_writer.set_acceleration((unsigned int) (m_config.default_acceleration.get_at(cur_extruder_index()) + 0.5)); //w30 if (this->is_QDT_Printer()) gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); @@ -4265,7 +4975,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // QDS - if (m_wipe.enable) { + if (m_wipe.enable && FILAMENT_CONFIG(wipe)) { m_wipe.path = Polyline(); for (ExtrusionPath &path : paths) { //QDS: Don't need to save duplicated point into wipe path @@ -4328,7 +5038,7 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string gcode += this->_extrude(path, description, speed); // QDS - if (m_wipe.enable) { + if (m_wipe.enable && FILAMENT_CONFIG(wipe)) { m_wipe.path = Polyline(); for (ExtrusionPath &path : multipath.paths) { //QDS: Don't need to save duplicated point into wipe path @@ -4343,7 +5053,7 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string //QDS: don't reset acceleration when printing first layer. During first layer, acceleration is always same value. if (!this->on_first_layer()) { // reset acceleration - gcode += m_writer.set_acceleration((unsigned int) floor(m_config.default_acceleration.value + 0.5)); + m_writer.set_acceleration((unsigned int) floor(m_config.default_acceleration.get_at(cur_extruder_index()) + 0.5)); //w30 if (this->is_QDT_Printer()) gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); @@ -4367,15 +5077,16 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des std::string GCode::extrude_path(ExtrusionPath path, std::string description, double speed) { // description += ExtrusionEntity::role_to_string(path.role()); - std::string gcode = this->_extrude(path, description, speed); - if (m_wipe.enable) { + bool flag = path.get_customize_flag() == CustomizeFlag::cfFloatingVerticalShell; + std::string gcode = this->_extrude(path, description, speed,flag); + if (m_wipe.enable && FILAMENT_CONFIG(wipe)) { m_wipe.path = std::move(path.polyline); m_wipe.path.reverse(); } //QDS: don't reset acceleration when printing first layer. During first layer, acceleration is always same value. if (!this->on_first_layer()) { // reset acceleration - gcode += m_writer.set_acceleration((unsigned int) floor(m_config.default_acceleration.value + 0.5)); + m_writer.set_acceleration((unsigned int) floor(m_config.default_acceleration.get_at(cur_extruder_index()) + 0.5)); //w30 if (this->is_QDT_Printer()) gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); @@ -4391,8 +5102,18 @@ std::string GCode::extrude_perimeters(const Print &print, const std::vectorget_cooling_node(); + if (ee_node_id != cooling_node) { + gcode += "; COOLING_NODE: " + std::to_string(ee_node_id) + "\n"; + } + gcode += this->extrude_entity(*ee, "perimeter", -1.); + } } return gcode; } @@ -4434,8 +5155,8 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill std::string gcode; if (! support_fills.entities.empty()) { - const double support_speed = m_config.support_speed.value; - const double support_interface_speed = m_config.get_abs_value("support_interface_speed"); + const double support_speed = m_config.support_speed.get_at(cur_extruder_index()); + const double support_interface_speed = m_config.support_interface_speed.get_at(cur_extruder_index()); for (const ExtrusionEntity *ee : support_fills.entities) { ExtrusionRole role = ee->role(); assert(role == erSupportMaterial || role == erSupportMaterialInterface || role == erSupportTransition); @@ -4558,12 +5279,12 @@ double GCode::get_overhang_degree_corr_speed(float normal_speed, double path_deg int lower_degree_bound = int(path_degree); // QDS: use lower speed of 75%-100% for better cooling if (path_degree >= 4 || path_degree == lower_degree_bound) - return m_config.get_abs_value(overhang_speed_key_map[lower_degree_bound].c_str()); + return m_config.get_abs_value_at(overhang_speed_key_map[lower_degree_bound].c_str(), cur_extruder_index()); int upper_degree_bound = lower_degree_bound + 1; - double lower_speed_bound = lower_degree_bound == 0 ? normal_speed : m_config.get_abs_value(overhang_speed_key_map[lower_degree_bound].c_str()); - double upper_speed_bound = upper_degree_bound == 0 ? normal_speed : m_config.get_abs_value(overhang_speed_key_map[upper_degree_bound].c_str()); + double lower_speed_bound = lower_degree_bound == 0 ? normal_speed : m_config.get_abs_value_at(overhang_speed_key_map[lower_degree_bound].c_str(), cur_extruder_index()); + double upper_speed_bound = upper_degree_bound == 0 ? normal_speed : m_config.get_abs_value_at(overhang_speed_key_map[upper_degree_bound].c_str(), cur_extruder_index()); lower_speed_bound = lower_speed_bound == 0 ? normal_speed : lower_speed_bound; upper_speed_bound = upper_speed_bound == 0 ? normal_speed : upper_speed_bound; @@ -4572,7 +5293,6 @@ double GCode::get_overhang_degree_corr_speed(float normal_speed, double path_deg return speed_out; } -//1.9.5 static bool need_smooth_speed(const ExtrusionPath &other_path, const ExtrusionPath &this_path) { if (this_path.smooth_speed - other_path.smooth_speed > smooth_speed_step) @@ -4646,7 +5366,6 @@ ExtrusionPaths GCode::split_and_mapping_speed(double &other_path_v, double &fina Point insert_p = line.a + (line.b - line.a) * rate; split_line_speed = insert_speed(min_step_length, x_base, smooth_length_count, final_v); - line_start_pt = insert_p; get_next_line = false; cuted_polyline.append(insert_p); @@ -4655,7 +5374,6 @@ ExtrusionPaths GCode::split_and_mapping_speed(double &other_path_v, double &fina ExtrusionPath path_step(cuted_polyline, this_path); path_step.smooth_speed = split_line_speed; splited_path.push_back(std::move(path_step)); - } // reverse path back @@ -4676,7 +5394,6 @@ ExtrusionPaths GCode::split_and_mapping_speed(double &other_path_v, double &fina ExtrusionPaths GCode::merge_same_speed_paths(const ExtrusionPaths &paths) { ExtrusionPaths output_paths; - //1.9.7.52 std::optional merged_path; for(size_t path_idx=0;path_idx(&path); const auto get_sloped_z = [&sloped, this](double z_ratio) { - const auto height = sloped->height; + const auto height = sloped->height; return lerp(m_nominal_z - height, m_nominal_z, z_ratio); }; @@ -4814,31 +5531,31 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, m_config.apply(m_calib_config); // adjust acceleration - if (m_config.default_acceleration.value > 0) { + if (m_config.default_acceleration.get_at(cur_extruder_index()) > 0) { double acceleration; - if (this->on_first_layer() && m_config.initial_layer_acceleration.value > 0) { - acceleration = m_config.initial_layer_acceleration.value; + if (this->on_first_layer() && m_config.initial_layer_acceleration.get_at(cur_extruder_index()) > 0) { + acceleration = m_config.initial_layer_acceleration.get_at(cur_extruder_index()); #if 0 } else if (this->object_layer_over_raft() && m_config.first_layer_acceleration_over_raft.value > 0) { acceleration = m_config.first_layer_acceleration_over_raft.value; } else if (m_config.bridge_acceleration.value > 0 && is_bridge(path.role())) { acceleration = m_config.bridge_acceleration.value; #endif - } else if (m_config.outer_wall_acceleration.value > 0 + } else if (m_config.outer_wall_acceleration.get_at(cur_extruder_index()) > 0 //QDS: FIXME, in fact,we only need to set acceleration for outer wall. But we don't know //whether the overhang perimeter is outer or not. So using specific acceleration together. && (path.role() == erExternalPerimeter || path.role() == erOverhangPerimeter)) { - acceleration = m_config.outer_wall_acceleration.value; - } else if (m_config.top_surface_acceleration.value > 0 && is_top_surface(path.role())) { - acceleration = m_config.top_surface_acceleration.value; - } else if (m_config.inner_wall_acceleration.value > 0 && path.role() == erPerimeter) { - acceleration = m_config.inner_wall_acceleration.value; - } else if (m_config.get_abs_value("sparse_infill_acceleration") > 0 && (path.role() == erInternalInfill)) { - acceleration = m_config.get_abs_value("sparse_infill_acceleration"); + acceleration = m_config.outer_wall_acceleration.get_at(cur_extruder_index()); + } else if (m_config.top_surface_acceleration.get_at(cur_extruder_index()) > 0 && is_top_surface(path.role())) { + acceleration = m_config.top_surface_acceleration.get_at(cur_extruder_index()); + } else if (m_config.inner_wall_acceleration.get_at(cur_extruder_index()) > 0 && path.role() == erPerimeter) { + acceleration = m_config.inner_wall_acceleration.get_at(cur_extruder_index()); + } else if (m_config.get_abs_value_at("sparse_infill_acceleration", cur_extruder_index()) > 0 && (path.role() == erInternalInfill)) { + acceleration = m_config.get_abs_value_at("sparse_infill_acceleration", cur_extruder_index()); } else { - acceleration = m_config.default_acceleration.value; + acceleration = m_config.default_acceleration.get_at(cur_extruder_index()); } - gcode += m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); + m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); } //w30 @@ -4866,78 +5583,90 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, else if (this->on_first_layer()) _mm3_per_mm *= m_config.initial_layer_flow_ratio.value; - double e_per_mm = m_writer.extruder()->e_per_mm3() * _mm3_per_mm; + double e_per_mm = m_writer.filament()->e_per_mm3() * _mm3_per_mm; - double min_speed = double(m_config.slow_down_min_speed.get_at(m_writer.extruder()->id())); + double min_speed = double(m_config.slow_down_min_speed.get_at(m_writer.filament()->id())); // set speed if (speed == -1) { if (path.role() == erPerimeter) { - speed = m_config.get_abs_value("inner_wall_speed"); - //1.9.5 - if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && path.smooth_speed != 0) + speed = m_config.inner_wall_speed.get_at(cur_extruder_index()); + //reset speed by auto compensation speed + if(use_seperate_speed) { + speed = m_config.circle_compensation_speed.get_at(cur_extruder_index()); + }else if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && path.smooth_speed != 0) speed = path.smooth_speed; - else if (m_config.enable_overhang_speed.value) { + else if (m_config.enable_overhang_speed.get_at(cur_extruder_index())) { double new_speed = 0; new_speed = get_overhang_degree_corr_speed(speed, path.overhang_degree); speed = new_speed == 0.0 ? speed : new_speed; } } else if (path.role() == erExternalPerimeter) { - speed = m_config.get_abs_value("outer_wall_speed"); - //1.9.5 - if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && path.smooth_speed != 0) + speed = m_config.outer_wall_speed.get_at(cur_extruder_index()); + // reset speed by auto compensation speed + if (use_seperate_speed) { + speed = m_config.circle_compensation_speed.get_at(cur_extruder_index()); + } else if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && path.smooth_speed != 0) speed = path.smooth_speed; - else if (m_config.enable_overhang_speed.value ) { + else if (m_config.enable_overhang_speed.get_at(cur_extruder_index())) { double new_speed = 0; new_speed = get_overhang_degree_corr_speed(speed, path.overhang_degree); - //w16 + //w16 //y58 if (m_resonance_avoidance) { if (new_speed > 0) { - if (new_speed > m_config.max_resonance_avoidance_speed.value) + if (new_speed > m_config.max_resonance_avoidance_speed.get_at(cur_extruder_index())) m_resonance_avoidance = false; } } speed = new_speed == 0.0 ? speed : new_speed; } - //1.9.5 } else if (path.role() == erOverhangPerimeter && path.overhang_degree == 5) { - speed = m_config.get_abs_value("overhang_totally_speed"); + speed = m_config.overhang_totally_speed.get_at(cur_extruder_index()); } else if (path.role() == erOverhangPerimeter || path.role() == erBridgeInfill || path.role() == erSupportTransition) { - speed = m_config.get_abs_value("bridge_speed"); + speed = m_config.bridge_speed.get_at(cur_extruder_index()); } else if (path.role() == erInternalInfill) { - speed = m_config.get_abs_value("sparse_infill_speed"); + speed = m_config.sparse_infill_speed.get_at(cur_extruder_index()); } else if (path.role() == erSolidInfill) { - speed = m_config.get_abs_value("internal_solid_infill_speed"); - } else if (path.role() == erTopSolidInfill) { - speed = m_config.get_abs_value("top_surface_speed"); + speed = m_config.internal_solid_infill_speed.get_at(cur_extruder_index()); + } else if (path.role() == erFloatingVerticalShell){ + if(use_seperate_speed){ + speed = m_config.bridge_speed.get_at(cur_extruder_index()); + } + else{ + speed = m_config.vertical_shell_speed.get_at(cur_extruder_index()).get_abs_value(m_config.internal_solid_infill_speed.get_at(cur_extruder_index())); + } + } + else if (path.role() == erTopSolidInfill) { + speed = m_config.top_surface_speed.get_at(cur_extruder_index()); } else if (path.role() == erIroning) { speed = m_config.get_abs_value("ironing_speed"); } else if (path.role() == erBottomSurface) { - speed = m_config.get_abs_value("initial_layer_infill_speed"); + speed = m_config.initial_layer_infill_speed.get_at(cur_extruder_index()); } else if (path.role() == erGapFill) { - speed = m_config.get_abs_value("gap_infill_speed"); + speed = m_config.gap_infill_speed.get_at(cur_extruder_index()); } else if (path.role() == erSupportMaterial || path.role() == erSupportMaterialInterface) { - const double support_speed = m_config.support_speed.value; - const double support_interface_speed = m_config.get_abs_value("support_interface_speed"); + const double support_speed = m_config.support_speed.get_at(cur_extruder_index()); + const double support_interface_speed = m_config.support_interface_speed.get_at(cur_extruder_index()); speed = (path.role() == erSupportMaterial) ? support_speed : support_interface_speed; } else { throw Slic3r::InvalidArgument("Invalid speed"); } } //QDS: if not set the speed, then use the filament_max_volumetric_speed directly + double filament_max_volumetric_speed = FILAMENT_CONFIG(filament_max_volumetric_speed); if( speed == 0 ) { if (_mm3_per_mm>0) - speed = EXTRUDER_CONFIG(filament_max_volumetric_speed) / _mm3_per_mm; + speed = filament_max_volumetric_speed / _mm3_per_mm; else - speed = EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm; + speed = filament_max_volumetric_speed / path.mm3_per_mm; } if (this->on_first_layer()) { //QDS: for solid infill of initial layer, speed can be higher as long as //wall lines have be attached if (path.role() != erBottomSurface) - speed = m_config.get_abs_value("initial_layer_speed"); + speed = m_config.initial_layer_speed.get_at(cur_extruder_index()); } //QDS: remove this config //else if (this->object_layer_over_raft()) @@ -4949,18 +5678,18 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, // m_config.max_volumetric_speed.value / path.mm3_per_mm // ); //} - if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) { - double extrude_speed = EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm; + if (filament_max_volumetric_speed > 0) { + double extrude_speed = filament_max_volumetric_speed / path.mm3_per_mm; if (_mm3_per_mm > 0) - extrude_speed = EXTRUDER_CONFIG(filament_max_volumetric_speed) / _mm3_per_mm; + extrude_speed = filament_max_volumetric_speed / _mm3_per_mm; // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) speed = std::min(speed, extrude_speed); } - //w16 + //w16 //y58 if (m_resonance_avoidance && path.role() == erExternalPerimeter) { - if (speed <= m_config.max_resonance_avoidance_speed.value) { - speed = std::min(speed, m_config.min_resonance_avoidance_speed.value); + if (speed <= m_config.max_resonance_avoidance_speed.get_at(cur_extruder_index())) { + speed = std::min(speed, m_config.min_resonance_avoidance_speed.get_at(cur_extruder_index())); } m_resonance_avoidance = true; } @@ -4988,6 +5717,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, char buf[64]; assert(is_decimal_separator_point()); + + if (use_seperate_speed) + gcode += "; Slow Down Start\n"; + if (path.role() != m_last_processor_extrusion_role) { m_last_processor_extrusion_role = path.role(); sprintf(buf, ";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(m_last_processor_extrusion_role).c_str()); @@ -5017,12 +5750,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, std::string comment; bool cooling_extrude = false; if (m_enable_cooling_markers) { - if (EXTRUDER_CONFIG(enable_overhang_bridge_fan)) { + if (FILAMENT_CONFIG(enable_overhang_bridge_fan)) { //QDS: Overhang_threshold_none means Overhang_threshold_1_4 and forcing cooling for all external perimeter - int overhang_threshold = EXTRUDER_CONFIG(overhang_fan_threshold) == Overhang_threshold_none ? - Overhang_threshold_none : EXTRUDER_CONFIG(overhang_fan_threshold) - 1; - if ((EXTRUDER_CONFIG(overhang_fan_threshold) == Overhang_threshold_none && path.role() == erExternalPerimeter || (path.get_overhang_degree() > overhang_threshold || - is_bridge(path.role())))) { + int overhang_threshold = FILAMENT_CONFIG(overhang_fan_threshold) == Overhang_threshold_none ? + Overhang_threshold_none : FILAMENT_CONFIG(overhang_fan_threshold) - 1; + if ((FILAMENT_CONFIG(overhang_fan_threshold) == Overhang_threshold_none && path.role() == erExternalPerimeter) || (path.get_overhang_degree() > overhang_threshold || + is_bridge(path.role()))) { gcode += ";_OVERHANG_FAN_START\n"; } } @@ -5130,16 +5863,20 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, gcode += ";_EXTRUDE_END\n"; - if (EXTRUDER_CONFIG(enable_overhang_bridge_fan)) { + if (FILAMENT_CONFIG(enable_overhang_bridge_fan)) { //QDS: Overhang_threshold_none means Overhang_threshold_1_4 and forcing cooling for all external perimeter - int overhang_threshold = EXTRUDER_CONFIG(overhang_fan_threshold) == Overhang_threshold_none ? - Overhang_threshold_none : EXTRUDER_CONFIG(overhang_fan_threshold) - 1; - if ((EXTRUDER_CONFIG(overhang_fan_threshold) == Overhang_threshold_none && path.role() == erExternalPerimeter || (path.get_overhang_degree() > overhang_threshold || - is_bridge(path.role())))) + int overhang_threshold = FILAMENT_CONFIG(overhang_fan_threshold) == Overhang_threshold_none ? + Overhang_threshold_none : FILAMENT_CONFIG(overhang_fan_threshold) - 1; + if ((FILAMENT_CONFIG(overhang_fan_threshold) == Overhang_threshold_none && path.role() == erExternalPerimeter) || (path.get_overhang_degree() > overhang_threshold || + is_bridge(path.role()))) gcode += ";_OVERHANG_FAN_END\n"; } } + if (use_seperate_speed) { + gcode += "; Slow Down End\n"; + } + this->set_last_pos(path.last_point()); return gcode; } @@ -5288,7 +6025,7 @@ LiftType GCode::to_lift_type(ZHopType z_hop_types) { case ZHopType::zhtNormal: return LiftType::NormalLift; case ZHopType::zhtSlope: - return LiftType::LazyLift; + return LiftType::SlopeLift; case ZHopType::zhtSpiral: return LiftType::SpiralLift; default: @@ -5299,7 +6036,7 @@ LiftType GCode::to_lift_type(ZHopType z_hop_types) { bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role, LiftType& lift_type) { - if (travel.length() < scale_(EXTRUDER_CONFIG(retraction_minimum_travel))) { + if (travel.length() < scale_(FILAMENT_CONFIG(retraction_minimum_travel))) { // skip retraction if the move is shorter than the configured threshold return false; } @@ -5378,11 +6115,11 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role, LiftTyp //QDS: force to retract when leave from external perimeter for a long travel //Better way is judging whether the travel move direction is same with last extrusion move. if (is_perimeter(m_last_processor_extrusion_role) && m_last_processor_extrusion_role != erPerimeter) { - if (ZHopType(EXTRUDER_CONFIG(z_hop_types)) == ZHopType::zhtAuto) { - lift_type = is_through_overhang(clipped_travel) ? LiftType::SpiralLift : LiftType::LazyLift; + if (ZHopType(FILAMENT_CONFIG(z_hop_types)) == ZHopType::zhtAuto) { + lift_type = is_through_overhang(clipped_travel) ? LiftType::SpiralLift : LiftType::SlopeLift; } else { - lift_type = to_lift_type(ZHopType(EXTRUDER_CONFIG(z_hop_types))); + lift_type = to_lift_type(ZHopType(FILAMENT_CONFIG(z_hop_types))); } return true; } @@ -5407,24 +6144,24 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role, LiftTyp return false; // retract if reduce_infill_retraction is disabled or doesn't apply when role is perimeter - if (ZHopType(EXTRUDER_CONFIG(z_hop_types)) == ZHopType::zhtAuto) { - lift_type = is_through_overhang(clipped_travel) ? LiftType::SpiralLift : LiftType::LazyLift; + if (ZHopType(FILAMENT_CONFIG(z_hop_types)) == ZHopType::zhtAuto) { + lift_type = is_through_overhang(clipped_travel) ? LiftType::SpiralLift : LiftType::SlopeLift; } else { - lift_type = to_lift_type(ZHopType(EXTRUDER_CONFIG(z_hop_types))); + lift_type = to_lift_type(ZHopType(FILAMENT_CONFIG(z_hop_types))); } return true; } -std::string GCode::retract(bool toolchange, bool is_last_retraction, LiftType lift_type) +std::string GCode::retract(bool toolchange, bool is_last_retraction, LiftType lift_type,bool apply_instantly) { std::string gcode; - if (m_writer.extruder() == nullptr) + if (m_writer.filament() == nullptr) return gcode; // wipe (if it's enabled for this extruder and we have a stored wipe path and no-zero wipe distance) - if (EXTRUDER_CONFIG(wipe) && m_wipe.has_path() && scale_(EXTRUDER_CONFIG(wipe_distance)) > SCALED_EPSILON) { + if (FILAMENT_CONFIG(wipe) && m_wipe.has_path() && scale_(FILAMENT_CONFIG(wipe_distance)) > SCALED_EPSILON) { gcode += toolchange ? m_writer.retract_for_toolchange(true) : m_writer.retract(true); gcode += m_wipe.wipe(*this, toolchange, is_last_retraction); } @@ -5437,39 +6174,44 @@ std::string GCode::retract(bool toolchange, bool is_last_retraction, LiftType li gcode += m_writer.reset_e(); //QDS - if (m_writer.extruder()->retraction_length() > 0||m_config.use_firmware_retraction) { + if (m_writer.filament()->retraction_length() > 0 || m_config.use_firmware_retraction) { // QDS: force to use normal lift for spiral vase mode - gcode += m_writer.lift(lift_type, m_spiral_vase != nullptr); + if (apply_instantly) + gcode += m_writer.eager_lift(lift_type,toolchange); + else + gcode += m_writer.lazy_lift(lift_type, m_spiral_vase != nullptr, toolchange); + } return gcode; } -std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool by_object) +std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bool by_object) { - if (!m_writer.need_toolchange(extruder_id)) + int new_extruder_id = get_extruder_id(new_filament_id); + if (!m_writer.need_toolchange(new_filament_id)) return ""; // if we are running a single-extruder setup, just set the extruder and return nothing if (!m_writer.multiple_extruders) { - m_placeholder_parser.set("current_extruder", extruder_id); - m_placeholder_parser.set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(extruder_id)); - m_placeholder_parser.set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(extruder_id)); + m_placeholder_parser.set("current_extruder", new_filament_id); + m_placeholder_parser.set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(new_filament_id)); + m_placeholder_parser.set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(new_filament_id)); std::string gcode; // Append the filament start G-code. - const std::string &filament_start_gcode = m_config.filament_start_gcode.get_at(extruder_id); + const std::string &filament_start_gcode = m_config.filament_start_gcode.get_at(new_filament_id); if (! filament_start_gcode.empty()) { // Process the filament_start_gcode for the filament. - gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, extruder_id); + gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id); check_add_eol(gcode); } //QDS: never use for QIDI Printer //w30 - if (m_config.enable_pressure_advance.get_at(extruder_id)) - gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(extruder_id)); + if (m_config.enable_pressure_advance.get_at(new_filament_id)) + gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id)); - gcode += m_writer.toolchange(extruder_id); + gcode += m_writer.toolchange(new_filament_id); return gcode; } @@ -5488,13 +6230,15 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b else m_writer.add_object_end_labels(gcode); - if (m_writer.extruder() != nullptr) { + if (m_writer.filament() != nullptr) { // Process the custom filament_end_gcode. set_extruder() is only called if there is no wipe tower // so it should not be injected twice. - unsigned int old_extruder_id = m_writer.extruder()->id(); - const std::string &filament_end_gcode = m_config.filament_end_gcode.get_at(old_extruder_id); + unsigned int old_filament_id = m_writer.filament()->id(); + const std::string &filament_end_gcode = m_config.filament_end_gcode.get_at(old_filament_id); if (! filament_end_gcode.empty()) { - gcode += placeholder_parser_process("filament_end_gcode", filament_end_gcode, old_extruder_id); + DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + gcode += placeholder_parser_process("filament_end_gcode", filament_end_gcode, old_filament_id, &config); check_add_eol(gcode); } } @@ -5502,39 +6246,58 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b // If ooze prevention is enabled, park current extruder in the nearest // standby point and set it to the standby temperature. - if (m_ooze_prevention.enable && m_writer.extruder() != nullptr) + if (m_ooze_prevention.enable && m_writer.filament() != nullptr) gcode += m_ooze_prevention.pre_toolchange(*this); // QDS - float new_retract_length = m_config.retraction_length.get_at(extruder_id); - float new_retract_length_toolchange = m_config.retract_length_toolchange.get_at(extruder_id); - int new_filament_temp = this->on_first_layer() ? m_config.nozzle_temperature_initial_layer.get_at(extruder_id): m_config.nozzle_temperature.get_at(extruder_id); + float new_retract_length = m_config.retraction_length.get_at(new_filament_id); + float new_retract_length_toolchange = m_config.retract_length_toolchange.get_at(new_filament_id); + int new_filament_temp = this->on_first_layer() ? m_config.nozzle_temperature_initial_layer.get_at(new_filament_id) : m_config.nozzle_temperature.get_at(new_filament_id); // QDS: if print_z == 0 use first layer temperature if (abs(print_z) < EPSILON) - new_filament_temp = m_config.nozzle_temperature_initial_layer.get_at(extruder_id); + new_filament_temp = m_config.nozzle_temperature_initial_layer.get_at(new_filament_id); Vec3d nozzle_pos = m_writer.get_position(); float old_retract_length, old_retract_length_toolchange, wipe_volume; int old_filament_temp, old_filament_e_feedrate; - float filament_area = float((M_PI / 4.f) * pow(m_config.filament_diameter.get_at(extruder_id), 2)); + float filament_area = float((M_PI / 4.f) * pow(m_config.filament_diameter.get_at(new_filament_id), 2)); //QDS: add handling for filament change in start gcode - int previous_extruder_id = -1; - if (m_writer.extruder() != nullptr || m_start_gcode_filament != -1) { - std::vector flush_matrix(cast(m_config.flush_volumes_matrix.values)); - const unsigned int number_of_extruders = (unsigned int)(sqrt(flush_matrix.size()) + EPSILON); - if (m_writer.extruder() != nullptr) - assert(m_writer.extruder()->id() < number_of_extruders); + int old_filament_id = -1; + if (m_writer.filament() != nullptr || m_start_gcode_filament != -1) { + std::vector flush_matrix(cast(get_flush_volumes_matrix(m_config.flush_volumes_matrix.values, new_extruder_id, m_config.nozzle_diameter.values.size()))); + const unsigned int number_of_extruders = (unsigned int) (m_config.filament_colour.values.size()); // if is multi_extruder only use the fist extruder matrix + if (m_writer.filament() != nullptr) + assert(m_writer.filament()->id() < number_of_extruders); else assert(m_start_gcode_filament < number_of_extruders); - previous_extruder_id = m_writer.extruder() != nullptr ? m_writer.extruder()->id() : m_start_gcode_filament; - old_retract_length = m_config.retraction_length.get_at(previous_extruder_id); - old_retract_length_toolchange = m_config.retract_length_toolchange.get_at(previous_extruder_id); - old_filament_temp = this->on_first_layer()? m_config.nozzle_temperature_initial_layer.get_at(previous_extruder_id) : m_config.nozzle_temperature.get_at(previous_extruder_id); - wipe_volume = flush_matrix[previous_extruder_id * number_of_extruders + extruder_id]; - wipe_volume *= m_config.flush_multiplier; - old_filament_e_feedrate = (int)(60.0 * m_config.filament_max_volumetric_speed.get_at(previous_extruder_id) / filament_area); + old_filament_id = m_writer.filament() != nullptr ? m_writer.filament()->id() : m_start_gcode_filament; + int old_extruder_id = m_writer.filament() != nullptr ? m_writer.filament()->extruder_id() : get_extruder_id(m_start_gcode_filament); + + old_retract_length = m_config.retraction_length.get_at(old_filament_id); + old_retract_length_toolchange = m_config.retract_length_toolchange.get_at(old_filament_id); + old_filament_temp = this->on_first_layer()? m_config.nozzle_temperature_initial_layer.get_at(old_filament_id) : m_config.nozzle_temperature.get_at(old_filament_id); + + //During the filament change, the extruder will extrude an extra length of grab_length for the corresponding detection, so the purge can reduce this length. + float grab_purge_volume = m_config.grab_length.get_at(new_extruder_id) * 2.4; + if (old_extruder_id != new_extruder_id) { + //calc flush volume between the same extruder id + int old_filament_id_in_new_extruder = m_writer.filament(new_extruder_id) != nullptr ? m_writer.filament(new_extruder_id)->id() : -1; + if (old_filament_id_in_new_extruder == -1) + wipe_volume = 0; + else { + wipe_volume = flush_matrix[old_filament_id_in_new_extruder * number_of_extruders + new_filament_id]; + wipe_volume *= m_config.flush_multiplier.get_at(new_extruder_id); + } + } + else { + wipe_volume = flush_matrix[old_filament_id * number_of_extruders + new_filament_id]; + wipe_volume *= m_config.flush_multiplier.get_at(new_extruder_id); // if is multi_extruder only use the fist extruder matrix + } + wipe_volume = std::max(0.f, wipe_volume-grab_purge_volume); + + old_filament_e_feedrate = (int) (60.0 * m_config.filament_max_volumetric_speed.get_at(old_filament_id) / filament_area); old_filament_e_feedrate = old_filament_e_feedrate == 0 ? 100 : old_filament_e_feedrate; //QDS: must clean m_start_gcode_filament m_start_gcode_filament = -1; @@ -5546,12 +6309,16 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b old_filament_e_feedrate = 200; } float wipe_length = wipe_volume / filament_area; - int new_filament_e_feedrate = (int)(60.0 * m_config.filament_max_volumetric_speed.get_at(extruder_id) / filament_area); + int new_filament_e_feedrate = (int)(60.0 * m_config.filament_max_volumetric_speed.get_at(new_filament_id) / filament_area); new_filament_e_feedrate = new_filament_e_feedrate == 0 ? 100 : new_filament_e_feedrate; + // set volumetric speed of outer wall ,ignore per obejct,just use default setting + float outer_wall_volumetric_speed = get_outer_wall_volumetric_speed(m_config, *m_print, new_filament_id, get_extruder_id(new_filament_id)); + float wipe_avoid_pos_x = 110.f; DynamicConfig dyn_config; - dyn_config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); - dyn_config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); + dyn_config.set_key_value("outer_wall_volumetric_speed", new ConfigOptionFloat(outer_wall_volumetric_speed)); + dyn_config.set_key_value("previous_extruder", new ConfigOptionInt(old_filament_id)); + dyn_config.set_key_value("next_extruder", new ConfigOptionInt((int)new_filament_id)); dyn_config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); dyn_config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); dyn_config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); @@ -5579,11 +6346,12 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b dyn_config.set_key_value("travel_point_2_y", new ConfigOptionFloat(float(travel_point_2.y()))); dyn_config.set_key_value("travel_point_3_x", new ConfigOptionFloat(float(travel_point_3.x()))); dyn_config.set_key_value("travel_point_3_y", new ConfigOptionFloat(float(travel_point_3.y()))); + dyn_config.set_key_value("wipe_avoid_perimeter", new ConfigOptionBool(false)); + dyn_config.set_key_value("wipe_avoid_pos_x", new ConfigOptionFloat(wipe_avoid_pos_x)); dyn_config.set_key_value("flush_length", new ConfigOptionFloat(wipe_length)); int flush_count = std::min(g_max_flush_count, (int)std::round(wipe_volume / g_purge_volume_one_time)); - //1.9.7.52 // handle cases for very small purge if (flush_count == 0 && wipe_volume > 0) flush_count += 1; @@ -5602,10 +6370,14 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b } // Process the custom change_filament_gcode. - const std::string& change_filament_gcode = m_config.change_filament_gcode.value; + std::string change_filament_gcode = m_config.change_filament_gcode.value; + + // Move the lift gcode here which is in the change_filament_gcode originally + change_filament_gcode = this->retract(false, false, LiftType::SpiralLift, true) + change_filament_gcode; + std::string toolchange_gcode_parsed; if (!change_filament_gcode.empty()) { - toolchange_gcode_parsed = placeholder_parser_process("change_filament_gcode", change_filament_gcode, extruder_id, &dyn_config); + toolchange_gcode_parsed = placeholder_parser_process("change_filament_gcode", change_filament_gcode, new_filament_id, &dyn_config); check_add_eol(toolchange_gcode_parsed); gcode += toolchange_gcode_parsed; @@ -5631,9 +6403,10 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b m_writer.reset_e(); } - // We inform the writer about what is happening, but we may not use the resulting gcode. - std::string toolchange_command = m_writer.toolchange(extruder_id); - if (! custom_gcode_changes_tool(toolchange_gcode_parsed, m_writer.toolchange_prefix(), extruder_id)) + //QDS: don't add T[next extruder] if there is no T cmd on filament change + //We inform the writer about what is happening, but we may not use the resulting gcode. + std::string toolchange_command = m_writer.toolchange(new_filament_id); + if (!custom_gcode_changes_tool(toolchange_gcode_parsed, m_writer.toolchange_prefix(), new_filament_id)) gcode += toolchange_command; else { // user provided his own toolchange gcode, no need to do anything @@ -5641,21 +6414,21 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b // Set the temperature if the wipe tower didn't (not needed for non-single extruder MM) if (m_config.single_extruder_multi_material && !m_config.enable_prime_tower) { - int temp = (m_layer_index <= 0 ? m_config.nozzle_temperature_initial_layer.get_at(extruder_id) : - m_config.nozzle_temperature.get_at(extruder_id)); + int temp = (m_layer_index <= 0 ? m_config.nozzle_temperature_initial_layer.get_at(new_filament_id) : + m_config.nozzle_temperature.get_at(new_filament_id)); gcode += m_writer.set_temperature(temp, false); } - m_placeholder_parser.set("current_extruder", extruder_id); - m_placeholder_parser.set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(extruder_id)); - m_placeholder_parser.set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(extruder_id)); + m_placeholder_parser.set("current_extruder", new_filament_id); + m_placeholder_parser.set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(new_filament_id)); + m_placeholder_parser.set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(new_filament_id)); // Append the filament start G-code. - const std::string &filament_start_gcode = m_config.filament_start_gcode.get_at(extruder_id); + const std::string &filament_start_gcode = m_config.filament_start_gcode.get_at(new_filament_id); if (! filament_start_gcode.empty()) { // Process the filament_start_gcode for the new filament. - gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, extruder_id); + gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id); check_add_eol(gcode); } // Set the new extruder to the operating temperature. @@ -5663,8 +6436,8 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b gcode += m_ooze_prevention.post_toolchange(*this); //QDS: never use for QIDI Printer //w30 - if (m_config.enable_pressure_advance.get_at(extruder_id)) - gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(extruder_id)); + if (m_config.enable_pressure_advance.get_at(new_filament_id)) + gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id)); return gcode; } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index fcb115b..c400a25 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -9,7 +9,7 @@ #include "PlaceholderParser.hpp" #include "PrintConfig.hpp" #include "GCode/AvoidCrossingPerimeters.hpp" -#include "GCode/CoolingBuffer.hpp" +#include "GCode/GCodeEditor.hpp" #include "GCode/RetractWhenCrossingPerimeters.hpp" #include "GCode/SpiralVase.hpp" #include "GCode/ToolOrdering.hpp" @@ -77,7 +77,6 @@ public: m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.prime_tower_width.value)), m_wipe_tower_pos(float(print_config.wipe_tower_x.get_at(plate_idx)), float(print_config.wipe_tower_y.get_at(plate_idx))), m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)), - m_extruder_offsets(print_config.extruder_offset.values), m_priming(priming), m_tool_changes(tool_changes), m_final_purge(final_purge), @@ -86,8 +85,15 @@ public: m_plate_origin(plate_origin), m_single_extruder_multi_material(print_config.single_extruder_multi_material), m_enable_timelapse_print(print_config.timelapse_type.value == TimelapseType::tlSmooth), - m_is_first_print(true) - {} + m_is_first_print(true), + m_print_config(&print_config) + { + // initialize with the extruder offset of master extruder id + m_extruder_offsets.resize(print_config.filament_map.size(), print_config.extruder_offset.get_at(print_config.master_extruder_id.value - 1)); + const auto& filament_map = print_config.filament_map.values; // 1 based idx + for (size_t idx = 0; idx < filament_map.size(); ++idx) + m_extruder_offsets[idx] = print_config.extruder_offset.get_at(filament_map[idx] - 1); + } std::string prime(GCode &gcodegen); void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; } @@ -100,10 +106,14 @@ public: void set_is_first_print(bool is) { m_is_first_print = is; } bool enable_timelapse_print() const { return m_enable_timelapse_print; } + void set_wipe_tower_depth(float depth) { m_wipe_tower_depth = depth; } + void set_wipe_tower_bbx(const BoundingBoxf & bbx) { m_wipe_tower_bbx = bbx; } + void set_rib_offset(const Vec2f &rib_offset) { m_rib_offset = rib_offset; } private: WipeTowerIntegration& operator=(const WipeTowerIntegration&); std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const; + Polyline generate_path_to_wipe_tower(const Point &start_pos, const Point &end_pos, const BoundingBox &avoid_polygon, const BoundingBox &printer_bbx) const; // Postprocesses gcode: rotates and moves G1 extrusions and returns result std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const; @@ -113,7 +123,7 @@ private: const float m_right; const Vec2f m_wipe_tower_pos; const float m_wipe_tower_rotation; - const std::vector m_extruder_offsets; + std::vector m_extruder_offsets; // Reference to cached values at the Printer class. const std::vector &m_priming; @@ -129,6 +139,10 @@ private: bool m_single_extruder_multi_material; bool m_enable_timelapse_print; bool m_is_first_print; + const PrintConfig * m_print_config; + float m_wipe_tower_depth; + BoundingBoxf m_wipe_tower_bbx; + Vec2f m_rib_offset{Vec2f(0, 0)}; }; class ColorPrintColors @@ -173,7 +187,7 @@ public: // throws std::runtime_exception on error, // throws CanceledException through print->throw_if_canceled(). void do_export(Print* print, const char* path, GCodeProcessorResult* result = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); - + void export_layer_filaments(GCodeProcessorResult* result); //QDS: set offset for gcode writer void set_gcode_offset(double x, double y) { m_writer.set_xy_offset(x, y); m_processor.set_xy_offset(x, y);} @@ -209,7 +223,7 @@ public: // QDS: detect lift type in needs_retraction bool needs_retraction(const Polyline &travel, ExtrusionRole role, LiftType &lift_type); - std::string retract(bool toolchange = false, bool is_last_retraction = false, LiftType lift_type = LiftType::SpiralLift); + std::string retract(bool toolchange = false, bool is_last_retraction = false, LiftType lift_type = LiftType::SpiralLift, bool apply_instantly = false); std::string unretract() { return m_writer.unlift() + m_writer.unretract(); } //QDS bool is_QDT_Printer(); @@ -287,6 +301,9 @@ private: }; void _do_export(Print &print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb); + //w44 + std::string generate_box_temp_command(const std::vector& all_extruders, FullPrintConfig m_config); + static std::vector collect_layers_to_print(const PrintObject &object); static std::vector>> collect_layers_to_print(const Print &print); @@ -297,6 +314,35 @@ private: bool spiral_vase_enable { false }; // Should the cooling buffer content be flushed at the end of this layer? bool cooling_buffer_flush { false }; + // the layer store pos of gcode + size_t gcode_store_pos = 0; + //store each layer_time + float layer_time = 0; + LayerResult() = default; + LayerResult(const std::string& gcode_, const size_t layer_id_, const bool spiral_vase_enable_, const bool cooling_buffer_flush_, const size_t gcode_store_pos_ = static_cast(-1)) : + gcode(gcode_), layer_id(layer_id_), spiral_vase_enable(spiral_vase_enable_), cooling_buffer_flush(cooling_buffer_flush_), gcode_store_pos(gcode_store_pos_){} + LayerResult(const LayerResult& other) = default; + LayerResult& operator=(const LayerResult& other) = default; + LayerResult(LayerResult&& other) noexcept { + gcode = std::move(other.gcode); + layer_id = other.layer_id; + spiral_vase_enable = other.spiral_vase_enable; + cooling_buffer_flush = other.cooling_buffer_flush; + gcode_store_pos = other.gcode_store_pos; + layer_time = other.layer_time; + } + + LayerResult& operator=(LayerResult&& other) noexcept { + if (this != &other) { + gcode = std::move(other.gcode); + layer_id = other.layer_id; + spiral_vase_enable = other.spiral_vase_enable; + cooling_buffer_flush = other.cooling_buffer_flush; + gcode_store_pos = other.gcode_store_pos; + layer_time = other.layer_time; + } + return *this; + } }; LayerResult process_layer( const Print &print, @@ -306,6 +352,8 @@ private: const bool last_layer, // Pairs of PrintObject index and its instance index. const std::vector *ordering, + // idientiy timelapse pos + const int most_used_extruder, // If set to size_t(-1), then print all copies of all objects. // Otherwise print a single copy of a single object. const size_t single_object_idx = size_t(-1), @@ -334,6 +382,8 @@ private: //QDS void check_placeholder_parser_failed(); + size_t cur_extruder_index() const; + size_t get_extruder_id(unsigned int filament_id) const; void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; } void set_last_scarf_seam_flag(bool flag) { m_last_scarf_seam_flag = flag; } @@ -347,7 +397,6 @@ private: std::string extrude_multi_path(ExtrusionMultiPath multipath, std::string description = "", double speed = -1.); std::string extrude_path(ExtrusionPath path, std::string description = "", double speed = -1.); - //1.9.5 //smooth speed function void smooth_speed_discontinuity_area(ExtrusionPaths &paths); ExtrusionPaths merge_same_speed_paths(const ExtrusionPaths &paths); @@ -369,7 +418,6 @@ private: ExtrusionEntitiesPtr perimeters; // Non-owned references to LayerRegion::fills::entities ExtrusionEntitiesPtr infills; - std::vector infills_overrides; std::vector perimeters_overrides; @@ -483,7 +531,7 @@ private: Point m_last_pos; bool m_last_pos_defined; bool m_last_scarf_seam_flag; - std::unique_ptr m_cooling_buffer; + std::unique_ptr m_gcode_editer; std::unique_ptr m_spiral_vase; #ifdef HAS_PRESSURE_EQUALIZER std::unique_ptr m_pressure_equalizer; @@ -503,11 +551,16 @@ private: std::vector m_label_objects_ids; std::string _encode_label_ids_to_base64(std::vector ids); + // 1 << 0: A1 series cannot supprot traditional timelapse when printing by object (cannot turn on timelapse) + // 1 << 1: A1 series cannot supprot traditional timelapse with spiral vase mode (cannot turn on timelapse) + // 1 << 2: Timelapse in smooth mode without wipe tower (turn on with prompt) int m_timelapse_warning_code = 0; bool m_support_traditional_timelapse = true; bool m_silent_time_estimator_enabled; + Print *m_print{nullptr}; + // Processor GCodeProcessor m_processor; @@ -522,19 +575,19 @@ private: bool m_resonance_avoidance = true; std::set m_initial_layer_extruders; + std::vector> m_sorted_layer_filaments; // QDS int get_bed_temperature(const int extruder_id, const bool is_first_layer, const BedType bed_type) const; + int get_highest_bed_temperature(const bool is_first_layer,const Print &print) const; - std::string _extrude(const ExtrusionPath &path, std::string description = "", double speed = -1, bool is_first_slope = false); - //1.9.5 + std::string _extrude(const ExtrusionPath &path, std::string description = "", double speed = -1, bool set_holes_and_compensation_speed = false, bool is_first_slope = false); ExtrusionPaths set_speed_transition(ExtrusionPaths &paths); ExtrusionPaths split_and_mapping_speed(double &other_path_v, double &final_v, ExtrusionPath &this_path, double max_smooth_length, bool split_from_left = true); double get_path_speed(const ExtrusionPath &path); double get_overhang_degree_corr_speed(float speed, double path_degree); - //1.9.5 double mapping_speed(double dist); double get_speed_coor_x(double speed); - void print_machine_envelope(GCodeOutputStream &file, Print &print); + void print_machine_envelope(GCodeOutputStream& file, Print& print, int extruder_id); void _print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); void _print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); // On the first printing layer. This flag triggers first layer speeds. diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 4d9f3dc..51e5e30 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -509,13 +509,14 @@ static float get_perimeter_spacing_external(const Layer &layer) // Called by avoid_perimeters() and by simplify_travel_heuristics(). static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &boundary, - const Point &start, - const Point &end, + const Point &start_point, + const Point &end_point, const Layer &layer, std::vector &result_out) { const Polygons &boundaries = boundary.boundaries; const EdgeGrid::Grid &edge_grid = boundary.grid; + Point start = start_point, end = end_point; // Find all intersections between boundaries and the line segment, sort them along the line segment. std::vector intersections; { @@ -523,6 +524,29 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo AllIntersectionsVisitor visitor(edge_grid, intersections, Line(start, end)); edge_grid.visit_cells_intersecting_line(start, end, visitor); Vec2d dir = (end - start).cast(); + // if do not intersect due to the boundaries inner-offset, try to find the closest point to do intersect again! + if (intersections.empty()) { + // try to find the closest point on boundaries to start/end with distance less than extend_distance, which is noted as new start_point/end_point + auto search_radius = 1.5 * get_perimeter_spacing(layer); + const std::vector closest_line_to_start = get_closest_lines_in_radius(boundary.grid, start, search_radius); + const std::vector closest_line_to_end = get_closest_lines_in_radius(boundary.grid, end, search_radius); + if (!(closest_line_to_start.empty() && closest_line_to_end.empty())) { + auto new_start_point = closest_line_to_start.empty() ? start : closest_line_to_start.front().point; + auto new_end_point = closest_line_to_end.empty() ? end : closest_line_to_end.front().point; + dir = (new_end_point - new_start_point).cast(); + auto unit_direction = dir.normalized(); + // out-offset new_start_point/new_end_point epsilon along the Line(new_start_point, new_end_point) for right intersection! + new_start_point = new_start_point - (unit_direction * double(coord_t(SCALED_EPSILON))).cast(); + new_end_point = new_end_point + (unit_direction * double(coord_t(SCALED_EPSILON))).cast(); + AllIntersectionsVisitor visitor(edge_grid, intersections, Line(new_start_point, new_end_point)); + edge_grid.visit_cells_intersecting_line(new_start_point, new_end_point, visitor); + if (!intersections.empty()) { + start = new_start_point; + end = new_end_point; + } + } + } + for (Intersection &intersection : intersections) { float dist_from_line_begin = (intersection.point - boundary.boundaries[intersection.border_idx][intersection.line_idx]).cast().norm(); intersection.distance = boundary.boundaries_params[intersection.border_idx][intersection.line_idx] + dist_from_line_begin; @@ -598,6 +622,7 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo result.push_back({end, -1}); + #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { static int iRun = 0; @@ -632,6 +657,7 @@ static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary size_t num_intersections = avoid_perimeters_inner(boundary, start, end, layer, path); result_out = to_polyline(path); + #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { static int iRun = 0; @@ -705,7 +731,7 @@ static bool need_wipe(const GCode &gcodegen, { const ExPolygons &lslices = gcodegen.layer()->lslices; const std::vector &lslices_bboxes = gcodegen.layer()->lslices_bboxes; - bool z_lift_enabled = gcodegen.config().z_hop.get_at(gcodegen.writer().extruder()->id()) > 0.; + bool z_lift_enabled = gcodegen.config().z_hop.get_at(gcodegen.writer().filament()->id()) > 0.; bool wipe_needed = false; // If the original unmodified path doesn't have any intersection with boundary, then it is entirely inside the object otherwise is entirely @@ -1127,7 +1153,6 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & const Point start = gcodegen.last_pos() + scaled_origin; const Point end = point + scaled_origin; const Line travel(start, end); - Polyline result_pl; size_t travel_intersection_count = 0; Vec2d startf = start.cast(); diff --git a/src/libslic3r/GCode/ConflictChecker.cpp b/src/libslic3r/GCode/ConflictChecker.cpp index 1c0c7f3..03ea59e 100644 --- a/src/libslic3r/GCode/ConflictChecker.cpp +++ b/src/libslic3r/GCode/ConflictChecker.cpp @@ -227,16 +227,16 @@ ConflictResultOpt ConflictChecker::find_inter_of_lines_in_diff_objs(PrintObjectP LinesBucketQueue conflictQueue; if (wtdptr.has_value()) { // wipe tower at 0 by default - auto wtpaths = wtdptr.value()->getFakeExtrusionPathsFromWipeTower(); - ExtrusionLayers wtels; - wtels.type = ExtrusionLayersType::WIPE_TOWER; - for (int i = 0; i < wtpaths.size(); ++i) { // assume that wipe tower always has same height - ExtrusionLayer el; - el.paths = wtpaths[i]; - el.bottom_z = wtpaths[i].front().height * (float) i; - el.layer = nullptr; - wtels.push_back(el); - } + //auto wtpaths = wtdptr.value()->getFakeExtrusionPathsFromWipeTower(); + ExtrusionLayers wtels = wtdptr.value()->getTrueExtrusionLayersFromWipeTower(); + //wtels.type = ExtrusionLayersType::WIPE_TOWER; + //for (int i = 0; i < wtpaths.size(); ++i) { // assume that wipe tower always has same height + // ExtrusionLayer el; + // el.paths = wtpaths[i]; + // el.bottom_z = wtpaths[i].front().height * (float) i; + // el.layer = nullptr; + // wtels.push_back(el); + //} conflictQueue.emplace_back_bucket(std::move(wtels), wtdptr.value(), {wtdptr.value()->plate_origin.x(), wtdptr.value()->plate_origin.y()}); } for (PrintObject *obj : objs) { diff --git a/src/libslic3r/GCode/ConflictChecker.hpp b/src/libslic3r/GCode/ConflictChecker.hpp index eac10aa..30a8367 100644 --- a/src/libslic3r/GCode/ConflictChecker.hpp +++ b/src/libslic3r/GCode/ConflictChecker.hpp @@ -33,8 +33,9 @@ struct ExtrusionLayer enum class ExtrusionLayersType { INFILL, PERIMETERS, SUPPORT, WIPE_TOWER }; -struct ExtrusionLayers : public std::vector +class ExtrusionLayers : public std::vector { +public: ExtrusionLayersType type; }; diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 62d6af5..33e7937 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -1,518 +1,66 @@ -#include "../GCode.hpp" #include "CoolingBuffer.hpp" -#include -#include -#include -#include -#include - -#if 0 - #define DEBUG - #define _DEBUG - #undef NDEBUG -#endif - -#include namespace Slic3r { -CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0) +float new_feedrate_to_reach_time_stretch(std::vector::const_iterator it_begin, + std::vector::const_iterator it_end, + float min_feedrate, + float time_stretch, + size_t max_iter = 20) { - this->reset(gcodegen.writer().get_position()); - - const std::vector &extruders = gcodegen.writer().extruders(); - m_extruder_ids.reserve(extruders.size()); - for (const Extruder &ex : extruders) { - m_num_extruders = std::max(ex.id() + 1, m_num_extruders); - m_extruder_ids.emplace_back(ex.id()); - } -} - -void CoolingBuffer::reset(const Vec3d &position) -{ - // QDS: add I and J axis to store center of arc - m_current_pos.assign(7, 0.f); - m_current_pos[0] = float(position.x()); - m_current_pos[1] = float(position.y()); - m_current_pos[2] = float(position.z()); - m_current_pos[4] = float(m_config.travel_speed.value); - m_fan_speed = -1; - m_additional_fan_speed = -1; - m_current_fan_speed = -1; -} - -struct CoolingLine -{ - enum Type { - TYPE_SET_TOOL = 1 << 0, - TYPE_EXTRUDE_END = 1 << 1, - TYPE_OVERHANG_FAN_START = 1 << 2, - TYPE_OVERHANG_FAN_END = 1 << 3, - TYPE_G0 = 1 << 4, - TYPE_G1 = 1 << 5, - TYPE_ADJUSTABLE = 1 << 6, - TYPE_EXTERNAL_PERIMETER = 1 << 7, - // The line sets a feedrate. - TYPE_HAS_F = 1 << 8, - TYPE_WIPE = 1 << 9, - TYPE_G4 = 1 << 10, - TYPE_G92 = 1 << 11, - //QDS: add G2 G3 type - TYPE_G2 = 1 << 12, - TYPE_G3 = 1 << 13, - TYPE_FORCE_RESUME_FAN = 1 << 14, - TYPE_SET_FAN_CHANGING_LAYER = 1 << 15, - }; - - CoolingLine(unsigned int type, size_t line_start, size_t line_end) : - type(type), line_start(line_start), line_end(line_end), - length(0.f), feedrate(0.f), time(0.f), time_max(0.f), slowdown(false) {} - - bool adjustable(bool slowdown_external_perimeters) const { - return (this->type & TYPE_ADJUSTABLE) && - (! (this->type & TYPE_EXTERNAL_PERIMETER) || slowdown_external_perimeters) && - this->time < this->time_max; - } - - bool adjustable() const { - return (this->type & TYPE_ADJUSTABLE) && this->time < this->time_max; - } - - size_t type; - // Start of this line at the G-code snippet. - size_t line_start; - // End of this line at the G-code snippet. - size_t line_end; - // XY Euclidian length of this segment. - float length; - // Current feedrate, possibly adjusted. - float feedrate; - // Current duration of this segment. - float time; - // Maximum duration of this segment. - float time_max; - // If marked with the "slowdown" flag, the line has been slowed down. - bool slowdown; -}; - -// Calculate the required per extruder time stretches. -struct PerExtruderAdjustments -{ - // Calculate the total elapsed time per this extruder, adjusted for the slowdown. - float elapsed_time_total() const { - float time_total = 0.f; - for (const CoolingLine &line : lines) - time_total += line.time; - return time_total; - } - // Calculate the total elapsed time when slowing down - // to the minimum extrusion feed rate defined for the current material. - float maximum_time_after_slowdown(bool slowdown_external_perimeters) const { - float time_total = 0.f; - for (const CoolingLine &line : lines) - if (line.adjustable(slowdown_external_perimeters)) { - if (line.time_max == FLT_MAX) - return FLT_MAX; - else - time_total += line.time_max; - } else - time_total += line.time; - return time_total; - } - // Calculate the adjustable part of the total time. - float adjustable_time(bool slowdown_external_perimeters) const { - float time_total = 0.f; - for (const CoolingLine &line : lines) - if (line.adjustable(slowdown_external_perimeters)) - time_total += line.time; - return time_total; - } - // Calculate the non-adjustable part of the total time. - float non_adjustable_time(bool slowdown_external_perimeters) const { - float time_total = 0.f; - for (const CoolingLine &line : lines) - if (! line.adjustable(slowdown_external_perimeters)) - time_total += line.time; - return time_total; - } - // Slow down the adjustable extrusions to the minimum feedrate allowed for the current extruder material. - // Used by both proportional and non-proportional slow down. - float slowdown_to_minimum_feedrate(bool slowdown_external_perimeters) { - float time_total = 0.f; - for (CoolingLine &line : lines) { - if (line.adjustable(slowdown_external_perimeters)) { - assert(line.time_max >= 0.f && line.time_max < FLT_MAX); - line.slowdown = true; - line.time = line.time_max; - line.feedrate = line.length / line.time; - } - time_total += line.time; - } - return time_total; - } - // Slow down each adjustable G-code line proportionally by a factor. - // Used by the proportional slow down. - float slow_down_proportional(float factor, bool slowdown_external_perimeters) { - assert(factor >= 1.f); - float time_total = 0.f; - for (CoolingLine &line : lines) { - if (line.adjustable(slowdown_external_perimeters)) { - line.slowdown = true; - line.time = std::min(line.time_max, line.time * factor); - line.feedrate = line.length / line.time; - } - time_total += line.time; - } - return time_total; - } - - // Sort the lines, adjustable first, higher feedrate first. - // Used by non-proportional slow down. - void sort_lines_by_decreasing_feedrate() { - std::sort(lines.begin(), lines.end(), [](const CoolingLine &l1, const CoolingLine &l2) { - bool adj1 = l1.adjustable(); - bool adj2 = l2.adjustable(); - return (adj1 == adj2) ? l1.feedrate > l2.feedrate : adj1; - }); - for (n_lines_adjustable = 0; - n_lines_adjustable < lines.size() && this->lines[n_lines_adjustable].adjustable(); - ++ n_lines_adjustable); - time_non_adjustable = 0.f; - for (size_t i = n_lines_adjustable; i < lines.size(); ++ i) - time_non_adjustable += lines[i].time; - } - - // Calculate the maximum time stretch when slowing down to min_feedrate. - // Slowdown to min_feedrate shall be allowed for this extruder's material. - // Used by non-proportional slow down. - float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) const { - float time_stretch = 0.f; - assert(this->slow_down_min_speed < min_feedrate + EPSILON); - for (size_t i = 0; i < n_lines_adjustable; ++ i) { - const CoolingLine &line = lines[i]; - if (line.feedrate > min_feedrate) - time_stretch += line.time * (line.feedrate / min_feedrate - 1.f); - } - return time_stretch; - } - - // Slow down all adjustable lines down to min_feedrate. - // Slowdown to min_feedrate shall be allowed for this extruder's material. - // Used by non-proportional slow down. - void slow_down_to_feedrate(float min_feedrate) { - assert(this->slow_down_min_speed < min_feedrate + EPSILON); - for (size_t i = 0; i < n_lines_adjustable; ++ i) { - CoolingLine &line = lines[i]; - if (line.feedrate > min_feedrate) { - line.time *= std::max(1.f, line.feedrate / min_feedrate); - line.feedrate = min_feedrate; - line.slowdown = true; - } - } - } - - // Extruder, for which the G-code will be adjusted. - unsigned int extruder_id = 0; - // Is the cooling slow down logic enabled for this extruder's material? - bool cooling_slow_down_enabled = false; - // Slow down the print down to slow_down_min_speed if the total layer time is below slow_down_layer_time. - float slow_down_layer_time = 0.f; - // Minimum print speed allowed for this extruder. - float slow_down_min_speed = 0.f; - //w14 - bool dont_slow_down_outer_wall = false; - - // Parsed lines. - std::vector lines; - // The following two values are set by sort_lines_by_decreasing_feedrate(): - // Number of adjustable lines, at the start of lines. - size_t n_lines_adjustable = 0; - // Non-adjustable time of lines starting with n_lines_adjustable. - float time_non_adjustable = 0; - // Current total time for this extruder. - float time_total = 0; - // Maximum time for this extruder, when the maximum slow down is applied. - float time_maximum = 0; - - // Temporaries for processing the slow down. Both thresholds go from 0 to n_lines_adjustable. - size_t idx_line_begin = 0; - size_t idx_line_end = 0; -}; - -// Calculate a new feedrate when slowing down by time_stretch for segments faster than min_feedrate. -// Used by non-proportional slow down. -float new_feedrate_to_reach_time_stretch( - std::vector::const_iterator it_begin, std::vector::const_iterator it_end, - float min_feedrate, float time_stretch, size_t max_iter = 20) -{ - float new_feedrate = min_feedrate; - for (size_t iter = 0; iter < max_iter; ++ iter) { + float new_feedrate = min_feedrate; + for (size_t iter = 0; iter < max_iter; ++iter) { double nomin = 0; double denom = time_stretch; - for (auto it = it_begin; it != it_end; ++ it) { - assert((*it)->slow_down_min_speed < min_feedrate + EPSILON); - for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) { - const CoolingLine &line = (*it)->lines[i]; + for (auto it = it_begin; it != it_end; ++it) { + assert((*it)->slow_down_min_speed < min_feedrate + EPSILON); + for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) { + const CoolingLine &line = (*it)->lines[i]; if (line.feedrate > min_feedrate) { - nomin += (double)line.time * (double)line.feedrate; - denom += (double)line.time; + nomin += (double) line.time * (double) line.feedrate; + denom += (double) line.time; } } } assert(denom > 0); - if (denom < 0) - return min_feedrate; - new_feedrate = (float)(nomin / denom); + if (denom < 0) return min_feedrate; + new_feedrate = (float) (nomin / denom); assert(new_feedrate > min_feedrate - EPSILON); - if (new_feedrate < min_feedrate + EPSILON) - goto finished; - for (auto it = it_begin; it != it_end; ++ it) - for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) { - const CoolingLine &line = (*it)->lines[i]; + if (new_feedrate < min_feedrate + EPSILON) goto finished; + for (auto it = it_begin; it != it_end; ++it) + for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) { + const CoolingLine &line = (*it)->lines[i]; if (line.feedrate > min_feedrate && line.feedrate < new_feedrate) - // Some of the line segments taken into account in the calculation of nomin / denom are now slower than new_feedrate, + // Some of the line segments taken into account in the calculation of nomin / denom are now slower than new_feedrate, // which makes the new_feedrate lower than it should be. // Re-run the calculation with a new min_feedrate limit, so that the segments with current feedrate lower than new_feedrate // are not taken into account. goto not_finished_yet; } goto finished; -not_finished_yet: + not_finished_yet: min_feedrate = new_feedrate; } // Failed to find the new feedrate for the time_stretch. -finished: +finished : // Test whether the time_stretch was achieved. #ifndef NDEBUG - { - float time_stretch_final = 0.f; - for (auto it = it_begin; it != it_end; ++ it) - time_stretch_final += (*it)->time_stretch_when_slowing_down_to_feedrate(new_feedrate); - assert(std::abs(time_stretch - time_stretch_final) < EPSILON); - } +{ + float time_stretch_final = 0.f; + for (auto it = it_begin; it != it_end; ++it) time_stretch_final += (*it)->time_stretch_when_slowing_down_to_feedrate(new_feedrate); + assert(std::abs(time_stretch - time_stretch_final) < EPSILON); +} + #endif /* NDEBUG */ - return new_feedrate; -} - -std::string CoolingBuffer::process_layer(std::string &&gcode, size_t layer_id, bool flush) -{ - // Cache the input G-code. - if (m_gcode.empty()) - m_gcode = std::move(gcode); - else - m_gcode += gcode; - - std::string out; - if (flush) { - // This is either an object layer or the very last print layer. Calculate cool down over the collected support layers - // and one object layer. - std::vector per_extruder_adjustments = this->parse_layer_gcode(m_gcode, m_current_pos); - float layer_time_stretched = this->calculate_layer_slowdown(per_extruder_adjustments); - out = this->apply_layer_cooldown(m_gcode, layer_id, layer_time_stretched, per_extruder_adjustments); - m_gcode.clear(); - } - return out; -} - -// Parse the layer G-code for the moves, which could be adjusted. -// Return the list of parsed lines, bucketed by an extruder. -std::vector CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector ¤t_pos) const -{ - std::vector per_extruder_adjustments(m_extruder_ids.size()); - std::vector map_extruder_to_per_extruder_adjustment(m_num_extruders, 0); - for (size_t i = 0; i < m_extruder_ids.size(); ++ i) { - PerExtruderAdjustments &adj = per_extruder_adjustments[i]; - unsigned int extruder_id = m_extruder_ids[i]; - adj.extruder_id = extruder_id; - adj.cooling_slow_down_enabled = m_config.slow_down_for_layer_cooling.get_at(extruder_id); - adj.slow_down_layer_time = float(m_config.slow_down_layer_time.get_at(extruder_id)); - adj.slow_down_min_speed = float(m_config.slow_down_min_speed.get_at(extruder_id)); - //w14 - adj.dont_slow_down_outer_wall = m_config.dont_slow_down_outer_wall.get_at(extruder_id); - map_extruder_to_per_extruder_adjustment[extruder_id] = i; - } - - unsigned int current_extruder = m_current_extruder; - PerExtruderAdjustments *adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]]; - const char *line_start = gcode.c_str(); - const char *line_end = line_start; - // Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command - // for a sequence of extrusion moves. - size_t active_speed_modifier = size_t(-1); - - for (; *line_start != 0; line_start = line_end) - { - while (*line_end != '\n' && *line_end != 0) - ++ line_end; - // sline will not contain the trailing '\n'. - std::string sline(line_start, line_end); - // CoolingLine will contain the trailing '\n'. - if (*line_end == '\n') - ++ line_end; - CoolingLine line(0, line_start - gcode.c_str(), line_end - gcode.c_str()); - if (boost::starts_with(sline, "G0 ")) - line.type = CoolingLine::TYPE_G0; - else if (boost::starts_with(sline, "G1 ")) - line.type = CoolingLine::TYPE_G1; - else if (boost::starts_with(sline, "G92 ")) - line.type = CoolingLine::TYPE_G92; - else if (boost::starts_with(sline, "G2 ")) - line.type = CoolingLine::TYPE_G2; - else if (boost::starts_with(sline, "G3 ")) - line.type = CoolingLine::TYPE_G3; - if (line.type) { - // G0, G1 or G92 - // Parse the G-code line. - std::vector new_pos(current_pos); - const char *c = sline.data() + 3; - for (;;) { - // Skip whitespaces. - for (; *c == ' ' || *c == '\t'; ++ c); - if (*c == 0 || *c == ';') - break; - - assert(is_decimal_separator_point()); // for atof - //QDS: Parse the axis. - size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') : - (*c == 'E') ? 3 : (*c == 'F') ? 4 : - (*c == 'I') ? 5 : (*c == 'J') ? 6 : size_t(-1); - if (axis != size_t(-1)) { - new_pos[axis] = float(atof(++c)); - if (axis == 4) { - // Convert mm/min to mm/sec. - new_pos[4] /= 60.f; - if ((line.type & CoolingLine::TYPE_G92) == 0) - // This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls. - line.type |= CoolingLine::TYPE_HAS_F; - } else if (axis == 5 || axis == 6) { - // QDS: get position of arc center - new_pos[axis] += current_pos[axis - 5]; - } - } - // Skip this word. - for (; *c != ' ' && *c != '\t' && *c != 0; ++ c); - } - bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER"); - bool wipe = boost::contains(sline, ";_WIPE"); - if (external_perimeter) - line.type |= CoolingLine::TYPE_EXTERNAL_PERIMETER; - if (wipe) - line.type |= CoolingLine::TYPE_WIPE; - //w14 - bool adjust_external = true; - if (adjustment->dont_slow_down_outer_wall && external_perimeter) adjust_external = false; - if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && !wipe && adjust_external) { - line.type |= CoolingLine::TYPE_ADJUSTABLE; - active_speed_modifier = adjustment->lines.size(); - } - if ((line.type & CoolingLine::TYPE_G92) == 0) { - //QDS: G0, G1, G2, G3. Calculate the duration. - if (m_config.use_relative_e_distances.value) - // Reset extruder accumulator. - current_pos[3] = 0.f; - float dif[4]; - for (size_t i = 0; i < 4; ++ i) - dif[i] = new_pos[i] - current_pos[i]; - float dxy2 = 0; - //QDS: support to calculate length of arc - if (line.type & CoolingLine::TYPE_G2 || line.type & CoolingLine::TYPE_G3) { - Vec3f start(current_pos[0], current_pos[1], 0); - Vec3f end(new_pos[0], new_pos[1], 0); - Vec3f center(new_pos[5], new_pos[6], 0); - bool is_ccw = line.type & CoolingLine::TYPE_G3; - float dxy = ArcSegment::calc_arc_length(start, end, center, is_ccw); - dxy2 = dxy * dxy; - } else { - dxy2 = dif[0] * dif[0] + dif[1] * dif[1]; - } - float dxyz2 = dxy2 + dif[2] * dif[2]; - if (dxyz2 > 0.f) { - // Movement in xyz, calculate time from the xyz Euclidian distance. - line.length = sqrt(dxyz2); - } else if (std::abs(dif[3]) > 0.f) { - // Movement in the extruder axis. - line.length = std::abs(dif[3]); - } - line.feedrate = new_pos[4]; - assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f); - if (line.length > 0) - line.time = line.length / line.feedrate; - line.time_max = line.time; - if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1)) - line.time_max = (adjustment->slow_down_min_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->slow_down_min_speed); - // QDS: add G2 and G3 support - if (active_speed_modifier < adjustment->lines.size() && ((line.type & CoolingLine::TYPE_G1) || - (line.type & CoolingLine::TYPE_G2) || - (line.type & CoolingLine::TYPE_G3))) { - // Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry. - assert((line.type & CoolingLine::TYPE_HAS_F) == 0); - CoolingLine &sm = adjustment->lines[active_speed_modifier]; - assert(sm.feedrate > 0.f); - sm.length += line.length; - sm.time += line.time; - if (sm.time_max != FLT_MAX) { - if (line.time_max == FLT_MAX) - sm.time_max = FLT_MAX; - else - sm.time_max += line.time_max; - } - // Don't store this line. - line.type = 0; - } - } - current_pos = std::move(new_pos); - } else if (boost::starts_with(sline, ";_EXTRUDE_END")) { - line.type = CoolingLine::TYPE_EXTRUDE_END; - active_speed_modifier = size_t(-1); - } else if (boost::starts_with(sline, m_toolchange_prefix)) { - unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + m_toolchange_prefix.size()); - // Only change extruder in case the number is meaningful. User could provide an out-of-range index through custom gcodes - those shall be ignored. - if (new_extruder < map_extruder_to_per_extruder_adjustment.size()) { - if (new_extruder != current_extruder) { - // Switch the tool. - line.type = CoolingLine::TYPE_SET_TOOL; - current_extruder = new_extruder; - adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]]; - } - } - else { - // Only log the error in case of MM printer. Single extruder printers likely ignore any T anyway. - if (map_extruder_to_per_extruder_adjustment.size() > 1) - BOOST_LOG_TRIVIAL(error) << "CoolingBuffer encountered an invalid toolchange, maybe from a custom gcode: " << sline; - } - - } else if (boost::starts_with(sline, ";_OVERHANG_FAN_START")) { - line.type = CoolingLine::TYPE_OVERHANG_FAN_START; - } else if (boost::starts_with(sline, ";_OVERHANG_FAN_END")) { - line.type = CoolingLine::TYPE_OVERHANG_FAN_END; - } else if (boost::starts_with(sline, "G4 ")) { - // Parse the wait time. - line.type = CoolingLine::TYPE_G4; - size_t pos_S = sline.find('S', 3); - size_t pos_P = sline.find('P', 3); - assert(is_decimal_separator_point()); // for atof - line.time = line.time_max = float( - (pos_S > 0) ? atof(sline.c_str() + pos_S + 1) : - (pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.); - } else if (boost::starts_with(sline, ";_FORCE_RESUME_FAN_SPEED")) { - line.type = CoolingLine::TYPE_FORCE_RESUME_FAN; - } else if (boost::starts_with(sline, ";_SET_FAN_SPEED_CHANGING_LAYER")) { - line.type = CoolingLine::TYPE_SET_FAN_CHANGING_LAYER; - } - if (line.type != 0) - adjustment->lines.emplace_back(std::move(line)); - } - - return per_extruder_adjustments; + return new_feedrate; } // Slow down an extruder range proportionally down to slow_down_layer_time. // Return the total time for the complete layer. -static inline float extruder_range_slow_down_proportional( - std::vector::iterator it_begin, - std::vector::iterator it_end, +static inline float extruder_range_slow_down_proportional(std::vector::iterator it_begin, + std::vector::iterator it_end, // Elapsed time for the extruders already processed. float elapsed_time_total0, // Initial total elapsed time before slow down. @@ -524,41 +72,33 @@ static inline float extruder_range_slow_down_proportional( float total_after_slowdown = elapsed_time_before_slowdown; // Now decide, whether the external perimeters shall be slowed down as well. float max_time_nep = elapsed_time_total0; - for (auto it = it_begin; it != it_end; ++ it) - max_time_nep += (*it)->maximum_time_after_slowdown(false); + for (auto it = it_begin; it != it_end; ++it) max_time_nep += (*it)->maximum_time_after_slowdown(false); if (max_time_nep > slow_down_layer_time) { // It is sufficient to slow down the non-external perimeter moves to reach the target layer time. // Slow down the non-external perimeters proportionally. float non_adjustable_time = elapsed_time_total0; - for (auto it = it_begin; it != it_end; ++ it) - non_adjustable_time += (*it)->non_adjustable_time(false); + for (auto it = it_begin; it != it_end; ++it) non_adjustable_time += (*it)->non_adjustable_time(false); // The following step is a linear programming task due to the minimum movement speeds of the print moves. // Run maximum 5 iterations until a good enough approximation is reached. - for (size_t iter = 0; iter < 5; ++ iter) { + for (size_t iter = 0; iter < 5; ++iter) { float factor = (slow_down_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time); assert(factor > 1.f); total_after_slowdown = elapsed_time_total0; - for (auto it = it_begin; it != it_end; ++ it) - total_after_slowdown += (*it)->slow_down_proportional(factor, false); - if (total_after_slowdown > 0.95f * slow_down_layer_time) - break; + for (auto it = it_begin; it != it_end; ++it) total_after_slowdown += (*it)->slow_down_proportional(factor, false); + if (total_after_slowdown > 0.95f * slow_down_layer_time) break; } } else { // Slow down everything. First slow down the non-external perimeters to maximum. - for (auto it = it_begin; it != it_end; ++ it) - (*it)->slowdown_to_minimum_feedrate(false); + for (auto it = it_begin; it != it_end; ++it) (*it)->slowdown_to_minimum_feedrate(false); // Slow down the external perimeters proportionally. float non_adjustable_time = elapsed_time_total0; - for (auto it = it_begin; it != it_end; ++ it) - non_adjustable_time += (*it)->non_adjustable_time(true); - for (size_t iter = 0; iter < 5; ++ iter) { + for (auto it = it_begin; it != it_end; ++it) non_adjustable_time += (*it)->non_adjustable_time(true); + for (size_t iter = 0; iter < 5; ++iter) { float factor = (slow_down_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time); assert(factor > 1.f); total_after_slowdown = elapsed_time_total0; - for (auto it = it_begin; it != it_end; ++ it) - total_after_slowdown += (*it)->slow_down_proportional(factor, true); - if (total_after_slowdown > 0.95f * slow_down_layer_time) - break; + for (auto it = it_begin; it != it_end; ++it) total_after_slowdown += (*it)->slow_down_proportional(factor, true); + if (total_after_slowdown > 0.95f * slow_down_layer_time) break; } } return total_after_slowdown; @@ -566,39 +106,36 @@ static inline float extruder_range_slow_down_proportional( // Slow down an extruder range to slow_down_layer_time. // Return the total time for the complete layer. -static inline void extruder_range_slow_down_non_proportional( - std::vector::iterator it_begin, - std::vector::iterator it_end, - float time_stretch) +static inline void extruder_range_slow_down_non_proportional(std::vector::iterator it_begin, + std::vector::iterator it_end, + float time_stretch) { // Slow down. Try to equalize the feedrates. - std::vector by_min_print_speed(it_begin, it_end); + std::vector by_min_print_speed(it_begin, it_end); // Find the next highest adjustable feedrate among the extruders. float feedrate = 0; for (PerExtruderAdjustments *adj : by_min_print_speed) { adj->idx_line_begin = 0; adj->idx_line_end = 0; assert(adj->idx_line_begin < adj->n_lines_adjustable); - if (adj->lines[adj->idx_line_begin].feedrate > feedrate) - feedrate = adj->lines[adj->idx_line_begin].feedrate; + if (adj->lines[adj->idx_line_begin].feedrate > feedrate) feedrate = adj->lines[adj->idx_line_begin].feedrate; } assert(feedrate > 0.f); // Sort by slow_down_min_speed, maximum speed first. - std::sort(by_min_print_speed.begin(), by_min_print_speed.end(), - [](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2){ return p1->slow_down_min_speed > p2->slow_down_min_speed; }); + std::sort(by_min_print_speed.begin(), by_min_print_speed.end(), + [](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2) { return p1->slow_down_min_speed > p2->slow_down_min_speed; }); // Slow down, fast moves first. for (;;) { // For each extruder, find the span of lines with a feedrate close to feedrate. for (PerExtruderAdjustments *adj : by_min_print_speed) { - for (adj->idx_line_end = adj->idx_line_begin; - adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate - EPSILON; - ++ adj->idx_line_end) ; + for (adj->idx_line_end = adj->idx_line_begin; adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate - EPSILON; + ++adj->idx_line_end) + ; } // Find the next highest adjustable feedrate among the extruders. float feedrate_next = 0.f; for (PerExtruderAdjustments *adj : by_min_print_speed) - if (adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate_next) - feedrate_next = adj->lines[adj->idx_line_end].feedrate; + if (adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate_next) feedrate_next = adj->lines[adj->idx_line_end].feedrate; // Slow down, limited by max(feedrate_next, slow_down_min_speed). for (auto adj = by_min_print_speed.begin(); adj != by_min_print_speed.end();) { // Slow down at most by time_stretch. @@ -606,31 +143,27 @@ static inline void extruder_range_slow_down_non_proportional( // All the adjustable speeds are now lowered to the same speed, // and the minimum speed is set to zero. float time_adjustable = 0.f; - for (auto it = adj; it != by_min_print_speed.end(); ++ it) - time_adjustable += (*it)->adjustable_time(true); + for (auto it = adj; it != by_min_print_speed.end(); ++it) time_adjustable += (*it)->adjustable_time(true); float rate = (time_adjustable + time_stretch) / time_adjustable; - for (auto it = adj; it != by_min_print_speed.end(); ++ it) - (*it)->slow_down_proportional(rate, true); + for (auto it = adj; it != by_min_print_speed.end(); ++it) (*it)->slow_down_proportional(rate, true); return; } else { - float feedrate_limit = std::max(feedrate_next, (*adj)->slow_down_min_speed); - bool done = false; + float feedrate_limit = std::max(feedrate_next, (*adj)->slow_down_min_speed); + bool done = false; float time_stretch_max = 0.f; - for (auto it = adj; it != by_min_print_speed.end(); ++ it) - time_stretch_max += (*it)->time_stretch_when_slowing_down_to_feedrate(feedrate_limit); + for (auto it = adj; it != by_min_print_speed.end(); ++it) time_stretch_max += (*it)->time_stretch_when_slowing_down_to_feedrate(feedrate_limit); if (time_stretch_max >= time_stretch) { feedrate_limit = new_feedrate_to_reach_time_stretch(adj, by_min_print_speed.end(), feedrate_limit, time_stretch, 20); done = true; } else time_stretch -= time_stretch_max; - for (auto it = adj; it != by_min_print_speed.end(); ++ it) - (*it)->slow_down_to_feedrate(feedrate_limit); - if (done) - return; + for (auto it = adj; it != by_min_print_speed.end(); ++it) (*it)->slow_down_to_feedrate(feedrate_limit); + if (done) return; } // Skip the other extruders with nearly the same slow_down_min_speed, as they have been processed already. auto next = adj; - for (++ next; next != by_min_print_speed.end() && (*next)->slow_down_min_speed > (*adj)->slow_down_min_speed - EPSILON; ++ next); + for (++next; next != by_min_print_speed.end() && (*next)->slow_down_min_speed > (*adj)->slow_down_min_speed - EPSILON; ++next) + ; adj = next; } if (feedrate_next == 0.f) @@ -649,34 +182,32 @@ float CoolingBuffer::calculate_layer_slowdown(std::vector by_slowdown_time; + std::vector by_slowdown_time; by_slowdown_time.reserve(per_extruder_adjustments.size()); // Only insert entries, which are adjustable (have cooling enabled and non-zero stretchable time). // Collect total print time of non-adjustable extruders. float elapsed_time_total0 = 0.f; for (PerExtruderAdjustments &adj : per_extruder_adjustments) { // Curren total time for this extruder. - adj.time_total = adj.elapsed_time_total(); + adj.time_total = adj.elapsed_time_total(); // Maximum time for this extruder, when all extrusion moves are slowed down to min_extrusion_speed. adj.time_maximum = adj.maximum_time_after_slowdown(true); if (adj.cooling_slow_down_enabled && adj.lines.size() > 0) { by_slowdown_time.emplace_back(&adj); - if (! m_cooling_logic_proportional) + if (!m_cooling_logic_proportional) // sorts the lines, also sets adj.time_non_adjustable adj.sort_lines_by_decreasing_feedrate(); } else elapsed_time_total0 += adj.elapsed_time_total(); } std::sort(by_slowdown_time.begin(), by_slowdown_time.end(), - [](const PerExtruderAdjustments *adj1, const PerExtruderAdjustments *adj2) - { return adj1->slow_down_layer_time < adj2->slow_down_layer_time; }); + [](const PerExtruderAdjustments *adj1, const PerExtruderAdjustments *adj2) { return adj1->slow_down_layer_time < adj2->slow_down_layer_time; }); - for (auto cur_begin = by_slowdown_time.begin(); cur_begin != by_slowdown_time.end(); ++ cur_begin) { + for (auto cur_begin = by_slowdown_time.begin(); cur_begin != by_slowdown_time.end(); ++cur_begin) { PerExtruderAdjustments &adj = *(*cur_begin); // Calculate the current adjusted elapsed_time_total over the non-finalized extruders. float total = elapsed_time_total0; - for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it) - total += (*it)->time_total; + for (auto it = cur_begin; it != by_slowdown_time.end(); ++it) total += (*it)->time_total; float slow_down_layer_time = adj.slow_down_layer_time * 1.001f; if (total > slow_down_layer_time) { // The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything. @@ -684,8 +215,7 @@ float CoolingBuffer::calculate_layer_slowdown(std::vectortime_maximum; + for (auto it = cur_begin; it != by_slowdown_time.end(); ++it) max_time += (*it)->time_maximum; if (max_time > slow_down_layer_time) { if (m_cooling_logic_proportional) extruder_range_slow_down_proportional(cur_begin, by_slowdown_time.end(), elapsed_time_total0, total, slow_down_layer_time); @@ -693,8 +223,7 @@ float CoolingBuffer::calculate_layer_slowdown(std::vectorslowdown_to_minimum_feedrate(true); + for (auto it = cur_begin; it != by_slowdown_time.end(); ++it) (*it)->slowdown_to_minimum_feedrate(true); } } elapsed_time_total0 += adj.elapsed_time_total(); @@ -703,253 +232,5 @@ float CoolingBuffer::calculate_layer_slowdown(std::vector &per_extruder_adjustments) -{ - // First sort the adjustment lines by of multiple extruders by their position in the source G-code. - std::vector lines; - { - size_t n_lines = 0; - for (const PerExtruderAdjustments &adj : per_extruder_adjustments) - n_lines += adj.lines.size(); - lines.reserve(n_lines); - for (const PerExtruderAdjustments &adj : per_extruder_adjustments) - for (const CoolingLine &line : adj.lines) - lines.emplace_back(&line); - std::sort(lines.begin(), lines.end(), [](const CoolingLine *ln1, const CoolingLine *ln2) { return ln1->line_start < ln2->line_start; } ); - } - // Second generate the adjusted G-code. - std::string new_gcode; - new_gcode.reserve(gcode.size() * 2); - bool overhang_fan_control= false; - int overhang_fan_speed = 0; - - enum class SetFanType { - sfChangingLayer = 0, - sfChangingFilament, - sfImmediatelyApply - }; - - auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &overhang_fan_control, &overhang_fan_speed](SetFanType type) { -#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder) - int fan_min_speed = EXTRUDER_CONFIG(fan_min_speed); - int fan_speed_new = EXTRUDER_CONFIG(reduce_fan_stop_start_freq) ? fan_min_speed : 0; - //QDS - //w13 - int additional_fan_speed_new; - if (m_config.seal) - additional_fan_speed_new = EXTRUDER_CONFIG(additional_cooling_fan_speed); - else - additional_fan_speed_new = EXTRUDER_CONFIG(additional_cooling_fan_speed_unseal); - //int additional_fan_speed_new = EXTRUDER_CONFIG(additional_cooling_fan_speed); - - int close_fan_the_first_x_layers = EXTRUDER_CONFIG(close_fan_the_first_x_layers); - // Is the fan speed ramp enabled? - int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer); - if (close_fan_the_first_x_layers <= 0 && full_fan_speed_layer > 0) { - // When ramping up fan speed from close_fan_the_first_x_layers to full_fan_speed_layer, force close_fan_the_first_x_layers above zero, - // so there will be a zero fan speed at least at the 1st layer. - close_fan_the_first_x_layers = 1; - } - if (int(layer_id) >= close_fan_the_first_x_layers) { - int fan_max_speed = EXTRUDER_CONFIG(fan_max_speed); - float slow_down_layer_time = float(EXTRUDER_CONFIG(slow_down_layer_time)); - float fan_cooling_layer_time = float(EXTRUDER_CONFIG(fan_cooling_layer_time)); - //QDS: always enable the fan speed interpolation according to layer time - //if (EXTRUDER_CONFIG(cooling)) { - if (layer_time < slow_down_layer_time) { - // Layer time very short. Enable the fan to a full throttle. - fan_speed_new = fan_max_speed; - } else if (layer_time < fan_cooling_layer_time) { - // Layer time quite short. Enable the fan proportionally according to the current layer time. - assert(layer_time >= slow_down_layer_time); - double t = (layer_time - slow_down_layer_time) / (fan_cooling_layer_time - slow_down_layer_time); - fan_speed_new = int(floor(t * fan_min_speed + (1. - t) * fan_max_speed) + 0.5); - } - //} - overhang_fan_speed = EXTRUDER_CONFIG(overhang_fan_speed); - if (int(layer_id) >= close_fan_the_first_x_layers && int(layer_id) + 1 < full_fan_speed_layer) { - // Ramp up the fan speed from close_fan_the_first_x_layers to full_fan_speed_layer. - float factor = float(int(layer_id + 1) - close_fan_the_first_x_layers) / float(full_fan_speed_layer - close_fan_the_first_x_layers); - fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255); - overhang_fan_speed = std::clamp(int(float(overhang_fan_speed) * factor + 0.5f), 0, 255); - } -#undef EXTRUDER_CONFIG - overhang_fan_control= overhang_fan_speed > fan_speed_new; - } else { - overhang_fan_control= false; - overhang_fan_speed = 0; - fan_speed_new = 0; - additional_fan_speed_new = 0; - } - if (fan_speed_new != m_fan_speed) { - m_fan_speed = fan_speed_new; - //QDS - m_current_fan_speed = fan_speed_new; - if (type == SetFanType::sfImmediatelyApply) - new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed); - else if (type == SetFanType::sfChangingLayer) - this->m_set_fan_changing_layer = true; - //QDS: don't need to handle change filament, because we are always force to resume fan speed when filament change is finished - } - //QDS - if (additional_fan_speed_new != m_additional_fan_speed) { - m_additional_fan_speed = additional_fan_speed_new; - if (type == SetFanType::sfImmediatelyApply) - new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed); - else if (type == SetFanType::sfChangingLayer) - this->m_set_addition_fan_changing_layer = true; - //QDS: don't need to handle change filament, because we are always force to resume fan speed when filament change is finished - } - }; - - const char *pos = gcode.c_str(); - int current_feedrate = 0; - //QDS - m_set_fan_changing_layer = false; - m_set_addition_fan_changing_layer = false; - change_extruder_set_fan(SetFanType::sfChangingLayer); - for (const CoolingLine *line : lines) { - const char *line_start = gcode.c_str() + line->line_start; - const char *line_end = gcode.c_str() + line->line_end; - if (line_start > pos) - new_gcode.append(pos, line_start - pos); - if (line->type & CoolingLine::TYPE_SET_TOOL) { - unsigned int new_extruder = (unsigned int)atoi(line_start + m_toolchange_prefix.size()); - if (new_extruder != m_current_extruder) { - m_current_extruder = new_extruder; - change_extruder_set_fan(SetFanType::sfChangingFilament); //QDS: will force to resume fan speed when filament change is finished - } - new_gcode.append(line_start, line_end - line_start); - } else if (line->type & CoolingLine::TYPE_OVERHANG_FAN_START) { - if (overhang_fan_control) { - //QDS - m_current_fan_speed = overhang_fan_speed; - new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, overhang_fan_speed); - } - } else if (line->type & CoolingLine::TYPE_OVERHANG_FAN_END) { - if (overhang_fan_control) { - //QDS - m_current_fan_speed = m_fan_speed; - new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed); - } - } else if (line->type & CoolingLine::TYPE_FORCE_RESUME_FAN) { - //QDS: force to write a fan speed command again - if (m_current_fan_speed != -1) - new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed); - if (m_additional_fan_speed != -1) - new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed); - } else if (line->type & CoolingLine::TYPE_SET_FAN_CHANGING_LAYER) { - //QDS: check whether fan speed need to changed when change layer - if (m_current_fan_speed != -1 && m_set_fan_changing_layer) { - new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed); - m_set_fan_changing_layer = false; - } - if (m_additional_fan_speed != -1 && m_set_addition_fan_changing_layer) { - new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed); - m_set_addition_fan_changing_layer = false; - } - } - else if (line->type & CoolingLine::TYPE_EXTRUDE_END) { - // Just remove this comment. - } else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) { - // Find the start of a comment, or roll to the end of line. - const char *end = line_start; - for (; end < line_end && *end != ';'; ++ end); - // Find the 'F' word. - const char *fpos = strstr(line_start + 2, " F") + 2; - int new_feedrate = current_feedrate; - // Modify the F word of the current G-code line. - bool modify = false; - // Remove the F word from the current G-code line. - bool remove = false; - assert(fpos != nullptr); - new_feedrate = line->slowdown ? int(floor(60. * line->feedrate + 0.5)) : atoi(fpos); - if (new_feedrate == current_feedrate) { - // No need to change the F value. - if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.) - // Feedrate does not change and this line does not move the print head. Skip the complete G-code line including the G-code comment. - end = line_end; - else - // Remove the feedrate from the G0/G1 line. The G-code line may become empty! - remove = true; - } else if (line->slowdown) { - // The F value will be overwritten. - modify = true; - } else { - // The F value is different from current_feedrate, but not slowed down, thus the G-code line will not be modified. - // Emit the line without the comment. - new_gcode.append(line_start, end - line_start); - current_feedrate = new_feedrate; - } - if (modify || remove) { - if (modify) { - // Replace the feedrate. - new_gcode.append(line_start, fpos - line_start); - current_feedrate = new_feedrate; - char buf[64]; - sprintf(buf, "%d", int(current_feedrate)); - new_gcode += buf; - } else { - // Remove the feedrate word. - const char *f = fpos; - // Roll the pointer before the 'F' word. - for (f -= 2; f > line_start && (*f == ' ' || *f == '\t'); -- f); - - if ((f - line_start == 1) && *line_start == 'G' && (*f == '1' || *f == '0')) { - // QDS: only remain "G1" or "G0" of this line after remove 'F' part, don't save - } else { - // Append up to the F word, without the trailing whitespace. - new_gcode.append(line_start, f - line_start + 1); - } - } - // Skip the non-whitespaces of the F parameter up the comment or end of line. - for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++ fpos); - // Append the rest of the line without the comment. - if (fpos < end) - // The G-code line is not empty yet. Emit the rest of it. - new_gcode.append(fpos, end - fpos); - else if (remove && new_gcode == "G1") { - // The G-code line only contained the F word, now it is empty. Remove it completely including the comments. - new_gcode.resize(new_gcode.size() - 2); - end = line_end; - } - } - // Process the rest of the line. - if (end < line_end) { - if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) { - // Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE" - std::string comment(end, line_end); - boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", ""); - if (line->type & CoolingLine::TYPE_EXTERNAL_PERIMETER) - boost::replace_all(comment, ";_EXTERNAL_PERIMETER", ""); - if (line->type & CoolingLine::TYPE_WIPE) - boost::replace_all(comment, ";_WIPE", ""); - new_gcode += comment; - } else { - // Just attach the rest of the source line. - new_gcode.append(end, line_end - end); - } - } - } else { - new_gcode.append(line_start, line_end - line_start); - } - pos = line_end; - } - const char *gcode_end = gcode.c_str() + gcode.size(); - if (pos < gcode_end) - new_gcode.append(pos, gcode_end - pos); - - return new_gcode; } -} // namespace Slic3r diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp index eeaaf63..3837f13 100644 --- a/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/src/libslic3r/GCode/CoolingBuffer.hpp @@ -1,70 +1,21 @@ -#ifndef slic3r_CoolingBuffer_hpp_ -#define slic3r_CoolingBuffer_hpp_ - +#ifndef slic3r_CoolingProcess_hpp_ +#define slic3r_CoolingProcess_hpp_ #include "../libslic3r.h" -#include -#include -#include +#include "GCodeEditor.hpp" namespace Slic3r { -class GCode; -class Layer; -struct PerExtruderAdjustments; - -// A standalone G-code filter, to control cooling of the print. -// The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited -// and the print is modified to stretch over a minimum layer time. -// -// The simple it sounds, the actual implementation is significantly more complex. -// Namely, for a multi-extruder print, each material may require a different cooling logic. -// For example, some materials may not like to print too slowly, while with some materials -// we may slow down significantly. -// -class CoolingBuffer { -public: - CoolingBuffer(GCode &gcodegen); - void reset(const Vec3d &position); - void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; } - std::string process_layer(std::string &&gcode, size_t layer_id, bool flush); - -private: - CoolingBuffer& operator=(const CoolingBuffer&) = delete; - std::vector parse_layer_gcode(const std::string &gcode, std::vector ¤t_pos) const; - float calculate_layer_slowdown(std::vector &per_extruder_adjustments); - // Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed. - // Returns the adjusted G-code. - std::string apply_layer_cooldown(const std::string &gcode, size_t layer_id, float layer_time, std::vector &per_extruder_adjustments); - - // G-code snippet cached for the support layers preceding an object layer. - std::string m_gcode; - // Internal data. - // QDS: X,Y,Z,E,F,I,J - std::vector m_axis; - std::vector m_current_pos; - // Current known fan speed or -1 if not known yet. - int m_fan_speed; - int m_additional_fan_speed; - // Cached from GCodeWriter. - // Printing extruder IDs, zero based. - std::vector m_extruder_ids; - // Highest of m_extruder_ids plus 1. - unsigned int m_num_extruders { 0 }; - const std::string m_toolchange_prefix; - // Referencs GCode::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified, - // the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required. - const PrintConfig &m_config; - unsigned int m_current_extruder; - - // Old logic: proportional. - bool m_cooling_logic_proportional = false; - //QDS: current fan speed - int m_current_fan_speed; - //QDS: - bool m_set_fan_changing_layer = false; - bool m_set_addition_fan_changing_layer = false; -}; - -} - -#endif + class CoolingBuffer + { + public: + CoolingBuffer(){}; + + float calculate_layer_slowdown(std::vector &per_extruder_adjustments); + + private: + // Old logic: proportional. + bool m_cooling_logic_proportional = false; + }; + + } + #endif diff --git a/src/libslic3r/GCode/GCodeEditor.cpp b/src/libslic3r/GCode/GCodeEditor.cpp new file mode 100644 index 0000000..2a637c1 --- /dev/null +++ b/src/libslic3r/GCode/GCodeEditor.cpp @@ -0,0 +1,636 @@ +#include "../GCode.hpp" +#include "GCodeEditor.hpp" +#include +#include +#include +#include +#include + +#if 0 + #define DEBUG + #define _DEBUG + #undef NDEBUG +#endif + +#include + +namespace Slic3r { + +GCodeEditor::GCodeEditor(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0) +{ + this->reset(gcodegen.writer().get_position()); + + const std::vector &extruders = gcodegen.writer().extruders(); + m_extruder_ids.reserve(extruders.size()); + for (const Extruder &ex : extruders) { + m_num_extruders = std::max(ex.id() + 1, m_num_extruders); + m_extruder_ids.emplace_back(ex.id()); + } +} + +void GCodeEditor::reset(const Vec3d &position) +{ + // QDS: add I and J axis to store center of arc + m_current_pos.assign(7, 0.f); + m_current_pos[0] = float(position.x()); + m_current_pos[1] = float(position.y()); + m_current_pos[2] = float(position.z()); + m_current_pos[4] = float(m_config.travel_speed.get_at(get_extruder_index(m_config, m_current_extruder))); + m_fan_speed = -1; + m_additional_fan_speed = -1; + m_current_fan_speed = -1; +} + +static void record_wall_lines(bool &flag, int &line_idx, PerExtruderAdjustments *adjustment, const std::pair &node_pos) +{ + if (flag && line_idx < adjustment->lines.size()) { + CoolingLine &ptr = adjustment->lines[line_idx]; + ptr.outwall_smooth_mark = true; + ptr.object_id = node_pos.first; + ptr.cooling_node_id = node_pos.second; + flag = false; + } +} + +static void mark_node_pos( + bool &flag, int &line_idx, std::pair &node_pos, const std::vector &object_label, int cooling_node_id, int object_id, PerExtruderAdjustments *adjustment) +{ + for (size_t object_idx = 0; object_idx < object_label.size(); ++object_idx) { + if (object_label[object_idx] == object_id) { + if (cooling_node_id == -1) break; + line_idx = adjustment->lines.size(); + flag = true; + node_pos.first = object_idx; + node_pos.second = cooling_node_id; + break; + } + } +} + + +std::string GCodeEditor::process_layer(std::string && gcode, + const size_t layer_id, + std::vector &per_extruder_adjustments, + const std::vector & object_label, + const bool flush, + const bool spiral_vase) +{ + // Cache the input G-code. + if (m_gcode.empty()) + m_gcode = std::move(gcode); + else + m_gcode += gcode; + + std::string out; + if (flush) { + // This is either an object layer or the very last print layer. Calculate cool down over the collected support layers + // and one object layer. + // record parse gcode info to per_extruder_adjustments + per_extruder_adjustments = this->parse_layer_gcode(m_gcode, m_current_pos, object_label, spiral_vase, layer_id > 0); + out = m_gcode; + m_gcode.clear(); + } + return out; +} + +//native-resource://sandbox_fs/webcontent/resource/assets/img/41ecc25c56.png +// Parse the layer G-code for the moves, which could be adjusted. +// Return the list of parsed lines, bucketed by an extruder. +std::vector GCodeEditor::parse_layer_gcode( + const std::string &gcode, + std::vector & current_pos, + const std::vector & object_label, + bool spiral_vase, + bool join_z_smooth) +{ + std::vector per_extruder_adjustments(m_extruder_ids.size()); + std::vector map_extruder_to_per_extruder_adjustment(m_num_extruders, 0); + for (size_t i = 0; i < m_extruder_ids.size(); ++ i) { + PerExtruderAdjustments &adj = per_extruder_adjustments[i]; + unsigned int extruder_id = m_extruder_ids[i]; + adj.extruder_id = extruder_id; + adj.cooling_slow_down_enabled = m_config.slow_down_for_layer_cooling.get_at(extruder_id); + adj.slow_down_layer_time = float(m_config.slow_down_layer_time.get_at(extruder_id)); + adj.slow_down_min_speed = float(m_config.slow_down_min_speed.get_at(extruder_id)); + //w14 + adj.dont_slow_down_outer_wall = m_config.dont_slow_down_outer_wall.get_at(extruder_id); + + map_extruder_to_per_extruder_adjustment[extruder_id] = i; + } + + unsigned int current_extruder = m_parse_gcode_extruder; + PerExtruderAdjustments *adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]]; + const char *line_start = gcode.c_str(); + const char *line_end = line_start; + // Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command + // for a sequence of extrusion moves. + size_t active_speed_modifier = size_t(-1); + int object_id = -1; + int cooling_node_id = -1; + std::string object_id_string = "; OBJECT_ID: "; + std::string cooling_node_label = "; COOLING_NODE: "; + bool append_wall_ptr = false; + bool append_inner_wall_ptr = false; + bool not_join_cooling = false; + std::pair node_pos; + int line_idx = -1; + for (; *line_start != 0; line_start = line_end) + { + while (*line_end != '\n' && *line_end != 0) + ++ line_end; + // sline will not contain the trailing '\n'. + std::string sline(line_start, line_end); + // CoolingLine will contain the trailing '\n'. + if (*line_end == '\n') + ++ line_end; + CoolingLine line(0, line_start - gcode.c_str(), line_end - gcode.c_str()); + if (boost::starts_with(sline, "G0 ")) + line.type = CoolingLine::TYPE_G0; + else if (boost::starts_with(sline, "G1 ")) + line.type = CoolingLine::TYPE_G1; + else if (boost::starts_with(sline, "G92 ")) + line.type = CoolingLine::TYPE_G92; + else if (boost::starts_with(sline, "G2 ")) + line.type = CoolingLine::TYPE_G2; + else if (boost::starts_with(sline, "G3 ")) + line.type = CoolingLine::TYPE_G3; + //QDS: parse object id & node id + else if (boost::starts_with(sline, object_id_string)) { + std::string sub = sline.substr(object_id_string.size()); + object_id = std::stoi(sub); + } else if (boost::starts_with(sline, cooling_node_label)) { + std::string sub = sline.substr(cooling_node_label.size()); + cooling_node_id = std::stoi(sub); + } + + if (line.type) { + // G0, G1 or G92 + // Parse the G-code line. + std::vector new_pos(current_pos); + const char *c = sline.data() + 3; + for (;;) { + // Skip whitespaces. + for (; *c == ' ' || *c == '\t'; ++ c); + if (*c == 0 || *c == ';') + break; + + assert(is_decimal_separator_point()); // for atof + //QDS: Parse the axis. + size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') : + (*c == 'E') ? 3 : (*c == 'F') ? 4 : + (*c == 'I') ? 5 : (*c == 'J') ? 6 : size_t(-1); + if (axis != size_t(-1)) { + new_pos[axis] = float(atof(++c)); + if (axis == 4) { + // Convert mm/min to mm/sec. + new_pos[4] /= 60.f; + if ((line.type & CoolingLine::TYPE_G92) == 0) + // This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls. + line.type |= CoolingLine::TYPE_HAS_F; + } else if (axis == 5 || axis == 6) { + // QDS: get position of arc center + new_pos[axis] += current_pos[axis - 5]; + } + } + // Skip this word. + for (; *c != ' ' && *c != '\t' && *c != 0; ++ c); + } + bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER"); + bool wipe = boost::contains(sline, ";_WIPE"); + + record_wall_lines(append_inner_wall_ptr, line_idx, adjustment, node_pos); + + if (wipe) + line.type |= CoolingLine::TYPE_WIPE; + + //w14 + bool adjust_external = true; + if (adjustment->dont_slow_down_outer_wall && external_perimeter) adjust_external = false; + + if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && !wipe && !not_join_cooling && adjust_external) { + line.type |= CoolingLine::TYPE_ADJUSTABLE; + active_speed_modifier = adjustment->lines.size(); + } + + record_wall_lines(append_wall_ptr, line_idx, adjustment, node_pos); + + if (external_perimeter) { + line.type |= CoolingLine::TYPE_EXTERNAL_PERIMETER; + if (line.type & CoolingLine::TYPE_ADJUSTABLE && join_z_smooth && !spiral_vase) { + // QDS: collect outwall info + mark_node_pos(append_wall_ptr, line_idx, node_pos, object_label, cooling_node_id, object_id, adjustment); + } + } + + if ((line.type & CoolingLine::TYPE_G92) == 0) { + //QDS: G0, G1, G2, G3. Calculate the duration. + if (m_config.use_relative_e_distances.value) + // Reset extruder accumulator. + current_pos[3] = 0.f; + float dif[4]; + for (size_t i = 0; i < 4; ++ i) + dif[i] = new_pos[i] - current_pos[i]; + float dxy2 = 0; + //QDS: support to calculate length of arc + if (line.type & CoolingLine::TYPE_G2 || line.type & CoolingLine::TYPE_G3) { + Vec3f start(current_pos[0], current_pos[1], 0); + Vec3f end(new_pos[0], new_pos[1], 0); + Vec3f center(new_pos[5], new_pos[6], 0); + bool is_ccw = line.type & CoolingLine::TYPE_G3; + float dxy = ArcSegment::calc_arc_length(start, end, center, is_ccw); + dxy2 = dxy * dxy; + } else { + dxy2 = dif[0] * dif[0] + dif[1] * dif[1]; + } + float dxyz2 = dxy2 + dif[2] * dif[2]; + if (dxyz2 > 0.f) { + // Movement in xyz, calculate time from the xyz Euclidian distance. + line.length = sqrt(dxyz2); + } else if (std::abs(dif[3]) > 0.f) { + // Movement in the extruder axis. + line.length = std::abs(dif[3]); + } + line.feedrate = new_pos[4]; + line.origin_feedrate = new_pos[4]; + + assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f); + if (line.length > 0) + line.time = line.length / line.feedrate; + + if (line.feedrate == 0) + line.time = 0; + + line.time_max = line.time; + if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1)) + line.time_max = (adjustment->slow_down_min_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->slow_down_min_speed); + line.origin_time_max = line.time_max; + // QDS: add G2 and G3 support + if (active_speed_modifier < adjustment->lines.size() && ((line.type & CoolingLine::TYPE_G1) || + (line.type & CoolingLine::TYPE_G2) || + (line.type & CoolingLine::TYPE_G3))) { + // Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry. + assert((line.type & CoolingLine::TYPE_HAS_F) == 0); + CoolingLine &sm = adjustment->lines[active_speed_modifier]; + assert(sm.feedrate > 0.f); + sm.length += line.length; + sm.time += line.time; + if (sm.time_max != FLT_MAX) { + if (line.time_max == FLT_MAX) + sm.time_max = FLT_MAX; + else + sm.time_max += line.time_max; + + sm.origin_time_max = sm.time_max; + } + // Don't store this line. + line.type = 0; + } + } + current_pos = std::move(new_pos); + } else if (boost::starts_with(sline, "; Slow Down Start")) { + not_join_cooling = true; + } else if (boost::starts_with(sline, "; Slow Down End")) { + not_join_cooling = false; + } else if (boost::starts_with(sline, ";_EXTRUDE_END")) { + line.type = CoolingLine::TYPE_EXTRUDE_END; + active_speed_modifier = size_t(-1); + } else if (boost::starts_with(sline, m_toolchange_prefix)) { + unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + m_toolchange_prefix.size()); + // Only change extruder in case the number is meaningful. User could provide an out-of-range index through custom gcodes - those shall be ignored. + if (new_extruder < map_extruder_to_per_extruder_adjustment.size()) { + if (new_extruder != current_extruder) { + // Switch the tool. + line.type = CoolingLine::TYPE_SET_TOOL; + current_extruder = new_extruder; + adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]]; + } + } + else { + // Only log the error in case of MM printer. Single extruder printers likely ignore any T anyway. + if (map_extruder_to_per_extruder_adjustment.size() > 1) + BOOST_LOG_TRIVIAL(error) << "CoolingBuffer encountered an invalid toolchange, maybe from a custom gcode: " << sline; + } + + } else if (boost::starts_with(sline, ";_OVERHANG_FAN_START")) { + line.type = CoolingLine::TYPE_OVERHANG_FAN_START; + } else if (boost::starts_with(sline, ";_OVERHANG_FAN_END")) { + line.type = CoolingLine::TYPE_OVERHANG_FAN_END; + } else if (boost::starts_with(sline, "G4 ")) { + // Parse the wait time. + line.type = CoolingLine::TYPE_G4; + size_t pos_S = sline.find('S', 3); + size_t pos_P = sline.find('P', 3); + assert(is_decimal_separator_point()); // for atof + line.time = line.time_max = float( + (pos_S > 0) ? atof(sline.c_str() + pos_S + 1) : + (pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.); + line.origin_time_max = line.time_max; + } else if (boost::starts_with(sline, ";_FORCE_RESUME_FAN_SPEED")) { + line.type = CoolingLine::TYPE_FORCE_RESUME_FAN; + } else if (boost::starts_with(sline, ";_SET_FAN_SPEED_CHANGING_LAYER")) { + line.type = CoolingLine::TYPE_SET_FAN_CHANGING_LAYER; + } else if (boost::starts_with(sline, "M624")) { + line.type = CoolingLine::TYPE_OBJECT_START; + } else if (boost::starts_with(sline, "M625")) { + line.type = CoolingLine::TYPE_OBJECT_END; + } + if (line.type != 0) + adjustment->lines.emplace_back(std::move(line)); + } + m_parse_gcode_extruder = current_extruder; + return per_extruder_adjustments; +} + +// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed. +// Returns the adjusted G-code. +std::string GCodeEditor::write_layer_gcode( + // Source G-code for the current layer. + const std::string &gcode, + // ID of the current layer, used to disable fan for the first n layers. + size_t layer_id, + // Total time of this layer after slow down, used to control the fan. + float layer_time, + // Per extruder list of G-code lines and their cool down attributes. + std::vector &per_extruder_adjustments) +{ + if (gcode.empty()) + return gcode; + + // First sort the adjustment lines by of multiple extruders by their position in the source G-code. + std::vector lines; + { + size_t n_lines = 0; + for (const PerExtruderAdjustments &adj : per_extruder_adjustments) + n_lines += adj.lines.size(); + lines.reserve(n_lines); + for (const PerExtruderAdjustments &adj : per_extruder_adjustments) + for (const CoolingLine &line : adj.lines) + lines.emplace_back(&line); + std::sort(lines.begin(), lines.end(), [](const CoolingLine *ln1, const CoolingLine *ln2) { return ln1->line_start < ln2->line_start; } ); + } + // Second generate the adjusted G-code. + std::string new_gcode; + new_gcode.reserve(gcode.size() * 2); + bool overhang_fan_control= false; + int overhang_fan_speed = 0; + float pre_start_overhang_fan_time = 0.f; + + enum class SetFanType { + sfChangingLayer = 0, + sfChangingFilament, + sfImmediatelyApply + }; + + auto change_extruder_set_fan = [this, layer_id, layer_time, &new_gcode, &overhang_fan_control, &overhang_fan_speed, &pre_start_overhang_fan_time](SetFanType type) { +#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder) + int fan_min_speed = EXTRUDER_CONFIG(fan_min_speed); + int fan_speed_new = EXTRUDER_CONFIG(reduce_fan_stop_start_freq) ? fan_min_speed : 0; + + //QDS + //y58 + //int additional_fan_speed_new = EXTRUDER_CONFIG(additional_cooling_fan_speed); + int additional_fan_speed_new; + if(m_config.seal) + additional_fan_speed_new = EXTRUDER_CONFIG(additional_cooling_fan_speed); + else + additional_fan_speed_new = EXTRUDER_CONFIG(additional_cooling_fan_speed_unseal); + + int close_fan_the_first_x_layers = EXTRUDER_CONFIG(close_fan_the_first_x_layers); + // Is the fan speed ramp enabled? + int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer); + if (close_fan_the_first_x_layers <= 0 && full_fan_speed_layer > 0) { + // When ramping up fan speed from close_fan_the_first_x_layers to full_fan_speed_layer, force close_fan_the_first_x_layers above zero, + // so there will be a zero fan speed at least at the 1st layer. + close_fan_the_first_x_layers = 1; + } + if (int(layer_id) >= close_fan_the_first_x_layers) { + int fan_max_speed = EXTRUDER_CONFIG(fan_max_speed); + float slow_down_layer_time = float(EXTRUDER_CONFIG(slow_down_layer_time)); + float fan_cooling_layer_time = float(EXTRUDER_CONFIG(fan_cooling_layer_time)); + //QDS: always enable the fan speed interpolation according to layer time + //if (EXTRUDER_CONFIG(cooling)) { + if (layer_time < slow_down_layer_time) { + // Layer time very short. Enable the fan to a full throttle. + fan_speed_new = fan_max_speed; + } else if (layer_time < fan_cooling_layer_time) { + // Layer time quite short. Enable the fan proportionally according to the current layer time. + assert(layer_time >= slow_down_layer_time); + double t = (layer_time - slow_down_layer_time) / (fan_cooling_layer_time - slow_down_layer_time); + fan_speed_new = int(floor(t * fan_min_speed + (1. - t) * fan_max_speed) + 0.5); + } + //} + overhang_fan_speed = EXTRUDER_CONFIG(overhang_fan_speed); + if (int(layer_id) >= close_fan_the_first_x_layers && int(layer_id) + 1 < full_fan_speed_layer) { + // Ramp up the fan speed from close_fan_the_first_x_layers to full_fan_speed_layer. + float factor = float(int(layer_id + 1) - close_fan_the_first_x_layers) / float(full_fan_speed_layer - close_fan_the_first_x_layers); + fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255); + overhang_fan_speed = std::clamp(int(float(overhang_fan_speed) * factor + 0.5f), 0, 255); + } +#undef EXTRUDER_CONFIG + overhang_fan_control= overhang_fan_speed > fan_speed_new; + } else { + overhang_fan_control= false; + overhang_fan_speed = 0; + fan_speed_new = 0; + additional_fan_speed_new = 0; + } + if (fan_speed_new != m_fan_speed) { + m_fan_speed = fan_speed_new; + //QDS + m_current_fan_speed = fan_speed_new; + if (type == SetFanType::sfImmediatelyApply) + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed); + else if (type == SetFanType::sfChangingLayer) + this->m_set_fan_changing_layer = true; + //QDS: don't need to handle change filament, because we are always force to resume fan speed when filament change is finished + } + //QDS + if (additional_fan_speed_new != m_additional_fan_speed) { + m_additional_fan_speed = additional_fan_speed_new; + if (type == SetFanType::sfImmediatelyApply) + new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed); + else if (type == SetFanType::sfChangingLayer) + this->m_set_addition_fan_changing_layer = true; + //QDS: don't need to handle change filament, because we are always force to resume fan speed when filament change is finished + } + //QDS: set fan pre start time value + pre_start_overhang_fan_time = overhang_fan_control ? m_config.pre_start_fan_time.get_at(m_current_extruder) : 0.f; + }; + + const char *pos = gcode.c_str(); + int current_feedrate = 0; + //QDS + m_set_fan_changing_layer = false; + m_set_addition_fan_changing_layer = false; + change_extruder_set_fan(SetFanType::sfChangingLayer); + + //QDS: start the fan earlier for overhangs + float cumulative_time = 0.f; + float search_time = 0.f; + + for (int i = 0,j = 0; i < lines.size(); i++) { + const CoolingLine *line = lines[i]; + if (pre_start_overhang_fan_time > 0.f && overhang_fan_speed > m_fan_speed) { + cumulative_time += line->time; + j = jtype & CoolingLine::TYPE_FORCE_RESUME_FAN) { + //stop search when find a force resume fan command + break; + } + search_time += line_iter->time; + if (line_iter->type & CoolingLine::TYPE_OVERHANG_FAN_START) { + m_current_fan_speed = overhang_fan_speed; + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, overhang_fan_speed); + break; + } + } + } + const char *line_start = gcode.c_str() + line->line_start; + const char *line_end = gcode.c_str() + line->line_end; + if (line_start > pos) + new_gcode.append(pos, line_start - pos); + if (line->type & CoolingLine::TYPE_SET_TOOL) { + unsigned int new_extruder = (unsigned int)atoi(line_start + m_toolchange_prefix.size()); + if (new_extruder != m_current_extruder) { + m_current_extruder = new_extruder; + change_extruder_set_fan(SetFanType::sfChangingFilament); //QDS: will force to resume fan speed when filament change is finished + cumulative_time = 0.f; + search_time = 0.f; + } + new_gcode.append(line_start, line_end - line_start); + } else if (line->type & CoolingLine::TYPE_OVERHANG_FAN_START) { + if (overhang_fan_control && m_current_fan_speed < overhang_fan_speed) { + //QDS + m_current_fan_speed = overhang_fan_speed; + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, overhang_fan_speed); + } + } else if (line->type & CoolingLine::TYPE_OVERHANG_FAN_END) { + if (overhang_fan_control) { + //QDS + m_current_fan_speed = m_fan_speed; + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed); + } + } else if (line->type & CoolingLine::TYPE_FORCE_RESUME_FAN) { + //QDS: force to write a fan speed command again + if (m_current_fan_speed != -1) + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed); + if (m_additional_fan_speed != -1) + new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed); + } else if (line->type & CoolingLine::TYPE_SET_FAN_CHANGING_LAYER) { + //QDS: check whether fan speed need to changed when change layer + if (m_current_fan_speed != -1 && m_set_fan_changing_layer) { + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed); + m_set_fan_changing_layer = false; + } + if (m_additional_fan_speed != -1 && m_set_addition_fan_changing_layer) { + new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed); + m_set_addition_fan_changing_layer = false; + } + } + else if (line->type & CoolingLine::TYPE_EXTRUDE_END) { + // Just remove this comment. + } else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) { + // Find the start of a comment, or roll to the end of line. + const char *end = line_start; + for (; end < line_end && *end != ';'; ++ end); + // Find the 'F' word. + const char *fpos = strstr(line_start + 2, " F") + 2; + int new_feedrate = current_feedrate; + // Modify the F word of the current G-code line. + bool modify = false; + // Remove the F word from the current G-code line. + bool remove = false; + assert(fpos != nullptr); + new_feedrate = line->slowdown ? int(floor(60. * line->feedrate + 0.5)) : atoi(fpos); + if (new_feedrate == current_feedrate) { + // No need to change the F value. + if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.) + // Feedrate does not change and this line does not move the print head. Skip the complete G-code line including the G-code comment. + end = line_end; + else + // Remove the feedrate from the G0/G1 line. The G-code line may become empty! + remove = true; + } else if (line->slowdown) { + // The F value will be overwritten. + modify = true; + } else { + // The F value is different from current_feedrate, but not slowed down, thus the G-code line will not be modified. + // Emit the line without the comment. + new_gcode.append(line_start, end - line_start); + current_feedrate = new_feedrate; + } + if (modify || remove) { + if (modify) { + // Replace the feedrate. + new_gcode.append(line_start, fpos - line_start); + current_feedrate = new_feedrate; + char buf[64]; + sprintf(buf, "%d", int(current_feedrate)); + new_gcode += buf; + } else { + // Remove the feedrate word. + const char *f = fpos; + // Roll the pointer before the 'F' word. + for (f -= 2; f > line_start && (*f == ' ' || *f == '\t'); -- f); + + if ((f - line_start == 1) && *line_start == 'G' && (*f == '1' || *f == '0')) { + // QDS: only remain "G1" or "G0" of this line after remove 'F' part, don't save + } else { + // Append up to the F word, without the trailing whitespace. + new_gcode.append(line_start, f - line_start + 1); + } + } + // Skip the non-whitespaces of the F parameter up the comment or end of line. + for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++ fpos); + // Append the rest of the line without the comment. + if (fpos < end) + // The G-code line is not empty yet. Emit the rest of it. + new_gcode.append(fpos, end - fpos); + else if (remove && new_gcode == "G1") { + // The G-code line only contained the F word, now it is empty. Remove it completely including the comments. + new_gcode.resize(new_gcode.size() - 2); + end = line_end; + } + } + // Process the rest of the line. + if (end < line_end) { + if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) { + // Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE" + std::string comment(end, line_end); + boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", ""); + if (line->type & CoolingLine::TYPE_EXTERNAL_PERIMETER) + boost::replace_all(comment, ";_EXTERNAL_PERIMETER", ""); + if (line->type & CoolingLine::TYPE_WIPE) + boost::replace_all(comment, ";_WIPE", ""); + new_gcode += comment; + } else { + // Just attach the rest of the source line. + new_gcode.append(end, line_end - end); + } + } + } else if (line->type & CoolingLine::TYPE_OBJECT_START) { + new_gcode.append(line_start, line_end - line_start); + if (pre_start_overhang_fan_time > 0.f && m_current_fan_speed > m_fan_speed) + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed); + } else if (line->type & CoolingLine::TYPE_OBJECT_END) { + if (pre_start_overhang_fan_time > 0.f && m_current_fan_speed > m_fan_speed) + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed); + new_gcode.append(line_start, line_end - line_start); + }else { + new_gcode.append(line_start, line_end - line_start); + } + pos = line_end; + } + const char *gcode_end = gcode.c_str() + gcode.size(); + if (pos < gcode_end) + new_gcode.append(pos, gcode_end - pos); + + return new_gcode; +} + +} // namespace Slic3r diff --git a/src/libslic3r/GCode/GCodeEditor.hpp b/src/libslic3r/GCode/GCodeEditor.hpp new file mode 100644 index 0000000..ff0240c --- /dev/null +++ b/src/libslic3r/GCode/GCodeEditor.hpp @@ -0,0 +1,299 @@ +#ifndef slic3r_GCodeEditer_hpp_ +#define slic3r_GCodeEditer_hpp_ + +#include "../libslic3r.h" +#include +#include +#include +#include +#include + +namespace Slic3r { + +class GCode; +class Layer; + +struct CoolingLine +{ + enum Type { + TYPE_SET_TOOL = 1 << 0, + TYPE_EXTRUDE_END = 1 << 1, + TYPE_OVERHANG_FAN_START = 1 << 2, + TYPE_OVERHANG_FAN_END = 1 << 3, + TYPE_G0 = 1 << 4, + TYPE_G1 = 1 << 5, + TYPE_ADJUSTABLE = 1 << 6, + TYPE_EXTERNAL_PERIMETER = 1 << 7, + // The line sets a feedrate. + TYPE_HAS_F = 1 << 8, + TYPE_WIPE = 1 << 9, + TYPE_G4 = 1 << 10, + TYPE_G92 = 1 << 11, + // QDS: add G2 G3 type + TYPE_G2 = 1 << 12, + TYPE_G3 = 1 << 13, + TYPE_FORCE_RESUME_FAN = 1 << 14, + TYPE_SET_FAN_CHANGING_LAYER = 1 << 15, + TYPE_OBJECT_START = 1 << 16, + TYPE_OBJECT_END = 1 << 17, + }; + + CoolingLine(unsigned int type, size_t line_start, size_t line_end) + : type(type), line_start(line_start), line_end(line_end), length(0.f), feedrate(0.f), origin_feedrate(0.f), time(0.f), time_max(0.f), slowdown(false) + {} + + bool adjustable(bool slowdown_external_perimeters) const + { + return (this->type & TYPE_ADJUSTABLE) && (!(this->type & TYPE_EXTERNAL_PERIMETER) || slowdown_external_perimeters) && this->time < this->time_max; + } + + bool adjustable() const { return (this->type & TYPE_ADJUSTABLE) && this->time < this->time_max; } + + size_t type; + // Start of this line at the G-code snippet. + size_t line_start; + // End of this line at the G-code snippet. + size_t line_end; + // XY Euclidian length of this segment. + float length; + // Current feedrate, possibly adjusted. + float feedrate; + // Current duration of this segment. + float time; + // Maximum duration of this segment. + float time_max; + // If marked with the "slowdown" flag, the line has been slowed down. + bool slowdown; + // Current feedrate, possibly adjusted. + float origin_feedrate = 0; + float origin_time_max = 0; + // Current duration of this segment. + //float origin_time; + bool outwall_smooth_mark = false; + int object_id = -1; + int cooling_node_id = -1; +}; + +struct PerExtruderAdjustments +{ + // Calculate the total elapsed time per this extruder, adjusted for the slowdown. + float elapsed_time_total() const + { + float time_total = 0.f; + for (const CoolingLine &line : lines) time_total += line.time; + return time_total; + } + // Calculate the total elapsed time when slowing down + // to the minimum extrusion feed rate defined for the current material. + float maximum_time_after_slowdown(bool slowdown_external_perimeters) const + { + float time_total = 0.f; + for (const CoolingLine &line : lines) + if (line.adjustable(slowdown_external_perimeters)) { + if (line.time_max == FLT_MAX) + return FLT_MAX; + else + time_total += line.time_max; + } else + time_total += line.time; + return time_total; + } + // Calculate the adjustable part of the total time. + float adjustable_time(bool slowdown_external_perimeters) const + { + float time_total = 0.f; + for (const CoolingLine &line : lines) + if (line.adjustable(slowdown_external_perimeters)) time_total += line.time; + return time_total; + } + // Calculate the non-adjustable part of the total time. + float non_adjustable_time(bool slowdown_external_perimeters) const + { + float time_total = 0.f; + for (const CoolingLine &line : lines) + if (!line.adjustable(slowdown_external_perimeters)) time_total += line.time; + return time_total; + } + // Slow down the adjustable extrusions to the minimum feedrate allowed for the current extruder material. + // Used by both proportional and non-proportional slow down. + float slowdown_to_minimum_feedrate(bool slowdown_external_perimeters) + { + float time_total = 0.f; + for (CoolingLine &line : lines) { + if (line.adjustable(slowdown_external_perimeters)) { + assert(line.time_max >= 0.f && line.time_max < FLT_MAX); + line.slowdown = true; + line.time = line.time_max; + line.feedrate = line.length / line.time; + } + time_total += line.time; + } + return time_total; + } + // Slow down each adjustable G-code line proportionally by a factor. + // Used by the proportional slow down. + float slow_down_proportional(float factor, bool slowdown_external_perimeters) + { + assert(factor >= 1.f); + float time_total = 0.f; + for (CoolingLine &line : lines) { + if (line.adjustable(slowdown_external_perimeters)) { + line.slowdown = true; + line.time = std::min(line.time_max, line.time * factor); + line.feedrate = line.length / line.time; + } + time_total += line.time; + } + return time_total; + } + + // Sort the lines, adjustable first, higher feedrate first. + // Used by non-proportional slow down. + void sort_lines_by_decreasing_feedrate() + { + std::sort(lines.begin(), lines.end(), [](const CoolingLine &l1, const CoolingLine &l2) { + bool adj1 = l1.adjustable(); + bool adj2 = l2.adjustable(); + return (adj1 == adj2) ? l1.feedrate > l2.feedrate : adj1; + }); + for (n_lines_adjustable = 0; n_lines_adjustable < lines.size() && this->lines[n_lines_adjustable].adjustable(); ++n_lines_adjustable) + ; + time_non_adjustable = 0.f; + for (size_t i = n_lines_adjustable; i < lines.size(); ++i) time_non_adjustable += lines[i].time; + } + + // Calculate the maximum time stretch when slowing down to min_feedrate. + // Slowdown to min_feedrate shall be allowed for this extruder's material. + // Used by non-proportional slow down. + float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) const + { + float time_stretch = 0.f; + assert(this->slow_down_min_speed < min_feedrate + EPSILON); + for (size_t i = 0; i < n_lines_adjustable; ++i) { + const CoolingLine &line = lines[i]; + if (line.feedrate > min_feedrate) time_stretch += line.time * (line.feedrate / min_feedrate - 1.f); + } + return time_stretch; + } + + // Slow down all adjustable lines down to min_feedrate. + // Slowdown to min_feedrate shall be allowed for this extruder's material. + // Used by non-proportional slow down. + void slow_down_to_feedrate(float min_feedrate) + { + assert(this->slow_down_min_speed < min_feedrate + EPSILON); + for (size_t i = 0; i < n_lines_adjustable; ++i) { + CoolingLine &line = lines[i]; + if (line.feedrate > min_feedrate) { + line.time *= std::max(1.f, line.feedrate / min_feedrate); + line.feedrate = min_feedrate; + line.slowdown = true; + } + } + } + + //collect lines time + float collection_line_times_of_extruder() { + float times = 0; + for (const CoolingLine &line: lines) { + times += line.time; + } + return times; + } + + // Extruder, for which the G-code will be adjusted. + unsigned int extruder_id = 0; + // Is the cooling slow down logic enabled for this extruder's material? + bool cooling_slow_down_enabled = false; + // Slow down the print down to slow_down_min_speed if the total layer time is below slow_down_layer_time. + float slow_down_layer_time = 0.f; + // Minimum print speed allowed for this extruder. + float slow_down_min_speed = 0.f; + + //w14 + bool dont_slow_down_outer_wall = false; + + // Parsed lines. + std::vector lines; + // The following two values are set by sort_lines_by_decreasing_feedrate(): + // Number of adjustable lines, at the start of lines. + size_t n_lines_adjustable = 0; + // Non-adjustable time of lines starting with n_lines_adjustable. + float time_non_adjustable = 0; + // Current total time for this extruder. + float time_total = 0; + // Maximum time for this extruder, when the maximum slow down is applied. + float time_maximum = 0; + + // Temporaries for processing the slow down. Both thresholds go from 0 to n_lines_adjustable. + size_t idx_line_begin = 0; + size_t idx_line_end = 0; +}; +// A standalone G-code filter, to control cooling of the print. +// The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited +// and the print is modified to stretch over a minimum layer time. +// +// The simple it sounds, the actual implementation is significantly more complex. +// Namely, for a multi-extruder print, each material may require a different cooling logic. +// For example, some materials may not like to print too slowly, while with some materials +// we may slow down significantly. +// +class GCodeEditor { +public: + GCodeEditor(GCode &gcodegen); + void reset(const Vec3d &position); + void set_current_extruder(unsigned int extruder_id) + { + m_current_extruder = extruder_id; + m_parse_gcode_extruder = extruder_id; + } + std::string process_layer(std::string && gcode, + const size_t layer_id, + std::vector &per_extruder_adjustments, + const std::vector & object_label, + const bool flush, + const bool spiral_vase); + + // float calculate_layer_slowdown(std::vector &per_extruder_adjustments); + // Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed. + // Returns the adjusted G-code. + std::string write_layer_gcode(const std::string &gcode, size_t layer_id, float layer_time, std::vector &per_extruder_adjustments); + +private : + GCodeEditor& operator=(const GCodeEditor&) = delete; + std::vector parse_layer_gcode(const std::string & gcode, + std::vector & current_pos, + const std::vector & object_label, + bool spiral_vase, + bool join_z_smooth); + + // G-code snippet cached for the support layers preceding an object layer. + std::string m_gcode; + // Internal data. + // QDS: X,Y,Z,E,F,I,J + std::vector m_axis; + std::vector m_current_pos; + // Current known fan speed or -1 if not known yet. + int m_fan_speed; + int m_additional_fan_speed; + // Cached from GCodeWriter. + // Printing extruder IDs, zero based. + std::vector m_extruder_ids; + // Highest of m_extruder_ids plus 1. + unsigned int m_num_extruders { 0 }; + const std::string m_toolchange_prefix; + // Referencs GCode::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified, + // the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required. + const PrintConfig &m_config; + unsigned int m_current_extruder; + unsigned int m_parse_gcode_extruder; + //QDS: current fan speed + int m_current_fan_speed; + //QDS: + bool m_set_fan_changing_layer = false; + bool m_set_addition_fan_changing_layer = false; +}; + +} + +#endif diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 156a4a2..cf528e4 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -46,7 +46,7 @@ static const Slic3r::Vec3f DEFAULT_EXTRUDER_OFFSET = Slic3r::Vec3f::Zero(); namespace Slic3r { -const std::vector GCodeProcessor::Reserved_Tags = { +const std::vector GCodeProcessor::ReservedTags = { " FEATURE: ", " WIPE_START", " WIPE_END", @@ -61,15 +61,25 @@ const std::vector GCodeProcessor::Reserved_Tags = { "_GP_ESTIMATED_PRINTING_TIME_PLACEHOLDER", "_GP_TOTAL_LAYER_NUMBER_PLACEHOLDER", " WIPE_TOWER_START", - //1.9.7.52 " WIPE_TOWER_END", "_GP_FILAMENT_USED_WEIGHT_PLACEHOLDER", "_GP_FILAMENT_USED_VOLUME_PLACEHOLDER", - "_GP_FILAMENT_USED_LENGTH_PLACEHOLDER" + "_GP_FILAMENT_USED_LENGTH_PLACEHOLDER", + " MACHINE_START_GCODE_END", + " MACHINE_END_GCODE_START", + " NOZZLE_CHANGE_START", + " NOZZLE_CHANGE_END" }; -const std::string GCodeProcessor::Flush_Start_Tag = " FLUSH_START"; -const std::string GCodeProcessor::Flush_End_Tag = " FLUSH_END"; +const std::vector GCodeProcessor::CustomTags = { + " FLUSH_START", + " FLUSH_END", + " VFLUSH_START", + " VFLUSH_END", + " SKIPPABLE_START", + " SKIPPABLE_END", + " SKIPTYPE: " +}; const float GCodeProcessor::Wipe_Width = 0.05f; @@ -87,12 +97,24 @@ static void set_option_value(ConfigOptionFloats& option, size_t id, float value) option.values[id] = static_cast(value); }; +static void set_option_value(ConfigOptionFloatsNullable& option, size_t id, float value) +{ + if (id < option.values.size()) + option.values[id] = static_cast(value); +}; + static float get_option_value(const ConfigOptionFloats& option, size_t id) { return option.values.empty() ? 0.0f : ((id < option.values.size()) ? static_cast(option.values[id]) : static_cast(option.values.back())); } +static float get_option_value(const ConfigOptionFloatsNullable& option, size_t id) +{ + return option.values.empty() ? 0.0f : + ((id < option.values.size()) ? static_cast(option.values[id]) : static_cast(option.values.back())); +} + static float estimated_acceleration_distance(float initial_rate, float target_rate, float acceleration) { return (acceleration == 0.0f) ? 0.0f : (sqr(target_rate) - sqr(initial_rate)) / (2.0f * acceleration); @@ -124,6 +146,78 @@ static float acceleration_time_from_distance(float initial_feedrate, float dista return (acceleration != 0.0f) ? (speed_from_distance(initial_feedrate, distance, acceleration) - initial_feedrate) / acceleration : 0.0f; } +static int get_object_label_id(const std::string_view comment_1) +{ + std::string comment(comment_1); + auto pos = comment.find(":"); + std::string num_str = comment.substr(pos + 1); + int id = -1; + try { + id = stoi(num_str); + } + catch (const std::exception &) {} + return id; +} + +static float get_z_height(const std::string_view comment_1) +{ + std::string comment(comment_1); + auto pos = comment.find(":"); + std::string num_str = comment.substr(pos + 1); + float print_z = 0.0f; + try { + print_z = stof(num_str); + } catch (const std::exception &) {} + return print_z; +} + +CommandProcessor::CommandProcessor() +{ + root = std::make_unique(); +} + +void CommandProcessor::register_command(const std::string& str, command_handler_t handler, bool early_quit) +{ + TrieNode* node = root.get(); + for (char ch : str) { + auto iter = node->children.find(ch); + if (iter == node->children.end()) { + std::unique_ptr new_node = std::make_unique(); + auto raw_ptr = new_node.get(); + node->children[ch] = std::move(new_node); + node = raw_ptr; + } + else { + node = iter->second.get(); + } + } + if (node->handler != nullptr) { + assert(false);// duplicated command + } + node->handler = handler; + node->early_quit = early_quit; +} + +bool CommandProcessor::process_comand(std::string_view cmd, const GCodeReader::GCodeLine& line) +{ + TrieNode* node = root.get(); + for (char ch : cmd) { + if (node->early_quit && node->handler) { + node->handler(line); + return true; + } + auto iter = node->children.find(ch); + if (iter == node->children.end()) { + return false; + } + node = iter->second.get(); + } + if (!node || !node->handler) + return false; + node->handler(line); + return true; +} + void GCodeProcessor::CachedPosition::reset() { std::fill(position.begin(), position.end(), FLT_MAX); @@ -223,14 +317,15 @@ void GCodeProcessor::TimeMachine::reset() std::fill(roles_time.begin(), roles_time.end(), 0.0f); layers_time = std::vector(); prepare_time = 0.0f; + m_additional_time_buffer.clear(); } -void GCodeProcessor::TimeMachine::simulate_st_synchronize(float additional_time, ExtrusionRole target_role) +void GCodeProcessor::TimeMachine::simulate_st_synchronize(float additional_time, ExtrusionRole target_role, block_handler_t block_handler) { if (!enabled) return; - calculate_time(0, additional_time,target_role); + calculate_time(0, additional_time,target_role,block_handler); } static void planner_forward_pass_kernel(GCodeProcessor::TimeBlock& prev, GCodeProcessor::TimeBlock& curr) @@ -303,13 +398,50 @@ static void recalculate_trapezoids(std::vector& block } } -void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks, float additional_time, ExtrusionRole target_role) +void GCodeProcessor::TimeMachine::handle_time_block(const TimeBlock& block, float time, int activate_machine_idx, GCodeProcessorResult& result) { - if (!enabled || blocks.size() < 2) + if (block.skippable_type != SkipType::stNone) + result.skippable_part_time[block.skippable_type] += block.time(); + result.moves[block.move_id].time[activate_machine_idx] = time; +} + +GCodeProcessor::TimeMachine::AdditionalBuffer GCodeProcessor::TimeMachine::merge_adjacent_addtional_time_blocks(const AdditionalBuffer& buffer) +{ + AdditionalBuffer merged; + if(buffer.empty()) + return merged; + + auto current_block = buffer.front(); + for(size_t idx = 1; idx < buffer.size(); ++idx){ + auto next_block = buffer[idx]; + if(current_block.first == next_block.first){ + current_block.second += next_block.second; + }else{ + merged.push_back(current_block); + current_block = next_block; + } + } + merged.push_back(current_block); + return merged; +} + + +void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks, float additional_time, ExtrusionRole target_role, block_handler_t block_handler) +{ + if(!enabled) return; + if(blocks.size() < 2){ + if (additional_time > 0) + m_additional_time_buffer.emplace_back(target_role, additional_time); + return ; + } assert(keep_last_n_blocks <= blocks.size()); + AdditionalBuffer additional_buffer = m_additional_time_buffer; + if(additional_time > 0) + additional_buffer.emplace_back(target_role, additional_time); + additional_buffer = merge_adjacent_addtional_time_blocks(additional_buffer); // forward_pass for (size_t i = 0; i + 1 < blocks.size(); ++i) { planner_forward_pass_kernel(blocks[i], blocks[i + 1]); @@ -322,18 +454,25 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks, floa recalculate_trapezoids(blocks); size_t n_blocks_process = blocks.size() - keep_last_n_blocks; - bool found_target_block = false; + size_t additional_buffer_idx = 0; + for (size_t i = 0; i < n_blocks_process; ++i) { const TimeBlock& block = blocks[i]; float block_time = block.time(); - bool is_valid_block = target_role == ExtrusionRole::erNone || target_role == block.role || i == n_blocks_process - 1; - if (!found_target_block && is_valid_block) { - block_time += additional_time; - found_target_block = true; + if(additional_buffer_idx < additional_buffer.size()){ + ExtrusionRole buf_role = additional_buffer[additional_buffer_idx].first; + float buf_time = additional_buffer[additional_buffer_idx].second; + bool is_valid_block = (buf_role == ExtrusionRole::erNone) || + (buf_role == block.role); + if (is_valid_block){ + block_time += buf_time; + additional_buffer_idx += 1; + } } time += block_time; + block_handler(block, time); gcode_time.cache += block_time; //QDS: don't calculate travel of start gcode into travel time if (!block.flags.prepare_stage || block.move_type != EMoveType::Travel) @@ -362,6 +501,10 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks, floa it_stop_time->elapsed_time = time; } + m_additional_time_buffer.clear(); + if(additional_buffer_idx(PrintEstimatedStatistics::ETimeMode::Count); ++i) { @@ -383,17 +527,19 @@ void GCodeProcessor::TimeProcessor::reset() machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].enabled = true; } -//1.9.7.52 void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector& moves, std::vector& lines_ends, const TimeProcessContext& context) { + using namespace ExtruderPreHeating; FilePtr in{ boost::nowide::fopen(filename.c_str(), "rb") }; if (in.f == nullptr) throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for reading.\n")); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": before process %1%")%filename.c_str(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": before process %1%") % filename.c_str(); // temporary file to contain modified gcode - std::string out_path = filename + ".postprocess"; - FilePtr out{ boost::nowide::fopen(out_path.c_str(), "wb") }; + std::string filename_in = filename; + std::string filename_out = filename + ".postprocess"; + + FilePtr out{ boost::nowide::fopen(filename_out.c_str(), "wb+") }; if (out.f == nullptr) { throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); } @@ -422,11 +568,11 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st return std::string(line_M73); }; - auto format_line_exhaust_fan_control = [](const std::string& mask,int fan_index,int percent) { + auto format_line_exhaust_fan_control = [](const std::string& mask, int fan_index, int percent) { char line_fan[64] = { 0 }; - sprintf(line_fan,mask.c_str(), + sprintf(line_fan, mask.c_str(), std::to_string(fan_index).c_str(), - std::to_string(int((percent/100.0)*255)).c_str()); + std::to_string(int((percent / 100.0) * 255)).c_str()); return std::string(line_fan); }; @@ -440,8 +586,28 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st return std::string(line_M73); }; - std::string gcode_line; - size_t g1_lines_counter = 0; + auto format_line_M104 = [&context](int target_temp, int target_extruder = -1, const std::string& comment = std::string()) { + std::string buffer = "M104"; + if (target_extruder != -1) + buffer += (" T" + std::to_string(context.physical_extruder_map[target_extruder])); + buffer += " S" + std::to_string(target_temp) + " N0"; // N0 means the gcode is generated by slicer + if (!comment.empty()) + buffer += " ;" + comment; + buffer += '\n'; + return buffer; + }; + + auto format_M73_remain_filament_changes = [](int filament_change_num, int total_filament_change)->std::string{ + char buf[64]; + snprintf(buf, sizeof(buf), "M73 E%d\n", total_filament_change - filament_change_num); + return std::string(buf); + }; + + // do not insert gcode into machine start & end gcode + unsigned int machine_start_gcode_end_line_id = (unsigned int)(-1); // mark the end line of machine start gcode + unsigned int machine_end_gcode_start_line_id = (unsigned int)(-1); // mark the start line of machine end gcode + std::vector> skippable_blocks; + // keeps track of last exported pair std::array, static_cast(PrintEstimatedStatistics::ETimeMode::Count)> last_exported_main; for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { @@ -454,14 +620,11 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st last_exported_stop[i] = time_in_minutes(machines[i].time); } - // buffer line to export only when greater than 64K to reduce writing calls - std::string export_line; - // replace placeholder lines with the proper final value // gcode_line is in/out parameter, to reduce expensive memory allocation - auto process_placeholders = [&](std::string& gcode_line) { + auto process_placeholders = [&](std::string& gcode_line, int line_id) { unsigned int extra_lines_count = 0; - //1.9.7.52 + auto format_filament_used_info = [](const std::string& info, std::mapval_per_extruder) { auto double_to_fmt_string = [](double num) -> std::string { char buf[20]; @@ -514,7 +677,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st sprintf(buf, "; estimated printing time (normal mode) = %s\n", get_time_dhms(machine.time).c_str()); ret += buf; - } + } else { // QDS estimator sprintf(buf, "; model printing time: %s; total estimated time: %s\n", @@ -531,10 +694,9 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st sprintf(buf, "; total layer number: %zd\n", context.total_layer_num); ret += buf; } - //1.9.7.52 else if (line == reserved_tag(ETags::Used_Filament_Weight_Placeholder)) { std::maptotal_weight_per_extruder; - for (const auto& pair : context.used_filaments.total_volumes_per_extruder) { + for (const auto& pair : context.used_filaments.total_volumes_per_filament) { auto filament_id = pair.first; auto volume = pair.second; auto iter = std::find_if(context.filament_lists.begin(), context.filament_lists.end(), [filament_id](const Extruder& filament) { return filament.id() == filament_id; }); @@ -548,7 +710,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st } else if (line == reserved_tag(ETags::Used_Filament_Volume_Placeholder)) { std::maptotal_volume_per_extruder; - for (const auto& pair : context.used_filaments.total_volumes_per_extruder) { + for (const auto &pair : context.used_filaments.total_volumes_per_filament) { auto filament_id = pair.first; auto volume = pair.second; auto iter = std::find_if(context.filament_lists.begin(), context.filament_lists.end(), [filament_id](const Extruder& filament) { return filament.id() == filament_id; }); @@ -560,7 +722,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st } else if (line == reserved_tag(ETags::Used_Filament_Length_Placeholder)) { std::maptotal_length_per_extruder; - for (const auto& pair : context.used_filaments.total_volumes_per_extruder) { + for (const auto &pair : context.used_filaments.total_volumes_per_filament) { auto filament_id = pair.first; auto volume = pair.second; auto iter = std::find_if(context.filament_lists.begin(), context.filament_lists.end(), [filament_id](const Extruder& filament) { return filament.id() == filament_id; }); @@ -571,18 +733,32 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st } ret += format_filament_used_info("total filament length [mm]", total_length_per_extruder); } + else if (line == reserved_tag(ETags::MachineStartGCodeEnd)) { + machine_start_gcode_end_line_id = line_id; + } + else if (line == reserved_tag(ETags::MachineEndGCodeStart)) { + machine_end_gcode_start_line_id = line_id; + } + else if (line == custom_tags(CustomETags::SKIPPABLE_START)){ + skippable_blocks.emplace_back(0,0); + skippable_blocks.back().first = line_id; + } + else if (line == custom_tags(CustomETags::SKIPPABLE_END)){ + skippable_blocks.back().second = line_id; + } } - if (! ret.empty()) + if (!ret.empty()) // Not moving the move operator on purpose, so that the gcode_line allocation will grow and it will not be reallocated after handful of lines are processed. gcode_line = ret; return std::tuple(!ret.empty(), (extra_lines_count == 0) ? extra_lines_count : extra_lines_count - 1); }; + // check for temporary lines auto is_temporary_decoration = [](const std::string_view gcode_line) { // remove trailing '\n' - assert(! gcode_line.empty()); + assert(!gcode_line.empty()); assert(gcode_line.back() == '\n'); // return true for decorations which are used in processing the gcode but that should not be exported into the final gcode @@ -601,13 +777,12 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st // add lines M73 to exported gcode auto process_line_move = [ // Lambdas, mostly for string formatting, all with an empty capture block. - time_in_minutes, format_time_float, format_line_M73_main, format_line_M73_stop_int, format_line_M73_stop_float, time_in_last_minute,format_line_exhaust_fan_control, - &self = std::as_const(*this), - // Caches, to be modified - &g1_times_cache_it, &last_exported_main, &last_exported_stop, - // String output - &export_line] - (const size_t g1_lines_counter) { + time_in_minutes, format_time_float, format_line_M73_main, format_line_M73_stop_int, format_line_M73_stop_float, time_in_last_minute, format_line_exhaust_fan_control, + &self = std::as_const(*this), + // Caches, to be modified + &g1_times_cache_it, &last_exported_main, &last_exported_stop + ] + (std::string& gcode_buffer, const size_t g1_lines_counter) { unsigned int exported_lines_count = 0; for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { const TimeMachine& machine = self.machines[i]; @@ -622,7 +797,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st time_in_minutes(machine.time - it->elapsed_time) }; if (last_exported_main[i] != to_export_main) { - export_line += format_line_M73_main(machine.line_m73_main_mask.c_str(), + gcode_buffer += format_line_M73_main(machine.line_m73_main_mask.c_str(), to_export_main.first, to_export_main.second); last_exported_main[i] = to_export_main; ++exported_lines_count; @@ -635,7 +810,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st if (last_exported_stop[i] != to_export_stop) { if (to_export_stop > 0) { if (last_exported_stop[i] != to_export_stop) { - export_line += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop); + gcode_buffer += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop); last_exported_stop[i] = to_export_stop; ++exported_lines_count; } @@ -657,9 +832,9 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st if (is_last) { if (std::distance(machine.stop_times.begin(), it_stop) == static_cast(machine.stop_times.size() - 1)) - export_line += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop); + gcode_buffer += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop); else - export_line += format_line_M73_stop_float(machine.line_m73_stop_mask.c_str(), time_in_last_minute(it_stop->elapsed_time - it->elapsed_time)); + gcode_buffer += format_line_M73_stop_float(machine.line_m73_stop_mask.c_str(), time_in_last_minute(it_stop->elapsed_time - it->elapsed_time)); last_exported_stop[i] = to_export_stop; ++exported_lines_count; @@ -674,105 +849,405 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st }; // helper function to write to disk - size_t out_file_pos = 0; - lines_ends.clear(); - auto write_string = [&export_line, &out, &out_path, &out_file_pos, &lines_ends](const std::string& str) { - fwrite((const void*)export_line.c_str(), 1, export_line.length(), out.f); + auto write_string = [](const std::string& file_path, FilePtr& out, std::string& str, size_t& out_file_pos, std::vector* line_ends = nullptr) { + fwrite((const void*)str.c_str(), 1, str.length(), out.f); if (ferror(out.f)) { out.close(); - boost::nowide::remove(out_path.c_str()); + boost::nowide::remove(file_path.c_str()); throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nIs the disk full?\n")); } - for (size_t i = 0; i < export_line.size(); ++ i) - if (export_line[i] == '\n') - lines_ends.emplace_back(out_file_pos + i + 1); - out_file_pos += export_line.size(); - export_line.clear(); - }; + if (line_ends != nullptr) { + for (size_t idx = 0; idx < str.size(); ++idx) { + if (str[idx] == '\n') + line_ends->emplace_back(out_file_pos + idx + 1); + } + } + out_file_pos += str.size(); + str.clear(); + }; - unsigned int line_id = 0; - std::vector> offsets; - - { + /* + Read the file according to the buffer block size, read one line of gcode from the buffer each time and process it. + Write to a new file when the buffer is full. + The callback function accepts the gcode content, line number, and buffer content + */ + auto gcode_process = [&write_string](FilePtr& in, FilePtr& out, const std::string& filename_in, const std::string& filename_out, const std::function& gcode_line_handler, std::vector* line_ends = nullptr, int buffer_size_in_kB = 64) { // Read the input stream 64kB at a time, extract lines and process them. - std::vector buffer(65536 * 10, 0); + std::vector buffer(buffer_size_in_kB * 1024 * 1.5, 0); + std::string export_line; + std::string gcode_line; + int line_id = 0; + size_t out_file_pos = 0; // Line buffer. - assert(gcode_line.empty()); for (;;) { size_t cnt_read = ::fread(buffer.data(), 1, buffer.size(), in.f); if (::ferror(in.f)) throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n")); - bool eof = cnt_read == 0; - auto it = buffer.begin(); + bool eof = cnt_read == 0; + auto it = buffer.begin(); auto it_bufend = buffer.begin() + cnt_read; - while (it != it_bufend || (eof && ! gcode_line.empty())) { + while (it != it_bufend || (eof && !gcode_line.empty())) { // Find end of line. - bool eol = false; + bool eol = false; auto it_end = it; - for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end) ; + for (; it_end != it_bufend && !(eol = *it_end == '\r' || *it_end == '\n'); ++it_end); // End of line is indicated also if end of file was reached. eol |= eof && it_end == it_bufend; gcode_line.insert(gcode_line.end(), it, it_end); if (eol) { ++line_id; - //w12 gcode_line += *it_end; if (*it_end == '\r' && *(++it_end) == '\n') gcode_line += '\n'; - // replace placeholder lines - auto [processed, lines_added_count] = process_placeholders(gcode_line); - if (processed && lines_added_count > 0) - offsets.push_back({ line_id, lines_added_count }); - if (! processed && ! is_temporary_decoration(gcode_line) && - (GCodeReader::GCodeLine::cmd_is(gcode_line, "G1") || - GCodeReader::GCodeLine::cmd_is(gcode_line, "G2") || - GCodeReader::GCodeLine::cmd_is(gcode_line, "G3"))) { - // remove temporary lines, add lines M73 where needed - unsigned int extra_lines_count = process_line_move(g1_lines_counter ++); - if (extra_lines_count > 0) - offsets.push_back({ line_id, extra_lines_count }); - } - + gcode_line_handler(gcode_line, export_line, line_id); export_line += gcode_line; - if (export_line.length() > 65535) - write_string(export_line); + if (export_line.length() >= buffer_size_in_kB * 1024) + write_string(filename_out, out, export_line, out_file_pos, line_ends); gcode_line.clear(); } // Skip EOL. it = it_end; if (it != it_bufend && *it == '\r') - ++ it; + ++it; if (it != it_bufend && *it == '\n') - ++ it; + ++it; } if (eof) break; } - } + if (!export_line.empty()) + write_string(filename_out, out, export_line, out_file_pos, line_ends); + }; - if (!export_line.empty()) - write_string(export_line); + auto handle_nozzle_change_line = [&filament_maps=context.filament_maps](const std::string& line, int& old_filament, int& next_filament, int& extruder_id)->bool { + std::regex re(R"(OF(\d+)\s+NF(\d+))"); + std::smatch match; - out.close(); - in.close(); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": after process %1%")%filename.c_str(); + if (!std::regex_search(line, match, re)) + return false; + + old_filament = std::stoi(match[1]); + next_filament = std::stoi(match[2]); + extruder_id = filament_maps[next_filament]; + return true; + }; + + constexpr int buffer_size_in_KB = 64; + std::vector filament_blocks; + std::vector extruder_blocks = { ExtruderUsageBlcok() }; // the first use of extruder will not generate nozzle change tag, so manually add a dummy block + std::vector> offsets; + size_t g1_lines_counter = 0; + lines_ends.clear(); + ExtruderUsageBlcok temp_construct_block; // the temperarily constructed block, will be pushed into container after initializing + + auto handle_filament_change = [&filament_blocks,&machine_start_gcode_end_line_id,&machine_end_gcode_start_line_id](int filament_id,int line_id){ + // skip the filaments change in machine start/end gcode + if (machine_start_gcode_end_line_id == (unsigned int)(-1) && (unsigned int)(line_id)machine_end_gcode_start_line_id) + return; + + if (!filament_blocks.empty()) + filament_blocks.back().upper_gcode_id = line_id; + filament_blocks.emplace_back(filament_id, line_id, -1); + }; + + auto gcode_time_handler = [&temp_construct_block,&filament_blocks, &extruder_blocks, &offsets, &handle_nozzle_change_line , & process_placeholders, &is_temporary_decoration, & process_line_move, & g1_lines_counter, & machine_start_gcode_end_line_id, & machine_end_gcode_start_line_id,handle_filament_change](std::string& gcode_line, std::string& gcode_buffer, int line_id) { + auto [processed, lines_added_count] = process_placeholders(gcode_line,line_id); + if (processed && lines_added_count > 0) + offsets.push_back({ line_id, lines_added_count }); + if (!processed && !is_temporary_decoration(gcode_line)) { + if (GCodeReader::GCodeLine::cmd_is(gcode_line, "G1") || + GCodeReader::GCodeLine::cmd_is(gcode_line, "G2") || + GCodeReader::GCodeLine::cmd_is(gcode_line, "G3") || + GCodeReader::GCodeLine::cmd_start_with(gcode_line, ";VG1") + ) { + // remove temporary lines, add lines M73 where needed + unsigned int extra_lines_count = process_line_move(gcode_buffer, g1_lines_counter++); + if (extra_lines_count > 0) + offsets.push_back({ line_id, extra_lines_count }); + } + else if (GCodeReader::GCodeLine::cmd_start_with(gcode_line, "T")) { + int fid; + int skips = GCodeReader::skip_whitespaces(gcode_line.data()) - gcode_line.data(); + std::istringstream str(gcode_line.substr(skips + 1)); // skip white spaces and T + str >> fid; + if (!str.fail() && 0 <= fid && fid < 255) { + handle_filament_change(fid, line_id); + } + } + else if (GCodeReader::GCodeLine::cmd_start_with(gcode_line, ";VT")) { + int fid; + int skips = GCodeReader::skip_whitespaces(gcode_line.data()) - gcode_line.data(); + std::istringstream str(gcode_line.substr(skips + 3)); // skip white spaces and ;VT + str >> fid; + if (!str.fail() && 0 <= fid && fid < 255) { + handle_filament_change(fid, line_id); + } + } + else if (GCodeReader::GCodeLine::cmd_start_with(gcode_line, "M1020")) { + size_t s_pos = gcode_line.find('S'); + if (s_pos != std::string::npos) { + std::istringstream str(gcode_line.substr(s_pos + 1)); // skip white spaces and T + int fid; + str >> fid; + if (!str.fail() && 0 <= fid && fid < 255) { + handle_filament_change(fid, line_id); + } + } + } + else if (GCodeReader::GCodeLine::cmd_start_with(gcode_line, (std::string(";") + reserved_tag(ETags::NozzleChangeStart)).c_str())) { + int prev_filament{ -1 }, next_filament{ -1 }, extruder_id{ -1 }; + handle_nozzle_change_line(gcode_line, prev_filament, next_filament, extruder_id); + if (!extruder_blocks.empty()) { + extruder_blocks.back().initialize_step_2(line_id); + } + } + else if (GCodeReader::GCodeLine::cmd_start_with(gcode_line, (std::string(";") + reserved_tag(ETags::NozzleChangeEnd)).c_str())) { + int prev_filament{ -1 }, next_filament{ -1 }, extruder_id{ -1 }; + handle_nozzle_change_line(gcode_line, prev_filament, next_filament, extruder_id); + if (!extruder_blocks.empty()) { + extruder_blocks.back().initialize_step_3(line_id, prev_filament, line_id); + } + temp_construct_block.initialize_step_1(extruder_id, line_id, next_filament); + extruder_blocks.emplace_back(temp_construct_block); + temp_construct_block.reset(); + } + } + }; + + // we don't need to get line ends here because it's not the final end + gcode_process(in, out, filename_in, filename_out, gcode_time_handler, nullptr, buffer_size_in_KB); // updates moves' gcode ids which have been modified by the insertion of the M73 lines - unsigned int curr_offset_id = 0; - unsigned int total_offset = 0; - for (GCodeProcessorResult::MoveVertex& move : moves) { - while (curr_offset_id < static_cast(offsets.size()) && offsets[curr_offset_id].first <= move.gcode_id) { - total_offset += offsets[curr_offset_id].second; - ++curr_offset_id; + handle_offsets_of_first_process(offsets, moves, filament_blocks, extruder_blocks, skippable_blocks, machine_start_gcode_end_line_id, machine_end_gcode_start_line_id); + + // If not initialized, use the time from the previous move. + { + std::optionaliter; + for (auto niter = moves.begin(); niter != moves.end(); ++niter) { + if (!iter) { + iter = niter; + continue; + } + if (niter->time[0] == 0 && niter->time[1] == 0) { + niter->time[0] = (*iter)->time[0]; + niter->time[1] = (*iter)->time[1]; + } + ++(*iter); } - move.gcode_id += total_offset; } - if (rename_file(out_path, filename)) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": Failed to rename the output G-code file from %1% to %2%")%out_path.c_str() % filename.c_str(); - throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + out_path + " to " + filename + '\n' + - "Is " + out_path + " locked?" + '\n'); + // stores then strings to be inserted. first key is line id ,second key is content + InsertedLinesMap inserted_operation_lines; + + // save filament change block by extruder id + std::unordered_map> extruder_change_info; + + // collect the position to insert remaining filament changes + { + int curr_filament = -1; + int total_filament_count = 0; + for (const auto& fb : filament_blocks) { + if (curr_filament != -1 && curr_filament != fb.filament_id) + total_filament_count += 1; + curr_filament = fb.filament_id; + } + curr_filament = -1; + int curr_filament_change_num = 0; + for (const auto& fb : filament_blocks) { + int extruder_id = context.filament_maps[fb.filament_id]; + if (curr_filament != -1 && curr_filament != fb.filament_id) { + curr_filament_change_num += 1; + inserted_operation_lines[fb.lower_gcode_id].emplace_back(format_M73_remain_filament_changes(curr_filament_change_num, total_filament_count), InsertLineType::FilamentChangePredict); + } + curr_filament = fb.filament_id; + } + } + + if (!filament_blocks.empty()) { + filament_blocks.back().upper_gcode_id = machine_end_gcode_start_line_id; + } + + // After traversing the G-code, the first and last extruder blocks still have uncompleted initialization steps + if (!extruder_blocks.empty()) { + int first_filament = 0; + int last_filament = 0; + + if (!filament_blocks.empty()) { + first_filament = filament_blocks.front().filament_id; + last_filament = filament_blocks.back().filament_id; + } + extruder_blocks.front().initialize_step_1(context.filament_maps[first_filament], machine_start_gcode_end_line_id, first_filament); + extruder_blocks.back().initialize_step_2(machine_end_gcode_start_line_id); + extruder_blocks.back().initialize_step_3(machine_end_gcode_start_line_id,last_filament,machine_end_gcode_start_line_id); + } + + for (auto& block : extruder_blocks) + extruder_change_info[block.extruder_id].emplace_back(block); + + if (context.enable_pre_heating) { + // get the real speed mode used in slicing + size_t valid_machine_id = 0; + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (machines[i].enabled) { + valid_machine_id = i; + break; + } + } + + auto pre_cooling_injector = std::make_unique( + moves, + context.filament_types, + context.filament_maps, + context.filament_nozzle_temp, + context.physical_extruder_map, + valid_machine_id, + context.inject_time_threshold, + context.pre_cooling_temp, + context.cooling_rate, + context.heating_rate, + skippable_blocks, + machine_start_gcode_end_line_id, + machine_end_gcode_start_line_id + ); + + pre_cooling_injector->build_extruder_free_blocks(filament_blocks, extruder_blocks); + pre_cooling_injector->process_pre_cooling_and_heating(inserted_operation_lines); + } + + auto pre_operation_iter = inserted_operation_lines.begin(); + auto filament_change_handle = [&inserted_operation_lines, &pre_operation_iter,enable_pre_heating = context.enable_pre_heating](std::string& gcode_line, std::string& gcode_buffer, int line_id) { + if (pre_operation_iter == inserted_operation_lines.end()) + return; + if (line_id == pre_operation_iter->first) { + for (auto& elem : pre_operation_iter->second) { + const std::string& str = elem.first; + const InsertLineType type = elem.second; + switch (type) + { + case InsertLineType::PlaceholderReplace: + case InsertLineType::TimePredict: break; // these types above has been handled before + case InsertLineType::PreCooling: + case InsertLineType::PreHeating: + { + if (enable_pre_heating) + gcode_line += str; + break; + } + case InsertLineType::ExtruderChangePredict: break; + case InsertLineType::FilamentChangePredict: + { + gcode_line += str; + break; + } + default: + break; + } + } + ++pre_operation_iter; + } + }; + + filename_in = filename_out; // filename_out is opened in read|write mode. During second process ,we ues filename_out as input + filename_out = filename + ".postprocessed"; + + FilePtr new_out = boost::nowide::fopen(filename_out.c_str(), "wb"); + std::fseek(out.f, 0, SEEK_SET); // move to start of the file and start reading gcode as in + + gcode_process(out, new_out, filename_in, filename_out, filament_change_handle, &lines_ends, buffer_size_in_KB); + new_out.close(); + + // recollect gcode offset caused by inserted operations + handle_offsets_of_second_process(inserted_operation_lines, moves); + + in.close(); + out.close(); + + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": after process %1%") % filename.c_str(); + + if (boost::nowide::remove(filename_in.c_str()) != 0) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": Failed to remove the temporary G-code file %1%") % filename_in.c_str(); + throw Slic3r::RuntimeError(std::string("Failed to remove the temporary G-code file ") + filename_in + '\n' + + "Is " + filename_in + " locked?" + '\n'); + } + if (rename_file(filename_out, filename)) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": Failed to rename the output G-code file from %1% to %2%") % filename_out.c_str() % filename.c_str(); + throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + filename_out + " to " + filename + '\n' + + "Is " + filename_out + " locked?" + '\n'); + } +} + +void GCodeProcessor::TimeProcessor::handle_offsets_of_first_process( + const std::vector>& offsets, + std::vector& moves, + std::vector& filament_blocks, + std::vector& extruder_blocks, + std::vector>& skippable_blocks, + unsigned int& machine_start_gcode_end_line_id, + unsigned int& machine_end_gcode_start_line_id) +{ + // process moves + { + unsigned int curr_offset_id = 0, total_offset = 0; + for (GCodeProcessorResult::MoveVertex& move : moves) { + while (curr_offset_id < static_cast(offsets.size()) && offsets[curr_offset_id].first <= move.gcode_id) { + total_offset += offsets[curr_offset_id].second; + ++curr_offset_id; + } + move.gcode_id += total_offset; + } + } + + std::vector offset_line_id(offsets.size()); + std::vector prefix_sum_offset(offsets.size()); + unsigned int sum = 0; + for(size_t idx =0;idxunsigned int { + if (line_id == (unsigned int)(-1)) + return 0; + auto it = std::upper_bound(offset_line_id.begin(), offset_line_id.end(), line_id); + if (it == offset_line_id.begin()) + return 0; + size_t index = std::distance(offset_line_id.begin(), it) - 1; + return prefix_sum_offset[index]; + }; + + for (ExtruderPreHeating::FilamentUsageBlock& block : filament_blocks) { + block.lower_gcode_id += get_offset_before_line_id(block.lower_gcode_id); + block.upper_gcode_id += get_offset_before_line_id(block.upper_gcode_id); + } + for (ExtruderPreHeating::ExtruderUsageBlcok& block : extruder_blocks) { + block.start_id += get_offset_before_line_id(block.start_id); + block.end_id += get_offset_before_line_id(block.end_id); + block.post_extrusion_start_id += get_offset_before_line_id(block.post_extrusion_start_id); + block.post_extrusion_end_id += get_offset_before_line_id(block.post_extrusion_end_id); + } + + for(auto& block: skippable_blocks){ + block.first += get_offset_before_line_id(block.first); + block.second += get_offset_before_line_id(block.second); + } + + machine_start_gcode_end_line_id += get_offset_before_line_id(machine_start_gcode_end_line_id); + machine_end_gcode_start_line_id += get_offset_before_line_id(machine_end_gcode_start_line_id); +} + +void GCodeProcessor::TimeProcessor::handle_offsets_of_second_process(const InsertedLinesMap& inserted_operation_lines, std::vector& moves) +{ + int total_offset = 0; + auto iter = inserted_operation_lines.begin(); + for (GCodeProcessorResult::MoveVertex& move : moves) { + while (iter != inserted_operation_lines.end() && iter->first < move.gcode_id) { + total_offset += iter->second.size(); + ++iter; + } + move.gcode_id += total_offset; } } @@ -782,7 +1257,7 @@ void GCodeProcessor::UsedFilaments::reset() volumes_per_color_change = std::vector(); model_extrude_cache = 0.0f; - model_volumes_per_extruder.clear(); + model_volumes_per_filament.clear(); flush_per_filament.clear(); @@ -790,13 +1265,13 @@ void GCodeProcessor::UsedFilaments::reset() filaments_per_role.clear(); wipe_tower_cache = 0.0f; - wipe_tower_volumes_per_extruder.clear(); + wipe_tower_volumes_per_filament.clear(); support_volume_cache = 0.0f; - support_volumes_per_extruder.clear(); + support_volumes_per_filament.clear(); total_volume_cache = 0.0f; - total_volumes_per_extruder.clear(); + total_volumes_per_filament.clear(); } void GCodeProcessor::UsedFilaments::increase_support_caches(double extruded_volume) @@ -832,65 +1307,65 @@ void GCodeProcessor::UsedFilaments::process_color_change_cache() void GCodeProcessor::UsedFilaments::process_total_volume_cache(GCodeProcessor* processor) { - size_t active_extruder_id = processor->m_extruder_id; + size_t active_filament_id = processor->get_filament_id(); if (total_volume_cache!= 0.0f) { - if (total_volumes_per_extruder.find(active_extruder_id) != total_volumes_per_extruder.end()) - total_volumes_per_extruder[active_extruder_id] += total_volume_cache; + if (total_volumes_per_filament.find(active_filament_id) != total_volumes_per_filament.end()) + total_volumes_per_filament[active_filament_id] += total_volume_cache; else - total_volumes_per_extruder[active_extruder_id] = total_volume_cache; + total_volumes_per_filament[active_filament_id] = total_volume_cache; total_volume_cache = 0.0f; } } void GCodeProcessor::UsedFilaments::process_model_cache(GCodeProcessor* processor) { - size_t active_extruder_id = processor->m_extruder_id; + size_t active_filament_id = processor->get_filament_id(); if (model_extrude_cache != 0.0f) { - if (model_volumes_per_extruder.find(active_extruder_id) != model_volumes_per_extruder.end()) - model_volumes_per_extruder[active_extruder_id] += model_extrude_cache; + if (model_volumes_per_filament.find(active_filament_id) != model_volumes_per_filament.end()) + model_volumes_per_filament[active_filament_id] += model_extrude_cache; else - model_volumes_per_extruder[active_extruder_id] = model_extrude_cache; + model_volumes_per_filament[active_filament_id] = model_extrude_cache; model_extrude_cache = 0.0f; } } void GCodeProcessor::UsedFilaments::process_wipe_tower_cache(GCodeProcessor* processor) { - size_t active_extruder_id = processor->m_extruder_id; + size_t active_filament_id = processor->get_filament_id(); if (wipe_tower_cache != 0.0f) { - if (wipe_tower_volumes_per_extruder.find(active_extruder_id) != wipe_tower_volumes_per_extruder.end()) - wipe_tower_volumes_per_extruder[active_extruder_id] += wipe_tower_cache; + if (wipe_tower_volumes_per_filament.find(active_filament_id) != wipe_tower_volumes_per_filament.end()) + wipe_tower_volumes_per_filament[active_filament_id] += wipe_tower_cache; else - wipe_tower_volumes_per_extruder[active_extruder_id] = wipe_tower_cache; + wipe_tower_volumes_per_filament[active_filament_id] = wipe_tower_cache; wipe_tower_cache = 0.0f; } } void GCodeProcessor::UsedFilaments::process_support_cache(GCodeProcessor* processor) { - size_t active_extruder_id = processor->m_extruder_id; + size_t active_filament_id = processor->get_filament_id(); if (support_volume_cache != 0.0f){ - if (support_volumes_per_extruder.find(active_extruder_id) != support_volumes_per_extruder.end()) - support_volumes_per_extruder[active_extruder_id] += support_volume_cache; + if (support_volumes_per_filament.find(active_filament_id) != support_volumes_per_filament.end()) + support_volumes_per_filament[active_filament_id] += support_volume_cache; else - support_volumes_per_extruder[active_extruder_id] = support_volume_cache; + support_volumes_per_filament[active_filament_id] = support_volume_cache; support_volume_cache = 0.0f; } } -void GCodeProcessor::UsedFilaments::update_flush_per_filament(size_t extrude_id, float flush_volume) +void GCodeProcessor::UsedFilaments::update_flush_per_filament(size_t filament_id, float flush_volume) { if (flush_volume != 0.f) { role_cache += flush_volume; - if (flush_per_filament.find(extrude_id) != flush_per_filament.end()) - flush_per_filament[extrude_id] += flush_volume; + if (flush_per_filament.find(filament_id) != flush_per_filament.end()) + flush_per_filament[filament_id] += flush_volume; else - flush_per_filament[extrude_id] = flush_volume; + flush_per_filament[filament_id] = flush_volume; - if (total_volumes_per_extruder.find(extrude_id) != total_volumes_per_extruder.end()) - total_volumes_per_extruder[extrude_id] += flush_volume; + if (total_volumes_per_filament.find(filament_id) != total_volumes_per_filament.end()) + total_volumes_per_filament[filament_id] += flush_volume; else - total_volumes_per_extruder[extrude_id] = flush_volume; + total_volumes_per_filament[filament_id] = flush_volume; } } @@ -899,9 +1374,9 @@ void GCodeProcessor::UsedFilaments::process_role_cache(GCodeProcessor* processor if (role_cache != 0.0f) { std::pair filament = { 0.0f, 0.0f }; - double s = PI * sqr(0.5 * processor->m_result.filament_diameters[processor->m_extruder_id]); + double s = PI * sqr(0.5 * processor->m_result.filament_diameters[processor->get_filament_id()]); filament.first = role_cache / s * 0.001; - filament.second = role_cache * processor->m_result.filament_densities[processor->m_extruder_id] * 0.001; + filament.second = role_cache * processor->m_result.filament_densities[processor->get_filament_id()] * 0.001; ExtrusionRole active_role = processor->m_extrusion_role; if (filaments_per_role.find(active_role) != filaments_per_role.end()) { @@ -969,7 +1444,7 @@ void GCodeProcessorResult::reset() { timelapse_warning_code = 0; printable_height = 0.0f; settings_ids.reset(); - extruders_count = 0; + filaments_count = 0; extruder_colors = std::vector(); filament_diameters = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER); required_nozzle_HRC = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_HRC); @@ -977,6 +1452,9 @@ void GCodeProcessorResult::reset() { filament_costs = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_COST); custom_gcode_per_print_z = std::vector(); spiral_vase_layers = std::vector>>(); + layer_filaments.clear(); + filament_change_count_map.clear(); + skippable_part_time.clear(); warnings.clear(); //QDS: add mutex for protection of gcode result @@ -990,7 +1468,9 @@ const std::vector> GCodeProces //QDS: QIDIStudio is also "qidi". Otherwise the time estimation didn't work. //FIXME: Workaround and should be handled when do removing-qidi { EProducer::QIDIStudio, SLIC3R_APP_NAME }, - { EProducer::QIDIStudio, "generated by QIDIStudio" } + { EProducer::QIDIStudio, "generated by QIDIStudio" }, + { EProducer::QIDISlicer, "generated by QIDISlicer" }, + { EProducer::BambuStudio, "generated by BambuStudio" } //{ EProducer::Slic3rPE, "generated by Slic3r QIDI Edition" }, //{ EProducer::Slic3r, "generated by Slic3r" }, //{ EProducer::SuperSlicer, "generated by SuperSlicer" }, @@ -1012,7 +1492,7 @@ bool GCodeProcessor::contains_reserved_tag(const std::string& gcode, std::string std::string comment = line.raw(); if (comment.length() > 2 && comment.front() == ';') { comment = comment.substr(1); - for (const std::string& s : Reserved_Tags) { + for (const std::string& s : ReservedTags) { if (boost::starts_with(comment, s)) { ret = true; found_tag = comment; @@ -1039,7 +1519,7 @@ bool GCodeProcessor::contains_reserved_tags(const std::string& gcode, unsigned i std::string comment = line.raw(); if (comment.length() > 2 && comment.front() == ';') { comment = comment.substr(1); - for (const std::string& s : Reserved_Tags) { + for (const std::string& s : ReservedTags) { if (boost::starts_with(comment, s)) { ret = true; found_tag.push_back(comment); @@ -1063,6 +1543,185 @@ GCodeProcessor::GCodeProcessor() m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].line_m73_stop_mask = "M73 C%s\n"; m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].line_m73_main_mask = "M73 Q%s S%s\n"; m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].line_m73_stop_mask = "M73 D%s\n"; + + register_commands(); +} + +void GCodeProcessor::register_commands() +{ + // !!! registered command must be upper case + std::unordered_map command_handler_list = { + {"G0", [this](const GCodeReader::GCodeLine& line) { process_G0(line); }}, // Move + {"G1", [this](const GCodeReader::GCodeLine& line) { process_G1(line); }}, // Move + {"G2", [this](const GCodeReader::GCodeLine& line) { process_G2_G3(line); }}, // Move + {"G3", [this](const GCodeReader::GCodeLine& line) { process_G2_G3(line); }}, // Move + {"G4", [this](const GCodeReader::GCodeLine& line) { process_G4(line); }}, // Delay + + {"G10", [this](const GCodeReader::GCodeLine& line) { process_G10(line); }}, // Retract + {"G11", [this](const GCodeReader::GCodeLine& line) { process_G11(line); }}, // Unretract + + {"G20", [this](const GCodeReader::GCodeLine& line) { process_G20(line); }}, // Set Units to Inches + {"G21", [this](const GCodeReader::GCodeLine& line) { process_G21(line); }}, // Set Units to Millimeters + {"G22", [this](const GCodeReader::GCodeLine& line) { process_G22(line); }}, // Firmware controlled retract + {"G23", [this](const GCodeReader::GCodeLine& line) { process_G23(line); }}, // Firmware controlled unretract + {"G28", [this](const GCodeReader::GCodeLine& line) { process_G28(line); }}, // Move to origin + {"G29", [this](const GCodeReader::GCodeLine& line) { process_G29(line); }}, + + {"G90", [this](const GCodeReader::GCodeLine& line) { process_G90(line); }}, // Set to Absolute Positioning + {"G91", [this](const GCodeReader::GCodeLine& line) { process_G91(line); }}, // Set to Relative Positioning + {"G92", [this](const GCodeReader::GCodeLine& line) { process_G92(line); }}, // Set Position + + {"M1", [this](const GCodeReader::GCodeLine& line) { process_M1(line); }}, // Sleep or Conditional stop + + {"M82", [this](const GCodeReader::GCodeLine& line) { process_M82(line); }}, // Set extruder to absolute mode + {"M83", [this](const GCodeReader::GCodeLine& line) { process_M83(line); }}, // Set extruder to relative mode + + {"M104", [this](const GCodeReader::GCodeLine& line) { process_M104(line); }}, // Set extruder temperature + {"M106", [this](const GCodeReader::GCodeLine& line) { process_M106(line); }}, // Set fan speed + {"M107", [this](const GCodeReader::GCodeLine& line) { process_M107(line); }}, // Disable fan + {"M108", [this](const GCodeReader::GCodeLine& line) { process_M108(line); }}, // Set tool (Sailfish) + {"M109", [this](const GCodeReader::GCodeLine& line) { process_M109(line); }}, // Set extruder temperature and wait + + {"M132", [this](const GCodeReader::GCodeLine& line) { process_M132(line); }}, // Recall stored home offsets + {"M135", [this](const GCodeReader::GCodeLine& line) { process_M135(line); }}, // Set tool (MakerWare) + + {"M140", [this](const GCodeReader::GCodeLine& line) { process_M140(line); }}, // Set bed temperature + {"M190", [this](const GCodeReader::GCodeLine& line) { process_M190(line); }}, // Wait bed temperature + {"M191", [this](const GCodeReader::GCodeLine& line) { process_M191(line); }}, // Wait chamber temperature + + {"M201", [this](const GCodeReader::GCodeLine& line) { process_M201(line); }}, // Set max printing acceleration + {"M203", [this](const GCodeReader::GCodeLine& line) { process_M203(line); }}, // Set maximum feedrate + {"M204", [this](const GCodeReader::GCodeLine& line) { process_M204(line); }}, // Set default acceleration + {"M205", [this](const GCodeReader::GCodeLine& line) { process_M205(line); }}, // Advanced settings + {"M221", [this](const GCodeReader::GCodeLine& line) { process_M221(line); }}, // Set extrude factor override percentage + + {"M400", [this](const GCodeReader::GCodeLine& line) { process_M400(line); }}, // QDS delay + {"M401", [this](const GCodeReader::GCodeLine& line) { process_M401(line); }}, // Repetier: Store x, y and z position + {"M402", [this](const GCodeReader::GCodeLine& line) { process_M402(line); }}, // Repetier: Go to stored position + {"M566", [this](const GCodeReader::GCodeLine& line) { process_M566(line); }}, // Set allowable instantaneous speed change + {"M702", [this](const GCodeReader::GCodeLine& line) { process_M702(line); }}, // Unload the current filament into the MK3 MMU2 unit at the end of print. + {"M1020", [this](const GCodeReader::GCodeLine& line) { process_M1020(line); }}, // Select Tool + + {"T", [this](const GCodeReader::GCodeLine& line) { process_T(line); }}, // Select Tool + {"SYNC", [this](const GCodeReader::GCodeLine& line) { process_SYNC(line); }}, // SYNC TIME + + {"VG1", [this](const GCodeReader::GCodeLine& line) { process_VG1(line); }}, + {"VM104", [this](const GCodeReader::GCodeLine& line) { process_VM104(line); }}, + {"VM109", [this](const GCodeReader::GCodeLine& line) { process_VM109(line); }} + }; + + std::unordered_setearly_quit_commands = { + "T" + }; + + auto to_lowercase = [](std::string str)->std::string { + std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { + return std::tolower(c); + }); + return str; + }; + + for (auto elem : command_handler_list) { + auto& uppercase_cmd = elem.first; + auto& handler = elem.second; + bool early_quit = early_quit_commands.count(uppercase_cmd) > 0; + m_command_processor.register_command(uppercase_cmd, handler,early_quit); + if (auto lowercase_cmd = to_lowercase(uppercase_cmd); lowercase_cmd != uppercase_cmd) + m_command_processor.register_command(lowercase_cmd, handler,early_quit); + } +} + +bool GCodeProcessor::check_multi_extruder_gcode_valid(const std::vector &unprintable_areas, + const std::vector &printable_heights, + const std::vector &filament_map, + const std::vector> &unprintable_filament_types) +{ + m_result.limit_filament_maps.clear(); + m_result.gcode_check_result.reset(); + + m_result.limit_filament_maps.resize(filament_map.size(), 0); + + auto to_2d = [](const Vec3d &pos) -> Point { + Point ps(scale_(pos.x()), scale_(pos.y())); + return ps; + }; + + struct GCodePosInfo + { + Points pos; + float max_print_z; + }; + std::map> gcode_path_pos; // object_id, filament_id, pos + for (const GCodeProcessorResult::MoveVertex &move : m_result.moves) { + if (move.type == EMoveType::Extrude/* || move.type == EMoveType::Travel*/) { + if (move.is_arc_move_with_interpolation_points()) { + for (int i = 0; i < move.interpolation_points.size(); i++) { + gcode_path_pos[move.object_label_id][int(move.extruder_id)].pos.emplace_back(to_2d(move.interpolation_points[i].cast())); + } + } + else { + gcode_path_pos[move.object_label_id][int(move.extruder_id)].pos.emplace_back(to_2d(move.position.cast())); + } + gcode_path_pos[move.object_label_id][int(move.extruder_id)].max_print_z = std::max(gcode_path_pos[move.object_label_id][int(move.extruder_id)].max_print_z, move.print_z); + } + } + + bool valid = true; + Point plate_offset = Point(scale_(m_x_offset), scale_(m_y_offset)); + for (auto obj_iter = gcode_path_pos.begin(); obj_iter != gcode_path_pos.end(); ++obj_iter) { + int object_label_id = obj_iter->first; + const std::map &path_pos = obj_iter->second; + for (auto iter = path_pos.begin(); iter != path_pos.end(); ++iter) { + int extruder_id = filament_map[iter->first] - 1; + Polygon path_poly(iter->second.pos); + BoundingBox bbox = path_poly.bounding_box(); + + // check printable area + // Simplified use bounding_box, Accurate calculation is not efficient + for (Polygon poly : unprintable_areas[extruder_id]) { + poly.translate(plate_offset); + if (poly.bounding_box().overlap(bbox)) { + m_result.gcode_check_result.error_code = 1; + std::pair filament_to_object_id; + filament_to_object_id.first = iter->first; + filament_to_object_id.second = object_label_id; + m_result.gcode_check_result.print_area_error_infos[extruder_id].push_back(filament_to_object_id); + valid = false; + } + } + + // check printable height + if (iter->second.max_print_z > printable_heights[extruder_id]) { + m_result.gcode_check_result.error_code |= (1 << 1); + std::pair filament_to_object_id; + filament_to_object_id.first = iter->first; + filament_to_object_id.second = object_label_id; + m_result.gcode_check_result.print_height_error_infos[extruder_id].push_back(filament_to_object_id); + m_result.limit_filament_maps[iter->first] |= (1 << extruder_id); + valid = false; + } + + for (int i = 0; i < unprintable_areas.size(); ++i) { + for (Polygon poly : unprintable_areas[i]) { + poly.translate(plate_offset); + if (!poly.bounding_box().overlap(bbox)) + continue; + + m_result.limit_filament_maps[iter->first] |= (1 << i); + } + } + } + } + + // apply unprintable filament type result + for (int extruder_id = 0; extruder_id < unprintable_filament_types.size(); ++extruder_id) { + const std::set &filament_ids = unprintable_filament_types[extruder_id]; + for (int filament_id : filament_ids) { + m_result.limit_filament_maps[filament_id] |= (1 << extruder_id); + } + }; + + return valid; } void GCodeProcessor::apply_config(const PrintConfig& config) @@ -1072,20 +1731,47 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_flavor = config.gcode_flavor; // QDS - size_t extruders_count = config.filament_diameter.values.size(); - m_result.extruders_count = extruders_count; + size_t filament_count = config.filament_diameter.values.size(); + m_result.filaments_count = filament_count; - m_extruder_offsets.resize(extruders_count); - m_extruder_colors.resize(extruders_count); - m_result.filament_diameters.resize(extruders_count); - m_result.required_nozzle_HRC.resize(extruders_count); - m_result.filament_densities.resize(extruders_count); - m_result.filament_vitrification_temperature.resize(extruders_count); - m_result.filament_costs.resize(extruders_count); - m_extruder_temps.resize(extruders_count); - m_result.nozzle_type = config.nozzle_type; - for (size_t i = 0; i < extruders_count; ++ i) { - m_extruder_offsets[i] = to_3d(config.extruder_offset.get_at(i).cast().eval(), 0.f); + assert(config.nozzle_volume.size() == config.nozzle_diameter.size()); + m_nozzle_volume.resize(config.nozzle_volume.size()); + for (size_t idx = 0; idx < config.nozzle_volume.size(); ++idx) + m_nozzle_volume[idx] = config.nozzle_volume.values[idx]; + + m_filament_nozzle_temp.resize(filament_count); + for (size_t idx = 0; idx < filament_count; ++idx) + m_filament_nozzle_temp[idx] = config.nozzle_temperature.get_at(idx); + + m_filament_types.resize(filament_count); + for (size_t idx = 0; idx < filament_count; ++idx) + m_filament_types[idx] = config.filament_type.get_at(idx); + + m_hotend_cooling_rate = config.hotend_cooling_rate.values; + m_hotend_heating_rate = config.hotend_heating_rate.values; + m_filament_pre_cooling_temp = config.filament_pre_cooling_temperature.values; + m_enable_pre_heating = config.enable_pre_heating; + m_physical_extruder_map = config.physical_extruder_map.values; + + m_extruder_offsets.resize(filament_count); + m_extruder_colors.resize(filament_count); + m_result.filament_diameters.resize(filament_count); + m_result.required_nozzle_HRC.resize(filament_count); + m_result.filament_densities.resize(filament_count); + m_result.filament_vitrification_temperature.resize(filament_count); + m_result.filament_costs.resize(filament_count); + m_extruder_temps.resize(filament_count); + std::vector(config.nozzle_type.size()).swap(m_result.nozzle_type); + for (size_t idx = 0; idx < m_result.nozzle_type.size(); ++idx) { + m_result.nozzle_type[idx] = NozzleType(config.nozzle_type.values[idx]); + } + + std::vector filament_map = config.filament_map.values; // 1 based idxs + // if filament map has wrong length, set filament to master extruder_id + filament_map.resize(filament_count, config.master_extruder_id.value); + + for (size_t i = 0; i < filament_count; ++ i) { + m_extruder_offsets[i] = to_3d(config.extruder_offset.get_at(filament_map[i] - 1).cast().eval(), 0.f); m_extruder_colors[i] = static_cast(i); m_result.filament_diameters[i] = static_cast(config.filament_diameter.get_at(i)); m_result.required_nozzle_HRC[i] = static_cast(config.required_nozzle_HRC.get_at(i)); @@ -1108,6 +1794,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config) // are considered to be active for the single extruder multi-material printers only. m_time_processor.filament_load_times = static_cast(config.machine_load_filament_time.value); m_time_processor.filament_unload_times = static_cast(config.machine_unload_filament_time.value); + m_time_processor.extruder_change_times = static_cast(config.machine_switch_extruder_time.value); for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); @@ -1127,6 +1814,12 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_result.printable_height = config.printable_height; + auto filament_maps = config.option("filament_map"); + if (filament_maps != nullptr) { + m_filament_maps = filament_maps->values; + std::transform(m_filament_maps.begin(), m_filament_maps.end(), m_filament_maps.begin(), [](int value) {return value - 1; }); + } + const ConfigOptionBool* spiral_vase = config.option("spiral_mode"); if (spiral_vase != nullptr) m_detect_layer_based_on_tag = spiral_vase->value; @@ -1142,13 +1835,59 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) m_parser.apply_config(config); //QDS - const ConfigOptionFloat* nozzle_volume = config.option("nozzle_volume"); - if (nozzle_volume != nullptr) - m_nozzle_volume = nozzle_volume->value; + const ConfigOptionFloatsNullable* nozzle_volume = config.option("nozzle_volume"); + if (nozzle_volume != nullptr) { + m_nozzle_volume.resize(nozzle_volume->size(), 0); + for (size_t idx = 0; idx < nozzle_volume->size(); ++idx) + m_nozzle_volume[idx] = nozzle_volume->values[idx]; + } - const ConfigOptionEnum* nozzle_type = config.option>("nozzle_type"); - if (nozzle_type != nullptr) - m_result.nozzle_type=nozzle_type->value; + const ConfigOptionIntsNullable* nozzle_temperature = config.option("nozzle_temperature"); + if (nozzle_temperature != nullptr) { + m_filament_nozzle_temp.resize(nozzle_temperature->size(), 0); + for (size_t idx = 0; idx < nozzle_temperature->size(); ++idx) + m_filament_nozzle_temp[idx] = nozzle_temperature->get_at(idx); + } + + const ConfigOptionStrings* filament_type = config.option("filament_type"); + if (filament_type != nullptr) { + m_filament_types.resize(filament_type->size()); + for (size_t idx = 0; idx < filament_type->size(); ++idx) + m_filament_types[idx] = filament_type->get_at(idx); + } + + const ConfigOptionFloatsNullable* hotend_cooling_rate = config.option("hotend_cooling_rate"); + if (hotend_cooling_rate != nullptr) { + m_hotend_cooling_rate = hotend_cooling_rate->values; + } + + const ConfigOptionFloatsNullable* hotend_heating_rate = config.option("hotend_heating_rate"); + if (hotend_heating_rate != nullptr) { + m_hotend_heating_rate = hotend_heating_rate->values; + } + + const ConfigOptionIntsNullable* filament_pre_cooling_temp = config.option("filament_pre_cooling_temperature"); + if (filament_pre_cooling_temp != nullptr) { + m_filament_pre_cooling_temp = filament_pre_cooling_temp->values; + } + + const ConfigOptionBool* enable_pre_heating = config.option("enable_pre_heating"); + if (enable_pre_heating != nullptr) { + m_enable_pre_heating = enable_pre_heating->value; + } + + const ConfigOptionInts* physical_extruder_map = config.option("physical_extruder_map"); + if (physical_extruder_map != nullptr) { + m_physical_extruder_map = physical_extruder_map->values; + } + + const ConfigOptionEnumsGenericNullable* nozzle_type = config.option("nozzle_type"); + if (nozzle_type != nullptr) { + m_result.nozzle_type.resize(nozzle_type->size()); + for (size_t idx = 0; idx < nozzle_type->values.size(); ++idx) { + m_result.nozzle_type[idx] = NozzleType(nozzle_type->values[idx]); + } + } const ConfigOptionEnum* gcode_flavor = config.option>("gcode_flavor"); if (gcode_flavor != nullptr) @@ -1176,7 +1915,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) m_result.settings_ids.printer = printer_settings_id->value; // QDS - m_result.extruders_count = config.option("filament_diameter")->values.size(); + m_result.filaments_count = config.option("filament_diameter")->values.size(); const ConfigOptionFloats* filament_diameters = config.option("filament_diameter"); if (filament_diameters != nullptr) { @@ -1187,8 +1926,8 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) } } - if (m_result.filament_diameters.size() < m_result.extruders_count) { - for (size_t i = m_result.filament_diameters.size(); i < m_result.extruders_count; ++i) { + if (m_result.filament_diameters.size() < m_result.filaments_count) { + for (size_t i = m_result.filament_diameters.size(); i < m_result.filaments_count; ++i) { m_result.filament_diameters.emplace_back(DEFAULT_FILAMENT_DIAMETER); } } @@ -1200,8 +1939,8 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) for (size_t i = 0; i < filament_HRC->values.size(); ++i) { m_result.required_nozzle_HRC[i] = static_cast(filament_HRC->values[i]); } } - if (m_result.required_nozzle_HRC.size() < m_result.extruders_count) { - for (size_t i = m_result.required_nozzle_HRC.size(); i < m_result.extruders_count; ++i) { m_result.required_nozzle_HRC.emplace_back(DEFAULT_FILAMENT_HRC); + if (m_result.required_nozzle_HRC.size() < m_result.filaments_count) { + for (size_t i = m_result.required_nozzle_HRC.size(); i < m_result.filaments_count; ++i) { m_result.required_nozzle_HRC.emplace_back(DEFAULT_FILAMENT_HRC); } } @@ -1214,12 +1953,18 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) } } - if (m_result.filament_densities.size() < m_result.extruders_count) { - for (size_t i = m_result.filament_densities.size(); i < m_result.extruders_count; ++i) { + if (m_result.filament_densities.size() < m_result.filaments_count) { + for (size_t i = m_result.filament_densities.size(); i < m_result.filaments_count; ++i) { m_result.filament_densities.emplace_back(DEFAULT_FILAMENT_DENSITY); } } + auto filament_maps = config.option("filament_map"); + if (filament_maps != nullptr) { + m_filament_maps = filament_maps->values; + std::transform(m_filament_maps.begin(), m_filament_maps.end(), m_filament_maps.begin(), [](int value) {return value - 1; }); + } + //QDS const ConfigOptionFloats* filament_costs = config.option("filament_cost"); if (filament_costs != nullptr) { @@ -1228,7 +1973,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) for (size_t i = 0; i < filament_costs->values.size(); ++i) m_result.filament_costs[i]=static_cast(filament_costs->values[i]); } - for (size_t i = m_result.filament_costs.size(); i < m_result.extruders_count; ++i) { + for (size_t i = m_result.filament_costs.size(); i < m_result.filaments_count; ++i) { m_result.filament_costs.emplace_back(DEFAULT_FILAMENT_COST); } @@ -1241,8 +1986,8 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) m_result.filament_vitrification_temperature[i] = static_cast(filament_vitrification_temperature->values[i]); } } - if (m_result.filament_vitrification_temperature.size() < m_result.extruders_count) { - for (size_t i = m_result.filament_vitrification_temperature.size(); i < m_result.extruders_count; ++i) { + if (m_result.filament_vitrification_temperature.size() < m_result.filaments_count) { + for (size_t i = m_result.filament_vitrification_temperature.size(); i < m_result.filaments_count; ++i) { m_result.filament_vitrification_temperature.emplace_back(DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE); } } @@ -1253,8 +1998,8 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) //QDS: for single extruder multi material, only use the offset of first extruder if (single_extruder_multi_material != nullptr && single_extruder_multi_material->getBool()) { Vec2f offset = extruder_offset->values[0].cast(); - m_extruder_offsets.resize(m_result.extruders_count); - for (size_t i = 0; i < m_result.extruders_count; ++i) { + m_extruder_offsets.resize(m_result.filaments_count); + for (size_t i = 0; i < m_result.filaments_count; ++i) { m_extruder_offsets[i] = { offset(0), offset(1), 0.0f }; } } @@ -1267,8 +2012,8 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) } } - if (m_extruder_offsets.size() < m_result.extruders_count) { - for (size_t i = m_extruder_offsets.size(); i < m_result.extruders_count; ++i) { + if (m_extruder_offsets.size() < m_result.filaments_count) { + for (size_t i = m_extruder_offsets.size(); i < m_result.filaments_count; ++i) { m_extruder_offsets.emplace_back(DEFAULT_EXTRUDER_OFFSET); } } @@ -1282,8 +2027,8 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) } } - if (m_result.extruder_colors.size() < m_result.extruders_count) { - for (size_t i = m_result.extruder_colors.size(); i < m_result.extruders_count; ++i) { + if (m_result.extruder_colors.size() < m_result.filaments_count) { + for (size_t i = m_result.extruder_colors.size(); i < m_result.filaments_count; ++i) { m_result.extruder_colors.emplace_back(std::string()); } } @@ -1299,7 +2044,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) m_extruder_colors[i] = static_cast(i); } - m_extruder_temps.resize(m_result.extruders_count); + m_extruder_temps.resize(m_result.filaments_count); const ConfigOptionFloat* machine_load_filament_time = config.option("machine_load_filament_time"); if (machine_load_filament_time != nullptr) @@ -1309,77 +2054,81 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) if (machine_unload_filament_time != nullptr) m_time_processor.filament_unload_times = static_cast(machine_unload_filament_time->value); + const ConfigOptionFloat* machine_switch_extruder_time = config.option("machine_switch_extruder_time"); + if (machine_switch_extruder_time != nullptr) + m_time_processor.extruder_change_times = static_cast(machine_switch_extruder_time->value); + if (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfKlipper) { - const ConfigOptionFloats* machine_max_acceleration_x = config.option("machine_max_acceleration_x"); + const ConfigOptionFloatsNullable* machine_max_acceleration_x = config.option("machine_max_acceleration_x"); if (machine_max_acceleration_x != nullptr) m_time_processor.machine_limits.machine_max_acceleration_x.values = machine_max_acceleration_x->values; - const ConfigOptionFloats* machine_max_acceleration_y = config.option("machine_max_acceleration_y"); + const ConfigOptionFloatsNullable* machine_max_acceleration_y = config.option("machine_max_acceleration_y"); if (machine_max_acceleration_y != nullptr) m_time_processor.machine_limits.machine_max_acceleration_y.values = machine_max_acceleration_y->values; - const ConfigOptionFloats* machine_max_acceleration_z = config.option("machine_max_acceleration_z"); + const ConfigOptionFloatsNullable* machine_max_acceleration_z = config.option("machine_max_acceleration_z"); if (machine_max_acceleration_z != nullptr) m_time_processor.machine_limits.machine_max_acceleration_z.values = machine_max_acceleration_z->values; - const ConfigOptionFloats* machine_max_acceleration_e = config.option("machine_max_acceleration_e"); + const ConfigOptionFloatsNullable* machine_max_acceleration_e = config.option("machine_max_acceleration_e"); if (machine_max_acceleration_e != nullptr) m_time_processor.machine_limits.machine_max_acceleration_e.values = machine_max_acceleration_e->values; - const ConfigOptionFloats* machine_max_speed_x = config.option("machine_max_speed_x"); + const ConfigOptionFloatsNullable* machine_max_speed_x = config.option("machine_max_speed_x"); if (machine_max_speed_x != nullptr) m_time_processor.machine_limits.machine_max_speed_x.values = machine_max_speed_x->values; - const ConfigOptionFloats* machine_max_speed_y = config.option("machine_max_speed_y"); + const ConfigOptionFloatsNullable* machine_max_speed_y = config.option("machine_max_speed_y"); if (machine_max_speed_y != nullptr) m_time_processor.machine_limits.machine_max_speed_y.values = machine_max_speed_y->values; - const ConfigOptionFloats* machine_max_speed_z = config.option("machine_max_speed_z"); + const ConfigOptionFloatsNullable* machine_max_speed_z = config.option("machine_max_speed_z"); if (machine_max_speed_z != nullptr) m_time_processor.machine_limits.machine_max_speed_z.values = machine_max_speed_z->values; - const ConfigOptionFloats* machine_max_speed_e = config.option("machine_max_speed_e"); + const ConfigOptionFloatsNullable* machine_max_speed_e = config.option("machine_max_speed_e"); if (machine_max_speed_e != nullptr) m_time_processor.machine_limits.machine_max_speed_e.values = machine_max_speed_e->values; - const ConfigOptionFloats* machine_max_jerk_x = config.option("machine_max_jerk_x"); + const ConfigOptionFloatsNullable* machine_max_jerk_x = config.option("machine_max_jerk_x"); if (machine_max_jerk_x != nullptr) m_time_processor.machine_limits.machine_max_jerk_x.values = machine_max_jerk_x->values; - const ConfigOptionFloats* machine_max_jerk_y = config.option("machine_max_jerk_y"); + const ConfigOptionFloatsNullable* machine_max_jerk_y = config.option("machine_max_jerk_y"); if (machine_max_jerk_y != nullptr) m_time_processor.machine_limits.machine_max_jerk_y.values = machine_max_jerk_y->values; - const ConfigOptionFloats* machine_max_jerk_z = config.option("machine_max_jerkz"); + const ConfigOptionFloatsNullable* machine_max_jerk_z = config.option("machine_max_jerkz"); if (machine_max_jerk_z != nullptr) m_time_processor.machine_limits.machine_max_jerk_z.values = machine_max_jerk_z->values; - const ConfigOptionFloats* machine_max_jerk_e = config.option("machine_max_jerk_e"); + const ConfigOptionFloatsNullable* machine_max_jerk_e = config.option("machine_max_jerk_e"); if (machine_max_jerk_e != nullptr) m_time_processor.machine_limits.machine_max_jerk_e.values = machine_max_jerk_e->values; - const ConfigOptionFloats* machine_max_acceleration_extruding = config.option("machine_max_acceleration_extruding"); + const ConfigOptionFloatsNullable* machine_max_acceleration_extruding = config.option("machine_max_acceleration_extruding"); if (machine_max_acceleration_extruding != nullptr) m_time_processor.machine_limits.machine_max_acceleration_extruding.values = machine_max_acceleration_extruding->values; - const ConfigOptionFloats* machine_max_acceleration_retracting = config.option("machine_max_acceleration_retracting"); + const ConfigOptionFloatsNullable* machine_max_acceleration_retracting = config.option("machine_max_acceleration_retracting"); if (machine_max_acceleration_retracting != nullptr) m_time_processor.machine_limits.machine_max_acceleration_retracting.values = machine_max_acceleration_retracting->values; // Legacy Marlin does not have separate travel acceleration, it uses the 'extruding' value instead. - const ConfigOptionFloats* machine_max_acceleration_travel = config.option(m_flavor == gcfMarlinLegacy + const ConfigOptionFloatsNullable* machine_max_acceleration_travel = config.option(m_flavor == gcfMarlinLegacy ? "machine_max_acceleration_extruding" : "machine_max_acceleration_travel"); if (machine_max_acceleration_travel != nullptr) m_time_processor.machine_limits.machine_max_acceleration_travel.values = machine_max_acceleration_travel->values; - const ConfigOptionFloats* machine_min_extruding_rate = config.option("machine_min_extruding_rate"); + const ConfigOptionFloatsNullable* machine_min_extruding_rate = config.option("machine_min_extruding_rate"); if (machine_min_extruding_rate != nullptr) m_time_processor.machine_limits.machine_min_extruding_rate.values = machine_min_extruding_rate->values; - const ConfigOptionFloats* machine_min_travel_rate = config.option("machine_min_travel_rate"); + const ConfigOptionFloatsNullable* machine_min_travel_rate = config.option("machine_min_travel_rate"); if (machine_min_travel_rate != nullptr) m_time_processor.machine_limits.machine_min_travel_rate.values = machine_min_travel_rate->values; } @@ -1438,7 +2187,7 @@ void GCodeProcessor::reset() m_e_local_positioning_type = EPositioningType::Absolute; m_extruder_offsets = std::vector(MIN_EXTRUDERS_COUNT, Vec3f::Zero()); m_flavor = gcfRepRapSprinter; - m_nozzle_volume = 0.f; + m_nozzle_volume = {0.f,0.f}; m_start_position = { 0.0f, 0.0f, 0.0f, 0.0f }; m_end_position = { 0.0f, 0.0f, 0.0f, 0.0f }; @@ -1446,8 +2195,11 @@ void GCodeProcessor::reset() m_cached_position.reset(); m_wiping = false; m_flushing = false; + m_virtual_flushing = false; + m_skippable = false; + m_skippable_type = SkipType::stNone; m_wipe_tower = false; - m_remaining_volume = 0.f; + m_remaining_volume = { 0.f,0.f }; // QDS: arc move related data m_move_path_type = EMovePathType::Noop_move; m_arc_center = Vec3f::Zero(); @@ -1463,8 +2215,10 @@ void GCodeProcessor::reset() m_fan_speed = 0.0f; m_extrusion_role = erNone; - m_extruder_id = 0; - m_last_extruder_id = 0; + + m_filament_id = {static_cast(-1),static_cast(-1)}; + m_last_filament_id = {static_cast(-1),static_cast(-1) }; + m_extruder_id = static_cast(-1); m_extruder_colors.resize(MIN_EXTRUDERS_COUNT); for (size_t i = 0; i < MIN_EXTRUDERS_COUNT; ++i) { m_extruder_colors[i] = static_cast(i); @@ -1473,6 +2227,12 @@ void GCodeProcessor::reset() for (size_t i = 0; i < MIN_EXTRUDERS_COUNT; ++i) { m_extruder_temps[i] = 0.0f; } + + m_physical_extruder_map.clear(); + m_filament_nozzle_temp.clear(); + m_enable_pre_heating = false; + m_hotend_cooling_rate = m_hotend_heating_rate = { 2.f }; + m_highest_bed_temp = 0; m_extruded_last_z = 0.0f; @@ -1549,13 +2309,20 @@ void GCodeProcessor::process_file(const std::string& filename, std::function("filament_colour"); + ConfigOptionInts *filament_map = config.opt("filament_map", true); + if (filament_color && filament_color->size() != filament_map->size()) { + filament_map->values.resize(filament_color->size(), 1); + } + apply_config(config); } else if (m_producer == EProducer::Simplify3D) @@ -1596,7 +2363,7 @@ void GCodeProcessor::initialize(const std::string& filename) m_result.filename = filename; m_result.id = ++s_result_id; // 1st move must be a dummy move - m_result.moves.emplace_back(GCodeProcessorResult::MoveVertex()); + m_result.moves.emplace_back(); } void GCodeProcessor::process_buffer(const std::string &buffer) @@ -1621,7 +2388,9 @@ void GCodeProcessor::finalize(bool post_process) for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time; - machine.calculate_time(0, 0, ExtrusionRole::erNone); + machine.calculate_time(0, 0, ExtrusionRole::erNone, [&result=m_result, i,&machine](const TimeBlock& block, int time) { + machine.handle_time_block(block,time,i,result); + }); if (gcode_time.needed && gcode_time.cache != 0.0f) gcode_time.times.push_back({ CustomGCode::ColorChange, gcode_time.cache }); } @@ -1652,8 +2421,21 @@ void GCodeProcessor::finalize(bool post_process) m_width_compare.output(); #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING if (post_process){ - //1.9.7.52 - TimeProcessContext context(m_layer_id,m_filament_lists,m_used_filaments); + constexpr float inject_time_threshold = 30.f; + TimeProcessContext context( + m_used_filaments, + m_filament_lists, + m_filament_maps, + m_filament_types, + m_filament_nozzle_temp, + m_physical_extruder_map, + m_layer_id, + m_hotend_cooling_rate, + m_hotend_heating_rate, + m_filament_pre_cooling_temp, + inject_time_threshold, + m_enable_pre_heating + ); m_time_processor.post_process(m_result.filename, m_result.moves, m_result.lines_ends, context); } #if ENABLE_GCODE_VIEWER_STATISTICS @@ -1825,7 +2607,7 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename) } else if (comment.find("extruderDiameter") != comment.npos) { std::vector extruder_diameters; extract_floats(comment, "extruderDiameter", extruder_diameters); - m_result.extruders_count = extruder_diameters.size(); + m_result.filaments_count = extruder_diameters.size(); } } else if (boost::starts_with(comment, "G-Code generated by Simplify3D(R)")) producer_detected = true; @@ -1837,8 +2619,8 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename) } }); - if (m_result.extruders_count == 0) - m_result.extruders_count = std::max(1, std::min(m_result.filament_diameters.size(), m_result.filament_densities.size())); + if (m_result.filaments_count == 0) + m_result.filaments_count = std::max(1, std::min(m_result.filament_diameters.size(), m_result.filament_densities.size())); if (bed_size.is_defined()) { m_result.printable_area = { @@ -1872,198 +2654,27 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line, bool if (cmd.length() > 1) { // process command lines - switch (cmd[0]) - { - case 'g': - case 'G': - switch (cmd.size()) { - case 2: - switch (cmd[1]) { - case '0': { process_G0(line); break; } // Move - case '1': { process_G1(line); break; } // Move - case '2': - case '3': { process_G2_G3(line); break; } // Move - //QDS - case '4': { process_G4(line); break; } // Delay - default: break; - } - break; - case 3: - switch (cmd[1]) { - case '1': - switch (cmd[2]) { - case '0': { process_G10(line); break; } // Retract - case '1': { process_G11(line); break; } // Unretract - default: break; - } - break; - case '2': - switch (cmd[2]) { - case '0': { process_G20(line); break; } // Set Units to Inches - case '1': { process_G21(line); break; } // Set Units to Millimeters - case '2': { process_G22(line); break; } // Firmware controlled retract - case '3': { process_G23(line); break; } // Firmware controlled unretract - case '8': { process_G28(line); break; } // Move to origin - case '9': { process_G29(line); break; } - default: break; - } - break; - case '9': - switch (cmd[2]) { - case '0': { process_G90(line); break; } // Set to Absolute Positioning - case '1': { process_G91(line); break; } // Set to Relative Positioning - case '2': { process_G92(line); break; } // Set Position - default: break; - } - break; - } - break; - default: - break; - } - break; - case 'm': - case 'M': - switch (cmd.size()) { - case 2: - switch (cmd[1]) { - case '1': { process_M1(line); break; } // Sleep or Conditional stop - default: break; - } - break; - case 3: - switch (cmd[1]) { - case '8': - switch (cmd[2]) { - case '2': { process_M82(line); break; } // Set extruder to absolute mode - case '3': { process_M83(line); break; } // Set extruder to relative mode - default: break; - } - break; - default: - break; - } - break; - case 4: - switch (cmd[1]) { - case '1': - switch (cmd[2]) { - case '0': - switch (cmd[3]) { - case '4': { process_M104(line); break; } // Set extruder temperature - case '6': { process_M106(line); break; } // Set fan speed - case '7': { process_M107(line); break; } // Disable fan - case '8': { process_M108(line); break; } // Set tool (Sailfish) - case '9': { process_M109(line); break; } // Set extruder temperature and wait - default: break; - } - break; - case '3': - switch (cmd[3]) { - case '2': { process_M132(line); break; } // Recall stored home offsets - case '5': { process_M135(line); break; } // Set tool (MakerWare) - default: break; - } - break; - case '4': - switch (cmd[3]) { - case '0': { process_M140(line); break; } // Set bed temperature - default: break; - } - break; - case '9': - switch (cmd[3]) { - case '0': { process_M190(line); break; } // Wait bed temperature - case '1': { process_M191(line); break; } // Wait chamber temperature - default: break; - } - break; - default: - break; - } - break; - case '2': - switch (cmd[2]) { - case '0': - switch (cmd[3]) { - case '1': { process_M201(line); break; } // Set max printing acceleration - case '3': { process_M203(line); break; } // Set maximum feedrate - case '4': { process_M204(line); break; } // Set default acceleration - case '5': { process_M205(line); break; } // Advanced settings - default: break; - } - break; - case '2': - switch (cmd[3]) { - case '1': { process_M221(line); break; } // Set extrude factor override percentage - default: break; - } - break; - default: - break; - } - break; - case '4': - switch (cmd[2]) { - case '0': - switch (cmd[3]) { - //QDS - case '0': { process_M400(line); break; } // QDS delay - case '1': { process_M401(line); break; } // Repetier: Store x, y and z position - case '2': { process_M402(line); break; } // Repetier: Go to stored position - default: break; - } - break; - default: - break; - } - break; - case '5': - switch (cmd[2]) { - case '6': - switch (cmd[3]) { - case '6': { process_M566(line); break; } // Set allowable instantaneous speed change - default: break; - } - break; - default: - break; - } - break; - case '7': - switch (cmd[2]) { - case '0': - switch (cmd[3]) { - case '2': { process_M702(line); break; } // Unload the current filament into the MK3 MMU2 unit at the end of print. - default: break; - } - break; - default: - break; - } - break; - default: - break; - } - break; - default: - break; - } - break; - case 't': - case 'T': - process_T(line); // Select Tool - break; - default: - break; - } + m_command_processor.process_comand(cmd, line); } else { const std::string &comment = line.raw(); if (comment.length() > 2 && comment.front() == ';') - // Process tags embedded into comments. Tag comments always start at the start of a line - // with a comment and continue with a tag without any whitespace separator. - process_tags(comment.substr(1), producers_enabled); + { + std::string comment_content = comment.substr(1); // only format like ";V{cmd}" is valid + if (comment_content[0] == 'V' || comment_content[0] == 'v') { + GCodeReader reader; + GCodeReader::GCodeLine new_line; + reader.parse_line(comment_content, [&new_line](const auto& greader, const auto& gline) { + new_line = gline; + }); + m_command_processor.process_comand(new_line.cmd(), new_line); + } + else { + // Process tags embedded into comments. Tag comments always start at the start of a line + // with a comment and continue with a tag without any whitespace separator. + process_tags(comment_content, producers_enabled); + } + } } } @@ -2201,6 +2812,98 @@ bool GCodeProcessor::get_last_z_from_gcode(const std::string& gcode_str, double& return is_z_changed; } +bool GCodeProcessor::get_last_position_from_gcode(const std::string &gcode_str, Vec3f &pos) +{ + int str_size = gcode_str.size(); + int start_index = 0; + int end_index = 0; + bool is_z_changed = false; + while (end_index < str_size) { + // find a full line + if (gcode_str[end_index] != '\n') { + end_index++; + continue; + } + // parse the line + if (end_index > start_index) { + std::string line_str = gcode_str.substr(start_index, end_index - start_index); + line_str.erase(0, line_str.find_first_not_of(" ")); + line_str.erase(line_str.find_last_not_of(";") + 1); + line_str.erase(line_str.find_last_not_of(" ") + 1); + + // command which may have z movement + if (line_str.size() > 5 && (line_str.find("G0 ") == 0 || line_str.find("G1 ") == 0 || line_str.find("G2 ") == 0 || line_str.find("G3 ") == 0)) { + { + float &x = pos.x(); + auto z_pos = line_str.find(" X"); + float temp_z = 0; + if (z_pos != line_str.npos && z_pos + 2 < line_str.size()) { + // Try to parse the numeric value. + std::string z_sub = line_str.substr(z_pos + 2); + char *c = &z_sub[0]; + char *end = c + sizeof(z_sub.c_str()); + + auto is_end_of_word = [](char c) { return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == 0 || c == ';'; }; + + auto [pend, ec] = fast_float::from_chars(c, end, temp_z); + if (pend != c && is_end_of_word(*pend)) { + // The axis value has been parsed correctly. + x = temp_z; + is_z_changed = true; + } + } + } + + { + float &y = pos.y(); + auto z_pos = line_str.find(" Y"); + float temp_z = 0; + if (z_pos != line_str.npos && z_pos + 2 < line_str.size()) { + // Try to parse the numeric value. + std::string z_sub = line_str.substr(z_pos + 2); + char *c = &z_sub[0]; + char *end = c + sizeof(z_sub.c_str()); + + auto is_end_of_word = [](char c) { return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == 0 || c == ';'; }; + + auto [pend, ec] = fast_float::from_chars(c, end, temp_z); + if (pend != c && is_end_of_word(*pend)) { + // The axis value has been parsed correctly. + y = temp_z; + is_z_changed = true; + } + } + } + + { + float &z = pos.z(); + auto z_pos = line_str.find(" Z"); + float temp_z = 0; + if (z_pos != line_str.npos && z_pos + 2 < line_str.size()) { + // Try to parse the numeric value. + std::string z_sub = line_str.substr(z_pos + 2); + char *c = &z_sub[0]; + char *end = c + sizeof(z_sub.c_str()); + + auto is_end_of_word = [](char c) { return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == 0 || c == ';'; }; + + auto [pend, ec] = fast_float::from_chars(c, end, temp_z); + if (pend != c && is_end_of_word(*pend)) { + // The axis value has been parsed correctly. + z = temp_z; + is_z_changed = true; + } + } + } + } + } + // loop to handle next line + start_index = end_index + 1; + end_index = start_index; + } + return is_z_changed; +} + void GCodeProcessor::process_tags(const std::string_view comment, bool producers_enabled) { static ExtrusionRole prev_role; @@ -2217,6 +2920,24 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers return; } + // ; OBJECT_ID start + if (boost::starts_with(comment, " start printing object")) { + m_object_label_id = get_object_label_id(comment); + return; + } + + // ; OBJECT_ID end + if (boost::starts_with(comment, " stop printing object")) { + m_object_label_id = -1; + return; + } + + // ; Z_HEIGHT: + if (boost::starts_with(comment, " Z_HEIGHT:")) { + m_print_z = get_z_height(comment); + return; + } + // wipe start tag if (boost::starts_with(comment, reserved_tag(ETags::Wipe_Start))) { m_wiping = true; @@ -2240,8 +2961,27 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers return; } + + if (boost::starts_with(comment, custom_tags(CustomETags::SKIPPABLE_START))) { + m_skippable = true; + return; + } + + if (boost::starts_with(comment, custom_tags(CustomETags::SKIPPABLE_END))) { + m_skippable = false; + m_skippable_type = SkipType::stNone; + return; + } + + // skippable type + if (boost::starts_with(comment, custom_tags(CustomETags::SKIPPABLE_TYPE))) { + std::string_view type =comment.substr(custom_tags(CustomETags::SKIPPABLE_TYPE).length()); + set_skippable_type(type); + return; + } + //QDS: flush start tag - if (boost::starts_with(comment, GCodeProcessor::Flush_Start_Tag)) { + if (boost::starts_with(comment, custom_tags(CustomETags::FLUSH_START))) { prev_role = m_extrusion_role; set_extrusion_role(erFlush); m_flushing = true; @@ -2249,12 +2989,25 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers } //QDS: flush end tag - if (boost::starts_with(comment, GCodeProcessor::Flush_End_Tag)) { + if (boost::starts_with(comment, custom_tags(CustomETags::FLUSH_END))) { set_extrusion_role(prev_role); m_flushing = false; return; } + if (boost::starts_with(comment, custom_tags(CustomETags::VFLUSH_START))) { + prev_role = m_extrusion_role; + set_extrusion_role(erFlush); + m_virtual_flushing = true; + return; + } + + if (boost::starts_with(comment, custom_tags(CustomETags::VFLUSH_END))) { + set_extrusion_role(prev_role); + m_virtual_flushing = false; + return; + } + if (!producers_enabled || m_producer == EProducer::QIDIStudio) { // height tag if (boost::starts_with(comment, reserved_tag(ETags::Height))) { @@ -2272,7 +3025,7 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers // color change tag if (boost::starts_with(comment, reserved_tag(ETags::Color_Change))) { - unsigned char extruder_id = 0; + unsigned char filament_id = 0; static std::vector Default_Colors = { "#0B2C7A", // { 0.043f, 0.173f, 0.478f }, // bluish "#1C8891", // { 0.110f, 0.533f, 0.569f }, @@ -2308,7 +3061,7 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Color_Change (" << comment << ")."; return; } - extruder_id = static_cast(eid); + filament_id = static_cast(eid); } } if (tokens.size() > 2) { @@ -2322,16 +3075,16 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers m_last_default_color_id = 0; } - if (extruder_id < m_extruder_colors.size()) - m_extruder_colors[extruder_id] = static_cast(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview + if (filament_id < m_extruder_colors.size()) + m_extruder_colors[filament_id] = static_cast(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview ++m_cp_color.counter; if (m_cp_color.counter == UCHAR_MAX) m_cp_color.counter = 0; - if (m_extruder_id == extruder_id) { - m_cp_color.current = m_extruder_colors[extruder_id]; + if (get_filament_id() == filament_id) { + m_cp_color.current = m_extruder_colors[filament_id]; store_move_vertex(EMoveType::Color_change); - CustomGCode::Item item = { static_cast(m_end_position[2]), CustomGCode::ColorChange, extruder_id + 1, color, "" }; + CustomGCode::Item item = { static_cast(m_end_position[2]), CustomGCode::ColorChange, filament_id + 1, color, "" }; m_result.custom_gcode_per_print_z.emplace_back(item); m_options_z_corrector.set(); process_custom_gcode_time(CustomGCode::ColorChange); @@ -2344,7 +3097,7 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers // pause print tag if (comment == reserved_tag(ETags::Pause_Print)) { store_move_vertex(EMoveType::Pause_Print); - CustomGCode::Item item = { static_cast(m_end_position[2]), CustomGCode::PausePrint, m_extruder_id + 1, "", "" }; + CustomGCode::Item item = { static_cast(m_end_position[2]), CustomGCode::PausePrint, get_filament_id() + 1, "", ""}; m_result.custom_gcode_per_print_z.emplace_back(item); m_options_z_corrector.set(); process_custom_gcode_time(CustomGCode::PausePrint); @@ -2354,7 +3107,7 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers // custom code tag if (comment == reserved_tag(ETags::Custom_Code)) { store_move_vertex(EMoveType::Custom_GCode); - CustomGCode::Item item = { static_cast(m_end_position[2]), CustomGCode::Custom, m_extruder_id + 1, "", "" }; + CustomGCode::Item item = { static_cast(m_end_position[2]), CustomGCode::Custom, get_filament_id() + 1, "", ""}; m_result.custom_gcode_per_print_z.emplace_back(item); m_options_z_corrector.set(); return; @@ -2865,7 +3618,9 @@ void GCodeProcessor::process_G0(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) { - float filament_diameter = (static_cast(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back(); + int filament_id = get_filament_id(); + int last_filament_id = get_last_filament_id(); + float filament_diameter = (static_cast(filament_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[filament_id] : m_result.filament_diameters.back(); float filament_radius = 0.5f * filament_diameter; float area_filament_cross_section = static_cast(M_PI) * sqr(filament_radius); auto absolute_position = [this, area_filament_cross_section](Axis axis, const GCodeReader::GCodeLine& lineG1) { @@ -2975,7 +3730,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) m_width = delta_pos[E] * static_cast(M_PI * sqr(1.05f * filament_radius)) / (delta_xyz * m_height); else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erNone) // cross section: circle - m_width = static_cast(m_result.filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / delta_xyz); + m_width = static_cast(m_result.filament_diameters[filament_id]) * std::sqrt(delta_pos[E] / delta_xyz); else // cross section: rectangle + 2 semicircles m_width = delta_pos[E] * static_cast(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast(1.0 - 0.25 * M_PI) * m_height; @@ -2991,16 +3746,17 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } else if (type == EMoveType::Unretract && m_flushing) { + int extruder_id = get_extruder_id(); float volume_flushed_filament = area_filament_cross_section * delta_pos[E]; - if (m_remaining_volume > volume_flushed_filament) + if (m_remaining_volume[extruder_id] > volume_flushed_filament) { - m_used_filaments.update_flush_per_filament(m_last_extruder_id, volume_flushed_filament); - m_remaining_volume -= volume_flushed_filament; + m_used_filaments.update_flush_per_filament(last_filament_id, volume_flushed_filament); + m_remaining_volume[extruder_id] -= volume_flushed_filament; } else { - m_used_filaments.update_flush_per_filament(m_last_extruder_id, m_remaining_volume); - m_used_filaments.update_flush_per_filament(m_extruder_id, volume_flushed_filament - m_remaining_volume); - m_remaining_volume = 0.f; + m_used_filaments.update_flush_per_filament(last_filament_id, m_remaining_volume[extruder_id]); + m_used_filaments.update_flush_per_filament(filament_id, volume_flushed_filament - m_remaining_volume[extruder_id]); + m_remaining_volume[extruder_id] = 0.f; } } @@ -3040,9 +3796,11 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) TimeBlock block; block.move_type = type; + block.skippable_type = m_skippable_type; //QDS: don't calculate travel time into extrusion path, except travel inside start and end gcode. block.role = (type != EMoveType::Travel || m_extrusion_role == erCustom) ? m_extrusion_role : erNone; block.distance = distance; + block.move_id = m_result.moves.size(); block.g1_line_id = m_g1_line_id; block.layer_id = std::max(1, m_layer_id); block.flags.prepare_stage = m_processing_start_custom_gcode; @@ -3082,7 +3840,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) curr.abs_axis_feedrate[a] = std::abs(curr.axis_feedrate[a]); if (curr.abs_axis_feedrate[a] != 0.0f) { - float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a)); + float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a), m_extruder_id); if (axis_max_feedrate != 0.0f) min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]); } } @@ -3106,7 +3864,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) //QDS for (unsigned char a = X; a <= E; ++a) { - float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a)); + float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a), m_extruder_id); if (acceleration * std::abs(delta_pos[a]) * inv_distance > axis_max_acceleration) acceleration = axis_max_acceleration / (std::abs(delta_pos[a]) * inv_distance); } @@ -3225,7 +3983,9 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) blocks.push_back(block); if (blocks.size() > TimeProcessor::Planner::refresh_threshold) { - machine.calculate_time(TimeProcessor::Planner::queue_size, 0, erNone); + machine.calculate_time(TimeProcessor::Planner::queue_size, 0, erNone, [&result=m_result, i,&machine](const TimeBlock& block, int time) { + machine.handle_time_block(block,time,i,result); + }); } } @@ -3234,11 +3994,11 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter && !m_seams_detector.has_first_vertex()) { //QDS: m_result.moves.back().position has plate offset, must minus plate offset before calculate the real seam position const Vec3f real_first_pos = Vec3f(m_result.moves.back().position.x() - m_x_offset, m_result.moves.back().position.y() - m_y_offset, m_result.moves.back().position.z()); - m_seams_detector.set_first_vertex(real_first_pos - m_extruder_offsets[m_extruder_id]); + m_seams_detector.set_first_vertex(real_first_pos - m_extruder_offsets[filament_id]); } else if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter && m_detect_layer_based_on_tag) { const Vec3f real_last_pos = Vec3f(m_result.moves.back().position.x() - m_x_offset, m_result.moves.back().position.y() - m_y_offset, m_result.moves.back().position.z()); - const Vec3f new_pos = real_last_pos - m_extruder_offsets[m_extruder_id]; + const Vec3f new_pos = real_last_pos - m_extruder_offsets[filament_id]; // We may have sloped loop, drop any previous start pos if we have z increment const std::optional first_vertex = m_seams_detector.get_first_vertex(); if (new_pos.z() > first_vertex->z()) { @@ -3254,7 +4014,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) const Vec3f curr_pos(m_end_position[X], m_end_position[Y], m_end_position[Z]); //QDS: m_result.moves.back().position has plate offset, must minus plate offset before calculate the real seam position const Vec3f real_last_pos = Vec3f(m_result.moves.back().position.x() - m_x_offset, m_result.moves.back().position.y() - m_y_offset, m_result.moves.back().position.z()); - const Vec3f new_pos = real_last_pos - m_extruder_offsets[m_extruder_id]; + const Vec3f new_pos = real_last_pos - m_extruder_offsets[filament_id]; const std::optional first_vertex = m_seams_detector.get_first_vertex(); // the threshold value = 0.0625f == 0.25 * 0.25 is arbitrary, we may find some smarter condition later @@ -3270,17 +4030,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) else if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter) { m_seams_detector.activate(true); Vec3f plate_offset = {(float) m_x_offset, (float) m_y_offset, 0.0f}; - m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id] - plate_offset); - } - - //QDS: some layer may only has G3/G3, update right layer height - if (m_detect_layer_based_on_tag && !m_result.spiral_vase_layers.empty()) { - if (delta_pos[Z] >= 0.0 && type == EMoveType::Extrude && m_result.spiral_vase_layers.back().first == FLT_MAX) { - // replace layer height placeholder with correct value - m_result.spiral_vase_layers.back().first = static_cast(m_end_position[Z]); - } - if (!m_result.moves.empty()) - m_result.spiral_vase_layers.back().second.second = m_result.moves.size() - 1 - m_seams_count; + m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[filament_id] - plate_offset); } if (m_detect_layer_based_on_tag && !m_result.spiral_vase_layers.empty()) { @@ -3301,10 +4051,392 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) store_move_vertex(type); } +void GCodeProcessor::process_VG1(const GCodeReader::GCodeLine& line) +{ + int filament_id = get_filament_id(); + int last_filament_id = get_last_filament_id(); + float filament_diameter = (static_cast(filament_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[filament_id] : m_result.filament_diameters.back(); + float filament_radius = 0.5f * filament_diameter; + float area_filament_cross_section = static_cast(M_PI) * sqr(filament_radius); + + auto absolute_position = [this, area_filament_cross_section](Axis axis, const GCodeReader::GCodeLine& lineG1) { + bool is_relative = (m_global_positioning_type == EPositioningType::Relative); + if (axis == E) + is_relative |= (m_e_local_positioning_type == EPositioningType::Relative); + + if (lineG1.has(Slic3r::Axis(axis))) { + float lengthsScaleFactor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; + float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor; + return is_relative ? m_start_position[axis] + ret : m_origin[axis] + ret; + } + else + return m_start_position[axis]; + }; + + auto move_type = [this](const AxisCoords& delta_pos) { + EMoveType type = EMoveType::Noop; + + if (m_wiping) + type = EMoveType::Wipe; + else if (delta_pos[E] < 0.0f) + type = (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) ? EMoveType::Travel : EMoveType::Retract; + else if (delta_pos[E] > 0.0f) { + if (delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f) + type = (delta_pos[Z] == 0.0f) ? EMoveType::Unretract : EMoveType::Travel; + else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f) + type = EMoveType::Extrude; + } + else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) + type = EMoveType::Travel; + + return type; + }; + + ++m_g1_line_id; + + // enable processing of lines M201/M203/M204/M205 + m_time_processor.machine_envelope_processing_enabled = true; + + // updates axes positions from line + for (unsigned char a = X; a <= E; ++a) { + m_end_position[a] = absolute_position((Axis)a, line); + } + + // updates feedrate from line, if present + if (line.has_f()) + m_feedrate = line.f() * MMMIN_TO_MMSEC; + + // calculates movement deltas + float max_abs_delta = 0.0f; + AxisCoords delta_pos; + for (unsigned char a = X; a <= E; ++a) { + delta_pos[a] = m_end_position[a] - m_start_position[a]; + max_abs_delta = std::max(max_abs_delta, std::abs(delta_pos[a])); + } + + // no displacement, return + if (max_abs_delta == 0.0f) + return; + + EMoveType type = move_type(delta_pos); + if (type == EMoveType::Extrude) { + float delta_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z])); + float volume_extruded_filament = area_filament_cross_section * delta_pos[E]; + float area_toolpath_cross_section = volume_extruded_filament / delta_xyz; + + if(m_extrusion_role == ExtrusionRole::erSupportMaterial || m_extrusion_role == ExtrusionRole::erSupportMaterialInterface || m_extrusion_role ==ExtrusionRole::erSupportTransition) + m_used_filaments.increase_support_caches(volume_extruded_filament); + else if (m_extrusion_role==ExtrusionRole::erWipeTower) { + m_used_filaments.increase_wipe_tower_caches(volume_extruded_filament); + } + else { + // save extruded volume to the cache + m_used_filaments.increase_model_caches(volume_extruded_filament); + } + // volume extruded filament / tool displacement = area toolpath cross section + m_mm3_per_mm = area_toolpath_cross_section; +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_mm3_per_mm_compare.update(area_toolpath_cross_section, m_extrusion_role); +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + + if (m_forced_height > 0.0f) + m_height = m_forced_height; + else { + if (m_end_position[Z] > m_extruded_last_z + EPSILON) + m_height = m_end_position[Z] - m_extruded_last_z; + } + + if (m_height == 0.0f) + m_height = DEFAULT_TOOLPATH_HEIGHT; + + if (m_end_position[Z] == 0.0f) + m_end_position[Z] = m_height; + + m_extruded_last_z = m_end_position[Z]; + m_options_z_corrector.update(m_height); + +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_height_compare.update(m_height, m_extrusion_role); +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + + if (m_forced_width > 0.0f) + m_width = m_forced_width; + else if (m_extrusion_role == erExternalPerimeter) + // cross section: rectangle + m_width = delta_pos[E] * static_cast(M_PI * sqr(1.05f * filament_radius)) / (delta_xyz * m_height); + else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erNone) + // cross section: circle + m_width = static_cast(m_result.filament_diameters[filament_id]) * std::sqrt(delta_pos[E] / delta_xyz); + else + // cross section: rectangle + 2 semicircles + m_width = delta_pos[E] * static_cast(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast(1.0 - 0.25 * M_PI) * m_height; + + if (m_width == 0.0f) + m_width = DEFAULT_TOOLPATH_WIDTH; + + // clamp width to avoid artifacts which may arise from wrong values of m_height + m_width = std::min(m_width, std::max(2.0f, 4.0f * m_height)); + +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_width_compare.update(m_width, m_extrusion_role); +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + } + else if (EMoveType::Unretract == type && m_virtual_flushing) { + int extruder_id = get_extruder_id(); + float volume_flushed_filament = area_filament_cross_section * delta_pos[E]; + if (m_remaining_volume[extruder_id] > volume_flushed_filament) + { + m_used_filaments.update_flush_per_filament(last_filament_id, volume_flushed_filament); + m_remaining_volume[extruder_id] -= volume_flushed_filament; + } + else { + m_used_filaments.update_flush_per_filament(last_filament_id, m_remaining_volume[extruder_id]); + m_used_filaments.update_flush_per_filament(filament_id, volume_flushed_filament - m_remaining_volume[extruder_id]); + m_remaining_volume[extruder_id] = 0.f; + } + } + + if (line.has_f()) + m_feedrate = line.f() * MMMIN_TO_MMSEC; + + // time estimate section + auto move_length = [](const AxisCoords& delta_pos) { + float sq_xyz_length = sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]); + return (sq_xyz_length > 0.0f) ? std::sqrt(sq_xyz_length) : std::abs(delta_pos[E]); + }; + + auto is_extrusion_only_move = [](const AxisCoords& delta_pos) { + return delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f && delta_pos[Z] == 0.0f && delta_pos[E] != 0.0f; + }; + + float distance = move_length(delta_pos); + assert(distance != 0.0f); + float inv_distance = 1.0f / distance; + + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + TimeMachine& machine = m_time_processor.machines[i]; + if (!machine.enabled) + continue; + + TimeMachine::State& curr = machine.curr; + TimeMachine::State& prev = machine.prev; + std::vector& blocks = machine.blocks; + + curr.feedrate = (delta_pos[E] == 0.0f) ? + minimum_travel_feedrate(static_cast(i), m_feedrate) : + minimum_feedrate(static_cast(i), m_feedrate); + + //QDS: calculeta enter and exit direction + curr.enter_direction = { static_cast(delta_pos[X]), static_cast(delta_pos[Y]), static_cast(delta_pos[Z]) }; + float norm = curr.enter_direction.norm(); + if (!is_extrusion_only_move(delta_pos)) + curr.enter_direction = curr.enter_direction / norm; + curr.exit_direction = curr.enter_direction; + + TimeBlock block; + block.move_type = type; + block.skippable_type = m_skippable_type; + //QDS: don't calculate travel time into extrusion path, except travel inside start and end gcode. + block.role = (type != EMoveType::Travel || m_extrusion_role == erCustom) ? m_extrusion_role : erNone; + block.distance = distance; + block.move_id = m_result.moves.size(); + block.g1_line_id = m_g1_line_id; + block.layer_id = std::max(1, m_layer_id); + block.flags.prepare_stage = m_processing_start_custom_gcode; + + //QDS: limite the cruise according to centripetal acceleration + //Only need to handle when both prev and curr segment has movement in x-y plane + if ((prev.exit_direction(0) != 0.0f || prev.exit_direction(1) != 0.0f) && + (curr.enter_direction(0) != 0.0f || curr.enter_direction(1) != 0.0f)) { + Vec3f v1 = prev.exit_direction; + v1(2, 0) = 0.0f; + v1.normalize(); + Vec3f v2 = curr.enter_direction; + v2(2, 0) = 0.0f; + v2.normalize(); + float norm_diff = (v2 - v1).norm(); + //QDS: don't need to consider limitation of centripetal acceleration + //when angle changing is larger than 28.96 degree or two lines are almost collinear. + //Attention!!! these two value must be same with MC side. + if (norm_diff < 0.5f && norm_diff > 0.00001f) { + //QDS: calculate angle + float dot = v1(0) * v2(0) + v1(1) * v2(1); + float cross = v1(0) * v2(1) - v1(1) * v2(0); + float angle = float(atan2(double(cross), double(dot))); + float sin_theta_2 = sqrt((1.0f - cos(angle)) * 0.5f); + float r = sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y])) * 0.5 / sin_theta_2; + float acc = get_acceleration(static_cast(i)); + curr.feedrate = std::min(curr.feedrate, sqrt(acc * r)); + } + } + + // calculates block cruise feedrate + float min_feedrate_factor = 1.0f; + for (unsigned char a = X; a <= E; ++a) { + curr.axis_feedrate[a] = curr.feedrate * delta_pos[a] * inv_distance; + if (a == E) + curr.axis_feedrate[a] *= machine.extrude_factor_override_percentage; + + curr.abs_axis_feedrate[a] = std::abs(curr.axis_feedrate[a]); + if (curr.abs_axis_feedrate[a] != 0.0f) { + float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a), m_extruder_id); + if (axis_max_feedrate != 0.0f) min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]); + } + } + //QDS: update curr.feedrate + curr.feedrate *= min_feedrate_factor; + block.feedrate_profile.cruise = curr.feedrate; + + if (min_feedrate_factor < 1.0f) { + for (unsigned char a = X; a <= E; ++a) { + curr.axis_feedrate[a] *= min_feedrate_factor; + curr.abs_axis_feedrate[a] *= min_feedrate_factor; + } + } + + // calculates block acceleration + float acceleration = + (type == EMoveType::Travel) ? get_travel_acceleration(static_cast(i)) : + (is_extrusion_only_move(delta_pos) ? + get_retract_acceleration(static_cast(i)) : + get_acceleration(static_cast(i))); + + //QDS + for (unsigned char a = X; a <= E; ++a) { + float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a), m_extruder_id); + if (acceleration * std::abs(delta_pos[a]) * inv_distance > axis_max_acceleration) + acceleration = axis_max_acceleration / (std::abs(delta_pos[a]) * inv_distance); + } + + block.acceleration = acceleration; + + // calculates block exit feedrate + curr.safe_feedrate = block.feedrate_profile.cruise; + + for (unsigned char a = X; a <= E; ++a) { + float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); + if (curr.abs_axis_feedrate[a] > axis_max_jerk) + curr.safe_feedrate = std::min(curr.safe_feedrate, axis_max_jerk); + } + + block.feedrate_profile.exit = curr.safe_feedrate; + + static const float PREVIOUS_FEEDRATE_THRESHOLD = 0.0001f; + + // calculates block entry feedrate + float vmax_junction = curr.safe_feedrate; + if (!blocks.empty() && prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD) { + bool prev_speed_larger = prev.feedrate > block.feedrate_profile.cruise; + float smaller_speed_factor = prev_speed_larger ? (block.feedrate_profile.cruise / prev.feedrate) : (prev.feedrate / block.feedrate_profile.cruise); + // Pick the smaller of the nominal speeds. Higher speed shall not be achieved at the junction during coasting. + vmax_junction = prev_speed_larger ? block.feedrate_profile.cruise : prev.feedrate; + + float v_factor = 1.0f; + bool limited = false; + + for (unsigned char a = X; a <= E; ++a) { + // Limit an axis. We have to differentiate coasting from the reversal of an axis movement, or a full stop. + if (a == X) { + Vec3f exit_v = prev.feedrate * (prev.exit_direction); + if (prev_speed_larger) + exit_v *= smaller_speed_factor; + Vec3f entry_v = block.feedrate_profile.cruise * (curr.enter_direction); + Vec3f jerk_v = entry_v - exit_v; + jerk_v = Vec3f(abs(jerk_v.x()), abs(jerk_v.y()), abs(jerk_v.z())); + Vec3f max_xyz_jerk_v = get_xyz_max_jerk(static_cast(i)); + + for (size_t i = 0; i < 3; i++) + { + if (jerk_v[i] > max_xyz_jerk_v[i]) { + v_factor *= max_xyz_jerk_v[i] / jerk_v[i]; + jerk_v *= v_factor; + limited = true; + } + } + } + else if (a == Y || a == Z) { + continue; + } + else { + float v_exit = prev.axis_feedrate[a]; + float v_entry = curr.axis_feedrate[a]; + + if (prev_speed_larger) + v_exit *= smaller_speed_factor; + + if (limited) { + v_exit *= v_factor; + v_entry *= v_factor; + } + + // Calculate the jerk depending on whether the axis is coasting in the same direction or reversing a direction. + float jerk = + (v_exit > v_entry) ? + (((v_entry > 0.0f) || (v_exit < 0.0f)) ? + // coasting + (v_exit - v_entry) : + // axis reversal + std::max(v_exit, -v_entry)) : + // v_exit <= v_entry + (((v_entry < 0.0f) || (v_exit > 0.0f)) ? + // coasting + (v_entry - v_exit) : + // axis reversal + std::max(-v_exit, v_entry)); + + + float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); + if (jerk > axis_max_jerk) { + v_factor *= axis_max_jerk / jerk; + limited = true; + } + } + } + + if (limited) + vmax_junction *= v_factor; + + // Now the transition velocity is known, which maximizes the shared exit / entry velocity while + // respecting the jerk factors, it may be possible, that applying separate safe exit / entry velocities will achieve faster prints. + float vmax_junction_threshold = vmax_junction * 0.99f; + + // Not coasting. The machine will stop and start the movements anyway, better to start the segment from start. + if (prev.safe_feedrate > vmax_junction_threshold && curr.safe_feedrate > vmax_junction_threshold) + vmax_junction = curr.safe_feedrate; + } + + float v_allowable = max_allowable_speed(-acceleration, curr.safe_feedrate, block.distance); + block.feedrate_profile.entry = std::min(vmax_junction, v_allowable); + + block.max_entry_speed = vmax_junction; + block.flags.nominal_length = (block.feedrate_profile.cruise <= v_allowable); + block.flags.recalculate = true; + block.safe_feedrate = curr.safe_feedrate; + + // calculates block trapezoid + block.calculate_trapezoid(); + + // updates previous + prev = curr; + + blocks.push_back(block); + + if (blocks.size() > TimeProcessor::Planner::refresh_threshold) { + machine.calculate_time(TimeProcessor::Planner::queue_size, 0, erNone, [&result=m_result, i,&machine](const TimeBlock& block, int time) { + machine.handle_time_block(block,time,i,result); + }); + } + } + + // store move + store_move_vertex(type); +} + // QDS: this function is absolutely new for G2 and G3 gcode void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line) { - float filament_diameter = (static_cast(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back(); + int filament_id = get_filament_id(); + float filament_diameter = (static_cast(filament_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[filament_id] : m_result.filament_diameters.back(); float filament_radius = 0.5f * filament_diameter; float area_filament_cross_section = static_cast(M_PI) * sqr(filament_radius); auto absolute_position = [this, area_filament_cross_section](Axis axis, const GCodeReader::GCodeLine& lineG2_3) { @@ -3465,7 +4597,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line) m_width = delta_pos[E] * static_cast(M_PI * sqr(1.05f * filament_radius)) / (delta_xyz * m_height); else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erNone) //QDS: cross section: circle - m_width = static_cast(m_result.filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / delta_xyz); + m_width = static_cast(m_result.filament_diameters[filament_id]) * std::sqrt(delta_pos[E] / delta_xyz); else //QDS: cross section: rectangle + 2 semicircles m_width = delta_pos[E] * static_cast(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast(1.0 - 0.25 * M_PI) * m_height; @@ -3505,9 +4637,11 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line) TimeBlock block; block.move_type = type; + block.skippable_type = m_skippable_type; //QDS: don't calculate travel time into extrusion path, except travel inside start and end gcode. block.role = (type != EMoveType::Travel || m_extrusion_role == erCustom) ? m_extrusion_role : erNone; block.distance = delta_xyz; + block.move_id = m_result.moves.size(); block.g1_line_id = m_g1_line_id; block.layer_id = std::max(1, m_layer_id); block.flags.prepare_stage = m_processing_start_custom_gcode; @@ -3531,7 +4665,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line) curr.abs_axis_feedrate[a] = std::abs(curr.axis_feedrate[a]); if (curr.abs_axis_feedrate[a] != 0.0f) { - float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a)); + float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a), m_extruder_id); if (axis_max_feedrate != 0.0f) min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]); } } @@ -3558,7 +4692,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line) axis_acc[a] = acceleration * std::abs(delta_pos[a]) * inv_distance; if (axis_acc[a] != 0.0f) { - float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a)); + float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a), m_extruder_id); if (axis_max_acceleration != 0.0f && axis_acc[a] > axis_max_acceleration) min_acc_factor = std::min(min_acc_factor, axis_max_acceleration / axis_acc[a]); } } @@ -3671,7 +4805,9 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line) blocks.push_back(block); if (blocks.size() > TimeProcessor::Planner::refresh_threshold) { - machine.calculate_time(TimeProcessor::Planner::queue_size, 0, erNone); + machine.calculate_time(TimeProcessor::Planner::queue_size, 0, erNone, [&result=m_result, i,&machine](const TimeBlock& block, int time) { + machine.handle_time_block(block,time,i,result); + }); } } @@ -3681,11 +4817,11 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line) if (m_seams_detector.is_active()) { //QDS: check for seam starting vertex if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter && !m_seams_detector.has_first_vertex()) { - m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id] - plate_offset); + m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[get_filament_id()] - plate_offset); } else if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter && m_detect_layer_based_on_tag) { const Vec3f real_last_pos = Vec3f(m_result.moves.back().position.x() - m_x_offset, m_result.moves.back().position.y() - m_y_offset, m_result.moves.back().position.z()); - const Vec3f new_pos = real_last_pos - m_extruder_offsets[m_extruder_id]; + const Vec3f new_pos = real_last_pos - m_extruder_offsets[filament_id]; // We may have sloped loop, drop any previous start pos if we have z increment const std::optional first_vertex = m_seams_detector.get_first_vertex(); if (new_pos.z() > first_vertex->z()) { m_seams_detector.set_first_vertex(new_pos); } @@ -3696,7 +4832,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line) m_end_position[X] = pos.x(); m_end_position[Y] = pos.y(); m_end_position[Z] = pos.z(); }; const Vec3f curr_pos(m_end_position[X], m_end_position[Y], m_end_position[Z]); - const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id] - plate_offset; + const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[filament_id] - plate_offset; const std::optional first_vertex = m_seams_detector.get_first_vertex(); //QDS: the threshold value = 0.0625f == 0.25 * 0.25 is arbitrary, we may find some smarter condition later @@ -3711,7 +4847,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line) } else if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter) { m_seams_detector.activate(true); - m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id] - plate_offset); + m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[filament_id] - plate_offset); } //QDS: some layer may only has G3/G3, update right layer height @@ -3874,9 +5010,15 @@ void GCodeProcessor::process_M83(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M104(const GCodeReader::GCodeLine& line) { + int filament_id = get_filament_id(); float new_temp; if (line.has_value('S', new_temp)) - m_extruder_temps[m_extruder_id] = new_temp; + m_extruder_temps[filament_id] = new_temp; +} + +void GCodeProcessor::process_VM104(const GCodeReader::GCodeLine& line) +{ + process_M104(line); } void GCodeProcessor::process_M106(const GCodeReader::GCodeLine& line) @@ -3914,6 +5056,7 @@ void GCodeProcessor::process_M108(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M109(const GCodeReader::GCodeLine& line) { + int filament_id = get_filament_id(); float new_temp; if (line.has_value('R', new_temp)) { float val; @@ -3923,10 +5066,15 @@ void GCodeProcessor::process_M109(const GCodeReader::GCodeLine& line) m_extruder_temps[eid] = new_temp; } else - m_extruder_temps[m_extruder_id] = new_temp; + m_extruder_temps[filament_id] = new_temp; } else if (line.has_value('S', new_temp)) - m_extruder_temps[m_extruder_id] = new_temp; + m_extruder_temps[filament_id] = new_temp; +} + +void GCodeProcessor::process_VM109(const GCodeReader::GCodeLine& line) +{ + process_M109(line); } void GCodeProcessor::process_M132(const GCodeReader::GCodeLine& line) @@ -3989,21 +5137,18 @@ void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line) { // see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration float factor = ((m_flavor != gcfRepRapSprinter && m_flavor != gcfRepRapFirmware) && m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; + int indx_limit = m_time_processor.machine_limits.machine_max_acceleration_x.size() / 2; + for (size_t index = 0; index < indx_limit; index += 2) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || m_time_processor.machine_envelope_processing_enabled) { + if (line.has_x()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, index + i, line.x() * factor); - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || - m_time_processor.machine_envelope_processing_enabled) { - if (line.has_x()) - set_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, i, line.x() * factor); + if (line.has_y()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, index + i, line.y() * factor); - if (line.has_y()) - set_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, i, line.y() * factor); + if (line.has_z()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, index + i, line.z() * factor); - if (line.has_z()) - set_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, i, line.z() * factor); - - if (line.has_e()) - set_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, i, line.e() * factor); + if (line.has_e()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, index + i, line.e() * factor); + } } } } @@ -4018,20 +5163,23 @@ void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line) // http://smoothieware.org/supported-g-codes float factor = (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfSmoothie || m_flavor == gcfKlipper) ? 1.0f : MMMIN_TO_MMSEC; - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || - m_time_processor.machine_envelope_processing_enabled) { - if (line.has_x()) - set_option_value(m_time_processor.machine_limits.machine_max_speed_x, i, line.x() * factor); + //QDS: + int indx_limit = m_time_processor.machine_limits.machine_max_speed_x.size() / 2; + for (size_t index = 0; index < indx_limit; index += 2) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || m_time_processor.machine_envelope_processing_enabled) { + if (line.has_x()) + set_option_value(m_time_processor.machine_limits.machine_max_speed_x, index + i, line.x() * factor); - if (line.has_y()) - set_option_value(m_time_processor.machine_limits.machine_max_speed_y, i, line.y() * factor); + if (line.has_y()) + set_option_value(m_time_processor.machine_limits.machine_max_speed_y, index + i, line.y() * factor); - if (line.has_z()) - set_option_value(m_time_processor.machine_limits.machine_max_speed_z, i, line.z() * factor); + if (line.has_z()) + set_option_value(m_time_processor.machine_limits.machine_max_speed_z, index + i, line.z() * factor); - if (line.has_e()) - set_option_value(m_time_processor.machine_limits.machine_max_speed_e, i, line.e() * factor); + if (line.has_e()) + set_option_value(m_time_processor.machine_limits.machine_max_speed_e, index + i, line.e() * factor); + } } } } @@ -4227,22 +5375,60 @@ void GCodeProcessor::process_M566(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M702(const GCodeReader::GCodeLine& line) { + int filament_id = get_filament_id(); if (line.has('C')) { // MK3 MMU2 specific M code: // M702 C is expected to be sent by the custom end G-code when finalizing a print. // The MK3 unit shall unload and park the active filament into the MMU2 unit. m_time_processor.extruder_unloaded = true; - simulate_st_synchronize(get_filament_unload_time(m_extruder_id)); + simulate_st_synchronize(get_filament_unload_time(filament_id)); } } + +void GCodeProcessor::process_SYNC(const GCodeReader::GCodeLine& line) +{ + float time = 0; + if (line.has_value('T', time) ) { + simulate_st_synchronize(time); + } +} + + void GCodeProcessor::process_T(const GCodeReader::GCodeLine& line) { process_T(line.cmd()); } +void GCodeProcessor::process_M1020(const GCodeReader::GCodeLine &line) +{ + int curr_filament_id = get_filament_id(false); + int curr_extruder_id = get_extruder_id(false); + if (line.raw().length() > 5) { + std::string filament_id_str = line.raw().substr(7); + if (filament_id_str.empty()) + return; + + int eid = 0; + eid = std::stoi(filament_id_str); + if (eid < 0 || eid > 254) { + // M1020-1 is a valid gcode line for RepRap Firmwares (used to deselects all tools) + if ((m_flavor != gcfRepRapFirmware && m_flavor != gcfRepRapSprinter) || eid != -1) + BOOST_LOG_TRIVIAL(error) << "Invalid M1020 command (" << line.raw() << ")."; + } + else { + if (eid >= m_result.filaments_count) + BOOST_LOG_TRIVIAL(error) << "Invalid M1020 command (" << line.raw() << ")."; + process_filament_change(eid); + } + } +} + void GCodeProcessor::process_T(const std::string_view command) { + int curr_filament_id = get_filament_id(false); + int curr_extruder_id = get_extruder_id(false); + //TODO: multi switch if (command.length() > 1) { int eid = 0; if (! parse_number(command.substr(1), eid) || eid < 0 || eid > 254) { @@ -4254,57 +5440,108 @@ void GCodeProcessor::process_T(const std::string_view command) // T-1 is a valid gcode line for RepRap Firmwares (used to deselects all tools) if ((m_flavor != gcfRepRapFirmware && m_flavor != gcfRepRapSprinter) || eid != -1) BOOST_LOG_TRIVIAL(error) << "Invalid T command (" << command << ")."; - } else { - unsigned char id = static_cast(eid); - if (m_extruder_id != id) { - if (id >= m_result.extruders_count) - BOOST_LOG_TRIVIAL(error) << "Invalid T command (" << command << ")."; - else { - m_last_extruder_id = m_extruder_id; - process_filaments(CustomGCode::ToolChange); - m_extruder_id = id; - m_cp_color.current = m_extruder_colors[id]; - //QDS: increase filament change times - m_result.lock(); - m_result.print_statistics.total_filamentchanges++; - m_result.unlock(); - - // Specific to the MK3 MMU2: - // The initial value of extruder_unloaded is set to true indicating - // that the filament is parked in the MMU2 unit and there is nothing to be unloaded yet. - float extra_time = get_filament_unload_time(static_cast(m_last_extruder_id)); - m_time_processor.extruder_unloaded = false; - extra_time += get_filament_load_time(static_cast(m_extruder_id)); - // store tool change move - store_move_vertex(EMoveType::Tool_change); - // construct a new time block to handle filament change - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - TimeMachine& machine = m_time_processor.machines[i]; - if (!machine.enabled) - continue; - TimeBlock block; - block.role = erFlush; - block.move_type = EMoveType::Tool_change; - block.layer_id = std::max(1, m_layer_id); - block.g1_line_id = m_g1_line_id; - block.flags.prepare_stage = m_processing_start_custom_gcode; - block.distance = 0; - block.calculate_trapezoid(); - - // when do st_sync, we will clear all of the blocks without keeping last n blocks, so we can directly add the new block into the blocks - machine.blocks.push_back(block); - } - - simulate_st_synchronize(extra_time, erFlush); - - } - } + } + else { + if (eid >= m_result.filaments_count) + BOOST_LOG_TRIVIAL(error) << "Invalid T command (" << command << ")."; + process_filament_change(eid); } } } + +void GCodeProcessor::process_filament_change(int id) +{ + assert(id < m_result.filaments_count); + int prev_extruder_id = get_extruder_id(false); + int prev_filament_id = get_filament_id(false); + int next_extruder_id = m_filament_maps[id]; + int next_filament_id = id; + float extra_time = 0; + + if (prev_filament_id == next_filament_id) + return; + + if (prev_extruder_id != -1) + m_last_filament_id[prev_extruder_id] = prev_filament_id; + + if (prev_extruder_id == next_extruder_id) { + // don't need extruder change + assert(prev_extruder_id != -1); + process_filaments(CustomGCode::ToolChange); + m_filament_id[next_extruder_id] = next_filament_id; + m_result.lock(); + m_result.print_statistics.total_filament_changes += 1; + m_result.unlock(); + extra_time += get_filament_unload_time(static_cast(prev_filament_id)); + m_time_processor.extruder_unloaded = false; + extra_time += get_filament_load_time(static_cast(next_filament_id)); + } + else { + if (prev_extruder_id == -1) { + // initialize + m_extruder_id = next_extruder_id; + m_filament_id[next_extruder_id] = next_filament_id; + m_time_processor.extruder_unloaded = false; + extra_time += get_filament_load_time(static_cast(next_filament_id)); + } + else { + //first process cache generated by last extruder + process_filaments(CustomGCode::ToolChange); + //switch to current extruder + m_extruder_id = next_extruder_id; + if (m_last_filament_id[next_extruder_id] == (unsigned char)(-1)) { + //no filament in current extruder + m_filament_id[next_extruder_id] = next_filament_id; + m_time_processor.extruder_unloaded = false; + extra_time += get_filament_load_time(static_cast(next_filament_id)); + } + else if (m_last_filament_id[next_extruder_id] != next_filament_id) { + //need to change filament + m_filament_id[next_extruder_id] = next_filament_id; + m_result.lock(); + m_result.print_statistics.total_filament_changes += 1; + m_result.unlock(); + extra_time += get_filament_unload_time(static_cast(prev_filament_id)); + m_time_processor.extruder_unloaded = false; + extra_time += get_filament_load_time(static_cast(next_filament_id)); + } + m_result.lock(); + m_result.print_statistics.total_extruder_changes++; + m_result.unlock(); + extra_time += get_extruder_change_time(next_extruder_id); + } + } + m_cp_color.current = m_extruder_colors[next_filament_id]; + // store tool change move + store_move_vertex(EMoveType::Tool_change); + + // construct a new time block to handle filament change + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + TimeMachine& machine = m_time_processor.machines[i]; + if (!machine.enabled) + continue; + TimeBlock block; + block.skippable_type = m_skippable_type; + block.move_id = m_result.moves.size() - 1; + block.role = erFlush; + block.move_type = EMoveType::Tool_change; + block.layer_id = std::max(1, m_layer_id); + block.g1_line_id = m_g1_line_id; + block.flags.prepare_stage = m_processing_start_custom_gcode; + block.distance = 0; + block.calculate_trapezoid(); + + // when do st_sync, we will clear all of the blocks without keeping last n blocks, so we can directly add the new block into the blocks + machine.blocks.push_back(block); + } + + simulate_st_synchronize(extra_time, erFlush); +} + void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type) { + int filament_id = get_filament_id(); m_last_line_id = (type == EMoveType::Color_change || type == EMoveType::Pause_Print || type == EMoveType::Custom_GCode) ? m_line_id + 1 : ((type == EMoveType::Seam) ? m_last_line_id : m_line_id); @@ -4317,30 +5554,32 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type) Vec3f(m_interpolation_points[i].x() + m_x_offset, m_interpolation_points[i].y() + m_y_offset, m_processing_start_custom_gcode ? m_first_layer_height : m_interpolation_points[i].z()) + - m_extruder_offsets[m_extruder_id]; + m_extruder_offsets[filament_id]; } m_result.moves.push_back({ - m_last_line_id, type, m_extrusion_role, - m_extruder_id, + //QDS: add arc move related data + path_type, + static_cast(filament_id), m_cp_color.current, - //QDS: add plate's offset to the rendering vertices - Vec3f(m_end_position[X] + m_x_offset, m_end_position[Y] + m_y_offset, m_processing_start_custom_gcode ? m_first_layer_height : m_end_position[Z]) + m_extruder_offsets[m_extruder_id], + m_last_line_id, static_cast(m_end_position[E] - m_start_position[E]), m_feedrate, m_width, m_height, m_mm3_per_mm, m_fan_speed, - m_extruder_temps[m_extruder_id], - static_cast(m_result.moves.size()), + m_extruder_temps[filament_id], static_cast(m_layer_id), //layer_duration: set later - //QDS: add arc move related data - path_type, - Vec3f(m_arc_center(0, 0) + m_x_offset, m_arc_center(1, 0) + m_y_offset, m_arc_center(2, 0)) + m_extruder_offsets[m_extruder_id], + {0.f,0.f}, // prefix sum of move time to this move : set later + //QDS: add plate's offset to the rendering vertices + Vec3f(m_end_position[X] + m_x_offset, m_end_position[Y] + m_y_offset, m_processing_start_custom_gcode ? m_first_layer_height : m_end_position[Z]) + m_extruder_offsets[filament_id], + Vec3f(m_arc_center(0, 0) + m_x_offset, m_arc_center(1, 0) + m_y_offset, m_arc_center(2, 0)) + m_extruder_offsets[filament_id], m_interpolation_points, + m_object_label_id, + m_print_z }); if (type == EMoveType::Seam) { @@ -4362,7 +5601,26 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type) void GCodeProcessor::set_extrusion_role(ExtrusionRole role) { m_used_filaments.process_role_cache(this); - m_extrusion_role = role; + if (role == erFloatingVerticalShell) { + m_extrusion_role = erSolidInfill; + } + else { + m_extrusion_role = role; + } +} + +void GCodeProcessor::set_skippable_type(const std::string_view type) +{ + if (!m_skippable){ + m_skippable_type = SkipType::stNone; + return; + } + auto iter = skip_type_map.find(type); + if(iter!=skip_type_map.end()) { + m_skippable_type = iter->second; + } else { + m_skippable_type = SkipType::stOther; + } } float GCodeProcessor::minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const @@ -4381,26 +5639,28 @@ float GCodeProcessor::minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMod return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_travel_rate, static_cast(mode))); } -float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const +float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis, int extruder_id) const { + int matched_pos = extruder_id * 2; switch (axis) { - case X: { return get_option_value(m_time_processor.machine_limits.machine_max_speed_x, static_cast(mode)); } - case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_speed_y, static_cast(mode)); } - case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_speed_z, static_cast(mode)); } - case E: { return get_option_value(m_time_processor.machine_limits.machine_max_speed_e, static_cast(mode)); } + case X: { return get_option_value(m_time_processor.machine_limits.machine_max_speed_x, matched_pos + static_cast(mode)); } + case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_speed_y, matched_pos + static_cast(mode)); } + case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_speed_z, matched_pos + static_cast(mode)); } + case E: { return get_option_value(m_time_processor.machine_limits.machine_max_speed_e, matched_pos + static_cast(mode)); } default: { return 0.0f; } } } -float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const +float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis, int extruder_id) const { + int matched_pos = extruder_id * 2; switch (axis) { - case X: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, static_cast(mode)); } - case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, static_cast(mode)); } - case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, static_cast(mode)); } - case E: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, static_cast(mode)); } + case X: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, matched_pos + static_cast(mode)); } + case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, matched_pos + static_cast(mode)); } + case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, matched_pos + static_cast(mode)); } + case E: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, matched_pos + static_cast(mode)); } default: { return 0.0f; } } } @@ -4484,6 +5744,12 @@ float GCodeProcessor::get_filament_unload_time(size_t extruder_id) return m_time_processor.extruder_unloaded ? 0.0f : m_time_processor.filament_unload_times; } +float GCodeProcessor::get_extruder_change_time(size_t extruder_id) +{ + //TODO: all extruder has the same value ? + return m_time_processor.extruder_change_times; +} + //QDS int GCodeProcessor::get_filament_vitrification_temperature(size_t extrude_id) { @@ -4504,7 +5770,9 @@ void GCodeProcessor::process_custom_gcode_time(CustomGCode::Type code) gcode_time.needed = true; //FIXME this simulates st_synchronize! is it correct? // The estimated time may be longer than the real print time. - machine.simulate_st_synchronize(0, erNone); + machine.simulate_st_synchronize(0, erNone, [&result=m_result, i,&machine](const TimeBlock& block, int time) { + machine.handle_time_block(block,time,i,result); + }); if (gcode_time.cache != 0.0f) { gcode_time.times.push_back({ code, gcode_time.cache }); gcode_time.cache = 0.0f; @@ -4522,7 +5790,8 @@ void GCodeProcessor::process_filaments(CustomGCode::Type code) m_used_filaments.process_support_cache(this); m_used_filaments.process_total_volume_cache(this); //QDS: reset remaining filament - m_remaining_volume = m_nozzle_volume; + size_t last_extruder_id = get_extruder_id(); + m_remaining_volume[last_extruder_id] = m_nozzle_volume[last_extruder_id]; } } @@ -4533,7 +5802,9 @@ void GCodeProcessor::simulate_st_synchronize(float additional_time, ExtrusionRol if (!machine.enabled) continue; - machine.simulate_st_synchronize(additional_time, target_role); + machine.simulate_st_synchronize(additional_time, target_role, [&result=m_result, i,&machine](const TimeBlock& block, int time) { + machine.handle_time_block(block,time,i,result); + }); } } @@ -4556,12 +5827,12 @@ void GCodeProcessor::update_estimated_times_stats() m_result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].reset(); m_result.print_statistics.volumes_per_color_change = m_used_filaments.volumes_per_color_change; - m_result.print_statistics.model_volumes_per_extruder = m_used_filaments.model_volumes_per_extruder; - m_result.print_statistics.wipe_tower_volumes_per_extruder = m_used_filaments.wipe_tower_volumes_per_extruder; - m_result.print_statistics.support_volumes_per_extruder = m_used_filaments.support_volumes_per_extruder; + m_result.print_statistics.model_volumes_per_extruder = m_used_filaments.model_volumes_per_filament; + m_result.print_statistics.wipe_tower_volumes_per_extruder = m_used_filaments.wipe_tower_volumes_per_filament; + m_result.print_statistics.support_volumes_per_extruder = m_used_filaments.support_volumes_per_filament; m_result.print_statistics.flush_per_filament = m_used_filaments.flush_per_filament; m_result.print_statistics.used_filaments_per_role = m_used_filaments.filaments_per_role; - m_result.print_statistics.total_volumes_per_extruder = m_used_filaments.total_volumes_per_extruder; + m_result.print_statistics.total_volumes_per_extruder = m_used_filaments.total_volumes_per_filament; } //QDS: ugly code... @@ -4569,28 +5840,29 @@ void GCodeProcessor::update_slice_warnings() { m_result.warnings.clear(); - auto get_used_extruders = [this]() { - std::vector used_extruders; - used_extruders.reserve(m_used_filaments.total_volumes_per_extruder.size()); - for (auto item : m_used_filaments.total_volumes_per_extruder) { - used_extruders.push_back(item.first); + auto get_used_filaments = [this]() { + std::vector used_filaments; + used_filaments.reserve(m_used_filaments.total_volumes_per_filament.size()); + for (auto& item : m_used_filaments.total_volumes_per_filament) { + used_filaments.push_back(item.first); } - return used_extruders; + return used_filaments; }; - auto used_extruders = get_used_extruders(); - assert(!used_extruders.empty()); + auto used_filaments = get_used_filaments(); + assert(!used_filaments.empty()); GCodeProcessorResult::SliceWarning warning; warning.level = 1; if (m_highest_bed_temp != 0) { - for (size_t i = 0; i < used_extruders.size(); i++) { - int temperature = get_filament_vitrification_temperature(used_extruders[i]); + for (size_t i = 0; i < used_filaments.size(); i++) { + int temperature = get_filament_vitrification_temperature(used_filaments[i]); if (temperature != 0 && m_highest_bed_temp >= temperature) - warning.params.push_back(std::to_string(used_extruders[i])); + warning.params.push_back(std::to_string(used_filaments[i])); } } if (!warning.params.empty()) { + warning.level = 3; warning.msg = BED_TEMP_TOO_HIGH_THAN_FILAMENT; warning.error_code = "1000C001"; m_result.warnings.push_back(warning); @@ -4600,18 +5872,28 @@ void GCodeProcessor::update_slice_warnings() warning.params.clear(); warning.level=1; - int nozzle_hrc = Print::get_hrc_by_nozzle_type(m_result.nozzle_type); - if (nozzle_hrc!=0) { - for (size_t i = 0; i < used_extruders.size(); i++) { - int HRC=0; - if (used_extruders[i] < m_result.required_nozzle_HRC.size()) - HRC = m_result.required_nozzle_HRC[used_extruders[i]]; - if (HRC != 0 && (nozzle_hrcnozzle_hrc_lists(m_result.nozzle_type.size(), 0); + // store the nozzle hrc of each extruder + for (size_t idx = 0; idx < m_result.nozzle_type.size(); ++idx) + nozzle_hrc_lists[idx] = Print::get_hrc_by_nozzle_type(m_result.nozzle_type[idx]); + + for (size_t idx = 0; idx < used_filaments.size(); ++idx) { + int filament_hrc = 0; + + if (used_filaments[idx] < m_result.required_nozzle_HRC.size()) + filament_hrc = m_result.required_nozzle_HRC[used_filaments[idx]]; + + int filament_extruder_id = m_filament_maps[used_filaments[idx]]; + int extruder_hrc = nozzle_hrc_lists[filament_extruder_id]; + + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": Check HRC: filament:%1%, hrc=%2%, extruder:%3%, hrc:%4%") % used_filaments[idx] % filament_hrc % filament_extruder_id % extruder_hrc; + + if (extruder_hrc!=0 && extruder_hrc < filament_hrc) + warning.params.push_back(std::to_string(used_filaments[idx])); } if (!warning.params.empty()) { + warning.level = 3; warning.msg = NOZZLE_HRC_CHECKER; warning.error_code = "1000C002"; m_result.warnings.push_back(warning); @@ -4621,21 +5903,34 @@ void GCodeProcessor::update_slice_warnings() warning.params.clear(); warning.level = 1; if (!m_result.support_traditional_timelapse) { + warning.level = 2; warning.msg = NOT_SUPPORT_TRADITIONAL_TIMELAPSE; + warning.error_code = "10018003"; + m_result.warnings.push_back(warning); + + // Compatible with older version for A series + warning.level = 3; warning.error_code = "1000C003"; m_result.warnings.push_back(warning); } if (m_result.timelapse_warning_code != 0) { if (m_result.timelapse_warning_code & 1) { + warning.level = 1; warning.msg = NOT_GENERATE_TIMELAPSE; - warning.error_code = "1001C001"; + warning.error_code = "10014001"; m_result.warnings.push_back(warning); } - if ((m_result.timelapse_warning_code >> 1) & 1) { + warning.level = 1; warning.msg = NOT_GENERATE_TIMELAPSE; - warning.error_code = "1001C002"; + warning.error_code = "10014002"; + m_result.warnings.push_back(warning); + } + if ((m_result.timelapse_warning_code >> 2) & 1) { + warning.level = 2; + warning.msg = SMOOTH_TIMELAPSE_WITHOUT_PRIME_TOWER; + warning.error_code = "10018004"; m_result.warnings.push_back(warning); } } @@ -4643,5 +5938,322 @@ void GCodeProcessor::update_slice_warnings() m_result.warnings.shrink_to_fit(); } +int GCodeProcessor::get_filament_id(bool force_initialize)const +{ + int extruder_id = get_extruder_id(force_initialize); + if (extruder_id == -1) + return force_initialize ? 0 : -1; + + if (m_filament_id[extruder_id] == (unsigned char)(-1)) + return force_initialize ? 0 : -1; + + return static_cast(m_filament_id[extruder_id]); +} + +int GCodeProcessor::get_last_filament_id(bool force_initialize)const +{ + int extruder_id = get_extruder_id(force_initialize); + if (extruder_id == -1) + return force_initialize ? 0 : -1; + + if (m_last_filament_id[extruder_id] == (unsigned char)(-1)) + return force_initialize ? 0 : -1; + + return static_cast(m_last_filament_id[extruder_id]); +} + +int GCodeProcessor::get_extruder_id(bool force_initialize)const +{ + if (m_extruder_id == (unsigned char)(-1)) + return force_initialize ? 0 : -1; + return static_cast(m_extruder_id); +} + +void GCodeProcessor::PreCoolingInjector::process_pre_cooling_and_heating(TimeProcessor::InsertedLinesMap& inserted_operation_lines) +{ + auto get_nozzle_temp = [this](int filament_id,bool from_or_to) { + if (filament_id == -1) + return from_or_to ? 140 : 0; // default temp + return filament_nozzle_temps[filament_id]; + }; + + std::map> per_extruder_free_blocks; + + for (auto& block : m_extruder_free_blocks) + per_extruder_free_blocks[block.extruder_id].emplace_back(block); + + for (auto& elem : per_extruder_free_blocks) { + int extruder_id = elem.first; + auto& extruder_free_blcoks = elem.second; + for (auto iter = extruder_free_blcoks.begin(); iter != extruder_free_blcoks.end(); ++iter) { + bool is_end = std::next(iter) == extruder_free_blcoks.end(); + bool apply_pre_cooling = true; + bool apply_pre_heating = is_end ? false : true; + float curr_temp = get_nozzle_temp(iter->last_filament_id,true); + float target_temp = get_nozzle_temp(iter->next_filament_id, false); + inject_cooling_heating_command(inserted_operation_lines, *iter, curr_temp, target_temp, apply_pre_cooling, apply_pre_heating); + } + } +} + +void GCodeProcessor::PreCoolingInjector::build_extruder_free_blocks(const std::vector& filament_usage_blocks, const std::vector& extruder_usage_blocks) +{ + if (extruder_usage_blocks.size() <= 1) + build_by_filament_blocks(filament_usage_blocks); + else + build_by_extruder_blocks(extruder_usage_blocks); +} + +void GCodeProcessor::PreCoolingInjector::inject_cooling_heating_command(TimeProcessor::InsertedLinesMap& inserted_operation_lines, const ExtruderFreeBlock& block, float curr_temp, float target_temp, bool pre_cooling, bool pre_heating) +{ + auto format_line_M104 = [&physical_extruder_map = this->physical_extruder_map](int target_temp, int target_extruder = -1, const std::string& comment = std::string()) { + std::string buffer = "M104"; + if (target_extruder != -1) + buffer += (" T" + std::to_string(physical_extruder_map[target_extruder])); + buffer += " S" + std::to_string(target_temp) + " N0"; // N0 means the gcode is generated by slicer + if (!comment.empty()) + buffer += " ;" + comment; + buffer += '\n'; + return buffer; + }; + + auto is_pre_cooling_valid = [&nozzle_temps = this->filament_nozzle_temps, &pre_cooling_temps = this->filament_pre_cooling_temps](int idx) ->bool { + if(idx < 0) + return false; + return pre_cooling_temps[idx] > 0 && pre_cooling_temps[idx] < nozzle_temps[idx]; + }; + + auto get_partial_free_cooling_thres = [&nozzle_temps = this->filament_nozzle_temps, &pre_cooling_temps = this->filament_pre_cooling_temps](int idx) -> float{ + if(idx < 0) + return 30.f; + return nozzle_temps[idx] - (float)(pre_cooling_temps[idx]); + }; + + auto gcode_move_comp = [](const GCodeProcessorResult::MoveVertex& a, unsigned int gcode_id) { + return a.gcode_id < gcode_id; + }; + + auto find_skip_block_end = [&skippable_blocks = this->skippable_blocks](unsigned int gcode_id) -> unsigned int{ + auto it = std::upper_bound( + skippable_blocks.begin(), skippable_blocks.end(), gcode_id, + [](unsigned int id, const std::pair&block) { return id < block.first; } + ); + if (it != skippable_blocks.begin()) { + auto candidate = std::prev(it); + if (gcode_id >= candidate->first && gcode_id <= candidate->second) + return candidate->second; + } + return 0; + }; + + auto find_skip_block_start = [&skippable_blocks = this->skippable_blocks](unsigned int gcode_id) -> unsigned int { + auto it = std::upper_bound( + skippable_blocks.begin(), skippable_blocks.end(), gcode_id, + [](unsigned int id, const std::pair&block) { return id < block.first; } + ); + if (it != skippable_blocks.begin()) { + auto candidate = std::prev(it); + if (gcode_id >= candidate->first && gcode_id <= candidate->second) + return candidate->first; + } + return 0; + }; + + auto adjust_iter = [&](std::vector::const_iterator iter, + const std::vector::const_iterator& begin, + const std::vector::const_iterator& end, + bool forward) -> std::vector::const_iterator + { + if (forward) { + while (iter != end) { + unsigned current_id = iter->gcode_id; + unsigned skip_block_end = find_skip_block_end(current_id); + if(skip_block_end == 0) + break; + iter = std::lower_bound(iter, end, skip_block_end + 1, gcode_move_comp); + } + } + else { + while (iter != begin) { + unsigned current_id = iter->gcode_id; + unsigned skip_block_start = find_skip_block_start(current_id); + if(skip_block_start == 0) + break; + auto new_iter = std::lower_bound(begin, iter, skip_block_start, gcode_move_comp); + if(new_iter == begin) + break; + iter = std::prev(new_iter); + } + } + return iter; + }; + + if (!pre_cooling && !pre_heating && block.free_upper_gcode_id <= block.free_lower_gcode_id) + return; + + auto move_iter_lower = std::lower_bound(moves.begin(), moves.end(), block.free_lower_gcode_id, gcode_move_comp); + auto move_iter_upper = std::lower_bound(moves.begin(), moves.end(), block.free_upper_gcode_id, gcode_move_comp); // closed iter + + if (move_iter_lower == moves.end() || move_iter_upper == moves.begin()) + return; + --move_iter_upper; + + auto partial_free_move_lower = std::lower_bound(moves.begin(), moves.end(), block.partial_free_lower_id, gcode_move_comp); + auto partial_free_move_upper = std::lower_bound(moves.begin(), moves.end(), block.partial_free_upper_id, gcode_move_comp); // closed iter + + if (partial_free_move_lower == moves.end() || partial_free_move_upper == moves.begin()) + return; + --partial_free_move_upper; + + if (move_iter_lower >= move_iter_upper) + return; + + bool apply_cooling_when_partial_free = is_pre_cooling_valid(block.last_filament_id) && pre_cooling; + + float partial_free_time_gap = partial_free_move_upper->time[valid_machine_id] - partial_free_move_lower->time[valid_machine_id]; // time of partial free + float complete_free_time_gap = move_iter_upper->time[valid_machine_id] - move_iter_lower->time[valid_machine_id]; // time of complete free + + if (apply_cooling_when_partial_free && partial_free_time_gap + complete_free_time_gap < inject_time_threshold) + return; + + if (!apply_cooling_when_partial_free && complete_free_time_gap < inject_time_threshold) + return; + + float ext_heating_rate = heating_rate[block.extruder_id]; + float ext_cooling_rate = cooling_rate[block.extruder_id]; + + if (apply_cooling_when_partial_free) { + float max_cooling_temp = std::min(curr_temp, std::min(get_partial_free_cooling_thres(block.last_filament_id), partial_free_time_gap * ext_cooling_rate)); + curr_temp -= max_cooling_temp; // set the temperature after doing cooling when post-extruding + inserted_operation_lines[block.partial_free_lower_id].emplace_back(format_line_M104(curr_temp, block.extruder_id, "Multi extruder pre cooling in post extrusion"), TimeProcessor::InsertLineType::PreCooling); + } + + if (pre_cooling && !pre_heating) { + // only perform cooling + if (target_temp >= curr_temp) + return; + inserted_operation_lines[block.free_lower_gcode_id].emplace_back(format_line_M104(target_temp, block.extruder_id, "Multi extruder pre cooling"), TimeProcessor::InsertLineType::PreCooling); + return; + } + if (!pre_cooling && pre_heating) { + // only perform heating + if (target_temp <= curr_temp) + return; + float heating_start_time = move_iter_upper->time[valid_machine_id] - (target_temp - curr_temp) / ext_heating_rate; + auto heating_move_iter = std::upper_bound(move_iter_lower, move_iter_upper + 1, heating_start_time, [valid_machine_id = this->valid_machine_id](float time, const GCodeProcessorResult::MoveVertex& a) {return time < a.time[valid_machine_id]; }); + if (heating_move_iter == move_iter_lower) { + inserted_operation_lines[block.free_lower_gcode_id].emplace_back(format_line_M104(target_temp, block.extruder_id, "Multi extruder pre heating"), TimeProcessor::InsertLineType::PreHeating); + } + else { + --heating_move_iter; + heating_move_iter = adjust_iter(heating_move_iter, move_iter_lower, move_iter_upper, false); + inserted_operation_lines[heating_move_iter->gcode_id].emplace_back(format_line_M104(target_temp, block.extruder_id, "Multi extruder pre heating"), TimeProcessor::InsertLineType::PreHeating); + } + return; + } + // perform cooling first and then perform heating + float mid_temp = std::max(0.f, (curr_temp * ext_heating_rate + target_temp * ext_cooling_rate - complete_free_time_gap * ext_cooling_rate * ext_heating_rate) / (ext_cooling_rate + ext_heating_rate)); + float heating_temp = target_temp - mid_temp; + float heating_start_time = move_iter_upper->time[valid_machine_id] - heating_temp / ext_heating_rate; + auto heating_move_iter = std::upper_bound(move_iter_lower, move_iter_upper + 1, heating_start_time, [valid_machine_id = this->valid_machine_id](float time, const GCodeProcessorResult::MoveVertex& a) {return time < a.time[valid_machine_id]; }); + if (heating_move_iter == move_iter_lower) + return; + --heating_move_iter; + heating_move_iter = adjust_iter(heating_move_iter, move_iter_lower, move_iter_upper, false); + + // get the insert pos of heat cmd and recalculate time gap and delta temp + float real_cooling_time = heating_move_iter->time[valid_machine_id] - move_iter_lower->time[valid_machine_id]; + int real_delta_temp = std::min((int)(real_cooling_time * ext_cooling_rate), (int)curr_temp); + if (real_delta_temp == 0) + return; + inserted_operation_lines[block.free_lower_gcode_id].emplace_back(format_line_M104(curr_temp - real_delta_temp, block.extruder_id, "Multi extruder pre cooling"), TimeProcessor::InsertLineType::PreCooling); + inserted_operation_lines[heating_move_iter->gcode_id].emplace_back(format_line_M104(target_temp, block.extruder_id, "Multi extruder pre heating"), TimeProcessor::InsertLineType::PreHeating); +} + +void GCodeProcessor::PreCoolingInjector::build_by_filament_blocks(const std::vector& filament_usage_blocks_) +{ + m_extruder_free_blocks.clear(); + std::vector> per_extruder_usage_blocks(2); + for (auto& block : filament_usage_blocks_) + per_extruder_usage_blocks[filament_maps[block.filament_id]].emplace_back(block); + + ExtruderPreHeating::FilamentUsageBlock start_filament_block(-1, 0, machine_start_gcode_end_id); + ExtruderPreHeating::FilamentUsageBlock end_filament_block(-1, machine_end_gcode_start_id, std::numeric_limits::max()); + + for (auto& blocks : per_extruder_usage_blocks) { + blocks.insert(blocks.begin(), start_filament_block); + blocks.emplace_back(end_filament_block); + } + + for(size_t extruder_id =0 ;extruder_idupper_gcode_id; + block.last_filament_id = iter->filament_id; + block.free_upper_gcode_id = niter->lower_gcode_id; + block.next_filament_id = niter->filament_id; + block.extruder_id = extruder_id; + block.partial_free_lower_id = block.free_lower_gcode_id; + block.partial_free_upper_id = block.free_lower_gcode_id; + m_extruder_free_blocks.emplace_back(block); + } + } + + sort(m_extruder_free_blocks.begin(), m_extruder_free_blocks.end(), [](const auto& a, const auto& b) { + return a.free_lower_gcode_id < b.free_lower_gcode_id || (a.free_lower_gcode_id == b.free_lower_gcode_id && a.free_upper_gcode_id < b.free_upper_gcode_id); + }); +} + +void GCodeProcessor::PreCoolingInjector::build_by_extruder_blocks(const std::vector& extruder_usage_blocks_) +{ + m_extruder_free_blocks.clear(); + std::vector> per_extruder_usage_blocks(2); + for (auto& block : extruder_usage_blocks_) + per_extruder_usage_blocks[block.extruder_id].emplace_back(block); + + for(size_t extruder_id =0;extruder_id::max()); + end_filament_block.initialize_step_3(std::numeric_limits::max(), -1, std::numeric_limits::max()); + + blocks.insert(blocks.begin(), start_filament_block); + blocks.emplace_back(end_filament_block); + } + + for (size_t extruder_id = 0; extruder_id < per_extruder_usage_blocks.size(); ++extruder_id) { + const auto& extruder_usage_blocks = per_extruder_usage_blocks[extruder_id]; + for (auto iter = extruder_usage_blocks.begin(); iter != extruder_usage_blocks.end(); ++iter) { + auto niter = std::next(iter); + if (niter == extruder_usage_blocks.end()) + break; + ExtruderFreeBlock block; + block.free_lower_gcode_id = iter->end_id; + block.last_filament_id = iter->end_filament; + block.free_upper_gcode_id = niter->start_id; + block.next_filament_id = niter->start_filament; + block.extruder_id = extruder_id; + block.partial_free_lower_id = iter->post_extrusion_start_id; + block.partial_free_upper_id = iter->post_extrusion_end_id; + m_extruder_free_blocks.emplace_back(block); + } + } + + sort(m_extruder_free_blocks.begin(), m_extruder_free_blocks.end(), [](const auto& a, const auto& b) { + return a.free_lower_gcode_id < b.free_lower_gcode_id || (a.free_lower_gcode_id == b.free_lower_gcode_id && a.free_upper_gcode_id < b.free_upper_gcode_id); + }); +} + } /* namespace Slic3r */ diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 35a7dd0..12c4659 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -23,6 +23,7 @@ namespace Slic3r { #define BED_TEMP_TOO_HIGH_THAN_FILAMENT "bed_temperature_too_high_than_filament" #define NOT_SUPPORT_TRADITIONAL_TIMELAPSE "not_support_traditional_timelapse" #define NOT_GENERATE_TIMELAPSE "not_generate_timelapse" +#define SMOOTH_TIMELAPSE_WITHOUT_PRIME_TOWER "smooth_timelapse_without_prime_tower" #define LONG_RETRACTION_WHEN_CUT "activate_long_retraction_when_cut" enum class EMoveType : unsigned char @@ -41,6 +42,18 @@ namespace Slic3r { Count }; + enum SkipType + { + stTimelapse, + stHeadWrapDetect, + stOther, + stNone + }; + + const std::unordered_map skip_type_map{ + {"timelapse", SkipType::stTimelapse}, + {"head_wrap_detect", SkipType::stHeadWrapDetect} + }; struct PrintEstimatedStatistics { enum class ETimeMode : unsigned char @@ -83,7 +96,8 @@ namespace Slic3r { std::map> used_filaments_per_role; std::array(ETimeMode::Count)> modes; - unsigned int total_filamentchanges; + unsigned int total_filament_changes; + unsigned int total_extruder_changes; PrintEstimatedStatistics() { reset(); } @@ -99,7 +113,8 @@ namespace Slic3r { total_volumes_per_extruder.clear(); flush_per_filament.clear(); used_filaments_per_role.clear(); - total_filamentchanges = 0; + total_filament_changes = 0; + total_extruder_changes = 0; } }; @@ -119,6 +134,18 @@ namespace Slic3r { using ConflictResultOpt = std::optional; + struct GCodeCheckResult + { + int error_code = 0; // 0 means succeed, 0001 printable area error, 0010 printable height error + std::map>> print_area_error_infos; // printable_area extruder_id to which cannot printed in this extruder + std::map>> print_height_error_infos; // printable_height extruder_id to which cannot printed in this extruder + void reset() { + error_code = 0; + print_area_error_infos.clear(); + print_height_error_infos.clear(); + } + }; + struct FilamentPrintableResult { std::vector conflict_filament; @@ -132,7 +159,17 @@ namespace Slic3r { struct GCodeProcessorResult { + struct FilamentSequenceHash + { + uint64_t operator()(const std::vector& layer_filament) const { + uint64_t key = 0; + for (auto& f : layer_filament) + key |= (uint64_t(1) << f); + return key; + } + }; ConflictResultOpt conflict_result; + GCodeCheckResult gcode_check_result; FilamentPrintableResult filament_printable_reuslt; struct SettingsIds @@ -150,12 +187,14 @@ namespace Slic3r { struct MoveVertex { - unsigned int gcode_id{ 0 }; EMoveType type{ EMoveType::Noop }; ExtrusionRole extrusion_role{ erNone }; + //QDS: arc move related data + EMovePathType move_path_type{ EMovePathType::Noop_move }; unsigned char extruder_id{ 0 }; unsigned char cp_color_id{ 0 }; - Vec3f position{ Vec3f::Zero() }; // mm + + unsigned int gcode_id{ 0 }; float delta_extruder{ 0.0f }; // mm float feedrate{ 0.0f }; // mm/s float width{ 0.0f }; // mm @@ -163,14 +202,15 @@ namespace Slic3r { float mm3_per_mm{ 0.0f }; float fan_speed{ 0.0f }; // percentage float temperature{ 0.0f }; // Celsius degrees - float time{ 0.0f }; // s float layer_duration{ 0.0f }; // s (layer id before finalize) + std::arraytime{ 0.f,0.f }; // prefix sum of time, assigned during finalize() - //QDS: arc move related data - EMovePathType move_path_type{ EMovePathType::Noop_move }; + Vec3f position{ Vec3f::Zero() }; // mm Vec3f arc_center_position{ Vec3f::Zero() }; // mm std::vector interpolation_points; // interpolation points of arc for drawing + int object_label_id{-1}; + float print_z{0.0f}; float volumetric_rate() const { return feedrate * mm3_per_mm; } //QDS: new function to support arc move @@ -197,6 +237,8 @@ namespace Slic3r { Pointfs printable_area; //QDS: add bed exclude area Pointfs bed_exclude_area; + std::vector extruder_areas; + std::vector extruder_heights; //QDS: add toolpath_outside bool toolpath_outside; //QDS: add object_label_enabled @@ -207,19 +249,28 @@ namespace Slic3r { bool support_traditional_timelapse{true}; float printable_height; SettingsIds settings_ids; - size_t extruders_count; + size_t filaments_count; std::vector extruder_colors; std::vector filament_diameters; std::vector required_nozzle_HRC; std::vector filament_densities; std::vector filament_costs; std::vector filament_vitrification_temperature; + std::vector filament_maps; + std::vector limit_filament_maps; PrintEstimatedStatistics print_statistics; std::vector custom_gcode_per_print_z; std::vector>> spiral_vase_layers; //QDS std::vector warnings; - NozzleType nozzle_type; + std::vector nozzle_type; + // first key stores filaments, second keys stores the layer ranges(enclosed) that use the filaments + std::unordered_map, std::vector>,FilamentSequenceHash> layer_filaments; + // first key stores `from` filament, second keys stores the `to` filament + std::map, int > filament_change_count_map; + + std::unordered_map skippable_part_time; + BedType bed_type = BedType::btCount; #if ENABLE_GCODE_VIEWER_STATISTICS int64_t time{ 0 }; @@ -242,7 +293,7 @@ namespace Slic3r { timelapse_warning_code = other.timelapse_warning_code; printable_height = other.printable_height; settings_ids = other.settings_ids; - extruders_count = other.extruders_count; + filaments_count = other.filaments_count; extruder_colors = other.extruder_colors; filament_diameters = other.filament_diameters; filament_densities = other.filament_densities; @@ -252,7 +303,12 @@ namespace Slic3r { spiral_vase_layers = other.spiral_vase_layers; warnings = other.warnings; bed_type = other.bed_type; + gcode_check_result = other.gcode_check_result; + limit_filament_maps = other.limit_filament_maps; filament_printable_reuslt = other.filament_printable_reuslt; + layer_filaments = other.layer_filaments; + filament_change_count_map = other.filament_change_count_map; + skippable_part_time = other.skippable_part_time; #if ENABLE_GCODE_VIEWER_STATISTICS time = other.time; #endif @@ -262,12 +318,85 @@ namespace Slic3r { void unlock() const { result_mutex.unlock(); } }; + namespace ExtruderPreHeating + { + struct FilamentUsageBlock + { + int filament_id; + unsigned int lower_gcode_id; + unsigned int upper_gcode_id; // [lower_gcode_id,upper_gcode_id) uses current filament , upper gcode id will be set after finding next block + FilamentUsageBlock(int filament_id_, unsigned int lower_gcode_id_, unsigned int upper_gcode_id_) :filament_id(filament_id_), lower_gcode_id(lower_gcode_id_), upper_gcode_id(upper_gcode_id_) {} + }; + + /** + * @brief Describle the usage of a exturder in a section + * + * The strucutre stores the start and end lines of the sections as well as + * the filament used at the beginning and end of the section. + * Post extrusion means the final extrusion before switching to the next extruder. + * + * Simplified GCode Flow: + * 1.Extruder Change Block (ext0 switch to ext1) + * 2.Extruder Usage Block (use ext1 to print) + * 3.Extruder Change Block (ext1 switch to ext0) + * 4.Extruder Usage Block (use ext0 to print) + * 5.Extruder Change Block (ext0 switch to ex1) + * ... + * + * So the construct of extruder usage block relys on two extruder change block + */ + struct ExtruderUsageBlcok + { + int extruder_id = -1; + unsigned int start_id = -1; + unsigned int end_id = -1; + int start_filament = -1; + int end_filament = -1; + unsigned int post_extrusion_start_id = -1; + unsigned int post_extrusion_end_id = -1; + + void initialize_step_1(int extruder_id_, int start_id_, int start_filament_) { + extruder_id = extruder_id_; + start_id = start_id_; + start_filament = start_filament_; + }; + void initialize_step_2(int post_extrusion_start_id_) { + post_extrusion_start_id = post_extrusion_start_id_; + } + void initialize_step_3(int end_id_, int end_filament_, int post_extrusion_end_id_) { + end_id = end_id_; + end_filament = end_filament_; + post_extrusion_end_id = post_extrusion_end_id_; + } + void reset() { + *this = ExtruderUsageBlcok(); + } + ExtruderUsageBlcok() = default; + }; + } + + + class CommandProcessor { + public: + using command_handler_t = std::function; + private: + struct TrieNode { + command_handler_t handler{ nullptr }; + std::unordered_map> children; + bool early_quit{ false }; // stop matching, trigger handle imediately + }; + public: + CommandProcessor(); + void register_command(const std::string& str, command_handler_t handler,bool early_quit = false); + bool process_comand(std::string_view cmd, const GCodeReader::GCodeLine& line); + private: + std::unique_ptr root; + }; class GCodeProcessor { - static const std::vector Reserved_Tags; - static const std::string Flush_Start_Tag; - static const std::string Flush_End_Tag; + static const std::vector ReservedTags; + static const std::vector CustomTags; public: enum class ETags : unsigned char { @@ -289,15 +418,32 @@ namespace Slic3r { //1.9.7.52 Used_Filament_Weight_Placeholder, Used_Filament_Volume_Placeholder, - Used_Filament_Length_Placeholder + Used_Filament_Length_Placeholder, + MachineStartGCodeEnd, + MachineEndGCodeStart, + NozzleChangeStart, + NozzleChangeEnd }; - static const std::string& reserved_tag(ETags tag) { return Reserved_Tags[static_cast(tag)]; } + enum class CustomETags : unsigned char + { + FLUSH_START, + FLUSH_END, + VFLUSH_START, + VFLUSH_END, + SKIPPABLE_START, + SKIPPABLE_END, + SKIPPABLE_TYPE + }; + + static const std::string& reserved_tag(ETags tag) { return ReservedTags[static_cast(tag)]; } + static const std::string& custom_tags(CustomETags tag) { return CustomTags[static_cast(tag)]; } // checks the given gcode for reserved tags and returns true when finding the 1st (which is returned into found_tag) static bool contains_reserved_tag(const std::string& gcode, std::string& found_tag); // checks the given gcode for reserved tags and returns true when finding any // (the first max_count found tags are returned into found_tag) static bool contains_reserved_tags(const std::string& gcode, unsigned int max_count, std::vector& found_tag); + static bool get_last_position_from_gcode(const std::string &gcode_str, Vec3f &pos); static int get_gcode_last_filament(const std::string &gcode_str); static bool get_last_z_from_gcode(const std::string& gcode_str, double& z); @@ -375,6 +521,8 @@ namespace Slic3r { EMoveType move_type{ EMoveType::Noop }; ExtrusionRole role{ erNone }; + SkipType skippable_type{ SkipType::stNone }; + unsigned int move_id{ 0 }; // index of the related move vertex, will be assigned duraing gcode process unsigned int g1_line_id{ 0 }; unsigned int layer_id{ 0 }; float distance{ 0.0f }; // mm @@ -458,28 +606,42 @@ namespace Slic3r { //QDS: prepare stage time before print model, including start gcode time and mostly same with start gcode time float prepare_time; + // accept the time block and total time + using block_handler_t = std::function; + using AdditionalBufferBlock = std::pair; + using AdditionalBuffer = std::vector; + AdditionalBuffer m_additional_time_buffer; + + AdditionalBuffer merge_adjacent_addtional_time_blocks(const AdditionalBuffer& buffer); + void reset(); /** * @brief Simulates firmware st_synchronize() call * - * Adding additional time to the specified extrusion role's time block. + * Adding additional time to the specified extrusion role's time block.The provided block handler + * can process the block and the corresponding time (usually assigned to the move of the block). * * @param additional_time Addtional time to calculate * @param target_role Target extrusion role for addtional time.Default is none,means any role is ok. + * @param block_handler Handler to set the processing logic for the block and its corresponding time. */ - void simulate_st_synchronize(float additional_time = 0.0f, ExtrusionRole target_role = ExtrusionRole::erNone); + void simulate_st_synchronize(float additional_time = 0.0f, ExtrusionRole target_role = ExtrusionRole::erNone, block_handler_t block_handler = block_handler_t()); /** * @brief Calculates the time for all blocks - * - * Computes the time for all blocks. + * + * Computes the time for all blocks. The provided block handler can process each block and the + * corresponding time (usually assigned to the move of the block). * * @param keep_last_n_blocks The number of last blocks to retain during calculation (default is 0). * @param additional_time Additional time to calculate. * @param target_role Target extrusion role for addtional time.Default is none, means any role is ok. + * @param block_handler Handler to set the processing logic for each block and its corresponding time. */ - void calculate_time(size_t keep_last_n_blocks = 0, float additional_time = 0.0f, ExtrusionRole target_role = ExtrusionRole::erNone); + void calculate_time(size_t keep_last_n_blocks = 0, float additional_time = 0.0f, ExtrusionRole target_role = ExtrusionRole::erNone, block_handler_t block_handler = block_handler_t()); + + void handle_time_block(const TimeBlock& block, float time, int activate_machine_idx, GCodeProcessorResult& result); }; struct UsedFilaments // filaments per ColorChange @@ -488,19 +650,19 @@ namespace Slic3r { std::vector volumes_per_color_change; double model_extrude_cache; - std::map model_volumes_per_extruder; + std::map model_volumes_per_filament; double wipe_tower_cache; - std::mapwipe_tower_volumes_per_extruder; + std::mapwipe_tower_volumes_per_filament; double support_volume_cache; - std::mapsupport_volumes_per_extruder; + std::mapsupport_volumes_per_filament; //QDS: the flush amount of every filament std::map flush_per_filament; double total_volume_cache; - std::maptotal_volumes_per_extruder; + std::maptotal_volumes_per_filament; double role_cache; std::map> filaments_per_role; @@ -527,17 +689,66 @@ namespace Slic3r { //1.9.7.52 struct TimeProcessContext { - size_t total_layer_num; + UsedFilaments used_filaments; // stores the accurate filament usage info std::vector filament_lists; - UsedFilaments used_filaments; - TimeProcessContext( size_t total_layer_num_, + std::vector filament_types; + std::vector filament_maps; // map each filament to extruder + std::vector filament_nozzle_temp; + std::vector physical_extruder_map; + + size_t total_layer_num; + std::vector cooling_rate{ 2.f }; // Celsius degree per second + std::vector heating_rate{ 2.f }; // Celsius degree per second + std::vector pre_cooling_temp{ 0 }; + float inject_time_threshold{ 30.f }; // only active pre cooling & heating if time gap is bigger than threshold + bool enable_pre_heating{ false }; + + TimeProcessContext( + const UsedFilaments& used_filaments_, const std::vector& filament_lists_, - const UsedFilaments& used_filaments_) - :total_layer_num(total_layer_num_), filament_lists(filament_lists_), used_filaments(used_filaments_) {} + const std::vector& filament_maps_, + const std::vector& filament_types_, + const std::vector& filament_nozzle_temp_, + const std::vector& physical_extruder_map_, + const size_t total_layer_num_, + const std::vector& cooling_rate_, + const std::vector& heating_rate_, + const std::vector& pre_cooling_temp_, + const float inject_time_threshold_, + const bool enable_pre_heating_ + ) : + used_filaments(used_filaments_), + filament_lists(filament_lists_), + filament_maps(filament_maps_), + filament_types(filament_types_), + filament_nozzle_temp(filament_nozzle_temp_), + physical_extruder_map(physical_extruder_map_), + total_layer_num(total_layer_num_), + cooling_rate(cooling_rate_), + heating_rate(heating_rate_), + pre_cooling_temp(pre_cooling_temp_), + enable_pre_heating(enable_pre_heating_), + inject_time_threshold(inject_time_threshold_) + { + } + }; struct TimeProcessor { + enum InsertLineType + { + PlaceholderReplace, + TimePredict, + FilamentChangePredict, + ExtruderChangePredict, + PreCooling, + PreHeating, + }; + + // first key is line id ,second key is content + using InsertedLinesMap = std::map>>; + struct Planner { // Size of the firmware planner queue. The old 8-bit Marlins usually just managed 16 trapezoidal blocks. @@ -558,6 +769,7 @@ namespace Slic3r { // Additional load / unload times for a filament exchange sequence. float filament_load_times; float filament_unload_times; + float extruder_change_times; std::array(PrintEstimatedStatistics::ETimeMode::Count)> machines; @@ -566,7 +778,99 @@ namespace Slic3r { // post process the file with the given filename to add remaining time lines M73 // and updates moves' gcode ids accordingly void post_process(const std::string& filename, std::vector& moves, std::vector& lines_ends, const TimeProcessContext& context); + private: + void handle_offsets_of_first_process( + const std::vector>& offsets, + std::vector& moves, + std::vector& filament_blocks, + std::vector& extruder_blocks, + std::vector>& skippable_blocks, + unsigned int& machine_start_gcode_end_line_id, + unsigned int& machine_end_gcode_start_line_id + ); + + void handle_offsets_of_second_process( + const InsertedLinesMap& inserted_operation_lines, + std::vector& moves + ); }; + + class PreCoolingInjector { + public: + struct ExtruderFreeBlock { + unsigned int free_lower_gcode_id; + unsigned int free_upper_gcode_id; + unsigned int partial_free_lower_id; // stores the range of extrusion in wipe tower. Without wipetower, partial free lower_id and upper id will be same as free lower id + unsigned int partial_free_upper_id; + int last_filament_id; + int next_filament_id; + int extruder_id; + }; + + void process_pre_cooling_and_heating(TimeProcessor::InsertedLinesMap& inserted_operation_lines); + void build_extruder_free_blocks(const std::vector& filament_usage_blocks, const std::vector& extruder_usage_blocks); + + PreCoolingInjector( + const std::vector& moves_, + const std::vector& filament_types_, + const std::vector& filament_maps_, + const std::vector& filament_nozzle_temps_, + const std::vector& physical_extruder_map_, + int valid_machine_id_, + float inject_time_threshold_, + const std::vector & pre_cooling_temp_, + const std::vector& cooling_rate_, + const std::vector& heating_rate_, + const std::vector>& skippable_blocks_, + unsigned int machine_start_gcode_end_id_, + unsigned int machine_end_gcode_start_id_ + ) : + moves(moves_), + filament_types(filament_types_), + filament_maps(filament_maps_), + filament_nozzle_temps(filament_nozzle_temps_), + physical_extruder_map(physical_extruder_map_), + valid_machine_id(valid_machine_id_), + inject_time_threshold(inject_time_threshold_), + filament_pre_cooling_temps(pre_cooling_temp_), + cooling_rate(cooling_rate_), + heating_rate(heating_rate_), + skippable_blocks(skippable_blocks_), + machine_start_gcode_end_id(machine_start_gcode_end_id_), + machine_end_gcode_start_id(machine_end_gcode_start_id_) + { + } + + private: + std::vector m_extruder_free_blocks; + const std::vector& moves; + const std::vector& filament_types; + const std::vector& filament_maps; + const std::vector& filament_nozzle_temps; + const std::vector& physical_extruder_map; + const int valid_machine_id; + const float inject_time_threshold; + const std::vector& cooling_rate; + const std::vector& heating_rate; + const std::vector& filament_pre_cooling_temps; // target cooling temp during post extrusion + const std::vector>& skippable_blocks; + const unsigned int machine_start_gcode_end_id; + const unsigned int machine_end_gcode_start_id; + + void inject_cooling_heating_command( + TimeProcessor::InsertedLinesMap& inserted_operation_lines, + const ExtruderFreeBlock& free_block, + float curr_temp, + float target_temp, + bool pre_cooling, + bool pre_heating + ); + + void build_by_filament_blocks(const std::vector& filament_usage_blocks); + void build_by_extruder_blocks(const std::vector& extruder_usage_blocks); + }; + + public: class SeamsDetector { @@ -695,23 +999,36 @@ namespace Slic3r { #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING private: + CommandProcessor m_command_processor; GCodeReader m_parser; EUnits m_units; EPositioningType m_global_positioning_type; EPositioningType m_e_local_positioning_type; std::vector m_extruder_offsets; GCodeFlavor m_flavor; - float m_nozzle_volume; + std::vector m_nozzle_volume; AxisCoords m_start_position; // mm AxisCoords m_end_position; // mm AxisCoords m_origin; // mm CachedPosition m_cached_position; bool m_wiping; - bool m_flushing; + bool m_flushing; // mark a section with real flush + bool m_virtual_flushing; // mark a section with virtual flush, only for statistics bool m_wipe_tower; - float m_remaining_volume; + bool m_skippable; + SkipType m_skippable_type; + int m_object_label_id{-1}; + float m_print_z{0.0f}; + std::vector m_remaining_volume; //1.9.7.52 std::vector m_filament_lists; + std::vector m_filament_nozzle_temp; + std::vector m_filament_types; + std::vector m_hotend_cooling_rate{ 2.f }; + std::vector m_hotend_heating_rate{ 2.f }; + std::vector m_filament_pre_cooling_temp{ 0 }; + float m_enable_pre_heating{ false }; + std::vector m_physical_extruder_map; //QDS: x, y offset for gcode generated double m_x_offset{ 0 }; @@ -731,8 +1048,10 @@ namespace Slic3r { float m_mm3_per_mm; float m_fan_speed; // percentage ExtrusionRole m_extrusion_role; + std::vector m_filament_maps; + std::vector m_last_filament_id; + std::vector m_filament_id; unsigned char m_extruder_id; - unsigned char m_last_extruder_id; ExtruderColors m_extruder_colors; ExtruderTemps m_extruder_temps; int m_highest_bed_temp; @@ -763,7 +1082,9 @@ namespace Slic3r { Simplify3D, CraftWare, ideaMaker, - KissSlicer + KissSlicer, + QIDISlicer, + BambuStudio }; static const std::vector> Producers; @@ -784,6 +1105,11 @@ namespace Slic3r { public: GCodeProcessor(); + // check whether the gcode path meets the filament_map grouping requirements + bool check_multi_extruder_gcode_valid(const std::vector &unprintable_areas, + const std::vector &printable_heights, + const std::vector &filament_map, + const std::vector>& unprintable_filament_types ); void apply_config(const PrintConfig& config); //1.9.7.52 void set_filaments(const std::vector&filament_lists) { m_filament_lists=filament_lists;} @@ -826,6 +1152,7 @@ namespace Slic3r { } private: + void register_commands(); void apply_config(const DynamicPrintConfig& config); void apply_config_simplify3d(const std::string& filename); void apply_config_superslicer(const std::string& filename); @@ -848,6 +1175,8 @@ namespace Slic3r { void process_G1(const GCodeReader::GCodeLine& line); void process_G2_G3(const GCodeReader::GCodeLine& line); + void process_VG1(const GCodeReader::GCodeLine& line); + // QDS: handle delay command void process_G4(const GCodeReader::GCodeLine& line); @@ -896,6 +1225,12 @@ namespace Slic3r { // Set extruder temperature void process_M104(const GCodeReader::GCodeLine& line); + // Process virtual command of M104, in order to help gcodeviewer work + void process_VM104(const GCodeReader::GCodeLine& line); + + // Process virtual command of M109, in order to help gcodeviewer work + void process_VM109(const GCodeReader::GCodeLine& line); + // Set fan speed void process_M106(const GCodeReader::GCodeLine& line); @@ -956,19 +1291,25 @@ namespace Slic3r { // Unload the current filament into the MK3 MMU2 unit at the end of print. void process_M702(const GCodeReader::GCodeLine& line); + void process_SYNC(const GCodeReader::GCodeLine& line); + // Processes T line (Select Tool) void process_T(const GCodeReader::GCodeLine& line); void process_T(const std::string_view command); + void process_M1020(const GCodeReader::GCodeLine &line); + + void process_filament_change(int id); //QDS: different path_type is only used for arc move void store_move_vertex(EMoveType type, EMovePathType path_type = EMovePathType::Noop_move); void set_extrusion_role(ExtrusionRole role); + void set_skippable_type(const std::string_view type); float minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const; float minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const; - float get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; - float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; + float get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis, int extruder_id) const; + float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis, int extruder_id) const; float get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; Vec3f get_xyz_max_jerk(PrintEstimatedStatistics::ETimeMode mode) const; float get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; @@ -979,6 +1320,7 @@ namespace Slic3r { void set_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value); float get_filament_load_time(size_t extruder_id); float get_filament_unload_time(size_t extruder_id); + float get_extruder_change_time(size_t extruder_id); int get_filament_vitrification_temperature(size_t extrude_id); void process_custom_gcode_time(CustomGCode::Type code); void process_filaments(CustomGCode::Type code); @@ -989,6 +1331,13 @@ namespace Slic3r { void update_estimated_times_stats(); //QDS: void update_slice_warnings(); + + // get current used filament + int get_filament_id(bool force_initialize = true) const; + // get last used filament in the same extruder with current filament + int get_last_filament_id(bool force_initialize = true) const; + //get current used extruder + int get_extruder_id(bool force_initialize = true)const; }; } /* namespace Slic3r */ diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 7d8bdf3..2edaf71 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -29,6 +29,7 @@ #include #endif +static const int average_filter_window_size = 5; namespace Slic3r { namespace SeamPlacerImpl { @@ -1125,15 +1126,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: // gather vector of all seams on the print_object - pair of layer_index and seam__index within that layer const std::vector &layers = m_seam_per_object[po].layers; - std::vector> seams; - for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) { - const std::vector &layer_perimeter_points = layers[layer_idx].points; - size_t current_point_index = 0; - while (current_point_index < layer_perimeter_points.size()) { - seams.emplace_back(layer_idx, layer_perimeter_points[current_point_index].perimeter.seam_index); - current_point_index = layer_perimeter_points[current_point_index].perimeter.end_index; - } - } + std::vector> seams = gather_all_seams_of_object(layers); // sort them before alignment. Alignment is sensitive to initializaion, this gives it better chance to choose something nice std::stable_sort(seams.begin(), seams.end(), [&comparator, &layers](const std::pair &left, const std::pair &right) { @@ -1257,6 +1250,93 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: #endif } +std::vector> SeamPlacer::gather_all_seams_of_object(const std::vector &layers) +{ + // gather vector of all seams on the print_object - pair of layer_index and seam__index within that layer + std::vector> seams; + for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) { + const std::vector &layer_perimeter_points = layers[layer_idx].points; + size_t current_point_index = 0; + while (current_point_index < layer_perimeter_points.size()) { + seams.emplace_back(layer_idx, layer_perimeter_points[current_point_index].perimeter.seam_index); + current_point_index = layer_perimeter_points[current_point_index].perimeter.end_index; + } + } + return seams; +} + +void SeamPlacer::filter_scarf_seam_switch_by_angle(const float &angle, std::vector &layers) +{ + std::vector> seams = gather_all_seams_of_object(layers); + + float max_distance = SeamPlacer::seam_align_tolerable_dist_factor * layers[seams[0].first].points[seams[0].second].perimeter.flow_width; + + std::vector seam_index_pos; + std::vector> seam_index_group; + //get each seam line group + for (size_t seam_idx = 0; seam_idx < seams.size(); seam_idx++) { + if (layers[seams[seam_idx].first].points[seams[seam_idx].second].is_grouped) + continue; + + layers[seams[seam_idx].first].points[seams[seam_idx].second].is_grouped = true; + seam_index_pos.push_back(seam_idx); + size_t prev_idx = seam_idx; + size_t next_seam = seam_idx + 1; + for (; next_seam < seams.size(); next_seam++) { + if (layers[seams[next_seam].first].points[seams[next_seam].second].is_grouped || seams[prev_idx].first == seams[next_seam].first) + continue; + + // if the seam is not continous with prev layer, break + if (seams[prev_idx].first + 1 != seams[next_seam].first) + break; + + if ((layers[seams[prev_idx].first].points[seams[prev_idx].second].position - layers[seams[next_seam].first].points[seams[next_seam].second].position).norm() <= + max_distance) { + + layers[seams[next_seam].first].points[seams[next_seam].second].is_grouped = true; + + float next_seam_angle = layers[seams[next_seam].first].points[seams[next_seam].second].local_ccw_angle; + + if (next_seam_angle < 0) + next_seam_angle *= -1; + + if (PI - angle > next_seam_angle) { + layers[seams[next_seam].first].points[seams[next_seam].second].enable_scarf_seam = true; + } + + prev_idx = next_seam; + seam_index_pos.push_back(next_seam); + } + } + + seam_index_group.emplace_back(std::move(seam_index_pos)); + seam_index_pos.clear(); + } + + // filter + { + for (size_t k = 0; k < seam_index_group.size(); k++) { + std::vector seam_group = seam_index_group[k]; + if (seam_group.size() <= 1) continue; + int half_window = average_filter_window_size / 2; + // average filter + for (size_t idx = 0; idx < seam_group.size(); idx++) { + double sum = 0; + int count = 0; + + for (int window_idx = -half_window; window_idx <= half_window; ++window_idx) { + int index = idx + window_idx; + if (index >= 0 && index < seam_group.size()) { + sum += layers[seams[seam_group[index]].first].points[seams[seam_group[index]].second].enable_scarf_seam ? 1 : 0; + count++; + } + } + layers[seams[seam_group[idx]].first].points[seams[seam_group[idx]].second].enable_scarf_seam = (sum / count) >= 0.5 ? true : false; + } + } + } +} + void SeamPlacer::init(const Print &print, std::function throw_if_canceled_func) { using namespace SeamPlacerImpl; @@ -1311,13 +1391,21 @@ void SeamPlacer::init(const Print &print, std::function throw_if_can BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: align_seam_points : end"; } + //check if enable scarf seam for each seam point + if (configured_seam_preference == spAligned || configured_seam_preference == spRear) { + BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: check_enable_scarf_seam : start"; + //find seam lines and get angle imformation + filter_scarf_seam_switch_by_angle(po->config().scarf_angle_threshold / 180.0f * PI, m_seam_per_object[po].layers); + //filter scarf seam setting with gaussian filter + BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: check_enable_scarf_seam : end"; + } #ifdef DEBUG_FILES debug_export_points(m_seam_per_object[po].layers, po->bounding_box(), comparator); #endif } } -void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos) const +void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos, bool &satisfy_angle_threshold) const { using namespace SeamPlacerImpl; const PrintObject *po = layer->object(); @@ -1343,10 +1431,12 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern if (const Perimeter &perimeter = layer_perimeters.points[closest_perimeter_point_index].perimeter; perimeter.finalized) { seam_position = perimeter.final_seam_position; seam_index = perimeter.seam_index; + satisfy_angle_threshold = layer_perimeters.points[seam_index].enable_scarf_seam; } else { seam_index = po->config().seam_position == spNearest ? pick_nearest_seam_point_index(layer_perimeters.points, perimeter.start_index, unscaled(last_pos)) : perimeter.seam_index; seam_position = layer_perimeters.points[seam_index].position; + satisfy_angle_threshold = layer_perimeters.points[seam_index].enable_scarf_seam; } Point seam_point = Point::new_scale(seam_position.x(), seam_position.y()); diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index 751aeaf..2e84eeb 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -69,7 +69,7 @@ struct Perimeter struct SeamCandidate { SeamCandidate(const Vec3f &pos, Perimeter &perimeter, float local_ccw_angle, EnforcedBlockedSeamPoint type) - : position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), embedded_distance(0.0f), local_ccw_angle(local_ccw_angle), type(type), central_enforcer(false) + : position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), embedded_distance(0.0f), local_ccw_angle(local_ccw_angle), type(type), central_enforcer(false), enable_scarf_seam(false),is_grouped(false) {} const Vec3f position; // pointer to Perimeter loop of this point. It is shared across all points of the loop @@ -82,6 +82,8 @@ struct SeamCandidate float local_ccw_angle; EnforcedBlockedSeamPoint type; bool central_enforcer; // marks this candidate as central point of enforced segment on the perimeter - important for alignment + bool enable_scarf_seam; // marks this candidate as a candidate for scarf seam + bool is_grouped; }; struct SeamCandidateCoordinateFunctor @@ -147,7 +149,7 @@ public: void init(const Print &print, std::function throw_if_canceled_func); - void place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos) const; + void place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos, bool &satisfy_angle_threshold) const; private: void gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info, const SeamPosition configured_seam_preference); @@ -160,6 +162,8 @@ private: const size_t layer_idx, const float max_distance, const SeamPlacerImpl::SeamComparator & comparator) const; + std::vector> gather_all_seams_of_object(const std::vector &layers); + void filter_scarf_seam_switch_by_angle(const float &angle, std::vector &layers); }; } // namespace Slic3r diff --git a/src/libslic3r/GCode/Smoothing.cpp b/src/libslic3r/GCode/Smoothing.cpp new file mode 100644 index 0000000..e76d2d5 --- /dev/null +++ b/src/libslic3r/GCode/Smoothing.cpp @@ -0,0 +1,224 @@ +#include "Smoothing.hpp" + +namespace Slic3r { + +void SmoothCalculator::build_node(std::vector & wall_collection, + const std::vector & object_label, + const std::vector &per_extruder_adjustments) +{ + if (per_extruder_adjustments.empty()) + return; + // QDS: update outwall feedrate + // update feedrate of outwall after initial cooling process + // initial and arrange node collection seq + for (size_t object_idx = 0; object_idx < object_label.size(); ++object_idx) { + OutwallCollection object_level; + object_level.object_id = object_label[object_idx]; + wall_collection.push_back(object_level); + } + + for (size_t extruder_idx = 0; extruder_idx < per_extruder_adjustments.size(); ++extruder_idx) { + const PerExtruderAdjustments &extruder_adjustments = per_extruder_adjustments[extruder_idx]; + for (size_t line_idx = 0; line_idx < extruder_adjustments.lines.size(); ++line_idx) { + const CoolingLine &line = extruder_adjustments.lines[line_idx]; + if (line.outwall_smooth_mark) { + // search node id + if (wall_collection[line.object_id].cooling_nodes.count(line.cooling_node_id) == 0) { + CoolingNode node; + wall_collection[line.object_id].cooling_nodes.emplace(line.cooling_node_id, node); + } + + CoolingNode &node = wall_collection[line.object_id].cooling_nodes[line.cooling_node_id]; + if (line.type & CoolingLine::TYPE_EXTERNAL_PERIMETER) { + node.outwall_line.emplace_back(line_idx, extruder_idx); + if (node.max_feedrate < line.feedrate) { + node.max_feedrate = line.feedrate; + node.filter_feedrate = node.max_feedrate; + } + } + + } + } + } +} + + +static void exclude_participate_in_speed_slowdown(std::vector> & lines, + std::vector &per_extruder_adjustments, + CoolingNode & node) +{ + // QDS: add protect, feedrate will be 0 if the outwall is overhang. just apply not adjust flage + bool apply_speed = node.max_feedrate > 0 && node.filter_feedrate > 0; + if (apply_speed) node.rate = node.filter_feedrate / node.max_feedrate; + + for (std::pair line_pos : lines) { + CoolingLine &line = per_extruder_adjustments[line_pos.second].lines[line_pos.first]; + if (apply_speed && line.feedrate > node.filter_feedrate) { + line.feedrate = node.filter_feedrate; + line.slowdown = true; + } + + // not adjust outwal line speed + line.type = line.type & (~CoolingLine::TYPE_ADJUSTABLE); + // update time cost + if (line.feedrate == 0 || line.length == 0) + line.time = 0; + else + line.time = line.length / line.feedrate; + } +} + +float SmoothCalculator::recaculate_layer_time(int layer_id, std::vector &extruder_adjustments) +{ + // rewrite feedrate + for (size_t obj_id = 0; obj_id < layers_wall_collection[layer_id].size(); ++obj_id) { + for (size_t node_id = 0; node_id < layers_wall_collection[layer_id][obj_id].cooling_nodes.size(); ++node_id) { + CoolingNode &node = layers_wall_collection[layer_id][obj_id].cooling_nodes[node_id]; + // set outwall speed + exclude_participate_in_speed_slowdown(node.outwall_line, extruder_adjustments, node); + } + } + + float layer_time = 0; + for (PerExtruderAdjustments extruder : extruder_adjustments) { + layer_time += extruder.collection_line_times_of_extruder(); + } + + return layer_time; +}; + +void SmoothCalculator::init_object_node_range() +{ + for (size_t object_id = 0; object_id < objects_node_range.size(); ++object_id) { + for (size_t layer_id = 1; layer_id < layers_wall_collection.size(); ++layer_id) { + const OutwallCollection &each_object = layers_wall_collection[layer_id][object_id]; + auto it = each_object.cooling_nodes.begin(); + while (it != each_object.cooling_nodes.end()) { + if (objects_node_range[object_id].count(it->first) == 0) { + objects_node_range[object_id].emplace(it->first, std::pair(layer_id, layer_id)); + } else { + objects_node_range[object_id][it->first].second = layer_id; + } + it++; + } + } + } +} + +void SmoothCalculator::smooth_layer_speed() +{ + init_object_node_range(); + + for (size_t obj_id = 0; obj_id < objects_node_range.size(); ++obj_id) { + auto it = objects_node_range[obj_id].begin(); + while (it != objects_node_range[obj_id].end()) { + int step_count = 0; + while (step_count < max_steps_count && speed_filter_continue(obj_id, it->first)) { + step_count++; + layer_speed_filter(obj_id, it->first); + } + it++; + } + } +} + +void SmoothCalculator::layer_speed_filter(const int object_id, const int node_id) +{ + int start_pos = guassian_filter.size() / 2; + // first layer don't need to be smoothed + int layer_start = objects_node_range[object_id][node_id].first; + int layer_end = objects_node_range[object_id][node_id].second; + + // QDS: some layers may empty as the support has indenpendent layer + for (int layer_id = layer_start; layer_id <= layer_end; ++layer_id) { + if (layers_wall_collection[layer_id].empty()) continue; + + if (layers_wall_collection[layer_id][object_id].cooling_nodes.count(node_id) == 0) break; + + CoolingNode &node = layers_wall_collection[layer_id][object_id].cooling_nodes[node_id]; + + if (node.outwall_line.empty()) continue; + + double conv_sum = 0; + for (int filter_pos_idx = 0; filter_pos_idx < guassian_filter.size(); ++filter_pos_idx) { + int remap_data_pos = layer_id - start_pos + filter_pos_idx; + + if (remap_data_pos < layer_start) + remap_data_pos = layer_start; + else if (remap_data_pos > layer_end) + remap_data_pos = layer_end; + + // some node may not start at layer 1 + double remap_data = node.filter_feedrate; + if (!layers_wall_collection[remap_data_pos][object_id].cooling_nodes[node_id].outwall_line.empty()) + remap_data = layers_wall_collection[remap_data_pos][object_id].cooling_nodes[node_id].filter_feedrate; + + conv_sum += guassian_filter[filter_pos_idx] * remap_data; + } + double filter_res = conv_sum / filter_sum; + if (filter_res < node.filter_feedrate) node.filter_feedrate = filter_res; + } +} + +bool SmoothCalculator::speed_filter_continue(const int object_id, const int node_id) +{ + int layer_id = objects_node_range[object_id][node_id].first; + int layer_end = objects_node_range[object_id][node_id].second; + + // QDS: some layers may empty as the support has indenpendent layer + for (; layer_id < layer_end; ++layer_id) { + if (std::abs(layers_wall_collection[layer_id + 1][object_id].cooling_nodes[node_id].filter_feedrate - + layers_wall_collection[layer_id][object_id].cooling_nodes[node_id].filter_feedrate) > guassian_stop_threshold) + return true; + } + return false; +} + +void SmoothCalculator::filter_layer_time() +{ + int start_pos = guassian_filter.size() / 2; + // first layer don't need to be smoothed + for (int layer_id = 1; layer_id < layers_cooling_time.size(); ++layer_id) { + if (layers_cooling_time[layer_id] > layer_time_smoothing_threshold) continue; + + double conv_sum = 0; + for (int filter_pos_idx = 0; filter_pos_idx < guassian_filter.size(); ++filter_pos_idx) { + int remap_data_pos = layer_id - start_pos + filter_pos_idx; + + if (remap_data_pos < 1) + remap_data_pos = 1; + else if (remap_data_pos > layers_cooling_time.size() - 1) + remap_data_pos = layers_cooling_time.size() - 1; + + // if the layer time big enough, surface defact will disappear + double data_temp = layers_cooling_time[remap_data_pos] > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : layers_cooling_time[remap_data_pos]; + + conv_sum += guassian_filter[filter_pos_idx] * data_temp; + } + double filter_res = conv_sum / filter_sum; + filter_res = filter_res > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : filter_res; + if (filter_res > layers_cooling_time[layer_id]) layers_cooling_time[layer_id] = filter_res; + } +} + +bool SmoothCalculator::layer_time_filter_continue() +{ + for (int layer_id = 1; layer_id < layers_cooling_time.size() - 1; ++layer_id) { + double layer_time = layers_cooling_time[layer_id] > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : layers_cooling_time[layer_id]; + double layer_time_cmp = layers_cooling_time[layer_id + 1] > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : layers_cooling_time[layer_id + 1]; + + if (std::abs(layer_time - layer_time_cmp) > guassian_layer_time_stop_threshold) return true; + } + return false; +} + +void SmoothCalculator::smooth_layer_time() +{ + int step_count = 0; + while (step_count < max_steps_count && layer_time_filter_continue()) { + step_count++; + filter_layer_time(); + } +} + +} // namespace Slic3r \ No newline at end of file diff --git a/src/libslic3r/GCode/Smoothing.hpp b/src/libslic3r/GCode/Smoothing.hpp new file mode 100644 index 0000000..fad1389 --- /dev/null +++ b/src/libslic3r/GCode/Smoothing.hpp @@ -0,0 +1,103 @@ +#ifndef slic3r_Smoothing_hpp_ +#define slic3r_Smoothing_hpp_ +#include "../libslic3r.h" +#include +#include + +namespace Slic3r { + +static const int guassian_window_size = 11; +static const int guassian_r = 2; +static const int guassian_stop_threshold = 5; +static const float guassian_layer_time_stop_threshold = 3.0; +static const int max_steps_count = 1000; + +struct CoolingNode +{ + // extruder pos, line pos; + std::vector> outwall_line; + float max_feedrate = 0; + float filter_feedrate = 0; + double rate = 1; +}; + +struct OutwallCollection +{ + int object_id; + std::map cooling_nodes; +}; + +class SmoothCalculator +{ + +public: + std::vector>> objects_node_range; + std::vector> layers_wall_collection; + std::vector layers_cooling_time; + + SmoothCalculator(const int objects_size, const double gap_limit) : layer_time_smoothing_threshold(gap_limit) + { + guassian_filter_generator(); + objects_node_range.resize(objects_size); + } + + SmoothCalculator(const int objects_size) + { + guassian_filter_generator(); + objects_node_range.resize(objects_size); + } + + void append_data(const std::vector &wall_collection, float cooling_time) + { + layers_wall_collection.push_back(wall_collection); + layers_cooling_time.push_back(cooling_time); + } + + void append_data(const std::vector &wall_collection) + { + layers_wall_collection.push_back(wall_collection); + } + + void build_node(std::vector &wall_collection, const std::vector &object_label, const std::vector &per_extruder_adjustments); + + float recaculate_layer_time(int layer_id, std::vector &extruder_adjustments); + + void smooth_layer_speed(); + +private: + // guassian filter + double guassian_function(double x, double r) { + return exp(-x * x / (2 * r * r)) / (r * sqrt(2 * PI)); + } + + void guassian_filter_generator() { + double r = guassian_r; + int half_win_size = guassian_window_size / 2; + for (int start = -half_win_size; start <= half_win_size; ++start) { + double y = guassian_function(start, r); + filter_sum += y; + guassian_filter.push_back(y); + } + } + + void init_object_node_range(); + + // filter the data + void layer_speed_filter(const int object_id, const int node_id); + + bool speed_filter_continue(const int object_id, const int node_id); + + // filter the data + void filter_layer_time(); + + bool layer_time_filter_continue(); + void smooth_layer_time(); + + std::vector guassian_filter; + double filter_sum = .0f; + float layer_time_smoothing_threshold = 30.0f; +}; + +} // namespace Slic3r + +#endif \ No newline at end of file diff --git a/src/libslic3r/GCode/ThumbnailData.hpp b/src/libslic3r/GCode/ThumbnailData.hpp index 87f123d..673b30f 100644 --- a/src/libslic3r/GCode/ThumbnailData.hpp +++ b/src/libslic3r/GCode/ThumbnailData.hpp @@ -38,6 +38,9 @@ struct ThumbnailsParams bool show_bed; bool transparent_background; int plate_id; + bool use_plate_box{true}; + bool post_processing_enabled{ false }; + Vec4f background_color{ 0.0f, 0.0f, 0.0f, 0.0f }; }; typedef std::function ThumbnailsGeneratorCallback; diff --git a/src/libslic3r/GCode/ToolOrderUtils.cpp b/src/libslic3r/GCode/ToolOrderUtils.cpp new file mode 100644 index 0000000..d3243b5 --- /dev/null +++ b/src/libslic3r/GCode/ToolOrderUtils.cpp @@ -0,0 +1,759 @@ +#include "ToolOrderUtils.hpp" +#include +#include +#include +#include +#include + +namespace Slic3r +{ + struct MinCostMaxFlow { + public: + struct Edge { + int from, to, capacity, cost, flow; + Edge(int u, int v, int cap, int cst) : from(u), to(v), capacity(cap), cost(cst), flow(0) {} + }; + + std::vector solve(); + void add_edge(int from, int to, int capacity, int cost); + bool spfa(int source, int sink); + int get_distance(int idx_in_left, int idx_in_right); + + std::vector> matrix; + std::vector l_nodes; + std::vector r_nodes; + std::vector edges; + std::vector> adj; + + int total_nodes{ -1 }; + int source_id{ -1 }; + int sink_id{ -1 }; + }; + + std::vector MinCostMaxFlow::solve() + { + while (spfa(source_id, sink_id)); + + std::vectormatching(l_nodes.size(), MaxFlowGraph::INVALID_ID); + // to get the match info, just traverse the left nodes and + // check the edges with flow > 0 and linked to right nodes + for (int u = 0; u < l_nodes.size(); ++u) { + for (int eid : adj[u]) { + Edge& e = edges[eid]; + if (e.flow > 0 && e.to >= l_nodes.size() && e.to < l_nodes.size() + r_nodes.size()) + matching[e.from] = r_nodes[e.to - l_nodes.size()]; + } + } + + return matching; + } + + void MinCostMaxFlow::add_edge(int from, int to, int capacity, int cost) + { + adj[from].emplace_back(edges.size()); + edges.emplace_back(from, to, capacity, cost); + //also add reverse edge ,set capacity to zero,cost to negative + adj[to].emplace_back(edges.size()); + edges.emplace_back(to, from, 0, -cost); + } + + bool MinCostMaxFlow::spfa(int source, int sink) + { + std::vectordist(total_nodes, MaxFlowGraph::INF); + std::vectorin_queue(total_nodes, false); + std::vectorflow(total_nodes, MaxFlowGraph::INF); + std::vectorprev(total_nodes, 0); + + std::queueq; + q.push(source); + in_queue[source] = true; + dist[source] = 0; + + while (!q.empty()) { + int now_at = q.front(); + q.pop(); + in_queue[now_at] = false; + + for (auto eid : adj[now_at]) //traverse all linked edges + { + Edge& e = edges[eid]; + if (e.flowdist[now_at] + e.cost) { + dist[e.to] = dist[now_at] + e.cost; + prev[e.to] = eid; + flow[e.to] = std::min(flow[now_at], e.capacity - e.flow); + if (!in_queue[e.to]) { + q.push(e.to); + in_queue[e.to] = true; + } + } + } + } + + if (dist[sink] == MaxFlowGraph::INF) + return false; + + int now_at = sink; + while (now_at != source) { + int prev_edge = prev[now_at]; + edges[prev_edge].flow += flow[sink]; + edges[prev_edge ^ 1].flow -= flow[sink]; + now_at = edges[prev_edge].from; + } + + return true; + } + + int MinCostMaxFlow::get_distance(int idx_in_left, int idx_in_right) + { + if (l_nodes[idx_in_left] == -1) { + return 0; + //TODO: test more here + int sum = 0; + for (int i = 0; i < matrix.size(); ++i) + sum += matrix[i][idx_in_right]; + sum /= matrix.size(); + return -sum; + } + + return matrix[l_nodes[idx_in_left]][r_nodes[idx_in_right]]; + } + + + MaxFlowSolver::MaxFlowSolver(const std::vector& u_nodes, const std::vector& v_nodes, + const std::unordered_map>& uv_link_limits, + const std::unordered_map>& uv_unlink_limits, + const std::vector& u_capacity, + const std::vector& v_capacity) + { + assert(u_capacity.empty() || u_capacity.size() == u_nodes.size()); + assert(v_capacity.empty() || v_capacity.size() == v_nodes.size()); + l_nodes = u_nodes; + r_nodes = v_nodes; + total_nodes = u_nodes.size() + v_nodes.size() + 2; + source_id = total_nodes - 2; + sink_id = total_nodes - 1; + + adj.resize(total_nodes); + + // add edge from source to left nodes + for (int idx = 0; idx < l_nodes.size(); ++idx) { + int capacity = u_capacity.empty() ? 1 : u_capacity[idx]; + add_edge(source_id, idx, capacity); + } + // add edge from right nodes to sink node + for (int idx = 0; idx < r_nodes.size(); ++idx) { + int capacity = v_capacity.empty() ? 1 : v_capacity[idx]; + add_edge(l_nodes.size() + idx, sink_id, capacity); + } + + // add edge from left nodes to right nodes + for (int i = 0; i < l_nodes.size(); ++i) { + int from_idx = i; + // process link limits , i can only link to uv_link_limits + if (auto iter = uv_link_limits.find(i); iter != uv_link_limits.end()) { + for (auto r_id : iter->second) + add_edge(from_idx, l_nodes.size() + r_id, 1); + continue; + } + // process unlink limits + std::optional> unlink_limits; + if (auto iter = uv_unlink_limits.find(i); iter != uv_unlink_limits.end()) + unlink_limits = iter->second; + + for (int j = 0; j < r_nodes.size(); ++j) { + // check whether i can link to j + if (unlink_limits.has_value() && std::find(unlink_limits->begin(), unlink_limits->end(), j) != unlink_limits->end()) + continue; + add_edge(from_idx, l_nodes.size() + j, 1); + } + } + } + + void MaxFlowSolver::add_edge(int from, int to, int capacity) + { + adj[from].emplace_back(edges.size()); + edges.emplace_back(from, to, capacity); + //also add reverse edge ,set capacity to zero + adj[to].emplace_back(edges.size()); + edges.emplace_back(to, from, 0); + } + + std::vector MaxFlowSolver::solve() { + std::vector augment; + std::vector previous(total_nodes, 0); + while (1) { + std::vector(total_nodes, 0).swap(augment); + std::queue travel; + travel.push(source_id); + augment[source_id] = MaxFlowGraph::INF; + while (!travel.empty()) { + int from = travel.front(); + travel.pop(); + + // traverse all linked edges + for (int i = 0; i < adj[from].size(); ++i) { + int eid = adj[from][i]; + Edge& tmp = edges[eid]; + if (augment[tmp.to] == 0 && tmp.capacity > tmp.flow) { + previous[tmp.to] = eid; + augment[tmp.to] = std::min(augment[from], tmp.capacity - tmp.flow); + travel.push(tmp.to); + } + } + + // already find an extend path, stop and do update + if (augment[sink_id] != 0) + break; + } + // no longer have extend path + if (augment[sink_id] == 0) + break; + + for (int i = sink_id; i != source_id; i = edges[previous[i]].from) { + edges[previous[i]].flow += augment[sink_id]; + edges[previous[i] ^ 1].flow -= augment[sink_id]; + } + } + + std::vector matching(l_nodes.size(), MaxFlowGraph::INVALID_ID); + // to get the match info, just traverse the left nodes and + // check the edge with flow > 0 and linked to right nodes + for (int u = 0; u < l_nodes.size(); ++u) { + for (int eid : adj[u]) { + Edge& e = edges[eid]; + if (e.flow > 0 && e.to >= l_nodes.size() && e.to < l_nodes.size() + r_nodes.size()) + matching[e.from] = r_nodes[e.to - l_nodes.size()]; + } + } + return matching; + } + + GeneralMinCostSolver::~GeneralMinCostSolver() + { + } + + GeneralMinCostSolver::GeneralMinCostSolver(const std::vector>& matrix_, const std::vector& u_nodes, const std::vector& v_nodes) + { + m_solver = std::make_unique(); + m_solver->matrix = matrix_;; + m_solver->l_nodes = u_nodes; + m_solver->r_nodes = v_nodes; + + m_solver->total_nodes = u_nodes.size() + v_nodes.size() + 2; + + m_solver->source_id =m_solver->total_nodes - 2; + m_solver->sink_id = m_solver->total_nodes - 1; + + m_solver->adj.resize(m_solver->total_nodes); + + + // add edge from source to left nodes,cost to 0 + for (int i = 0; i < m_solver->l_nodes.size(); ++i) + m_solver->add_edge(m_solver->source_id, i, 1, 0); + + // add edge from right nodes to sink,cost to 0 + for (int i = 0; i < m_solver->r_nodes.size(); ++i) + m_solver->add_edge(m_solver->l_nodes.size() + i, m_solver->sink_id, 1, 0); + + // add edge from left node to right nodes + for (int i = 0; i < m_solver->l_nodes.size(); ++i) { + int from_idx = i; + for (int j = 0; j < m_solver->r_nodes.size(); ++j) { + int to_idx = m_solver->l_nodes.size() + j; + m_solver->add_edge(from_idx, to_idx, 1, m_solver->get_distance(i, j)); + } + } + } + + std::vector GeneralMinCostSolver::solve() { + return m_solver->solve(); + } + + MinFlushFlowSolver::~MinFlushFlowSolver() + { + } + + MinFlushFlowSolver::MinFlushFlowSolver(const std::vector>& matrix_, const std::vector& u_nodes, const std::vector& v_nodes, + const std::unordered_map>& uv_link_limits, + const std::unordered_map>& uv_unlink_limits, + const std::vector& u_capacity, + const std::vector& v_capacity) + { + assert(u_capacity.empty() || u_capacity.size() == u_nodes.size()); + assert(v_capacity.empty() || v_capacity.size() == v_nodes.size()); + m_solver = std::make_unique(); + m_solver->matrix = matrix_;; + m_solver->l_nodes = u_nodes; + m_solver->r_nodes = v_nodes; + + m_solver->total_nodes = u_nodes.size() + v_nodes.size() + 2; + + m_solver->source_id =m_solver->total_nodes - 2; + m_solver->sink_id = m_solver->total_nodes - 1; + + m_solver->adj.resize(m_solver->total_nodes); + + // add edge from source to left nodes,cost to 0 + for (int i = 0; i < m_solver->l_nodes.size(); ++i) { + int capacity = u_capacity.empty() ? 1 : u_capacity[i]; + m_solver->add_edge(m_solver->source_id, i, capacity, 0); + } + // add edge from right nodes to sink,cost to 0 + for (int i = 0; i < m_solver->r_nodes.size(); ++i) { + int capacity = v_capacity.empty() ? 1 : v_capacity[i]; + m_solver->add_edge(m_solver->l_nodes.size() + i, m_solver->sink_id, capacity, 0); + } + // add edge from left node to right nodes + for (int i = 0; i < m_solver->l_nodes.size(); ++i) { + int from_idx = i; + // process link limits, i can only link to link_limits + if (auto iter = uv_link_limits.find(i); iter != uv_link_limits.end()) { + for (auto r_id : iter->second) + m_solver->add_edge(from_idx, m_solver->l_nodes.size() + r_id, 1, m_solver->get_distance(i, r_id)); + continue; + } + + // process unlink limits, check whether i can link to j + std::optional> unlink_limits; + if (auto iter = uv_unlink_limits.find(i); iter != uv_unlink_limits.end()) + unlink_limits = iter->second; + for (int j = 0; j < m_solver->r_nodes.size(); ++j) { + if (unlink_limits.has_value() && std::find(unlink_limits->begin(), unlink_limits->end(), j) != unlink_limits->end()) + continue; + m_solver->add_edge(from_idx, m_solver->l_nodes.size() + j, 1, m_solver->get_distance(i, j)); + } + } + } + + std::vector MinFlushFlowSolver::solve() { + return m_solver->solve(); + } + + MatchModeGroupSolver::~MatchModeGroupSolver() + { + } + + MatchModeGroupSolver::MatchModeGroupSolver(const std::vector>& matrix_, const std::vector& u_nodes, const std::vector& v_nodes, const std::vector& v_capacity, const std::unordered_map>& uv_unlink_limits) + { + assert(v_nodes.size() == v_capacity.size()); + m_solver = std::make_unique(); + m_solver->matrix = matrix_;; + m_solver->l_nodes = u_nodes; + m_solver->r_nodes = v_nodes; + + m_solver->total_nodes = u_nodes.size() + v_nodes.size() + 2; + + m_solver->source_id = m_solver->total_nodes - 2; + m_solver->sink_id = m_solver->total_nodes - 1; + + m_solver->adj.resize(m_solver->total_nodes); + + + // add edge from source to left nodes,cost to 0 + for (int i = 0; i < m_solver->l_nodes.size(); ++i) + m_solver->add_edge(m_solver->source_id, i, 1, 0); + + // add edge from right nodes to sink,cost to 0 + for (int i = 0; i < m_solver->r_nodes.size(); ++i) + m_solver->add_edge(m_solver->l_nodes.size() + i, m_solver->sink_id, v_capacity[i], 0); + + // add edge from left node to right nodes + for (int i = 0; i < m_solver->l_nodes.size(); ++i) { + int from_idx = i; + + // process unlink limits, check whether i can link to j + std::optional> unlink_limits; + if (auto iter = uv_unlink_limits.find(i); iter != uv_unlink_limits.end()) + unlink_limits = iter->second; + for (int j = 0; j < m_solver->r_nodes.size(); ++j) { + if (unlink_limits.has_value() && std::find(unlink_limits->begin(), unlink_limits->end(), j) != unlink_limits->end()) + continue; + m_solver->add_edge(from_idx, m_solver->l_nodes.size() + j, 1, m_solver->get_distance(i, j)); + } + } + } + + std::vector MatchModeGroupSolver::solve() { + return m_solver->solve(); + } + + //solve the problem by searching the least flush of current filament + static std::vector solve_extruder_order_with_greedy(const std::vector>& wipe_volumes, + const std::vector curr_layer_extruders, + const std::optional& start_extruder_id, + float* min_cost) + { + float cost = 0; + std::vector best_seq; + std::vectoris_visited(curr_layer_extruders.size(), false); + std::optionalprev_filament = start_extruder_id; + int idx = curr_layer_extruders.size(); + while (idx > 0) { + if (!prev_filament) { + auto iter = std::find_if(is_visited.begin(), is_visited.end(), [](auto item) {return item == 0; }); + assert(iter != is_visited.end()); + prev_filament = curr_layer_extruders[iter - is_visited.begin()]; + } + int target_idx = -1; + int target_cost = std::numeric_limits::max(); + for (size_t k = 0; k < is_visited.size(); ++k) { + if (!is_visited[k]) { + if (wipe_volumes[*prev_filament][curr_layer_extruders[k]] < target_cost) { + target_idx = k; + target_cost = wipe_volumes[*prev_filament][curr_layer_extruders[k]]; + } + } + } + assert(target_idx != -1); + cost += target_cost; + best_seq.emplace_back(curr_layer_extruders[target_idx]); + prev_filament = curr_layer_extruders[target_idx]; + is_visited[target_idx] = true; + idx -= 1; + } + if (min_cost) + *min_cost = cost; + return best_seq; + } + + //solve the problem by forcasting one layer + static std::vector solve_extruder_order_with_forcast(const std::vector>& wipe_volumes, + std::vector curr_layer_extruders, + std::vector next_layer_extruders, + const std::optional& start_extruder_id, + float* min_cost) + { + std::sort(curr_layer_extruders.begin(), curr_layer_extruders.end()); + std::sort(next_layer_extruders.begin(), next_layer_extruders.end()); + float best_cost = std::numeric_limits::max(); + std::vectorbest_seq; + + do { + std::optionalprev_extruder_1 = start_extruder_id; + float curr_layer_cost = 0; + for (size_t idx = 0; idx < curr_layer_extruders.size(); ++idx) { + if (prev_extruder_1) + curr_layer_cost += wipe_volumes[*prev_extruder_1][curr_layer_extruders[idx]]; + prev_extruder_1 = curr_layer_extruders[idx]; + } + if (curr_layer_cost > best_cost) + continue; + do { + std::optionalprev_extruder_2 = prev_extruder_1; + float total_cost = curr_layer_cost; + + for (size_t idx = 0; idx < next_layer_extruders.size(); ++idx) { + if (prev_extruder_2) + total_cost += wipe_volumes[*prev_extruder_2][next_layer_extruders[idx]]; + prev_extruder_2 = next_layer_extruders[idx]; + } + + if (total_cost < best_cost) { + best_cost = total_cost; + best_seq = curr_layer_extruders; + } + } while (std::next_permutation(next_layer_extruders.begin(), next_layer_extruders.end())); + } while (std::next_permutation(curr_layer_extruders.begin(), curr_layer_extruders.end())); + + if (min_cost) { + float real_cost = 0; + std::optionalprev_extruder = start_extruder_id; + for (size_t idx = 0; idx < best_seq.size(); ++idx) { + if (prev_extruder) + real_cost += wipe_volumes[*prev_extruder][best_seq[idx]]; + prev_extruder = best_seq[idx]; + } + *min_cost = real_cost; + } + return best_seq; + } + + // Shortest hamilton path problem + static std::vector solve_extruder_order(const std::vector>& wipe_volumes, + std::vector all_extruders, + std::optional start_extruder_id, + float* min_cost) + { + bool add_start_extruder_flag = false; + + if (start_extruder_id) { + auto start_iter = std::find(all_extruders.begin(), all_extruders.end(), start_extruder_id); + if (start_iter == all_extruders.end()) + all_extruders.insert(all_extruders.begin(), *start_extruder_id), add_start_extruder_flag = true; + else + std::swap(*all_extruders.begin(), *start_iter); + } + else { + start_extruder_id = all_extruders.front(); + } + + unsigned int iterations = (1 << all_extruders.size()); + unsigned int final_state = iterations - 1; + std::vector>cache(iterations, std::vector(all_extruders.size(), 0x7fffffff)); + std::vector>prev(iterations, std::vector(all_extruders.size(), -1)); + cache[1][0] = 0.; + for (unsigned int state = 0; state < iterations; ++state) { + if (state & 1) { + for (unsigned int target = 0; target < all_extruders.size(); ++target) { + if (state >> target & 1) { + for (unsigned int mid_point = 0; mid_point < all_extruders.size(); ++mid_point) { + if (state >> mid_point & 1) { + auto tmp = cache[state - (1 << target)][mid_point] + wipe_volumes[all_extruders[mid_point]][all_extruders[target]]; + if (cache[state][target] > tmp) { + cache[state][target] = tmp; + prev[state][target] = mid_point; + } + } + } + } + } + } + } + + //get res + float cost = std::numeric_limits::max(); + int final_dst = 0; + for (unsigned int dst = 0; dst < all_extruders.size(); ++dst) { + if (all_extruders[dst] != start_extruder_id && cost > cache[final_state][dst]) { + cost = cache[final_state][dst]; + if (min_cost) + *min_cost = cost; + final_dst = dst; + } + } + + std::vectorpath; + unsigned int curr_state = final_state; + int curr_point = final_dst; + while (curr_point != -1) { + path.emplace_back(all_extruders[curr_point]); + auto mid_point = prev[curr_state][curr_point]; + curr_state -= (1 << curr_point); + curr_point = mid_point; + }; + + if (add_start_extruder_flag) + path.pop_back(); + + std::reverse(path.begin(), path.end()); + return path; + } + + + + template + static std::vector collect_filaments_in_groups(const std::unordered_set& group, const std::vector& filament_list) { + std::vectorret; + ret.reserve(group.size()); + for (auto& f : filament_list) { + if (auto iter = group.find(f); iter != group.end()) + ret.emplace_back(static_cast(f)); + } + return ret; + } + + // get best filament order of single nozzle + std::vector get_extruders_order(const std::vector>& wipe_volumes, + const std::vector& curr_layer_extruders, + const std::vector& next_layer_extruders, + const std::optional& start_extruder_id, + bool use_forcast, + float* cost) + { + if (curr_layer_extruders.empty()) { + if (cost) + *cost = 0; + return curr_layer_extruders; + } + if (curr_layer_extruders.size() == 1) { + if (cost) { + *cost = 0; + if (start_extruder_id) + *cost = wipe_volumes[*start_extruder_id][curr_layer_extruders[0]]; + } + return curr_layer_extruders; + } + + if (use_forcast) + return solve_extruder_order_with_forcast(wipe_volumes, curr_layer_extruders, next_layer_extruders, start_extruder_id, cost); + else if (curr_layer_extruders.size() <= 20) + return solve_extruder_order(wipe_volumes, curr_layer_extruders, start_extruder_id, cost); + else + return solve_extruder_order_with_greedy(wipe_volumes, curr_layer_extruders, start_extruder_id, cost); + } + + + int reorder_filaments_for_minimum_flush_volume(const std::vector& filament_lists, + const std::vector& filament_maps, + const std::vector>& layer_filaments, + const std::vector& flush_matrix, + std::optional&)>> get_custom_seq, + std::vector>* filament_sequences) + { + //only when layer filament num <= 5,we do forcast + constexpr int max_n_with_forcast = 5; + int cost = 0; + std::vector>groups(2); //save the grouped filaments + std::vector>> layer_sequences(2); //save the reordered filament sequence by group + std::map> custom_layer_sequence_map; // save the filament sequences of custom layer + + // group the filament + for (int i = 0; i < filament_maps.size(); ++i) { + if (filament_maps[i] == 0) + groups[0].insert(filament_lists[i]); + if (filament_maps[i] == 1) + groups[1].insert(filament_lists[i]); + } + + // store custom layer sequence + for (size_t layer = 0; layer < layer_filaments.size(); ++layer) { + const auto& curr_lf = layer_filaments[layer]; + + std::vectorcustom_filament_seq; + if (get_custom_seq && (*get_custom_seq)(layer, custom_filament_seq) && !custom_filament_seq.empty()) { + std::vector unsign_custom_extruder_seq; + for (int extruder : custom_filament_seq) { + unsigned int unsign_extruder = static_cast(extruder) - 1; + auto it = std::find(curr_lf.begin(), curr_lf.end(), unsign_extruder); + if (it != curr_lf.end()) + unsign_custom_extruder_seq.emplace_back(unsign_extruder); + } + assert(curr_lf.size() == unsign_custom_extruder_seq.size()); + + custom_layer_sequence_map[layer] = unsign_custom_extruder_seq; + } + } + using uint128_t = boost::multiprecision::uint128_t; + auto extruders_to_hash_key = [](const std::vector& curr_layer_extruders, + const std::vector& next_layer_extruders, + const std::optional& prev_extruder, + bool use_forcast)->uint128_t + { + uint128_t hash_key = 0; + //31-0 bit define current layer extruder,63-32 bit define next layer extruder,95~64 define prev extruder + if (prev_extruder) + hash_key |= (uint128_t(1) << (64 + *prev_extruder)); + + if (use_forcast) { + for (auto item : next_layer_extruders) + hash_key |= (uint128_t(1) << (32 + item)); + } + + for (auto item : curr_layer_extruders) + hash_key |= (uint128_t(1) << item); + return hash_key; + }; + + + // get best layer sequence by group + for (size_t idx = 0; idx < groups.size(); ++idx) { + // case with one group + if (groups[idx].empty()) + continue; + std::optionalcurrent_extruder_id; + + std::unordered_map>> caches; + + for (size_t layer = 0; layer < layer_filaments.size(); ++layer) { + const auto& curr_lf = layer_filaments[layer]; + + if (auto iter = custom_layer_sequence_map.find(layer); iter != custom_layer_sequence_map.end()) { + auto sequence_in_group = collect_filaments_in_groups(groups[idx], iter->second); + + float tmp_cost = 0; + std::optionalprev = current_extruder_id; + for (auto& f : sequence_in_group) { + if (prev) { tmp_cost += flush_matrix[idx][*prev][f]; } + prev = f; + } + cost += tmp_cost; + + if (!sequence_in_group.empty()) + current_extruder_id = sequence_in_group.back(); + //insert an empty array + if (filament_sequences) + layer_sequences[idx].emplace_back(std::vector()); + + continue; + } + + std::vectorfilament_used_in_group = collect_filaments_in_groups(groups[idx], curr_lf); + + std::vectornext_lf; + if (layer + 1 < layer_filaments.size()) + next_lf = layer_filaments[layer + 1]; + std::vectorfilament_used_in_group_next_layer = collect_filaments_in_groups(groups[idx], next_lf); + + bool use_forcast = (filament_used_in_group.size() <= max_n_with_forcast && filament_used_in_group_next_layer.size() <= max_n_with_forcast); + float tmp_cost = 0; + std::vectorsequence; + uint128_t hash_key = extruders_to_hash_key(filament_used_in_group, filament_used_in_group_next_layer, current_extruder_id, use_forcast); + if (auto iter = caches.find(hash_key); iter != caches.end()) { + tmp_cost = iter->second.first; + sequence = iter->second.second; + } + else { + sequence = get_extruders_order(flush_matrix[idx], filament_used_in_group, filament_used_in_group_next_layer, current_extruder_id, use_forcast, &tmp_cost); + caches[hash_key] = { tmp_cost,sequence }; + } + + assert(sequence.size() == filament_used_in_group.size()); + + if (filament_sequences) + layer_sequences[idx].emplace_back(sequence); + + if (!sequence.empty()) + current_extruder_id = sequence.back(); + cost += tmp_cost; + } + } + + // get the final layer sequences + // if only have one group,we need to check whether layer sequence[idx] is valid + if (filament_sequences) { + filament_sequences->clear(); + filament_sequences->resize(layer_filaments.size()); + int last_group_id = 0; + //if last_group == 0,print group 0 first ,else print group 1 first + if (!custom_layer_sequence_map.empty()) { + const auto& first_layer = custom_layer_sequence_map.begin()->first; + const auto& first_layer_filaments = custom_layer_sequence_map.begin()->second; + assert(!first_layer_filaments.empty()); + + bool first_group = groups[0].count(first_layer_filaments.front()) ? 0 : 1; + last_group_id = (first_layer & 1) ? !first_group : first_group; + } + + for (size_t layer = 0; layer < layer_filaments.size(); ++layer) { + auto& curr_layer_seq = (*filament_sequences)[layer]; + if (custom_layer_sequence_map.find(layer) != custom_layer_sequence_map.end()) { + curr_layer_seq = custom_layer_sequence_map[layer]; + if (!curr_layer_seq.empty()) { + last_group_id = groups[0].count(curr_layer_seq.back()) ? 0 : 1; + } + continue; + } + if (last_group_id == 1) { + // try reuse the last group + if (!layer_sequences[1].empty() && !layer_sequences[1][layer].empty()) + curr_layer_seq.insert(curr_layer_seq.end(), layer_sequences[1][layer].begin(), layer_sequences[1][layer].end()); + if (!layer_sequences[0].empty() && !layer_sequences[0][layer].empty()) { + curr_layer_seq.insert(curr_layer_seq.end(), layer_sequences[0][layer].begin(), layer_sequences[0][layer].end()); + last_group_id = 0; // update last group id + } + } + else if(last_group_id == 0) { + if (!layer_sequences[0].empty() && !layer_sequences[0][layer].empty()) { + curr_layer_seq.insert(curr_layer_seq.end(), layer_sequences[0][layer].begin(), layer_sequences[0][layer].end()); + } + if (!layer_sequences[1].empty() && !layer_sequences[1][layer].empty()) { + curr_layer_seq.insert(curr_layer_seq.end(), layer_sequences[1][layer].begin(), layer_sequences[1][layer].end()); + last_group_id = 1; // update last group id + } + } + } + } + + return cost; + } +} diff --git a/src/libslic3r/GCode/ToolOrderUtils.hpp b/src/libslic3r/GCode/ToolOrderUtils.hpp new file mode 100644 index 0000000..26c9a01 --- /dev/null +++ b/src/libslic3r/GCode/ToolOrderUtils.hpp @@ -0,0 +1,114 @@ +#ifndef TOOL_ORDER_UTILS_HPP +#define TOOL_ORDER_UTILS_HPP + +#include +#include +#include +#include +#include +#include + +namespace Slic3r { + +using FlushMatrix = std::vector>; + +namespace MaxFlowGraph { + const int INF = std::numeric_limits::max(); + const int INVALID_ID = -1; +} + +class MaxFlowSolver +{ +private: + struct Edge { + int from, to, capacity, flow; + Edge(int u, int v, int cap) :from(u), to(v), capacity(cap), flow(0) {} + }; +public: + MaxFlowSolver(const std::vector& u_nodes, const std::vector& v_nodes, + const std::unordered_map>& uv_link_limits = {}, + const std::unordered_map>& uv_unlink_limits = {}, + const std::vector& u_capacity = {}, + const std::vector& v_capacity = {} + ); + std::vector solve(); + +private: + void add_edge(int from, int to, int capacity); + + int total_nodes; + int source_id; + int sink_id; + std::vectoredges; + std::vectorl_nodes; + std::vectorr_nodes; + std::vector>adj; +}; + + +struct MinCostMaxFlow; + +class GeneralMinCostSolver +{ +public: + GeneralMinCostSolver(const std::vector>& matrix_, + const std::vector& u_nodes, + const std::vector& v_nodes); + + std::vector solve(); + ~GeneralMinCostSolver(); +private: + std::unique_ptr m_solver; +}; + + +class MinFlushFlowSolver +{ +public: + MinFlushFlowSolver(const std::vector>& matrix_, + const std::vector& u_nodes, + const std::vector& v_nodes, + const std::unordered_map>& uv_link_limits = {}, + const std::unordered_map>& uv_unlink_limits = {}, + const std::vector& u_capacity = {}, + const std::vector& v_capacity = {} + ); + std::vector solve(); + ~MinFlushFlowSolver(); +private: + std::unique_ptr m_solver; +}; + + +class MatchModeGroupSolver +{ +public: + MatchModeGroupSolver(const std::vector>& matrix_, + const std::vector& u_nodes, + const std::vector& v_nodes, + const std::vector& v_capacity, + const std::unordered_map>& uv_unlink_limits = {}); + + std::vector solve(); + ~MatchModeGroupSolver(); +private: + std::unique_ptr m_solver; +}; + + +std::vector get_extruders_order(const std::vector> &wipe_volumes, + const std::vector &curr_layer_extruders, + const std::vector &next_layer_extruders, + const std::optional &start_extruder_id, + bool use_forcast = false, + float *cost = nullptr); + +int reorder_filaments_for_minimum_flush_volume(const std::vector &filament_lists, + const std::vector &filament_maps, + const std::vector> &layer_filaments, + const std::vector &flush_matrix, + std::optional &)>> get_custom_seq, + std::vector> *filament_sequences); + +} +#endif // !TOOL_ORDER_UTILS_HPP diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index a8830be..9ba4dec 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -3,6 +3,9 @@ #include "Layer.hpp" #include "ClipperUtils.hpp" #include "ParameterUtils.hpp" +#include "GCode/ToolOrderUtils.hpp" +#include "FilamentGroupUtils.hpp" +#include "I18N.hpp" // #define SLIC3R_DEBUG @@ -16,117 +19,31 @@ #include #include #include +#include #include namespace Slic3r { -const static bool g_wipe_into_objects = false; + //! macro used to mark string used at localization, + //! return same string + #ifndef _L + #define _L(s) Slic3r::I18N::translate(s) + #endif -// Shortest hamilton path problem -static std::vector solve_extruder_order(const std::vector>& wipe_volumes, std::vector all_extruders, std::optional start_extruder_id) + const static bool g_wipe_into_objects = false; + constexpr double similar_color_threshold_de2000 = 20.0; + +static std::setget_filament_by_type(const std::vector& used_filaments, const PrintConfig* print_config, const std::string& type) { - bool add_start_extruder_flag = false; - - if (start_extruder_id) { - auto start_iter = std::find(all_extruders.begin(), all_extruders.end(), start_extruder_id); - if (start_iter == all_extruders.end()) - all_extruders.insert(all_extruders.begin(), *start_extruder_id), add_start_extruder_flag = true; - else - std::swap(*all_extruders.begin(), *start_iter); + std::set target_filaments; + for (unsigned int filament_id : used_filaments) { + std::string filament_type = print_config->filament_type.get_at(filament_id); + if (filament_type == type) + target_filaments.insert(filament_id); } - else { - *start_extruder_id = all_extruders.front(); - } - - unsigned int iterations = (1 << all_extruders.size()); - unsigned int final_state = iterations - 1; - std::vector>cache(iterations, std::vector(all_extruders.size(),0x7fffffff)); - std::vector>prev(iterations, std::vector(all_extruders.size(), -1)); - cache[1][0] = 0.; - for (unsigned int state = 0; state < iterations; ++state) { - if (state & 1) { - for (unsigned int target = 0; target < all_extruders.size(); ++target) { - if (state >> target & 1) { - for (unsigned int mid_point = 0; mid_point < all_extruders.size(); ++mid_point) { - if(state>>mid_point&1){ - auto tmp = cache[state - (1 << target)][mid_point] + wipe_volumes[all_extruders[mid_point]][all_extruders[target]]; - if (cache[state][target] >tmp) { - cache[state][target] = tmp; - prev[state][target] = mid_point; - } - } - } - } - } - } - } - - //get res - float cost = std::numeric_limits::max(); - int final_dst =0; - for (unsigned int dst = 0; dst < all_extruders.size(); ++dst) { - if (all_extruders[dst] != start_extruder_id && cost > cache[final_state][dst]) { - cost = cache[final_state][dst]; - final_dst = dst; - } - } - - std::vectorpath; - unsigned int curr_state = final_state; - int curr_point = final_dst; - while (curr_point != -1) { - path.emplace_back(all_extruders[curr_point]); - auto mid_point = prev[curr_state][curr_point]; - curr_state -= (1 << curr_point); - curr_point = mid_point; - }; - - if (add_start_extruder_flag) - path.pop_back(); - - std::reverse(path.begin(), path.end()); - return path; -} - -std::vector get_extruders_order(const std::vector> &wipe_volumes, std::vector all_extruders, std::optionalstart_extruder_id) -{ -#define USE_DP_OPTIMIZE -#ifdef USE_DP_OPTIMIZE - return solve_extruder_order(wipe_volumes, all_extruders, start_extruder_id); -#else -if (all_extruders.size() > 1) { - int begin_index = 0; - auto iter = std::find(all_extruders.begin(), all_extruders.end(), start_extruder_id); - if (iter != all_extruders.end()) { - for (int i = 0; i < all_extruders.size(); ++i) { - if (all_extruders[i] == start_extruder_id) { - std::swap(all_extruders[i], all_extruders[0]); - } - } - begin_index = 1; - } - - std::pair> volumes_to_extruder_order; - volumes_to_extruder_order.first = 10000 * all_extruders.size(); - std::sort(all_extruders.begin() + begin_index, all_extruders.end()); - do { - float flush_volume = 0; - for (int i = 0; i < all_extruders.size() - 1; ++i) { - flush_volume += wipe_volumes[all_extruders[i]][all_extruders[i + 1]]; - } - if (flush_volume < volumes_to_extruder_order.first) { - volumes_to_extruder_order = std::pair(flush_volume, all_extruders); - } - } while (std::next_permutation(all_extruders.begin() + begin_index, all_extruders.end())); - - if (volumes_to_extruder_order.second.size() > 0) - return volumes_to_extruder_order.second; - } - return all_extruders; - -#endif // OPTIMIZE + return target_filaments; } @@ -146,6 +63,25 @@ bool LayerTools::is_extruder_order(unsigned int a, unsigned int b) const return false; } +bool check_filament_printable_after_group(const std::vector &used_filaments, const std::vector &filament_maps, const PrintConfig *print_config) +{ + for (unsigned int filament_id : used_filaments) { + std::string filament_type = print_config->filament_type.get_at(filament_id); + for (size_t idx = 0; idx < print_config->unprintable_filament_types.values.size(); ++idx) { + if (filament_maps[filament_id] == idx) { + std::vector limit_types = split_string(print_config->unprintable_filament_types.get_at(idx), ','); + auto iter = std::find(limit_types.begin(), limit_types.end(), filament_type); + if (iter != limit_types.end()) { + std::string extruder_name = idx == 0 ? _L("left") : _L("right"); + std::string error_msg = _L("Grouping error: ") + filament_type + _L(" can not be placed in the ") + extruder_name + _L(" nozzle"); + throw Slic3r::RuntimeError(error_msg); + } + } + } + } + return true; +} + // Return a zero based extruder from the region, or extruder_override if overriden. unsigned int LayerTools::wall_filament(const PrintRegion ®ion) const { @@ -194,16 +130,223 @@ static double calc_max_layer_height(const PrintConfig &config, double max_object return std::max(max_layer_height, max_object_layer_height); } +//calculate the flush weight (first value) and filament change count(second value) +static FilamentChangeStats calc_filament_change_info_by_toolorder(const PrintConfig* config, const std::vector& filament_map, const std::vector& flush_matrix, const std::vector>& layer_sequences) +{ + FilamentChangeStats ret; + std::unordered_map flush_volume_per_filament; + std::vectorlast_filament_per_extruder(2, -1); + + int total_filament_change_count = 0; + float total_filament_flush_weight = 0; + for (const auto& ls : layer_sequences) { + for (const auto& item : ls) { + int extruder_id = filament_map[item]; + int last_filament = last_filament_per_extruder[extruder_id]; + if (last_filament != -1 && last_filament != item) { + int flush_volume = flush_matrix[extruder_id][last_filament][item]; + flush_volume_per_filament[item] += flush_volume; + total_filament_change_count += 1; + } + last_filament_per_extruder[extruder_id] = item; + } + } + + for (auto& fv : flush_volume_per_filament) { + float weight = config->filament_density.get_at(fv.first) * 0.001 * fv.second; + total_filament_flush_weight += weight; + } + + ret.filament_change_count = total_filament_change_count; + ret.filament_flush_weight = (int)total_filament_flush_weight; + + return ret; +} + +void ToolOrdering::handle_dontcare_extruder(const std::vector& tool_order_layer0) +{ + if(m_layer_tools.empty() || tool_order_layer0.empty()) + return; + + // Reorder the extruders of first layer + { + LayerTools& lt = m_layer_tools[0]; + std::vector layer0_extruders = lt.extruders; + lt.extruders.clear(); + for (unsigned int extruder_id : tool_order_layer0) { + auto iter = std::find(layer0_extruders.begin(), layer0_extruders.end(), extruder_id); + if (iter != layer0_extruders.end()) { + lt.extruders.push_back(extruder_id); + *iter = (unsigned int)-1; + } + } + + for (unsigned int extruder_id : layer0_extruders) { + if (extruder_id == 0) + continue; + + if (extruder_id != (unsigned int)-1) + lt.extruders.push_back(extruder_id); + } + + // all extruders are zero + if (lt.extruders.empty()) { + lt.extruders.push_back(tool_order_layer0[0]); + } + } + + int last_extruder_id = m_layer_tools[0].extruders.back(); + for (int i = 1; i < m_layer_tools.size(); i++) { + LayerTools& lt = m_layer_tools[i]; + + if (lt.extruders.empty()) + continue; + if (lt.extruders.size() == 1 && lt.extruders.front() == 0) + lt.extruders.front() = last_extruder_id; + else { + if (lt.extruders.front() == 0) + // Pop the "don't care" extruder, the "don't care" region will be merged with the next one. + lt.extruders.erase(lt.extruders.begin()); + // Reorder the extruders to start with the last one. + for (size_t i = 1; i < lt.extruders.size(); ++i) + if (lt.extruders[i] == last_extruder_id) { + // Move the last extruder to the front. + memmove(lt.extruders.data() + 1, lt.extruders.data(), i * sizeof(unsigned int)); + lt.extruders.front() = last_extruder_id; + break; + } + } + last_extruder_id = lt.extruders.back(); + } + + // Reindex the extruders, so they are zero based, not 1 based. + for (LayerTools& lt : m_layer_tools){ + for (unsigned int& extruder_id : lt.extruders) { + assert(extruder_id > 0); + --extruder_id; + } + } +} + +void ToolOrdering::handle_dontcare_extruder(unsigned int last_extruder_id) +{ + if(m_layer_tools.empty()) + return; + if(last_extruder_id == (unsigned int)-1){ + // The initial print extruder has not been decided yet. + // Initialize the last_extruder_id with the first non-zero extruder id used for the print. + last_extruder_id = 0; + for (size_t i = 0; i < m_layer_tools.size() && last_extruder_id == 0; ++ i) { + const LayerTools < = m_layer_tools[i]; + for (unsigned int extruder_id : lt.extruders) + if (extruder_id > 0) { + last_extruder_id = extruder_id; + break; + } + } + if (last_extruder_id == 0) + // Nothing to extrude. + return; + }else{ + // 1 based idx + ++ last_extruder_id; + } + + for (LayerTools < : m_layer_tools) { + if (lt.extruders.empty()) + continue; + if (lt.extruders.size() == 1 && lt.extruders.front() == 0) + lt.extruders.front() = last_extruder_id; + else { + if (lt.extruders.front() == 0) + // Pop the "don't care" extruder, the "don't care" region will be merged with the next one. + lt.extruders.erase(lt.extruders.begin()); + // Reorder the extruders to start with the last one. + for (size_t i = 1; i < lt.extruders.size(); ++ i) + if (lt.extruders[i] == last_extruder_id) { + // Move the last extruder to the front. + memmove(lt.extruders.data() + 1, lt.extruders.data(), i * sizeof(unsigned int)); + lt.extruders.front() = last_extruder_id; + break; + } + + // On first layer with wipe tower, prefer a soluble extruder + // at the beginning, so it is not wiped on the first layer. + if (lt == m_layer_tools[0] && m_print_config_ptr && m_print_config_ptr->enable_prime_tower) { + for (size_t i = 0; ifilament_soluble.get_at(lt.extruders[i]-1)) { // 1-based... + std::swap(lt.extruders[i], lt.extruders.front()); + break; + } + } + } + last_extruder_id = lt.extruders.back(); + } + + // Reindex the extruders, so they are zero based, not 1 based. + for (LayerTools < : m_layer_tools){ + for (unsigned int &extruder_id : lt.extruders) { + assert(extruder_id > 0); + -- extruder_id; + } + } +} + +void ToolOrdering::sort_and_build_data(const Print& print, unsigned int first_extruder, bool prime_multi_material) +{ + // if first extruder is -1, we can decide the first layer tool order before doing reorder function + // so we shouldn't reorder first layer in reorder function + bool reorder_first_layer = (first_extruder != (unsigned int)(-1)); + reorder_extruders_for_minimum_flush_volume(reorder_first_layer); + m_sorted = true; + + double max_layer_height = 0.; + double object_bottom_z = 0.; + for (const auto& object : print.objects()) { + for (const Layer* layer : object->layers()) { + if (layer->has_extrusions()) { + object_bottom_z = layer->print_z - layer->height; + break; + } + } + max_layer_height = std::max(max_layer_height, object->config().layer_height.value); + } + + max_layer_height = calc_max_layer_height(print.config(), max_layer_height); + + this->collect_extruder_statistics(prime_multi_material); + + this->fill_wipe_tower_partitions(print.config(), object_bottom_z, max_layer_height); +} + +void ToolOrdering::sort_and_build_data(const PrintObject& object , unsigned int first_extruder, bool prime_multi_material) +{ + // if first extruder is -1, we can decide the first layer tool order before doing reorder function + // so we shouldn't reorder first layer in reorder function + bool reorder_first_layer = (first_extruder != (unsigned int)(-1)); + reorder_extruders_for_minimum_flush_volume(reorder_first_layer); + m_sorted = true; + + double max_layer_height = calc_max_layer_height(object.print()->config(), object.config().layer_height); + + this->collect_extruder_statistics(prime_multi_material); + + this->fill_wipe_tower_partitions(object.print()->config(), object.layers().front()->print_z - object.layers().front()->height, max_layer_height); +} + + // For the use case when each object is printed separately // (print->config().print_sequence == PrintSequence::ByObject is true). ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extruder, bool prime_multi_material) { m_print_object_ptr = &object; + m_print = const_cast(object.print()); if (object.layers().empty()) return; // Initialize the print layers for just a single object. { + // construct layer tools by z height std::vector zs; zs.reserve(zs.size() + object.layers().size() + object.support_layers().size()); for (auto layer : object.layers()) @@ -212,24 +355,21 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude zs.emplace_back(layer->print_z); this->initialize_layers(zs); } - double max_layer_height = calc_max_layer_height(object.print()->config(), object.config().layer_height); - // Collect extruders reuqired to print the layers. + // Collect extruders reuqired to print the layers. Add dontcare extruders this->collect_extruders(object, std::vector>()); // QDS // Reorder the extruders to minimize tool switches. - if (first_extruder == (unsigned int)-1) { - this->reorder_extruders(generate_first_layer_tool_order(object)); - } - else { - this->reorder_extruders(first_extruder); - } - - this->fill_wipe_tower_partitions(object.print()->config(), object.layers().front()->print_z - object.layers().front()->height, max_layer_height); + if(first_extruder == (unsigned int)-1) + this->handle_dontcare_extruder(generate_first_layer_tool_order(object)); + else + this->handle_dontcare_extruder(first_extruder); this->collect_extruder_statistics(prime_multi_material); + double max_layer_height = calc_max_layer_height(object.print()->config(), object.config().layer_height); + this->mark_skirt_layers(object.print()->config(), max_layer_height); } @@ -237,10 +377,10 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude // (print->config().print_sequence == PrintSequence::ByObject is false). ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool prime_multi_material) { + m_print = const_cast(&print); // for update the context of print m_print_config_ptr = &print.config(); // Initialize the print layers for all objects and all layers. - coordf_t object_bottom_z = 0.; coordf_t max_layer_height = 0.; { std::vector zs; @@ -251,13 +391,6 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool for (auto layer : object->support_layers()) zs.emplace_back(layer->print_z); - // Find first object layer that is not empty and save its print_z - for (const Layer* layer : object->layers()) - if (layer->has_extrusions()) { - object_bottom_z = layer->print_z - layer->height; - break; - } - max_layer_height = std::max(max_layer_height, object->config().layer_height.value); } this->initialize_layers(zs); @@ -288,14 +421,10 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool first_layer_tool_order = generate_first_layer_tool_order(print); } - if (!first_layer_tool_order.empty()) { - this->reorder_extruders(first_layer_tool_order); - } - else { - this->reorder_extruders(first_extruder); - } - - this->fill_wipe_tower_partitions(print.config(), object_bottom_z, max_layer_height); + if(!first_layer_tool_order.empty()) + this->handle_dontcare_extruder(first_layer_tool_order); + else + this->handle_dontcare_extruder(first_extruder); this->collect_extruder_statistics(prime_multi_material); @@ -313,7 +442,7 @@ std::vector ToolOrdering::generate_first_layer_tool_order(const Pr auto first_layer = object->get_layer(0); for (auto layerm : first_layer->regions()) { int extruder_id = layerm->region().config().option("wall_filament")->getInt(); - + for (auto expoly : layerm->raw_slices) { if (offset_ex(expoly, -0.2 * scale_(print.config().initial_layer_line_width)).empty()) continue; @@ -438,9 +567,15 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto // Collect the support extruders. for (auto support_layer : object.support_layers()) { LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z); - ExtrusionRole role = support_layer->support_fills.role(); - bool has_support = role == erMixed || role == erSupportMaterial || role == erSupportTransition; - bool has_interface = role == erMixed || role == erSupportMaterialInterface; + ExtrusionRole role = support_layer->support_fills.role(); + bool has_support = false; + bool has_interface = false; + for (const ExtrusionEntity *ee : support_layer->support_fills.entities) { + ExtrusionRole er = ee->role(); + if (er == erSupportMaterial || er == erSupportTransition) has_support = true; + if (er == erSupportMaterialInterface) has_interface = true; + if (has_support && has_interface) break; + } unsigned int extruder_support = object.config().support_filament.value; unsigned int extruder_interface = object.config().support_interface_filament.value; if (has_support) @@ -544,142 +679,31 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto } } -// Reorder extruders to minimize layer changes. -void ToolOrdering::reorder_extruders(unsigned int last_extruder_id) +bool ToolOrdering::check_tpu_group(const std::vector&used_filaments,const std::vector& filament_maps,const PrintConfig* config) { - if (m_layer_tools.empty()) - return; + int check_extruder_id = -1; + int master_extruder_id = config->master_extruder_id.value - 1; // transfer to 0 based idx + std::map> extruder_filament_nums; + for (unsigned int filament_id : used_filaments) { + int extruder_id = filament_maps[filament_id]; + extruder_filament_nums[extruder_id].push_back(filament_id); - if (last_extruder_id == (unsigned int)-1) { - // The initial print extruder has not been decided yet. - // Initialize the last_extruder_id with the first non-zero extruder id used for the print. - last_extruder_id = 0; - for (size_t i = 0; i < m_layer_tools.size() && last_extruder_id == 0; ++ i) { - const LayerTools < = m_layer_tools[i]; - for (unsigned int extruder_id : lt.extruders) - if (extruder_id > 0) { - last_extruder_id = extruder_id; - break; - } - } - if (last_extruder_id == 0) - // Nothing to extrude. - return; - } else - // 1 based index - ++ last_extruder_id; - - for (LayerTools < : m_layer_tools) { - if (lt.extruders.empty()) - continue; - if (lt.extruders.size() == 1 && lt.extruders.front() == 0) - lt.extruders.front() = last_extruder_id; - else { - if (lt.extruders.front() == 0) - // Pop the "don't care" extruder, the "don't care" region will be merged with the next one. - lt.extruders.erase(lt.extruders.begin()); - // Reorder the extruders to start with the last one. - for (size_t i = 1; i < lt.extruders.size(); ++ i) - if (lt.extruders[i] == last_extruder_id) { - // Move the last extruder to the front. - memmove(lt.extruders.data() + 1, lt.extruders.data(), i * sizeof(unsigned int)); - lt.extruders.front() = last_extruder_id; - break; - } - - // On first layer with wipe tower, prefer a soluble extruder - // at the beginning, so it is not wiped on the first layer. - if (lt == m_layer_tools[0] && m_print_config_ptr && m_print_config_ptr->enable_prime_tower) { - for (size_t i = 0; ifilament_soluble.get_at(lt.extruders[i]-1)) { // 1-based... - std::swap(lt.extruders[i], lt.extruders.front()); - break; - } - } - } - last_extruder_id = lt.extruders.back(); - } - - // Reindex the extruders, so they are zero based, not 1 based. - for (LayerTools < : m_layer_tools) - for (unsigned int &extruder_id : lt.extruders) { - assert(extruder_id > 0); - -- extruder_id; - } - - // reorder the extruders for minimum flush volume - reorder_extruders_for_minimum_flush_volume(); -} - -// QDS -void ToolOrdering::reorder_extruders(std::vector tool_order_layer0) -{ - if (m_layer_tools.empty()) - return; - - if (tool_order_layer0.empty()) - return; - - // Reorder the extruders of first layer - { - LayerTools& lt = m_layer_tools[0]; - std::vector layer0_extruders = lt.extruders; - lt.extruders.clear(); - for (unsigned int extruder_id : tool_order_layer0) { - auto iter = std::find(layer0_extruders.begin(), layer0_extruders.end(), extruder_id); - if (iter != layer0_extruders.end()) { - lt.extruders.push_back(extruder_id); - *iter = (unsigned int)-1; - } - } - - for (unsigned int extruder_id : layer0_extruders) { - if (extruder_id == 0) - continue; - - if (extruder_id != (unsigned int)-1) - lt.extruders.push_back(extruder_id); - } - - // all extruders are zero - if (lt.extruders.empty()) { - lt.extruders.push_back(tool_order_layer0[0]); + std::string filament_type = config->filament_type.get_at(filament_id); + if (filament_type == "TPU") { + // if we meet two TPU filaments, just return false + if(check_extruder_id==-1) + check_extruder_id = filament_maps[filament_id]; + else + return false; } } - int last_extruder_id = m_layer_tools[0].extruders.back(); - for (int i = 1; i < m_layer_tools.size(); i++) { - LayerTools& lt = m_layer_tools[i]; - - if (lt.extruders.empty()) - continue; - if (lt.extruders.size() == 1 && lt.extruders.front() == 0) - lt.extruders.front() = last_extruder_id; - else { - if (lt.extruders.front() == 0) - // Pop the "don't care" extruder, the "don't care" region will be merged with the next one. - lt.extruders.erase(lt.extruders.begin()); - // Reorder the extruders to start with the last one. - for (size_t i = 1; i < lt.extruders.size(); ++i) - if (lt.extruders[i] == last_extruder_id) { - // Move the last extruder to the front. - memmove(lt.extruders.data() + 1, lt.extruders.data(), i * sizeof(unsigned int)); - lt.extruders.front() = last_extruder_id; - break; - } - } - last_extruder_id = lt.extruders.back(); + // TPU can only place in master extruder, and it should have no other filaments in the same extruder + if (check_extruder_id != -1 && (check_extruder_id != master_extruder_id || extruder_filament_nums[check_extruder_id].size() > 1)) { + return false; } - // Reindex the extruders, so they are zero based, not 1 based. - for (LayerTools& lt : m_layer_tools) - for (unsigned int& extruder_id : lt.extruders) { - assert(extruder_id > 0); - --extruder_id; - } - - // reorder the extruders for minimum flush volume - reorder_extruders_for_minimum_flush_volume(); + return true; } void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height) @@ -811,9 +835,267 @@ void ToolOrdering::collect_extruder_statistics(bool prime_multi_material) } } -void ToolOrdering::reorder_extruders_for_minimum_flush_volume() +void ToolOrdering::cal_most_used_extruder(const PrintConfig &config) { - const PrintConfig *print_config = m_print_config_ptr; + // record + std::vector extruder_count; + extruder_count.resize(config.nozzle_diameter.size(), 0); + for (LayerTools &layer_tools : m_layer_tools) { + std::vector filaments = layer_tools.extruders; + std::set layer_extruder_count; + //count once only + for (unsigned int &filament : filaments) { + layer_extruder_count.insert(config.filament_map.values[filament] - 1); + } + + //record + for (int extruder_id : layer_extruder_count) { + extruder_count[extruder_id]++; + } + } + + // set key for most used extruder + // count most used extruder + most_used_extruder = 0; + for (int extruder_id = 1; extruder_id < extruder_count.size(); extruder_id++) { + if (extruder_count[extruder_id] >= extruder_count[most_used_extruder]) + most_used_extruder = extruder_id; + } +} + +//QDS: find first non support filament +bool ToolOrdering::cal_non_support_filaments(const PrintConfig &config, + unsigned int & first_non_support_filament, + std::vector & initial_non_support_filaments, + std::vector & initial_filaments) +{ + int find_count = 0; + int find_first_filaments_count = 0; + bool has_non_support = has_non_support_filament(config); + for (const LayerTools &layer_tool : m_layer_tools) { + for (const unsigned int &filament : layer_tool.extruders) { + //check first filament + if (!config.filament_map.values.empty() && initial_filaments[config.filament_map.values[filament] - 1] == -1) { + initial_filaments[config.filament_map.values[filament] - 1] = filament; + find_first_filaments_count++; + } + + if (has_non_support) { + // check first non support filaments + if (config.filament_is_support.get_at(filament)) + continue; + + if (first_non_support_filament == (unsigned int) -1) first_non_support_filament = filament; + + // params missing, add protection + // filament map missing means single nozzle, no need to set initial_non_support_filaments + if (config.filament_map.values.empty()) + return true; + + if (initial_non_support_filaments[config.filament_map.values[filament] - 1] == -1) { + initial_non_support_filaments[config.filament_map.values[filament] - 1] = filament; + find_count++; + } + + if (find_count == initial_non_support_filaments.size()) + return true; + } else if (find_first_filaments_count == initial_filaments.size() || config.filament_map.values.empty()){ + return false; + } + + } + } + + return false; +} + +bool ToolOrdering::has_non_support_filament(const PrintConfig &config) { + for (const unsigned int &filament : m_all_printing_extruders) { + if (!config.filament_is_support.get_at(filament)) { + return true; + } + } + + return false; +} + +std::set, std::vector>> generate_combinations(const std::vector &extruders) +{ + int n = extruders.size(); + std::vector flags(n); + std::set, std::vector>> unique_combinations; + + if (extruders.empty()) + return unique_combinations; + + for (int i = 1; i <= n / 2; ++i) { + std::fill(flags.begin(), flags.begin() + i, true); + std::fill(flags.begin() + i, flags.end(), false); + + do { + std::vector group1, group2; + for (int j = 0; j < n; ++j) { + if (flags[j]) { + group1.push_back(extruders[j]); + } else { + group2.push_back(extruders[j]); + } + } + + if (group1.size() > group2.size()) { std::swap(group1, group2); } + + unique_combinations.insert({group1, group2}); + + } while (std::prev_permutation(flags.begin(), flags.end())); + } + + return unique_combinations; +} + +float get_flush_volume(const std::vector &filament_maps, const std::vector &extruders, const std::vector &matrix, size_t nozzle_nums) +{ + std::vector> nozzle_filaments; + nozzle_filaments.resize(nozzle_nums); + + for (unsigned int filament_id : extruders) { + nozzle_filaments[filament_maps[filament_id]].emplace_back(filament_id); + } + + float flush_volume = 0; + for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) { + for (size_t i = 0; i + 1 < nozzle_filaments[nozzle_id].size(); ++i) { + flush_volume += matrix[nozzle_id][nozzle_filaments[nozzle_id][i]][nozzle_filaments[nozzle_id][i+1]]; + } + } + + return flush_volume; +} + +std::vector ToolOrdering::get_recommended_filament_maps(const std::vector>& layer_filaments, const Print* print, const FilamentMapMode mode,const std::vector>&physical_unprintables,const std::vector>&geometric_unprintables) +{ + using namespace FilamentGroupUtils; + if (!print || layer_filaments.empty()) + return std::vector(); + + const auto& print_config = print->config(); + const unsigned int filament_nums = (unsigned int)(print_config.filament_colour.values.size() + EPSILON); + + // get flush matrix + std::vector nozzle_flush_mtx; + size_t extruder_nums = print_config.nozzle_diameter.values.size(); + for (size_t nozzle_id = 0; nozzle_id < extruder_nums; ++nozzle_id) { + std::vector flush_matrix(cast(get_flush_volumes_matrix(print_config.flush_volumes_matrix.values, nozzle_id, extruder_nums))); + std::vector> wipe_volumes; + for (unsigned int i = 0; i < filament_nums; ++i) + wipe_volumes.push_back(std::vector(flush_matrix.begin() + i * filament_nums, flush_matrix.begin() + (i + 1) * filament_nums)); + + nozzle_flush_mtx.emplace_back(wipe_volumes); + } + auto flush_multiplies = print_config.flush_multiplier.values; + flush_multiplies.resize(extruder_nums, 1); + for (size_t nozzle_id = 0; nozzle_id < extruder_nums; ++nozzle_id) { + for (auto& vec : nozzle_flush_mtx[nozzle_id]) { + for (auto& v : vec) + v *= flush_multiplies[nozzle_id]; + } + } + + std::vector other_layers_seqs = get_other_layers_print_sequence(print_config.other_layers_print_sequence_nums.value, print_config.other_layers_print_sequence.values); + + // other_layers_seq: the layer_idx and extruder_idx are base on 1 + auto get_custom_seq = [&other_layers_seqs](int layer_idx, std::vector& out_seq) -> bool { + for (size_t idx = other_layers_seqs.size() - 1; idx != size_t(-1); --idx) { + const auto& other_layers_seq = other_layers_seqs[idx]; + if (layer_idx + 1 >= other_layers_seq.first.first && layer_idx + 1 <= other_layers_seq.first.second) { + out_seq = other_layers_seq.second; + return true; + } + } + return false; + }; + + int master_extruder_id = print_config.master_extruder_id.value -1; // switch to 0 based idx + std::vectorret(filament_nums, master_extruder_id); + bool ignore_ext_filament = false; // TODO: read from config + // if mutli_extruder, calc group,otherwise set to 0 + if (extruder_nums == 2) { + std::vector extruder_ams_count_str = print_config.extruder_ams_count.values; + auto extruder_ams_counts = get_extruder_ams_count(extruder_ams_count_str); + std::vector group_size = calc_max_group_size(extruder_ams_counts, ignore_ext_filament); + + auto machine_filament_info = build_machine_filaments(print->get_extruder_filament_info(), extruder_ams_counts, ignore_ext_filament); + + std::vector filament_types = print_config.filament_type.values; + std::vector filament_colours = print_config.filament_colour.values; + std::vector filament_is_support = print_config.filament_is_support.values; + std::vector filament_ids = print_config.filament_ids.values; + // speacially handle tpu filaments + auto used_filaments = collect_sorted_used_filaments(layer_filaments); + auto tpu_filaments = get_filament_by_type(used_filaments, &print_config, "TPU"); + FGMode fg_mode = mode == FilamentMapMode::fmmAutoForMatch ? FGMode::MatchMode: FGMode::FlushMode; + + std::vector> ext_unprintable_filaments; + collect_unprintable_limits(physical_unprintables, geometric_unprintables, ext_unprintable_filaments); + + FilamentGroupContext context; + { + context.model_info.flush_matrix = std::move(nozzle_flush_mtx); + context.model_info.unprintable_filaments = ext_unprintable_filaments; + context.model_info.layer_filaments = layer_filaments; + context.model_info.filament_ids = filament_ids; + + for (size_t idx = 0; idx < filament_types.size(); ++idx) { + FilamentGroupUtils::FilamentInfo info; + info.color = filament_colours[idx]; + info.type = filament_types[idx]; + info.is_support = filament_is_support[idx]; + context.model_info.filament_info.emplace_back(std::move(info)); + } + + context.machine_info.machine_filament_info = machine_filament_info; + context.machine_info.max_group_size = std::move(group_size); + context.machine_info.master_extruder_id = master_extruder_id; + + context.group_info.total_filament_num = (int)(filament_nums); + context.group_info.max_gap_threshold = 0.01; + context.group_info.strategy = FGStrategy::BestCost; + context.group_info.mode = fg_mode; + context.group_info.ignore_ext_filament = ignore_ext_filament; + } + + + if (!tpu_filaments.empty()) { + ret = calc_filament_group_for_tpu(tpu_filaments, context.group_info.total_filament_num, context.machine_info.master_extruder_id); + } + else { + FilamentGroup fg(context); + fg.get_custom_seq = get_custom_seq; + ret = fg.calc_filament_group(); + } + } + + return ret; +} + +FilamentChangeStats ToolOrdering::get_filament_change_stats(FilamentChangeMode mode) +{ + switch (mode) + { + case Slic3r::ToolOrdering::SingleExt: + return m_stats_by_single_extruder; + case Slic3r::ToolOrdering::MultiExtBest: + return m_stats_by_multi_extruder_best; + case Slic3r::ToolOrdering::MultiExtCurr: + return m_stats_by_multi_extruder_curr; + default: + break; + } + return m_stats_by_single_extruder; +} + +void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first_layer) +{ + const PrintConfig* print_config = m_print_config_ptr; if (!print_config && m_print_object_ptr) { print_config = &(m_print_object_ptr->print()->config()); } @@ -821,37 +1103,103 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume() if (!print_config || m_layer_tools.empty()) return; - // Get wiping matrix to get number of extruders and convert vector to vector: - std::vector flush_matrix(cast(print_config->flush_volumes_matrix.values)); - const unsigned int number_of_extruders = (unsigned int) (sqrt(flush_matrix.size()) + EPSILON); - // Extract purging volumes for each extruder pair: - std::vector> wipe_volumes; - for (unsigned int i = 0; i < number_of_extruders; ++i) - wipe_volumes.push_back(std::vector(flush_matrix.begin() + i * number_of_extruders, flush_matrix.begin() + (i + 1) * number_of_extruders)); + const unsigned int number_of_extruders = (unsigned int)(print_config->filament_colour.values.size() + EPSILON); - auto extruders_to_hash_key = [](const std::vector& extruders, std::optionalinitial_extruder_id)->uint32_t { - uint32_t hash_key = 0; - // high 16 bit define initial extruder ,low 16 bit define extruder set - if (initial_extruder_id) - hash_key |= (1 << (16 + *initial_extruder_id)); - for (auto item : extruders) - hash_key |= (1 << item); - return hash_key; - }; + using FlushMatrix = std::vector>; + size_t nozzle_nums = print_config->nozzle_diameter.values.size(); + + std::vector nozzle_flush_mtx; + for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) { + std::vector flush_matrix(cast(get_flush_volumes_matrix(print_config->flush_volumes_matrix.values, nozzle_id, nozzle_nums))); + std::vector> wipe_volumes; + for (unsigned int i = 0; i < number_of_extruders; ++i) + wipe_volumes.push_back(std::vector(flush_matrix.begin() + i * number_of_extruders, flush_matrix.begin() + (i + 1) * number_of_extruders)); + + nozzle_flush_mtx.emplace_back(wipe_volumes); + } + + auto flush_multiplies = print_config->flush_multiplier.values; + flush_multiplies.resize(nozzle_nums, 1); + for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) { + for (auto& vec : nozzle_flush_mtx[nozzle_id]) { + for (auto& v : vec) + v *= flush_multiplies[nozzle_id]; + } + } + + + std::vectorfilament_maps(number_of_extruders, 0); + FilamentMapMode map_mode = FilamentMapMode::fmmAutoForFlush; + + std::vector> layer_filaments; + for (auto& lt : m_layer_tools) { + layer_filaments.emplace_back(lt.extruders); + } + + std::vector used_filaments = collect_sorted_used_filaments(layer_filaments); + + std::vector>geometric_unprintables = m_print->get_geometric_unprintable_filaments(); + std::vector>physical_unprintables = m_print->get_physical_unprintable_filaments(used_filaments); + + filament_maps = m_print->get_filament_maps(); + map_mode = m_print->get_filament_map_mode(); + // only check and map in sequence mode, in by object mode, we check the map in print.cpp + if (print_config->print_sequence != PrintSequence::ByObject || m_print->objects().size() == 1) { + if (map_mode < FilamentMapMode::fmmManual) { + const PrintConfig* print_config = m_print_config_ptr; + if (!print_config && m_print_object_ptr) { + print_config = &(m_print_object_ptr->print()->config()); + } + + filament_maps = ToolOrdering::get_recommended_filament_maps(layer_filaments, m_print, map_mode, physical_unprintables, geometric_unprintables); + + if (filament_maps.empty()) + return; + std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value + 1; }); + m_print->update_filament_maps_to_config(filament_maps); + } + std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value - 1; }); + + check_filament_printable_after_group(used_filaments, filament_maps, print_config); + + if (nozzle_nums > 1 && !check_tpu_group(used_filaments, filament_maps, print_config)) { + int master_extruder_id = print_config->master_extruder_id.value - 1; // to 0 based + std::string nozzle_name = master_extruder_id == 0 ? _L("left") : _L("right"); + std::string exception_str = _L("TPU is incompatible with AMS and must be printed seperately in the ") + nozzle_name + _L(" nozzle.\nPlease adjust the filament group accordingly."); + throw Slic3r::RuntimeError(exception_str); + } + } + else { + // we just need to change the map to 0 based + std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) {return value - 1; }); + } + + std::vector>filament_sequences; + std::vectorfilament_lists(number_of_extruders); + std::iota(filament_lists.begin(), filament_lists.end(), 0); std::vector other_layers_seqs; - const ConfigOptionInts *other_layers_print_sequence_op = print_config->option("other_layers_print_sequence"); - const ConfigOptionInt *other_layers_print_sequence_nums_op = print_config->option("other_layers_print_sequence_nums"); + const ConfigOptionInts* other_layers_print_sequence_op = print_config->option("other_layers_print_sequence"); + const ConfigOptionInt* other_layers_print_sequence_nums_op = print_config->option("other_layers_print_sequence_nums"); if (other_layers_print_sequence_op && other_layers_print_sequence_nums_op) { - const std::vector &print_sequence = other_layers_print_sequence_op->values; - int sequence_nums = other_layers_print_sequence_nums_op->value; + const std::vector& print_sequence = other_layers_print_sequence_op->values; + int sequence_nums = other_layers_print_sequence_nums_op->value; other_layers_seqs = get_other_layers_print_sequence(sequence_nums, print_sequence); } + std::vectorfirst_layer_filaments; + if (!m_layer_tools.empty()) + first_layer_filaments = m_layer_tools[0].extruders; + // other_layers_seq: the layer_idx and extruder_idx are base on 1 - auto get_custom_seq = [&other_layers_seqs](int layer_idx, std::vector& out_seq) -> bool { + auto get_custom_seq = [&other_layers_seqs, &reorder_first_layer, &first_layer_filaments](int layer_idx, std::vector& out_seq) -> bool { + if (!reorder_first_layer && layer_idx == 0) { + out_seq.resize(first_layer_filaments.size()); + std::transform(first_layer_filaments.begin(), first_layer_filaments.end(), out_seq.begin(), [](auto item) {return item + 1; }); + return true; + } for (size_t idx = other_layers_seqs.size() - 1; idx != size_t(-1); --idx) { - const auto &other_layers_seq = other_layers_seqs[idx]; + const auto& other_layers_seq = other_layers_seqs[idx]; if (layer_idx + 1 >= other_layers_seq.first.first && layer_idx + 1 <= other_layers_seq.first.second) { out_seq = other_layers_seq.second; return true; @@ -860,52 +1208,62 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume() return false; }; - std::optionalcurrent_extruder_id; - for (int i = 0; i < m_layer_tools.size(); ++i) { - LayerTools& lt = m_layer_tools[i]; - if (lt.extruders.empty()) - continue; + reorder_filaments_for_minimum_flush_volume( + filament_lists, + filament_maps, + layer_filaments, + nozzle_flush_mtx, + get_custom_seq, + &filament_sequences + ); - std::vector custom_extruder_seq; - if (get_custom_seq(i, custom_extruder_seq) && !custom_extruder_seq.empty()) { - std::vector unsign_custom_extruder_seq; - for (int extruder : custom_extruder_seq) { - unsigned int unsign_extruder = static_cast(extruder) - 1; - auto it = std::find(lt.extruders.begin(), lt.extruders.end(), unsign_extruder); - if (it != lt.extruders.end()) { - unsign_custom_extruder_seq.emplace_back(unsign_extruder); - } - } - assert(lt.extruders.size() == unsign_custom_extruder_seq.size()); - lt.extruders = unsign_custom_extruder_seq; - current_extruder_id = lt.extruders.back(); - continue; - } - - // The algorithm complexity is O(n2*2^n) - if (i != 0) { - auto hash_key = extruders_to_hash_key(lt.extruders, current_extruder_id); - auto iter = m_tool_order_cache.find(hash_key); - if (iter == m_tool_order_cache.end()) { - lt.extruders = get_extruders_order(wipe_volumes, lt.extruders, current_extruder_id); - std::vector hash_val; - hash_val.reserve(lt.extruders.size()); - for (auto item : lt.extruders) - hash_val.emplace_back(static_cast(item)); - m_tool_order_cache[hash_key] = hash_val; - } - else { - std::vectorextruder_order; - extruder_order.reserve(iter->second.size()); - for (auto item : iter->second) - extruder_order.emplace_back(static_cast(item)); - lt.extruders = std::move(extruder_order); - } - } - current_extruder_id = lt.extruders.back(); + auto curr_flush_info = calc_filament_change_info_by_toolorder(print_config, filament_maps, nozzle_flush_mtx, filament_sequences); + if (nozzle_nums <= 1) + m_stats_by_single_extruder = curr_flush_info; + else { + m_stats_by_multi_extruder_curr = curr_flush_info; + if (map_mode == fmmAutoForFlush) + m_stats_by_multi_extruder_best = curr_flush_info; } -} + // in multi extruder mode,collect data with other mode + if (nozzle_nums > 1) { + // always calculate the info by one extruder + { + std::vector>filament_sequences_one_extruder; + auto maps_without_group = filament_maps; + for (auto& item : maps_without_group) + item = 0; + reorder_filaments_for_minimum_flush_volume( + filament_lists, + maps_without_group, + layer_filaments, + nozzle_flush_mtx, + get_custom_seq, + &filament_sequences_one_extruder + ); + m_stats_by_single_extruder = calc_filament_change_info_by_toolorder(print_config, maps_without_group, nozzle_flush_mtx, filament_sequences_one_extruder); + } + // if not in best for flush mode,also calculate the info by best for flush mode + if (map_mode != fmmAutoForFlush) + { + std::vector>filament_sequences_one_extruder; + std::vectorfilament_maps_auto = get_recommended_filament_maps(layer_filaments, m_print, fmmAutoForFlush, physical_unprintables, geometric_unprintables); + reorder_filaments_for_minimum_flush_volume( + filament_lists, + filament_maps_auto, + layer_filaments, + nozzle_flush_mtx, + get_custom_seq, + &filament_sequences_one_extruder + ); + m_stats_by_multi_extruder_best = calc_filament_change_info_by_toolorder(print_config, filament_maps_auto, nozzle_flush_mtx, filament_sequences_one_extruder); + } + } + + for (size_t i = 0; i < filament_sequences.size(); ++i) + m_layer_tools[i].extruders = std::move(filament_sequences[i]); +} // Layers are marked for infinite skirt aka draft shield. Not all the layers have to be printed. void ToolOrdering::mark_skirt_layers(const PrintConfig &config, coordf_t max_layer_height) { diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index e021b58..75a0dd6 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -8,6 +8,9 @@ #include #include +#include "../FilamentGroup.hpp" +#include "../ExtrusionEntity.hpp" +#include "../PrintConfig.hpp" namespace Slic3r { @@ -91,6 +94,37 @@ private: const LayerTools* m_layer_tools = nullptr; // so we know which LayerTools object this belongs to }; + +struct FilamentChangeStats +{ + int filament_flush_weight{0}; + int filament_change_count{0}; + int extruder_change_count{0}; + + void clear(){ + filament_flush_weight = 0; + filament_change_count = 0; + extruder_change_count = 0; + } + + FilamentChangeStats& operator+=(const FilamentChangeStats& other) { + this->filament_flush_weight += other.filament_flush_weight; + this->filament_change_count += other.filament_change_count; + this->extruder_change_count += other.extruder_change_count; + return *this; + } + + FilamentChangeStats operator+(const FilamentChangeStats& other){ + FilamentChangeStats ret; + ret.filament_flush_weight = this->filament_flush_weight + other.filament_flush_weight; + ret.filament_change_count = this->filament_change_count + other.filament_change_count; + ret.extruder_change_count = this->extruder_change_count + other.extruder_change_count; + return ret; + } + +}; + + class LayerTools { public: @@ -146,6 +180,11 @@ private: class ToolOrdering { public: + enum FilamentChangeMode { + SingleExt, + MultiExtBest, + MultiExtCurr + }; ToolOrdering() = default; // For the use case when each object is printed separately @@ -156,8 +195,17 @@ public: // (print->config().print_sequence == PrintSequence::ByObject is false). ToolOrdering(const Print& print, unsigned int first_extruder, bool prime_multi_material = false); - void clear() { - m_layer_tools.clear(); m_tool_order_cache.clear(); + void handle_dontcare_extruder(const std::vector& first_layer_tool_order); + void handle_dontcare_extruder(unsigned int first_extruder); + + void sort_and_build_data(const PrintObject &object, unsigned int first_extruder, bool prime_multi_material = false); + void sort_and_build_data(const Print& print, unsigned int first_extruder, bool prime_multi_material = false); + + void clear() { + m_layer_tools.clear(); + m_stats_by_single_extruder.clear(); + m_stats_by_multi_extruder_best.clear(); + m_stats_by_multi_extruder_curr.clear(); } // Only valid for non-sequential print: @@ -187,16 +235,33 @@ public: std::vector& layer_tools() { return m_layer_tools; } bool has_wipe_tower() const { return ! m_layer_tools.empty() && m_first_printing_extruder != (unsigned int)-1 && m_layer_tools.front().has_wipe_tower; } + int get_most_used_extruder() const { return most_used_extruder; } + /* + * called in single extruder mode, the value in map are all 0 + * called in dual extruder mode, the value in map will be 0 or 1 + * 0 based group id + */ + static std::vector get_recommended_filament_maps(const std::vector>& layer_filaments, const Print* print,const FilamentMapMode mode, const std::vector>& physical_unprintables, const std::vector>& geometric_unprintables); + + static bool check_tpu_group(const std::vector&used_filaments,const std::vector& filament_maps,const PrintConfig* config); + + // should be called after doing reorder + FilamentChangeStats get_filament_change_stats(FilamentChangeMode mode); + void cal_most_used_extruder(const PrintConfig &config); + bool cal_non_support_filaments(const PrintConfig &config, + unsigned int & first_non_support_filament, + std::vector & initial_non_support_filaments, + std::vector & initial_filaments); + + bool has_non_support_filament(const PrintConfig &config); + private: void initialize_layers(std::vector &zs); void collect_extruders(const PrintObject &object, const std::vector> &per_layer_extruder_switches); - void reorder_extruders(unsigned int last_extruder_id); - // QDS - void reorder_extruders(std::vector tool_order_layer0); void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height); void mark_skirt_layers(const PrintConfig &config, coordf_t max_layer_height); void collect_extruder_statistics(bool prime_multi_material); - void reorder_extruders_for_minimum_flush_volume(); + void reorder_extruders_for_minimum_flush_volume(bool reorder_first_layer); // QDS std::vector generate_first_layer_tool_order(const Print& print); @@ -209,9 +274,16 @@ private: unsigned int m_last_printing_extruder = (unsigned int)-1; // All extruders, which extrude some material over m_layer_tools. std::vector m_all_printing_extruders; - std::unordered_map> m_tool_order_cache; const PrintConfig* m_print_config_ptr = nullptr; const PrintObject* m_print_object_ptr = nullptr; + Print* m_print; + bool m_sorted = false; + + FilamentChangeStats m_stats_by_single_extruder; + FilamentChangeStats m_stats_by_multi_extruder_curr; + FilamentChangeStats m_stats_by_multi_extruder_best; + + int most_used_extruder; }; } // namespace SLic3r diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index ab9e740..611e870 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -9,12 +9,22 @@ #include "GCodeProcessor.hpp" #include "BoundingBox.hpp" +#include "ClipperUtils.hpp" #include "LocalesUtils.hpp" +#include "Triangulation.hpp" namespace Slic3r { +bool flat_ironing = true; // Whether to enable flat ironing for the wipe tower +float flat_iron_area = 4.f; +constexpr float flat_iron_speed = 10.f * 60.f; static const double wipe_tower_wall_infill_overlap = 0.0; +static constexpr double WIPE_TOWER_RESOLUTION = 0.1; +static constexpr double WT_SIMPLIFY_TOLERANCE_SCALED = 0.001 / SCALING_FACTOR; +static constexpr int arc_fit_size = 20; +#define SCALED_WIPE_TOWER_RESOLUTION (WIPE_TOWER_RESOLUTION / SCALING_FACTOR) +enum class LimitFlow { None, LimitPrintFlow, LimitRammingFlow }; inline float align_round(float value, float base) { @@ -60,6 +70,456 @@ static bool is_valid_gcode(const std::string &gcode) return is_valid; } +Polygon chamfer_polygon(Polygon &polygon, double chamfer_dis = 2., double angle_tol = 30. / 180. * PI) +{ + if (polygon.points.size() < 3) return polygon; + Polygon res; + res.points.reserve(polygon.points.size() * 2); + int mod = polygon.points.size(); + double cos_angle_tol = abs(std::cos(angle_tol)); + + for (int i = 0; i < polygon.points.size(); i++) { + Vec2d a = unscaled(polygon.points[(i - 1 + mod) % mod]); + Vec2d b = unscaled(polygon.points[i]); + Vec2d c = unscaled(polygon.points[(i + 1) % mod]); + double ab_len = (a - b).norm(); + double bc_len = (b - c).norm(); + Vec2d ab = (b - a) / ab_len; + Vec2d bc = (c - b) / bc_len; + assert(ab_len != 0); + assert(bc_len != 0); + float cosangle = ab.dot(bc); + //std::cout << " angle " << acos(cosangle) << " cosangle " << cosangle << std::endl; + //std::cout << " ab_len " << ab_len << " bc_len " << bc_len << std::endl; + if (abs(cosangle) < cos_angle_tol) { + float real_chamfer_dis = std::min({chamfer_dis, ab_len / 2.1, bc_len / 2.1}); // 2.1 to ensure the points do not coincide + Vec2d left = b - ab * real_chamfer_dis; + Vec2d right = b + bc * real_chamfer_dis; + res.points.push_back(scaled(left)); + res.points.push_back(scaled(right)); + } else + res.points.push_back(polygon.points[i]); + } + res.points.shrink_to_fit(); + return res; +} + +Polygon WipeTower::rounding_polygon(Polygon &polygon, double rounding /*= 2.*/, double angle_tol/* = 30. / 180. * PI*/) +{ + if (polygon.points.size() < 3) return polygon; + Polygon res; + res.points.reserve(polygon.points.size() * 2); + int mod = polygon.points.size(); + double cos_angle_tol = abs(std::cos(angle_tol)); + + for (int i = 0; i < polygon.points.size(); i++) { + Vec2d a = unscaled(polygon.points[(i - 1 + mod) % mod]); + Vec2d b = unscaled(polygon.points[i]); + Vec2d c = unscaled(polygon.points[(i + 1) % mod]); + double ab_len = (a - b).norm(); + double bc_len = (b - c).norm(); + Vec2d ab = (b - a) / ab_len; + Vec2d bc = (c - b) / bc_len; + assert(ab_len != 0); + assert(bc_len != 0); + float cosangle = ab.dot(bc); + cosangle = std::clamp(cosangle, -1.f, 1.f); + bool is_ccw = cross2(ab, bc) > 0; + if (abs(cosangle) < cos_angle_tol) { + float real_rounding_dis = std::min({rounding, ab_len / 2.1, bc_len / 2.1}); // 2.1 to ensure the points do not coincide + Vec2d left = b - ab * real_rounding_dis; + Vec2d right = b + bc * real_rounding_dis; + //Point r_left = scaled(left); + //Point r_right = scaled(right); + // std::cout << " r_left " << r_left[0] << " " << r_left[1] << std::endl; + //std::cout << " r_right " << r_right[0] << " " << r_right[1] << std::endl; + { + float half_angle = std::acos(cosangle)/2.f; + //std::cout << " half_angle " << cos(half_angle) << std::endl; + + Vec2d dir = (right - left).normalized(); + dir = Vec2d{-dir[1], dir[0]}; + dir = is_ccw ? dir : -dir; + double dis = real_rounding_dis / sin(half_angle); + //std::cout << " dis " << dis << std::endl; + + Vec2d center = b + dir * dis; + double radius = (left - center).norm(); + ArcSegment arc(scaled(center), scaled(radius), scaled(left), scaled(right), is_ccw ? ArcDirection::Arc_Dir_CCW : ArcDirection::Arc_Dir_CW); + int n = arc_fit_size; + //std::cout << "start " << arc.start_point[0] << " " << arc.start_point[1] << std::endl; + //std::cout << "end " << arc.end_point[0] << " " << arc.end_point[1] << std::endl; + //std::cout << "start angle " << arc.polar_start_theta << " end angle " << arc.polar_end_theta << std::endl; + for (int j = 0; j < n; j++) { + float cur_angle = arc.polar_start_theta + (float)j/n * arc.angle_radians ; + //std::cout << " cur_angle " << cur_angle << std::endl; + if (cur_angle > 2 * PI) + cur_angle -= 2 * PI; + else if (cur_angle < 0) + cur_angle += 2 * PI; + Point tmp = arc.center + Point{arc.radius * std::cos(cur_angle), arc.radius *std::sin(cur_angle)}; + //std::cout << "j = " << j << std::endl; + //std::cout << "tmp = " << tmp[0]<<" "< 0; + if (abs(cosangle) < cos_angle_tol) { + float real_rounding_dis = std::min({rounding, ab_len / 2.1, bc_len / 2.1}); // 2.1 to ensure the points do not coincide + Vec2d left = b - ab * real_rounding_dis; + Vec2d right = b + bc * real_rounding_dis; + //Point r_left = scaled(left); + //Point r_right = scaled(right); + // std::cout << " r_left " << r_left[0] << " " << r_left[1] << std::endl; + // std::cout << " r_right " << r_right[0] << " " << r_right[1] << std::endl; + { + Vec2d center = b; + double radius = real_rounding_dis; + ArcSegment arc(scaled(center), scaled(radius), scaled(left), scaled(right), is_ccw ? ArcDirection::Arc_Dir_CCW : ArcDirection::Arc_Dir_CW); + int n = arc_fit_size; + // std::cout << "start " << arc.start_point[0] << " " << arc.start_point[1] << std::endl; + // std::cout << "end " << arc.end_point[0] << " " << arc.end_point[1] << std::endl; + // std::cout << "start angle " << arc.polar_start_theta << " end angle " << arc.polar_end_theta << std::endl; + for (int j = 0; j < n; j++) { + float cur_angle = arc.polar_start_theta + (float) j / n * arc.angle_radians; + // std::cout << " cur_angle " << cur_angle << std::endl; + if (cur_angle > 2 * PI) + cur_angle -= 2 * PI; + else if (cur_angle < 0) + cur_angle += 2 * PI; + Point tmp = arc.center + Point{arc.radius * std::cos(cur_angle), arc.radius * std::sin(cur_angle)}; + // std::cout << "j = " << j << std::endl; + // std::cout << "tmp = " << tmp[0]<<" "< ray_intersetion_line(const Vec2f &a, const Vec2f &v1, const Vec2f &b, const Vec2f &c) +{ + const Vec2f v2 = c - b; + double denom = cross2(v1, v2); + if (fabs(denom) < EPSILON) return {false, Vec2f(0, 0)}; + const Vec2f v12 = (a - b); + double nume_a = cross2(v2, v12); + double nume_b = cross2(v1, v12); + double t1 = nume_a / denom; + double t2 = nume_b / denom; + if (t1 >= 0 && t2 >= 0 && t2 <= 1.) { + // Get the intersection point. + Vec2f res = a + t1 * v1; + return std::pair(true, res); + } + return std::pair(false, Vec2f{0, 0}); +} +Polygon scale_polygon(const std::vector &points) { + Polygon res; + for (const auto &p : points) res.points.push_back(scaled(p)); + return res; +} +std::vector unscale_polygon(const Polygon& polygon) +{ + std::vector res; + for (const auto &p : polygon.points) res.push_back(unscaled(p)); + return res; +} + +Polygon generate_rectange(const Line &line, coord_t offset) +{ + Point p1 = line.a; + Point p2 = line.b; + + double dx = p2.x() - p1.x(); + double dy = p2.y() - p1.y(); + + double length = std::sqrt(dx * dx + dy * dy); + + double ux = dx / length; + double uy = dy / length; + + double vx = -uy; + double vy = ux; + + double ox = vx * offset; + double oy = vy * offset; + + Points rect; + rect.resize(4); + rect[0] = {p1.x() + ox, p1.y() + oy}; + rect[1] = {p1.x() - ox, p1.y() - oy}; + rect[2] = {p2.x() - ox, p2.y() - oy}; + rect[3] = {p2.x() + ox, p2.y() + oy}; + Polygon poly(rect); + return poly; +}; + +struct Segment +{ + Vec2f start; + Vec2f end; + bool is_arc = false; + ArcSegment arcsegment; + Segment(const Vec2f &s, const Vec2f &e) : start(s), end(e) {} + bool is_valid() const { return start.y() < end.y(); } +}; + +std::vector remove_points_from_segment(const Segment &segment, const std::vector &skip_points, double range) +{ + std::vector result; + result.push_back(segment); + float x = segment.start.x(); + + for (const Vec2f &point : skip_points) { + std::vector newResult; + for (const auto &seg : result) { + if (point.y() + range <= seg.start.y() || point.y() - range >= seg.end.y()) { + newResult.push_back(seg); + } else { + if (point.y() - range > seg.start.y()) { newResult.push_back(Segment(Vec2f(x, seg.start.y()), Vec2f(x, point.y() - range))); } + if (point.y() + range < seg.end.y()) { newResult.push_back(Segment(Vec2f(x, point.y() + range), Vec2f(x, seg.end.y()))); } + } + } + + result = newResult; + } + + result.erase(std::remove_if(result.begin(), result.end(), [](const Segment &seg) { return !seg.is_valid(); }), result.end()); + return result; +} + +struct IntersectionInfo +{ + Vec2f pos; + int idx; + int pair_idx; // gap_pair idx + float dis_from_idx; + bool is_forward; +}; + +struct PointWithFlag +{ + Vec2f pos; + int pair_idx; // gap_pair idx + bool is_forward; +}; +IntersectionInfo move_point_along_polygon(const std::vector &points, const Vec2f &startPoint, int startIdx, float offset, bool forward, int pair_idx) +{ + float remainingDistance = offset; + IntersectionInfo res; + int mod = points.size(); + if (forward) { + int next = (startIdx + 1) % mod; + remainingDistance -= (points[next] - startPoint).norm(); + if (remainingDistance <= 0) { + res.idx = startIdx; + res.pos = startPoint + (points[next] - startPoint).normalized() * offset; + res.pair_idx = pair_idx; + res.dis_from_idx = (points[startIdx] - res.pos).norm(); + return res; + } else { + for (int i = (startIdx + 1) % mod; i != startIdx; i = (i + 1) % mod) { + float segmentLength = (points[(i + 1) % mod] - points[i]).norm(); + if (remainingDistance <= segmentLength) { + float ratio = remainingDistance / segmentLength; + res.idx = i; + res.pos = points[i] + ratio * (points[(i + 1) % mod] - points[i]); + res.dis_from_idx = remainingDistance; + res.pair_idx = pair_idx; + return res; + } + remainingDistance -= segmentLength; + } + res.idx = (startIdx - 1 + mod) % mod; + res.pos = points[startIdx]; + res.pair_idx = pair_idx; + res.dis_from_idx = (res.pos - points[res.idx]).norm(); + } + } else { + int next = (startIdx + 1) % mod; + remainingDistance -= (points[startIdx] - startPoint).norm(); + if (remainingDistance <= 0) { + res.idx = startIdx; + res.pos = startPoint - (points[next] - points[startIdx]).normalized() * offset; + res.dis_from_idx = (res.pos - points[startIdx]).norm(); + res.pair_idx = pair_idx; + return res; + } + for (int i = (startIdx - 1 + mod) % mod; i != startIdx; i = (i - 1 + mod) % mod) { + float segmentLength = (points[(i + 1) % mod] - points[i]).norm(); + if (remainingDistance <= segmentLength) { + float ratio = remainingDistance / segmentLength; + res.idx = i; + res.pos = points[(i + 1) % mod] - ratio * (points[(i + 1) % mod] - points[i]); + res.dis_from_idx = segmentLength - remainingDistance; + res.pair_idx = pair_idx; + return res; + } + remainingDistance -= segmentLength; + } + res.idx = startIdx; + res.pos = points[res.idx]; + res.pair_idx = pair_idx; + res.dis_from_idx = 0; + } + return res; +}; + +void insert_points(std::vector &pl, int idx, Vec2f pos, int pair_idx, bool is_forward) +{ + int next = (idx + 1) % pl.size(); + Vec2f pos1 = pl[idx].pos; + Vec2f pos2 = pl[next].pos; + if ((pos - pos1).squaredNorm() < EPSILON) { + pl[idx].pair_idx = pair_idx; + pl[idx].is_forward = is_forward; + } else if ((pos - pos2).squaredNorm() < EPSILON) { + pl[next].pair_idx = pair_idx; + pl[next].is_forward = is_forward; + } else { + pl.insert(pl.begin() + idx + 1, PointWithFlag{pos, pair_idx, is_forward}); + } +} + +Polylines remove_points_from_polygon(const Polygon &polygon, const std::vector &skip_points, double range, bool is_left, Polygon &insert_skip_pg) +{ + assert(polygon.size() > 2); + Polylines result; + std::vector new_pl;// add intersection points for gaps, where bool indicates whether it's a gap point. + std::vector inter_info; + Vec2f ray = is_left ? Vec2f(-1, 0) : Vec2f(1, 0); + auto polygon_box = get_extents(polygon); + Point anchor_point = is_left ? Point{polygon_box.max[0], polygon_box.min[1]} : polygon_box.min; // rd:ld + std::vector points; + { + points.reserve(polygon.points.size()); + int idx = polygon.closest_point_index(anchor_point); + Polyline tmp_poly = polygon.split_at_index(idx); + for (auto &p : tmp_poly) points.push_back(unscale(p).cast()); + points.pop_back(); + } + + for (int i = 0; i < skip_points.size(); i++) { + for (int j = 0; j < points.size(); j++) { + Vec2f& p1 = points[j]; + Vec2f& p2 = points[(j + 1) % points.size()]; + auto [is_inter, inter_pos] = ray_intersetion_line(skip_points[i], ray, p1, p2); + if (is_inter) { + IntersectionInfo forward = move_point_along_polygon(points, inter_pos, j, range, true, i); + IntersectionInfo backward = move_point_along_polygon(points, inter_pos, j, range, false, i); + backward.is_forward = false; + forward.is_forward = true; + inter_info.push_back(backward); + inter_info.push_back(forward); + break; + } + } + } + + // insert point to new_pl + for (const auto &p : points) new_pl.push_back({p, -1}); + std::sort(inter_info.begin(), inter_info.end(), [](const IntersectionInfo &lhs, const IntersectionInfo &rhs) { + if (rhs.idx == lhs.idx) return lhs.dis_from_idx < rhs.dis_from_idx; + return lhs.idx < rhs.idx; + }); + for (int i = inter_info.size() - 1; i >= 0; i--) { insert_points(new_pl, inter_info[i].idx, inter_info[i].pos, inter_info[i].pair_idx, inter_info[i].is_forward); } + + { + //set insert_pg for wipe_path + for (auto &p : new_pl) insert_skip_pg.points.push_back(scaled(p.pos)); + } + + int beg = 0; + bool skip = true; + int i = beg; + Polyline pl; + + do { + if (skip || new_pl[i].pair_idx == -1) { + pl.points.push_back(scaled(new_pl[i].pos)); + i = (i + 1) % new_pl.size(); + skip = false; + } else { + if (!pl.points.empty()) { + pl.points.push_back(scaled(new_pl[i].pos)); + result.push_back(pl); + pl.points.clear(); + } + int left = new_pl[i].pair_idx; + int j = (i + 1) % new_pl.size(); + while (j != beg && new_pl[j].pair_idx != left) { + if (new_pl[j].pair_idx != -1 && !new_pl[j].is_forward) left = new_pl[j].pair_idx; + j = (j + 1) % new_pl.size(); + } + i = j; + skip = true; + } + } while (i != beg); + + if (!pl.points.empty()) { + if (new_pl[i].pair_idx==-1) pl.points.push_back(scaled(new_pl[i].pos)); + result.push_back(pl); + } + return result; +} + +Polylines contrust_gap_for_skip_points(const Polygon &polygon, const std::vector & skip_points ,float wt_width,float gap_length,Polygon& insert_skip_polygon) +{ + if (skip_points.empty()) { + insert_skip_polygon = polygon; + return Polylines{to_polyline(polygon)}; + } + bool is_left = false; + const auto &pt = skip_points.front(); + if (abs(pt.x()) < wt_width/2.f) { + is_left = true; + } + return remove_points_from_polygon(polygon, skip_points, gap_length, is_left, insert_skip_polygon); + +}; + +Polygon generate_rectange_polygon(const Vec2f &wt_box_min ,const Vec2f & wt_box_max) { + Polygon res; + res.points.push_back(scaled(wt_box_min)); + res.points.push_back(scaled(Vec2f{wt_box_max[0], wt_box_min[1]})); + res.points.push_back(scaled(wt_box_max)); + res.points.push_back(scaled(Vec2f{wt_box_min[0], wt_box_max[1]})); + return res; +} + class WipeTowerWriter { public: @@ -170,7 +630,7 @@ public: float get_and_reset_used_filament_length() { float temp = m_used_filament_length; m_used_filament_length = 0.f; return temp; } // Extrude with an explicitely provided amount of extrusion. - WipeTowerWriter& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) + WipeTowerWriter &extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false ,LimitFlow limit_flow = LimitFlow::LimitPrintFlow) { if ((std::abs(x - m_current_pos.x()) <= (float)EPSILON) && (std::abs(y - m_current_pos.y()) < (float)EPSILON) && e == 0.f && (f == 0.f || f == m_current_feedrate)) // Neither extrusion nor a travel move. @@ -200,6 +660,12 @@ public: m_extrusions.emplace_back(WipeTower::Extrusion(rot, width, m_current_tool)); } + if (e == 0.f) { + m_gcode += set_travel_acceleration(); + } else { + m_gcode += set_normal_acceleration(); + } + m_gcode += "G1"; if (std::abs(rot.x() - rotated_current_pos.x()) > (float)EPSILON) m_gcode += set_format_X(rot.x()); @@ -212,9 +678,11 @@ public: m_gcode += set_format_E(e); if (f != 0.f && f != m_current_feedrate) { - if (limit_volumetric_flow) { + if (limit_flow!= LimitFlow::None) { float e_speed = e / (((len == 0.f) ? std::abs(e) : len) / f * 60.f); - f /= std::max(1.f, e_speed / m_filpar[m_current_tool].max_e_speed); + float tmp = m_filpar[m_current_tool].max_e_speed; + if (limit_flow == LimitFlow::LimitRammingFlow) tmp = m_filpar[m_current_tool].max_e_ramming_speed; + f /= std::max(1.f, e_speed / tmp); } m_gcode += set_format_F(f); } @@ -228,8 +696,86 @@ public: return *this; } - WipeTowerWriter& extrude_explicit(const Vec2f &dest, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) - { return extrude_explicit(dest.x(), dest.y(), e, f, record_length); } + // Extrude with an explicitely provided amount of extrusion. + WipeTowerWriter &extrude_arc_explicit(ArcSegment &arc, float f = 0.f, bool record_length = false, LimitFlow limit_flow = LimitFlow::LimitPrintFlow) + { + float x = (float)unscale(arc.end_point).x(); + float y = (float)unscale(arc.end_point).y(); + float len = unscaled(arc.length); + float e = len * m_extrusion_flow; + if (len < (float) EPSILON && e == 0.f && (f == 0.f || f == m_current_feedrate)) + // Neither extrusion nor a travel move. + return *this; + if (record_length) m_used_filament_length += e; + + // Now do the "internal rotation" with respect to the wipe tower center + Vec2f rotated_current_pos(this->pos_rotated()); + Vec2f rot(this->rotate(Vec2f(x, y))); // this is where we want to go + + if (!m_preview_suppressed && e > 0.f && len > 0.f) { +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + change_analyzer_mm3_per_mm(len, e); +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + // Width of a squished extrusion, corrected for the roundings of the squished extrusions. + // This is left zero if it is a travel move. + float width = e * m_filpar[0].filament_area / (len * m_layer_height); + // Correct for the roundings of a squished extrusion. + width += m_layer_height * float(1. - M_PI / 4.); + if (m_extrusions.empty() || m_extrusions.back().pos != rotated_current_pos) m_extrusions.emplace_back(WipeTower::Extrusion(rotated_current_pos, 0, m_current_tool)); + { + int n = arc_fit_size; + for (int j = 0; j < n; j++) { + float cur_angle = arc.polar_start_theta + (float) j / n * arc.angle_radians; + if (cur_angle > 2 * PI) + cur_angle -= 2 * PI; + else if (cur_angle < 0) + cur_angle += 2 * PI; + Point tmp = arc.center + Point{arc.radius * std::cos(cur_angle), arc.radius * std::sin(cur_angle)}; + m_extrusions.emplace_back(WipeTower::Extrusion(this->rotate(unscaled(tmp)), width, m_current_tool)); + } + m_extrusions.emplace_back(WipeTower::Extrusion(rot, width, m_current_tool)); + } + + } + + if (e == 0.f) { + m_gcode += set_travel_acceleration(); + } else { + m_gcode += set_normal_acceleration(); + } + + m_gcode += arc.direction == ArcDirection::Arc_Dir_CCW ? "G3" : "G2"; + const Vec2f center_offset = this->rotate(unscaled(arc.center)) - rotated_current_pos; + m_gcode += set_format_X(rot.x()); + m_gcode += set_format_Y(rot.y()); + m_gcode += set_format_I(center_offset.x()); + m_gcode += set_format_J(center_offset.y()); + + if (e != 0.f) m_gcode += set_format_E(e); + + if (f != 0.f && f != m_current_feedrate) { + if (limit_flow != LimitFlow::None) { + float e_speed = e / (((len == 0.f) ? std::abs(e) : len) / f * 60.f); + float tmp = m_filpar[m_current_tool].max_e_speed; + if (limit_flow == LimitFlow::LimitRammingFlow) tmp = m_filpar[m_current_tool].max_e_ramming_speed; + f /= std::max(1.f, e_speed / tmp); + } + m_gcode += set_format_F(f); + } + + m_current_pos.x() = x; + m_current_pos.y() = y; + + // Update the elapsed time with a rough estimate. + m_elapsed_time += ((len == 0.f) ? std::abs(e) : len) / m_current_feedrate * 60.f; + m_gcode += "\n"; + return *this; + } + + WipeTowerWriter &extrude_explicit(const Vec2f &dest, float e, float f = 0.f, bool record_length = false, LimitFlow limit_flow = LimitFlow::LimitPrintFlow) + { + return extrude_explicit(dest.x(), dest.y(), e, f, record_length, limit_flow); + } // Travel to a new XY position. f=0 means use the current value. WipeTowerWriter& travel(float x, float y, float f = 0.f) @@ -239,12 +785,16 @@ public: { return extrude_explicit(dest.x(), dest.y(), 0.f, f); } // Extrude a line from current position to x, y with the extrusion amount given by m_extrusion_flow. - WipeTowerWriter& extrude(float x, float y, float f = 0.f) + WipeTowerWriter &extrude(float x, float y, float f = 0.f, LimitFlow limit_flow = LimitFlow::LimitPrintFlow) { float dx = x - m_current_pos.x(); float dy = y - m_current_pos.y(); - return extrude_explicit(x, y, std::sqrt(dx*dx+dy*dy) * m_extrusion_flow, f, true); + return extrude_explicit(x, y, std::sqrt(dx * dx + dy * dy) * m_extrusion_flow, f, false, limit_flow); } + WipeTowerWriter &extrude_arc(ArcSegment &arc, float f = 0.f, LimitFlow limit_flow = LimitFlow::LimitPrintFlow) + { + return extrude_arc_explicit(arc, f, false , limit_flow); + } WipeTowerWriter& extrude(const Vec2f &dest, const float f = 0.f) { return extrude(dest.x(), dest.y(), f); } @@ -273,7 +823,41 @@ public: } while (i != index_of_closest); return (*this); } + WipeTowerWriter &line(const WipeTower *wipe_tower, Vec2f p0, Vec2f p1,const float f = 0.f) + { + bool need_change_flow = wipe_tower->need_thick_bridge_flow(p0.y()); + if (need_change_flow) { + set_extrusion_flow(wipe_tower->extrusion_flow(0.2)); + append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) + std::to_string(0.2) + "\n"); + } + if (abs(x() - p0.x()) > abs(x() - p1.x())) std::swap(p0, p1); + travel(p0.x(), y()); + travel(x(), p0.y()); + extrude(p1, f); + if (need_change_flow) { + set_extrusion_flow(wipe_tower->get_extrusion_flow()); + append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) + std::to_string(m_layer_height) + "\n"); + } + return (*this); + } + WipeTowerWriter &rectangle_fill_box(const WipeTower *wipe_tower, const WipeTower::box_coordinates &fill_box, std::vector &finish_rect_wipe_path, const float f = 0.f) + { + float width = fill_box.rd.x() - fill_box.ld.x(); + float height = fill_box.ru.y() - fill_box.rd.y(); + if (height > wipe_tower->m_perimeter_width - wipe_tower->WT_EPSILON) { + rectangle_fill_box(wipe_tower, fill_box.ld, width, height, f); + Vec2f target = (pos() == fill_box.ld ? fill_box.rd : (pos() == fill_box.rd ? fill_box.ru : (pos() == fill_box.ru ? fill_box.lu : fill_box.ld))); + finish_rect_wipe_path.emplace_back(pos()); + finish_rect_wipe_path.emplace_back(target); + } else if (height > wipe_tower->WT_EPSILON) { + line(wipe_tower, fill_box.ld, fill_box.rd); + Vec2f target = (pos() == fill_box.ld ? fill_box.rd : fill_box.ld); + finish_rect_wipe_path.emplace_back(pos()); + finish_rect_wipe_path.emplace_back(target); + } + return (*this); + } WipeTowerWriter &rectangle_fill_box(const WipeTower* wipe_tower, const Vec2f &ld, float width, float height, const float f = 0.f) { bool need_change_flow = wipe_tower->need_thick_bridge_flow(ld.y()); @@ -321,6 +905,51 @@ public: return (*this); } + WipeTowerWriter &polygon(const Polygon &wall_polygon, const float f = 0.f) + { + Polyline pl = to_polyline(wall_polygon); + pl.simplify(WT_SIMPLIFY_TOLERANCE_SCALED); + pl.simplify_by_fitting_arc(SCALED_WIPE_TOWER_RESOLUTION); + + auto get_closet_idx = [this](std::vector &corners) -> int { + Vec2f anchor{this->m_current_pos.x(), this->m_current_pos.y()}; + int closestIndex = -1; + float minDistance = std::numeric_limits::max(); + for (int i = 0; i < corners.size(); ++i) { + float distance = (corners[i].start - anchor).squaredNorm(); + if (distance < minDistance) { + minDistance = distance; + closestIndex = i; + } + } + return closestIndex; + }; + std::vector segments; + for (int i = 0; i < pl.fitting_result.size(); i++) { + if (pl.fitting_result[i].path_type == EMovePathType::Linear_move) { + for (int j = pl.fitting_result[i].start_point_index; j < pl.fitting_result[i].end_point_index; j++) + segments.push_back({unscaled(pl.points[j]), unscaled(pl.points[j + 1])}); + } else { + int beg = pl.fitting_result[i].start_point_index; + int end = pl.fitting_result[i].end_point_index; + segments.push_back({unscaled(pl.points[beg]), unscaled(pl.points[end])}); + segments.back().is_arc = true; + segments.back().arcsegment = pl.fitting_result[i].arc_data; + } + } + + int index_of_closest = get_closet_idx(segments); + int i = index_of_closest; + travel(segments[i].start); // travel to the closest points + segments[i].is_arc ? extrude_arc(segments[i].arcsegment, f) : extrude(segments[i].end, f); + do { + i = (i + 1) % segments.size(); + if (i == index_of_closest) break; + segments[i].is_arc ? extrude_arc(segments[i].arcsegment, f) : extrude(segments[i].end, f); + } while (1); + return (*this); + } + WipeTowerWriter& load(float e, float f = 0.f) { if (e == 0.f && (f == 0.f || f == m_current_feedrate)) @@ -351,7 +980,7 @@ public: } float end_point = x() + (farthest_x > x() ? 1.f : -1.f) * x_distance; - return extrude_explicit(end_point, y(), loading_dist, x_speed * 60.f, false, false); + return extrude_explicit(end_point, y(), loading_dist, x_speed * 60.f, false, LimitFlow::None); } // Elevate the extruder head above the current print_z position. @@ -372,8 +1001,8 @@ public: // extrude quickly amount e to x2 with feed f. WipeTowerWriter& ram(float x1, float x2, float dy, float e0, float e, float f) { - extrude_explicit(x1, m_current_pos.y() + dy, e0, f, true, false); - extrude_explicit(x2, m_current_pos.y(), e, 0.f, true, false); + extrude_explicit(x1, m_current_pos.y() + dy, e0, f, true, LimitFlow::None); + extrude_explicit(x2, m_current_pos.y(), e, 0.f, true, LimitFlow::None); return *this; } @@ -382,8 +1011,8 @@ public: // at the current Y position to spread the leaking material. WipeTowerWriter& cool(float x1, float x2, float e1, float e2, float f) { - extrude_explicit(x1, m_current_pos.y(), e1, f, false, false); - extrude_explicit(x2, m_current_pos.y(), e2, false, false); + extrude_explicit(x1, m_current_pos.y(), e1, f, false, LimitFlow::None); + extrude_explicit(x2, m_current_pos.y(), e2, 0.f, false, LimitFlow::None); return *this; } @@ -498,6 +1127,163 @@ public: return add_wipe_point(Vec2f(x, y)); } + WipeTowerWriter &add_wipe_path(const Polygon & polygon,double wipe_dist) + { + int closest_idx = polygon.closest_point_index(scaled(m_current_pos)); + Polyline wipe_path = polygon.split_at_index(closest_idx); + wipe_path.reverse(); + for (int i = 0; i < wipe_path.size(); ++i) { + if (wipe_dist < EPSILON) break; + add_wipe_point(unscaled(wipe_path[i])); + if (i != 0) wipe_dist -= (unscaled(wipe_path[i]) - unscaled(wipe_path[i - 1])).norm(); + } + return *this; + } + void generate_path(Polylines &pls, float feedrate, float retract_length, float retract_speed, bool used_fillet) + { + auto get_closet_idx = [this](std::vector &corners) -> int { + Vec2f anchor{this->m_current_pos.x(), this->m_current_pos.y()}; + int closestIndex = -1; + float minDistance = std::numeric_limits::max(); + for (int i = 0; i < corners.size(); ++i) { + float distance = (corners[i].start - anchor).squaredNorm(); + if (distance < minDistance) { + minDistance = distance; + closestIndex = i; + } + } + return closestIndex; + }; + for (auto &pl : pls) pl.simplify_by_fitting_arc(SCALED_WIPE_TOWER_RESOLUTION); + + std::vector segments; + for (const auto &pl : pls) { + if (pl.points.size()<2) continue; + for (int i = 0; i < pl.fitting_result.size(); i++) { + if (pl.fitting_result[i].path_type == EMovePathType::Linear_move) { + for (int j = pl.fitting_result[i].start_point_index; j < pl.fitting_result[i].end_point_index; j++) + segments.push_back({unscaled(pl.points[j]), unscaled(pl.points[j + 1])}); + } else { + int beg = pl.fitting_result[i].start_point_index; + int end = pl.fitting_result[i].end_point_index; + segments.push_back({unscaled(pl.points[beg]), unscaled(pl.points[end])}); + segments.back().is_arc = true; + segments.back().arcsegment = pl.fitting_result[i].arc_data; + } + + } + } + int index_of_closest = get_closet_idx(segments); + int i = index_of_closest; + travel(segments[i].start); // travel to the closest points + segments[i].is_arc? extrude_arc(segments[i].arcsegment,feedrate) : extrude(segments[i].end, feedrate); + do { + i = (i + 1) % segments.size(); + if (i == index_of_closest) break; + float dx = segments[i].start.x() - m_current_pos.x(); + float dy = segments[i].start.y() - m_current_pos.y(); + float len = std::sqrt(dx * dx + dy * dy); + if (len > EPSILON) { + retract(retract_length, retract_speed); + travel(segments[i].start, 600.); + retract(-retract_length, retract_speed); + } + segments[i].is_arc ? extrude_arc(segments[i].arcsegment, feedrate) : extrude(segments[i].end, feedrate); + } while (1); + } + void spiral_flat_ironing(const Vec2f ¢er, float area, float step_length, float feedrate) + { + float edge_length = std::sqrt(area); + Vec2f box_max = center + Vec2f{step_length, step_length}; + Vec2f box_min = center - Vec2f{step_length, step_length}; + int n = std::ceil(edge_length / step_length / 2.f); + assert(n > 0); + while (n--) { + travel(box_max.x(), m_current_pos.y(), feedrate); + travel(m_current_pos.x(), box_max.y(), feedrate); + travel(box_min.x(), m_current_pos.y(), feedrate); + travel(m_current_pos.x(), box_min.y(), feedrate); + + box_max += Vec2f{step_length, step_length}; + box_min -= Vec2f{step_length, step_length}; + } + } + + void set_first_layer(bool is_first_layer) { m_is_first_layer = is_first_layer; } + void set_normal_acceleration(const std::vector &accelerations) { m_normal_accelerations = accelerations; }; + void set_first_layer_normal_acceleration(const std::vector &accelerations) { m_first_layer_normal_accelerations = accelerations; }; + void set_travel_acceleration(const std::vector &accelerations) { m_travel_accelerations = accelerations; }; + void set_first_layer_travel_acceleration(const std::vector &accelerations) { m_first_layer_travel_accelerations = accelerations; }; + void set_max_acceleration(unsigned int acceleration) { m_max_acceleration = acceleration; }; + void set_filament_map(const std::vector &filament_map) { m_filament_map = filament_map; } + void set_accel_to_decel_enable(bool enable) { m_accel_to_decel_enable = enable; } + void set_accel_to_decel_factor(float factor) { m_accel_to_decel_factor = factor; } + +private: + std::string set_normal_acceleration() { + std::vector accelerations = m_is_first_layer ? m_first_layer_normal_accelerations : m_normal_accelerations; + if (accelerations.empty() || m_filament_map.empty()) + return std::string(); + + unsigned int acc = accelerations[m_filament_map[m_current_tool] - 1]; + return set_acceleration_impl(acc); + } + std::string set_travel_acceleration() + { + std::vector accelerations = m_is_first_layer ? m_first_layer_travel_accelerations : m_travel_accelerations; + if (accelerations.empty() || accelerations.empty()) + return std::string(); + + unsigned int acc = accelerations[m_filament_map[m_current_tool] - 1]; + return set_acceleration_impl(acc); + } + std::string set_acceleration_impl(unsigned int acceleration) { + // Clamp the acceleration to the allowed maximum. + if (m_max_acceleration > 0 && acceleration > m_max_acceleration) + acceleration = m_max_acceleration; + + if (acceleration == 0 || acceleration == m_last_acceleration) + return std::string(); + + m_last_acceleration = acceleration; + + std::ostringstream gcode; + if (m_gcode_flavor == gcfRepetier) { + // M201: Set max printing acceleration + gcode << "M201 X" << acceleration << " Y" << acceleration; + gcode << "\n"; + // M202: Set max travel acceleration + gcode << "M202 X" << acceleration << " Y" << acceleration; + } else if (m_gcode_flavor == gcfRepRapFirmware) { + // M204: Set default acceleration + gcode << "M204 P" << acceleration; + } else if (m_gcode_flavor == gcfMarlinFirmware) { + // This is new MarlinFirmware with separated print/retraction/travel acceleration. + // Use M204 P, we don't want to override travel acc by M204 S (which is deprecated anyway). + gcode << "M204 P" << acceleration; + } + else if (m_gcode_flavor == gcfKlipper && m_accel_to_decel_enable) { + gcode << "SET_VELOCITY_LIMIT ACCEL_TO_DECEL=" << acceleration * m_accel_to_decel_factor / 100; + gcode << "\nM204 S" << acceleration; + } + else { + // M204: Set default acceleration + gcode << "M204 S" << acceleration; + } + gcode << "\n"; + return gcode.str(); + } + std::vector m_normal_accelerations; + std::vector m_first_layer_normal_accelerations; + std::vector m_travel_accelerations; + std::vector m_first_layer_travel_accelerations; + bool m_is_first_layer{false}; + unsigned int m_max_acceleration{0}; + unsigned int m_last_acceleration{0}; + std::vector m_filament_map; + bool m_accel_to_decel_enable; + float m_accel_to_decel_factor; + private: Vec2f m_start_pos; Vec2f m_current_pos; @@ -549,6 +1335,8 @@ private: m_current_feedrate = f; return buf; } + std::string set_format_I(float i) { return " I" + Slic3r::float_to_string_decimal_point(i, 3); } + std::string set_format_J(float j) { return " J" + Slic3r::float_to_string_decimal_point(j, 3); } WipeTowerWriter& operator=(const WipeTowerWriter &rhs); @@ -571,6 +1359,7 @@ WipeTower::ToolChangeResult WipeTower::construct_tcr(WipeTowerWriter& writer, bool priming, size_t old_tool, bool is_finish, + bool is_tool_change, float purge_volume) const { ToolChangeResult result; @@ -586,6 +1375,32 @@ WipeTower::ToolChangeResult WipeTower::construct_tcr(WipeTowerWriter& writer, result.extrusions = std::move(writer.extrusions()); result.wipe_path = std::move(writer.wipe_path()); result.is_finish_first = is_finish; + result.nozzle_change_result = m_nozzle_change_result; + result.is_tool_change = is_tool_change; + result.tool_change_start_pos = is_tool_change ? result.start_pos : Vec2f(0, 0); + + // QDS + result.purge_volume = purge_volume; + return result; +} + +WipeTower::ToolChangeResult WipeTower::construct_block_tcr(WipeTowerWriter &writer, bool priming, size_t filament_id, bool is_finish, float purge_volume) const +{ + ToolChangeResult result; + result.priming = priming; + result.initial_tool = int(filament_id); + result.new_tool = int(filament_id); + result.print_z = m_z_pos; + result.layer_height = m_layer_height; + result.elapsed_time = writer.elapsed_time(); + result.start_pos = writer.start_pos_rotated(); + result.end_pos = priming ? writer.pos() : writer.pos_rotated(); + result.gcode = std::move(writer.gcode()); + result.extrusions = std::move(writer.extrusions()); + result.wipe_path = std::move(writer.wipe_path()); + result.is_finish_first = is_finish; + result.is_tool_change = false; + result.tool_change_start_pos = Vec2f(0, 0); // QDS result.purge_volume = purge_volume; return result; @@ -593,10 +1408,167 @@ WipeTower::ToolChangeResult WipeTower::construct_tcr(WipeTowerWriter& writer, // QDS const std::map WipeTower::min_depth_per_height = { - {100.f, 20.f}, {250.f, 40.f} + {5.f,5.f}, {100.f, 20.f}, {250.f, 40.f}, {350.f, 60.f} }; -WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origin, const float prime_volume, size_t initial_tool, const float wipe_tower_height) : +float WipeTower::get_limit_depth_by_height(float max_height) +{ + float min_wipe_tower_depth = 0.f; + auto iter = WipeTower::min_depth_per_height.begin(); + while (iter != WipeTower::min_depth_per_height.end()) { + auto curr_height_to_depth = *iter; + + // This is the case that wipe tower height is lower than the first min_depth_to_height member. + if (curr_height_to_depth.first >= max_height) { + min_wipe_tower_depth = curr_height_to_depth.second; + break; + } + + iter++; + + // If curr_height_to_depth is the last member, use its min_depth. + if (iter == WipeTower::min_depth_per_height.end()) { + min_wipe_tower_depth = curr_height_to_depth.second; + break; + } + + // If wipe tower height is between the current and next member, set the min_depth as linear interpolation between them + auto next_height_to_depth = *iter; + if (next_height_to_depth.first > max_height) { + float height_base = curr_height_to_depth.first; + float height_diff = next_height_to_depth.first - curr_height_to_depth.first; + float min_depth_base = curr_height_to_depth.second; + float depth_diff = next_height_to_depth.second - curr_height_to_depth.second; + + min_wipe_tower_depth = min_depth_base + (max_height - curr_height_to_depth.first) / height_diff * depth_diff; + break; + } + } + return min_wipe_tower_depth; +} + +float WipeTower::get_auto_brim_by_height(float max_height) { + if (max_height < 100) return max_height/100.f * 8.f; + return 8.f; +} + +Vec2f WipeTower::move_box_inside_box(const BoundingBox &box1, const BoundingBox &box2,int scaled_offset) +{ + Vec2f res{0, 0}; + if (box1.size()[0] >= box2.size()[0]- 2*scaled_offset || box1.size()[1] >= box2.size()[1]-2*scaled_offset) return res; + + if (box1.max[0] > box2.max[0] - scaled_offset) { + res[0] = unscaled((box2.max[0] - scaled_offset) - box1.max[0]); + } + else if (box1.min[0] < box2.min[0] + scaled_offset) { + res[0] = unscaled((box2.min[0] + scaled_offset) - box1.min[0]); + } + + if (box1.max[1] > box2.max[1] - scaled_offset) { + res[1] = unscaled((box2.max[1] - scaled_offset) - box1.max[1]); + } + else if (box1.min[1] < box2.min[1] + scaled_offset) { + res[1] = unscaled((box2.min[1] + scaled_offset) - box1.min[1]); + } + return res; +} + +Polygon WipeTower::rib_section(float width, float depth, float rib_length, float rib_width,bool fillet_wall) +{ + Polygon res; + res.points.resize(16); + float theta = std::atan(width / depth); + float costheta = std::cos(theta); + float sintheta = std::sin(theta); + float w = rib_width / 2.f; + float diag = std::sqrt(width * width + depth * depth); + float l = (rib_length - diag) / 2; + Vec2f diag_dir1 = Vec2f{width, depth}.normalized(); + Vec2f diag_dir1_perp{-diag_dir1[1], diag_dir1[0]}; + Vec2f diag_dir2 = Vec2f{-width, depth}.normalized(); + Vec2f diag_dir2_perp{-diag_dir2[1], diag_dir2[0]}; + std::vector p{{0, 0}, {width, 0}, {width, depth}, {0, depth}}; + Polyline p_render; + for (auto &x : p) p_render.points.push_back(scaled(x)); + res.points[0] = scaled(Vec2f{p[0].x(), p[0].y() + w / sintheta}); + res.points[1] = scaled(Vec2f{p[0] - diag_dir1 * l + diag_dir1_perp * w}); + res.points[2] = scaled(Vec2f{p[0] - diag_dir1 * l - diag_dir1_perp * w}); + res.points[3] = scaled(Vec2f{p[0].x() + w / costheta, p[0].y()}); + + res.points[4] = scaled(Vec2f{p[1].x() - w / costheta, p[1].y()}); + res.points[5] = scaled(Vec2f{p[1] - diag_dir2 * l + diag_dir2_perp * w}); + res.points[6] = scaled(Vec2f{p[1] - diag_dir2 * l - diag_dir2_perp * w}); + res.points[7] = scaled(Vec2f{p[1].x(), p[1].y() + w / sintheta}); + + res.points[8] = scaled(Vec2f{p[2].x(), p[2].y() - w / sintheta}); + res.points[9] = scaled(Vec2f{p[2] + diag_dir1 * l - diag_dir1_perp * w}); + res.points[10] = scaled(Vec2f{p[2] + diag_dir1 * l + diag_dir1_perp * w}); + res.points[11] = scaled(Vec2f{p[2].x() - w / costheta, p[2].y()}); + + res.points[12] = scaled(Vec2f{p[3].x() + w / costheta, p[3].y()}); + res.points[13] = scaled(Vec2f{p[3] + diag_dir2 * l - diag_dir2_perp * w}); + res.points[14] = scaled(Vec2f{p[3] + diag_dir2 * l + diag_dir2_perp * w}); + res.points[15] = scaled(Vec2f{p[3].x(), p[3].y() - w / sintheta}); + res.remove_duplicate_points(); + if (fillet_wall) { res = rounding_polygon(res); } + res.points.shrink_to_fit(); + return res; +} + +TriangleMesh WipeTower::its_make_rib_tower(float width, float depth, float height, float rib_length, float rib_width, bool fillet_wall) +{ + TriangleMesh res; + Polygon bottom = rib_section(width, depth, rib_length, rib_width, fillet_wall); + Polygon top = rib_section(width, depth, std::sqrt(width * width + depth * depth), rib_width, fillet_wall); + if (fillet_wall) + assert(bottom.points.size() == top.points.size()); + int offset = bottom.points.size(); + res.its.vertices.reserve(offset * 2); + auto faces_bottom = Triangulation::triangulate(bottom); + auto faces_top = Triangulation::triangulate(top); + res.its.indices.reserve(offset * 2 + faces_bottom.size() + faces_top.size()); + for (auto &t : faces_bottom) res.its.indices.push_back({t[1], t[0], t[2]}); + for (auto &t : faces_top) res.its.indices.push_back({t[0] + offset, t[1] + offset, t[2] + offset}); + + for (int i = 0; i < bottom.size(); i++) res.its.vertices.push_back({unscaled(bottom[i][0]), unscaled(bottom[i][1]), 0}); + for (int i = 0; i < top.size(); i++) res.its.vertices.push_back({unscaled(top[i][0]), unscaled(top[i][1]), height}); + + for (int i = 0; i < offset; i++) { + int a = i; + int b = (i + 1) % offset; + int c = i + offset; + int d = b + offset; + res.its.indices.push_back({a, b, c}); + res.its.indices.push_back({d, c, b}); + } + return res; +} + +TriangleMesh WipeTower::its_make_rib_brim(const Polygon& brim, float layer_height) { + TriangleMesh res; + int offset = brim.size(); + res.its.vertices.reserve(brim.size() * 2); + auto faces= Triangulation::triangulate(brim); + res.its.indices.reserve(brim.size() * 2 + 2 * faces.size()); + for (auto &t : faces) res.its.indices.push_back({t[1], t[0], t[2]}); + for (auto &t : faces) res.its.indices.push_back({t[0] + offset, t[1] + offset, t[2] + offset}); + + for (int i = 0; i < brim.size(); i++) res.its.vertices.push_back({unscaled(brim[i][0]), unscaled(brim[i][1]), 0}); + for (int i = 0; i < brim.size(); i++) res.its.vertices.push_back({unscaled(brim[i][0]), unscaled(brim[i][1]), layer_height}); + + for (int i = 0; i < offset; i++) { + int a = i; + int b = (i + 1) % offset; + int c = i + offset; + int d = b + offset; + res.its.indices.push_back({a, b, c}); + res.its.indices.push_back({d, c, b}); + } + return res; +} + + +WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origin, size_t initial_tool, const float wipe_tower_height) : m_semm(config.single_extruder_multi_material.value), m_wipe_tower_pos(config.wipe_tower_x.get_at(plate_idx), config.wipe_tower_y.get_at(plate_idx)), m_wipe_tower_width(float(config.prime_tower_width)), @@ -610,17 +1582,51 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi m_bridging(10.f), m_no_sparse_layers(config.wipe_tower_no_sparse_layers), m_gcode_flavor(config.gcode_flavor), - m_travel_speed(config.travel_speed), + m_travel_speed(config.travel_speed.get_at(get_extruder_index(config, (unsigned int)initial_tool))), m_current_tool(initial_tool), //wipe_volumes(flush_matrix) - m_wipe_volume(prime_volume), - m_enable_timelapse_print(config.timelapse_type.value == TimelapseType::tlSmooth) + m_enable_timelapse_print(config.timelapse_type.value == TimelapseType::tlSmooth), + m_filaments_change_length(config.filament_change_length.values), + m_is_multi_extruder(config.nozzle_diameter.size() > 1), + m_use_gap_wall(config.prime_tower_skip_points.value), + m_use_rib_wall(config.prime_tower_rib_wall.value), + m_extra_rib_length((float)config.prime_tower_extra_rib_length.value), + m_rib_width((float)config.prime_tower_rib_width.value), + m_used_fillet(config.prime_tower_fillet_wall.value), + m_extra_spacing((float)config.prime_tower_infill_gap.value/100.f), + m_tower_framework(config.prime_tower_enable_framework.value), + m_max_speed((float)config.prime_tower_max_speed.value*60.f), + m_printable_height(config.extruder_printable_height.values), + m_accel_to_decel_enable(config.accel_to_decel_enable.value), + m_accel_to_decel_factor(config.accel_to_decel_factor.value) { + m_normal_accels.clear(); + for (auto value : config.default_acceleration.values) { + m_normal_accels.emplace_back((unsigned int) floor(value + 0.5)); + } + + m_first_layer_normal_accels.clear(); + for (auto value : config.initial_layer_acceleration.values) { + m_first_layer_normal_accels.emplace_back((unsigned int) floor(value + 0.5)); + } + + m_travel_accels.clear(); + for (auto value : config.travel_acceleration.values) { + m_travel_accels.emplace_back((unsigned int) floor(value + 0.5)); + } + + m_first_layer_travel_accels.clear(); + for (auto value : config.initial_layer_travel_acceleration.values) { + m_first_layer_travel_accels.emplace_back((unsigned int) floor(value + 0.5)); + } + + m_max_accels = config.machine_max_acceleration_extruding.values.front(); + // Read absolute value of first layer speed, if given as percentage, // it is taken over following default. Speeds from config are not // easily accessible here. const float default_speed = 60.f; - m_first_layer_speed = config.get_abs_value("initial_layer_speed"); + m_first_layer_speed = config.initial_layer_speed.get_at(get_extruder_index(config, (unsigned int)initial_tool)); if (m_first_layer_speed == 0.f) // just to make sure autospeed doesn't break it. m_first_layer_speed = default_speed / 2.f; @@ -657,6 +1663,8 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi m_bed_bottom_left = m_bed_shape == RectangularBed ? Vec2f(bed_points.front().x(), bed_points.front().y()) : Vec2f::Zero(); + flat_ironing = config.nozzle_diameter.values.size() > 1;//Only used for dual extrusion + m_last_layer_id.resize(config.nozzle_diameter.size(), -1); } @@ -672,6 +1680,7 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config) m_filpar[idx].is_support = config.filament_is_support.get_at(idx); m_filpar[idx].nozzle_temperature = config.nozzle_temperature.get_at(idx); m_filpar[idx].nozzle_temperature_initial_layer = config.nozzle_temperature_initial_layer.get_at(idx); + m_filpar[idx].category = config.filament_adhesiveness_category.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. @@ -697,7 +1706,26 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config) if (max_vol_speed!= 0.f) m_filpar[idx].max_e_speed = (max_vol_speed / filament_area()); + float ramming_vol_speed = float(config.filament_ramming_volumetric_speed.get_at(idx)); + if (config.filament_ramming_volumetric_speed.is_nil(idx) || is_approx(config.filament_ramming_volumetric_speed.get_at(idx),-1.)) ramming_vol_speed = max_vol_speed; + m_filpar[idx].max_e_ramming_speed = (ramming_vol_speed / filament_area()); + std::unordered_set uniqueElements(m_filament_map.begin(), m_filament_map.end()); + m_filpar[idx].precool_t.resize(uniqueElements.size(), 0.f); + m_filpar[idx].precool_t_first_layer.resize(uniqueElements.size(), 0.f); + if (config.enable_pre_heating.value && !config.filament_pre_cooling_temperature.is_nil(idx) && config.filament_pre_cooling_temperature.get_at(idx) != 0) { + for (int i = 0; i < m_filpar[idx].precool_t.size(); i++) { + m_filpar[idx].precool_t[i] = std::max(0.f, float(config.nozzle_temperature.get_at(idx)) - float(config.filament_pre_cooling_temperature.get_at(idx))) / + float(config.hotend_cooling_rate.values.at(i)); + m_filpar[idx].precool_t_first_layer[i] = std::max(0.f, float(config.nozzle_temperature_initial_layer.get_at(idx)) - + float(config.filament_pre_cooling_temperature.get_at(idx))) / + float(config.hotend_cooling_rate.values.at(i)); + } + } + if (!config.filament_ramming_travel_time.is_nil(idx)) + m_filpar[idx].ramming_travel_time = float(config.filament_ramming_travel_time.get_at(idx)); + m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter + m_nozzle_change_perimeter_width = 2*m_perimeter_width; // QDS: remove useless config #if 0 if (m_semm) { @@ -712,6 +1740,10 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config) #endif m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later + + m_filpar[idx].retract_length = config.retraction_length.get_at(idx); + m_filpar[idx].retract_speed = config.retraction_speed.get_at(idx); + m_filpar[idx].wipe_dist = config.wipe_distance.get_at(idx); } @@ -729,14 +1761,51 @@ std::vector WipeTower::prime( return std::vector(); } +Vec2f WipeTower::get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length) +{ + const float &xl = cleaning_box.ld.x(); + const float &xr = cleaning_box.rd.x(); + int line_count = wipe_length / (xr - xl); + + float dy = m_layer_info->extra_spacing * m_perimeter_width; + float y_offset = float(line_count) * dy; + const Vec2f pos_offset = Vec2f(0.f, m_depth_traversed); + + Vec2f res; + int index = m_cur_layer_id % 4; + //Vec2f offset = m_use_gap_wall ? Vec2f(5 * m_perimeter_width, 0) : Vec2f{0, 0}; + Vec2f offset = Vec2f{0, 0}; + switch (index % 4) { + case 0: + res = offset +cleaning_box.ld + pos_offset; + break; + case 1: + res = -offset +cleaning_box.rd + pos_offset + Vec2f(0, y_offset); + break; + case 2: + res = -offset+ cleaning_box.rd + pos_offset; + break; + case 3: + res = offset+cleaning_box.ld + pos_offset + Vec2f(0, y_offset); + break; + default: break; + } + return res; +} + WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool extrude_perimeter, bool first_toolchange_to_nonsoluble) { + m_nozzle_change_result.gcode.clear(); + if (!m_filament_map.empty() && tool < m_filament_map.size() && m_filament_map[m_current_tool] != m_filament_map[tool]) { + m_nozzle_change_result = nozzle_change(m_current_tool, tool); + } + size_t old_tool = m_current_tool; float wipe_depth = 0.f; float wipe_length = 0.f; float purge_volume = 0.f; - + float nozzle_change_depth = 0.f; // Finds this toolchange info if (tool != (unsigned int)(-1)) { @@ -745,6 +1814,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool extrude_per wipe_length = b.wipe_length; wipe_depth = b.required_depth; purge_volume = b.purge_volume; + nozzle_change_depth = b.nozzle_change_depth; break; } } @@ -767,6 +1837,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool extrude_per "; CP TOOLCHANGE START\n") .comment_with_value(" toolchange #", m_num_tool_changes + 1); // the number is zero-based + set_for_wipe_tower_writer(writer); if (tool != (unsigned)(-1)) writer.append(std::string("; material : " + (m_current_tool < m_filpar.size() ? m_filpar[m_current_tool].material : "(NONE)") + " -> " + m_filpar[tool].material + "\n").c_str()) @@ -775,8 +1846,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool extrude_per writer.speed_override_backup(); writer.speed_override(100); - Vec2f initial_position = cleaning_box.ld + Vec2f(0.f, m_depth_traversed); - writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); + float feedrate = is_first_layer() ? std::min(m_first_layer_speed * 60.f, 5400.f) : std::min(60.0f * m_filpar[m_current_tool].max_e_speed / m_extrusion_flow, 5400.f); // Increase the extruder driver current to allow fast ramming. //QDS @@ -792,11 +1862,46 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool extrude_per toolchange_Load(writer, cleaning_box); // QDS //writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road - if (extrude_perimeter) { - box_coordinates wt_box(Vec2f(0.f, (m_current_shape == SHAPE_REVERSED) ? m_layer_info->toolchanges_depth() - m_layer_info->depth : 0.f), - m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); - // align the perimeter + if (m_is_multi_extruder && is_tpu_filament(tool)) { + float dy = 2 * m_perimeter_width; + float nozzle_change_speed = 60.0f * m_filpar[tool].max_e_speed / m_extrusion_flow; + nozzle_change_speed *= 0.25; + + const float &xl = cleaning_box.ld.x(); + const float &xr = cleaning_box.rd.x(); + + Vec2f start_pos = m_nozzle_change_result.start_pos + Vec2f(0, m_perimeter_width); + bool left_to_right = true; + double tpu_travel_length = 5; + double e_flow = extrusion_flow(m_layer_height); + double length = tpu_travel_length / e_flow; + int tpu_line_count = length / (m_wipe_tower_width - 2 * m_perimeter_width) + 1; + + writer.travel(start_pos); + + for (int i = 0; true; ++i) { + if (left_to_right) + writer.travel(xr - m_perimeter_width, writer.y(), nozzle_change_speed); + else + writer.travel(xl + m_perimeter_width, writer.y(), nozzle_change_speed); + + if (i == tpu_line_count - 1) + break; + + writer.travel(writer.x(), writer.y() + dy); + left_to_right = !left_to_right; + } + } + + Vec2f initial_position = get_next_pos(cleaning_box, wipe_length); + writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); + + if (extrude_perimeter) { + box_coordinates wt_box(Vec2f(0.f, (m_current_shape == SHAPE_REVERSED) ? m_layer_info->toolchanges_depth() - m_layer_info->depth : 0.f), m_wipe_tower_width, + m_layer_info->depth + m_perimeter_width); + + // align the perimeter Vec2f pos = initial_position; switch (m_cur_layer_id % 4){ case 0: @@ -816,20 +1921,19 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool extrude_per writer.set_initial_position(pos, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); wt_box = align_perimeter(wt_box); - writer.rectangle(wt_box); + writer.rectangle(wt_box, feedrate); } - { - writer.travel(Vec2f(0, 0)); - writer.travel(initial_position); - } + writer.travel(initial_position); + toolchange_Wipe(writer, cleaning_box, wipe_length); // Wipe the newly loaded filament until the end of the assigned wipe area. + writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_End) + "\n"); ++ m_num_tool_changes; } else toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].nozzle_temperature); - m_depth_traversed += wipe_depth; + m_depth_traversed += (wipe_depth - nozzle_change_depth); //QDS //if (m_set_extruder_trimpot) @@ -846,9 +1950,136 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool extrude_per if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - return construct_tcr(writer, false, old_tool, false, purge_volume); + return construct_tcr(writer, false, old_tool, false, true, purge_volume); } +WipeTower::NozzleChangeResult WipeTower::nozzle_change(int old_filament_id, int new_filament_id) +{ + float wipe_depth = 0.f; + float wipe_length = 0.f; + float purge_volume = 0.f; + int nozzle_change_line_count = 0; + + // Finds this toolchange info + if (new_filament_id != (unsigned int) (-1)) { + for (const auto &b : m_layer_info->tool_changes) + if (b.new_tool == new_filament_id) { + wipe_length = b.wipe_length; + wipe_depth = b.required_depth; + purge_volume = b.purge_volume; + if (has_tpu_filament()) + nozzle_change_line_count = ((b.nozzle_change_depth + WT_EPSILON) / m_nozzle_change_perimeter_width) / 2; + else + nozzle_change_line_count = (b.nozzle_change_depth + WT_EPSILON) / m_nozzle_change_perimeter_width; + break; + } + } else { + // Otherwise we are going to Unload only. And m_layer_info would be invalid. + } + + auto format_nozzle_change_line = [](bool start, int old_filament_id, int new_filament_id)->std::string { + char buff[64]; + std::string tag = start ? GCodeProcessor::reserved_tag(GCodeProcessor::ETags::NozzleChangeStart) : GCodeProcessor::reserved_tag(GCodeProcessor::ETags::NozzleChangeEnd); + snprintf(buff, sizeof(buff), ";%s OF%d NF%d\n", tag.c_str(), old_filament_id, new_filament_id); + return std::string(buff); + }; + + float nozzle_change_speed = 60.0f * m_filpar[m_current_tool].max_e_speed / m_extrusion_flow; + if (is_tpu_filament(m_current_tool)) { + nozzle_change_speed *= 0.25; + } + + WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); + writer.set_extrusion_flow(m_extrusion_flow) + .set_z(m_z_pos) + .set_initial_tool(m_current_tool) + .set_extrusion_flow(m_extrusion_flow) + .set_y_shift(m_y_shift + (new_filament_id != (unsigned int) (-1) && (m_current_shape == SHAPE_REVERSED) ? m_layer_info->depth - m_layer_info->toolchanges_depth() : 0.f)) + .append(format_nozzle_change_line(true,old_filament_id,new_filament_id)); + + set_for_wipe_tower_writer(writer); + + box_coordinates cleaning_box(Vec2f(m_perimeter_width, m_perimeter_width), m_wipe_tower_width - 2 * m_perimeter_width, + (new_filament_id != (unsigned int) (-1) ? wipe_depth + m_depth_traversed - m_perimeter_width : m_wipe_tower_depth - m_perimeter_width)); + + Vec2f initial_position = cleaning_box.ld + Vec2f(0.f, m_depth_traversed); + writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); + + const float &xl = cleaning_box.ld.x(); + const float &xr = cleaning_box.rd.x(); + + float dy = m_layer_info->extra_spacing * m_perimeter_width; + if (has_tpu_filament()) + dy = 2 * m_perimeter_width; + + float start_y = writer.y(); + + m_left_to_right = true; + + bool need_change_flow = false; + // now the wiping itself: + for (int i = 0; true; ++i) { + if (m_left_to_right) + writer.extrude(xr + wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), nozzle_change_speed); + else + writer.extrude(xl - wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), nozzle_change_speed); + + if (writer.y() - float(EPSILON) > cleaning_box.lu.y()) + break; // in case next line would not fit + + if (i == nozzle_change_line_count - 1) + break; + + // stepping to the next line: + writer.extrude(writer.x(), writer.y() + dy); + m_left_to_right = !m_left_to_right; + } + + writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow. + + m_depth_traversed += nozzle_change_line_count * dy; + + NozzleChangeResult result; + + if (is_tpu_filament(m_current_tool)) + { + bool left_to_right = !m_left_to_right; + double tpu_travel_length = 5; + double e_flow = extrusion_flow(m_layer_height); + double length = tpu_travel_length / e_flow; + int tpu_line_count = length / (m_wipe_tower_width - 2 * m_perimeter_width) + 1; + + writer.travel(writer.x(), writer.y() - m_perimeter_width); + + for (int i = 0; true; ++i) { + if (left_to_right) + writer.travel(xr - m_perimeter_width, writer.y(), nozzle_change_speed); + else + writer.travel(xl + m_perimeter_width, writer.y(), nozzle_change_speed); + + if (i == tpu_line_count - 1) + break; + + writer.travel(writer.x(), writer.y() - dy); + left_to_right = !left_to_right; + } + } + else { + result.wipe_path.push_back(writer.pos()); + if (m_left_to_right) { + result.wipe_path.push_back(Vec2f(0, writer.y())); + } else { + result.wipe_path.push_back(Vec2f(m_wipe_tower_width, writer.y())); + } + } + + writer.append(format_nozzle_change_line(false, old_filament_id, new_filament_id)); + + result.start_pos = writer.start_pos_rotated(); + result.end_pos = writer.pos(); + result.gcode = writer.gcode(); + return result; +} // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. void WipeTower::toolchange_Unload( @@ -1094,7 +2325,8 @@ void WipeTower::toolchange_Wipe( } #endif - m_left_to_right = true; + m_left_to_right = ((m_cur_layer_id + 3) % 4 >= 2); + bool is_from_up = (m_cur_layer_id % 2 == 1); // QDS: do not need to move dy #if 0 @@ -1130,9 +2362,12 @@ void WipeTower::toolchange_Wipe( writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) + std::to_string(m_layer_height) + "\n"); } - if (writer.y() - float(EPSILON) > cleaning_box.lu.y()) + if (!is_from_up && (writer.y() - float(EPSILON) > cleaning_box.lu.y())) break; // in case next line would not fit + if (is_from_up && (writer.y() + float(EPSILON) < cleaning_box.ld.y())) + break; + x_to_wipe -= (xr - xl); if (x_to_wipe < WT_EPSILON) { // QDS: Delete some unnecessary travel @@ -1140,7 +2375,11 @@ void WipeTower::toolchange_Wipe( break; } // stepping to the next line: - writer.extrude(writer.x(), writer.y() + dy); + if (is_from_up) + writer.extrude(writer.x(), writer.y() - dy); + else + writer.extrude(writer.x(), writer.y() + dy); + m_left_to_right = !m_left_to_right; } @@ -1173,12 +2412,12 @@ WipeTower::box_coordinates WipeTower::align_perimeter(const WipeTower::box_coord box_coordinates aligned_box = perimeter_box; float spacing = m_extra_spacing * m_perimeter_width; - float up = perimeter_box.lu(1) - m_perimeter_width; + float up = perimeter_box.lu(1) - m_perimeter_width - EPSILON; up = align_ceil(up, spacing); up += m_perimeter_width; up = std::min(up, m_wipe_tower_depth); - float down = perimeter_box.ld(1) - m_perimeter_width; + float down = perimeter_box.ld(1) - m_perimeter_width + EPSILON; down = align_floor(down, spacing); down += m_perimeter_width; down = std::max(down, -m_y_shift); @@ -1189,6 +2428,19 @@ WipeTower::box_coordinates WipeTower::align_perimeter(const WipeTower::box_coord return aligned_box; } +void WipeTower::set_for_wipe_tower_writer(WipeTowerWriter &writer) +{ + writer.set_normal_acceleration(m_normal_accels); + writer.set_travel_acceleration(m_travel_accels); + writer.set_first_layer_normal_acceleration(m_first_layer_normal_accels); + writer.set_first_layer_travel_acceleration(m_first_layer_travel_accels); + writer.set_max_acceleration(m_max_accels); + writer.set_filament_map(m_filament_map); + writer.set_accel_to_decel_enable(m_accel_to_decel_enable); + writer.set_accel_to_decel_factor(m_accel_to_decel_factor); + writer.set_first_layer(m_cur_layer_id == 0); +} + WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool extruder_fill) { assert(! this->layer_finished()); @@ -1202,13 +2454,14 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool .set_initial_tool(m_current_tool) .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)); + set_for_wipe_tower_writer(writer); + writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_Start) + "\n"); // Slow down on the 1st layer. bool first_layer = is_first_layer(); // QDS: speed up perimeter speed to 90mm/s for non-first layer float feedrate = first_layer ? std::min(m_first_layer_speed * 60.f, 5400.f) : std::min(60.0f * m_filpar[m_current_tool].max_e_speed / m_extrusion_flow, 5400.f); - writer.feedrate(feedrate); float fill_box_y = m_layer_info->toolchanges_depth() + m_perimeter_width; box_coordinates fill_box(Vec2f(m_perimeter_width, fill_box_y), m_wipe_tower_width - 2 * m_perimeter_width, m_layer_info->depth - fill_box_y); @@ -1319,7 +2572,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool box_coordinates box = wt_box; for (size_t i = 0; i < loops_num; ++i) { box.expand(spacing); - writer.rectangle(box); + writer.rectangle(box, feedrate); } if (first_layer) { @@ -1351,7 +2604,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - return construct_tcr(writer, false, old_tool, true, 0.f); + return construct_tcr(writer, false, old_tool, true, false, 0.f); } // Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box @@ -1399,7 +2652,20 @@ void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned in depth += std::ceil(length_to_extrude / width) * m_perimeter_width; //depth *= m_extra_spacing; - m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, depth, 0.f, 0.f, wipe_volume, length_to_extrude, purge_volume)); + float nozzle_change_depth = 0; + if (!m_filament_map.empty() && m_filament_map[old_tool] != m_filament_map[new_tool]) { + double e_flow = nozzle_change_extrusion_flow(layer_height_par); + double length = m_filaments_change_length[old_tool] / e_flow; + int nozzle_change_line_count = std::ceil(length / (m_wipe_tower_width - 2*m_nozzle_change_perimeter_width)); + if (m_need_reverse_travel) + nozzle_change_depth = m_tpu_fixed_spacing * nozzle_change_line_count * m_nozzle_change_perimeter_width; + else + nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width; + depth += nozzle_change_depth; + } + WipeTowerInfo::ToolChange tool_change = WipeTowerInfo::ToolChange(old_tool, new_tool, depth, 0.f, 0.f, wipe_volume, length_to_extrude, purge_volume); + tool_change.nozzle_change_depth = nozzle_change_depth; + m_plan.back().tool_changes.push_back(tool_change); #endif } @@ -1413,43 +2679,13 @@ void WipeTower::plan_tower() for (auto& info : m_plan) max_depth = std::max(max_depth, info.toolchanges_depth()); - float min_wipe_tower_depth = 0.f; - auto iter = WipeTower::min_depth_per_height.begin(); - while (iter != WipeTower::min_depth_per_height.end()) { - auto curr_height_to_depth = *iter; - - // This is the case that wipe tower height is lower than the first min_depth_to_height member. - if (curr_height_to_depth.first >= m_wipe_tower_height) { - min_wipe_tower_depth = curr_height_to_depth.second; - break; - } - - iter++; - - // If curr_height_to_depth is the last member, use its min_depth. - if (iter == WipeTower::min_depth_per_height.end()) { - min_wipe_tower_depth = curr_height_to_depth.second; - break; - } - - // If wipe tower height is between the current and next member, set the min_depth as linear interpolation between them - auto next_height_to_depth = *iter; - if (next_height_to_depth.first > m_wipe_tower_height) { - float height_base = curr_height_to_depth.first; - float height_diff = next_height_to_depth.first - curr_height_to_depth.first; - float min_depth_base = curr_height_to_depth.second; - float depth_diff = next_height_to_depth.second - curr_height_to_depth.second; - - min_wipe_tower_depth = min_depth_base + (m_wipe_tower_height - curr_height_to_depth.first) / height_diff * depth_diff; - break; - } - } + float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(m_wipe_tower_height); { if (m_enable_timelapse_print && max_depth < EPSILON) max_depth = min_wipe_tower_depth; - if (max_depth + EPSILON < min_wipe_tower_depth) + if (max_depth + EPSILON < min_wipe_tower_depth && !has_tpu_filament()) m_extra_spacing = min_wipe_tower_depth / max_depth; else m_extra_spacing = 1.f; @@ -1467,6 +2703,12 @@ void WipeTower::plan_tower() x_to_wipe_new = std::max(x_to_wipe_new, x_to_wipe); int line_count = std::ceil((x_to_wipe_new - WT_EPSILON) / line_len); + + { // nozzle change length + int nozzle_change_line_count = (toolchange.nozzle_change_depth + WT_EPSILON) / m_perimeter_width; + line_count += nozzle_change_line_count; + } + toolchange.required_depth = line_count * m_perimeter_width; toolchange.wipe_volume = x_to_wipe_new / x_to_wipe * toolchange.wipe_volume; toolchange.wipe_length = x_to_wipe_new; @@ -1490,7 +2732,7 @@ void WipeTower::plan_tower() float max_depth_for_all = 0; for (int layer_index = int(m_plan.size()) - 1; layer_index >= 0; --layer_index) { - float this_layer_depth = std::max(m_plan[layer_index].depth, m_plan[layer_index].toolchanges_depth()); + float this_layer_depth = std::max(m_plan[layer_index].depth, m_plan[layer_index].toolchanges_depth()); if (m_enable_timelapse_print && this_layer_depth < EPSILON) this_layer_depth = min_wipe_tower_depth; @@ -1546,6 +2788,15 @@ void WipeTower::save_on_last_wipe() } } +bool WipeTower::is_tpu_filament(int filament_id) const +{ + return m_filpar[filament_id].material == "TPU"; +} + +bool WipeTower::is_need_reverse_travel(int filament_id) const +{ + return m_filpar[filament_id].ramming_travel_time > EPSILON; +} // QDS: consider both soluable and support properties // Return index of first toolchange that switches to non-soluble and non-support extruder @@ -1559,15 +2810,24 @@ int WipeTower::first_toolchange_to_nonsoluble_nonsupport( return -1; } -static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first, - WipeTower::ToolChangeResult& second) +WipeTower::ToolChangeResult WipeTower::merge_tcr(ToolChangeResult &first, ToolChangeResult &second) { assert(first.new_tool == second.initial_tool); WipeTower::ToolChangeResult out = first; - if (first.end_pos != second.start_pos) - out.gcode += "G1 X" + Slic3r::float_to_string_decimal_point(second.start_pos.x(), 3) - + " Y" + Slic3r::float_to_string_decimal_point(second.start_pos.y(), 3) - + "\n"; + if ((first.end_pos - second.start_pos).norm() > (float)EPSILON) { + std::string travel_gcode = "G1 X" + Slic3r::float_to_string_decimal_point(second.start_pos.x(), 3) + " Y" + + Slic3r::float_to_string_decimal_point(second.start_pos.y(), 3) + "F" + std::to_string(m_max_speed) + "\n"; + bool need_insert_travel = true; + if (second.is_tool_change + && is_approx(second.start_pos.x(), second.tool_change_start_pos.x()) + && is_approx(second.start_pos.y(), second.tool_change_start_pos.y())) { + // will insert travel in gcode.cpp + need_insert_travel = false; + } + + if (need_insert_travel) + out.gcode += travel_gcode; + } out.gcode += second.gcode; out.extrusions.insert(out.extrusions.end(), second.extrusions.begin(), second.extrusions.end()); out.end_pos = second.end_pos; @@ -1575,11 +2835,1405 @@ static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first, out.initial_tool = first.initial_tool; out.new_tool = second.new_tool; + if (!first.nozzle_change_result.gcode.empty()) + out.nozzle_change_result = first.nozzle_change_result; + else if (!second.nozzle_change_result.gcode.empty()) + out.nozzle_change_result = second.nozzle_change_result; + + if (first.is_tool_change) { + out.is_tool_change = true; + out.tool_change_start_pos = first.tool_change_start_pos; + } + else if (second.is_tool_change) { + out.is_tool_change = true; + out.tool_change_start_pos = second.tool_change_start_pos; + } + else { + out.is_tool_change = false; + } + // QDS out.purge_volume += second.purge_volume; return out; } +void WipeTower::get_wall_skip_points(const WipeTowerInfo &layer) +{ + m_wall_skip_points.clear(); + std::unordered_map cur_block_depth; + for (int i = 0; i < int(layer.tool_changes.size()); ++i) { + const WipeTowerInfo::ToolChange &tool_change = layer.tool_changes[i]; + size_t old_filament = tool_change.old_tool; + size_t new_filament = tool_change.new_tool; + float spacing = m_layer_info->extra_spacing; + if (m_need_reverse_travel && m_layer_info->extra_spacing < m_tpu_fixed_spacing) spacing = 1; + else if (m_need_reverse_travel) spacing = spacing / m_tpu_fixed_spacing; + float nozzle_change_depth = tool_change.nozzle_change_depth * spacing; + //float nozzle_change_depth = tool_change.nozzle_change_depth * (has_tpu_filament() ? m_tpu_fixed_spacing : layer.extra_spacing); + auto* block = get_block_by_category(m_filpar[new_filament].category, false); + if (!block) + continue; + //float wipe_depth = tool_change.required_depth - nozzle_change_depth; + float wipe_depth = ceil(tool_change.wipe_length / (m_wipe_tower_width - 2 * m_perimeter_width)) * m_perimeter_width*layer.extra_spacing; + float process_depth = 0.f; + if (!cur_block_depth.count(m_filpar[new_filament].category)) + cur_block_depth[m_filpar[new_filament].category] = block->start_depth; + process_depth = cur_block_depth[m_filpar[new_filament].category]; + if (!m_filament_map.empty() && new_filament < m_filament_map.size() && m_filament_map[old_filament] != m_filament_map[new_filament]) { + if (m_filament_categories[new_filament] == m_filament_categories[old_filament]) + process_depth += nozzle_change_depth; + else { + if (!cur_block_depth.count(m_filpar[old_filament].category)) { + auto* old_block = get_block_by_category(m_filpar[old_filament].category, false); + if (!old_block) + continue; + cur_block_depth[m_filpar[old_filament].category] = old_block->start_depth; + } + cur_block_depth[m_filpar[old_filament].category] += nozzle_change_depth; + } + } + { + Vec2f res; + int index = m_cur_layer_id % 4; + switch (index % 4) { + case 0: res = Vec2f(0, process_depth); break; + case 1: res = Vec2f(m_wipe_tower_width, process_depth + wipe_depth - layer.extra_spacing*m_perimeter_width); break; + case 2: res = Vec2f(m_wipe_tower_width, process_depth); break; + case 3: res = Vec2f(0, process_depth + wipe_depth - layer.extra_spacing * m_perimeter_width); break; + default: break; + } + + m_wall_skip_points.emplace_back(res); + } + cur_block_depth[m_filpar[new_filament].category] = process_depth + tool_change.required_depth - tool_change.nozzle_change_depth * layer.extra_spacing; + } + } + +WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool solid_toolchange,bool solid_nozzlechange) +{ + m_nozzle_change_result.gcode.clear(); + if (!m_filament_map.empty() && new_tool < m_filament_map.size() && m_filament_map[m_current_tool] != m_filament_map[new_tool]) { + //If it is the last layer and exceeds the printable height, cancel ramming + if (is_valid_last_layer(m_current_tool)) + m_nozzle_change_result = nozzle_change_new(m_current_tool, new_tool, solid_nozzlechange); + } + + size_t old_tool = m_current_tool; + float wipe_depth = 0.f; + float wipe_length = 0.f; + float purge_volume = 0.f; + float nozzle_change_depth = 0.f; + int nozzle_change_line_count = 0; + + if (new_tool != (unsigned int) (-1)) { + for (const auto &b : m_layer_info->tool_changes) + if (b.new_tool == new_tool) { + wipe_length = b.wipe_length; + wipe_depth = b.required_depth; + purge_volume = b.purge_volume; + nozzle_change_depth = b.nozzle_change_depth; + if (m_need_reverse_travel) + nozzle_change_line_count = ((b.nozzle_change_depth + WT_EPSILON) / m_nozzle_change_perimeter_width) / 2; + else + nozzle_change_line_count = (b.nozzle_change_depth + WT_EPSILON) / m_nozzle_change_perimeter_width; + break; + } + } + + WipeTowerBlock* block = get_block_by_category(m_filpar[new_tool].category, false); + if (!block) { + assert(block != nullptr); + return WipeTower::ToolChangeResult(); + } + m_cur_block = block; + box_coordinates cleaning_box(Vec2f(m_perimeter_width, block->cur_depth), m_wipe_tower_width - 2 * m_perimeter_width, wipe_depth-m_layer_info->extra_spacing*nozzle_change_depth); + + WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); + writer.set_extrusion_flow(m_extrusion_flow) + .set_z(m_z_pos) + .set_initial_tool(m_current_tool) + .set_y_shift(m_y_shift + (new_tool != (unsigned int) (-1) && (m_current_shape == SHAPE_REVERSED) ? m_layer_info->depth - m_layer_info->toolchanges_depth() : 0.f)) + .append(";--------------------\n" + "; CP TOOLCHANGE START\n") + .comment_with_value(" toolchange #", m_num_tool_changes + 1); // the number is zero-based + + set_for_wipe_tower_writer(writer); + + if (new_tool != (unsigned) (-1)) + writer.append( std::string("; material : " + (m_current_tool < m_filpar.size() ? m_filpar[m_current_tool].material : "(NONE)") + " -> " + m_filpar[new_tool].material + "\n").c_str()) + .append(";--------------------\n"); + + writer.speed_override_backup(); + writer.speed_override(100); + + // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. + if (new_tool != (unsigned int) -1) { // This is not the last change. + Vec2f initial_position = get_next_pos(cleaning_box, wipe_length); + writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); + + writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_Start) + "\n"); + toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, + is_first_layer() ? m_filpar[new_tool].nozzle_temperature_initial_layer : m_filpar[new_tool].nozzle_temperature); + toolchange_Change(writer, new_tool, m_filpar[new_tool].material); // Change the tool, set a speed override for soluble and flex materials. + toolchange_Load(writer, cleaning_box); +# if 0 + if (m_is_multi_extruder && is_need_reverse_travel(new_tool)) { + float dy = m_layer_info->extra_spacing * m_nozzle_change_perimeter_width; + if (m_layer_info->extra_spacing < m_tpu_fixed_spacing) { + dy = m_tpu_fixed_spacing * m_nozzle_change_perimeter_width; + } + + float nozzle_change_speed = 60.0f * m_filpar[new_tool].max_e_speed / m_extrusion_flow; + nozzle_change_speed *= 0.25; + + const float &xl = cleaning_box.ld.x(); + const float &xr = cleaning_box.rd.x(); + + Vec2f start_pos = m_nozzle_change_result.origin_start_pos + Vec2f(0, m_nozzle_change_perimeter_width); + bool left_to_right = true; + int tpu_line_count = (nozzle_change_line_count + 2 - 1) / 2; // nozzle_change_line_count / 2 round up + + writer.travel(start_pos); + + for (int i = 0; true; ++i) { + if (left_to_right) + writer.travel(xr - m_perimeter_width, writer.y(), nozzle_change_speed); + else + writer.travel(xl + m_perimeter_width, writer.y(), nozzle_change_speed); + + if (i == tpu_line_count - 1) break; + + writer.travel(writer.x(), writer.y() + dy); + left_to_right = !left_to_right; + } + writer.travel(initial_position); + } +#endif + toolchange_wipe_new(writer, cleaning_box, wipe_length, solid_toolchange); + + writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_End) + "\n"); + ++m_num_tool_changes; + } else + toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].nozzle_temperature); + + block->cur_depth += (wipe_depth - nozzle_change_depth * m_layer_info->extra_spacing); + block->last_filament_change_id = new_tool; + + // QDS + writer.speed_override_restore(); + writer.feedrate(m_travel_speed * 60.f) + .flush_planner_queue() + .reset_extruder() + .append("; CP TOOLCHANGE END\n" + ";------------------\n" + "\n\n"); + + // Ask our writer about how much material was consumed: + if (m_current_tool < m_used_filament_length.size()) + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + + return construct_tcr(writer, false, old_tool, false, true, purge_volume); +} + +WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id, int new_filament_id, bool solid_infill) +{ + int nozzle_change_line_count = 0; + if (new_filament_id != (unsigned int) (-1)) { + for (const auto &b : m_layer_info->tool_changes) + if (b.new_tool == new_filament_id) { + if (m_need_reverse_travel) + nozzle_change_line_count = ((b.nozzle_change_depth + WT_EPSILON) / m_nozzle_change_perimeter_width) / 2; + else + nozzle_change_line_count = (b.nozzle_change_depth + WT_EPSILON) / m_nozzle_change_perimeter_width; + break; + } + } + if (nozzle_change_line_count <= 0) return m_nozzle_change_result; + auto format_nozzle_change_line = [](bool start, int old_filament_id, int new_filament_id) -> std::string { + char buff[64]; + std::string tag = start ? GCodeProcessor::reserved_tag(GCodeProcessor::ETags::NozzleChangeStart) : GCodeProcessor::reserved_tag(GCodeProcessor::ETags::NozzleChangeEnd); + snprintf(buff, sizeof(buff), ";%s OF%d NF%d\n", tag.c_str(), old_filament_id, new_filament_id); + return std::string(buff); + }; + + float nz_extrusion_flow = nozzle_change_extrusion_flow(m_layer_height); + float nozzle_change_speed = 60.0f * m_filpar[m_current_tool].max_e_ramming_speed / nz_extrusion_flow; + if (solid_infill) + nozzle_change_speed = std::min( 40.f * 60.f , nozzle_change_speed);//If the contact layers belong to different categories, then reduce the speed. + + float bridge_speed = std::min(60.0f * m_filpar[m_current_tool].max_e_ramming_speed / nozzle_change_extrusion_flow(0.2), nozzle_change_speed); // limit the bridge speed by add flow + + WipeTowerWriter writer(m_layer_height, m_nozzle_change_perimeter_width, m_gcode_flavor, m_filpar); + writer.set_extrusion_flow(nz_extrusion_flow) + .set_z(m_z_pos) + .set_initial_tool(m_current_tool) + .set_y_shift(m_y_shift + (new_filament_id != (unsigned int) (-1) && (m_current_shape == SHAPE_REVERSED) ? m_layer_info->depth - m_layer_info->toolchanges_depth() : 0.f)) + .append(format_nozzle_change_line(true, old_filament_id, new_filament_id)); + set_for_wipe_tower_writer(writer); + + WipeTowerBlock* block = get_block_by_category(m_filpar[old_filament_id].category, false); + if (!block) { + assert(false); + return WipeTower::NozzleChangeResult(); + } + m_cur_block = block; + float dy = m_layer_info->extra_spacing * m_nozzle_change_perimeter_width; + if (m_need_reverse_travel && m_extra_spacing < m_tpu_fixed_spacing) + dy = m_tpu_fixed_spacing * m_nozzle_change_perimeter_width; + + float x_offset = m_perimeter_width + (m_nozzle_change_perimeter_width - m_perimeter_width) / 2; + box_coordinates cleaning_box(Vec2f(x_offset,block->cur_depth + (m_nozzle_change_perimeter_width - m_perimeter_width) / 2), + m_wipe_tower_width - 2 * x_offset, + nozzle_change_line_count * dy - (m_nozzle_change_perimeter_width - m_perimeter_width) / 2);//top can not print + + Vec2f initial_position = cleaning_box.ld; + writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); + + const float &xl = cleaning_box.ld.x(); + const float &xr = cleaning_box.rd.x(); + dy = solid_infill ? m_nozzle_change_perimeter_width : dy; + if (solid_infill) + nozzle_change_line_count = std::floor(EPSILON + (cleaning_box.ru[1] - cleaning_box.rd[1] + (m_nozzle_change_perimeter_width - m_perimeter_width) / 2.f) / + m_nozzle_change_perimeter_width); + m_left_to_right = true; + bool need_change_flow = false; + float ramming_length = nozzle_change_line_count * (xr - xl); + int extruder_id = m_filament_map[m_current_tool]-1; + float per_cooling_max_speed = m_filpar[m_current_tool].precool_t[extruder_id] > EPSILON ? ramming_length / m_filpar[m_current_tool].precool_t[extruder_id] * 60.f : + nozzle_change_speed; + if (is_first_layer()) + per_cooling_max_speed = m_filpar[m_current_tool].precool_t_first_layer[extruder_id] > EPSILON ? ramming_length / m_filpar[m_current_tool].precool_t_first_layer[extruder_id] * 60.f : + nozzle_change_speed; + if (nozzle_change_speed > per_cooling_max_speed) nozzle_change_speed = per_cooling_max_speed; + if (bridge_speed > per_cooling_max_speed) bridge_speed = per_cooling_max_speed; + for (int i = 0; true; ++i) { + if (need_thick_bridge_flow(writer.pos().y())) { + writer.set_extrusion_flow(nozzle_change_extrusion_flow(0.2)); + writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) + std::to_string(0.2) + "\n"); + need_change_flow = true; + } + if (m_left_to_right) + writer.extrude(xr + wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), need_change_flow ? bridge_speed : nozzle_change_speed,LimitFlow::LimitRammingFlow); + else + writer.extrude(xl - wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), need_change_flow ? bridge_speed : nozzle_change_speed,LimitFlow::LimitRammingFlow); + if (i == nozzle_change_line_count - 1) + break; + if ((writer.y() + dy - cleaning_box.ru.y()+(m_nozzle_change_perimeter_width+m_perimeter_width)/2) > (float)EPSILON) break; + if (need_change_flow) { + writer.set_extrusion_flow(nozzle_change_extrusion_flow(m_layer_height)); + writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) + std::to_string(m_layer_height) + "\n"); + need_change_flow = false; + } + writer.extrude(writer.x(), writer.y() + dy, nozzle_change_speed, LimitFlow::LimitRammingFlow); + m_left_to_right = !m_left_to_right; + } + if (need_change_flow) { + writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) + std::to_string(m_layer_height) + "\n"); + } + + writer.set_extrusion_flow(nz_extrusion_flow); // Reset the extrusion flow. + block->cur_depth += nozzle_change_line_count * dy; + block->last_nozzle_change_id = old_filament_id; + + NozzleChangeResult result; + if (is_need_reverse_travel(m_current_tool)) { + bool left_to_right = !m_left_to_right; + int tpu_line_count = nozzle_change_line_count ; + nozzle_change_speed *= 2; // due to nozzle change 2 perimeter + float need_reverse_travel_dis = m_filpar[m_current_tool].ramming_travel_time * nozzle_change_speed/60.f; + float real_travel_dis = tpu_line_count * (xr - xl - 2 * m_perimeter_width); + if (real_travel_dis < need_reverse_travel_dis) + nozzle_change_speed *= real_travel_dis / need_reverse_travel_dis; + writer.travel(writer.x(), writer.y() + m_nozzle_change_perimeter_width); + + for (int i = 0; true; ++i) { + need_reverse_travel_dis -= (xr - xl - 2 * m_perimeter_width); + float offset_dis = 0.f; + if (need_reverse_travel_dis < 0) { + offset_dis = -need_reverse_travel_dis; + } + if (left_to_right) + writer.travel(xr - m_perimeter_width - offset_dis, writer.y(), nozzle_change_speed); + else + writer.travel(xl + m_perimeter_width + offset_dis , writer.y(), nozzle_change_speed); + if (need_reverse_travel_dis < EPSILON) break; + if (i == tpu_line_count - 1) + break; + + writer.travel(writer.x(), writer.y() - dy); + left_to_right = !left_to_right; + } + } else { + result.wipe_path.push_back(writer.pos_rotated()); + if (m_left_to_right) { + result.wipe_path.push_back(Vec2f(0, writer.pos_rotated().y())); + } else { + result.wipe_path.push_back(Vec2f(m_wipe_tower_width, writer.pos_rotated().y())); + } + } + + writer.append(format_nozzle_change_line(false, old_filament_id, new_filament_id)); + + result.start_pos = writer.start_pos_rotated(); + result.origin_start_pos = initial_position; + result.end_pos = writer.pos_rotated(); + result.gcode = writer.gcode(); + return result; +} + +WipeTower::ToolChangeResult WipeTower::finish_layer_new(bool extrude_perimeter, bool extrude_fill, bool extrude_fill_wall) +{ + assert(!this->layer_finished()); + m_current_layer_finished = true; + + WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); + writer.set_extrusion_flow(m_extrusion_flow) + .set_z(m_z_pos) + .set_initial_tool(m_current_tool) + .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)); + + set_for_wipe_tower_writer(writer); + + writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_Start) + "\n"); + + // Slow down on the 1st layer. + bool first_layer = is_first_layer(); + // QDS: speed up perimeter speed to 90mm/s for non-first layer + float feedrate = first_layer ? std::min(m_first_layer_speed * 60.f, m_max_speed) : std::min(60.0f * m_filpar[m_current_tool].max_e_speed / m_extrusion_flow, m_max_speed); + + float fill_box_depth = m_wipe_tower_depth - 2 * m_perimeter_width; + if (m_wipe_tower_blocks.size() == 1) { + fill_box_depth = m_layer_info->depth - 2 * m_perimeter_width; + } + box_coordinates fill_box(Vec2f(m_perimeter_width, m_perimeter_width), m_wipe_tower_width - 2 * m_perimeter_width, fill_box_depth); + + writer.set_initial_position((m_left_to_right ? fill_box.ru : fill_box.lu), m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); + + bool toolchanges_on_layer = m_layer_info->toolchanges_depth() > WT_EPSILON; + + std::vector finish_rect_wipe_path; + if (extrude_fill_wall) { + // inner perimeter of the sparse section, if there is space for it: + if (fill_box.ru.y() - fill_box.rd.y() > WT_EPSILON) { + writer.rectangle_fill_box(this, fill_box, finish_rect_wipe_path, feedrate); + } + } + + // Extrude infill to support the material to be printed above. + const float dy = (fill_box.lu.y() - fill_box.ld.y() - m_perimeter_width); + float left = fill_box.lu.x() + 2 * m_perimeter_width; + float right = fill_box.ru.x() - 2 * m_perimeter_width; + if (extrude_fill && dy > m_perimeter_width) { + writer.travel(fill_box.ld + Vec2f(m_perimeter_width * 2, 0.f)) + .append(";--------------------\n" + "; CP EMPTY GRID START\n") + .comment_with_value(" layer #", m_num_layer_changes + 1); + + // Is there a soluble filament wiped/rammed at the next layer? + // If so, the infill should not be sparse. + bool solid_infill = m_layer_info + 1 == m_plan.end() ? + false : + std::any_of((m_layer_info + 1)->tool_changes.begin(), (m_layer_info + 1)->tool_changes.end(), + [this](const WipeTowerInfo::ToolChange &tch) { return m_filpar[tch.new_tool].is_soluble || m_filpar[tch.old_tool].is_soluble; }); + solid_infill |= first_layer && m_adhesion; + + if (solid_infill) { + float sparse_factor = 1.5f; // 1=solid, 2=every other line, etc. + if (first_layer) { // the infill should touch perimeters + left -= m_perimeter_width; + right += m_perimeter_width; + sparse_factor = 1.f; + } + float y = fill_box.ld.y() + m_perimeter_width; + int n = dy / (m_perimeter_width * sparse_factor); + float spacing = (dy - m_perimeter_width) / (n - 1); + int i = 0; + for (i = 0; i < n; ++i) { + writer.extrude(writer.x(), y, feedrate).extrude(i % 2 ? left : right, y); + y = y + spacing; + } + writer.extrude(writer.x(), fill_box.lu.y()); + } else { + // Extrude an inverse U at the left of the region and the sparse infill. + writer.extrude(fill_box.lu + Vec2f(m_perimeter_width * 2, 0.f), feedrate); + + const int n = 1 + int((right - left) / m_bridging); + const float dx = (right - left) / n; + for (int i = 1; i <= n; ++i) { + float x = left + dx * i; + writer.travel(x, writer.y()); + writer.extrude(x, i % 2 ? fill_box.rd.y() : fill_box.ru.y()); + } + + finish_rect_wipe_path.clear(); + // QDS: add wipe_path for this case: only with finish rectangle + finish_rect_wipe_path.emplace_back(writer.pos()); + finish_rect_wipe_path.emplace_back(Vec2f(left + dx * n, n % 2 ? fill_box.ru.y() : fill_box.rd.y())); + } + + writer.append("; CP EMPTY GRID END\n" + ";------------------\n\n\n\n\n\n\n"); + } + + // outer perimeter (always): + // QDS + float wipe_tower_depth = m_wipe_tower_depth; + if (m_wipe_tower_blocks.size() == 1) { + wipe_tower_depth = m_layer_info->depth + m_perimeter_width; + } + box_coordinates wt_box(Vec2f(0.f, 0.f), m_wipe_tower_width, wipe_tower_depth); + wt_box = align_perimeter(wt_box); + + //if (extrude_perimeter && !m_use_rib_wall) { + // if (!m_use_gap_wall) + // writer.rectangle(wt_box, feedrate); + // else + // generate_support_wall(writer, wt_box, feedrate, first_layer); + //} + Polygon outer_wall; + outer_wall = generate_support_wall_new(writer, wt_box, feedrate, first_layer, m_use_rib_wall, extrude_perimeter, m_use_gap_wall); + if (extrude_perimeter) { + Polyline shift_polyline = to_polyline(outer_wall); + shift_polyline.translate(0, scaled(m_y_shift)); + m_outer_wall[m_z_pos].push_back(shift_polyline); + } + // brim chamfer + float spacing = m_perimeter_width - m_layer_height * float(1. - M_PI_4); + // How many perimeters shall the brim have? + int loops_num = (m_wipe_tower_brim_width + spacing / 2.f) / spacing; + const float max_chamfer_width = 3.f; + if (!first_layer) { + // stop print chamfer if depth changes + if (m_layer_info->depth != m_plan.front().depth) { + loops_num = 0; + } else { + // limit max chamfer width to 3 mm + int chamfer_loops_num = (int) (max_chamfer_width / spacing); + int dist_to_1st = m_layer_info - m_plan.begin() - m_first_layer_idx; + loops_num = std::min(loops_num, chamfer_loops_num) - dist_to_1st; + } + } + + if (loops_num > 0) { + //box_coordinates box = wt_box; + for (size_t i = 0; i < loops_num; ++i) { + outer_wall = offset(outer_wall, scaled(spacing)).front(); + writer.polygon(outer_wall, feedrate); + m_outer_wall[m_z_pos].push_back(to_polyline(outer_wall)); + } + + /*for (size_t i = 0; i < loops_num; ++i) { + box.expand(spacing); + writer.rectangle(box, feedrate); + }*/ + + if (first_layer) { + // Save actual brim width to be later passed to the Print object, which will use it + // for skirt calculation and pass it to GLCanvas for precise preview box + m_wipe_tower_brim_width_real = loops_num * spacing + spacing / 2.f; + //m_wipe_tower_brim_width_real = wt_box.ld.x() - box.ld.x() + spacing / 2.f; + } + //wt_box = box; + } + + if (extrude_perimeter || loops_num > 0) { + writer.add_wipe_path(outer_wall, m_filpar[m_current_tool].wipe_dist); + } + else { + // Now prepare future wipe. box contains rectangle that was extruded last (ccw). + Vec2f target = (writer.pos() == wt_box.ld ? wt_box.rd : (writer.pos() == wt_box.rd ? wt_box.ru : (writer.pos() == wt_box.ru ? wt_box.lu : wt_box.ld))); + + // QDS: add wipe_path for this case: only with finish rectangle + if (finish_rect_wipe_path.size() == 2 && finish_rect_wipe_path[0] == writer.pos()) target = finish_rect_wipe_path[1]; + + writer.add_wipe_point(writer.pos()).add_wipe_point(target); + } + writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_End) + "\n"); + + // Ask our writer about how much material was consumed. + // Skip this in case the layer is sparse and config option to not print sparse layers is enabled. + if (!m_no_sparse_layers || toolchanges_on_layer) + if (m_current_tool < m_used_filament_length.size()) + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + + m_nozzle_change_result.gcode.clear(); + return construct_tcr(writer, false, m_current_tool, true, false, 0.f); +} + +WipeTower::ToolChangeResult WipeTower::finish_block(const WipeTowerBlock &block, int filament_id, bool extrude_fill) +{ + WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); + writer.set_extrusion_flow(m_extrusion_flow) + .set_z(m_z_pos) + .set_initial_tool(filament_id) + .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)); + + set_for_wipe_tower_writer(writer); + + writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_Start) + "\n"); + + // Slow down on the 1st layer. + bool first_layer = is_first_layer(); + // QDS: speed up perimeter speed to 90mm/s for non-first layer + float feedrate = first_layer ? std::min(m_first_layer_speed * 60.f, m_max_speed) : std::min(60.0f * m_filpar[filament_id].max_e_speed / m_extrusion_flow, m_max_speed); + + box_coordinates fill_box(Vec2f(0, 0), 0, 0); + fill_box = box_coordinates(Vec2f(m_perimeter_width, block.cur_depth), m_wipe_tower_width - 2 * m_perimeter_width, block.start_depth + block.layer_depths[m_cur_layer_id] - block.cur_depth - m_perimeter_width); + + writer.set_initial_position((m_left_to_right ? fill_box.ru : fill_box.lu), m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); + + bool toolchanges_on_layer = m_layer_info->toolchanges_depth() > WT_EPSILON; + + std::vector finish_rect_wipe_path; + // inner perimeter of the sparse section, if there is space for it: + if (fill_box.ru.y() - fill_box.rd.y() > WT_EPSILON) { + writer.rectangle_fill_box(this, fill_box, finish_rect_wipe_path, feedrate); + } + + // Extrude infill to support the material to be printed above. + const float dy = (fill_box.lu.y() - fill_box.ld.y() - m_perimeter_width); + float left = fill_box.lu.x() + 2 * m_perimeter_width; + float right = fill_box.ru.x() - 2 * m_perimeter_width; + if (extrude_fill && dy > m_perimeter_width) { + writer.travel(fill_box.ld + Vec2f(m_perimeter_width * 2, 0.f)) + .append(";--------------------\n" + "; CP EMPTY GRID START\n") + .comment_with_value(" layer #", m_num_layer_changes + 1); + + // Is there a soluble filament wiped/rammed at the next layer? + // If so, the infill should not be sparse. + bool solid_infill = m_layer_info + 1 == m_plan.end() ? + false : + std::any_of((m_layer_info + 1)->tool_changes.begin(), (m_layer_info + 1)->tool_changes.end(), + [this](const WipeTowerInfo::ToolChange &tch) { return m_filpar[tch.new_tool].is_soluble || m_filpar[tch.old_tool].is_soluble; }); + solid_infill |= first_layer && m_adhesion; + + if (solid_infill) { + float sparse_factor = 1.5f; // 1=solid, 2=every other line, etc. + if (first_layer) { // the infill should touch perimeters + left -= m_perimeter_width; + right += m_perimeter_width; + sparse_factor = 1.f; + } + float y = fill_box.ld.y() + m_perimeter_width; + int n = dy / (m_perimeter_width * sparse_factor); + float spacing = (dy - m_perimeter_width) / (n - 1); + int i = 0; + for (i = 0; i < n; ++i) { + writer.extrude(writer.x(), y, feedrate).extrude(i % 2 ? left : right, y); + y = y + spacing; + } + writer.extrude(writer.x(), fill_box.lu.y()); + } else { + // Extrude an inverse U at the left of the region and the sparse infill. + writer.extrude(fill_box.lu + Vec2f(m_perimeter_width * 2, 0.f), feedrate); + + const int n = 1 + int((right - left) / m_bridging); + const float dx = (right - left) / n; + for (int i = 1; i <= n; ++i) { + float x = left + dx * i; + writer.travel(x, writer.y()); + writer.extrude(x, i % 2 ? fill_box.rd.y() : fill_box.ru.y()); + } + finish_rect_wipe_path.clear(); + // QDS: add wipe_path for this case: only with finish rectangle + finish_rect_wipe_path.emplace_back(writer.pos()); + finish_rect_wipe_path.emplace_back(Vec2f(left + dx * n, n % 2 ? fill_box.ru.y() : fill_box.rd.y())); + } + + writer.append("; CP EMPTY GRID END\n" + ";------------------\n\n\n\n\n\n\n"); + } + + // outer perimeter (always): + // QDS + box_coordinates wt_box(Vec2f(0.f, 0.f), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); + wt_box = align_perimeter(wt_box); + + // Now prepare future wipe. box contains rectangle that was extruded last (ccw). + Vec2f target = (writer.pos() == wt_box.ld ? wt_box.rd : (writer.pos() == wt_box.rd ? wt_box.ru : (writer.pos() == wt_box.ru ? wt_box.lu : wt_box.ld))); + + // QDS: add wipe_path for this case: only with finish rectangle + if (finish_rect_wipe_path.size() == 2 && finish_rect_wipe_path[0] == writer.pos()) target = finish_rect_wipe_path[1]; + + writer.add_wipe_point(writer.pos()).add_wipe_point(target); + + writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_End) + "\n"); + + // Ask our writer about how much material was consumed. + // Skip this in case the layer is sparse and config option to not print sparse layers is enabled. + if (!m_no_sparse_layers || toolchanges_on_layer) + if (filament_id < m_used_filament_length.size()) + m_used_filament_length[filament_id] += writer.get_and_reset_used_filament_length(); + + return construct_block_tcr(writer, false, filament_id, true, 0.f); +} + +WipeTower::ToolChangeResult WipeTower::finish_block_solid(const WipeTowerBlock &block, int filament_id, bool extrude_fill, bool interface_solid) +{ + float layer_height = m_layer_height; + float e_flow = m_extrusion_flow; + if (m_cur_layer_id > 1 && !block.solid_infill[m_cur_layer_id - 1] && m_extrusion_flow < extrusion_flow(0.2)) { + layer_height = 0.2; + e_flow = extrusion_flow(0.2); + } + + WipeTowerWriter writer(layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); + writer.set_extrusion_flow(e_flow) + .set_z(m_z_pos) + .set_initial_tool(filament_id) + .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)); + + set_for_wipe_tower_writer(writer); + + writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_Start) + "\n"); + + // Slow down on the 1st layer. + bool first_layer = is_first_layer(); + // QDS: speed up perimeter speed to 90mm/s for non-first layer + float feedrate = first_layer ? std::min(m_first_layer_speed * 60.f, m_max_speed) : std::min(60.0f * m_filpar[filament_id].max_e_speed / m_extrusion_flow, m_max_speed); + feedrate = interface_solid ? 20.f * 60.f : feedrate; + box_coordinates fill_box(Vec2f(0, 0), 0, 0); + fill_box = box_coordinates(Vec2f(m_perimeter_width, block.cur_depth), m_wipe_tower_width - 2 * m_perimeter_width, + block.start_depth + block.layer_depths[m_cur_layer_id] - block.cur_depth - m_perimeter_width); + + writer.set_initial_position((m_left_to_right ? fill_box.rd : fill_box.ld), m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); + m_left_to_right = !m_left_to_right; + bool toolchanges_on_layer = m_layer_info->toolchanges_depth() > WT_EPSILON; + + // Extrude infill to support the material to be printed above. + const float dy = (fill_box.lu.y() - fill_box.ld.y()); + float left = fill_box.lu.x(); + float right = fill_box.ru.x(); + std::vector finish_rect_wipe_path; + { + writer.append(";--------------------\n" + "; CP EMPTY GRID START\n") + .comment_with_value(" layer #", m_num_layer_changes + 1); + + float y = fill_box.ld.y(); + int n = (dy + 0.25 * m_perimeter_width) / m_perimeter_width + 1; + float spacing = m_perimeter_width; + int i = 0; + for (i = 0; i < n; ++i) { + writer.extrude(m_left_to_right ? right : left, writer.y(), feedrate); + if (i == n - 1) { + writer.add_wipe_point(writer.pos()).add_wipe_point(Vec2f(m_left_to_right ? left : right, writer.y())); + break; + } + m_left_to_right = !m_left_to_right; + y = y + spacing; + writer.extrude(writer.x(), y, feedrate); + } + + writer.append("; CP EMPTY GRID END\n" + ";------------------\n\n\n\n\n\n\n"); + } + + writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_End) + "\n"); + + // Ask our writer about how much material was consumed. + // Skip this in case the layer is sparse and config option to not print sparse layers is enabled. + if (!m_no_sparse_layers || toolchanges_on_layer) + if (filament_id < m_used_filament_length.size()) + m_used_filament_length[filament_id] += writer.get_and_reset_used_filament_length(); + + return construct_block_tcr(writer, false, filament_id, true, 0.f); +} + +void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinates &cleaning_box, float wipe_length,bool solid_tool_toolchange) +{ + writer.set_extrusion_flow(m_extrusion_flow * (is_first_layer() ? 1.15f : 1.f)).append("; CP TOOLCHANGE WIPE\n"); + + if (!m_nozzle_change_result.gcode.empty()) + writer.change_analyzer_line_width(m_perimeter_width); + + // QDS: add the note for gcode-check, when the flow changed, the width should follow the change + if (is_first_layer()) { + writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Width) + std::to_string(1.15 * m_perimeter_width) + "\n"); + } + float retract_length = m_filpar[m_current_tool].retract_length; + float retract_speed = m_filpar[m_current_tool].retract_speed * 60; + + const float &xl = cleaning_box.ld.x(); + const float &xr = cleaning_box.rd.x(); + + float x_to_wipe = wipe_length; + float dy = solid_tool_toolchange ? m_perimeter_width :m_layer_info->extra_spacing * m_perimeter_width; + x_to_wipe = solid_tool_toolchange ? std::numeric_limits::max(): x_to_wipe; + float target_speed = is_first_layer() ? std::min(m_first_layer_speed * 60.f, m_max_speed) : m_max_speed; + target_speed = solid_tool_toolchange ? 20.f * 60.f : target_speed; + float wipe_speed = 0.33f * target_speed; + + m_left_to_right = ((m_cur_layer_id + 3) % 4 >= 2); + + bool is_from_up = (m_cur_layer_id % 2 == 1); + + // now the wiping itself: + for (int i = 0; true; ++i) { + if (i != 0) { + if (wipe_speed < 0.34f * target_speed) + wipe_speed = 0.375f * target_speed; + else if (wipe_speed < 0.377 * target_speed) + wipe_speed = 0.458f * target_speed; + else if (wipe_speed < 0.46f * target_speed) + wipe_speed = 0.875f * target_speed; + else + wipe_speed = std::min(target_speed, wipe_speed + 50.f); + } + + bool need_change_flow = need_thick_bridge_flow(writer.y()); + // QDS: check the bridging area and use the bridge flow + if (need_change_flow) { + writer.set_extrusion_flow(extrusion_flow(0.2)); + writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) + std::to_string(0.2) + "\n"); + } + + float ironing_length = 3.; + if (i == 0 && m_use_gap_wall) { // QDS: add ironing after extruding start + if (m_left_to_right) { + float dx = xr + wipe_tower_wall_infill_overlap * m_perimeter_width - writer.pos().x(); + if (abs(dx) < ironing_length) ironing_length = abs(dx); + writer.extrude(writer.x() + ironing_length, writer.y(), wipe_speed); + writer.retract(retract_length, retract_speed); + writer.travel(writer.x() - 1.5 * ironing_length, writer.y(), 600.); + if (flat_ironing) { + writer.travel(writer.x() + 0.5f * ironing_length, writer.y(), 240.); + Vec2f pos{writer.x() + 1.f * ironing_length, writer.y()}; + writer.spiral_flat_ironing(writer.pos(), flat_iron_area, m_perimeter_width, flat_iron_speed); + writer.travel(pos, wipe_speed); + } else + writer.travel(writer.x() + 1.5 * ironing_length, writer.y(), 240.); + writer.retract(-retract_length, retract_speed); + writer.extrude(xr + wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), wipe_speed); + } else { + float dx = xl - wipe_tower_wall_infill_overlap * m_perimeter_width - writer.pos().x(); + if (abs(dx) < ironing_length) ironing_length = abs(dx); + writer.extrude(writer.x() - ironing_length, writer.y(), wipe_speed); + writer.retract(retract_length, retract_speed); + writer.travel(writer.x() + 1.5 * ironing_length, writer.y(), 600.); + if (flat_ironing) { + writer.travel(writer.x() - 0.5f * ironing_length, writer.y(), 240.); + Vec2f pos{writer.x() - 1.0f * ironing_length, writer.y()}; + writer.spiral_flat_ironing(writer.pos(), flat_iron_area, m_perimeter_width, flat_iron_speed); + writer.travel(pos, wipe_speed); + }else + writer.travel(writer.x() - 1.5 * ironing_length, writer.y(), 240.); + writer.retract(-retract_length, retract_speed); + writer.extrude(xl - wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), wipe_speed); + } + } else { + if (m_left_to_right) + writer.extrude(xr + wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), wipe_speed); + else + writer.extrude(xl - wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), wipe_speed); + } + + // QDS: recover the flow in non-bridging area + if (need_change_flow) { + writer.set_extrusion_flow(m_extrusion_flow); + writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) + std::to_string(m_layer_height) + "\n"); + } + + if (!is_from_up && (writer.y() + dy - float(EPSILON) >cleaning_box.lu.y() - m_perimeter_width)) + break; // in case next line would not fit + + if (is_from_up && (writer.y() - dy+ float(EPSILON))tool_changes.back().new_tool) m_left_to_right = !m_left_to_right; + + writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow. + // QDS: add the note for gcode-check when the flow changed + if (is_first_layer()) { writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Width) + std::to_string(m_perimeter_width) + "\n"); } +} + +WipeTower::WipeTowerBlock * WipeTower::get_block_by_category(int filament_adhesiveness_category, bool create) +{ + auto iter = std::find_if(m_wipe_tower_blocks.begin(), m_wipe_tower_blocks.end(), [&filament_adhesiveness_category](const WipeTower::WipeTowerBlock &item) { + return item.filament_adhesiveness_category == filament_adhesiveness_category; + }); + + if (iter != m_wipe_tower_blocks.end()) { + return &(*iter); + } + + if (create) { + WipeTower::WipeTowerBlock new_block; + new_block.block_id = m_wipe_tower_blocks.size(); + new_block.filament_adhesiveness_category = filament_adhesiveness_category; + m_wipe_tower_blocks.emplace_back(new_block); + return &m_wipe_tower_blocks.back(); + } + + return nullptr; +} + +void WipeTower::add_depth_to_block(int filament_id, int filament_adhesiveness_category, float depth, bool is_nozzle_change) +{ + std::vector &layer_depth = m_all_layers_depth[m_cur_layer_id]; + auto iter = std::find_if(layer_depth.begin(), layer_depth.end(), [&filament_adhesiveness_category](const WipeTower::BlockDepthInfo &item) { + return item.category == filament_adhesiveness_category; + }); + + if (iter != layer_depth.end()) { + iter->depth += depth; + if (is_nozzle_change) + iter->nozzle_change_depth += depth; + } + else { + WipeTower::BlockDepthInfo new_block; + new_block.category = filament_adhesiveness_category; + new_block.depth = depth; + if (is_nozzle_change) + new_block.nozzle_change_depth += depth; + layer_depth.emplace_back(std::move(new_block)); + } +} + +int WipeTower::get_filament_category(int filament_id) +{ + if (filament_id >= m_filament_categories.size()) + return 0; + return m_filament_categories[filament_id]; +} + +bool WipeTower::is_in_same_extruder(int filament_id_1, int filament_id_2) +{ + if (filament_id_1 >= m_filament_map.size() || filament_id_2 >= m_filament_map.size()) + return true; + + return m_filament_map[filament_id_1] == m_filament_map[filament_id_2]; +} + +void WipeTower::reset_block_status() +{ + for (auto &block : m_wipe_tower_blocks) { + block.cur_depth = block.start_depth; + block.last_filament_change_id = -1; + block.last_nozzle_change_id = -1; + } +} +void WipeTower::set_nozzle_last_layer_id() +{ + for (int idx = 0; idx < m_plan.size(); idx++) { + auto &info = m_plan[idx]; + for(int i =0 ; i= 0) m_last_layer_id[m_filament_map[old_tool] - 1] = idx; + m_last_layer_id[m_filament_map[new_tool] - 1] = idx; + } + } +} + +void WipeTower::update_all_layer_depth(float wipe_tower_depth) +{ + m_wipe_tower_depth = 0.f; + float start_offset = m_perimeter_width; + float start_depth = start_offset; + for (auto& block : m_wipe_tower_blocks) { + block.depth *= m_extra_spacing; + block.start_depth = start_depth; + start_depth += block.depth; + m_wipe_tower_depth += block.depth; + + for (auto& layer_depth : block.layer_depths) { + layer_depth *= m_extra_spacing; + } + + for (WipeTowerInfo& plan_info : m_plan) { + plan_info.depth *= m_extra_spacing; + } + } + if (m_wipe_tower_depth > 0) + m_wipe_tower_depth += start_offset; + + if (m_enable_timelapse_print) { + if (is_approx(m_wipe_tower_depth, 0.f)) + m_wipe_tower_depth = wipe_tower_depth; + for (WipeTowerInfo &plan_info : m_plan) { + plan_info.depth = m_wipe_tower_depth; + } + } +} + +void WipeTower::generate_wipe_tower_blocks() +{ + // 1. generate all layer depth + m_all_layers_depth.clear(); + m_all_layers_depth.resize(m_plan.size()); + m_cur_layer_id = 0; + for (auto& info : m_plan) { + for (const WipeTowerInfo::ToolChange &tool_change : info.tool_changes) { + if (is_in_same_extruder(tool_change.old_tool, tool_change.new_tool)) { + int filament_adhesiveness_category = get_filament_category(tool_change.new_tool); + add_depth_to_block(tool_change.new_tool, filament_adhesiveness_category, tool_change.required_depth); + } + else { + int old_filament_category = get_filament_category(tool_change.old_tool); + add_depth_to_block(tool_change.old_tool, old_filament_category, tool_change.nozzle_change_depth, true); + int new_filament_category = get_filament_category(tool_change.new_tool); + add_depth_to_block(tool_change.new_tool, new_filament_category, tool_change.required_depth - tool_change.nozzle_change_depth); + + } + } + ++m_cur_layer_id; + } + + // 2. generate all layer depth + std::vector> all_layer_category_to_depth(m_plan.size()); + for (size_t layer_id = 0; layer_id < m_all_layers_depth.size(); ++layer_id) { + const auto& layer_blocks = m_all_layers_depth[layer_id]; + std::unordered_map &category_to_depth = all_layer_category_to_depth[layer_id]; + for (auto block : layer_blocks) { + category_to_depth[block.category] = block.depth; + } + } + + // 3. generate wipe tower block + m_wipe_tower_blocks.clear(); + for (int layer_id = 0; layer_id < all_layer_category_to_depth.size(); ++layer_id) { + const auto &layer_category_depths = all_layer_category_to_depth[layer_id]; + for (auto iter = layer_category_depths.begin(); iter != layer_category_depths.end(); ++iter) { + auto* block = get_block_by_category(iter->first, true); + if (block->layer_depths.empty()) { + block->layer_depths.resize(all_layer_category_to_depth.size(), 0); + block->solid_infill.resize(all_layer_category_to_depth.size(), false); + block->finish_depth.resize(all_layer_category_to_depth.size(), 0); + } + block->depth = std::max(block->depth, iter->second); + block->layer_depths[layer_id] = iter->second; + } + } + + // add solid infill flag + int solid_infill_layer = 4; + for (WipeTowerBlock& block : m_wipe_tower_blocks) { + for (int layer_id = 0; layer_id < all_layer_category_to_depth.size(); ++layer_id) { + std::unordered_map &category_to_depth = all_layer_category_to_depth[layer_id]; + if (is_approx(category_to_depth[block.filament_adhesiveness_category], 0.f)) { + int layer_count = solid_infill_layer; + while (layer_count > 0) { + if (layer_id + layer_count < all_layer_category_to_depth.size()) { + std::unordered_map& up_layer_depth = all_layer_category_to_depth[layer_id + layer_count]; + if (!is_approx(up_layer_depth[block.filament_adhesiveness_category], 0.f)) { + block.solid_infill[layer_id] = true; + break; + } + } + --layer_count; + } + } + } + } + + // 4. get real depth for every layer + for (int layer_id = m_plan.size() - 1; layer_id >= 0; --layer_id) { + m_plan[layer_id].depth = 0; + for (auto& block : m_wipe_tower_blocks) { + if (layer_id < m_plan.size() - 1) + block.layer_depths[layer_id] = std::max(block.layer_depths[layer_id], block.layer_depths[layer_id + 1]); + m_plan[layer_id].depth += block.layer_depths[layer_id]; + } + } + + if (m_tower_framework) { + for (int layer_id = 1; layer_id < m_plan.size(); ++layer_id) { + m_plan[layer_id].depth = 0; + for (auto &block : m_wipe_tower_blocks) { + block.layer_depths[layer_id] = block.layer_depths[0]; + m_plan[layer_id].depth += block.layer_depths[layer_id]; + } + } + } +} + +void WipeTower::plan_tower_new() +{ + if (m_wipe_tower_brim_width < 0) m_wipe_tower_brim_width = get_auto_brim_by_height(m_wipe_tower_height); + if (m_use_rib_wall) { + // recalculate wipe_tower_with and layer's depth + generate_wipe_tower_blocks(); + float max_depth = std::accumulate(m_wipe_tower_blocks.begin(), m_wipe_tower_blocks.end(), 0.f, [](float a, const auto &t) { return a + t.depth; }) + m_perimeter_width; + float square_width = align_ceil(std::sqrt(max_depth * m_wipe_tower_width * m_extra_spacing), m_perimeter_width); + //std::cout << " before m_wipe_tower_width = " << m_wipe_tower_width << " max_depth = " << max_depth << std::endl; + m_wipe_tower_width = square_width; + float width = m_wipe_tower_width - 2 * m_perimeter_width; + for (int idx = 0; idx < m_plan.size(); idx++) { + for (auto &toolchange : m_plan[idx].tool_changes) { + float length_to_extrude = toolchange.wipe_length; + float depth = std::ceil(length_to_extrude / width) * m_perimeter_width; + float nozzle_change_depth = 0; + if (!m_filament_map.empty() && m_filament_map[toolchange.old_tool] != m_filament_map[toolchange.new_tool]) { + double e_flow = nozzle_change_extrusion_flow(m_plan[idx].height); + double length = m_filaments_change_length[toolchange.old_tool] / e_flow; + int nozzle_change_line_count = std::ceil(length / (m_wipe_tower_width - 2*m_nozzle_change_perimeter_width)); + if (m_need_reverse_travel) + nozzle_change_depth = m_tpu_fixed_spacing * nozzle_change_line_count * m_nozzle_change_perimeter_width; + else + nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width; + depth += nozzle_change_depth; + } + toolchange.nozzle_change_depth = nozzle_change_depth; + toolchange.required_depth = depth; + } + } + } + + generate_wipe_tower_blocks(); + + float max_depth = 0.f; + for (const auto &block : m_wipe_tower_blocks) { + max_depth += block.depth; + } + //std::cout << " after square " << m_wipe_tower_width << " depth " << max_depth << std::endl; + + float min_wipe_tower_depth = get_limit_depth_by_height(m_wipe_tower_height); + + // only for get m_extra_spacing + { + if (m_enable_timelapse_print && max_depth < EPSILON) { + max_depth = min_wipe_tower_depth; + if (m_use_rib_wall) { m_wipe_tower_width = max_depth; } + } + + if (max_depth + EPSILON < min_wipe_tower_depth) { + //if enable rib_wall, there is no need to set extra_spacing + if (m_use_rib_wall) + m_rib_length = std::max(m_rib_length, min_wipe_tower_depth * (float) std::sqrt(2)); + else + m_extra_spacing = std::max(min_wipe_tower_depth / max_depth, m_extra_spacing); + } + + for (int idx = 0; idx < m_plan.size(); idx++) { + auto &info = m_plan[idx]; + if (idx == 0 && m_extra_spacing > 1.f + EPSILON) { + // apply solid fill for the first layer + info.extra_spacing = 1.f; + for (auto &toolchange : info.tool_changes) { + float x_to_wipe = volume_to_length(toolchange.wipe_volume, m_perimeter_width, info.height); + float line_len = m_wipe_tower_width - 2 * m_perimeter_width; + float x_to_wipe_new = x_to_wipe * m_extra_spacing; + x_to_wipe_new = std::floor(x_to_wipe_new / line_len) * line_len; + x_to_wipe_new = std::max(x_to_wipe_new, x_to_wipe); + + int line_count = std::ceil((x_to_wipe_new - WT_EPSILON) / line_len); + // nozzle change length + int nozzle_change_line_count = (toolchange.nozzle_change_depth + WT_EPSILON) / m_nozzle_change_perimeter_width; + + toolchange.required_depth = line_count * m_perimeter_width + nozzle_change_line_count * m_nozzle_change_perimeter_width; + toolchange.wipe_volume = x_to_wipe_new / x_to_wipe * toolchange.wipe_volume; + toolchange.wipe_length = x_to_wipe_new; + } + } else { + info.extra_spacing = m_extra_spacing; + for (auto &toolchange : info.tool_changes) { + toolchange.required_depth *= m_extra_spacing; + toolchange.wipe_length = volume_to_length(toolchange.wipe_volume, m_perimeter_width, info.height); + } + } + } + } + + update_all_layer_depth(max_depth); + set_nozzle_last_layer_id(); + m_rib_length = std::max({m_rib_length, sqrt(m_wipe_tower_depth * m_wipe_tower_depth + m_wipe_tower_width * m_wipe_tower_width)}); + m_rib_length += m_extra_rib_length; + m_rib_length = std::max(0.f, m_rib_length); + m_rib_width = std::min(m_rib_width, std::min(m_wipe_tower_depth, m_wipe_tower_width) / 2.f); // Ensure that the rib wall of the wipetower are attached to the infill. + +} + +int WipeTower::get_wall_filament_for_all_layer() +{ + std::map category_counts; + std::map filament_counts; + int current_tool = m_current_tool; + for (const auto &layer : m_plan) { + if (layer.tool_changes.empty()){ + filament_counts[current_tool]++; + category_counts[get_filament_category(current_tool)]++; + continue; + } + std::unordered_set used_tools; + std::unordered_set used_category; + for (size_t i = 0; i < layer.tool_changes.size(); ++i) { + if (i == 0) { + filament_counts[layer.tool_changes[i].old_tool]++; + category_counts[get_filament_category(layer.tool_changes[i].old_tool)]++; + used_tools.insert(layer.tool_changes[i].old_tool); + used_category.insert(get_filament_category(layer.tool_changes[i].old_tool)); + } + if (!used_category.count(get_filament_category(layer.tool_changes[i].new_tool))) + category_counts[get_filament_category(layer.tool_changes[i].new_tool)]++; + if (!used_tools.count(layer.tool_changes[i].new_tool)) + filament_counts[layer.tool_changes[i].new_tool]++; + used_tools.insert(layer.tool_changes[i].new_tool); + used_category.insert(get_filament_category(layer.tool_changes[i].new_tool)); + } + current_tool = layer.tool_changes.empty()?current_tool:layer.tool_changes.back().new_tool; + } + + // std::vector> category_counts_vec; + int selected_category = -1; + int selected_count = 0; + + for (auto iter = category_counts.begin(); iter != category_counts.end(); ++iter) { + if (iter->second > selected_count) { + selected_category = iter->first; + selected_count = iter->second; + } + } + + // std::sort(category_counts_vec.begin(), category_counts_vec.end(), [](const std::pair &left, const std::pair& right) { + // return left.second > right.second; + // }); + + int filament_id = -1; + int filament_count = 0; + for (auto iter = filament_counts.begin(); iter != filament_counts.end(); ++iter) { + if (m_filament_categories[iter->first] == selected_category && iter->second > filament_count) { + filament_id = iter->first; + filament_count = iter->second; + } + } + return filament_id; +} + +void WipeTower::generate_new(std::vector> &result) +{ + if (m_plan.empty()) + return; + //m_extra_spacing = 1.f; + m_wipe_tower_height = m_plan.back().z;//real wipe_tower_height + plan_tower_new(); + + m_layer_info = m_plan.begin(); + + for (const auto &layer : m_plan) { + if (!layer.tool_changes.empty()) { + m_current_tool = layer.tool_changes.front().old_tool; + break; + } + } + + for (auto &used : m_used_filament_length) // reset used filament stats + used = 0.f; + + int wall_filament = get_wall_filament_for_all_layer(); + + std::vector layer_result; + int index = 0; + std::unordered_set solid_blocks_id;// The contact surface of different bonded materials is solid. + for (auto layer : m_plan) { + reset_block_status(); + m_cur_layer_id = index++; + set_layer(layer.z, layer.height, 0, false, layer.z == m_plan.back().z); + if (m_layer_info->depth < m_perimeter_width) continue; + if (m_wipe_tower_blocks.size() == 1) { + if (m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width) { + // align y shift to perimeter width + float dy = m_extra_spacing * m_perimeter_width; + m_y_shift = (m_wipe_tower_depth - m_layer_info->depth) / 2.f; + m_y_shift = align_round(m_y_shift, dy); + } + } + + get_wall_skip_points(layer); + + ToolChangeResult finish_layer_tcr; + ToolChangeResult timelapse_wall; + + auto get_wall_filament_for_this_layer = [this, &layer, &wall_filament]() -> int { + if (layer.tool_changes.size() == 0) + return -1; + + int candidate_id = -1; + for (size_t idx = 0; idx < layer.tool_changes.size(); ++idx) { + if (idx == 0) { + if (layer.tool_changes[idx].old_tool == wall_filament && is_valid_last_layer(layer.tool_changes[idx].old_tool)) + return wall_filament; + else if (m_filpar[layer.tool_changes[idx].old_tool].category == m_filpar[wall_filament].category &&is_valid_last_layer(layer.tool_changes[idx].old_tool)) { + candidate_id = layer.tool_changes[idx].old_tool; + } + } + if (layer.tool_changes[idx].new_tool == wall_filament) { + return wall_filament; + } + + if ((candidate_id == -1) && (m_filpar[layer.tool_changes[idx].new_tool].category == m_filpar[wall_filament].category)) + candidate_id = layer.tool_changes[idx].new_tool; + } + return candidate_id == -1 ? layer.tool_changes[0].new_tool : candidate_id; + }; + int wall_idx = get_wall_filament_for_this_layer(); + + // this layer has no tool_change + if (wall_idx == -1) { + bool need_insert_solid_infill = false; + for (const WipeTowerBlock &block : m_wipe_tower_blocks) { + if (block.solid_infill[m_cur_layer_id] && (block.filament_adhesiveness_category != m_filament_categories[m_current_tool])) { + need_insert_solid_infill = true; + break; + } + } + + if (need_insert_solid_infill) { + wall_idx = m_current_tool; + } else { + if (m_enable_timelapse_print) { + timelapse_wall = only_generate_out_wall(true); + } + finish_layer_tcr = finish_layer_new(m_enable_timelapse_print ? false : true, layer.extruder_fill); + std::for_each(m_wipe_tower_blocks.begin(), m_wipe_tower_blocks.end(), [this](WipeTowerBlock &block) { + block.finish_depth[this->m_cur_layer_id] = block.start_depth; + }); + } + } + + // generate tool change + bool insert_wall = false; + int insert_finish_layer_idx = -1; + if (wall_idx != -1 && m_enable_timelapse_print) { + timelapse_wall = only_generate_out_wall(true); + } + for (int i = 0; i < int(layer.tool_changes.size()); ++i) { + ToolChangeResult wall_gcode; + if (i == 0 && (layer.tool_changes[i].old_tool == wall_idx)) { + finish_layer_tcr = finish_layer_new(m_enable_timelapse_print ? false : true, false, false); + } + const auto * block = get_block_by_category(m_filpar[layer.tool_changes[i].new_tool].category, false); + int id = std::find_if(m_wipe_tower_blocks.begin(), m_wipe_tower_blocks.end(), [&](const WipeTowerBlock &b) { return &b == block; }) - m_wipe_tower_blocks.begin(); + bool solid_toolchange = solid_blocks_id.count(id); + + const auto * block2 = get_block_by_category(m_filpar[layer.tool_changes[i].old_tool].category, false); + id = std::find_if(m_wipe_tower_blocks.begin(), m_wipe_tower_blocks.end(), [&](const WipeTowerBlock &b) { return &b == block2; }) - m_wipe_tower_blocks.begin(); + bool solid_nozzlechange = solid_blocks_id.count(id); + layer_result.emplace_back(tool_change_new(layer.tool_changes[i].new_tool, solid_toolchange,solid_nozzlechange)); + + if (i == 0 && (layer.tool_changes[i].old_tool == wall_idx)) { + + } + else if (layer.tool_changes[i].new_tool == wall_idx) { + finish_layer_tcr = finish_layer_new(m_enable_timelapse_print ? false : true, false, false); + insert_finish_layer_idx = i; + } + } + + std::unordered_set next_solid_blocks_id; + // insert finish block + if (wall_idx != -1) { + if (layer.tool_changes.empty()) { + finish_layer_tcr = finish_layer_new(m_enable_timelapse_print ? false : true, false, false); + } + + for (WipeTowerBlock& block : m_wipe_tower_blocks) { + block.finish_depth[m_cur_layer_id] = block.start_depth + block.depth; + if (block.cur_depth + EPSILON >= block.start_depth + block.layer_depths[m_cur_layer_id]-m_perimeter_width) { + continue; + } + int id = std::find_if(m_wipe_tower_blocks.begin(), m_wipe_tower_blocks.end(), [&](const WipeTowerBlock &b) { return &b == █ }) - m_wipe_tower_blocks.begin(); + bool interface_solid = solid_blocks_id.count(id); + int finish_layer_filament = -1; + if (block.last_filament_change_id != -1) { + finish_layer_filament = block.last_filament_change_id; + } else if (block.last_nozzle_change_id != -1) { + finish_layer_filament = block.last_nozzle_change_id; + } + + if (!layer.tool_changes.empty()) { + WipeTowerBlock * last_layer_finish_block = get_block_by_category(get_filament_category(layer.tool_changes.front().old_tool), false); + if (last_layer_finish_block && last_layer_finish_block->block_id == block.block_id && finish_layer_filament == -1) + finish_layer_filament = layer.tool_changes.front().old_tool; + } + + if (finish_layer_filament == -1) { + finish_layer_filament = wall_idx; + } + // Cancel the block of the last layer + if (!is_valid_last_layer(finish_layer_filament)) continue; + ToolChangeResult finish_block_tcr; + if (interface_solid || (block.solid_infill[m_cur_layer_id] && block.filament_adhesiveness_category != m_filament_categories[finish_layer_filament])) { + interface_solid = interface_solid && !((block.solid_infill[m_cur_layer_id] && block.filament_adhesiveness_category != m_filament_categories[finish_layer_filament]));//noly reduce speed when + if (!interface_solid) { + int tmp_id = std::find_if(m_wipe_tower_blocks.begin(), m_wipe_tower_blocks.end(), [&](const WipeTowerBlock &b) { return &b == █ }) - + m_wipe_tower_blocks.begin(); + next_solid_blocks_id.insert(tmp_id); + } + finish_block_tcr = finish_block_solid(block, finish_layer_filament, layer.extruder_fill, interface_solid); + block.finish_depth[m_cur_layer_id] = block.start_depth + block.depth; + } + else { + finish_block_tcr = finish_block(block, finish_layer_filament, layer.extruder_fill); + block.finish_depth[m_cur_layer_id] = block.cur_depth; + } + + bool has_inserted = false; + { + auto fc_iter = std::find_if(layer_result.begin(), layer_result.end(), + [&finish_layer_filament](const WipeTower::ToolChangeResult &item) { return item.new_tool == finish_layer_filament; }); + if (fc_iter != layer_result.end()) { + *fc_iter = merge_tcr(*fc_iter, finish_block_tcr); + has_inserted = true; + } + } + + if (block.last_filament_change_id == -1 && !has_inserted) { + auto nc_iter = std::find_if(layer_result.begin(), layer_result.end(), + [&finish_layer_filament](const WipeTower::ToolChangeResult &item) { return item.initial_tool == finish_layer_filament; }); + if (nc_iter != layer_result.end()) { + *nc_iter = merge_tcr(finish_block_tcr, *nc_iter); + has_inserted = true; + } + } + + if (!has_inserted) { + if (finish_block_tcr.gcode.empty()) + finish_block_tcr = finish_block_tcr; + else + finish_layer_tcr = merge_tcr(finish_layer_tcr, finish_block_tcr); + } + } + } + // record the contact layers of different categories + solid_blocks_id = next_solid_blocks_id; + if (layer_result.empty()) { + // there is nothing to merge finish_layer with + layer_result.emplace_back(std::move(finish_layer_tcr)); + } + else if (is_valid_gcode(finish_layer_tcr.gcode)) { + if (insert_finish_layer_idx == -1) + layer_result[0] = merge_tcr(finish_layer_tcr, layer_result[0]); + else + layer_result[insert_finish_layer_idx] = merge_tcr(layer_result[insert_finish_layer_idx], finish_layer_tcr); + } + + if (m_enable_timelapse_print && !timelapse_wall.gcode.empty()) { + layer_result.insert(layer_result.begin(), std::move(timelapse_wall)); + } + result.emplace_back(std::move(layer_result)); + } + assert(m_outer_wall.size() == m_plan.size()); +} + // Processes vector m_plan and calls respective functions to generate G-code for the wipe tower // Resulting ToolChangeResults are appended into vector "result" @@ -1613,8 +4267,9 @@ void WipeTower::generate(std::vector> & used = 0.f; m_old_temperature = -1; // reset last temperature written in the gcode - int index = 0; + std::vector layer_result; + int index = 0; for (auto layer : m_plan) { m_cur_layer_id = index++; @@ -1685,7 +4340,7 @@ void WipeTower::generate(std::vector> & } } -WipeTower::ToolChangeResult WipeTower::only_generate_out_wall() +WipeTower::ToolChangeResult WipeTower::only_generate_out_wall(bool is_new_mode) { size_t old_tool = m_current_tool; @@ -1695,10 +4350,12 @@ WipeTower::ToolChangeResult WipeTower::only_generate_out_wall() .set_initial_tool(m_current_tool) .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)); + set_for_wipe_tower_writer(writer); + // Slow down on the 1st layer. bool first_layer = is_first_layer(); // QDS: speed up perimeter speed to 90mm/s for non-first layer - float feedrate = first_layer ? std::min(m_first_layer_speed * 60.f, 5400.f) : std::min(60.0f * m_filpar[m_current_tool].max_e_speed / m_extrusion_flow, 5400.f); + float feedrate = first_layer ? std::min(m_first_layer_speed * 60.f, m_max_speed) : std::min(60.0f * m_filpar[m_current_tool].max_e_speed / m_extrusion_flow, m_max_speed); float fill_box_y = m_layer_info->toolchanges_depth() + m_perimeter_width; box_coordinates fill_box(Vec2f(m_perimeter_width, fill_box_y), m_wipe_tower_width - 2 * m_perimeter_width, m_layer_info->depth - fill_box_y); @@ -1714,14 +4371,25 @@ WipeTower::ToolChangeResult WipeTower::only_generate_out_wall() writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_Start) + "\n"); // outer perimeter (always): // QDS - box_coordinates wt_box(Vec2f(0.f, (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); + + float wipe_tower_depth = m_layer_info->depth + m_perimeter_width; + if (is_new_mode && m_enable_timelapse_print) + wipe_tower_depth = m_wipe_tower_depth; + box_coordinates wt_box(Vec2f(0.f, (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)), m_wipe_tower_width, wipe_tower_depth); wt_box = align_perimeter(wt_box); - writer.rectangle(wt_box, feedrate); - + Polygon outer_wall; + //if (m_use_gap_wall) + // generate_support_wall(writer, wt_box, feedrate, first_layer); + //else + // writer.rectangle(wt_box, feedrate); + outer_wall = generate_support_wall_new(writer, wt_box, feedrate, first_layer, m_use_rib_wall, true, m_use_gap_wall); + m_outer_wall[m_z_pos].push_back(to_polyline(outer_wall)); // Now prepare future wipe. box contains rectangle that was extruded last (ccw). - Vec2f target = (writer.pos() == wt_box.ld ? wt_box.rd : (writer.pos() == wt_box.rd ? wt_box.ru : (writer.pos() == wt_box.ru ? wt_box.lu : wt_box.ld))); - writer.add_wipe_point(writer.pos()).add_wipe_point(target); + // Vec2f target = (writer.pos() == wt_box.ld ? wt_box.rd : (writer.pos() == wt_box.rd ? wt_box.ru : (writer.pos() == wt_box.ru ? wt_box.lu : wt_box.ld))); + //writer.add_wipe_point(writer.pos()).add_wipe_point(target); + + writer.add_wipe_path(outer_wall, m_filpar[m_current_tool].wipe_dist); writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_End) + "\n"); // Ask our writer about how much material was consumed. @@ -1729,13 +4397,207 @@ WipeTower::ToolChangeResult WipeTower::only_generate_out_wall() if (!m_no_sparse_layers || toolchanges_on_layer) if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - return construct_tcr(writer, false, old_tool, true, 0.f); + return construct_tcr(writer, false, old_tool, true, false, 0.f); } +Polygon WipeTower::generate_rib_polygon(const box_coordinates &wt_box) +{ + auto get_current_layer_rib_len = [](float cur_height, float max_height, float max_len) -> float { return std::abs(max_height - cur_height) / max_height * max_len; }; + coord_t diagonal_width = scaled(m_rib_width)/2; + float a = this->m_wipe_tower_width, b = this->m_wipe_tower_depth; + Line line_1(Point::new_scale(Vec2f{0, 0}), Point::new_scale(Vec2f{a, b})); + Line line_2(Point::new_scale(Vec2f{a, 0}), Point::new_scale(Vec2f{0, b})); + float diagonal_extra_length = std::max(0.f, m_rib_length - (float) unscaled(line_1.length())) / 2.f; + diagonal_extra_length = scaled(get_current_layer_rib_len(this->m_z_pos, this->m_wipe_tower_height, diagonal_extra_length)); + Point y_shift{0, scaled(this->m_y_shift)}; + + line_1.extend(double(diagonal_extra_length)); + line_2.extend(double(diagonal_extra_length)); + line_1.translate(-y_shift); + line_2.translate(-y_shift); + + Polygon poly_1 = generate_rectange(line_1, diagonal_width); + Polygon poly_2 = generate_rectange(line_2, diagonal_width); + Polygon poly; + poly.points.push_back(Point::new_scale(wt_box.ld)); + poly.points.push_back(Point::new_scale(wt_box.rd)); + poly.points.push_back(Point::new_scale(wt_box.ru)); + poly.points.push_back(Point::new_scale(wt_box.lu)); + + Polygons p_1_2 = union_({poly_1, poly_2, poly}); + //Polygon res_poly = p_1_2.front(); + //for (auto &p : res_poly.points) res.push_back(unscale(p).cast()); + /*if (p_1_2.front().points.size() != 16) + std::cout << "error " << std::endl;*/ + return p_1_2.front(); +}; + +Polygon WipeTower::generate_support_wall_new(WipeTowerWriter &writer, const box_coordinates &wt_box, double feedrate, bool first_layer,bool rib_wall, bool extrude_perimeter, bool skip_points) +{ + auto get_closet_idx = [this, &writer](Polylines &pls) -> std::pair { + Vec2f anchor{writer.x(), writer.y()}; + int closestIndex = -1; + int closestPl = -1; + float minDistance = std::numeric_limits::max(); + for (int i = 0; i < pls.size(); ++i) { + for (int j = 0; j < pls[i].size(); ++j) { + float distance = (unscaled(pls[i][j]) - anchor).squaredNorm(); + if (distance < minDistance) { + minDistance = distance; + closestPl = i; + closestIndex = j; + } + } + } + return {closestPl, closestIndex}; + }; + + float retract_length = m_filpar[m_current_tool].retract_length; + float retract_speed = m_filpar[m_current_tool].retract_speed * 60; + Polygon wall_polygon = rib_wall ? generate_rib_polygon(wt_box) : generate_rectange_polygon(wt_box.ld, wt_box.ru); + Polylines result_wall; + Polygon insert_skip_polygon; + if (m_used_fillet) { + if (!rib_wall && m_y_shift > EPSILON)// do nothing because the fillet will cause it to be suspended. + { + } else { + wall_polygon = rib_wall ? rounding_polygon(wall_polygon) : wall_polygon; // rectangle_wall do nothing + Polygon wt_box_polygon = generate_rectange_polygon(wt_box.ld, wt_box.ru); + wall_polygon = union_({wall_polygon, wt_box_polygon}).front(); + } + } + if (!extrude_perimeter) return wall_polygon; + + if (skip_points) { result_wall = contrust_gap_for_skip_points(wall_polygon,m_wall_skip_points,m_wipe_tower_width,2.5*m_perimeter_width,insert_skip_polygon); } + else { + result_wall.push_back(to_polyline(wall_polygon)); + insert_skip_polygon = wall_polygon; + } + writer.generate_path(result_wall, feedrate, retract_length, retract_speed,m_used_fillet); + if (m_cur_layer_id == 0) { + BoundingBox bbox = get_extents(result_wall); + m_rib_offset = Vec2f(-unscaled(bbox.min.x()), -unscaled(bbox.min.y())); + } + + return insert_skip_polygon; +} + +Polygon WipeTower::generate_support_wall(WipeTowerWriter &writer, const box_coordinates &wt_box, double feedrate, bool first_layer) +{ + float retract_length = m_filpar[m_current_tool].retract_length; + float retract_speed = m_filpar[m_current_tool].retract_speed *60 ; + bool is_left = false; + bool is_right = false; + for (auto pt : m_wall_skip_points) { + if (abs(pt.x()) < EPSILON) { + is_left = true; + } else if (abs(pt.x() - m_wipe_tower_width) < EPSILON) { + is_right = true; + } + } + + if (is_left && is_right) { + Vec2f *p = nullptr; + p->x(); + } + + if (!is_left && !is_right) { + Vec2f *p = nullptr; + p->x(); + } + + // 3 ------------- 2 + // | | + // | | + // 0 ------------- 1 + + int index = 0; + Vec2f cur_pos = writer.pos(); + if (abs(cur_pos.x() - wt_box.ld.x()) > abs(cur_pos.x() - wt_box.rd.x())) { + if (abs(cur_pos.y() - wt_box.ld.y()) > abs(cur_pos.y() - wt_box.lu.y())) { + index = 2; + } else { + index = 1; + } + } else { + if (abs(cur_pos.y() - wt_box.ld.y()) > abs(cur_pos.y() - wt_box.lu.y())) { + index = 3; + } else { + index = 0; + } + } + + std::vector points; + points.emplace_back(wt_box.ld); + points.emplace_back(wt_box.rd); + points.emplace_back(wt_box.ru); + points.emplace_back(wt_box.lu); + + writer.travel(points[index]); + int extruded_nums = 0; + while (extruded_nums < 4) { + index = (index + 1) % 4; + if (index == 2) { + if (is_right) { + std::vector break_segments = remove_points_from_segment(Segment(wt_box.rd, wt_box.ru), m_wall_skip_points, 2.5 * m_perimeter_width); + for (auto iter = break_segments.begin(); iter != break_segments.end(); ++iter) { + float dx = iter->start.x() - writer.pos().x(); + float dy = iter->start.y() - writer.pos().y(); + float len = std::sqrt(dx * dx + dy * dy); + if (len > 0) { + writer.retract(retract_length, retract_speed); + writer.travel(iter->start, 600.); + writer.retract(-retract_length, retract_speed); + } else + writer.travel(iter->start, 600.); + + writer.extrude(iter->end, feedrate); + } + writer.travel(wt_box.ru, feedrate); + } else { + writer.extrude(wt_box.ru, feedrate); + } + } else if (index == 0) { + if (is_left) { + std::vector break_segments = remove_points_from_segment(Segment(wt_box.ld, wt_box.lu), m_wall_skip_points, 2.5 * m_perimeter_width); + for (auto iter = break_segments.rbegin(); iter != break_segments.rend(); ++iter) { + float dx = iter->end.x() - writer.pos().x(); + float dy = iter->end.y() - writer.pos().y(); + float len = std::sqrt(dx * dx + dy * dy); + if (len > 0) { + writer.retract(retract_length, retract_speed); + writer.travel(iter->end, 600.); + writer.retract(-retract_length, retract_speed); + } else + writer.travel(iter->end, 600.); + + writer.extrude(iter->start, feedrate); + } + writer.travel(wt_box.ld, feedrate); + } else { + writer.extrude(wt_box.ld, feedrate); + } + } else { + writer.extrude(points[index], feedrate); + } + extruded_nums++; + } + + return Polygon(); +} + + bool WipeTower::get_floating_area(float &start_pos_y, float &end_pos_y) const { if (m_layer_info == m_plan.begin() || (m_layer_info - 1) == m_plan.begin()) return false; + if (!m_cur_block) + return false; + + end_pos_y = m_cur_block->start_depth + m_cur_block->depth - m_perimeter_width; + start_pos_y = m_cur_block->finish_depth[m_cur_layer_id - 1]; + +#if 0 float last_layer_fill_box_y = (m_layer_info - 1)->toolchanges_depth() + m_perimeter_width; float last_layer_wipe_depth = (m_layer_info - 1)->depth; if (last_layer_wipe_depth - last_layer_fill_box_y <= 2 * m_perimeter_width) @@ -1743,12 +4605,12 @@ bool WipeTower::get_floating_area(float &start_pos_y, float &end_pos_y) const { start_pos_y = last_layer_fill_box_y + m_perimeter_width; end_pos_y = last_layer_wipe_depth - m_perimeter_width; - +#endif return true; } bool WipeTower::need_thick_bridge_flow(float pos_y) const { - if (m_extrusion_flow >= extrusion_flow(0.2)) + if (m_layer_height >= 0.2) return false; float y_min = 0., y_max = 0.; @@ -1758,4 +4620,14 @@ bool WipeTower::need_thick_bridge_flow(float pos_y) const { return false; } +bool WipeTower::is_valid_last_layer(int tool) const +{ + int nozzle_id = -1; + if (tool >= 0 && tool < m_filament_map.size()) + nozzle_id = m_filament_map[tool]-1; + if (nozzle_id < 0 || nozzle_id >= m_printable_height.size()) return true; + if (m_last_layer_id[nozzle_id] == m_cur_layer_id && m_z_pos > m_printable_height[nozzle_id]) return false; + return true; +} + } // namespace Slic3r diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 6d9a0f5..70f50c8 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -8,6 +8,10 @@ #include #include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include namespace Slic3r { @@ -17,15 +21,21 @@ class PrintConfig; enum GCodeFlavor : unsigned char; - class WipeTower { public: + friend class WipeTowerWriter; static const std::string never_skip_tag() { return "_GCODE_WIPE_TOWER_NEVER_SKIP_TAG"; } // WipeTower height to minimum depth map static const std::map min_depth_per_height; - + static float get_limit_depth_by_height(float max_height); + static float get_auto_brim_by_height(float max_height); + static TriangleMesh its_make_rib_tower(float width, float depth, float height, float rib_length, float rib_width, bool fillet_wall); + static TriangleMesh its_make_rib_brim(const Polygon& brim, float layer_height); + static Polygon rib_section(float width, float depth, float rib_length, float rib_width, bool fillet_wall); + static Vec2f move_box_inside_box(const BoundingBox &box1, const BoundingBox &box2, int offset = 0); + static Polygon rounding_polygon(Polygon &polygon, double rounding = 2., double angle_tol = 30. / 180. * PI); struct Extrusion { Extrusion(const Vec2f &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {} @@ -38,6 +48,18 @@ public: unsigned int tool; }; + struct NozzleChangeResult + { + std::string gcode; + + Vec2f start_pos; // rotated + Vec2f end_pos; + + Vec2f origin_start_pos; // not rotated + + std::vector wipe_path; + }; + struct ToolChangeResult { // Print heigh of this tool change. @@ -60,6 +82,9 @@ public: // Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later) bool priming; + bool is_tool_change{false}; + Vec2f tool_change_start_pos; + // Pass a polyline so that normal G-code generator can do a wipe for us. // The wipe cannot be done by the wipe tower because it has to pass back // a loaded extruder, so it would have to either do a wipe with no retraction @@ -81,6 +106,8 @@ public: // executing the gcode finish_layer_tcr. bool is_finish_first = false; + NozzleChangeResult nozzle_change_result; + // Sum the total length of the extrusion. float total_extrusion_length_in_plane() { float e_length = 0.f; @@ -132,14 +159,22 @@ public: bool priming, size_t old_tool, bool is_finish, + bool is_tool_change, float purge_volume) const; + ToolChangeResult construct_block_tcr(WipeTowerWriter& writer, + bool priming, + size_t filament_id, + bool is_finish, + float purge_volume) const; + + // x -- x coordinates of wipe tower in mm ( left bottom corner ) // y -- y coordinates of wipe tower in mm ( left bottom corner ) // width -- width of wipe tower in mm ( default 60 mm - leave as it is ) // wipe_area -- space available for one toolchange in mm // QDS: add partplate logic - WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origin, const float wipe_volume, size_t initial_tool, const float wipe_tower_height); + WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origin, size_t initial_tool, const float wipe_tower_height); // Set the extruder properties. @@ -152,12 +187,27 @@ public: // Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result" void generate(std::vector> &result); - WipeTower::ToolChangeResult only_generate_out_wall(); + WipeTower::ToolChangeResult only_generate_out_wall(bool is_new_mode = false); + Polygon generate_support_wall(WipeTowerWriter &writer, const box_coordinates &wt_box, double feedrate, bool first_layer); + Polygon generate_support_wall_new(WipeTowerWriter &writer, const box_coordinates &wt_box, double feedrate, bool first_layer,bool rib_wall, bool extrude_perimeter, bool skip_points); + Polygon generate_rib_polygon(const box_coordinates &wt_box); float get_depth() const { return m_wipe_tower_depth; } float get_brim_width() const { return m_wipe_tower_brim_width_real; } + BoundingBoxf get_bbx() const { + if (m_outer_wall.empty()) return BoundingBoxf({Vec2d(0,0)}); + BoundingBox box = get_extents(m_outer_wall.begin()->second); + BoundingBoxf res = BoundingBoxf(unscale(box.min), unscale(box.max)); + return res; + } + std::map get_outer_wall() const + { + return m_outer_wall; + } float get_height() const { return m_wipe_tower_height; } float get_layer_height() const { return m_layer_height; } + float get_rib_length() const { return m_rib_length; } + float get_rib_width() const { return m_rib_width; } void set_last_layer_extruder_fill(bool extruder_fill) { if (!m_plan.empty()) { @@ -190,10 +240,9 @@ public: m_num_tool_changes = 0; } else ++ m_num_layer_changes; - + // Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height: m_extrusion_flow = extrusion_flow(layer_height); - // Advance m_layer_info iterator, making sure we got it right while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end()) ++m_layer_info; @@ -221,6 +270,8 @@ public: // QDS ToolChangeResult tool_change(size_t new_tool, bool extrude_perimeter = false, bool first_toolchange_to_nonsoluble = false); + NozzleChangeResult nozzle_change(int old_filament_id, int new_filament_id); + // Fill the unfilled space with a sparse infill. // Call this method only if layer_finished() is false. ToolChangeResult finish_layer(bool extruder_perimeter = true, bool extruder_fill = true); @@ -231,6 +282,12 @@ public: if (layer_height < 0) return m_extrusion_flow; return layer_height * (m_perimeter_width - layer_height * (1.f - float(M_PI) / 4.f)) / filament_area(); } + float nozzle_change_extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow + { + if (layer_height < 0) + return m_extrusion_flow; + return layer_height * (m_nozzle_change_perimeter_width - layer_height * (1.f - float(M_PI) / 4.f)) / filament_area(); + } bool get_floating_area(float& start_pos_y, float& end_pos_y) const; bool need_thick_bridge_flow(float pos_y) const; @@ -244,8 +301,23 @@ public: std::vector get_used_filament() const { return m_used_filament_length; } int get_number_of_toolchanges() const { return m_num_tool_changes; } + void set_filament_map(const std::vector &filament_map) { m_filament_map = filament_map; } + + void set_has_tpu_filament(bool has_tpu) { m_has_tpu_filament = has_tpu; } + void set_need_reverse_travel(const std::vector & used_extruders) + { + for (unsigned int filament_id : used_extruders) { + if (m_filpar[filament_id].ramming_travel_time > EPSILON) { + m_need_reverse_travel = true; + return; + } + } + } + bool has_tpu_filament() const { return m_has_tpu_filament; } + struct FilamentParameters { std::string material = "PLA"; + int category; bool is_soluble = false; // QDS bool is_support = false; @@ -266,8 +338,70 @@ public: std::vector ramming_speed; float nozzle_diameter; float filament_area; + float retract_length; + float retract_speed; + float wipe_dist; + float max_e_ramming_speed = 0.f; + float ramming_travel_time=0.f; //Travel time after ramming + std::vector precool_t;//Pre-cooling time, set to 0 to ensure the ramming speed is controlled solely by ramming volumetric speed. + std::vector precool_t_first_layer; }; + + void set_used_filament_ids(const std::vector &used_filament_ids) { m_used_filament_ids = used_filament_ids; }; + void set_filament_categories(const std::vector & filament_categories) { m_filament_categories = filament_categories;}; + std::vector m_used_filament_ids; + std::vector m_filament_categories; + + struct WipeTowerBlock + { + int block_id{0}; + int filament_adhesiveness_category{0}; + std::vector layer_depths; + std::vector solid_infill; + std::vector finish_depth{0}; // the start pos of finish frame for every layer + float depth{0}; + float start_depth{0}; + float cur_depth{0}; + int last_filament_change_id{-1}; + int last_nozzle_change_id{-1}; + }; + + struct BlockDepthInfo + { + int category{-1}; + float depth{0}; + float nozzle_change_depth{0}; + }; + + std::vector> m_all_layers_depth; + std::vector m_wipe_tower_blocks; + int m_last_block_id; + WipeTowerBlock* m_cur_block{nullptr}; + + // help function + WipeTowerBlock* get_block_by_category(int filament_adhesiveness_category, bool create); + void add_depth_to_block(int filament_id, int filament_adhesiveness_category, float depth, bool is_nozzle_change = false); + int get_filament_category(int filament_id); + bool is_in_same_extruder(int filament_id_1, int filament_id_2); + void reset_block_status(); + int get_wall_filament_for_all_layer(); + // for generate new wipe tower + void generate_new(std::vector> &result); + + void plan_tower_new(); + void generate_wipe_tower_blocks(); + void update_all_layer_depth(float wipe_tower_depth); + void set_nozzle_last_layer_id(); + + ToolChangeResult tool_change_new(size_t new_tool, bool solid_change = false, bool solid_nozzlechange=false); + NozzleChangeResult nozzle_change_new(int old_filament_id, int new_filament_id, bool solid_change = false); + ToolChangeResult finish_layer_new(bool extrude_perimeter = true, bool extrude_fill = true, bool extrude_fill_wall = true); + ToolChangeResult finish_block(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true); + ToolChangeResult finish_block_solid(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true ,bool interface_solid =false); + void toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinates &cleaning_box, float wipe_length,bool solid_toolchange=false); + Vec2f get_rib_offset() const { return m_rib_offset; } + private: enum wipe_shape // A fill-in direction { @@ -300,7 +434,23 @@ private: float m_travel_speed = 0.f; float m_first_layer_speed = 0.f; size_t m_first_layer_idx = size_t(-1); - size_t m_cur_layer_id; + std::vector m_last_layer_id; + std::vector m_filaments_change_length; + size_t m_cur_layer_id; + NozzleChangeResult m_nozzle_change_result; + std::vector m_filament_map; + bool m_has_tpu_filament{false}; + bool m_is_multi_extruder{false}; + bool m_use_gap_wall{false}; + bool m_use_rib_wall{false}; + float m_rib_length=0.f; + float m_rib_width=0.f; + float m_extra_rib_length=0.f; + bool m_used_fillet{false}; + Vec2f m_rib_offset{Vec2f(0.f, 0.f)}; + bool m_tower_framework{false}; + bool m_need_reverse_travel{false}; + // G-code generator parameters. // QDS: remove useless config //float m_cooling_tube_retraction = 0.f; @@ -314,6 +464,14 @@ private: bool m_adhesion = true; GCodeFlavor m_gcode_flavor; + std::vector m_normal_accels; + std::vector m_first_layer_normal_accels; + std::vector m_travel_accels; + std::vector m_first_layer_travel_accels; + unsigned int m_max_accels; + bool m_accel_to_decel_enable; + float m_accel_to_decel_factor; + // Bed properties enum { RectangularBed, @@ -324,6 +482,7 @@ private: Vec2f m_bed_bottom_left; // bottom-left corner coordinates (for rectangular beds) float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill. + float m_nozzle_change_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter. // Extruder specific parameters. @@ -340,14 +499,18 @@ private: size_t m_current_tool = 0; // QDS //const std::vector> wipe_volumes; - const float m_wipe_volume; 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_tpu_fixed_spacing = 2; + float m_max_speed = 5400.f; // the maximum printing speed on the prime tower. + std::vector m_wall_skip_points; + std::map m_outer_wall; + std::vector m_printable_height; bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; } + bool is_valid_last_layer(int tool) const; // Calculates length of extrusion line to extrude given volume float volume_to_length(float volume, float line_width, float layer_height) const { @@ -360,12 +523,17 @@ private: // Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental void make_wipe_tower_square(); + Vec2f get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length); + // Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe void save_on_last_wipe(); + bool is_tpu_filament(int filament_id) const; + bool is_need_reverse_travel(int filament) const; // QDS box_coordinates align_perimeter(const box_coordinates& perimeter_box); + void set_for_wipe_tower_writer(WipeTowerWriter &writer); // to store information about tool changes for a given layer struct WipeTowerInfo{ @@ -377,6 +545,7 @@ private: float first_wipe_line; float wipe_volume; float wipe_length; + float nozzle_change_depth{0}; // QDS float purge_volume; ToolChange(size_t old, size_t newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f, float wl = 0, float pv = 0) @@ -409,7 +578,7 @@ private: void toolchange_Unload( WipeTowerWriter &writer, - const box_coordinates &cleaning_box, + const box_coordinates &cleaning_box, const std::string& current_material, const int new_temperature); @@ -417,15 +586,17 @@ private: WipeTowerWriter &writer, const size_t new_tool, const std::string& new_material); - + void toolchange_Load( WipeTowerWriter &writer, const box_coordinates &cleaning_box); - + void toolchange_Wipe( WipeTowerWriter &writer, const box_coordinates &cleaning_box, float wipe_volume); + void get_wall_skip_points(const WipeTowerInfo &layer); + ToolChangeResult merge_tcr(ToolChangeResult &first, ToolChangeResult &second); }; @@ -433,4 +604,4 @@ private: } // namespace Slic3r -#endif // WipeTowerPrusaMM_hpp_ +#endif // WipeTowerPrusaMM_hpp_ diff --git a/src/libslic3r/GCodeReader.hpp b/src/libslic3r/GCodeReader.hpp index 7219960..7fd1c36 100644 --- a/src/libslic3r/GCodeReader.hpp +++ b/src/libslic3r/GCodeReader.hpp @@ -79,6 +79,11 @@ public: return strncmp(cmd, cmd_test, len) == 0 && GCodeReader::is_end_of_word(cmd[len]); } + static bool cmd_start_with(const std::string& gcode_line, const char* cmd_test) { + const char* cmd = GCodeReader::skip_whitespaces(gcode_line.c_str()); + return strncmp(cmd, cmd_test, strlen(cmd_test)) == 0; + } + private: std::string m_raw; float m_axis[NUM_AXES]; @@ -151,15 +156,6 @@ public: float& j() { return m_position[J]; } float j() const { return m_position[J]; } -private: - template - bool parse_file_raw_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback); - template - bool parse_file_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback); - - const char* parse_line_internal(const char *ptr, const char *end, GCodeLine &gline, std::pair &command); - void update_coordinates(GCodeLine &gline, std::pair &command); - static bool is_whitespace(char c) { return c == ' ' || c == '\t'; } static bool is_end_of_line(char c) { return c == '\r' || c == '\n' || c == 0; } static bool is_end_of_gcode_line(char c) { return c == ';' || is_end_of_line(c); } @@ -175,6 +171,15 @@ private: return c; } +private: + template + bool parse_file_raw_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback); + template + bool parse_file_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback); + + const char* parse_line_internal(const char *ptr, const char *end, GCodeLine &gline, std::pair &command); + void update_coordinates(GCodeLine &gline, std::pair &command); + GCodeConfig m_config; float m_position[NUM_AXES]; bool m_verbose; diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 9d5412e..c3bff19 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -32,11 +32,11 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config) void GCodeWriter::set_extruders(std::vector extruder_ids) { std::sort(extruder_ids.begin(), extruder_ids.end()); - m_extruders.clear(); - m_extruders.reserve(extruder_ids.size()); + m_filament_extruders.clear(); + m_filament_extruders.reserve(extruder_ids.size()); for (unsigned int extruder_id : extruder_ids) - m_extruders.emplace_back(Extruder(extruder_id, &this->config, config.single_extruder_multi_material.value)); - + m_filament_extruders.emplace_back(Extruder(extruder_id, &this->config, config.single_extruder_multi_material.value)); + /* we enable support for multiple extruder if any extruder greater than 0 is used (even if prints only uses that one) since we need to output Tx commands first extruder has index 0 */ @@ -165,7 +165,45 @@ std::string GCodeWriter::set_chamber_temperature(int temperature, bool wait) return gcode.str(); } -std::string GCodeWriter::set_acceleration(unsigned int acceleration) +void GCodeWriter::set_acceleration(unsigned int acceleration) +{ + m_acceleration = acceleration; +} + +void GCodeWriter::set_travel_acceleration(const std::vector& accelerations) +{ + m_travel_accelerations = accelerations; +} + +void GCodeWriter::set_first_layer_travel_acceleration(const std::vector &travel_accelerations) +{ + m_first_layer_travel_accelerations = travel_accelerations; +} + +void GCodeWriter::set_first_layer(bool is_first_layer) +{ + m_is_first_layer = is_first_layer; +} + +std::string GCodeWriter::set_extrude_acceleration() +{ + return set_acceleration_impl(m_acceleration); +} + +std::string GCodeWriter::set_travel_acceleration() +{ + std::vector travel_accelerations = m_is_first_layer ? m_first_layer_travel_accelerations : m_travel_accelerations; + if (travel_accelerations.empty()) + return std::string(); + + Extruder *cur_filament = filament(); + if (!cur_filament) + return std::string(); + + return set_acceleration_impl(travel_accelerations[cur_filament->extruder_id()]); +} + +std::string GCodeWriter::set_acceleration_impl(unsigned int acceleration) { // Clamp the acceleration to the allowed maximum. if (m_max_acceleration > 0 && acceleration > m_max_acceleration) @@ -253,11 +291,11 @@ std::string GCodeWriter::reset_e(bool force) || FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) return ""; - - if (m_extruder != nullptr) { - if (m_extruder->E() == 0. && ! force) + + if (m_curr_extruder_id!=-1 && m_curr_filament_extruder[m_curr_extruder_id] != nullptr) { + if (m_curr_filament_extruder[m_curr_extruder_id]->E() == 0. && !force) return ""; - m_extruder->reset_E(); + m_curr_filament_extruder[m_curr_extruder_id]->reset_E(); } if (!this->config.use_relative_e_distances) { @@ -294,18 +332,23 @@ std::string GCodeWriter::toolchange_prefix() const FLAVOR_IS(gcfSailfish) ? "M108 T" : "T"; } -std::string GCodeWriter::toolchange(unsigned int extruder_id) +std::string GCodeWriter::toolchange(unsigned int filament_id) { // set the new extruder - auto it_extruder = Slic3r::lower_bound_by_predicate(m_extruders.begin(), m_extruders.end(), [extruder_id](const Extruder &e) { return e.id() < extruder_id; }); - assert(it_extruder != m_extruders.end() && it_extruder->id() == extruder_id); - m_extruder = &*it_extruder; + auto filament_extruder_iter = Slic3r::lower_bound_by_predicate(m_filament_extruders.begin(), m_filament_extruders.end(), [filament_id](const Extruder &e) { return e.id() < filament_id; }); + assert(filament_extruder_iter != m_filament_extruders.end() && filament_extruder_iter->id() == filament_id); + m_curr_extruder_id = filament_extruder_iter->extruder_id(); + m_curr_filament_extruder[m_curr_extruder_id] = &*filament_extruder_iter; // return the toolchange command // if we are running a single-extruder setup, just set the extruder and return nothing std::ostringstream gcode; if (this->multiple_extruders) { - gcode << this->toolchange_prefix() << extruder_id; + // QDS + if (this->m_is_qdt_printer) + gcode << "M1020 S" << filament_id; + else + gcode << this->toolchange_prefix() << filament_id; //QDS if (GCodeWriter::full_gcode_comment) gcode << " ; change extruder"; @@ -339,10 +382,80 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com GCodeG1Formatter w; w.emit_xy(point_on_plate); - w.emit_f(this->config.travel_speed.value * 60.0); + w.emit_f(this->config.travel_speed.get_at(get_extruder_index(this->config, filament()->id())) * 60.0); //QDS w.emit_comment(GCodeWriter::full_gcode_comment, comment); - return w.string(); + return set_travel_acceleration() + w.string(); +} + +/* If this method is called more than once before calling unlift(), +it will not perform subsequent lifts, even if Z was raised manually +(i.e. with travel_to_z()) and thus _lifted was reduced. */ +std::string GCodeWriter::lazy_lift(LiftType lift_type, bool spiral_vase, bool tool_change) +{ + // check whether the above/below conditions are met + double target_lift = 0; + { + //QDS + int extruder_id = filament()->extruder_id(); + int filament_id = filament()->id(); + double above = this->config.retract_lift_above.get_at(extruder_id); + double below = this->config.retract_lift_below.get_at(extruder_id); + if (m_pos.z() >= above && m_pos.z() <= below){ + target_lift = this->config.z_hop.get_at(filament_id); + if (tool_change && this->config.prime_tower_lift_height.value > 0) target_lift = this->config.prime_tower_lift_height.value; + } + } + // QDS + if (m_lifted == 0 && m_to_lift == 0 && target_lift > 0) { + if (spiral_vase) { + m_lifted = target_lift; + return this->_travel_to_z(m_pos(2) + target_lift, "lift Z",tool_change); + } + else { + m_to_lift = target_lift; + m_to_lift_type = lift_type; + } + } + return ""; +} + +// QDS: immediately execute an undelayed lift move with a spiral lift pattern +// designed specifically for subsequent gcode injection (e.g. timelapse) +std::string GCodeWriter::eager_lift(const LiftType type, bool tool_change) +{ + std::string lift_move; + double target_lift = 0; + { + //QDS + int extruder_id = filament()->extruder_id(); + int filament_id = filament()->id(); + double above = this->config.retract_lift_above.get_at(extruder_id); + double below = this->config.retract_lift_below.get_at(extruder_id); + if (m_pos.z() >= above && m_pos.z() <= below){ + target_lift = this->config.z_hop.get_at(filament_id); + if (tool_change && this->config.prime_tower_lift_height.value > 0) target_lift = this->config.prime_tower_lift_height.value; + } + } + + // QDS: spiral lift only safe with known position + // TODO: check the arc will move within bed area + if (type == LiftType::SpiralLift && this->is_current_position_clear()) { + double radius = target_lift / (2 * PI * atan(GCodeWriter::slope_threshold)); + // static spiral alignment when no move in x,y plane. + // spiral centra is a radius distance to the right (y=0) + Vec2d ij_offset = { radius, 0 }; + if (target_lift > 0) { + lift_move = this->_spiral_travel_to_z(m_pos(2) + target_lift, ij_offset, "spiral lift Z",tool_change); + } + } + //QDS: if position is unknown use normal lift + else if (target_lift > 0) { + lift_move = _travel_to_z(m_pos(2) + target_lift, "normal lift Z",tool_change); + } + m_lifted = target_lift; + m_to_lift = 0; + return lift_move; } std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &comment) @@ -388,9 +501,9 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co ij_offset = { -ij_offset(1), ij_offset(0) }; slop_move = this->_spiral_travel_to_z(target(2), ij_offset, "spiral lift Z"); } - //QDS: LazyLift - else if (m_to_lift_type == LiftType::LazyLift && - this->is_current_position_clear() && + //QDS: SlopeLift + else if (m_to_lift_type == LiftType::SlopeLift && + this->is_current_position_clear() && atan2(delta(2), delta_no_z.norm()) < GCodeWriter::slope_threshold) { //QDS: check whether we can make a travel like // _____ @@ -399,7 +512,7 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co Vec3d slope_top_point = Vec3d(temp(0), temp(1), delta(2)) + source; GCodeG1Formatter w0; w0.emit_xyz(slope_top_point); - w0.emit_f(this->config.travel_speed.value * 60.0); + w0.emit_f(this->config.travel_speed.get_at(get_extruder_index(this->config, filament()->id())) * 60.0); //QDS w0.emit_comment(GCodeWriter::full_gcode_comment, "slope lift Z"); slop_move = w0.string(); @@ -414,20 +527,20 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co GCodeG1Formatter w0; if (this->is_current_position_clear()) { w0.emit_xyz(target); - w0.emit_f(this->config.travel_speed.value * 60.0); + w0.emit_f(this->config.travel_speed.get_at(get_extruder_index(this->config, filament()->id())) * 60.0); w0.emit_comment(GCodeWriter::full_gcode_comment, comment); xy_z_move = w0.string(); } else { w0.emit_xy(Vec2d(target.x(), target.y())); - w0.emit_f(this->config.travel_speed.value * 60.0); + w0.emit_f(this->config.travel_speed.get_at(get_extruder_index(this->config, filament()->id())) * 60.0); w0.emit_comment(GCodeWriter::full_gcode_comment, comment); xy_z_move = w0.string() + _travel_to_z(target.z(), comment); } } m_pos = dest_point; this->set_current_position_clear(true); - return slop_move + xy_z_move; + return set_travel_acceleration() + slop_move + xy_z_move; } else if (!this->will_move_z(point(2))) { double nominal_z = m_pos(2) - m_lifted; @@ -454,20 +567,20 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co { //force to move xy first then z after filament change w.emit_xy(Vec2d(point_on_plate.x(), point_on_plate.y())); - w.emit_f(this->config.travel_speed.value * 60.0); + w.emit_f(this->config.travel_speed.get_at(get_extruder_index(this->config, filament()->id())) * 60.0); w.emit_comment(GCodeWriter::full_gcode_comment, comment); out_string = w.string() + _travel_to_z(point_on_plate.z(), comment); } else { GCodeG1Formatter w; w.emit_xyz(point_on_plate); - w.emit_f(this->config.travel_speed.value * 60.0); + w.emit_f(this->config.travel_speed.get_at(get_extruder_index(this->config, filament()->id())) * 60.0); w.emit_comment(GCodeWriter::full_gcode_comment, comment); out_string = w.string(); } m_pos = dest_point; this->set_current_position_clear(true); - return out_string; + return set_travel_acceleration() + out_string; } std::string GCodeWriter::travel_to_z(double z, const std::string &comment) @@ -486,33 +599,37 @@ std::string GCodeWriter::travel_to_z(double z, const std::string &comment) /* In all the other cases, we perform an actual Z move and cancel the lift. */ m_lifted = 0; - return this->_travel_to_z(z, comment); + return set_travel_acceleration() + this->_travel_to_z(z, comment); } -std::string GCodeWriter::_travel_to_z(double z, const std::string &comment) +std::string GCodeWriter::_travel_to_z(double z, const std::string &comment, bool tool_change) { m_pos(2) = z; - double speed = this->config.travel_speed_z.value; + double speed = this->config.travel_speed_z.get_at(get_extruder_index(this->config, filament()->id())); if (speed == 0.) - speed = this->config.travel_speed.value; - + speed = this->config.travel_speed.get_at(get_extruder_index(this->config, filament()->id())); + if (tool_change && this->config.prime_tower_lift_speed.value>0) { + speed = this->config.prime_tower_lift_speed.value; // lift speed + } GCodeG1Formatter w; w.emit_z(z); w.emit_f(speed * 60.0); //QDS w.emit_comment(GCodeWriter::full_gcode_comment, comment); - return w.string(); + return set_travel_acceleration() + w.string(); } -std::string GCodeWriter::_spiral_travel_to_z(double z, const Vec2d &ij_offset, const std::string &comment) +std::string GCodeWriter::_spiral_travel_to_z(double z, const Vec2d &ij_offset, const std::string &comment, bool tool_change) { m_pos(2) = z; - double speed = this->config.travel_speed_z.value; + double speed = this->config.travel_speed_z.get_at(get_extruder_index(this->config, filament()->id())); if (speed == 0.) - speed = this->config.travel_speed.value; - + speed = this->config.travel_speed.get_at(get_extruder_index(this->config, filament()->id())); + if (tool_change && this->config.prime_tower_lift_speed.value>0) { + speed = this->config.prime_tower_lift_speed.value; // lift speed + } std::string output = "G17\n"; GCodeG2G3Formatter w(true); w.emit_z(z); @@ -520,7 +637,7 @@ std::string GCodeWriter::_spiral_travel_to_z(double z, const Vec2d &ij_offset, c w.emit_string(" P1 "); w.emit_f(speed * 60.0); w.emit_comment(GCodeWriter::full_gcode_comment, comment); - return output + w.string(); + return set_travel_acceleration() + output + w.string(); } bool GCodeWriter::will_move_z(double z) const @@ -544,8 +661,9 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std: { m_pos(0) = point(0); m_pos(1) = point(1); + if (!force_no_extrusion) - m_extruder->extrude(dE); + filament()->extrude(dE); //QDS: take plate offset into consider Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset }; @@ -553,10 +671,10 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std: GCodeG1Formatter w; w.emit_xy(point_on_plate); if (!force_no_extrusion) - w.emit_e(m_extruder->E()); + w.emit_e(filament()->E()); //QDS w.emit_comment(GCodeWriter::full_gcode_comment, comment); - return w.string(); + return set_extrude_acceleration() + w.string(); } //QDS: generate G2 or G3 extrude which moves by arc @@ -567,7 +685,7 @@ std::string GCodeWriter::extrude_arc_to_xy(const Vec2d& point, const Vec2d& cent m_pos(0) = point(0); m_pos(1) = point(1); if (!force_no_extrusion) - m_extruder->extrude(dE); + filament()->extrude(dE); Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset }; @@ -575,10 +693,10 @@ std::string GCodeWriter::extrude_arc_to_xy(const Vec2d& point, const Vec2d& cent w.emit_xy(point_on_plate); w.emit_ij(center_offset); if (!force_no_extrusion) - w.emit_e(m_extruder->E()); + w.emit_e(filament()->E()); //QDS w.emit_comment(GCodeWriter::full_gcode_comment, comment); - return w.string(); + return set_extrude_acceleration() + w.string(); } std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment, bool force_no_extrusion) @@ -586,38 +704,38 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std m_pos = point; m_lifted = 0; if (!force_no_extrusion) - m_extruder->extrude(dE); - + filament()->extrude(dE); + //QDS: take plate offset into consider Vec3d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset, point(2) }; GCodeG1Formatter w; w.emit_xyz(point_on_plate); if (!force_no_extrusion) - w.emit_e(m_extruder->E()); + w.emit_e(filament()->E()); //QDS w.emit_comment(GCodeWriter::full_gcode_comment, comment); - return w.string(); + return set_extrude_acceleration() + w.string(); } std::string GCodeWriter::retract(bool before_wipe) { - double factor = before_wipe ? m_extruder->retract_before_wipe() : 1.; + double factor = before_wipe ? filament()->retract_before_wipe() : 1.; assert(factor >= 0. && factor <= 1. + EPSILON); return this->_retract( - factor * m_extruder->retraction_length(), - factor * m_extruder->retract_restart_extra(), + factor * filament()->retraction_length(), + factor * filament()->retract_restart_extra(), "retract" ); } std::string GCodeWriter::retract_for_toolchange(bool before_wipe) { - double factor = before_wipe ? m_extruder->retract_before_wipe() : 1.; + double factor = before_wipe ? filament()->retract_before_wipe() : 1.; assert(factor >= 0. && factor <= 1. + EPSILON); return this->_retract( - factor * m_extruder->retract_length_toolchange(), - factor * m_extruder->retract_restart_extra_toolchange(), + factor * filament()->retract_length_toolchange(), + factor * filament()->retract_restart_extra_toolchange(), "retract for toolchange" ); } @@ -627,7 +745,7 @@ std::string GCodeWriter::_retract(double length, double restart_extra, const std std::string gcode; if (config.use_firmware_retraction) length = 1; - if (double dE = m_extruder->retract(length, restart_extra); dE != 0) { + if (double dE = filament()->retract(length, restart_extra); dE != 0) { //add firmware retraction if (config.use_firmware_retraction) { gcode = FLAVOR_IS(gcfMachinekit) ? "G22 ;retract" : "G10 ;retract \n"; @@ -635,8 +753,8 @@ std::string GCodeWriter::_retract(double length, double restart_extra, const std else { //QDS GCodeG1Formatter w; - w.emit_e(m_extruder->E()); - w.emit_f(m_extruder->retract_speed() * 60.); + w.emit_e(filament()->E()); + w.emit_f(filament()->retract_speed() * 60.); //QDS w.emit_comment(GCodeWriter::full_gcode_comment, comment); gcode = w.string(); @@ -655,8 +773,8 @@ std::string GCodeWriter::unretract() if (FLAVOR_IS(gcfMakerWare)) gcode = "M101 ; extruder on\n"; - - if (double dE = m_extruder->unretract(); dE != 0) { + + if (double dE = filament()->unretract(); dE != 0) { if (config.use_firmware_retraction) { gcode += FLAVOR_IS(gcfMachinekit) ? "G23 ;unretract \n" : "G11 ;unretract \n"; gcode += reset_e(); @@ -665,8 +783,8 @@ std::string GCodeWriter::unretract() //QDS // use G1 instead of G0 because G0 will blend the restart with the previous travel move GCodeG1Formatter w; - w.emit_e(m_extruder->E()); - w.emit_f(m_extruder->deretract_speed() * 60.); + w.emit_e(filament()->E()); + w.emit_f(filament()->deretract_speed() * 60.); //QDS w.emit_comment(GCodeWriter::full_gcode_comment, " ; unretract"); gcode += w.string(); @@ -676,34 +794,6 @@ std::string GCodeWriter::unretract() return gcode; } -/* If this method is called more than once before calling unlift(), - it will not perform subsequent lifts, even if Z was raised manually - (i.e. with travel_to_z()) and thus _lifted was reduced. */ -std::string GCodeWriter::lift(LiftType lift_type, bool spiral_vase) -{ - // check whether the above/below conditions are met - double target_lift = 0; - { - //QDS - double above = this->config.retract_lift_above.get_at(m_extruder->id()); - double below = this->config.retract_lift_below.get_at(m_extruder->id()); - if (m_pos.z() >= above && m_pos.z() <= below) - target_lift = this->config.z_hop.get_at(m_extruder->id()); - } - // QDS - if (m_lifted == 0 && m_to_lift == 0 && target_lift > 0) { - if (spiral_vase) { - m_lifted = target_lift; - return this->_travel_to_z(m_pos(2) + target_lift, "lift Z"); - } - else { - m_to_lift = target_lift; - m_to_lift_type = lift_type; - } - } - return ""; -} - std::string GCodeWriter::unlift() { std::string gcode; @@ -742,7 +832,7 @@ std::string GCodeWriter::set_fan(const GCodeFlavor gcode_flavor, unsigned int sp default: gcode << "M106 S" << 255.0 * speed / 100.0; break; } - if (GCodeWriter::full_gcode_comment) + if (GCodeWriter::full_gcode_comment) gcode << " ; enable fan"; gcode << "\n"; } @@ -803,6 +893,30 @@ void GCodeWriter::add_object_change_labels(std::string& gcode) add_object_start_labels(gcode); } +std::string GCodeWriter::set_extruder(unsigned int filament_id) +{ + auto filament_ext_it = Slic3r::lower_bound_by_predicate(m_filament_extruders.begin(), m_filament_extruders.end(), [filament_id](const Extruder &e) { return e.id() < filament_id; }); + unsigned int extruder_id = filament_ext_it->extruder_id(); + assert(filament_ext_it != m_filament_extruders.end() && filament_ext_it->id() == filament_id); + //TODO: optmize here, pass extruder_id to toolchange + return this->need_toolchange(filament_id) ? this->toolchange(filament_id) : ""; +} + +void GCodeWriter::init_extruder(unsigned int filament_id) +{ + if (m_curr_extruder_id == -1 && filament_id != -1) { + auto filament_extruder_iter = Slic3r::lower_bound_by_predicate(m_filament_extruders.begin(), m_filament_extruders.end(), [filament_id](const Extruder &e) { return e.id() < filament_id; }); + assert(filament_extruder_iter != m_filament_extruders.end() && filament_extruder_iter->id() == filament_id); + m_curr_extruder_id = filament_extruder_iter->extruder_id(); + m_curr_filament_extruder[m_curr_extruder_id] = &*filament_extruder_iter; + } +} + +bool GCodeWriter::need_toolchange(unsigned int filament_id)const +{ + return filament()==nullptr || filament()->id()!=filament_id; +} + void GCodeFormatter::emit_axis(const char axis, const double v, size_t digits) { assert(digits <= 9); static constexpr const std::array pow_10{1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000}; diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index 0e89f94..e1f8463 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -13,7 +13,7 @@ namespace Slic3r { enum class LiftType { NormalLift, - LazyLift, + SlopeLift, SpiralLift }; @@ -21,9 +21,10 @@ class GCodeWriter { public: GCodeConfig config; bool multiple_extruders; - - GCodeWriter() : - multiple_extruders(false), m_extruder(nullptr), + + GCodeWriter() : + multiple_extruders(false), m_curr_filament_extruder{ nullptr,nullptr }, + m_curr_extruder_id (-1), m_single_extruder_multi_material(false), m_last_acceleration(0), m_max_acceleration(0), m_last_jerk(0), m_max_jerk(0), @@ -32,18 +33,20 @@ public: m_to_lift(0), m_to_lift_type(LiftType::NormalLift) {} - Extruder* extruder() { return m_extruder; } - const Extruder* extruder() const { return m_extruder; } + Extruder* filament(size_t extruder_id) { assert(extruder_id < m_curr_filament_extruder.size()); return m_curr_filament_extruder[extruder_id]; } + const Extruder* filament(size_t extruder_id) const { assert(extruder_id < m_curr_filament_extruder.size()); return m_curr_filament_extruder[extruder_id]; } + Extruder* filament() { if (m_curr_extruder_id == -1) return nullptr; return m_curr_filament_extruder[m_curr_extruder_id]; } + const Extruder* filament() const { if(m_curr_extruder_id==-1) return nullptr; return m_curr_filament_extruder[m_curr_extruder_id]; } void apply_print_config(const PrintConfig &print_config); // Extruders are expected to be sorted in an increasing order. void set_extruders(std::vector extruder_ids); - const std::vector& extruders() const { return m_extruders; } - std::vector extruder_ids() const { - std::vector out; - out.reserve(m_extruders.size()); - for (const Extruder &e : m_extruders) - out.push_back(e.id()); + const std::vector& extruders() const { return m_filament_extruders; } + std::vector extruder_ids() const { + std::vector out; + out.reserve(m_filament_extruders.size()); + for (const Extruder &e : m_filament_extruders) + out.push_back(e.id()); return out; } std::string preamble(); @@ -51,20 +54,22 @@ public: std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1) const; std::string set_bed_temperature(int temperature, bool wait = false); std::string set_chamber_temperature(int temperature, bool wait = false); - std::string set_acceleration(unsigned int acceleration); + void set_acceleration(unsigned int acceleration); + void set_travel_acceleration(const std::vector& travel_accelerations); + void set_first_layer_travel_acceleration(const std::vector& travel_accelerations); + void set_first_layer(bool is_first_layer); std::string set_pressure_advance(double pa) const; std::string set_jerk_xy(double jerk); std::string reset_e(bool force = false); std::string update_progress(unsigned int num, unsigned int tot, bool allow_100 = false) const; // return false if this extruder was already selected - bool need_toolchange(unsigned int extruder_id) const - { return m_extruder == nullptr || m_extruder->id() != extruder_id; } - std::string set_extruder(unsigned int extruder_id) - { return this->need_toolchange(extruder_id) ? this->toolchange(extruder_id) : ""; } + bool need_toolchange(unsigned int filament_id) const; + std::string set_extruder(unsigned int filament_id); + void init_extruder(unsigned int filament_id); // Prefix of the toolchange G-code line, to be used by the CoolingBuffer to separate sections of the G-code // printed with the same extruder. std::string toolchange_prefix() const; - std::string toolchange(unsigned int extruder_id); + std::string toolchange(unsigned int filament_id); std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string()); double get_current_speed() { return m_current_speed; }; std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string()); @@ -78,7 +83,10 @@ public: std::string retract(bool before_wipe = false); std::string retract_for_toolchange(bool before_wipe = false); std::string unretract(); - std::string lift(LiftType lift_type = LiftType::NormalLift, bool spiral_vase = false); + // do lift instantly + std::string eager_lift(const LiftType type,bool tool_change = false); + // record a lift request, do realy lift in next travel + std::string lazy_lift(LiftType lift_type = LiftType::NormalLift, bool spiral_vase = false, bool tool_change=false); std::string unlift(); Vec3d get_position() const { return m_pos; } void set_position(Vec3d& in) { m_pos = in; } @@ -106,16 +114,23 @@ public: //QDS: void set_current_position_clear(bool clear) { m_is_current_pos_clear = clear; }; bool is_current_position_clear() const { return m_is_current_pos_clear; }; + void set_is_qdt_printer(bool is_qdt_printer) { m_is_qdt_printer = is_qdt_printer; }; //QDS: static const bool full_gcode_comment; //Radian threshold of slope for lazy lift and spiral lift; static const double slope_threshold; +private: + std::string set_extrude_acceleration(); + std::string set_travel_acceleration(); + std::string set_acceleration_impl(unsigned int acceleration); + private: // Extruders are sorted by their ID, so that binary search is possible. - std::vector m_extruders; + std::vector m_filament_extruders; bool m_single_extruder_multi_material; - Extruder* m_extruder; + std::vector m_curr_filament_extruder; + int m_curr_extruder_id; unsigned int m_last_acceleration; // Limit for setting the acceleration, to respect the machine limits set for the Marlin firmware. // If set to zero, the limit is not in action. @@ -140,11 +155,18 @@ private: double m_x_offset{ 0 }; double m_y_offset{ 0 }; double m_current_speed{ 0 }; + bool m_is_qdt_printer = false; + std::string m_gcode_label_objects_start; std::string m_gcode_label_objects_end; - std::string _travel_to_z(double z, const std::string &comment); - std::string _spiral_travel_to_z(double z, const Vec2d &ij_offset, const std::string &comment); + bool m_is_first_layer{false}; + unsigned int m_acceleration{0}; + std::vector m_travel_accelerations; // multi extruder, extruder size + std::vector m_first_layer_travel_accelerations; // multi extruder, extruder size + + std::string _travel_to_z(double z, const std::string &comment,bool tool_change=false); + std::string _spiral_travel_to_z(double z, const Vec2d &ij_offset, const std::string &comment, bool tool_change = false); std::string _retract(double length, double restart_extra, const std::string &comment); }; diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 93fa586..249cc69 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -459,6 +459,13 @@ const Vec3d &Transformation::get_rotation_by_quaternion() const Transform3d Transformation::get_rotation_matrix() const { return extract_rotation_matrix(m_matrix); } +void Transformation::set_rotation_matrix(const Transform3d &rot_mat) +{ + const Vec3d offset = get_offset(); + m_matrix = rot_mat * extract_scale(m_matrix); + m_matrix.translation() = offset; +} + void Transformation::set_rotation(const Vec3d &rotation) { const Vec3d offset = get_offset(); @@ -466,20 +473,6 @@ void Transformation::set_rotation(const Vec3d &rotation) m_matrix.translation() = offset; } -void Transformation::set_rotation(Axis axis, double rotation) -{ - rotation = angle_to_0_2PI(rotation); - if (is_approx(std::abs(rotation), 2.0 * double(PI))) rotation = 0.0; - - auto [curr_rotation, scale] = extract_rotation_scale(m_matrix); - Vec3d angles = extract_rotation(curr_rotation); - angles[axis] = rotation; - - const Vec3d offset = get_offset(); - m_matrix = rotation_transform(angles) * scale; - m_matrix.translation() = offset; -} - const Vec3d &Transformation::get_scaling_factor() const { const Transform3d scale = extract_scale(m_matrix); diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 265bdfd..cc6191c 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -399,9 +399,9 @@ public: double get_rotation(Axis axis) const { return get_rotation()[axis]; } Transform3d get_rotation_matrix() const; + void set_rotation_matrix(const Transform3d &rot_mat); void set_rotation(const Vec3d &rotation); - void set_rotation(Axis axis, double rotation); const Vec3d &get_scaling_factor() const; double get_scaling_factor(Axis axis) const { return get_scaling_factor()[axis]; } diff --git a/src/libslic3r/Geometry/VoronoiUtils.cpp b/src/libslic3r/Geometry/VoronoiUtils.cpp index 1e94363..6917f5e 100644 --- a/src/libslic3r/Geometry/VoronoiUtils.cpp +++ b/src/libslic3r/Geometry/VoronoiUtils.cpp @@ -7,6 +7,7 @@ namespace Slic3r::Geometry { +using PolygonsSegmentIndexIt = std::vector::iterator; using PolygonsSegmentIndexConstIt = std::vector::const_iterator; using LinesIt = Lines::iterator; using ColoredLinesIt = ColoredLines::iterator; @@ -29,6 +30,7 @@ template SegmentCellRange VoronoiUtils::compute_segment_cell_range(const template SegmentCellRange VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); template Points VoronoiUtils::discretize_parabola(const Point &, const Arachne::PolygonsSegmentIndex &, const Point &, const Point &, coord_t, float); template Arachne::PolygonsPointIndex VoronoiUtils::get_source_point_index(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); +template Arachne::PolygonsPointIndex VoronoiUtils::get_source_point_index(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexIt, PolygonsSegmentIndexIt); template typename boost::polygon::enable_if< diff --git a/src/libslic3r/Interlocking/InterlockingGenerator.cpp b/src/libslic3r/Interlocking/InterlockingGenerator.cpp new file mode 100644 index 0000000..a52c92d --- /dev/null +++ b/src/libslic3r/Interlocking/InterlockingGenerator.cpp @@ -0,0 +1,333 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "ClipperUtils.hpp" +#include "InterlockingGenerator.hpp" +#include "Layer.hpp" + +namespace std { +template<> struct hash +{ + size_t operator()(const Slic3r::GridPoint3& pp) const noexcept + { + static int prime = 31; + int result = 89; + result = static_cast(result * prime + pp.x()); + result = static_cast(result * prime + pp.y()); + result = static_cast(result * prime + pp.z()); + return static_cast(result); + } +}; +} // namespace std + + +namespace Slic3r { + +void InterlockingGenerator::generate_interlocking_structure(PrintObject* print_object) +{ + const auto& config = print_object->config(); + if (!config.interlocking_beam) { + return; + } + + const float rotation = Geometry::deg2rad(config.interlocking_orientation.value); + const coord_t beam_layer_count = config.interlocking_beam_layer_count; + const int interface_depth = config.interlocking_depth; + const int boundary_avoidance = config.interlocking_boundary_avoidance; + const coord_t beam_width = scaled(config.interlocking_beam_width.value); + + const DilationKernel interface_dilation(GridPoint3(interface_depth, interface_depth, interface_depth), DilationKernel::Type::PRISM); + + const bool air_filtering = boundary_avoidance > 0; + const DilationKernel air_dilation(GridPoint3(boundary_avoidance, boundary_avoidance, boundary_avoidance), DilationKernel::Type::PRISM); + + const coord_t cell_width = beam_width + beam_width; + const Vec3crd cell_size(cell_width, cell_width, 2 * beam_layer_count); + + for (size_t region_a_index = 0; region_a_index < print_object->num_printing_regions(); region_a_index++) { + const PrintRegion& region_a = print_object->printing_region(region_a_index); + const auto extruder_nr_a = region_a.extruder(FlowRole::frExternalPerimeter); + + for (size_t region_b_index = region_a_index + 1; region_b_index < print_object->num_printing_regions(); region_b_index++) { + const PrintRegion& region_b = print_object->printing_region(region_b_index); + const auto extruder_nr_b = region_b.extruder(FlowRole::frExternalPerimeter); + if (extruder_nr_a == extruder_nr_b) { + continue; + } + + InterlockingGenerator gen(*print_object, region_a_index, region_b_index, beam_width, boundary_avoidance, rotation, cell_size, beam_layer_count, + interface_dilation, air_dilation, air_filtering); + gen.generateInterlockingStructure(); + } + } +} + +std::pair InterlockingGenerator::growBorderAreasPerpendicular(const ExPolygons& a, const ExPolygons& b, const coord_t& detect) const +{ + const coord_t min_line = + std::min(print_object.printing_region(region_a_index).flow(print_object, frExternalPerimeter, 0.1).scaled_width(), + print_object.printing_region(region_b_index).flow(print_object, frExternalPerimeter, 0.1).scaled_width()); + + const ExPolygons total_shrunk = offset_ex(union_ex(offset_ex(a, min_line), offset_ex(b, min_line)), 2 * -min_line); + + ExPolygons from_border_a = diff_ex(a, total_shrunk); + ExPolygons from_border_b = diff_ex(b, total_shrunk); + + ExPolygons temp_a, temp_b; + for (coord_t i = 0; i < (detect / min_line) + 2; ++i) { + temp_a = offset_ex(from_border_a, min_line); + temp_b = offset_ex(from_border_b, min_line); + from_border_a = diff_ex(temp_a, temp_b); + from_border_b = diff_ex(temp_b, temp_a); + } + + return {from_border_a, from_border_b}; +} + +void InterlockingGenerator::handleThinAreas(const std::unordered_set& has_all_meshes) const +{ + const coord_t number_of_beams_detect = boundary_avoidance; + const coord_t number_of_beams_expand = boundary_avoidance - 1; + constexpr coord_t rounding_errors = 5; + + const coord_t max_beam_width = beam_width; + const coord_t detect = (max_beam_width * number_of_beams_detect) + rounding_errors; + const coord_t expand = (max_beam_width * number_of_beams_expand) + rounding_errors; + const coord_t close_gaps = + std::min(print_object.printing_region(region_a_index).flow(print_object, frExternalPerimeter, 0.1).scaled_width(), + print_object.printing_region(region_b_index).flow(print_object, frExternalPerimeter, 0.1).scaled_width()) / 4; + + // Make an inclusionary polygon, to only actually handle thin areas near actual microstructures (so not in skin for example). + std::vector near_interlock_per_layer; + near_interlock_per_layer.assign(print_object.layer_count(), Polygons()); + for (const auto& cell : has_all_meshes) { + const auto bottom_corner = vu.toLowerCorner(cell); + for (coord_t layer_nr = bottom_corner.z(); + layer_nr < bottom_corner.z() + cell_size.z() && layer_nr < static_cast(near_interlock_per_layer.size()); ++layer_nr) { + near_interlock_per_layer[static_cast(layer_nr)].push_back(vu.toPolygon(cell)); + } + } + for (auto& near_interlock : near_interlock_per_layer) { + near_interlock = offset(union_(closing(near_interlock, rounding_errors)), detect); + polygons_rotate(near_interlock, rotation); + } + + // Only alter layers when they are present in both meshes, zip should take care if that. + for (size_t layer_nr = 0; layer_nr < print_object.layer_count(); layer_nr++){ + auto layer = print_object.get_layer(layer_nr); + ExPolygons polys_a = to_expolygons(layer->get_region(region_a_index)->slices.surfaces); + ExPolygons polys_b = to_expolygons(layer->get_region(region_b_index)->slices.surfaces); + + const auto [from_border_a, from_border_b] = growBorderAreasPerpendicular(polys_a, polys_b, detect); + + // Get the areas of each mesh that are _not_ thin (large), by performing a morphological open. + const ExPolygons large_a = opening_ex(polys_a, detect); + const ExPolygons large_b = opening_ex(polys_b, detect); + + // Derive the area that the thin areas need to expand into (so the added areas to the thin strips) from the information we already have. + const ExPolygons thin_expansion_a = + offset_ex(intersection_ex(intersection_ex(intersection_ex(large_b, offset_ex(diff_ex(polys_a, large_a), expand)), + near_interlock_per_layer[layer_nr]), + from_border_a), + rounding_errors); + const ExPolygons thin_expansion_b = + offset_ex(intersection_ex(intersection_ex(intersection_ex(large_a, offset_ex(diff_ex(polys_b, large_b), expand)), + near_interlock_per_layer[layer_nr]), + from_border_b), + rounding_errors); + + // Expanded thin areas of the opposing polygon should 'eat into' the larger areas of the polygon, + // and conversely, add the expansions to their own thin areas. + layer->get_region(region_a_index)->slices.set(closing_ex(diff_ex(union_ex(polys_a, thin_expansion_a), thin_expansion_b), close_gaps), stInternal); + layer->get_region(region_b_index)->slices.set(closing_ex(diff_ex(union_ex(polys_b, thin_expansion_b), thin_expansion_a), close_gaps), stInternal); + } +} + +void InterlockingGenerator::generateInterlockingStructure() const +{ + std::vector> voxels_per_mesh = getShellVoxels(interface_dilation); + + std::unordered_set& has_any_mesh = voxels_per_mesh[0]; + std::unordered_set& has_all_meshes = voxels_per_mesh[1]; + has_any_mesh.merge(has_all_meshes); // perform union and intersection simultaneously. Cannibalizes voxels_per_mesh + + if (has_all_meshes.empty()) { + return; + } + + const std::vector layer_regions = computeUnionedVolumeRegions(); + + if (air_filtering) { + std::unordered_set air_cells; + addBoundaryCells(layer_regions, air_dilation, air_cells); + + for (const GridPoint3& p : air_cells) { + has_all_meshes.erase(p); + } + + handleThinAreas(has_all_meshes); + } + + applyMicrostructureToOutlines(has_all_meshes, layer_regions); +} + +std::vector> InterlockingGenerator::getShellVoxels(const DilationKernel& kernel) const +{ + std::vector> voxels_per_mesh(2); + + // mark all cells which contain some boundary + for (size_t region_idx = 0; region_idx < 2; region_idx++) + { + const size_t region = (region_idx == 0) ? region_a_index : region_b_index; + std::unordered_set& mesh_voxels = voxels_per_mesh[region_idx]; + + std::vector rotated_polygons_per_layer(print_object.layer_count()); + for (size_t layer_nr = 0; layer_nr < print_object.layer_count(); layer_nr++) + { + auto layer = print_object.get_layer(layer_nr); + rotated_polygons_per_layer[layer_nr] = to_expolygons(layer->get_region(region)->slices.surfaces); + expolygons_rotate(rotated_polygons_per_layer[layer_nr], rotation); + } + + addBoundaryCells(rotated_polygons_per_layer, kernel, mesh_voxels); + } + + return voxels_per_mesh; +} + +void InterlockingGenerator::addBoundaryCells(const std::vector& layers, + const DilationKernel& kernel, + std::unordered_set& cells) const +{ + auto voxel_emplacer = [&cells](GridPoint3 p) { + if (p.z() < 0) { + return true; + } + cells.emplace(p); + return true; + }; + + for (size_t layer_nr = 0; layer_nr < layers.size(); layer_nr++) { + const coord_t z = static_cast(layer_nr); + vu.walkDilatedPolygons(layers[layer_nr], z, kernel, voxel_emplacer); + ExPolygons skin = layers[layer_nr]; + if (layer_nr > 0) { + skin = xor_ex(skin, layers[layer_nr - 1]); + } + skin = opening_ex(skin, cell_size.x() / 2.f); // remove superfluous small areas, which would anyway be included because of walkPolygons + vu.walkDilatedAreas(skin, z, kernel, voxel_emplacer); + } +} + +std::vector InterlockingGenerator::computeUnionedVolumeRegions() const +{ + const size_t max_layer_count = print_object.layer_count() + + 1; // introduce ghost layer on top for correct skin computation of topmost layer. + std::vector layer_regions(max_layer_count); + + for (size_t layer_nr = 0; layer_nr < max_layer_count - 1; layer_nr++) { + auto& layer_region = layer_regions[static_cast(layer_nr)]; + for (size_t region_idx : {region_a_index, region_b_index}) { + auto layer = print_object.get_layer(layer_nr); + expolygons_append(layer_region, to_expolygons(layer->get_region(region_idx)->slices.surfaces)); + } + layer_region = closing_ex(layer_region, ignored_gap_); // Morphological close to merge meshes into single volume + expolygons_rotate(layer_region, rotation); + } + return layer_regions; +} + +std::vector> InterlockingGenerator::generateMicrostructure() const +{ + std::vector> cell_area_per_mesh_per_layer; + cell_area_per_mesh_per_layer.resize(2); + cell_area_per_mesh_per_layer[0].resize(2); + //const coord_t beam_w_sum = beam_width + beam_width; + //const coord_t middle = cell_size.x() * beam_width / beam_w_sum; + const coord_t middle = cell_size.x() / 2; + const coord_t width[2] = {middle, cell_size.x() - middle}; + for (size_t mesh_idx : {0ul, 1ul}) { + Point offset(mesh_idx ? middle : 0, 0); + Point area_size(width[mesh_idx], cell_size.y()); + + Polygon poly; + poly.append(offset); + poly.append(offset + Point(area_size.x(), 0)); + poly.append(offset + area_size); + poly.append(offset + Point(0, area_size.y())); + cell_area_per_mesh_per_layer[0][mesh_idx].emplace_back(poly); + } + cell_area_per_mesh_per_layer[1] = cell_area_per_mesh_per_layer[0]; + for (ExPolygons& polys : cell_area_per_mesh_per_layer[1]) { + for (ExPolygon& poly : polys) { + for (Point& p : poly.contour) { + std::swap(p.x(), p.y()); + } + } + } + return cell_area_per_mesh_per_layer; +} + +void InterlockingGenerator::applyMicrostructureToOutlines(const std::unordered_set& cells, + const std::vector& layer_regions) const +{ + std::vector> cell_area_per_mesh_per_layer = generateMicrostructure(); + + const float unapply_rotation = -rotation; + const size_t max_layer_count = print_object.layer_count(); + + std::vector structure_per_layer[2]; // for each mesh the structure on each layer + + // Every `beam_layer_count` number of layers are combined to an interlocking beam layer + // to store these we need ceil(max_layer_count / beam_layer_count) of these layers + // the formula is rewritten as (max_layer_count + beam_layer_count - 1) / beam_layer_count, so it works for integer division + size_t num_interlocking_layers = (max_layer_count + static_cast(beam_layer_count) - 1ul) / + static_cast(beam_layer_count); + structure_per_layer[0].resize(num_interlocking_layers); + structure_per_layer[1].resize(num_interlocking_layers); + + // Only compute cell structure for half the layers, because since our beams are two layers high, every odd layer of the structure will + // be the same as the layer below. + for (const GridPoint3& grid_loc : cells) { + Vec3crd bottom_corner = vu.toLowerCorner(grid_loc); + for (size_t mesh_idx = 0; mesh_idx < 2; mesh_idx++) { + for (size_t layer_nr = bottom_corner.z(); layer_nr < bottom_corner.z() + cell_size.z() && layer_nr < max_layer_count; + layer_nr += beam_layer_count) { + ExPolygons areas_here = cell_area_per_mesh_per_layer[static_cast(layer_nr / beam_layer_count) % + cell_area_per_mesh_per_layer.size()][mesh_idx]; + for (auto & here : areas_here) { + here.translate(bottom_corner.x(), bottom_corner.y()); + } + expolygons_append(structure_per_layer[mesh_idx][static_cast(layer_nr / beam_layer_count)], areas_here); + } + } + } + + for (size_t mesh_idx = 0; mesh_idx < 2; mesh_idx++) { + for (size_t layer_nr = 0; layer_nr < structure_per_layer[mesh_idx].size(); layer_nr++) { + ExPolygons& layer_structure = structure_per_layer[mesh_idx][layer_nr]; + layer_structure = union_ex(layer_structure); + expolygons_rotate(layer_structure, unapply_rotation); + } + } + + for (size_t region_idx = 0; region_idx < 2; region_idx++) { + const size_t region = (region_idx == 0) ? region_a_index : region_b_index; + for (size_t layer_nr = 0; layer_nr < max_layer_count; layer_nr++) { + ExPolygons layer_outlines = layer_regions[layer_nr]; + expolygons_rotate(layer_outlines, unapply_rotation); + + const ExPolygons areas_here = intersection_ex(structure_per_layer[region_idx][layer_nr / static_cast(beam_layer_count)], layer_outlines); + const ExPolygons& areas_other = structure_per_layer[!region_idx][layer_nr / static_cast(beam_layer_count)]; + + auto layer = print_object.get_layer(layer_nr); + auto& slices = layer->get_region(region)->slices; + ExPolygons polys = to_expolygons(slices.surfaces); + slices.set(union_ex(diff_ex(polys, areas_other), // reduce layer areas inward with beams from other mesh + areas_here) // extend layer areas outward with newly added beams + , stInternal); + } + } +} + +} // namespace Slic3r diff --git a/src/libslic3r/Interlocking/InterlockingGenerator.hpp b/src/libslic3r/Interlocking/InterlockingGenerator.hpp new file mode 100644 index 0000000..4e82ea3 --- /dev/null +++ b/src/libslic3r/Interlocking/InterlockingGenerator.hpp @@ -0,0 +1,172 @@ +// Copyright (c) 2022 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef INTERLOCKING_GENERATOR_HPP +#define INTERLOCKING_GENERATOR_HPP + +#include "../Print.hpp" +#include "VoxelUtils.hpp" + +namespace Slic3r { + +/*! + * Class for generating an interlocking structure between two adjacent models of a different extruder. + * + * The structure consists of horizontal beams of the two materials interlaced. + * In the z direction the direction of these beams is alternated with 90*. + * + * Example with two materials # and O + * Even beams: Odd beams: + * ###### ##OO##OO + * OOOOOO ##OO##OO + * ###### ##OO##OO + * OOOOOO ##OO##OO + * + * One material of a single cell of the structure looks like this: + * .-*-. + * .-* *-. + * |*-. *-. + * | *-. *-. + * .-* *-. *-. *-. + * .-* *-. *-. .-*| + * .-* .-* *-. *-.-* | + * |*-. .-* .-* *-. | .-* + * | *-.-* .-* *-|-* + * *-. | .-* + * *-|-* + * + * We set up a voxel grid of (2*beam_w,2*beam_w,2*beam_h) and mark all the voxels which contain both meshes. + * We then remove all voxels which also contain air, so that the interlocking pattern will not be visible from the outside. + * We then generate and combine the polygons for each voxel and apply those areas to the outlines ofthe meshes. + */ +class InterlockingGenerator +{ +public: + /*! + * Generate an interlocking structure between each two adjacent meshes. + */ + static void generate_interlocking_structure(PrintObject* print_object); + +private: + /*! + * Generate an interlocking structure between two meshes + */ + void generateInterlockingStructure() const; + + /*! + * Private class for storing some variables used in the computation of the interlocking structure between two meshes. + * \param region_a_index The first region + * \param region_b_index The second region + * \param rotation The angle by which to rotate the interlocking pattern + * \param cell_size The size of a voxel cell in (coord_t, coord_t, layer_count) + * \param beam_layer_count The number of layers for the height of the beams + * \param interface_dilation The thicknening kernel for the interface + * \param air_dilation The thickening kernel applied to air so that cells near the outside of the model won't be generated + * \param air_filtering Whether to fully remove all of the interlocking cells which would be visible on the outside (i.e. touching air). + * If no air filtering then those cells will be cut off in the middle of a beam. + */ + InterlockingGenerator(PrintObject& print_object, + const size_t region_a_index, + const size_t region_b_index, + const coord_t beam_width, + const coord_t boundary_avoidance, + const float rotation, + const Vec3crd& cell_size, + const coord_t beam_layer_count, + const DilationKernel& interface_dilation, + const DilationKernel& air_dilation, + const bool air_filtering) + : print_object(print_object) + , region_a_index(region_a_index) + , region_b_index(region_b_index) + , beam_width(beam_width) + , boundary_avoidance(boundary_avoidance) + , vu(cell_size) + , rotation(rotation) + , cell_size(cell_size) + , beam_layer_count(beam_layer_count) + , interface_dilation(interface_dilation) + , air_dilation(air_dilation) + , air_filtering(air_filtering) + {} + + /*! Given two polygons, return the parts that border on air, and grow 'perpendicular' up to 'detect' distance. + * + * \param a The first polygon. + * \param b The second polygon. + * \param detec The expand distance. (Not equal to offset, but a series of small offsets and differences). + * \return A pair of polygons that repressent the 'borders' of a and b, but expanded 'perpendicularly'. + */ + std::pair growBorderAreasPerpendicular(const ExPolygons& a, const ExPolygons& b, const coord_t& detect) const; + + /*! Special handling for thin strips of material. + * + * Expand the meshes into each other where they need it, namely when a thin strip of material needs to be attached. + * \param has_all_meshes Only do this special handling if there's actually microstructure nearby that needs to be adhered to. + */ + void handleThinAreas(const std::unordered_set& has_all_meshes) const; + + /*! + * Compute the voxels overlapping with the shell of both models. + * This includes the walls, but also top/bottom skin. + * + * \param kernel The dilation kernel to give the returned voxel shell more thickness + * \return The shell voxels for mesh a and those for mesh b + */ + std::vector> getShellVoxels(const DilationKernel& kernel) const; + + /*! + * Compute the voxels overlapping with the shell of some layers. + * This includes the walls, but also top/bottom skin. + * + * \param layers The layer outlines for which to compute the shell voxels + * \param kernel The dilation kernel to give the returned voxel shell more thickness + * \param[out] cells The output cells which elong to the shell + */ + void addBoundaryCells(const std::vector& layers, const DilationKernel& kernel, std::unordered_set& cells) const; + + /*! + * Compute the regions occupied by both models. + * + * A morphological close is performed so that we don't register small gaps between the two models as being separate. + * \return layer_regions The computed layer regions + */ + std::vector computeUnionedVolumeRegions() const; + + /*! + * Generate the polygons for the beams of a single cell + * \return cell_area_per_mesh_per_layer The output polygons for each beam + */ + std::vector> generateMicrostructure() const; + + /*! + * Change the outlines of the meshes with the computed interlocking structure. + * + * \param cells The cells where we want to apply the interlocking structure. + * \param layer_regions The total volume of the two meshes combined (and small gaps closed) + */ + void applyMicrostructureToOutlines(const std::unordered_set& cells, const std::vector& layer_regions) const; + + static const coord_t ignored_gap_ = 100u; //!< Distance between models to be considered next to each other so that an interlocking structure will be generated there + + PrintObject& print_object; + const size_t region_a_index; + const size_t region_b_index; + const coord_t beam_width; + const coord_t boundary_avoidance; + + const VoxelUtils vu; + + const float rotation; + const Vec3crd cell_size; + const coord_t beam_layer_count; + const DilationKernel interface_dilation; + const DilationKernel air_dilation; + // Whether to fully remove all of the interlocking cells which would be visible on the outside. If no air filtering then those cells + // will be cut off midway in a beam. + const bool air_filtering; +}; + +} // namespace Slic3r + +#endif // INTERLOCKING_GENERATOR_HPP diff --git a/src/libslic3r/Interlocking/VoxelUtils.cpp b/src/libslic3r/Interlocking/VoxelUtils.cpp new file mode 100644 index 0000000..ed01223 --- /dev/null +++ b/src/libslic3r/Interlocking/VoxelUtils.cpp @@ -0,0 +1,219 @@ +// Copyright (c) 2022 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "VoxelUtils.hpp" +#include "../Geometry.hpp" +#include "../Fill/FillRectilinear.hpp" +#include "../Surface.hpp" + +namespace Slic3r +{ + +DilationKernel::DilationKernel(GridPoint3 kernel_size, DilationKernel::Type type) + : kernel_size_(kernel_size) + , type_(type) +{ + coord_t mult = kernel_size.x() * kernel_size.y() * kernel_size.z(); // multiplier for division to avoid rounding and to avoid use of floating point numbers + relative_cells_.reserve(mult); + GridPoint3 half_kernel = kernel_size / 2; + + GridPoint3 start = -half_kernel; + GridPoint3 end = kernel_size - half_kernel; + for (coord_t x = start.x(); x < end.x(); x++) + { + for (coord_t y = start.y(); y < end.y(); y++) + { + for (coord_t z = start.z(); z < end.z(); z++) + { + GridPoint3 current(x, y, z); + if (type != Type::CUBE) + { + GridPoint3 limit((x < 0) ? start.x() : end.x() - 1, (y < 0) ? start.y() : end.y() - 1, (z < 0) ? start.z() : end.z() - 1); + if (limit.x() == 0) + limit.x() = 1; + if (limit.y() == 0) + limit.y() = 1; + if (limit.z() == 0) + limit.z() = 1; + const GridPoint3 rel_dists = (mult * current).array() / limit.array(); + if ((type == Type::DIAMOND && rel_dists.x() + rel_dists.y() + rel_dists.z() > mult) || (type == Type::PRISM && rel_dists.x() + rel_dists.y() > mult)) + { + continue; // don't consider this cell + } + } + relative_cells_.emplace_back(x, y, z); + } + } + } +} + +bool VoxelUtils::walkLine(Vec3crd start, Vec3crd end, const std::function& process_cell_func) const +{ + Vec3crd diff = end - start; + + const GridPoint3 start_cell = toGridPoint(start); + const GridPoint3 end_cell = toGridPoint(end); + if (start_cell == end_cell) + { + return process_cell_func(start_cell); + } + + Vec3crd current_cell = start_cell; + while (true) + { + bool continue_ = process_cell_func(current_cell); + + if (! continue_) + { + return false; + } + + int stepping_dim = -1; // dimension in which the line next exits the current cell + double percentage_along_line = std::numeric_limits::max(); + for (int dim = 0; dim < 3; dim++) + { + if (diff[dim] == 0) + { + continue; + } + coord_t crossing_boundary = toLowerCoord(current_cell[dim], dim) + (diff[dim] > 0) * cell_size_[dim]; + double percentage_along_line_here = (crossing_boundary - start[dim]) / static_cast(diff[dim]); + if (percentage_along_line_here < percentage_along_line) + { + percentage_along_line = percentage_along_line_here; + stepping_dim = dim; + } + } + assert(stepping_dim != -1); + if (percentage_along_line > 1.0) + { + // next cell is beyond the end + return true; + } + current_cell[stepping_dim] += (diff[stepping_dim] > 0) ? 1 : -1; + } + return true; +} + + +bool VoxelUtils::walkPolygons(const ExPolygon& polys, coord_t z, const std::function& process_cell_func) const +{ + for (const Polygon& poly : to_polygons(polys)) + { + Point last = poly.back(); + for (Point p : poly) + { + bool continue_ = walkLine(Vec3crd(last.x(), last.y(), z), Vec3crd(p.x(), p.y(), z), process_cell_func); + if (! continue_) + { + return false; + } + last = p; + } + } + return true; +} + +bool VoxelUtils::walkDilatedPolygons(const ExPolygon& polys, coord_t z, const DilationKernel& kernel, const std::function& process_cell_func) const +{ + ExPolygon translated = polys; + GridPoint3 k = kernel.kernel_size_; + k.x() %= 2; + k.y() %= 2; + k.z() %= 2; + const Vec3crd translation = (Vec3crd(1, 1, 1) - k).array() * cell_size_.array() / 2; + if (translation.x() && translation.y()) + { + translated.translate(Point(translation.x(), translation.y())); + } + return walkPolygons(translated, z + translation.z(), dilate(kernel, process_cell_func)); +} + +bool VoxelUtils::walkAreas(const ExPolygon& polys, coord_t z, const std::function& process_cell_func) const +{ + ExPolygon translated = polys; + const Vec3crd translation = -cell_size_ / 2; // offset half a cell so that the dots of spreadDotsArea are centered on the middle of the cell isntead of the lower corners. + if (translation.x() && translation.y()) + { + translated.translate(Point(translation.x(), translation.y())); + } + return _walkAreas(translated, z, process_cell_func); +} + +static Points spreadDotsArea(const ExPolygon& polygons, Point grid_size) +{ + std::unique_ptr filler(Fill::new_from_type(ipAlignedRectilinear)); + filler->angle = Geometry::deg2rad(90.f); + filler->spacing = unscaled(grid_size.x()); + filler->bounding_box = get_extents(polygons); + + FillParams params; + params.density = 1.f; + params.anchor_length_max = 0; + + Surface surface(stInternal, polygons); + auto polylines = filler->fill_surface(&surface, params); + + Points result; + for (const Polyline& line : polylines) { + assert(line.size() == 2); + Point a = line[0]; + Point b = line[1]; + assert(a.x() == b.x()); + if (a.y() > b.y()) { + std::swap(a, b); + } + for (coord_t y = a.y() - (a.y() % grid_size.y()) - grid_size.y(); y < b.y(); y += grid_size.y()) { + if (y < a.y()) + continue; + result.emplace_back(a.x(), y); + } + } + + return result; +} + +bool VoxelUtils::_walkAreas(const ExPolygon& polys, coord_t z, const std::function& process_cell_func) const +{ + Points skin_points = spreadDotsArea(polys, Point(cell_size_.x(), cell_size_.y())); + for (Point p : skin_points) + { + bool continue_ = process_cell_func(toGridPoint(Vec3crd(p.x() + cell_size_.x() / 2, p.y() + cell_size_.y() / 2, z))); + if (! continue_) + { + return false; + } + } + return true; +} + +bool VoxelUtils::walkDilatedAreas(const ExPolygon& polys, coord_t z, const DilationKernel& kernel, const std::function& process_cell_func) const +{ + ExPolygon translated = polys; + GridPoint3 k = kernel.kernel_size_; + k.x() %= 2; + k.y() %= 2; + k.z() %= 2; + const Vec3crd translation = (Vec3crd(1, 1, 1) - k).array() * cell_size_.array() / 2 // offset half a cell when using an even kernel + - cell_size_.array() / 2; // offset half a cell so that the dots of spreadDotsArea are centered on the middle of the cell isntead of the lower corners. + if (translation.x() && translation.y()) + { + translated.translate(Point(translation.x(), translation.y())); + } + return _walkAreas(translated, z + translation.z(), dilate(kernel, process_cell_func)); +} + +std::function VoxelUtils::dilate(const DilationKernel& kernel, const std::function& process_cell_func) const +{ + return [&process_cell_func, &kernel](GridPoint3 loc) + { + for (const GridPoint3& rel : kernel.relative_cells_) + { + bool continue_ = process_cell_func(loc + rel); + if (! continue_) + return false; + } + return true; + }; +} +} // namespace cura diff --git a/src/libslic3r/Interlocking/VoxelUtils.hpp b/src/libslic3r/Interlocking/VoxelUtils.hpp new file mode 100644 index 0000000..8496b3b --- /dev/null +++ b/src/libslic3r/Interlocking/VoxelUtils.hpp @@ -0,0 +1,212 @@ +// Copyright (c) 2022 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_VOXEL_UTILS_H +#define UTILS_VOXEL_UTILS_H + +#include + +#include "../Polygon.hpp" +#include "../ExPolygon.hpp" + +namespace Slic3r +{ + +using GridPoint3 = Vec3crd; + +/*! + * Class for holding the relative positiongs wrt a reference cell on which to perform a dilation. + */ +struct DilationKernel +{ + /*! + * A cubic kernel checks all voxels in a cube around a reference voxel. + * _____ + * |\ ___\ + * | | | + * \|____| + * + * A diamond kernel uses a manhattan distance to create a diamond shape around a reference voxel. + * /|\ + * /_|_\ + * \ | / + * \|/ + * + * A prism kernel is diamond in XY, but extrudes straight in Z around a reference voxel. + * / \ + * / \ + * |\ /| + * | \ / | + * | | | + * \ | / + * \|/ + */ + enum class Type + { + CUBE, + DIAMOND, + PRISM + }; + GridPoint3 kernel_size_; //!< Size of the kernel in number of voxel cells + Type type_; + std::vector relative_cells_; //!< All offset positions relative to some reference cell which is to be dilated + + DilationKernel(GridPoint3 kernel_size, Type type); +}; + +/*! + * Utility class for walking over a 3D voxel grid. + * + * Contains the math for intersecting voxels with lines, polgons, areas, etc. + */ +class VoxelUtils +{ +public: + using grid_coord_t = coord_t; + + Vec3crd cell_size_; + + VoxelUtils(Vec3crd cell_size) + : cell_size_(cell_size) + { + } + + /*! + * Process voxels which a line segment crosses. + * + * \param start Start point of the line + * \param end End point of the line + * \param process_cell_func Function to perform on each cell the line crosses + * \return Whether executing was stopped short as indicated by the \p cell_processing_function + */ + bool walkLine(Vec3crd start, Vec3crd end, const std::function& process_cell_func) const; + + /*! + * Process voxels which the line segments of a polygon crosses. + * + * \warning Voxels may be processed multiple times! + * + * \param polys The polygons to walk + * \param z The height at which the polygons occur + * \param process_cell_func Function to perform on each voxel cell + * \return Whether executing was stopped short as indicated by the \p cell_processing_function + */ + bool walkPolygons(const ExPolygon& polys, coord_t z, const std::function& process_cell_func) const; + + /*! + * Process voxels near the line segments of a polygon. + * For each voxel the polygon crosses we process each of the offset voxels according to the kernel. + * + * \warning Voxels may be processed multiple times! + * + * \param polys The polygons to walk + * \param z The height at which the polygons occur + * \param process_cell_func Function to perform on each voxel cell + * \return Whether executing was stopped short as indicated by the \p cell_processing_function + */ + bool walkDilatedPolygons(const ExPolygon& polys, coord_t z, const DilationKernel& kernel, const std::function& process_cell_func) const; + bool walkDilatedPolygons(const ExPolygons& polys, coord_t z, const DilationKernel& kernel, const std::function& process_cell_func) const + { + for (const auto & poly : polys) { + if (!walkDilatedPolygons(poly, z, kernel, process_cell_func)) { + return false; + } + } + + return true; + } + +private: + /*! + * \warning the \p polys is assumed to be translated by half the cell_size in xy already + */ + bool _walkAreas(const ExPolygon& polys, coord_t z, const std::function& process_cell_func) const; + +public: + /*! + * Process all voxels inside the area of a polygons object. + * + * \warning The voxels along the area are not processed. Thin areas might not process any voxels at all. + * + * \param polys The area to fill + * \param z The height at which the polygons occur + * \param process_cell_func Function to perform on each voxel cell + * \return Whether executing was stopped short as indicated by the \p cell_processing_function + */ + bool walkAreas(const ExPolygon& polys, coord_t z, const std::function& process_cell_func) const; + + /*! + * Process all voxels inside the area of a polygons object. + * For each voxel inside the polygon we process each of the offset voxels according to the kernel. + * + * \warning The voxels along the area are not processed. Thin areas might not process any voxels at all. + * + * \param polys The area to fill + * \param z The height at which the polygons occur + * \param process_cell_func Function to perform on each voxel cell + * \return Whether executing was stopped short as indicated by the \p cell_processing_function + */ + bool walkDilatedAreas(const ExPolygon& polys, coord_t z, const DilationKernel& kernel, const std::function& process_cell_func) const; + bool walkDilatedAreas(const ExPolygons& polys, coord_t z, const DilationKernel& kernel, const std::function& process_cell_func) const + { + for (const auto & poly : polys) { + if (!walkDilatedAreas(poly, z, kernel, process_cell_func)) { + return false; + } + } + + return true; + } + + /*! + * Dilate with a kernel. + * + * Extends the \p process_cell_func, so that for each cell we process nearby cells as well. + * + * Apply this function to a process_cell_func to create a new process_cell_func which applies the effect to nearby voxels as well. + * + * \param kernel The offset positions relative to the input of \p process_cell_func + * \param process_cell_func Function to perform on each voxel cell + */ + std::function dilate(const DilationKernel& kernel, const std::function& process_cell_func) const; + + GridPoint3 toGridPoint(const Vec3crd& point) const + { + return GridPoint3(toGridCoord(point.x(), 0), toGridCoord(point.y(), 1), toGridCoord(point.z(), 2)); + } + + grid_coord_t toGridCoord(const coord_t& coord, const size_t dim) const + { + assert(dim < 3); + return coord / cell_size_[dim] - (coord < 0); + } + + Vec3crd toLowerCorner(const GridPoint3& location) const + { + return Vec3crd(toLowerCoord(location.x(), 0), toLowerCoord(location.y(), 1), toLowerCoord(location.z(), 2)); + } + + coord_t toLowerCoord(const grid_coord_t& grid_coord, const size_t dim) const + { + assert(dim < 3); + return grid_coord * cell_size_[dim]; + } + + /*! + * Returns a rectangular polygon equal to the cross section of a voxel cell at coordinate \p p + */ + Polygon toPolygon(const GridPoint3 p) const + { + Polygon ret; + Vec3crd c = toLowerCorner(p); + ret.append({c.x(), c.y()}); + ret.append({c.x() + cell_size_.x(), c.y()}); + ret.append({c.x() + cell_size_.x(), c.y() + cell_size_.y()}); + ret.append({c.x(), c.y() + cell_size_.y()}); + return ret; + } +}; + +} // namespace Slic3r + +#endif // UTILS_VOXEL_UTILS_H diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 4dc2799..8fbb188 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -5,8 +5,10 @@ #include "ShortestPath.hpp" #include "SVG.hpp" #include "BoundingBox.hpp" - +#include "libslic3r/AABBTreeLines.hpp" #include +static const int Continuitious_length = scale_(0.01); +static const int dist_scale_threshold = 1.2; namespace Slic3r { @@ -33,6 +35,12 @@ LayerRegion* Layer::add_region(const PrintRegion *print_region) m_regions.emplace_back(new LayerRegion(this, print_region)); return m_regions.back(); } +void Layer::apply_auto_circle_compensation() +{ + for (LayerRegion *layerm : m_regions) { + layerm->auto_circle_compensation(layerm->slices, this->object()->get_auto_circle_compenstaion_params(), scale_(this->object()->config().circle_compensation_manual_offset)); + } +} // merge all regions' slices to get islands void Layer::make_slices() @@ -168,9 +176,9 @@ void Layer::make_perimeters() const PrintRegionConfig &other_config = other_layerm->region().config(); if (config.wall_filament == other_config.wall_filament && config.wall_loops == other_config.wall_loops - && config.inner_wall_speed == other_config.inner_wall_speed - && config.outer_wall_speed == other_config.outer_wall_speed - && config.gap_infill_speed.value == other_config.gap_infill_speed.value + && config.inner_wall_speed.get_at(get_extruder_id(config.wall_filament)) == other_config.inner_wall_speed.get_at(get_extruder_id(config.wall_filament)) + && config.outer_wall_speed.get_at(get_extruder_id(config.wall_filament)) == other_config.outer_wall_speed.get_at(get_extruder_id(config.wall_filament)) + && config.gap_infill_speed.get_at(get_extruder_id(config.wall_filament)) == other_config.gap_infill_speed.get_at(get_extruder_id(config.wall_filament)) && config.detect_overhang_wall == other_config.detect_overhang_wall && config.filter_out_gap_fill.value == other_config.filter_out_gap_fill.value && config.opt_serialize("inner_wall_line_width") == other_config.opt_serialize("inner_wall_line_width") @@ -180,7 +188,7 @@ void Layer::make_perimeters() && config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness && config.fuzzy_skin_point_distance == other_config.fuzzy_skin_point_distance && config.seam_slope_conditional == other_config.seam_slope_conditional - && config.scarf_angle_threshold == other_config.scarf_angle_threshold + //&& config.scarf_angle_threshold == other_config.scarf_angle_threshold && config.seam_slope_entire_loop == other_config.seam_slope_entire_loop && config.seam_slope_steps == other_config.seam_slope_steps && config.seam_slope_inner_walls == other_config.seam_slope_inner_walls) @@ -195,7 +203,8 @@ void Layer::make_perimeters() if (layerms.size() == 1) { // optimization (*layerm)->fill_surfaces.surfaces.clear(); - (*layerm)->make_perimeters((*layerm)->slices, &(*layerm)->fill_surfaces, &(*layerm)->fill_no_overlap_expolygons); + (*layerm)->make_perimeters((*layerm)->slices, &(*layerm)->fill_surfaces, &(*layerm)->fill_no_overlap_expolygons, this->loop_nodes); + (*layerm)->fill_expolygons = to_expolygons((*layerm)->fill_surfaces.surfaces); } else { SurfaceCollection new_slices; @@ -219,7 +228,7 @@ void Layer::make_perimeters() SurfaceCollection fill_surfaces; //QDS ExPolygons fill_no_overlap; - layerm_config->make_perimeters(new_slices, &fill_surfaces, &fill_no_overlap); + layerm_config->make_perimeters(new_slices, &fill_surfaces, &fill_no_overlap, this->loop_nodes); // assign fill_surfaces to each layer if (!fill_surfaces.surfaces.empty()) { @@ -237,6 +246,170 @@ void Layer::make_perimeters() BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << " - Done"; } +//QDS: use aabbtree to get distance +class ContinuitiousDistancer +{ + std::vector lines; + AABBTreeIndirect::Tree<2, double> tree; + +public: + ContinuitiousDistancer(const Points &pts) + { + Lines pt_to_lines = to_lines(pts); + for (const auto &line : pt_to_lines) + lines.emplace_back(line.a.cast(), line.b.cast()); + + tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines); + } + + float distance_from_perimeter(const Vec2f &point) const + { + Vec2d p = point.cast(); + size_t hit_idx_out{}; + Vec2d hit_point_out = Vec2d::Zero(); + auto distance = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, p, hit_idx_out, hit_point_out); + if (distance < 0) { + return std::numeric_limits::max(); + } + + distance = sqrt(distance); + const Linef &line = lines[hit_idx_out]; + Vec2d v1 = line.b - line.a; + Vec2d v2 = p - line.a; + if ((v1.x() * v2.y()) - (v1.y() * v2.x()) > 0.0) { distance *= -1; } + return distance; + } + + Lines to_lines(const Points &pts) + { + Lines lines; + if (pts.size() >= 2) { + lines.reserve(pts.size() - 1); + for (Points::const_iterator it = pts.begin(); it != pts.end() - 1; ++it) { lines.push_back(Line(*it, *(it + 1))); } + } + return lines; + } +}; + + +static double get_node_continuity_rang_limit(const std::vector &Prev_node_widths, int prev_pt_idx) { + double width = 0; + double prev_width = Prev_node_widths.front(); + if (prev_pt_idx!=0 && prev_pt_idx < Prev_node_widths.size()) { + prev_width = static_cast(Prev_node_widths[prev_pt_idx]); + } + + width = prev_width * dist_scale_threshold; + return width; +} + +void Layer::calculate_perimeter_continuity(std::vector &prev_nodes) { + for (size_t node_pos = 0; node_pos < loop_nodes.size(); ++node_pos) { + LoopNode &node=loop_nodes[node_pos]; + double width = 0; + ContinuitiousDistancer node_distancer(node.node_contour.pts); + for (size_t prev_pos = 0; prev_pos < prev_nodes.size(); ++prev_pos) { + LoopNode &prev_node = prev_nodes[prev_pos]; + + // no overlap or has diff speed + if (!node.bbox.overlap(prev_node.bbox)) + continue; + + //calculate dist, checkout the continuity + Polyline continuitious_pl; + //check start pt + size_t start = 0; + bool conntiouitious_flag = false; + int end = prev_node.node_contour.pts.size() - 1; + + //if the countor is loop + if (prev_node.node_contour.is_loop) { + for (; end >= 0; --end) { + if (continuitious_pl.length() >= Continuitious_length) { + node.lower_node_id.push_back(prev_node.node_id); + prev_node.upper_node_id.push_back(node.node_id); + conntiouitious_flag = true; + break; + } + + Point pt = prev_node.node_contour.pts[end]; + float dist = node_distancer.distance_from_perimeter(pt.cast()); + // get corr width + width = get_node_continuity_rang_limit(prev_node.node_contour.widths, end); + + if (dist < width && dist > -width) + continuitious_pl.append_before(pt); + else + break; + } + + if (conntiouitious_flag || end < 0) + continue; + } + + int last_pt_idx = end; + // line need to check end point + if (!prev_node.node_contour.is_loop) + last_pt_idx ++; + + for (; start < last_pt_idx; ++start) { + Point pt = prev_node.node_contour.pts[start]; + float dist = node_distancer.distance_from_perimeter(pt.cast()); + //get corr width + width = get_node_continuity_rang_limit(prev_node.node_contour.widths, start); + + if (dist < width && dist > -width) { + continuitious_pl.append(pt); + continue; + } + + if (continuitious_pl.empty() || continuitious_pl.length() < Continuitious_length) { + continuitious_pl.clear(); + continue; + } + + node.lower_node_id.push_back(prev_node.node_id); + prev_node.upper_node_id.push_back(node.node_id); + continuitious_pl.clear(); + break; + + } + + if (continuitious_pl.length() >= Continuitious_length) { + node.lower_node_id.push_back(prev_node.node_id); + prev_node.upper_node_id.push_back(node.node_id); + } + } + } + +} + +void Layer::recrod_cooling_node_for_each_extrusion() { + for (LayerRegion *region : this->regions()) { + for (int extrusion_idx = 0; extrusion_idx < region->perimeters.entities.size(); extrusion_idx++) { + const auto *extrusions = static_cast(region->perimeters.entities[extrusion_idx]); + int start = extrusions->loop_node_range.first; + int end = extrusions->loop_node_range.second; + if (start >= end) + continue; + + int cooling_node = this->loop_nodes[start].merged_id; + int pos = this->loop_nodes[start].loop_id; + int next_pos = start + 1 < end ? this->loop_nodes[start + 1].loop_id : -1; + for (int idx = 0; idx < extrusions->entities.size(); idx++) { + if (idx == next_pos && next_pos > 0) { + start++; + cooling_node = this->loop_nodes[start].merged_id; + next_pos = start + 1 < end ? this->loop_nodes[start + 1].loop_id : -1; + } + + extrusions->entities[idx]->set_cooling_node(cooling_node); + } + + } + } +} + void Layer::export_region_slices_to_svg(const char *path) const { BoundingBox bbox; @@ -399,6 +572,11 @@ coordf_t Layer::get_sparse_infill_max_void_area() return max_void_area; } +size_t Layer::get_extruder_id(unsigned int filament_id) const +{ + return m_object->print()->get_extruder_id(filament_id); +} + BoundingBox get_extents(const LayerRegion &layer_region) { BoundingBox bbox; diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index aa180d9..20bf313 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -7,7 +7,7 @@ #include "SurfaceCollection.hpp" #include "ExtrusionEntityCollection.hpp" #include "RegionExpansion.hpp" - +#include namespace Slic3r { @@ -80,7 +80,8 @@ public: void slices_to_fill_surfaces_clipped(); void prepare_fill_surfaces(); //QDS - void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap); + void auto_circle_compensation(SurfaceCollection &slices, const AutoContourHolesCompensationParams &auto_contour_holes_compensation_params, float manual_offset = 0.0f); + void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap, std::vector &loop_nodes); 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. @@ -154,7 +155,9 @@ public: // QDS ExPolygons loverhangs; + std::vector> loverhangs_with_type; BoundingBox loverhangs_bbox; + std::vector loop_nodes; size_t region_count() const { return m_regions.size(); } const LayerRegion* get_region(int idx) const { return m_regions[idx]; } LayerRegion* get_region(int idx) { return m_regions[idx]; } @@ -162,6 +165,7 @@ public: const LayerRegionPtrs& regions() const { return m_regions; } // Test whether whether there are any slices assigned to this layer. bool empty() const; + void apply_auto_circle_compensation(); void make_slices(); // Backup and restore raw sliced regions if needed. //FIXME Review whether not to simplify the code by keeping the raw_slices all the time. @@ -180,6 +184,9 @@ public: return false; } void make_perimeters(); + //QDS + void calculate_perimeter_continuity(std::vector &prev_nodes); + void recrod_cooling_node_for_each_extrusion(); // Phony version of make_fills() without parameters for Perl integration only. void make_fills() { this->make_fills(nullptr, nullptr); } void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator = nullptr); @@ -238,6 +245,8 @@ public: return idx; } + size_t get_extruder_id(unsigned int filament_id) const; + protected: friend class PrintObject; friend std::vector new_layers(PrintObject*, const std::vector&); diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 8857b32..25ff35c 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -13,6 +13,8 @@ #include #include +static const double max_deviation = scale_(0.5); +static const double max_variance = 5 * scale_(0.01) * scale_(0.01); namespace Slic3r { @@ -64,12 +66,74 @@ void LayerRegion::slices_to_fill_surfaces_clipped() } } -void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap) +void LayerRegion::auto_circle_compensation(SurfaceCollection& slices, const AutoContourHolesCompensationParams &auto_contour_holes_compensation_params, float manual_offset) +{ + // filament is 1 base + int filament_idx = this->region().config().wall_filament - 1; + + double limited_speed = auto_contour_holes_compensation_params.circle_compensation_speed[filament_idx]; + double counter_speed_coef = auto_contour_holes_compensation_params.counter_speed_coef[filament_idx]; + double counter_diameter_coef = auto_contour_holes_compensation_params.counter_diameter_coef[filament_idx]; + double counter_compensate_coef = scale_(auto_contour_holes_compensation_params.counter_compensate_coef[filament_idx]); + + double hole_speed_coef = auto_contour_holes_compensation_params.hole_speed_coef[filament_idx]; + double hole_diameter_coef = auto_contour_holes_compensation_params.hole_diameter_coef[filament_idx]; + double hole_compensate_coef = scale_(auto_contour_holes_compensation_params.hole_compensate_coef[filament_idx]); + + double counter_limit_min_value = scale_(auto_contour_holes_compensation_params.counter_limit_min_value[filament_idx]); + double counter_limit_max_value = scale_(auto_contour_holes_compensation_params.counter_limit_max_value[filament_idx]); + double hole_limit_min_value = scale_(auto_contour_holes_compensation_params.hole_limit_min_value[filament_idx]); + double hole_limit_max_value = scale_(auto_contour_holes_compensation_params.hole_limit_max_value[filament_idx]); + + double diameter_limit_value = scale_(auto_contour_holes_compensation_params.diameter_limit[filament_idx]); + + for (Surface &surface : slices.surfaces) { + Point center; + double diameter = 0; + if (surface.expolygon.contour.is_approx_circle(max_deviation, max_variance, center, diameter)) { + double offset_value = scale_(counter_speed_coef * limited_speed) + counter_diameter_coef * diameter + counter_compensate_coef; + if (offset_value < counter_limit_min_value) { + offset_value = counter_limit_min_value; + } else if (offset_value > counter_limit_max_value) { + offset_value = counter_limit_max_value; + } + offset_value -= manual_offset / 2; + Polygons offseted_polys = offset(surface.expolygon.contour, offset_value); + if (offseted_polys.size() == 1) { + surface.expolygon.contour = offseted_polys[0]; + if (diameter < diameter_limit_value) + surface.counter_circle_compensation = true; + } + } + for (size_t i = 0; i < surface.expolygon.holes.size(); ++i) { + Polygon &hole = surface.expolygon.holes[i]; + if (hole.is_approx_circle(max_deviation, max_variance, center, diameter)) { + double offset_value = scale_(hole_speed_coef * limited_speed) + hole_diameter_coef * diameter + hole_compensate_coef; + if (offset_value < hole_limit_min_value) { + offset_value = hole_limit_min_value; + } else if (offset_value > hole_limit_max_value) { + offset_value = hole_limit_max_value; + } + // positive value means shrinking hole, which oppsite to contour + offset_value = -offset_value; + offset_value -= manual_offset / 2; + Polygons offseted_polys = offset(hole, offset_value); + if (offseted_polys.size() == 1) { + hole = offseted_polys[0]; + if (diameter < diameter_limit_value) + surface.holes_circle_compensation.push_back(i); + } + } + } + } +} + +void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection *fill_surfaces, ExPolygons *fill_no_overlap, std::vector &loop_nodes) { this->perimeters.clear(); this->thin_fills.clear(); - const PrintConfig &print_config = this->layer()->object()->print()->config(); + const PrintConfig & print_config = this->layer()->object()->print()->config(); const PrintRegionConfig ®ion_config = this->region().config(); const PrintObjectConfig& object_config = this->layer()->object()->config(); // This needs to be in sync with PrintObject::_slice() slicing_mode_normal_below_layer! @@ -93,7 +157,8 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec &this->thin_fills, fill_surfaces, //QDS - fill_no_overlap + fill_no_overlap, + &loop_nodes ); if (this->layer()->lower_layer != nullptr) @@ -494,7 +559,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly 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}, + ExpansionZone{std::move(top_expolygons), expansion_params_into_solid_infill} }; SurfaceCollection bridges; @@ -528,6 +593,25 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly expansion_zones.at(0).parameters = RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps); Surfaces tops = expand_merge_surfaces(fill_surfaces.surfaces, stTop, expansion_zones, closing_radius); + //expansion_zone[0]: shell , expansion_zone[1]: sparse + //apply minimu sparse infill area logic, this should also be added in bridge_over_infill + if (!this->layer()->object()->print()->config().spiral_mode && this->region().config().sparse_infill_density.value > 0) { + auto &sparse=expansion_zones[1].expolygons; + auto &shells=expansion_zones[0].expolygons; + double min_area = scale_(scale_(this->region().config().minimum_sparse_infill_area.value)); + ExPolygons areas_to_be_solid{}; + sparse.erase(std::remove_if(sparse.begin(), sparse.end(), [min_area, &areas_to_be_solid](ExPolygon& expoly) { + if (expoly.area() <= min_area) { + areas_to_be_solid.push_back(expoly); + return true; + } + return false; + }), sparse.end()); + + if (!areas_to_be_solid.empty()) + shells = union_ex(shells, areas_to_be_solid); + } + // m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternal, stInternalSolid }); fill_surfaces.clear(); unsigned zones_expolygons_count = 0; @@ -555,298 +639,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly #else -//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. -//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 -#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. -void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered) -{ - const bool has_infill = this->region().config().sparse_infill_density.value > 0.; - //QDS - auto nozzle_diameter = this->region().nozzle_dmr_avg(this->layer()->object()->print()->config()); - const float margin = float(scale_(EXTERNAL_INFILL_MARGIN)); - const float bridge_margin = std::min(float(scale_(BRIDGE_INFILL_MARGIN)), float(scale_(nozzle_diameter * BRIDGE_INFILL_MARGIN / 0.4))); - - // QDS - const PrintObjectConfig& object_config = this->layer()->object()->config(); - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial"); -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - - // 1) Collect bottom and bridge surfaces, each of them grown by a fixed 3mm offset - // for better anchoring. - // Bottom surfaces, grown. - Surfaces bottom; - // Bridge surfaces, initialy not grown. - Surfaces bridges; - // Top surfaces, grown. - Surfaces top; - // Internal surfaces, not grown. - Surfaces internal; - // Areas, where an infill of various types (top, bottom, bottom bride, sparse, void) could be placed. - Polygons fill_boundaries = to_polygons(this->fill_expolygons); - Polygons lower_layer_covered_tmp; - - // Collect top surfaces and internal surfaces. - // Collect fill_boundaries: If we're slicing with no infill, we can't extend external surfaces over non-existent infill. - // This loop destroys the surfaces (aliasing this->fill_surfaces.surfaces) by moving into top/internal/fill_boundaries! - - { - // Voids are sparse infills if infill rate is zero. - Polygons voids; - - double max_grid_area = -1; - if (this->layer()->lower_layer != nullptr) - max_grid_area = this->layer()->lower_layer->get_sparse_infill_max_void_area(); - for (const Surface &surface : this->fill_surfaces.surfaces) { - if (surface.is_top()) { - // Collect the top surfaces, inflate them and trim them by the bottom surfaces. - // This gives the priority to bottom surfaces. - if (max_grid_area < 0 || surface.expolygon.area() < max_grid_area) - surfaces_append(top, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface); - else - //QDS: Don't need to expand too much in this situation. Expand 3mm to eliminate hole and 1mm for contour - surfaces_append(top, intersection_ex(offset(surface.expolygon.contour, margin / 3.0, EXTERNAL_SURFACES_OFFSET_PARAMETERS), - offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS)), surface); - } else if (surface.surface_type == stBottom || (surface.surface_type == stBottomBridge && lower_layer == nullptr)) { - // Grown by 3mm. - surfaces_append(bottom, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface); - } else if (surface.surface_type == stBottomBridge) { - if (! surface.empty()) - bridges.emplace_back(surface); - } - if (surface.is_internal()) { - assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid); - if (! has_infill && lower_layer != nullptr) - polygons_append(voids, surface.expolygon); - internal.emplace_back(std::move(surface)); - } - } - if (! has_infill && lower_layer != nullptr && ! voids.empty()) { - // Remove voids from fill_boundaries, that are not supported by the layer below. - if (lower_layer_covered == nullptr) { - lower_layer_covered = &lower_layer_covered_tmp; - lower_layer_covered_tmp = to_polygons(lower_layer->lslices); - } - if (! lower_layer_covered->empty()) - voids = diff(voids, *lower_layer_covered); - fill_boundaries = diff(fill_boundaries, voids); - } - } - -#if 0 - { - static int iRun = 0; - bridges.export_to_svg(debug_out_path("bridges-before-grouping-%d.svg", iRun ++), true); - } -#endif - - if (bridges.empty()) - { - fill_boundaries = union_safety_offset(fill_boundaries); - } else - { - // 1) Calculate the inflated bridge regions, each constrained to its island. - ExPolygons fill_boundaries_ex = union_safety_offset_ex(fill_boundaries); - std::vector bridges_grown; - std::vector bridge_bboxes; - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - static int iRun = 0; - SVG svg(debug_out_path("3_process_external_surfaces-fill_regions-%d.svg", iRun ++).c_str(), get_extents(fill_boundaries_ex)); - svg.draw(fill_boundaries_ex); - svg.draw_outline(fill_boundaries_ex, "black", "blue", scale_(0.05)); - svg.Close(); - } - -// export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial"); -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - - { - // Bridge expolygons, grown, to be tested for intersection with other bridge regions. - std::vector fill_boundaries_ex_bboxes = get_extents_vector(fill_boundaries_ex); - bridges_grown.reserve(bridges.size()); - bridge_bboxes.reserve(bridges.size()); - for (size_t i = 0; i < bridges.size(); ++ i) { - // Find the island of this bridge. - const Point pt = bridges[i].expolygon.contour.points.front(); - int idx_island = -1; - for (int j = 0; j < int(fill_boundaries_ex.size()); ++ j) - if (fill_boundaries_ex_bboxes[j].contains(pt) && - fill_boundaries_ex[j].contains(pt)) { - idx_island = j; - break; - } - // Grown by 3mm. - //QDS: eliminate too narrow area to avoid generating bridge on top layer when wall loop is 1 - //Polygons polys = offset(bridges[i].expolygon, bridge_margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS); - Polygons polys = offset2({ bridges[i].expolygon }, -scale_(nozzle_diameter * 0.1), bridge_margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS); - if (idx_island == -1) { - BOOST_LOG_TRIVIAL(trace) << "Bridge did not fall into the source region!"; - } else { - // Found an island, to which this bridge region belongs. Trim it, - polys = intersection(polys, fill_boundaries_ex[idx_island]); - } - bridge_bboxes.push_back(get_extents(polys)); - bridges_grown.push_back(std::move(polys)); - } - } - - // 2) Group the bridge surfaces by overlaps. - std::vector bridge_group(bridges.size(), (size_t)-1); - size_t n_groups = 0; - for (size_t i = 0; i < bridges.size(); ++ i) { - // A grup id for this bridge. - size_t group_id = (bridge_group[i] == size_t(-1)) ? (n_groups ++) : bridge_group[i]; - bridge_group[i] = group_id; - // For all possibly overlaping bridges: - for (size_t j = i + 1; j < bridges.size(); ++ j) { - if (! bridge_bboxes[i].overlap(bridge_bboxes[j])) - continue; - if (intersection(bridges_grown[i], bridges_grown[j]).empty()) - continue; - // The two bridge regions intersect. Give them the same group id. - if (bridge_group[j] != size_t(-1)) { - // The j'th bridge has been merged with some other bridge before. - size_t group_id_new = bridge_group[j]; - for (size_t k = 0; k < j; ++ k) - if (bridge_group[k] == group_id) - bridge_group[k] = group_id_new; - group_id = group_id_new; - } - bridge_group[j] = group_id; - } - } - - // 3) Merge the groups with the same group id, detect bridges. - { - BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z << ", bridge groups: " << n_groups; - for (size_t group_id = 0; group_id < n_groups; ++ group_id) { - size_t n_bridges_merged = 0; - size_t idx_last = (size_t)-1; - for (size_t i = 0; i < bridges.size(); ++ i) { - if (bridge_group[i] == group_id) { - ++ n_bridges_merged; - idx_last = i; - } - } - if (n_bridges_merged == 0) - // This group has no regions assigned as these were moved into another group. - continue; - // Collect the initial ungrown regions and the grown polygons. - ExPolygons initial; - Polygons grown; - for (size_t i = 0; i < bridges.size(); ++ i) { - if (bridge_group[i] != group_id) - continue; - initial.push_back(std::move(bridges[i].expolygon)); - polygons_append(grown, bridges_grown[i]); - } - // detect bridge direction before merging grown surfaces otherwise adjacent bridges - // would get merged into a single one while they need different directions - // also, supply the original expolygon instead of the grown one, because in case - // of very thin (but still working) anchors, the grown expolygon would go beyond them - double custom_angle = Geometry::deg2rad(this->region().config().bridge_angle.value); - if (custom_angle > 0.0) { - bridges[idx_last].bridge_angle = custom_angle; - } else { - auto [bridging_dir, unsupported_dist] = detect_bridging_direction(to_polygons(initial), to_polygons(lower_layer->lslices)); - bridges[idx_last].bridge_angle = PI + std::atan2(bridging_dir.y(), bridging_dir.x()); - } - - /* - BridgeDetector bd(initial, lower_layer->lslices, this->bridging_flow(frInfill, object_config.thick_bridges).scaled_width()); - #ifdef SLIC3R_DEBUG - printf("Processing bridge at layer %zu:\n", this->layer()->id()); - #endif - double custom_angle = Geometry::deg2rad(this->region().config().bridge_angle.value); - if (bd.detect_angle(custom_angle)) { - bridges[idx_last].bridge_angle = bd.angle; - if (this->layer()->object()->has_support()) { -// polygons_append(this->bridged, bd.coverage()); - append(this->unsupported_bridge_edges, bd.unsupported_edges()); - } - } else if (custom_angle > 0) { - // Bridge was not detected (likely it is only supported at one side). Still it is a surface filled in - // using a bridging flow, therefore it makes sense to respect the custom bridging direction. - bridges[idx_last].bridge_angle = custom_angle; - } - */ - // without safety offset, artifacts are generated (GH #2494) - surfaces_append(bottom, union_safety_offset_ex(grown), bridges[idx_last]); - } - - fill_boundaries = to_polygons(fill_boundaries_ex); - BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done"; - } - - #if 0 - { - static int iRun = 0; - bridges.export_to_svg(debug_out_path("bridges-after-grouping-%d.svg", iRun ++), true); - } - #endif - } - - Surfaces new_surfaces; - { - // Intersect the grown surfaces with the actual fill boundaries. - Polygons bottom_polygons = to_polygons(bottom); - // Merge top and bottom in a single collection. - surfaces_append(top, std::move(bottom)); - - for (size_t i = 0; i < top.size(); ++ i) { - Surface &s1 = top[i]; - if (s1.empty()) - continue; - Polygons polys; - polygons_append(polys, to_polygons(std::move(s1))); - for (size_t j = i + 1; j < top.size(); ++ j) { - Surface &s2 = top[j]; - if (! s2.empty() && surfaces_could_merge(s1, s2)) { - polygons_append(polys, to_polygons(std::move(s2))); - s2.clear(); - } - } - if (s1.is_top()) - // Trim the top surfaces by the bottom surfaces. This gives the priority to the bottom surfaces. - polys = diff(polys, bottom_polygons); - surfaces_append( - new_surfaces, - // Don't use a safety offset as fill_boundaries were already united using the safety offset. - intersection_ex(polys, fill_boundaries), - s1); - } - } - - // Subtract the new top surfaces from the other non-top surfaces and re-add them. - Polygons new_polygons = to_polygons(new_surfaces); - for (size_t i = 0; i < internal.size(); ++ i) { - Surface &s1 = internal[i]; - if (s1.empty()) - continue; - Polygons polys; - polygons_append(polys, to_polygons(std::move(s1))); - for (size_t j = i + 1; j < internal.size(); ++ j) { - Surface &s2 = internal[j]; - if (! s2.empty() && surfaces_could_merge(s1, s2)) { - polygons_append(polys, to_polygons(std::move(s2))); - s2.clear(); - } - } - ExPolygons new_expolys = diff_ex(polys, new_polygons); - polygons_append(new_polygons, to_polygons(new_expolys)); - surfaces_append(new_surfaces, std::move(new_expolys), s1); - } - - this->fill_surfaces.surfaces = std::move(new_surfaces); - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final"); -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ -} #endif void LayerRegion::prepare_fill_surfaces() @@ -854,7 +647,7 @@ void LayerRegion::prepare_fill_surfaces() #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_slices_to_svg_debug("2_prepare_fill_surfaces-initial"); export_region_fill_surfaces_to_svg_debug("2_prepare_fill_surfaces-initial"); -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ /* Note: in order to make the psPrepareInfill step idempotent, we should never alter fill_surfaces boundaries on which our idempotency relies since that's diff --git a/src/libslic3r/MacUtils.hpp b/src/libslic3r/MacUtils.hpp index 388baa1..29366ee 100644 --- a/src/libslic3r/MacUtils.hpp +++ b/src/libslic3r/MacUtils.hpp @@ -4,7 +4,7 @@ namespace Slic3r { bool is_macos_support_boost_add_file_log(); - +int is_mac_version_15(); } #endif diff --git a/src/libslic3r/MacUtils.mm b/src/libslic3r/MacUtils.mm index bb9ecd1..f5fa82f 100644 --- a/src/libslic3r/MacUtils.mm +++ b/src/libslic3r/MacUtils.mm @@ -6,10 +6,18 @@ namespace Slic3r { bool is_macos_support_boost_add_file_log() { if (@available(macOS 12.0, *)) { - return true; - } else { - return false; - } + return true; + } else { + return false; + } } +int is_mac_version_15() +{ + if (@available(macOS 15.0, *)) {//This code runs on macOS 15 or later. + return true; + } else { + return false; + } +} }; // namespace Slic3r diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 927207b..1f49648 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -760,47 +760,50 @@ bool do_boolean_single(McutMesh &srcMesh, const McutMesh &cutMesh, const std::st void do_boolean(McutMesh& srcMesh, const McutMesh& cutMesh, const std::string& boolean_opts) { - TriangleMesh tri_src = mcut_to_triangle_mesh(srcMesh); - std::vector src_parts = its_split(tri_src.its); + try { + TriangleMesh tri_src = mcut_to_triangle_mesh(srcMesh); + std::vector src_parts = its_split(tri_src.its); - TriangleMesh tri_cut = mcut_to_triangle_mesh(cutMesh); - std::vector cut_parts = its_split(tri_cut.its); + TriangleMesh tri_cut = mcut_to_triangle_mesh(cutMesh); + std::vector cut_parts = its_split(tri_cut.its); - if (src_parts.empty() && boolean_opts == "UNION") { - srcMesh = cutMesh; - return; - } - if(cut_parts.empty()) return; - - // when src mesh has multiple connected components, mcut refuses to work. - // But we can force it to work by spliting the src mesh into disconnected components, - // and do booleans seperately, then merge all the results. - indexed_triangle_set all_its; - if (boolean_opts == "UNION" || boolean_opts == "A_NOT_B") { - for (size_t i = 0; i < src_parts.size(); i++) { - auto src_part = triangle_mesh_to_mcut(src_parts[i]); - for (size_t j = 0; j < cut_parts.size(); j++) { - auto cut_part = triangle_mesh_to_mcut(cut_parts[j]); - do_boolean_single(*src_part, *cut_part, boolean_opts); - } - TriangleMesh tri_part = mcut_to_triangle_mesh(*src_part); - its_merge(all_its, tri_part.its); + if (src_parts.empty() && boolean_opts == "UNION") { + srcMesh = cutMesh; + return; } - } - else if (boolean_opts == "INTERSECTION") { - for (size_t i = 0; i < src_parts.size(); i++) { - for (size_t j = 0; j < cut_parts.size(); j++) { + if (cut_parts.empty()) return; + + // when src mesh has multiple connected components, mcut refuses to work. + // But we can force it to work by spliting the src mesh into disconnected components, + // and do booleans seperately, then merge all the results. + indexed_triangle_set all_its; + if (boolean_opts == "UNION" || boolean_opts == "A_NOT_B") { + for (size_t i = 0; i < src_parts.size(); i++) { auto src_part = triangle_mesh_to_mcut(src_parts[i]); - auto cut_part = triangle_mesh_to_mcut(cut_parts[j]); - bool success = do_boolean_single(*src_part, *cut_part, boolean_opts); - if (success) { - TriangleMesh tri_part = mcut_to_triangle_mesh(*src_part); - its_merge(all_its, tri_part.its); + for (size_t j = 0; j < cut_parts.size(); j++) { + auto cut_part = triangle_mesh_to_mcut(cut_parts[j]); + do_boolean_single(*src_part, *cut_part, boolean_opts); + } + TriangleMesh tri_part = mcut_to_triangle_mesh(*src_part); + its_merge(all_its, tri_part.its); + } + } else if (boolean_opts == "INTERSECTION") { + for (size_t i = 0; i < src_parts.size(); i++) { + for (size_t j = 0; j < cut_parts.size(); j++) { + auto src_part = triangle_mesh_to_mcut(src_parts[i]); + auto cut_part = triangle_mesh_to_mcut(cut_parts[j]); + bool success = do_boolean_single(*src_part, *cut_part, boolean_opts); + if (success) { + TriangleMesh tri_part = mcut_to_triangle_mesh(*src_part); + its_merge(all_its, tri_part.its); + } } } } + srcMesh = *triangle_mesh_to_mcut(all_its); + } catch (const std::exception &e) { + BOOST_LOG_TRIVIAL(error) << "check error:" << e.what(); } - srcMesh = *triangle_mesh_to_mcut(all_its); } void make_boolean(const TriangleMesh &src_mesh, const TriangleMesh &cut_mesh, std::vector &dst_mesh, const std::string &boolean_opts) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 39bae64..1eb1043 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -49,8 +49,9 @@ namespace Slic3r { const std::vector CONST_FILAMENTS = { - "", "4", "8", "0C", "1C", "2C", "3C", "4C", "5C", "6C", "7C", "8C", "9C", "AC", "BC", "CC", "DC", -}; // 5 10 15 16 + "", "4", "8", "0C", "1C", "2C", "3C", "4C", "5C", "6C", "7C", "8C", "9C", "AC", "BC", "CC","DC",//16 + "EC", "0FC", "1FC", "2FC", "3FC", "4FC", "5FC", "6FC", "7FC", "8FC", "9FC", "AFC", "BFC", "CFC", "DFC", "EFC",//32 +}; // 1 5 10 15 16 // QDS initialization of static variables std::map Model::extruderParamsMap = { {0,{"",0,0}}}; GlobalSpeedMap Model::printSpeedMap{}; @@ -196,26 +197,34 @@ Model Model::read_from_step(const std::string& Model model; bool result = false; bool is_cb_cancel = false; - std::string message; - Step step_file(input_file); - step_file.load(); + Step::Step_Status status; + Step step_file(input_file, stepFn); + status = step_file.load(); + if(status != Step::Step_Status::LOAD_SUCCESS) { + goto _finished; + } if (step_mesh_fn) { if (step_mesh_fn(step_file, linear_defletion, angle_defletion, is_split_compound) == -1) { + status = Step::Step_Status::CANCEL; + goto _finished; + } + } + + status = step_file.mesh(&model, is_cb_cancel, is_split_compound, linear_defletion, angle_defletion); + +_finished: + + switch (status){ + case Step::Step_Status::CANCEL: { Model empty_model; return empty_model; } - } - result = load_step(input_file.c_str(), &model, is_cb_cancel, linear_defletion, angle_defletion, is_split_compound, stepFn, stepIsUtf8Fn); - if (is_cb_cancel) { - Model empty_model; - return empty_model; - } - - if (!result) { - if (message.empty()) + case Step::Step_Status::LOAD_ERROR: throw Slic3r::RuntimeError(_L("Loading of a model file failed.")); - else - throw Slic3r::RuntimeError(message); + case Step::Step_Status::MESH_ERROR: + throw Slic3r::RuntimeError(_L("Meshing of a model file failed or no valid shape.")); + default: + break; } if (model.objects.empty()) @@ -257,12 +266,10 @@ Model Model::read_from_file(const std::string& config_substitutions = &temp_config_substitutions_context; //QDS: plate_data PlateDataPtrs temp_plate_data; - bool temp_is_xxx; Semver temp_version; if (plate_data == nullptr) plate_data = &temp_plate_data; - if (is_xxx == nullptr) - is_xxx = &temp_is_xxx; + if (file_version == nullptr) file_version = &temp_version; @@ -275,24 +282,24 @@ Model Model::read_from_file(const std::string& result = load_stl(input_file.c_str(), &model, nullptr, stlFn,256); else if (boost::algorithm::iends_with(input_file, ".obj")) { ObjInfo obj_info; - result = load_obj(input_file.c_str(), &model, obj_info, message); + result = load_obj(input_file.c_str(), &model, obj_info, message, nullptr, (is_xxx && *is_xxx) ? true : false); if (result){ - unsigned char first_extruder_id; + ObjDialogInOut in_out; + in_out.model = &model; + in_out.lost_material_name = obj_info.lost_material_name; if (obj_info.vertex_colors.size() > 0) { - std::vector vertex_filament_ids; if (objFn) { // 1.result is ok and pop up a dialog - objFn(obj_info.vertex_colors, false, vertex_filament_ids, first_extruder_id, obj_info.ml_region, obj_info.ml_name, obj_info.ml_id); - if (vertex_filament_ids.size() > 0) { - result = obj_import_vertex_color_deal(vertex_filament_ids, first_extruder_id, & model); - } + in_out.input_colors = std::move(obj_info.vertex_colors); + in_out.is_single_color = false; + in_out.deal_vertex_color = true; + objFn(in_out); } } else if (obj_info.face_colors.size() > 0 && obj_info.has_uv_png == false) { // mtl file - std::vector face_filament_ids; if (objFn) { // 1.result is ok and pop up a dialog - objFn(obj_info.face_colors, obj_info.is_single_mtl, face_filament_ids, first_extruder_id, obj_info.ml_region, obj_info.ml_name, obj_info.ml_id); - if (face_filament_ids.size() > 0) { - result = obj_import_face_color_deal(face_filament_ids, first_extruder_id, &model); - } + in_out.input_colors = std::move(obj_info.face_colors); + in_out.is_single_color = obj_info.is_single_mtl; + in_out.deal_vertex_color = false; + objFn(in_out); } } /*else if (obj_info.has_uv_png && obj_info.uvs.size() > 0) { boost::filesystem::path full_path(input_file); @@ -2832,7 +2839,24 @@ unsigned int ModelObject::update_instances_print_volume_state(const BuildVolume } const Transform3d matrix = model_instance->get_matrix() * vol->get_matrix(); - BuildVolume::ObjectState state = build_volume.object_state(vol->mesh().its, matrix.cast(), true /* may be below print bed */); + BuildVolume::ObjectState state; + switch(build_volume.type()) + { + case BuildVolume::Type::Rectangle: + { + BoundingBoxf3 transformed_bb = vol->get_convex_hull().transformed_bounding_box(matrix); + state = build_volume.volume_state_bbox(transformed_bb); + //state = build_volume.object_state(vol->mesh().its, matrix.cast(), true /* may be below print bed */); + break; + } + case BuildVolume::Type::Circle: + case BuildVolume::Type::Convex: + case BuildVolume::Type::Custom: + default: + state = build_volume.object_state(vol->mesh().its, matrix.cast(), true /* may be below print bed */); + break; + } + if (state == BuildVolume::ObjectState::Inside) // Volume is completely inside. inside_outside |= INSIDE; @@ -3087,6 +3111,17 @@ void ModelVolume::update_extruder_count(size_t extruder_count) } } +void ModelVolume::update_extruder_count_when_delete_filament(size_t extruder_count, size_t filament_id, int replace_filament_id) +{ + std::vector used_extruders = get_extruders(); + for (int extruder_id : used_extruders) { + if (extruder_id >= filament_id) { + mmu_segmentation_facets.set_enforcer_block_type_limit(*this, (EnforcerBlockerType)(extruder_count), (EnforcerBlockerType)(filament_id), (EnforcerBlockerType)(replace_filament_id)); + break; + } + } +} + void ModelVolume::center_geometry_after_creation(bool update_source_offset) { Vec3d shift = this->mesh().bounding_box().center(); @@ -3119,11 +3154,9 @@ void ModelVolume::calculate_convex_hull_2d(const Geometry::Transformation &tran return; Points pts; - Vec3d rotation = transformation.get_rotation(); - Vec3d mirror = transformation.get_mirror(); - Vec3d scale = transformation.get_scaling_factor(); - //rotation(2) = 0.f; - Transform3d new_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, scale, mirror); + Geometry::Transformation new_trans(transformation); + new_trans.reset_offset(); + Transform3d new_matrix = new_trans.get_matrix(); pts.reserve(its.vertices.size()); // Using the shared vertices should be a bit quicker than using the STL faces. @@ -3455,6 +3488,12 @@ void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) cons mesh->transform(get_matrix(dont_translate)); } +void ModelInstance::rotate(Matrix3d rotation_matrix) +{ + auto new_rotation_mat = Transform3d(rotation_matrix) * m_transformation.get_rotation_matrix(); + m_transformation.set_rotation_matrix(new_rotation_mat); +} + BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate) const { // Rotate around mesh origin. @@ -3514,32 +3553,34 @@ const Transform3d &ModelInstance::get_matrix(bool dont_translate, bool dont_rota void Model::setPrintSpeedTable(const DynamicPrintConfig& config, const PrintConfig& print_config) { //Slic3r::DynamicPrintConfig config = wxGetApp().preset_bundle->full_config(); printSpeedMap.maxSpeed = 0; + + // todo multi_extruders: the following parameters need get exact filament id if (config.has("inner_wall_speed")) { - printSpeedMap.perimeterSpeed = config.opt_float("inner_wall_speed"); + printSpeedMap.perimeterSpeed = config.opt_float_nullable("inner_wall_speed", 0); if (printSpeedMap.perimeterSpeed > printSpeedMap.maxSpeed) printSpeedMap.maxSpeed = printSpeedMap.perimeterSpeed; } if (config.has("outer_wall_speed")) { - printSpeedMap.externalPerimeterSpeed = config.opt_float("outer_wall_speed"); + printSpeedMap.externalPerimeterSpeed = config.opt_float_nullable("outer_wall_speed", 0); printSpeedMap.maxSpeed = std::max(printSpeedMap.maxSpeed, printSpeedMap.externalPerimeterSpeed); } if (config.has("sparse_infill_speed")) { - printSpeedMap.infillSpeed = config.opt_float("sparse_infill_speed"); + printSpeedMap.infillSpeed = config.opt_float_nullable("sparse_infill_speed", 0); if (printSpeedMap.infillSpeed > printSpeedMap.maxSpeed) printSpeedMap.maxSpeed = printSpeedMap.infillSpeed; } if (config.has("internal_solid_infill_speed")) { - printSpeedMap.solidInfillSpeed = config.opt_float("internal_solid_infill_speed"); + printSpeedMap.solidInfillSpeed = config.opt_float_nullable("internal_solid_infill_speed", 0); if (printSpeedMap.solidInfillSpeed > printSpeedMap.maxSpeed) printSpeedMap.maxSpeed = printSpeedMap.solidInfillSpeed; } if (config.has("top_surface_speed")) { - printSpeedMap.topSolidInfillSpeed = config.opt_float("top_surface_speed"); + printSpeedMap.topSolidInfillSpeed = config.opt_float_nullable("top_surface_speed", 0); if (printSpeedMap.topSolidInfillSpeed > printSpeedMap.maxSpeed) printSpeedMap.maxSpeed = printSpeedMap.topSolidInfillSpeed; } if (config.has("support_speed")) { - printSpeedMap.supportSpeed = config.opt_float("support_speed"); + printSpeedMap.supportSpeed = config.opt_float_nullable("support_speed", 0); if (printSpeedMap.supportSpeed > printSpeedMap.maxSpeed) printSpeedMap.maxSpeed = printSpeedMap.supportSpeed; @@ -3565,12 +3606,13 @@ void Model::setPrintSpeedTable(const DynamicPrintConfig& config, const PrintConf } // find temperature of heatend and bed and matierial of an given extruder -void Model::setExtruderParams(const DynamicPrintConfig& config, int extruders_count) { +void Model::setExtruderParams(const DynamicPrintConfig &config, int filament_count) +{ extruderParamsMap.clear(); //Slic3r::DynamicPrintConfig config = wxGetApp().preset_bundle->full_config(); // QDS //int numExtruders = wxGetApp().preset_bundle->filament_presets.size(); - for (unsigned int i = 0; i != extruders_count; ++i) { + for (unsigned int i = 0; i != filament_count; ++i) { std::string matName = ""; // QDS int bedTemp = 35; @@ -3579,7 +3621,7 @@ void Model::setExtruderParams(const DynamicPrintConfig& config, int extruders_co matName = config.opt_string("filament_type", i); } if (config.has("nozzle_temperature")) { - endTemp = config.opt_int("nozzle_temperature", i); + endTemp = config.opt_int_nullable("nozzle_temperature", i); } // FIXME: curr_bed_type is now a plate config rather than a global config. @@ -3599,6 +3641,7 @@ static void get_real_filament_id(const unsigned char &id, std::string &result) { if (id < CONST_FILAMENTS.size()) { result = CONST_FILAMENTS[id]; } else { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "check error:CONST_FILAMENTS out of array "; result = "";//error } }; @@ -3643,6 +3686,7 @@ bool Model::obj_import_vertex_color_deal(const std::vector &verte auto volume = obj->volumes[0]; volume->config.set("extruder", first_extruder_id); auto face_count = volume->mesh().its.indices.size(); + volume->mmu_segmentation_facets.reset(); volume->mmu_segmentation_facets.reserve(face_count); if (volume->mesh().its.vertices.size() != vertex_filament_ids.size()) { return false; @@ -3655,9 +3699,6 @@ bool Model::obj_import_vertex_color_deal(const std::vector &verte if (filament_id0 <= 1 && filament_id1 <= 1 && filament_id2 <= 2) { continue; } - if (i == 0) { - std::cout << ""; - } VertexColorCase vertex_color_case; unsigned char iso_index; calc_vertex_color_case(filament_id0, filament_id1, filament_id2, vertex_color_case, iso_index); @@ -3736,12 +3777,17 @@ bool Model::obj_import_face_color_deal(const std::vector &face_fi auto volume = obj->volumes[0]; volume->config.set("extruder", first_extruder_id); auto face_count = volume->mesh().its.indices.size(); + volume->mmu_segmentation_facets.reset(); volume->mmu_segmentation_facets.reserve(face_count); - if (volume->mesh().its.indices.size() != face_filament_ids.size()) { return false; } + if (volume->mesh().its.indices.size() != face_filament_ids.size()) { + return false; + } for (size_t i = 0; i < volume->mesh().its.indices.size(); i++) { auto face = volume->mesh().its.indices[i]; auto filament_id = face_filament_ids[i]; - if (filament_id <= 1) { continue; } + if (filament_id < 1) { + continue; + } std::string result; get_real_filament_id(filament_id, result); volume->mmu_segmentation_facets.set_triangle_from_string(i, result); @@ -3766,22 +3812,23 @@ double Model::findMaxSpeed(const ModelObject* object) { double supportSpeedObj = Model::printSpeedMap.supportSpeed; double smallPerimeterSpeedObj = Model::printSpeedMap.smallPerimeterSpeed; for (std::string objectKey : objectKeys) { + // todo multi_extruders: if (objectKey == "inner_wall_speed"){ - perimeterSpeedObj = object->config.opt_float(objectKey); + perimeterSpeedObj = object->config.get().opt_float_nullable(objectKey, 0); externalPerimeterSpeedObj = Model::printSpeedMap.externalPerimeterSpeed / Model::printSpeedMap.perimeterSpeed * perimeterSpeedObj; } if (objectKey == "sparse_infill_speed") - infillSpeedObj = object->config.opt_float(objectKey); + infillSpeedObj = object->config.get().opt_float_nullable(objectKey, 0); if (objectKey == "internal_solid_infill_speed") - solidInfillSpeedObj = object->config.opt_float(objectKey); + solidInfillSpeedObj = object->config.get().opt_float_nullable(objectKey, 0); if (objectKey == "top_surface_speed") - topSolidInfillSpeedObj = object->config.opt_float(objectKey); + topSolidInfillSpeedObj = object->config.get().opt_float_nullable(objectKey, 0); if (objectKey == "support_speed") - supportSpeedObj = object->config.opt_float(objectKey); + supportSpeedObj = object->config.get().opt_float_nullable(objectKey, 0); if (objectKey == "outer_wall_speed") - externalPerimeterSpeedObj = object->config.opt_float(objectKey); + externalPerimeterSpeedObj = object->config.get().opt_float_nullable(objectKey, 0); if (objectKey == "small_perimeter_speed") - smallPerimeterSpeedObj = object->config.opt_float(objectKey); + smallPerimeterSpeedObj = object->config.get().option(objectKey)->get_at(0).get_abs_value(externalPerimeterSpeedObj); } objMaxSpeed = std::max(perimeterSpeedObj, std::max(externalPerimeterSpeedObj, std::max(infillSpeedObj, std::max(solidInfillSpeedObj, std::max(topSolidInfillSpeedObj, std::max(supportSpeedObj, std::max(smallPerimeterSpeedObj,objMaxSpeed))))))); if (objMaxSpeed <= 0) objMaxSpeed = 250.; @@ -3916,27 +3963,33 @@ double ModelInstance::get_auto_brim_width() const void ModelInstance::get_arrange_polygon(void *ap, const Slic3r::DynamicPrintConfig &config_global) const { -// static const double SIMPLIFY_TOLERANCE_MM = 0.1; + // static const double SIMPLIFY_TOLERANCE_MM = 0.1; - Vec3d rotation = get_rotation(); - rotation.z() = 0.; - Transform3d trafo_instance = - Geometry::assemble_transform(get_offset().z() * Vec3d::UnitZ(), rotation, get_scaling_factor(), get_mirror()); + Geometry::Transformation trafo_instance = get_transformation(); - Polygon p = get_object()->convex_hull_2d(trafo_instance); + // BOOST_LOG_TRIVIAL(debug) << "get_arrange_polygon: " << object->name << " instance trans:\n" + // << trafo_instance.get_matrix().matrix() << "\n object trans:\n" + // << object->volumes.front()->get_transformation().get_matrix().matrix(); -// if (!p.points.empty()) { -// Polygons pp{p}; -// pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); -// if (!pp.empty()) p = pp.front(); -// } + trafo_instance.set_offset(Vec3d(0, 0, get_offset(Z))); - arrangement::ArrangePolygon& ret = *(arrangement::ArrangePolygon*)ap; - ret.poly.contour = std::move(p); - ret.translation = Vec2crd{scaled(get_offset(X)), scaled(get_offset(Y))}; - ret.rotation = get_rotation(Z); + Polygon p = get_object()->convex_hull_2d(trafo_instance.get_matrix()); - //QDS: add materials related information + // if (!p.points.empty()) { + // Polygons pp{p}; + // pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); + // if (!pp.empty()) p = pp.front(); + // } + + arrangement::ArrangePolygon &ret = *(arrangement::ArrangePolygon *) ap; + ret.poly.contour = std::move(p); + ret.translation = Vec2crd{scaled(get_offset(X)), scaled(get_offset(Y))}; + ret.rotation = 0; + + // QDS: add materials related information + auto get_filament_name = [](int id) { + return Model::extruderParamsMap.find(id) != Model::extruderParamsMap.end() ? Model::extruderParamsMap.at(id).materialName : "PLA"; + }; ModelVolume *volume = NULL; for (size_t i = 0; i < object->volumes.size(); ++i) { if (object->volumes[i]->is_model_part()) { @@ -3946,24 +3999,35 @@ void ModelInstance::get_arrange_polygon(void *ap, const Slic3r::DynamicPrintConf return; } auto ve = object->volumes[i]->get_extruders(); - ret.extrude_ids.insert(ret.extrude_ids.end(), ve.begin(), ve.end()); + for (auto id : ve) { + ret.extrude_id_filament_types.insert({id, get_filament_name(id)}); + } } } // get per-object support extruders - auto op = object->get_config_value(config_global, "enable_support"); + auto op = object->get_config_value(config_global, "enable_support"); bool is_support_enabled = op && op->getBool(); if (is_support_enabled) { auto op1 = object->get_config_value(config_global, "support_filament"); auto op2 = object->get_config_value(config_global, "support_interface_filament"); - int extruder_id; + int extruder_id; // id==0 means follow previous material, so need not be recorded - if (op1 && (extruder_id = op1->getInt()) > 0) ret.extrude_ids.push_back(extruder_id); - if (op2 && (extruder_id = op2->getInt()) > 0) ret.extrude_ids.push_back(extruder_id); + if (op1 && (extruder_id = op1->getInt()) > 0) ret.extrude_id_filament_types.insert({extruder_id, get_filament_name(extruder_id)}); + if (op2 && (extruder_id = op2->getInt()) > 0) ret.extrude_id_filament_types.insert({extruder_id, get_filament_name(extruder_id)}); } - if (ret.extrude_ids.empty()) //the default extruder - ret.extrude_ids.push_back(1); + if (ret.extrude_id_filament_types.empty()) // the default extruder + ret.extrude_id_filament_types.insert({1, get_filament_name(1)}); +} + +void ModelInstance::apply_arrange_result(const Vec2d &offs, double rotation) +{ + // write the transformation data into the model instance + rotate(Eigen::AngleAxisd(rotation, Eigen::Vector3d::UnitZ()).toRotationMatrix()); + set_offset(X, unscale(offs(X))); + set_offset(Y, unscale(offs(Y))); + this->object->invalidate_bounding_box(); } indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, EnforcerBlockerType type) const @@ -3982,10 +4046,13 @@ void FacetsAnnotation::get_facets(const ModelVolume& mv, std::vectorset(selector); } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 1ad2d2d..fefcd00 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -728,7 +728,23 @@ enum class EnforcerBlockerType : int8_t { Extruder14, Extruder15, Extruder16, - ExtruderMax = Extruder16 + Extruder17, + Extruder18, + Extruder19, + Extruder20, + Extruder21, + Extruder22, + Extruder23, + Extruder24, + Extruder25, + Extruder26, + Extruder27, + Extruder28, + Extruder29, + Extruder30, + Extruder31, + Extruder32, + ExtruderMax = Extruder32 }; enum class ConversionType : int { @@ -754,7 +770,10 @@ public: indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const; // QDS void get_facets(const ModelVolume& mv, std::vector& facets_per_type) const; - void set_enforcer_block_type_limit(const ModelVolume& mv, EnforcerBlockerType max_type); + void set_enforcer_block_type_limit(const ModelVolume &mv, + EnforcerBlockerType max_type, + EnforcerBlockerType to_delete_filament = EnforcerBlockerType::NONE, + EnforcerBlockerType replace_filament = EnforcerBlockerType::NONE); indexed_triangle_set get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const; bool has_facets(const ModelVolume& mv, EnforcerBlockerType type) const; bool empty() const { return m_data.first.empty(); } @@ -960,6 +979,7 @@ public: // QDS std::vector get_extruders() const; void update_extruder_count(size_t extruder_count); + void update_extruder_count_when_delete_filament(size_t extruder_count, size_t filament_id, int replace_filament_id = -1); // Split this volume, append the result to the object owning this volume. // Return the number of volumes created from this one. @@ -1013,7 +1033,6 @@ public: double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); } void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } - void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); } const Vec3d &get_scaling_factor() const { return m_transformation.get_scaling_factor(); } double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } @@ -1072,7 +1091,7 @@ private: std::shared_ptr m_convex_hull; //QDS: add convex hull 2d related logic mutable Polygon m_convex_hull_2d; //QDS, used for convex_hell_2d acceleration - mutable Transform3d m_cached_trans_matrix; //QDS, used for convex_hell_2d acceleration + mutable Transform3d m_cached_trans_matrix{Transform3d::Identity()}; // QDS, used for convex_hell_2d acceleration mutable Polygon m_cached_2d_polygon; //QDS, used for convex_hell_2d acceleration Geometry::Transformation m_transformation; @@ -1249,6 +1268,7 @@ inline const ModelVolume* model_volume_find_by_id(const ModelVolumePtrs &model_v enum ModelInstanceEPrintVolumeState : unsigned char { ModelInstancePVS_Inside, + ModelInstancePVS_Limited, ModelInstancePVS_Partly_Outside, ModelInstancePVS_Fully_Outside, ModelInstanceNum_BedStates @@ -1316,19 +1336,9 @@ public: double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); } void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } - void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); } // QDS - void rotate(Matrix3d rotation_matrix) { - auto R = m_transformation.get_rotation_matrix(); - auto R_new = rotation_matrix * R; - auto euler_angles = Geometry::extract_euler_angles(R_new); - //BOOST_LOG_TRIVIAL(debug) << "old R:\n" - // << R.matrix() << "\nnew R:\n" - // << R_new.matrix() << "\nold euler angles: " << m_transformation.get_rotation().transpose() << "\n" - // << "new euler angles: " << euler_angles.transpose(); - set_rotation(euler_angles); - } + void rotate(Matrix3d rotation_matrix); const Vec3d& get_scaling_factor() const { return m_transformation.get_scaling_factor(); } double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } @@ -1371,14 +1381,7 @@ public: void get_arrange_polygon(void *arrange_polygon, const Slic3r::DynamicPrintConfig &config = Slic3r::DynamicPrintConfig()) const; // Apply the arrange result on the ModelInstance - void apply_arrange_result(const Vec2d& offs, double rotation) - { - // write the transformation data into the model instance - set_rotation(Z, rotation); - set_offset(X, unscale(offs(X))); - set_offset(Y, unscale(offs(Y))); - this->object->invalidate_bounding_box(); - } + void apply_arrange_result(const Vec2d &offs, double rotation); protected: friend class Print; @@ -1623,7 +1626,7 @@ public: static Polygon getBedPolygon() { return Model::printSpeedMap.bed_poly; } //QDS static functions that update extruder params and speed table static void setPrintSpeedTable(const DynamicPrintConfig& config, const PrintConfig& print_config); - static void setExtruderParams(const DynamicPrintConfig& config, int extruders_count); + static void setExtruderParams(const DynamicPrintConfig& config, int filament_count); // QDS: backup static Model read_from_archive( diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index d39edf0..2fcc561 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -119,7 +119,7 @@ arrangement::ArrangePolygon get_arrange_poly(ModelInstance* inst, const Slic3r:: ArrangePolygon get_instance_arrange_poly(ModelInstance* instance, const Slic3r::DynamicPrintConfig& config) { ArrangePolygon ap = get_arrange_poly(PtrWrapper{ instance }, config); - + int first_extruder_id = ap.extrude_id_filament_types.begin()->first - 1; //QDS: add temperature information if (config.has("curr_bed_type")) { ap.bed_temp = 0; @@ -127,29 +127,28 @@ ArrangePolygon get_instance_arrange_poly(ModelInstance* instance, const Slic3r:: BedType curr_bed_type = config.opt_enum("curr_bed_type"); const ConfigOptionInts* bed_opt = config.option(get_bed_temp_key(curr_bed_type)); - if (bed_opt != nullptr) - ap.bed_temp = bed_opt->get_at(ap.extrude_ids.front()-1); + if (bed_opt != nullptr) ap.bed_temp = bed_opt->get_at(first_extruder_id); const ConfigOptionInts* bed_opt_1st_layer = config.option(get_bed_temp_1st_layer_key(curr_bed_type)); if (bed_opt_1st_layer != nullptr) - ap.first_bed_temp = bed_opt_1st_layer->get_at(ap.extrude_ids.front()-1); + ap.first_bed_temp = bed_opt_1st_layer->get_at(first_extruder_id); } if (config.has("nozzle_temperature")) //get the print temperature - ap.print_temp = config.opt_int("nozzle_temperature", ap.extrude_ids.front() - 1); + ap.print_temp = config.opt_int_nullable("nozzle_temperature", first_extruder_id); if (config.has("nozzle_temperature_initial_layer")) //get the nozzle_temperature_initial_layer - ap.first_print_temp = config.opt_int("nozzle_temperature_initial_layer", ap.extrude_ids.front() - 1); + ap.first_print_temp = config.opt_int_nullable("nozzle_temperature_initial_layer", first_extruder_id); if (config.has("temperature_vitrification")) { - ap.vitrify_temp = config.opt_int("temperature_vitrification", ap.extrude_ids.front() - 1); + ap.vitrify_temp = config.opt_int("temperature_vitrification", first_extruder_id); } // get filament temp types auto* filament_types_opt = dynamic_cast(config.option("filament_type")); if (filament_types_opt) { std::set filament_temp_types; - for (auto i : ap.extrude_ids) { - std::string type_str = filament_types_opt->get_at(i-1); + for (auto id : ap.extrude_id_filament_types) { + std::string type_str = filament_types_opt->get_at(id.first-1); int temp_type = Print::get_filament_temp_type(type_str); filament_temp_types.insert(temp_type); } diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 358d9f4..e8defab 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1332,9 +1332,9 @@ static inline std::vector> mmu_segmentation_top_and_bott int granularity = 1; for (size_t i = 0; i < print_object.num_printing_regions(); ++ i) { const PrintRegionConfig &config = print_object.printing_region(i).config(); - max_top_layers = std::max(max_top_layers, config.top_shell_layers.value); - max_bottom_layers = std::max(max_bottom_layers, config.bottom_shell_layers.value); - granularity = std::max(granularity, std::max(config.top_shell_layers.value, config.bottom_shell_layers.value) - 1); + max_top_layers = std::max(max_top_layers, config.top_color_penetration_layers.value); + max_bottom_layers = std::max(max_bottom_layers, config.bottom_color_penetration_layers.value); + granularity = std::max(granularity, std::max(config.top_color_penetration_layers.value, config.bottom_color_penetration_layers.value) - 1); } // Project upwards pointing painted triangles over top surfaces, @@ -1473,15 +1473,15 @@ static inline std::vector> mmu_segmentation_top_and_bott // Minimum radius of a region to be printable. Used to filter regions by morphological opening. float small_region_threshold { 0.f }; // Maximum number of top layers for a queried color. - int top_shell_layers { 0 }; + int top_color_penetration_layers { 0 }; // Maximum number of bottom layers for a queried color. - int bottom_shell_layers { 0 }; + int bottom_color_penetration_layers { 0 }; //QDS: spacing according to width and layer height float extrusion_spacing{ 0.f }; }; PrintConfig print_config = print_object.print()->config(); float default_line_width = float(print_object.config().line_width.value); - auto layer_color_stat = [&layers = std::as_const(layers), print_config, default_line_width](const size_t layer_idx, const size_t color_idx) -> LayerColorStat { + auto layer_color_stat = [&layers = std::as_const(layers), print_config, default_line_width, &print_object](const size_t layer_idx, const size_t color_idx) -> LayerColorStat { LayerColorStat out; const Layer &layer = *layers[layer_idx]; for (const LayerRegion *region : layer.regions()) @@ -1493,9 +1493,9 @@ static inline std::vector> mmu_segmentation_top_and_bott float nozzle_diamter = float(print_config.nozzle_diameter.get_at(config.wall_filament - 1)); float outer_wall_line_width = float(config.outer_wall_line_width) == 0.0 ? (default_line_width == 0.0 ? nozzle_diamter : default_line_width) : float(config.outer_wall_line_width.value); out.extrusion_width = std::max(out.extrusion_width, outer_wall_line_width); - out.top_shell_layers = std::max(out.top_shell_layers, config.top_shell_layers); - out.bottom_shell_layers = std::max(out.bottom_shell_layers, config.bottom_shell_layers); - out.small_region_threshold = config.gap_infill_speed.value > 0 ? + out.top_color_penetration_layers = std::max(out.top_color_penetration_layers, config.top_color_penetration_layers); + out.bottom_color_penetration_layers = std::max(out.bottom_color_penetration_layers, config.bottom_color_penetration_layers); + out.small_region_threshold = config.gap_infill_speed.get_at(print_object.print()->get_extruder_id(config.wall_filament - 1)) > 0 ? // Gap fill enabled. Enable a single line of 1/2 extrusion width. 0.5f * outer_wall_line_width : // Gap fill disabled. Enable two lines slightly overlapping. @@ -1527,7 +1527,7 @@ static inline std::vector> mmu_segmentation_top_and_bott append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex); float offset = 0.f; ExPolygons layer_slices_trimmed = input_expolygons[layer_idx]; - for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx) { + for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_color_penetration_layers), int(0)); --last_idx) { //QDS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line //offset -= stat.extrusion_width ; offset -= (stat.extrusion_spacing + stat.extrusion_width); @@ -1547,7 +1547,7 @@ static inline std::vector> mmu_segmentation_top_and_bott append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex); float offset = 0.f; ExPolygons layer_slices_trimmed = input_expolygons[layer_idx]; - for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_shell_layers, num_layers); ++last_idx) { + for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_color_penetration_layers, num_layers); ++last_idx) { //QDS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line //offset -= stat.extrusion_width; offset -= (stat.extrusion_spacing + stat.extrusion_width); @@ -2309,7 +2309,9 @@ std::vector> multi_material_segmentation_by_painting(con BOOST_LOG_TRIVIAL(debug) << "MM segmentation - layers segmentation in parallel - end"; throw_on_cancel_callback(); - if (auto max_width = print_object.config().mmu_segmented_region_max_width, interlocking_depth = print_object.config().mmu_segmented_region_interlocking_depth; max_width > 0.f || interlocking_depth > 0.f) { + auto interlocking_beam = print_object.config().interlocking_beam; + if (auto max_width = print_object.config().mmu_segmented_region_max_width, interlocking_depth = print_object.config().mmu_segmented_region_interlocking_depth; + !interlocking_beam && (max_width > 0.f || interlocking_depth > 0.f)) { cut_segmented_layers(input_expolygons, segmented_regions, float(scale_(max_width)), float(scale_(interlocking_depth)), throw_on_cancel_callback); throw_on_cancel_callback(); } diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index 0076300..b0362f5 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -118,6 +118,21 @@ bool MultiPoint::remove_duplicate_points() return false; } +bool MultiPoint::remove_colinear_points() { + if (points.size() < 3) return false; + bool changed = false; + for (size_t i = 1; i < points.size() - 1; ) { + if (Line::distance_to_infinite_squared(points[i], points[i - 1], points[i+1]) < SCALED_EPSILON) { + points.erase(points.begin() + i); + changed = true; + } else { + ++ i; + } + } + if (points.size() < 3) points.clear(); + return changed; +} + bool MultiPoint::intersection(const Line& line, Point* intersection) const { Lines lines = this->lines(); @@ -454,4 +469,11 @@ BoundingBox get_extents_rotated(const MultiPoint &mp, double angle) return get_extents_rotated(mp.points, angle); } +void MultiPoint::symmetric_y(const coord_t &x_axis) +{ + for (Point &pt : points) { + pt(0) = 2 * x_axis - pt(0); + } +} + } diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 4c1f904..605ce55 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -77,6 +77,7 @@ public: bool has_duplicate_points() const; // Remove exact duplicates, return true if any duplicate has been removed. bool remove_duplicate_points(); + bool remove_colinear_points(); void clear() { this->points.clear(); } void append(const Point &point) { this->points.push_back(point); } void append(const Points &src) { this->append(src.begin(), src.end()); } @@ -94,7 +95,7 @@ public: bool intersection(const Line& line, Point* intersection) const; bool first_intersection(const Line& line, Point* intersection) const; bool intersections(const Line &line, Points *intersections) const; - + void symmetric_y(const coord_t &y_axis); static Points _douglas_peucker(const Points &points, const double tolerance); static Points visivalingam(const Points& pts, const double tolerance); static Points concave_hull_2d(const Points& pts, const double tolerence); diff --git a/src/libslic3r/ObjColorUtils.cpp b/src/libslic3r/ObjColorUtils.cpp index fbe39c4..979e2c9 100644 --- a/src/libslic3r/ObjColorUtils.cpp +++ b/src/libslic3r/ObjColorUtils.cpp @@ -5,10 +5,11 @@ bool obj_color_deal_algo(std::vector & input_colors, std::vector & cluster_colors_from_algo, std::vector & cluster_labels_from_algo, - char & cluster_number) + char & cluster_number, + int max_cluster) { QuantKMeans quant(10); - quant.apply(input_colors, cluster_colors_from_algo, cluster_labels_from_algo, (int) cluster_number); + quant.apply(input_colors, cluster_colors_from_algo, cluster_labels_from_algo, (int) cluster_number, max_cluster); if (cluster_number == -1) { return false; } diff --git a/src/libslic3r/ObjColorUtils.hpp b/src/libslic3r/ObjColorUtils.hpp index e654fac..5bb4563 100644 --- a/src/libslic3r/ObjColorUtils.hpp +++ b/src/libslic3r/ObjColorUtils.hpp @@ -266,4 +266,5 @@ public: bool obj_color_deal_algo(std::vector &input_colors, std::vector& cluster_colors_from_algo, std::vector& cluster_labels_from_algo, - char & cluster_number); \ No newline at end of file + char & cluster_number, + int max_cluster); \ No newline at end of file diff --git a/src/libslic3r/ParameterUtils.cpp b/src/libslic3r/ParameterUtils.cpp index 8254346..f160fa2 100644 --- a/src/libslic3r/ParameterUtils.cpp +++ b/src/libslic3r/ParameterUtils.cpp @@ -44,4 +44,36 @@ void get_other_layers_print_sequence(const std::vector &cust } } +int get_index_for_extruder_parameter(const DynamicPrintConfig &config, const std::string &opt_key, int cur_extruder_id, ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type) +{ + std::string id_name, variant_name; + unsigned int stride = 1; + if (printer_options_with_variant_1.count(opt_key) > 0) { // printer parameter + id_name = "printer_extruder_id"; + variant_name = "printer_extruder_variant"; + } else if (printer_options_with_variant_2.count(opt_key) > 0) { + id_name = "printer_extruder_id"; + variant_name = "printer_extruder_variant"; + stride = 2; + } else if (filament_options_with_variant.count(opt_key) > 0) { + // filament don't use id anymore + // id_name = "filament_extruder_id"; + variant_name = "filament_extruder_variant"; + } else if (print_options_with_variant.count(opt_key) > 0) { + id_name = "print_extruder_id"; + variant_name = "print_extruder_variant"; + } else { + return 0; + } + + // variant index + int variant_index = config.get_index_for_extruder(cur_extruder_id + 1, id_name, extruder_type, nozzle_volume_type, variant_name, stride); + if (variant_index < 0) { + assert(false); + return 0; + } + + return variant_index; +} + }; // namespace Slic3r diff --git a/src/libslic3r/ParameterUtils.hpp b/src/libslic3r/ParameterUtils.hpp index 090fd8d..795e7ff 100644 --- a/src/libslic3r/ParameterUtils.hpp +++ b/src/libslic3r/ParameterUtils.hpp @@ -3,12 +3,14 @@ #include #include +#include "PrintConfig.hpp" namespace Slic3r { using LayerPrintSequence = std::pair, std::vector>; std::vector get_other_layers_print_sequence(int sequence_nums, const std::vector &sequence); void get_other_layers_print_sequence(const std::vector &customize_sequences, int &sequence_nums, std::vector &sequence); +extern int get_index_for_extruder_parameter(const DynamicPrintConfig &config, const std::string &opt_key, int cur_extruder_id, ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type); } // namespace Slic3r #endif // slic3r_Parameter_Utils_hpp_ diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 10797b0..6e79467 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -7,6 +7,7 @@ #include "Clipper2Utils.hpp" #include "Arachne/WallToolPaths.hpp" #include "Line.hpp" +#include "Layer.hpp" #include #include #include @@ -18,6 +19,7 @@ static const double narrow_loop_length_threshold = 10; static const double min_degree_gap = 0.1; static const int max_overhang_degree = overhang_sampling_number - 1; static const std::vector non_uniform_degree_map = { 0, 10, 25, 50, 75, 100}; +static const int insert_point_count = 3; //QDS: when the width of expolygon is smaller than //ext_perimeter_width + ext_perimeter_spacing * (1 - SMALLER_EXT_INSET_OVERLAP_TOLERANCE), //we think it's small detail area and will generate smaller line width for it @@ -46,14 +48,16 @@ public: // QDS: is perimeter using smaller width bool is_smaller_width_perimeter; // Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole. - unsigned short depth; + unsigned short depth; // Should this contur be fuzzyfied on path generation? bool fuzzify; + // Slow down speed for circle + bool need_circle_compensation = false; // Children contour, may be both CCW and CW oriented (outer contours or holes). std::vector children; - - PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour, bool fuzzify, bool is_small_width_perimeter = false) : - polygon(polygon), is_contour(is_contour), is_smaller_width_perimeter(is_small_width_perimeter), depth(depth), fuzzify(fuzzify) {} + + PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour, bool fuzzify, bool is_small_width_perimeter = false, bool need_circle_compensation_ = false) : + polygon(polygon), is_contour(is_contour), is_smaller_width_perimeter(is_small_width_perimeter), depth(depth), fuzzify(fuzzify), need_circle_compensation(need_circle_compensation_) {} // External perimeter. It may be CCW or CW oriented (outer contour or hole contour). bool is_external() const { return this->depth == 0; } // An island, which may have holes, but it does not have another internal island. @@ -103,12 +107,13 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine& ext_lines, double fuzzy const double range_random_point_dist = fuzzy_skin_point_dist / 2.; double dist_left_over = double(rand()) * (min_dist_between_points / 2) / double(RAND_MAX); // the distance to be traversed on the line before making the first new point + // do not apply hole compensation in fuzzy skin mode auto* p0 = &ext_lines.front(); std::vector out; out.reserve(ext_lines.size()); for (auto& p1 : ext_lines) { if (p0->p == p1.p) { // Connect endpoints. - out.emplace_back(p1.p, p1.w, p1.perimeter_index); + out.emplace_back(p1.p, p1.w, p1.perimeter_index, false); continue; } @@ -119,7 +124,7 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine& ext_lines, double fuzzy double dist_last_point = dist_left_over + p0p1_size * 2.; for (double p0pa_dist = dist_left_over; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + double(rand()) * range_random_point_dist / double(RAND_MAX)) { double r = double(rand()) * (fuzzy_skin_thickness * 2.) / double(RAND_MAX) - fuzzy_skin_thickness; - out.emplace_back(p0->p + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast(), p1.w, p1.perimeter_index); + out.emplace_back(p0->p + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast(), p1.w, p1.perimeter_index, false); dist_last_point = p0pa_dist; } dist_left_over = p0p1_size - dist_last_point; @@ -128,7 +133,7 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine& ext_lines, double fuzzy while (out.size() < 3) { size_t point_idx = ext_lines.size() - 2; - out.emplace_back(ext_lines[point_idx].p, ext_lines[point_idx].w, ext_lines[point_idx].perimeter_index); + out.emplace_back(ext_lines[point_idx].p, ext_lines[point_idx].w, ext_lines[point_idx].perimeter_index,false); if (point_idx == 0) break; --point_idx; @@ -190,7 +195,7 @@ static void lowpass_filter_by_paths_overhang_degree(ExtrusionPaths& paths) { if (paths[index].role() == erOverhangPerimeter) break; temp_length = paths[index].length(); - if (temp_length > right_total_length) + if (temp_length > right_total_length) neighbor_path.emplace_back(std::pair(right_total_length, old_overhang_series[index])); else neighbor_path.emplace_back(std::pair(temp_length, old_overhang_series[index])); @@ -338,6 +343,37 @@ public: } }; +static void sampling_at_line_end(Polyline &poly, double mini_length, int insert_count) +{ + + + Point end_point = poly.last_point(); + const double length = poly.length(); + if (length < mini_length * 2) + return; + + if (length < mini_length * (insert_count * 2 - 1)) + insert_count = 0.5 * length / mini_length; + + poly.points.pop_back(); + double length_count = 0; + Point dir = end_point - poly.first_point(); + + for (int count = 0; count < insert_count; ++count) { + length_count += mini_length; + poly.append(poly.first_point() + dir * (length_count / length)); + } + + dir *= -1; + for (int count = 0; count < insert_count; ++count) { + + poly.append(end_point + dir * (length_count / length)); + + length_count -= mini_length; + } + poly.append(end_point); +} + static std::deque detect_overahng_degree(Polygons lower_polygons, Polylines middle_overhang_polyines, const double &lower_bound, @@ -356,6 +392,9 @@ static std::deque detect_overahng_degree(Polygons low for (size_t polyline_idx = 0; polyline_idx < middle_overhang_polyines.size(); ++polyline_idx) { //filter too short polyline Polyline middle_poly = middle_overhang_polyines[polyline_idx]; + //sample at start and end + if (middle_poly.size() == 2) + sampling_at_line_end(middle_poly, (upper_bound - lower_bound) / 2, insert_point_count); Polyline polyline_with_insert_points; points_overhang.clear(); @@ -454,7 +493,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime for (const PerimeterGeneratorLoop &loop : loops) { bool is_external = loop.is_external(); bool is_small_width = loop.is_smaller_width_perimeter; - + CustomizeFlag flag = loop.need_circle_compensation ? CustomizeFlag::cfCircleCompensation : CustomizeFlag::cfNone; + ExtrusionRole role; ExtrusionLoopRole loop_role; role = is_external ? erExternalPerimeter : erPerimeter; @@ -526,7 +566,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime remain_polines = diff_pl_2({to_polyline(polygon)}, lower_polygons_series_clipped); - bool detect_overhang_degree = perimeter_generator.config->enable_overhang_speed && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None; + bool detect_overhang_degree = perimeter_generator.config->enable_overhang_speed.get_at(get_extruder_index(*(perimeter_generator.print_config), perimeter_generator.config->wall_filament - 1)) + && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None; //QDS: fuzziy skin may generate a line that approximates a point, which can cause the clipper to get empty results if (loop.fuzzify && remain_polines.empty() && inside_polines.empty()) { @@ -579,7 +620,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime overhang_dist_boundary->second); // QDS: add path with overhang degree - for (PolylineWithDegree polylines_collection : polylines_degree_collection) { + for (PolylineWithDegree &polylines_collection : polylines_degree_collection) { extrusion_paths_append(paths, std::move(polylines_collection.polyline), polylines_collection.overhang_degree, @@ -634,17 +675,32 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime paths.emplace_back(std::move(path)); } - coll.append(ExtrusionLoop(std::move(paths), loop_role)); + for (ExtrusionPath& path : paths) { + path.set_customize_flag(flag); + } + + coll.append(ExtrusionLoop(std::move(paths), loop_role, flag)); } - + // Append thin walls to the nearest-neighbor search (only for first iteration) + Point zero_point(0, 0); + if (! thin_walls.empty()) { + BoundingBox bbox; + for (auto &entity : coll.entities) { bbox.merge(entity->as_polyline().bounding_box()); } + for (auto& thin_wall : thin_walls) { + // find the corner of bbox that's farthest from the thin wall + Point corner_far = bbox.min; + if ((corner_far.cast() - thin_wall.first_point().cast()).squaredNorm() < + (bbox.max.cast() - thin_wall.first_point().cast()).squaredNorm()) + corner_far = bbox.max; + zero_point = corner_far; + } variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow, coll.entities); thin_walls.clear(); } - + // Traverse children and build the final collection. - Point zero_point(0, 0); std::vector> chain = chain_extrusion_entities(coll.entities, &zero_point); ExtrusionEntityCollection out; for (const std::pair &idx : chain) { @@ -683,7 +739,7 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path& subject, con clipper.ZFillFunction([](const ClipperLib_Z::IntPoint& e1bot, const ClipperLib_Z::IntPoint& e1top, const ClipperLib_Z::IntPoint& e2bot, const ClipperLib_Z::IntPoint& e2top, ClipperLib_Z::IntPoint& pt) { // The clipping contour may be simplified by clipping it with a bounding box of "subject" path. - // The clipping function used may produce self intersections outside of the "subject" bounding box. Such self intersections are + // The clipping function used may produce self intersections outside of the "subject" bounding box. Such self intersections are // harmless to the result of the clipping operation, // Both ends of each edge belong to the same source: Either they are from subject or from clipping path. assert(e1bot.z() >= 0 && e1top.z() >= 0); @@ -892,13 +948,32 @@ static void detect_brigde_wall_arachne(const PerimeterGenerator &perimeter_gener static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& perimeter_generator, std::vector& pg_extrusions) { ExtrusionEntityCollection extrusion_coll; - for (PerimeterGeneratorArachneExtrusion& pg_extrusion : pg_extrusions) { + if (perimeter_generator.print_config->z_direction_outwall_speed_continuous) + extrusion_coll.loop_node_range.first = perimeter_generator.loop_nodes->size(); + + for (PerimeterGeneratorArachneExtrusion &pg_extrusion : pg_extrusions) { Arachne::ExtrusionLine* extrusion = pg_extrusion.extrusion; if (extrusion->empty()) continue; - + //get extrusion date const bool is_external = extrusion->inset_idx == 0; - ExtrusionRole role = is_external ? erExternalPerimeter : erPerimeter; + ExtrusionRole role = is_external ? erExternalPerimeter : erPerimeter; + + if (is_external && perimeter_generator.print_config->z_direction_outwall_speed_continuous) { + LoopNode node; + NodeContour node_contour; + node_contour.is_loop = extrusion->is_closed; + for (size_t i = 0; i < extrusion->junctions.size(); i++) { + node_contour.pts.push_back(extrusion->junctions[i].p); + node_contour.widths.push_back(extrusion->junctions[i].w); + } + node.node_contour = node_contour; + node.node_id = perimeter_generator.loop_nodes->size(); + node.loop_id = extrusion_coll.entities.size(); + node.bbox = get_extents(node.node_contour.pts); + node.bbox.offset(perimeter_generator.config->outer_wall_line_width/2); + perimeter_generator.loop_nodes->push_back(std::move(node)); + } if (pg_extrusion.fuzzify) fuzzy_extrusion_line(*extrusion, scaled(perimeter_generator.config->fuzzy_skin_thickness.value), scaled(perimeter_generator.config->fuzzy_skin_point_distance.value)); @@ -938,7 +1013,8 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p extrusion_paths_append(temp_paths, clip_extrusion(extrusion_path, lower_slices_paths, ClipperLib_Z::ctIntersection), role, is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow); - if (perimeter_generator.config->enable_overhang_speed && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) { + if (perimeter_generator.config->enable_overhang_speed.get_at(get_extruder_index(*(perimeter_generator.print_config), perimeter_generator.config->wall_filament - 1)) + && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) { Flow flow = is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow; std::map> clipper_serise; @@ -986,7 +1062,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p } else { paths = std::move(temp_paths); - + } // get overhang paths by checking what parts of this loop fall // outside the grown lower slices (thus where the distance between @@ -1036,7 +1112,8 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p chain_and_reorder_extrusion_paths(paths, &start_point); - if (perimeter_generator.config->enable_overhang_speed && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) { + if (perimeter_generator.config->enable_overhang_speed.get_at(get_extruder_index(*(perimeter_generator.print_config), perimeter_generator.config->wall_filament - 1)) + && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) { // QDS: filter the speed smooth_overhang_level(paths); } @@ -1047,6 +1124,8 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p extrusion_paths_append(paths, *extrusion, role, is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow); } + bool apply_hole_compensation = extrusion->shouldApplyHoleCompensation(); + // Append paths to collection. if (!paths.empty()) { if (extrusion->is_closed) { @@ -1063,6 +1142,11 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p } assert(extrusion_loop.paths.front().first_point() == extrusion_loop.paths.back().last_point()); + if (apply_hole_compensation) { + for (auto& path : extrusion_loop.paths) + path.set_customize_flag(CustomizeFlag::cfCircleCompensation); + extrusion_loop.set_customize_flag(CustomizeFlag::cfCircleCompensation); + } extrusion_coll.append(std::move(extrusion_loop)); } else { @@ -1085,14 +1169,35 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p multi_path.paths.emplace_back(std::move(*it_path)); } + if (apply_hole_compensation) { + for (auto& path : multi_path.paths) + path.set_customize_flag(CustomizeFlag::cfCircleCompensation); + multi_path.set_customize_flag(CustomizeFlag::cfCircleCompensation); + } extrusion_coll.append(ExtrusionMultiPath(std::move(multi_path))); } } + } + if (perimeter_generator.print_config->z_direction_outwall_speed_continuous) + extrusion_coll.loop_node_range.second = perimeter_generator.loop_nodes->size(); return extrusion_coll; } +static Polygons to_polygons_with_flag(const ExPolygon& src, const bool contour_flag, const std::vector& holes_flag, std::vector& flags_out) +{ + Polygons polygons; + polygons.reserve(src.num_contours()); + polygons.push_back(src.contour); + polygons.insert(polygons.end(), src.holes.begin(), src.holes.end()); + flags_out.reserve(holes_flag.size() + 1); + if(contour_flag == true) + flags_out.emplace_back(0); + for (size_t idx = 0; idx < holes_flag.size(); ++idx) + flags_out.emplace_back(holes_flag[idx] + 1); + return polygons; +} void PerimeterGenerator::process_classic() @@ -1101,19 +1206,19 @@ void PerimeterGenerator::process_classic() m_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); coord_t perimeter_width = this->perimeter_flow.scaled_width(); coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing(); - + // external perimeters m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width(); coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); coord_t ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing())); - + // overhang perimeters m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); - + // solid infill coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing(); - + // Calculate the minimum required spacing between two adjacent traces. // This should be equal to the nominal flow spacing but we experiment // with some tolerance in order to avoid triggering medial axis when @@ -1126,7 +1231,7 @@ void PerimeterGenerator::process_classic() // internal flow which is unrelated. coord_t min_spacing = coord_t(perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE)); coord_t ext_min_spacing = coord_t(ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE)); - bool has_gap_fill = this->config->gap_infill_speed.value > 0; + bool has_gap_fill = this->config->gap_infill_speed.get_at(get_extruder_index(*print_config, this->config->wall_filament - 1)) > 0; // QDS: this flow is for smaller external perimeter for small area coord_t ext_min_spacing_smaller = coord_t(ext_perimeter_spacing * (1 - SMALLER_EXT_INSET_OVERLAP_TOLERANCE)); @@ -1166,10 +1271,30 @@ void PerimeterGenerator::process_classic() if (loop_number > 0 && ((this->object_config->top_one_wall_type != TopOneWallType::None && this->upper_slices == nullptr) || (this->object_config->only_one_wall_first_layer && layer_id == 0))) loop_number = 0; + bool counter_circle_compensation = surface.counter_circle_compensation; + std::vector compensation_holes_centers; + for (int i : surface.holes_circle_compensation) { + Point center = surface.expolygon.holes[i].centroid(); + compensation_holes_centers.emplace_back(center); + } + + double eps = 1000; + auto is_compensation_hole = [&compensation_holes_centers, &eps](const Polygon &hole) -> bool { + auto iter = std::find_if(compensation_holes_centers.begin(), compensation_holes_centers.end(), [&hole, &eps](const Point &item) { + double distance = std::sqrt(std::pow(hole.centroid().x() - item.x(), 2) + std::pow(hole.centroid().y() - item.y(), 2)); + return distance < eps; + }); + + return iter != compensation_holes_centers.end(); + }; + ExPolygons last = union_ex(surface.expolygon.simplify_p(surface_simplify_resolution)); + if (last.size() != 1) + counter_circle_compensation = false; ExPolygons gaps; ExPolygons top_fills; ExPolygons fill_clip; + std::vector outwall_paths; 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 @@ -1237,16 +1362,16 @@ void PerimeterGenerator::process_classic() coord_t distance = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing; //QDS //offsets = this->config->thin_walls ? - // This path will ensure, that the perimeters do not overfill, as in + // This path will ensure, that the perimeters do not overfill, as in // 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 + // 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.)) : - // If "detect thin walls" is not enabled, this paths will be entered, which + // If "detect thin walls" is not enabled, this paths will be entered, which // leads to overflows, as in prusa3d/Slic3r GH #32 //offset_ex(last, - float(distance)); @@ -1284,23 +1409,51 @@ void PerimeterGenerator::process_classic() // outer contour may overlap with itself. //FIXME evaluate the overlaps, annotate each point with an overlap depth, // compensate for the depth of intersection. - contours[i].emplace_back(expolygon.contour, i, true, fuzzify_contours); - + contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, fuzzify_contours, false, counter_circle_compensation)); if (!expolygon.holes.empty()) { holes[i].reserve(holes[i].size() + expolygon.holes.size()); - for (const Polygon& hole : expolygon.holes) - holes[i].emplace_back(hole, i, false, fuzzify_holes); + for (const Polygon &hole : expolygon.holes) + holes[i].emplace_back(hole, i, false, fuzzify_holes, false, is_compensation_hole(hole)); } } //QDS: save perimeter loop which use smaller width if (i == 0) { + //store outer wall + if (print_config->z_direction_outwall_speed_continuous) { + // not loop + for (const ThickPolyline &polyline : thin_walls) { + NodeContour node_contour; + node_contour.is_loop = false; + node_contour.pts = polyline.points; + node_contour.widths.insert(node_contour.widths.end(), polyline.width.begin(), polyline.width.end()); + outwall_paths.push_back(node_contour); + } + + // loop + for (const Polyline &polyline : to_polylines(offsets_with_smaller_width)) { + NodeContour node_contour; + node_contour.is_loop = true; + node_contour.pts = polyline.points; + node_contour.widths.push_back(scale_(this->smaller_ext_perimeter_flow.width())); + outwall_paths.push_back(node_contour); + } + + for (const Polyline &polyline : to_polylines(offsets)) { + NodeContour node_contour; + node_contour.is_loop = true; + node_contour.pts = polyline.points; + node_contour.widths.push_back(scale_(this->ext_perimeter_flow.width())); + outwall_paths.push_back(node_contour); + } + } + for (const ExPolygon& expolygon : offsets_with_smaller_width) { - contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, fuzzify_contours, true)); + contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, fuzzify_contours, true, counter_circle_compensation)); if (!expolygon.holes.empty()) { holes[i].reserve(holes[i].size() + expolygon.holes.size()); for (const Polygon& hole : expolygon.holes) - holes[i].emplace_back(PerimeterGeneratorLoop(hole, i, false, fuzzify_contours, true)); + holes[i].emplace_back(PerimeterGeneratorLoop(hole, i, false, fuzzify_contours, true, is_compensation_hole(hole))); } } } @@ -1320,7 +1473,7 @@ void PerimeterGenerator::process_classic() else offset_top_surface = 0; //don't takes into account too thin areas - double min_width_top_surface = (this->object_config->top_area_threshold / 100) * std::max(double(ext_perimeter_spacing / 2 + 10), 1.0 * (double(perimeter_width))); + double min_width_top_surface = (this->object_config->top_area_threshold / 100) * std::max(ext_perimeter_spacing / 2.0, perimeter_width / 2.0); //QDS: get boungding box of last BoundingBox last_box = get_extents(last); @@ -1379,7 +1532,7 @@ void PerimeterGenerator::process_classic() if (i == loop_number && (! has_gap_fill || this->config->sparse_infill_density.value == 0)) { // The last run of this loop is executed to collect gaps for gap fill. - // As the gap fill is either disabled or not + // As the gap fill is either disabled or not break; } } @@ -1443,7 +1596,7 @@ void PerimeterGenerator::process_classic() // 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 - bool is_outer_wall_first = + bool is_outer_wall_first = this->object_config->wall_sequence == WallSequence::OuterInner; if (is_outer_wall_first || //QDS: always print outer wall first when there indeed has brim. @@ -1471,6 +1624,48 @@ void PerimeterGenerator::process_classic() } entities.entities = std::move( entities_reorder); } + + //QDS: add node for loops + if (!outwall_paths.empty() && this->layer_id > 0) { + entities.loop_node_range.first = this->loop_nodes->size(); + if (outwall_paths.size() == 1) { + LoopNode node; + node.node_id = this->loop_nodes->size(); + node.loop_id = 0; + node.node_contour = outwall_paths.front(); + node.bbox = get_extents(node.node_contour.pts); + node.bbox.offset(SCALED_EPSILON); + this->loop_nodes->push_back(node); + } else { + std::vector matched; + matched.resize(outwall_paths.size(), false); + for (int entity_idx = 0; entity_idx < entities.entities.size(); ++entity_idx) { + //skip inner wall + if(entities.entities[entity_idx]->role() == erPerimeter) + continue; + + for (size_t lines_idx = 0; lines_idx < outwall_paths.size(); ++lines_idx) { + if(matched[lines_idx]) + continue; + + if (entities.entities[entity_idx]->first_point().is_in_lines(outwall_paths[lines_idx].pts)) { + matched[lines_idx] = true; + LoopNode node; + node.node_id = this->loop_nodes->size(); + node.loop_id = entity_idx; + node.node_contour = outwall_paths[lines_idx]; + node.bbox = get_extents(node.node_contour.pts); + node.bbox.offset(SCALED_EPSILON); + this->loop_nodes->push_back(node); + break; + } + } + } + } + entities.loop_node_range.second = this->loop_nodes->size(); + } + + // append perimeters for this slice as a collection if (! entities.empty()) this->loops->append(entities); @@ -1478,7 +1673,7 @@ void PerimeterGenerator::process_classic() // fill gaps if (! gaps.empty()) { - // collapse + // collapse double min = 0.2 * perimeter_width * (1 - INSET_OVERLAP_TOLERANCE); double max = 2. * perimeter_spacing; ExPolygons gaps_ex = diff_ex( @@ -1532,7 +1727,7 @@ void PerimeterGenerator::process_classic() // 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 // non-collapsing regions - coord_t inset = + coord_t inset = (loop_number < 0) ? 0 : (loop_number == 0) ? // one loop @@ -1624,6 +1819,7 @@ void PerimeterGenerator::process_arachne() { // other perimeters m_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); + coord_t perimeter_width = this->perimeter_flow.scaled_width(); coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing(); // external perimeters @@ -1652,103 +1848,130 @@ void PerimeterGenerator::process_arachne() double surface_simplify_resolution = (print_config->enable_arc_fitting && this->config->fuzzy_skin == FuzzySkinType::None) ? 0.2 * m_scaled_resolution : m_scaled_resolution; // we need to process each island separately because we might have different // extra perimeters for each one + for (const Surface& surface : this->slices->surfaces) { // detect how many perimeters must be generated for this island int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops - if (loop_number > 0 && this->object_config->only_one_wall_first_layer && layer_id == 0 || - (this->object_config->top_one_wall_type == TopOneWallType::Topmost && this->upper_slices == nullptr)) - loop_number = 0; + bool apply_circle_compensation = true; ExPolygons last = offset_ex(surface.expolygon.simplify_p(surface_simplify_resolution), -float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); - Polygons last_p = to_polygons(last); + int new_size = std::accumulate(last.begin(), last.end(), 0, [](int prev, const ExPolygon& expoly) { return prev + expoly.num_contours(); }); + if (last.size() != 1 || new_size != surface.expolygon.num_contours()) + apply_circle_compensation = false; - double min_nozzle_diameter = *std::min_element(print_config->nozzle_diameter.values.begin(), print_config->nozzle_diameter.values.end()); - Arachne::WallToolPathsParams input_params; - { - if (const auto& min_feature_size_opt = object_config->min_feature_size) - input_params.min_feature_size = min_feature_size_opt.value * 0.01 * min_nozzle_diameter; + std::vector circle_poly_indices; + Polygons last_p; + if (apply_circle_compensation) + last_p = to_polygons_with_flag(last.front(), surface.counter_circle_compensation, surface.holes_circle_compensation, circle_poly_indices); + else + last_p = to_polygons(last); - if (const auto& min_bead_width_opt = object_config->min_bead_width) - input_params.min_bead_width = min_bead_width_opt.value * 0.01 * min_nozzle_diameter; + std::vector total_perimeters; + ExPolygons infill_contour; - if (const auto& wall_transition_filter_deviation_opt = object_config->wall_transition_filter_deviation) - input_params.wall_transition_filter_deviation = wall_transition_filter_deviation_opt.value * 0.01 * min_nozzle_diameter; + if (loop_number >= 0) { + bool generate_one_wall_by_first_layer = this->object_config->only_one_wall_first_layer && layer_id == 0; + bool generate_one_wall_by_top_most = this->object_config->top_one_wall_type != TopOneWallType::None && this->upper_slices == nullptr; + bool generate_one_wall_by_top = this->object_config->top_one_wall_type == TopOneWallType::Alltop && this->upper_slices != nullptr; - if (const auto& wall_transition_length_opt = object_config->wall_transition_length) - input_params.wall_transition_length = wall_transition_length_opt.value * 0.01 * min_nozzle_diameter; + bool is_one_wall = loop_number == 0 || generate_one_wall_by_first_layer || generate_one_wall_by_top_most; + // whether to seperate the generatation of wall into two parts,first generate outer wall,then generate the remaining wall + bool seperate_wall_generation = !is_one_wall && generate_one_wall_by_top; - input_params.wall_transition_angle = this->object_config->wall_transition_angle.value; - input_params.wall_distribution_count = this->object_config->wall_distribution_count.value; - } + double min_nozzle_diameter = *std::min_element(print_config->nozzle_diameter.values.begin(), print_config->nozzle_diameter.values.end()); + Arachne::WallToolPathsParams input_params; + { + if (const auto& min_feature_size_opt = object_config->min_feature_size) + input_params.min_feature_size = min_feature_size_opt.value * 0.01 * min_nozzle_diameter; - int remain_loops = -1; - if (loop_number > 0 && this->object_config->top_one_wall_type == TopOneWallType::Alltop) { - if (this->upper_slices != nullptr) - remain_loops = loop_number - 1; + if (const auto& min_bead_width_opt = object_config->min_bead_width) + input_params.min_bead_width = min_bead_width_opt.value * 0.01 * min_nozzle_diameter; - loop_number = 0; - } + if (const auto& wall_transition_filter_deviation_opt = object_config->wall_transition_filter_deviation) + input_params.wall_transition_filter_deviation = wall_transition_filter_deviation_opt.value * 0.01 * min_nozzle_diameter; - Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, layer_height, input_params); - std::vector perimeters = wallToolPaths.getToolPaths(); - loop_number = int(perimeters.size()) - 1; + if (const auto& wall_transition_length_opt = object_config->wall_transition_length) + input_params.wall_transition_length = wall_transition_length_opt.value * 0.01 * min_nozzle_diameter; - //QDS: top one wall for arachne - ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour()); - ExPolygons inner_infill_contour; - - if( remain_loops >= 0 ) - { - ExPolygons the_layer_surface = infill_contour; - // QDS: get boungding box of last - BoundingBox infill_contour_box = get_extents(infill_contour); - infill_contour_box.offset(SCALED_EPSILON); - - // QDS: get the Polygons upper the polygon this layer - Polygons upper_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->upper_slices, infill_contour_box); - - infill_contour = diff_ex(infill_contour, upper_polygons_series_clipped); - - coord_t perimeter_width = this->perimeter_flow.scaled_width(); - //QDS: add bridge area - if (this->lower_slices != nullptr) { - BoundingBox infill_contour_box = get_extents(infill_contour); - infill_contour_box.offset(SCALED_EPSILON); - // QDS: get the Polygons below the polygon this layer - Polygons lower_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->lower_slices, 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); + input_params.wall_transition_angle = this->object_config->wall_transition_angle.value; + input_params.wall_distribution_count = this->object_config->wall_distribution_count.value; } - //QDS: filter small area and extend top surface a bit to hide the wall line - double min_width_top_surface = (this->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); - //QDS: get the inner surface that not export to top - ExPolygons surface_not_export_to_top = diff_ex(the_layer_surface, infill_contour); + // these variables are only valid if need to seperate wall generation + ExPolygons top_expolys_by_one_wall; + std::vector first_perimeters; + ExPolygons infill_contour_by_one_wall; - //QDS: get real top surface - 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, layer_height, input_params); + // do detail check whether to enable one wall + if (seperate_wall_generation) { + Arachne::WallToolPaths one_wall_paths(last_p, ext_perimeter_spacing, perimeter_spacing, 1, 0, layer_height, input_params); + if (apply_circle_compensation) + one_wall_paths.EnableHoleCompensation(true, circle_poly_indices); - std::vector perimeters_inner = innerWallToolPaths.getToolPaths(); - remain_loops = int(perimeters_inner.size()) - 1; + first_perimeters = one_wall_paths.getToolPaths(); + infill_contour_by_one_wall = union_ex(one_wall_paths.getInnerContour()); - //QDS: set wall's perporsity - if (!perimeters.empty()) { - for (int perimeter_idx = 0; perimeter_idx < perimeters_inner.size(); perimeter_idx++) { - if (perimeters_inner[perimeter_idx].empty()) continue; + BoundingBox infill_bbox = get_extents(infill_contour_by_one_wall); + infill_bbox.offset(EPSILON); - for (Arachne::ExtrusionLine &wall : perimeters_inner[perimeter_idx]) { - // QDS: 0 means outer wall - wall.inset_idx++; + Polygons upper_polygons_clipped; + if (this->upper_slices) + upper_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->upper_slices, infill_bbox); + top_expolys_by_one_wall = diff_ex(infill_contour_by_one_wall, upper_polygons_clipped); + + Polygons lower_polygons_clipped; + if (this->lower_slices) + lower_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->lower_slices, infill_bbox); + ExPolygons bottom_expolys = diff_ex(top_expolys_by_one_wall, lower_polygons_clipped); + + top_expolys_by_one_wall = diff_ex(top_expolys_by_one_wall, bottom_expolys); + seperate_wall_generation = should_enable_top_one_wall(last, top_expolys_by_one_wall); + } + + if (seperate_wall_generation) { + // only generate one wall around top areas + // keep the first generated wall + total_perimeters = first_perimeters; + infill_contour = union_ex(infill_contour_by_one_wall); + // deal with remaining walls to be generated + if (loop_number > 0) { + last = diff_ex(infill_contour_by_one_wall, top_expolys_by_one_wall); + last_p = to_polygons(last); // disable contour compensation in remaining walls + Arachne::WallToolPaths paths_new(last_p, perimeter_spacing, perimeter_spacing, loop_number, 0, layer_height, input_params); + auto new_perimeters = paths_new.getToolPaths(); + for (auto& perimeters : new_perimeters) { + if (!perimeters.empty()) { + for (auto& p : perimeters) { + p.inset_idx += 1; + } + total_perimeters.emplace_back(std::move(perimeters)); + } } + infill_contour = union_ex(union_ex(paths_new.getInnerContour()), top_expolys_by_one_wall); + infill_contour = intersection_ex(infill_contour, infill_contour_by_one_wall); } } - perimeters.insert(perimeters.end(), perimeters_inner.begin(), perimeters_inner.end()); - - inner_infill_contour = union_ex(innerWallToolPaths.getInnerContour()); + else { + if (is_one_wall) { + // plan wall width as one wall + Arachne::WallToolPaths one_wall_paths(last_p, ext_perimeter_spacing, perimeter_spacing, 1, 0, layer_height, input_params); + if (apply_circle_compensation) + one_wall_paths.EnableHoleCompensation(true, circle_poly_indices); + total_perimeters = one_wall_paths.getToolPaths(); + infill_contour = union_ex(one_wall_paths.getInnerContour()); + } + else { + // plan wall width as noraml + Arachne::WallToolPaths normal_paths(last_p, ext_perimeter_spacing, perimeter_spacing, loop_number + 1, 0, layer_height, input_params); + if (apply_circle_compensation) + normal_paths.EnableHoleCompensation(true, circle_poly_indices); + total_perimeters = normal_paths.getToolPaths(); + infill_contour = union_ex(normal_paths.getInnerContour()); + } + } + } + else { + infill_contour = last; } #ifdef ARACHNE_DEBUG @@ -1761,15 +1984,15 @@ void PerimeterGenerator::process_arachne() // 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) + assert([&total_perimeters = std::as_const(total_perimeters)]() -> bool { + for (const Arachne::VariableWidthLines& perimeter : total_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 start_perimeter = int(total_perimeters.size()) - 1; int end_perimeter = -1; int direction = -1; @@ -1777,15 +2000,15 @@ void PerimeterGenerator::process_arachne() this->object_config->wall_sequence == WallSequence::OuterInner || this->object_config->wall_sequence == WallSequence::InnerOuterInner; if (is_outer_wall_first) { start_perimeter = 0; - end_perimeter = int(perimeters.size()); + end_perimeter = int(total_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()) + if (total_perimeters[perimeter_idx].empty()) continue; - for (Arachne::ExtrusionLine& wall : perimeters[perimeter_idx]) + for (Arachne::ExtrusionLine& wall : total_perimeters[perimeter_idx]) all_extrusions.emplace_back(&wall); } @@ -1947,20 +2170,41 @@ void PerimeterGenerator::process_arachne() if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(*this, ordered_extrusions); !extrusion_coll.empty()) this->loops->append(extrusion_coll); - const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing; + const coord_t spacing = (total_perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing; // 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 add_infill_contour_for_arachne(infill_contour, loop_number, ext_perimeter_spacing, perimeter_spacing, min_perimeter_infill_spacing, spacing, false); - //QDS: add infill_contour of top one wall part - if( !inner_infill_contour.empty() ) - add_infill_contour_for_arachne(inner_infill_contour, remain_loops, ext_perimeter_spacing, perimeter_spacing, min_perimeter_infill_spacing, spacing, true); - } } +// expand the top expoly and determine whether to enable top one wall feature +bool PerimeterGenerator::should_enable_top_one_wall(const ExPolygons& original_expolys, ExPolygons& top) +{ + coord_t perimeter_width = this->perimeter_flow.scaled_width(); + coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); + + auto get_expolygs_area = [](const ExPolygons& expolys)->double{ + return std::accumulate(expolys.begin(), expolys.end(), (double)(0), [](double val, const ExPolygon& expoly) { + return val + expoly.area(); + }); + }; + + //QDS: filter small area and extend top surface a bit to hide the wall line + double min_width_top_surface = (this->object_config->top_area_threshold / 100) * std::max(ext_perimeter_spacing / 2.0, perimeter_width / 2.0); + auto shrunk_top = offset_ex(top, - min_width_top_surface); + double shrunk_area = get_expolygs_area(shrunk_top); + double original_area = get_expolygs_area(original_expolys); + + if (shrunk_area / (original_area + EPSILON) < 0.1 || original_area < scale_(1)*scale_(1)) + top.clear(); + else + top = offset_ex(shrunk_top, min_width_top_surface + perimeter_width); + return !top.empty(); +} + bool PerimeterGeneratorLoop::is_internal_contour() const { // An internal contour is a contour containing no other contours diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index f707858..25cd195 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -40,7 +40,7 @@ public: std::pair m_lower_overhang_dist_boundary; std::pair m_external_overhang_dist_boundary; std::pair m_smaller_external_overhang_dist_boundary; - + std::vector *loop_nodes; PerimeterGenerator( // Input: @@ -59,20 +59,27 @@ public: // Infills without the gap fills SurfaceCollection* fill_surfaces, //QDS - ExPolygons* fill_no_overlap) + ExPolygons* fill_no_overlap, + std::vector *loop_nodes) : slices(slices), upper_slices(nullptr), lower_slices(nullptr), layer_height(layer_height), layer_id(-1), perimeter_flow(flow), ext_perimeter_flow(flow), overhang_flow(flow), solid_infill_flow(flow), config(config), object_config(object_config), print_config(print_config), m_spiral_vase(spiral_mode), - m_scaled_resolution(scaled(print_config->resolution.value > EPSILON ? print_config->resolution.value : EPSILON)), - loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces), fill_no_overlap(fill_no_overlap), + m_scaled_resolution(scaled(print_config->resolution.value > EPSILON ? print_config->resolution.value : EPSILON)), loops(loops), + gap_fill(gap_fill), + fill_surfaces(fill_surfaces), + fill_no_overlap(fill_no_overlap), + loop_nodes(loop_nodes), m_ext_mm3_per_mm(-1), m_mm3_per_mm(-1), m_mm3_per_mm_overhang(-1), m_ext_mm3_per_mm_smaller_width(-1) {} void process_classic(); void process_arachne(); + // to save memory, directly modify top + bool should_enable_top_one_wall(const ExPolygons& original_expolys, ExPolygons& top); + 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 ); double ext_mm3_per_mm() const { return m_ext_mm3_per_mm; } diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 3134e4c..906c9ad 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -199,7 +199,7 @@ namespace client explicit expr(double d, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_DOUBLE), it_range(it_begin, it_end) { data.d = d; } explicit expr(const char *s) : type(TYPE_STRING) { data.s = new std::string(s); } explicit expr(const std::string &s) : type(TYPE_STRING) { data.s = new std::string(s); } - explicit expr(const std::string &s, const Iterator &it_begin, const Iterator &it_end) : + explicit expr(const std::string &s, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_STRING), it_range(it_begin, it_end) { data.s = new std::string(s); } expr(const expr &rhs) : type(rhs.type), it_range(rhs.it_range) { if (rhs.type == TYPE_STRING) data.s = new std::string(*rhs.data.s); else data.set(rhs.data); } @@ -209,7 +209,7 @@ namespace client { data.set(rhs.data); rhs.type = TYPE_EMPTY; } ~expr() { this->reset(); } - expr &operator=(const expr &rhs) + expr &operator=(const expr &rhs) { //QDS: avoid memory leak when call operator= directly before call reset() if (this->type == TYPE_STRING && this->data.s) { @@ -219,15 +219,15 @@ namespace client this->type = rhs.type; this->it_range = rhs.it_range; - if (rhs.type == TYPE_STRING) + if (rhs.type == TYPE_STRING) this->data.s = new std::string(*rhs.data.s); else this->data.set(rhs.data); - return *this; + return *this; } - expr &operator=(expr &&rhs) - { + expr &operator=(expr &&rhs) + { type = rhs.type; this->it_range = rhs.it_range; data.set(rhs.data); @@ -235,7 +235,7 @@ namespace client return *this; } - void reset() + void reset() { //QDS if (this->type == TYPE_STRING && this->data.s) { @@ -261,14 +261,14 @@ namespace client const std::string& s() const { return *data.s; } void set_s(const std::string &s) { this->reset(); this->data.s = new std::string(s); this->type = TYPE_STRING; } void set_s(std::string &&s) { this->reset(); this->data.s = new std::string(std::move(s)); this->type = TYPE_STRING; } - - std::string to_string() const + + std::string to_string() const { std::string out; switch (type) { case TYPE_BOOL: out = data.b ? "true" : "false"; break; case TYPE_INT: out = std::to_string(data.i); break; - case TYPE_DOUBLE: + case TYPE_DOUBLE: #if 0 // The default converter produces trailing zeros after the decimal point. out = std::to_string(data.d); @@ -317,12 +317,12 @@ namespace client boost::iterator_range it_range; expr unary_minus(const Iterator start_pos) const - { + { switch (this->type) { case TYPE_INT : return expr(- this->i(), start_pos, this->it_range.end()); case TYPE_DOUBLE: - return expr(- this->d(), start_pos, this->it_range.end()); + return expr(- this->d(), start_pos, this->it_range.end()); default: this->throw_exception("Cannot apply unary minus operator."); } @@ -332,12 +332,12 @@ namespace client } expr unary_integer(const Iterator start_pos) const - { + { switch (this->type) { case TYPE_INT: return expr(this->i(), start_pos, this->it_range.end()); case TYPE_DOUBLE: - return expr(static_cast(this->d()), start_pos, this->it_range.end()); + return expr(static_cast(this->d()), start_pos, this->it_range.end()); default: this->throw_exception("Cannot convert to integer."); } @@ -347,7 +347,7 @@ namespace client } expr round(const Iterator start_pos) const - { + { switch (this->type) { case TYPE_INT: return expr(this->i(), start_pos, this->it_range.end()); @@ -361,8 +361,38 @@ namespace client return expr(); } + expr floor(const Iterator start_pos)const + { + switch (this->type) { + case TYPE_INT: + return expr(this->i(), start_pos, this->it_range.end()); + case TYPE_DOUBLE: + return expr(static_cast(std::floor(this->d())), start_pos, this->it_range.end()); + default: + this->throw_exception("Cannot floor a non-numeric value."); + } + assert(false); + // Suppress compiler warnings. + return expr(); + } + + expr ceil(const Iterator start_pos)const + { + switch (this->type) { + case TYPE_INT: + return expr(this->i(), start_pos, this->it_range.end()); + case TYPE_DOUBLE: + return expr(static_cast(std::ceil(this->d())), start_pos, this->it_range.end()); + default: + this->throw_exception("Cannot ceil a non-numeric value."); + } + assert(false); + // Suppress compiler warnings. + return expr(); + } + expr unary_not(const Iterator start_pos) const - { + { switch (this->type) { case TYPE_BOOL: return expr(! this->b(), start_pos, this->it_range.end()); @@ -375,7 +405,7 @@ namespace client } expr &operator+=(const expr &rhs) - { + { if (this->type == TYPE_STRING) { // Convert the right hand side to string and append. *this->data.s += rhs.to_string(); @@ -399,7 +429,7 @@ namespace client } expr &operator-=(const expr &rhs) - { + { const char *err_msg = "Cannot subtract non-numeric types."; this->throw_if_not_numeric(err_msg); rhs.throw_if_not_numeric(err_msg); @@ -414,7 +444,7 @@ namespace client } expr &operator*=(const expr &rhs) - { + { const char *err_msg = "Cannot multiply with non-numeric type."; this->throw_if_not_numeric(err_msg); rhs.throw_if_not_numeric(err_msg); @@ -488,16 +518,16 @@ namespace client // Both types are numeric. switch (op) { case '=': - value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ? + value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ? (std::abs(lhs.as_d() - rhs.as_d()) < 1e-8) : (lhs.i() == rhs.i()); break; case '<': - value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ? + value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ? (lhs.as_d() < rhs.as_d()) : (lhs.i() < rhs.i()); break; case '>': default: - value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ? + value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ? (lhs.as_d() > rhs.as_d()) : (lhs.i() > rhs.i()); break; } @@ -509,7 +539,7 @@ namespace client value = lhs.b() == rhs.b(); } else if (lhs.type == TYPE_STRING || rhs.type == TYPE_STRING) { // One type is string, the other could be converted to string. - value = (op == '=') ? (lhs.to_string() == rhs.to_string()) : + value = (op == '=') ? (lhs.to_string() == rhs.to_string()) : (op == '<') ? (lhs.to_string() < rhs.to_string()) : (lhs.to_string() > rhs.to_string()); } else { boost::throw_exception(qi::expectation_failure( @@ -529,7 +559,7 @@ namespace client static void throw_if_not_numeric(const expr ¶m) { const char *err_msg = "Not a numeric type."; - param.throw_if_not_numeric(err_msg); + param.throw_if_not_numeric(err_msg); } enum Function2ParamsType { @@ -538,7 +568,7 @@ namespace client }; // Store the result into param1. static void function_2params(expr ¶m1, expr ¶m2, Function2ParamsType fun) - { + { throw_if_not_numeric(param1); throw_if_not_numeric(param2); if (param1.type == TYPE_DOUBLE || param2.type == TYPE_DOUBLE) { @@ -567,7 +597,7 @@ namespace client // Store the result into param1. static void random(expr ¶m1, expr ¶m2, std::mt19937 &rng) - { + { throw_if_not_numeric(param1); throw_if_not_numeric(param2); if (param1.type == TYPE_DOUBLE || param2.type == TYPE_DOUBLE) { @@ -583,7 +613,7 @@ namespace client // param3 is optional template static void digits(expr ¶m1, expr ¶m2, expr ¶m3) - { + { throw_if_not_numeric(param1); if (param2.type != TYPE_INT) param2.throw_exception("digits: second parameter must be integer"); @@ -663,13 +693,13 @@ namespace client } } - void throw_exception(const char *message) const + void throw_exception(const char *message) const { boost::throw_exception(qi::expectation_failure( this->it_range.begin(), this->it_range.end(), spirit::info(std::string("*") + message))); } - void throw_if_not_numeric(const char *message) const + void throw_if_not_numeric(const char *message) const { if (this->type != TYPE_INT && this->type != TYPE_DOUBLE) this->throw_exception(message); @@ -706,6 +736,14 @@ namespace client // Table to translate symbol tag to a human readable error message. static std::map tag_to_error_message; + size_t get_extruder_id() const { + const ConfigOptionInts * filament_map_opt = external_config->option("filament_map"); + if (filament_map_opt && current_extruder_id < filament_map_opt->values.size()) { + return filament_map_opt->values[current_extruder_id]; + } + return 0; + } + static void evaluate_full_macro(const MyContext *ctx, bool &result) { result = ! ctx->just_boolean_expression; } const ConfigOption* optptr(const t_config_option_key &opt_key) const override @@ -724,7 +762,7 @@ namespace client template static void legacy_variable_expansion( - const MyContext *ctx, + const MyContext *ctx, boost::iterator_range &opt_key, std::string &output) { @@ -760,7 +798,7 @@ namespace client template static void legacy_variable_expansion2( - const MyContext *ctx, + const MyContext *ctx, boost::iterator_range &opt_key, boost::iterator_range &opt_vector_index, std::string &output) @@ -808,58 +846,110 @@ namespace client OptWithPos &opt, expr &output) { - if (opt.opt->is_vector()) - ctx->throw_exception("Referencing a vector variable when scalar is expected", opt.it_range); - switch (opt.opt->type()) { - case coFloat: output.set_d(opt.opt->getFloat()); break; - case coInt: output.set_i(opt.opt->getInt()); break; - case coString: output.set_s(static_cast(opt.opt)->value); break; - case coPercent: output.set_d(opt.opt->getFloat()); break; - case coPoint: output.set_s(opt.opt->serialize()); break; - case coBool: output.set_b(opt.opt->getBool()); break; - case coFloatOrPercent: - { - std::string opt_key(opt.it_range.begin(), opt.it_range.end()); - if (boost::ends_with(opt_key, "line_width")) { - // Extrusion width supports defaults and a complex graph of dependencies. - output.set_d(Flow::extrusion_width(opt_key, *ctx, static_cast(ctx->current_extruder_id))); - } else if (! static_cast(opt.opt)->percent) { - // Not a percent, just return the value. - output.set_d(opt.opt->getFloat()); - } else { - // Resolve dependencies using the "ratio_over" link to a parent value. - const ConfigOptionDef *opt_def = print_config_def.get(opt_key); - assert(opt_def != nullptr); - double v = opt.opt->getFloat() * 0.01; // percent to ratio - for (;;) { - const ConfigOption *opt_parent = opt_def->ratio_over.empty() ? nullptr : ctx->resolve_symbol(opt_def->ratio_over); - if (opt_parent == nullptr) - ctx->throw_exception("FloatOrPercent variable failed to resolve the \"ratio_over\" dependencies", opt.it_range); - if (boost::ends_with(opt_def->ratio_over, "line_width")) { - // Extrusion width supports defaults and a complex graph of dependencies. - assert(opt_parent->type() == coFloatOrPercent); - v *= Flow::extrusion_width(opt_def->ratio_over, static_cast(opt_parent), *ctx, static_cast(ctx->current_extruder_id)); - break; - } - if (opt_parent->type() == coFloat || opt_parent->type() == coFloatOrPercent) { - v *= opt_parent->getFloat(); - if (opt_parent->type() == coFloat || ! static_cast(opt_parent)->percent) - break; - v *= 0.01; // percent to ratio - } - // Continue one level up in the "ratio_over" hierarchy. - opt_def = print_config_def.get(opt_def->ratio_over); - assert(opt_def != nullptr); - } - output.set_d(v); - } - break; - } - //QDS: Add enum. Otherwise enum can not be judged in placeholder - case coEnum: output.set_s(opt.opt->serialize()); break; - default: - ctx->throw_exception("Unknown scalar variable type", opt.it_range); + if (opt.opt->is_vector()) { + switch (opt.opt->type()) { + case coFloats: { + const ConfigOptionFloatsNullable* opt_floats = static_cast(opt.opt); + if (opt_floats->size() == 1) { // old version + output.set_d(static_cast(opt.opt)->get_at(0)); + } + else { + output.set_d(static_cast(opt.opt)->get_at(ctx->get_extruder_id())); + } + break; + } + case coFloatsOrPercents: { + const ConfigOptionFloatsOrPercentsNullable * opt_floats = static_cast(opt.opt); + size_t index = 0; // old version only one value + if (opt_floats->size() > 1) { + index = ctx->get_extruder_id(); + } + std::string opt_key(opt.it_range.begin(), opt.it_range.end()); + if (!opt_floats->get_at(index).percent) { + // Not a percent, just return the value. + output.set_d(opt_floats->get_at(index).value); + } else { + + FloatOrPercent opt_value = opt_floats->get_at(index); + double v = opt_value.value; + if (opt_value.percent) { + // Resolve dependencies using the "ratio_over" link to a parent value. + const ConfigOptionDef *opt_def = print_config_def.get(opt_key); + assert(opt_def != nullptr); + assert(opt_def->type == coFloatsOrPercents); + v *= 0.01; + while (true) { + const ConfigOption *opt_parent = opt_def->ratio_over.empty() ? nullptr : ctx->resolve_symbol(opt_def->ratio_over); + if (opt_parent == nullptr) { + ctx->throw_exception("FloatOrPercentNullable variable failed to resolve the \"ratio_over\" dependencies", opt.it_range); + } + if (opt_parent->type() == coFloat || opt_parent->type() == coFloatOrPercent) { + v *= opt_parent->getFloat(); + if (opt_parent->type() == coFloat || !static_cast(opt_parent)->percent) break; + v *= 0.01; // percent to ratio + } + } + } + output.set_d(v); + } + break; + } + default: ctx->throw_exception("Unknown scalar variable type", opt.it_range); + } } + else { + switch (opt.opt->type()) { + case coFloat: output.set_d(opt.opt->getFloat()); break; + case coInt: output.set_i(opt.opt->getInt()); break; + case coString: output.set_s(static_cast(opt.opt)->value); break; + case coPercent: output.set_d(opt.opt->getFloat()); break; + case coPoint: output.set_s(opt.opt->serialize()); break; + case coBool: output.set_b(opt.opt->getBool()); break; + case coFloatOrPercent: + { + std::string opt_key(opt.it_range.begin(), opt.it_range.end()); + if (boost::ends_with(opt_key, "line_width")) { + // Extrusion width supports defaults and a complex graph of dependencies. + output.set_d(Flow::extrusion_width(opt_key, *ctx, static_cast(ctx->current_extruder_id))); + } else if (! static_cast(opt.opt)->percent) { + // Not a percent, just return the value. + output.set_d(opt.opt->getFloat()); + } else { + // Resolve dependencies using the "ratio_over" link to a parent value. + const ConfigOptionDef *opt_def = print_config_def.get(opt_key); + assert(opt_def != nullptr); + double v = opt.opt->getFloat() * 0.01; // percent to ratio + for (;;) { + const ConfigOption *opt_parent = opt_def->ratio_over.empty() ? nullptr : ctx->resolve_symbol(opt_def->ratio_over); + if (opt_parent == nullptr) + ctx->throw_exception("FloatOrPercent variable failed to resolve the \"ratio_over\" dependencies", opt.it_range); + if (boost::ends_with(opt_def->ratio_over, "line_width")) { + // Extrusion width supports defaults and a complex graph of dependencies. + assert(opt_parent->type() == coFloatOrPercent); + v *= Flow::extrusion_width(opt_def->ratio_over, static_cast(opt_parent), *ctx, static_cast(ctx->current_extruder_id)); + break; + } + if (opt_parent->type() == coFloat || opt_parent->type() == coFloatOrPercent) { + v *= opt_parent->getFloat(); + if (opt_parent->type() == coFloat || ! static_cast(opt_parent)->percent) + break; + v *= 0.01; // percent to ratio + } + // Continue one level up in the "ratio_over" hierarchy. + opt_def = print_config_def.get(opt_def->ratio_over); + assert(opt_def != nullptr); + } + output.set_d(v); + } + break; + } + //QDS: Add enum. Otherwise enum can not be judged in placeholder + case coEnum: output.set_s(opt.opt->serialize()); break; + default: + ctx->throw_exception("Unknown scalar variable type", opt.it_range); + } + } + output.it_range = opt.it_range; } @@ -897,7 +987,7 @@ namespace client template static void evaluate_index(expr &expr_index, int &output) { - if (expr_index.type != expr::TYPE_INT) + if (expr_index.type != expr::TYPE_INT) expr_index.throw_exception("Non-integer index is not allowed to address a vector variable."); output = expr_index.i(); } @@ -911,6 +1001,13 @@ namespace client expr::random(param1, param2, ctx->context_data->rng); } + template + static void filament_change(const MyContext* ctx, expr& param) + { + MyContext *context = const_cast(ctx); + context->current_extruder_id = param.as_i(); + } + template static void throw_exception(const std::string &msg, const boost::iterator_range &it_range) { @@ -1012,18 +1109,18 @@ namespace client // This parser is to be used inside a raw[] directive to accept a single valid UTF-8 character. // If an invalid UTF-8 sequence is encountered, a qi::expectation_failure is thrown. struct utf8_char_skipper_parser : qi::primitive_parser - { - // Define the attribute type exposed by this parser component + { + // Define the attribute type exposed by this parser component template struct attribute - { + { typedef wchar_t type; }; - // This function is called during the actual parsing process + // This function is called during the actual parsing process template bool parse(Iterator& first, Iterator const& last, Context& context, Skipper const& skipper, Attribute& attr) const - { + { // The skipper shall always be empty, any white space will be accepted. // skip_over(first, last, skipper); if (first == last) @@ -1067,7 +1164,7 @@ namespace client // This function is called during error handling to create a human readable string for the error context. template spirit::info what(Context&) const - { + { return spirit::info("unicode_char"); } }; @@ -1148,11 +1245,11 @@ namespace client // An if expression enclosed in {} (the outmost {} are already parsed by the caller). if_else_output = eps[_b=true] > - bool_expr_eval(_r1)[_a=_1] > '}' > + bool_expr_eval(_r1)[_a=_1] > '}' > text_block(_r1)[px::bind(&expr::set_if, _a, _b, _1, _val)] > '{' > - *(kw["elsif"] > bool_expr_eval(_r1)[_a=_1] > '}' > + *(kw["elsif"] > bool_expr_eval(_r1)[_a=_1] > '}' > text_block(_r1)[px::bind(&expr::set_if, _a, _b, _1, _val)] > '{') > - -(kw["else"] > lit('}') > + -(kw["else"] > lit('}') > text_block(_r1)[px::bind(&expr::set_if, _b, _b, _1, _val)] > '{') > kw["endif"]; if_else_output.name("if_else_output"); @@ -1170,7 +1267,7 @@ namespace client legacy_variable_expansion = (identifier >> &lit(']')) [ px::bind(&MyContext::legacy_variable_expansion, _r1, _1, _val) ] - | (identifier > lit('[') > identifier > ']') + | (identifier > lit('[') > identifier > ']') [ px::bind(&MyContext::legacy_variable_expansion2, _r1, _1, _2, _val) ] ; legacy_variable_expansion.name("legacy_variable_expansion"); @@ -1185,12 +1282,12 @@ namespace client >> -('?' > conditional_expression(_r1) > ':' > conditional_expression(_r1)) [px::bind(&expr::ternary_op, _val, _1, _2)]; conditional_expression.name("conditional_expression"); - logical_or_expression = + logical_or_expression = logical_and_expression(_r1) [_val = _1] >> *( ((kw["or"] | "||") > logical_and_expression(_r1) ) [px::bind(&expr::logical_or, _val, _1)] ); logical_or_expression.name("logical_or_expression"); - logical_and_expression = + logical_and_expression = equality_expression(_r1) [_val = _1] >> *( ((kw["and"] | "&&") > equality_expression(_r1) ) [px::bind(&expr::logical_and, _val, _1)] ); logical_and_expression.name("logical_and_expression"); @@ -1210,7 +1307,7 @@ namespace client bool_expr_eval = conditional_expression(_r1) [ px::bind(&expr::evaluate_boolean, _1, _val) ]; bool_expr_eval.name("bool_expr_eval"); - relational_expression = + relational_expression = additive_expression(_r1) [_val = _1] >> *( ("<=" > additive_expression(_r1) ) [px::bind(&expr::leq, _val, _1)] | (">=" > additive_expression(_r1) ) [px::bind(&expr::geq, _val, _1)] @@ -1255,33 +1352,40 @@ namespace client { out = value.unary_integer(out.it_range.begin()); } static void round(expr &value, expr &out) { out = value.round(out.it_range.begin()); } + static void floor(expr &value, expr &out) + { out = value.floor(out.it_range.begin()); } + static void ceil(expr &value, expr &out) + { out = value.ceil(out.it_range.begin());} // For indicating "no optional parameter". static void noexpr(expr &out) { out.reset(); } }; unary_expression = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> ( - scalar_variable_reference(_r1) [ _val = _1 ] - | (lit('(') > conditional_expression(_r1) > ')' > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ] - | (lit('-') > unary_expression(_r1) ) [ px::bind(&FactorActions::minus_, _1, _val) ] - | (lit('+') > unary_expression(_r1) > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ] - | ((kw["not"] | '!') > unary_expression(_r1) > iter_pos) [ px::bind(&FactorActions::not_, _1, _val) ] - | (kw["min"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > ')') - [ px::bind(&expr::min, _val, _2) ] - | (kw["max"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > ')') - [ px::bind(&expr::max, _val, _2) ] - | (kw["random"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > ')') - [ px::bind(&MyContext::random, _r1, _val, _2) ] - | (kw["digits"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > optional_parameter(_r1)) - [ px::bind(&expr::template digits, _val, _2, _3) ] - | (kw["zdigits"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > optional_parameter(_r1)) - [ px::bind(&expr::template digits, _val, _2, _3) ] - | (kw["int"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::to_int, _1, _val) ] - | (kw["round"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::round, _1, _val) ] - | (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ] - | (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ] - | (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _1, _2, _val) ] - | raw[lexeme['"' > *((utf8char - char_('\\') - char_('"')) | ('\\' > char_)) > '"']] - [ px::bind(&FactorActions::string_, _1, _val) ] - ); + scalar_variable_reference(_r1) [ _val = _1 ] + | (lit('(') > conditional_expression(_r1) > ')' > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ] + | (lit('-') > unary_expression(_r1) ) [ px::bind(&FactorActions::minus_, _1, _val) ] + | (lit('+') > unary_expression(_r1) > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ] + | ((kw["not"] | '!') > unary_expression(_r1) > iter_pos) [ px::bind(&FactorActions::not_, _1, _val) ] + | (kw["min"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > ')') + [ px::bind(&expr::min, _val, _2) ] + | (kw["max"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > ')') + [ px::bind(&expr::max, _val, _2) ] + | (kw["random"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > ')') + [ px::bind(&MyContext::random, _r1, _val, _2) ] + | (kw["filament_change"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&MyContext::filament_change, _r1, _1) ] + | (kw["digits"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > optional_parameter(_r1)) + [ px::bind(&expr::template digits, _val, _2, _3) ] + | (kw["zdigits"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > optional_parameter(_r1)) + [ px::bind(&expr::template digits, _val, _2, _3) ] + | (kw["int"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::to_int, _1, _val) ] + | (kw["round"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::round, _1, _val) ] + | (kw["ceil"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::ceil, _1, _val) ] + | (kw["floor"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::floor, _1, _val) ] + | (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ] + | (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ] + | (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _1, _2, _val) ] + | raw[lexeme['"' > *((utf8char - char_('\\') - char_('"')) | ('\\' > char_)) > '"']] + [ px::bind(&FactorActions::string_, _1, _val) ] + ); unary_expression.name("unary_expression"); optional_parameter = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> ( @@ -1290,10 +1394,10 @@ namespace client ); optional_parameter.name("optional_parameter"); - scalar_variable_reference = + scalar_variable_reference = variable_reference(_r1)[_a=_1] >> ( - ('[' > additive_expression(_r1)[px::bind(&MyContext::evaluate_index, _1, _b)] > ']' > + ('[' > additive_expression(_r1)[px::bind(&MyContext::evaluate_index, _1, _b)] > ']' > iter_pos[px::bind(&MyContext::vector_variable_reference, _r1, _a, _b, _1, _val)]) | eps[px::bind(&MyContext::scalar_variable_reference, _r1, _a, _val)] ); @@ -1320,7 +1424,10 @@ namespace client ("min") ("max") ("random") + ("filament_change") ("round") + ("floor") + ("ceil") ("not") ("or") ("true"); diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index a43fab7..2aa66db 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -179,6 +179,49 @@ Point Point::projection_onto(const Line &line) const return ((line.a - *this).cast().squaredNorm() < (line.b - *this).cast().squaredNorm()) ? line.a : line.b; } + +bool Point::is_in_lines(const Points &pts) const +{ + const Point check_point = *this; + for (int pt_idx = 1; pt_idx < pts.size(); pt_idx++) { + const Point pt = pts[pt_idx]; + const Point prev_pt = pts[pt_idx - 1]; + + // if on the endpoints + if ((check_point.x() == pt.x() && check_point.y() == pt.y()) || (check_point.x() == prev_pt.x() && check_point.y() == prev_pt.y())) + return true; + + bool in_x_range = !(check_point.x() > pt.x() == check_point.x() > prev_pt.x()); + bool in_y_range = !(check_point.y() > pt.y() == check_point.y() > prev_pt.y()); + + //on vert line + if (pt.x() == prev_pt.x()) { + if (in_y_range && pt.x() == check_point.x()) + return true; + continue; + } + + // on hori line + if (pt.y() == prev_pt.y()) { + if (in_x_range && pt.y() == check_point.y()) + return true; + continue; + } + + //not right range + if (!in_x_range || !in_y_range) + continue; + + // check if in line + Line line(prev_pt, pt); + double distance = line.distance_to(*this); + if (std::abs(distance) < SCALED_EPSILON) + return true; + } + + return false; +} + bool has_duplicate_points(std::vector &&pts) { std::sort(pts.begin(), pts.end()); diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 1e97e70..8479297 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -214,6 +214,14 @@ public: return this->x() < rhs.x() || this->y() < rhs.y(); return false; } + bool any_comp(const coord_t val, const std::string &op) + { + if (op == ">") + return this->x() > val || this->y() > val; + else if (op == "<") + return this->x() < val || this->y() < val; + return false; + } void rotate(double angle) { this->rotate(std::cos(angle), std::sin(angle)); } void rotate(double cos_a, double sin_a) { @@ -237,6 +245,7 @@ public: double ccw_angle(const Point &p1, const Point &p2) const; Point projection_onto(const MultiPoint &poly) const; Point projection_onto(const Line &line) const; + bool is_in_lines(const Points &pts) const; }; inline bool operator<(const Point &l, const Point &r) @@ -399,7 +408,7 @@ public: ++ m_grid_log2; m_grid_resolution = 1 << m_grid_log2; assert(m_grid_resolution >= gridres); - assert(gridres > m_grid_resolution / 2); + assert(gridres >= m_grid_resolution / 2); } void insert(const ValueType &value) { @@ -611,6 +620,34 @@ inline Point align_to_grid(Point coord, Point spacing, Point base) } } // namespace Slic3r +// requseted by ConfigOptionPointsGroups +namespace std { + template<> + struct hash { + size_t operator()(const Slic3r::Vec2ds& vec) { + size_t seed = 0; + for (const auto& element : vec) { + seed ^= std::hash()(element[0]) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= std::hash()(element[1]) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + return seed; + } + }; + + template<> + struct hash> + { + size_t operator()(const std::vector &vec) + { + size_t seed = 0; + for (const auto &element : vec) { + seed ^= std::hash()(element) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + return seed; + } + }; +} + // start Boost #include #include diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index 376dbc6..fc75536 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -4,6 +4,8 @@ #include "Polygon.hpp" #include "Polyline.hpp" +#include + namespace Slic3r { double Polygon::length() const @@ -99,6 +101,39 @@ void Polygon::douglas_peucker(double tolerance) this->points = std::move(p); } +bool Polygon::is_approx_circle(double max_deviation, double max_variance, Point ¢er, double &diameter) const +{ + if (this->points.size() < 8) { + return false; + } + + center = centroid(); + std::vector distances; + for (const auto &point : this->points) { + double distance = std::sqrt(std::pow(point.x() - center.x(), 2) + std::pow(point.y() - center.y(), 2)); + distances.push_back(distance); + } + + double max_dist = *std::max_element(distances.begin(), distances.end()); + double min_dist = *std::min_element(distances.begin(), distances.end()); + + if ((max_dist - min_dist) > max_deviation) { + return false; + } + + double avg_dist = std::accumulate(distances.begin(), distances.end(), 0.0) / distances.size(); + double variance = 0; + for (double d : distances) { variance += std::pow(d - avg_dist, 2); } + variance /= distances.size(); + + if (variance > max_variance) { + return false; + } + + diameter = 2 * avg_dist; + return true; +} + Polygons Polygon::simplify(double tolerance) const { // Works on CCW polygons only, CW contour will be reoriented to CCW by Clipper's simplify_polygons()! diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index ae6f71d..30d0e52 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -61,6 +61,10 @@ public: bool is_valid() const { return this->points.size() >= 3; } void douglas_peucker(double tolerance); + // Point ¢er : out, the center of circle + // double &diameter: out, the diameter of circle + bool is_approx_circle(double max_deviation, double max_variance, Point ¢er, double &diameter) const; + // Does an unoriented polygon contain a point? bool contains(const Point &point) const { return Slic3r::contains(*this, point, true); } // Approximate on boundary test. diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 2f24e04..558c1b7 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -618,6 +618,58 @@ ThickLines ThickPolyline::thicklines() const return lines; } +Polyline Polyline::rebase_at(size_t idx) +{ + if (!this->is_closed()) + return {}; + Polyline ret = *this; + size_t n = this->points.size(); + for (size_t j = 0; j < n - 1; ++j) { + ret.points[j] = this->points[(idx + j) % (n - 1)]; + } + ret.points[n - 1] = ret.points.front(); + return ret; +} + +ThickPolyline ThickPolyline::rebase_at(size_t idx) +{ + if (!this->is_closed()) + return {}; + + ThickPolyline ret = *this; + static_cast(ret) = Polyline::rebase_at(idx); + size_t n = this->points.size(); + ret.width.resize(2 * n - 2, 0); + + auto get_in_width = [&](size_t i)->double { + if (i == 0) return this->width[0]; + if (i == n - 1) return this->width.back(); + return this->width[2 * i - 1]; + }; + auto get_out_width = [&](size_t i)->double { + if (i == 0) return this->width[0]; + if (i == n - 1) return this->width.back(); + return this->width[2 * i]; + }; + + ret.width[0] = get_out_width(idx % (n-1)); + for (size_t j = 1; j < n - 1; ++j) { + size_t i = (idx + j) % (n-1); + ret.width[2 * j - 1] = get_in_width(i); + ret.width[2 * j] = get_out_width(i); + } + + ret.width[2 * n - 3] = ret.width.front(); + return ret; +} + +coordf_t ThickPolyline::get_width_at(size_t point_idx) const +{ + if (point_idx < 2) + return width[point_idx]; + return width[2 * point_idx - 1]; +} + Lines3 Polyline3::lines() const { Lines3 lines; diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 7b12c6d..1a3c0de 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -102,6 +102,8 @@ public: void append(const Polyline& src); void append(Polyline&& src); + Polyline rebase_at(size_t idx); + Point& operator[](Points::size_type idx) { return this->points[idx]; } const Point& operator[](Points::size_type idx) const { return this->points[idx]; } @@ -265,6 +267,8 @@ public: Polyline::clear(); width.clear(); } + ThickPolyline rebase_at(size_t idx); + coordf_t get_width_at(size_t point_idx) const; std::vector width; std::pair endpoints; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index a9be36d..bf9993c 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -320,11 +320,12 @@ void Preset::normalize(DynamicPrintConfig &config) { // QDS auto* filament_diameter = dynamic_cast(config.option("filament_diameter")); - if (filament_diameter != nullptr) + //not use any more + /*if (filament_diameter != nullptr) // Loaded the FFF Printer settings. Verify, that all extruder dependent values have enough values. - config.set_num_filaments((unsigned int)filament_diameter->values.size()); + config.set_num_filaments((unsigned int)filament_diameter->values.size());*/ - if (config.option("filament_diameter") != nullptr) { + if (filament_diameter) { // This config contains single or multiple filament presets. // Ensure that the filament preset vector options contain the correct number of values. // QDS @@ -333,6 +334,8 @@ void Preset::normalize(DynamicPrintConfig &config) for (const std::string &key : Preset::filament_options()) { if (key == "compatible_prints" || key == "compatible_printers") continue; + if (filament_options_with_variant.find(key) != filament_options_with_variant.end()) + continue; auto *opt = config.option(key, false); /*assert(opt != nullptr); assert(opt->is_vector());*/ @@ -522,20 +525,46 @@ bool Preset::save(DynamicPrintConfig* parent_config) if (parent_config) { DynamicPrintConfig temp_config; std::vector dirty_options = config.diff(*parent_config); + std::string extruder_id_name, extruder_variant_name; + std::set *key_set1 = nullptr, *key_set2 = nullptr; + Preset::get_extruder_names_and_keysets(type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); + + if (!extruder_id_name.empty()) { + dirty_options.emplace_back(extruder_id_name); + } + if (!extruder_variant_name.empty()) { + dirty_options.emplace_back(extruder_variant_name); + } for (auto option: dirty_options) { ConfigOption *opt_src = config.option(option); ConfigOption *opt_dst = temp_config.option(option, true); - opt_dst->set(opt_src); + if (opt_dst->is_scalar() || !(opt_dst->nullable())) + opt_dst->set(opt_src); + else { + ConfigOptionVectorBase* opt_vec_src = static_cast(opt_src); + ConfigOptionVectorBase* opt_vec_dst = static_cast(opt_dst); + ConfigOptionVectorBase* opt_vec_inherit = static_cast(parent_config->option(option)); + if (opt_vec_src->size() == 1) + opt_dst->set(opt_src); + else if (key_set1->find(option) != key_set1->end()) { + opt_vec_dst->set_with_nil(opt_vec_src, opt_vec_inherit, 1); + } + else if (key_set2->find(option) != key_set2->end()) { + opt_vec_dst->set_with_nil(opt_vec_src, opt_vec_inherit, 2); + } + else + opt_dst->set(opt_src); + } } - temp_config.save_to_json(this->file, this->name, from_str, this->version.to_string(), this->custom_defined); + temp_config.save_to_json(this->file, this->name, from_str, this->version.to_string()); } else if (!filament_id.empty() && inherits().empty()) { DynamicPrintConfig temp_config = config; temp_config.set_key_value(QDT_JSON_KEY_FILAMENT_ID, new ConfigOptionString(filament_id)); - temp_config.save_to_json(this->file, this->name, from_str, this->version.to_string(), this->custom_defined); + temp_config.save_to_json(this->file, this->name, from_str, this->version.to_string()); } else { - this->config.save_to_json(this->file, this->name, from_str, this->version.to_string(), this->custom_defined); + this->config.save_to_json(this->file, this->name, from_str, this->version.to_string()); } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " save config for: " << this->name << " and filament_id: " << filament_id << " and base_id: " << this->base_id; @@ -619,7 +648,7 @@ bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const Pre return PlaceholderParser::evaluate_boolean_expression(condition, active_printer.preset.config, extra_config); } catch (const std::runtime_error &err) { //FIXME in case of an error, return "compatible with everything". - printf("Preset::is_compatible_with_printer - parsing error of compatible_printers_condition %s:\n%s\n", active_printer.preset.name.c_str(), err.what()); + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": parsing error of compatible_printers_condition %1%: %2%")%active_printer.preset.name %err.what(); return true; } } @@ -636,7 +665,7 @@ bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const Pre config.set_key_value("printer_preset", new ConfigOptionString(active_printer.preset.name)); const ConfigOption *opt = active_printer.preset.config.option("nozzle_diameter"); if (opt) - config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); + config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); return is_compatible_with_printer(preset, active_printer, &config); } @@ -668,7 +697,7 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config) } } //QDS: add config related log - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": name %1%, is_visible set to %2%")%name % is_visible; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": name %1%, is_visible set to %2%")%name % is_visible; } std::string Preset::get_filament_type(std::string &display_filament_type) @@ -709,6 +738,31 @@ std::string Preset::get_current_printer_type(PresetBundle *preset_bundle) return ""; } +void Preset::get_extruder_names_and_keysets(Type type, std::string& extruder_id_name, std::string& extruder_variant_name, std::set** p_key_set1, std::set** p_key_set2) +{ + if (type == Preset::TYPE_PRINT) { + extruder_id_name = "print_extruder_id"; + extruder_variant_name = "print_extruder_variant"; + *p_key_set1 = &print_options_with_variant; + *p_key_set2 = &empty_options; + } + else if (type == Preset::TYPE_PRINTER) { + extruder_id_name = "printer_extruder_id"; + extruder_variant_name = "printer_extruder_variant"; + *p_key_set1 = &printer_options_with_variant_1; + *p_key_set2 = &printer_options_with_variant_2; + } + else if (type == Preset::TYPE_FILAMENT) { + extruder_variant_name = "filament_extruder_variant"; + *p_key_set1 = &filament_options_with_variant; + *p_key_set2 = &empty_options; + } + else { + *p_key_set1 = &empty_options; + *p_key_set2 = &empty_options; + } +} + bool Preset::has_lidar(PresetBundle *preset_bundle) { bool has_lidar = false; @@ -728,13 +782,6 @@ bool Preset::has_lidar(PresetBundle *preset_bundle) return has_lidar; } -bool Preset::is_custom_defined() -{ - if (custom_defined == "1") - return true; - return false; -} - // The method previously only supports to be called on preset_bundle->printers.get_edited_preset() // I extened to support call on all presets bool Preset::is_qdt_vendor_preset(PresetBundle *preset_bundle) @@ -802,10 +849,10 @@ static std::vector s_Preset_print_options { "layer_height", "initial_layer_print_height", "wall_loops", "slice_closing_radius", "spiral_mode", "spiral_mode_smooth", "spiral_mode_max_xy_smoothing", "slicing_mode", //1.9.5 "top_shell_layers", "top_shell_thickness", "bottom_shell_layers", "bottom_shell_thickness", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", - "detect_overhang_wall", + "detect_overhang_wall", "top_color_penetration_layers", "bottom_color_penetration_layers", "smooth_speed_discontinuity_area","smooth_coefficient", "seam_position", "wall_sequence", "is_infill_first", "sparse_infill_density", "sparse_infill_pattern", "sparse_infill_anchor", "sparse_infill_anchor_max", - "top_surface_pattern", "bottom_surface_pattern", "internal_solid_infill_pattern", "infill_direction", "bridge_angle", + "top_surface_pattern", "bottom_surface_pattern", "internal_solid_infill_pattern", "infill_direction", "bridge_angle","infill_shift_step", "infill_rotate_step", "symmetric_infill_y_axis", "minimum_sparse_infill_area", "reduce_infill_retraction", "ironing_pattern", "ironing_type", "ironing_flow", "ironing_speed", "ironing_spacing","ironing_direction", "ironing_inset", "max_travel_detour_distance", @@ -816,13 +863,14 @@ static std::vector s_Preset_print_options { "inner_wall_speed", "outer_wall_speed", "sparse_infill_speed", "internal_solid_infill_speed", "top_surface_speed", "support_speed", "support_object_xy_distance", "support_object_first_layer_gap","support_interface_speed", "bridge_speed", "gap_infill_speed", "travel_speed", "travel_speed_z", "initial_layer_speed", "outer_wall_acceleration", - "initial_layer_acceleration", "top_surface_acceleration", "default_acceleration", "inner_wall_acceleration", "sparse_infill_acceleration", + "initial_layer_acceleration", "top_surface_acceleration", "default_acceleration", "travel_acceleration", "initial_layer_travel_acceleration", "inner_wall_acceleration", "sparse_infill_acceleration", "accel_to_decel_enable", "accel_to_decel_factor", "skirt_loops", "skirt_distance", "skirt_height", "draft_shield", "brim_width", "brim_object_gap", "brim_type", "enable_support", "support_type", "support_threshold_angle", "enforce_support_layers", "raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion", "support_base_pattern", "support_base_pattern_spacing", "support_expansion", "support_style", // QDS + "print_extruder_id", "print_extruder_variant", "independent_support_layer_height", "support_angle", "support_interface_top_layers", "support_interface_bottom_layers", "support_interface_pattern", "support_interface_spacing", "support_interface_loop_pattern", @@ -833,8 +881,10 @@ static std::vector s_Preset_print_options { "ooze_prevention", "standby_temperature_delta", "interface_shells", "line_width", "initial_layer_line_width", "inner_wall_line_width", "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", "top_surface_line_width", "support_line_width", "infill_wall_overlap", "bridge_flow", - "elefant_foot_compensation", "xy_contour_compensation", "xy_hole_compensation", "resolution", "enable_prime_tower", - "prime_tower_width", "prime_tower_brim_width", "prime_volume", + "elefant_foot_compensation", "xy_contour_compensation", "xy_hole_compensation", "resolution", "enable_prime_tower", "prime_tower_enable_framework", + "prime_tower_width", "prime_tower_brim_width", "prime_tower_skip_points","prime_tower_max_speed", + "prime_tower_rib_wall","prime_tower_extra_rib_length","prime_tower_rib_width","prime_tower_fillet_wall","prime_tower_infill_gap","prime_tower_lift_speed","prime_tower_lift_height", + "enable_circle_compensation", "circle_compensation_manual_offset", "apply_scarf_seam_on_circles", "wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits", "flush_into_infill", "flush_into_objects", "flush_into_support","process_notes", // QDS @@ -851,12 +901,14 @@ static std::vector s_Preset_print_options { "seam_gap", "wipe_speed", "top_solid_infill_flow_ratio", "initial_layer_flow_ratio", "default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk", "travel_jerk", "filter_out_gap_fill", "mmu_segmented_region_max_width", "mmu_segmented_region_interlocking_depth", - "small_perimeter_speed", "small_perimeter_threshold", + "small_perimeter_speed", "small_perimeter_threshold", "z_direction_outwall_speed_continuous", + "vertical_shell_speed","detect_floating_vertical_shell", // calib "print_flow_ratio", //Orca "exclude_object", /*"seam_slope_type",*/ "seam_slope_conditional", "scarf_angle_threshold", /*"seam_slope_start_height", */"seam_slope_entire_loop",/* "seam_slope_min_length",*/ - "seam_slope_steps", "seam_slope_inner_walls", "role_base_wipe_speed"/*, "seam_slope_gap"*/ + "seam_slope_steps", "seam_slope_inner_walls", "role_base_wipe_speed"/*, "seam_slope_gap"*/, + "interlocking_beam", "interlocking_orientation", "interlocking_beam_layer_count", "interlocking_depth", "interlocking_boundary_avoidance", "interlocking_beam_width" //w16 ,"resonance_avoidance", "min_resonance_avoidance_speed", "max_resonance_avoidance_speed" //w13 @@ -865,16 +917,18 @@ static std::vector s_Preset_print_options { static std::vector s_Preset_filament_options { /*"filament_colour", */ "default_filament_colour","required_nozzle_HRC","filament_diameter", "filament_type", "filament_soluble", "filament_is_support","filament_scarf_seam_type", "filament_scarf_height", "filament_scarf_gap","filament_scarf_length", - "filament_max_volumetric_speed", - "filament_flow_ratio", "filament_density", "filament_cost", "filament_minimal_purge_on_wipe_tower", + "filament_max_volumetric_speed", "impact_strength_z", "filament_ramming_volumetric_speed", + "filament_flow_ratio", "filament_density", "filament_adhesiveness_category", "filament_cost", "filament_minimal_purge_on_wipe_tower", "nozzle_temperature", "nozzle_temperature_initial_layer", // QDS "cool_plate_temp", "eng_plate_temp", "hot_plate_temp", "textured_plate_temp", "cool_plate_temp_initial_layer", "eng_plate_temp_initial_layer", "hot_plate_temp_initial_layer","textured_plate_temp_initial_layer", "supertack_plate_temp_initial_layer", "supertack_plate_temp", + "circle_compensation_speed", "counter_coef_1", "counter_coef_2", "counter_coef_3", "hole_coef_1", "hole_coef_2", "hole_coef_3", + "counter_limit_min", "counter_limit_max", "hole_limit_min", "hole_limit_max", "diameter_limit", // "bed_type", //QDS:temperature_vitrification - "temperature_vitrification", "reduce_fan_stop_start_freq", "slow_down_for_layer_cooling", "fan_min_speed", - "fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "overhang_fan_threshold", "overhang_threshold_participating_cooling","close_fan_the_first_x_layers", "full_fan_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed", + "temperature_vitrification", "reduce_fan_stop_start_freq", "slow_down_for_layer_cooling", "fan_min_speed","filament_ramming_travel_time","filament_pre_cooling_temperature", + "fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "pre_start_fan_time", "overhang_fan_threshold", "overhang_threshold_participating_cooling","close_fan_the_first_x_layers", "full_fan_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed", "filament_start_gcode", "filament_end_gcode", //exhaust fan control "activate_air_filtration","during_print_exhaust_fan_speed","complete_print_exhaust_fan_speed", @@ -886,13 +940,18 @@ static std::vector s_Preset_filament_options { //QDS "filament_wipe_distance", "additional_cooling_fan_speed", "nozzle_temperature_range_low", "nozzle_temperature_range_high", + "filament_extruder_variant", //OrcaSlicer "enable_pressure_advance", "pressure_advance", "chamber_temperatures","filament_notes", "filament_long_retractions_when_cut","filament_retraction_distances_when_cut","filament_shrink", + //QDS filament change length while the extruder color + "filament_change_length","filament_prime_volume", //w13 "additional_cooling_fan_speed_unseal" //w14 ,"dont_slow_down_outer_wall" + //y58 + ,"box_temperature_range_low", "box_temperature_range_high", "box_temperature" }; static std::vector s_Preset_machine_limits_options { @@ -905,10 +964,11 @@ static std::vector s_Preset_machine_limits_options { static std::vector s_Preset_printer_options { "printer_technology", - "printable_area", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor", + "printable_area", "extruder_printable_area", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor", "single_extruder_multi_material", "machine_start_gcode", "machine_end_gcode","printing_by_object_gcode","before_layer_change_gcode", "layer_change_gcode", "time_lapse_gcode", "change_filament_gcode", - "printer_model", "printer_variant", "printable_height", "extruder_clearance_dist_to_rod", "extruder_clearance_max_radius","extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod", - "nozzle_height", + "printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type", + "printable_height", "extruder_printable_height", "extruder_clearance_dist_to_rod", "extruder_clearance_max_radius","extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod", + "nozzle_height", "unprintable_filament_types", "master_extruder_id", "default_print_profile", "inherits", "silent_mode", // QDS @@ -916,16 +976,22 @@ static std::vector s_Preset_printer_options { "nozzle_type","auxiliary_fan", "nozzle_volume","upward_compatible_machine", "z_hop_types","support_chamber_temp_control","support_air_filtration","printer_structure","thumbnail_size", //w12 "thumbnails_formats", - "best_object_pos","head_wrap_detect_zone","printer_notes", + "best_object_pos", "head_wrap_detect_zone","printer_notes", "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut", //OrcaSlicer "host_type", "print_host", "printhost_apikey", "print_host_webui", "printhost_cafile","printhost_port","printhost_authorization_type", "printhost_user", "printhost_password", "printhost_ssl_ignore_revoke", - "use_relative_e_distances", "extruder_type","use_firmware_retraction" + "use_relative_e_distances", "extruder_type","use_firmware_retraction", + "grab_length","machine_switch_extruder_time","hotend_cooling_rate","hotend_heating_rate","enable_pre_heating", "physical_extruder_map", + "bed_temperature_formula" //w34 ,"support_multi_bed_types" + //y58 + ,"support_box_temp_control" + //y60 + ,"is_support_3mf" }; static std::vector s_Preset_sla_print_options { @@ -1126,6 +1192,12 @@ void PresetCollection::load_presets( // Store the loaded presets into a new vector, otherwise the binary search for already existing presets would be broken. // (see the "Preset already present, not loading" message). std::deque presets_loaded; + + //QDS: get the extruder related info for this preset collection + std::string extruder_id_name, extruder_variant_name; + std::set *key_set1 = nullptr, *key_set2 = nullptr; + Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); + //QDS: change to json format for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) { @@ -1182,8 +1254,6 @@ void PresetCollection::load_presets( if (key_values.find(QDT_JSON_KEY_FILAMENT_ID) != key_values.end()) preset.filament_id = key_values[QDT_JSON_KEY_FILAMENT_ID]; - if (key_values.find(QDT_JSON_KEY_IS_CUSTOM) != key_values.end()) - preset.custom_defined = key_values[QDT_JSON_KEY_IS_CUSTOM]; if (key_values.find(QDT_JSON_KEY_DESCRIPTION) != key_values.end()) preset.description = key_values[QDT_JSON_KEY_DESCRIPTION]; if (key_values.find("instantiation") != key_values.end()) @@ -1205,19 +1275,21 @@ void PresetCollection::load_presets( if (inherit_preset) { preset.config = inherit_preset->config; preset.filament_id = inherit_preset->filament_id; + preset.config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); } else { - // We support custom root preset now auto inherits_config2 = dynamic_cast(inherits_config); - if ((inherits_config2 && !inherits_config2->value.empty()) && !preset.is_custom_defined()) { - BOOST_LOG_TRIVIAL(error) << boost::format("can not find parent for config %1%!")%preset.file; + if ((inherits_config2 && !inherits_config2->value.empty())) { + BOOST_LOG_TRIVIAL(error) << boost::format("can not find parent %1% for config %2%!")%inherits_config2->value %preset.file; continue; } + // We support custom root preset now // Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field. preset.config = default_preset.config; + preset.config.apply(std::move(config)); } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load preset: " << name << " and filament_id: " << preset.filament_id << " and base_id: " << preset.base_id; - preset.config.apply(std::move(config)); + Preset::normalize(preset.config); // Report configuration fields, which are misplaced into a wrong group. std::string incorrect_keys = Preset::remove_invalid_keys(preset.config, default_preset.config); @@ -1263,7 +1335,7 @@ void PresetCollection::load_presets( m_presets.insert(m_presets.end(), std::make_move_iterator(presets_loaded.begin()), std::make_move_iterator(presets_loaded.end())); std::sort(m_presets.begin() + m_num_default_presets, m_presets.end()); //QDS: add config related logs - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": loaded %1% presets from %2%, type %3%")%presets_loaded.size() %dir %Preset::get_type_string(m_type); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": loaded %1% presets from %2%, type %3%")%presets_loaded.size() %dir %Preset::get_type_string(m_type); //this->select_preset(first_visible_idx()); if (! errors_cummulative.empty()) throw Slic3r::RuntimeError(errors_cummulative); @@ -1276,9 +1348,7 @@ Preset* PresetCollection::get_preset_differed_for_save(Preset& preset) if (preset.is_system || preset.is_default) return nullptr; - Preset* new_preset = new Preset(); - *new_preset = preset; - + Preset* new_preset = nullptr; //QDS: only save difference for user preset std::string& inherits = preset.inherits(); Preset* parent_preset = nullptr; @@ -1286,15 +1356,46 @@ Preset* PresetCollection::get_preset_differed_for_save(Preset& preset) parent_preset = this->find_preset(inherits, false, true); } if (parent_preset) { + new_preset = new Preset(); + *new_preset = preset; + DynamicPrintConfig temp_config; std::vector dirty_options = preset.config.diff(parent_preset->config); + std::string extruder_id_name, extruder_variant_name; + std::set *key_set1 = nullptr, *key_set2 = nullptr; + Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); + + if (!extruder_id_name.empty()) { + dirty_options.emplace_back(extruder_id_name); + } + if (!extruder_variant_name.empty()) { + dirty_options.emplace_back(extruder_variant_name); + } + for (auto option: dirty_options) { ConfigOption *opt_src = preset.config.option(option); ConfigOption *opt_dst = temp_config.option(option, true); - opt_dst->set(opt_src); + if (opt_dst->is_scalar() || !(opt_dst->nullable())) + opt_dst->set(opt_src); + else { + ConfigOptionVectorBase* opt_vec_src = static_cast(opt_src); + ConfigOptionVectorBase* opt_vec_dst = static_cast(opt_dst); + ConfigOptionVectorBase* opt_vec_inherit = static_cast(parent_preset->config.option(option)); + if (opt_vec_src->size() == 1) + opt_dst->set(opt_src); + else if (key_set1->find(option) != key_set1->end()) { + opt_vec_dst->set_with_nil(opt_vec_src, opt_vec_inherit, 1); + } + else if (key_set2->find(option) != key_set2->end()) { + opt_vec_dst->set_with_nil(opt_vec_src, opt_vec_inherit, 2); + } + else + opt_dst->set(opt_src); + } } + new_preset->config = temp_config; } @@ -1368,6 +1469,10 @@ void PresetCollection::load_project_embedded_presets(std::vector& proje std::vector::iterator it; BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, type %1% , total preset counts %2%")%Preset::get_type_string(m_type) %project_presets.size(); + std::string extruder_id_name, extruder_variant_name; + std::set *key_set1 = nullptr, *key_set2 = nullptr; + Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); + lock(); for (it = project_presets.begin(); it != project_presets.end(); it++) { Preset* preset = *it; @@ -1405,11 +1510,13 @@ void PresetCollection::load_project_embedded_presets(std::vector& proje } else { // Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field. - preset->config = default_preset.config; - BOOST_LOG_TRIVIAL(warning) << boost::format("can not find parent for config %1%!")%preset->file; - //continue; + //QDS 202407: don't load project embedded preset when can not find inherit + //preset->config = default_preset.config; + BOOST_LOG_TRIVIAL(error) << boost::format("can not find parent for config %1%!")%preset->file; + continue; } - preset->config.apply(std::move(config)); + preset->config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); + //preset->config.apply(std::move(config)); Preset::normalize(preset->config); // Report configuration fields, which are misplaced into a wrong group. std::string incorrect_keys = Preset::remove_invalid_keys(preset->config, default_preset.config); @@ -1448,7 +1555,8 @@ std::vector PresetCollection::get_project_embedded_presets() Preset* new_preset = get_preset_differed_for_save(preset); - project_presets.push_back(new_preset); + if (new_preset) + project_presets.push_back(new_preset); } unlock(); BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, type %1% , total preset counts %2%")%Preset::get_type_string(m_type) %project_presets.size(); @@ -1581,32 +1689,28 @@ void PresetCollection::save_user_presets(const std::string& dir_path, const std: preset->sync_info.clear(); preset->file = path_for_preset(*preset); - if (preset->is_custom_defined()) { + //QDS: only save difference for user preset + std::string inherits = Preset::inherits(preset->config); + if (inherits.empty()) { + // We support custom root preset now + //BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" can not find inherits for %1% , should not happen")%preset->name; + //// QDS add sync info + //preset->sync_info = "delete"; + //need_to_delete_list.push_back(preset->setting_id); + //delete_name_list.push_back(preset->name); preset->save(nullptr); - } else { - //QDS: only save difference for user preset - std::string inherits = Preset::inherits(preset->config); - if (inherits.empty()) { - // We support custom root preset now - //BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" can not find inherits for %1% , should not happen")%preset->name; - //// QDS add sync info - //preset->sync_info = "delete"; - //need_to_delete_list.push_back(preset->setting_id); - //delete_name_list.push_back(preset->name); - preset->save(nullptr); - continue; - } - Preset* parent_preset = this->find_preset(inherits, false, true); - if (!parent_preset) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" can not find parent preset for %1% , inherits %2%")%preset->name %inherits; - continue; - } - - if (preset->base_id.empty()) - preset->base_id = parent_preset->setting_id; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << preset->name << " filament_id: " << preset->filament_id << " base_id: " << preset->base_id; - preset->save(&(parent_preset->config)); + continue; } + Preset* parent_preset = this->find_preset(inherits, false, true); + if (!parent_preset) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" can not find parent preset for %1% , inherits %2%")%preset->name %inherits; + continue; + } + + if (preset->base_id.empty()) + preset->base_id = parent_preset->setting_id; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << preset->name << " filament_id: " << preset->filament_id << " base_id: " << preset->base_id; + preset->save(&(parent_preset->config)); } for (auto delete_name: delete_name_list) @@ -1747,7 +1851,15 @@ bool PresetCollection::load_user_preset(std::string name, std::map *key_set1 = nullptr, *key_set2 = nullptr; + Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); + + new_config.update_diff_values_to_child_config(cloud_config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); + } + else + new_config.apply(std::move(cloud_config)); Preset::normalize(new_config); // Report configuration fields, which are misplaced into a wrong group. std::string incorrect_keys = Preset::remove_invalid_keys(new_config, default_preset.config); @@ -1857,11 +1969,11 @@ bool PresetCollection::validate_preset(const std::string &preset_name, std::stri // Load a preset from an already parsed config file, insert it into the sorted sequence of presets // and select it, losing previous modifications. -Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select, Semver file_version, bool is_custom_defined) +Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select, Semver file_version) { DynamicPrintConfig cfg(this->default_preset().config); cfg.apply_only(config, cfg.keys(), true); - return this->load_preset(path, name, std::move(cfg), select, file_version, is_custom_defined); + return this->load_preset(path, name, std::move(cfg), select, file_version); } static bool profile_print_params_same(const DynamicPrintConfig &cfg_old, const DynamicPrintConfig &cfg_new) @@ -1922,35 +2034,23 @@ std::pair PresetCollection::load_external_preset( it = this->find_preset_renamed(original_name); found = it != m_presets.end(); } + + std::string extruder_id_name, extruder_variant_name; + std::set *key_set1 = nullptr, *key_set2 = nullptr; + Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); + + if (!inherits.empty() && (different_settings_list.size() > 0)) { auto iter = this->find_preset_internal(inherits); if (iter != m_presets.end() && iter->name == inherits) { //std::vector dirty_options = cfg.diff(iter->config); - for (auto &opt : keys) { - if (different_settings_list.find(opt) != different_settings_list.end()) - continue; - ConfigOption *opt_src = iter->config.option(opt); - ConfigOption *opt_dst = cfg.option(opt); - if (opt_src && opt_dst && (*opt_src != *opt_dst)) { - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" change key %1% from old_value %2% to inherit's value %3%, preset_name %4%, inherits_name %5%") - %opt %(opt_dst->serialize()) %(opt_src->serialize()) %original_name %inherits; - opt_dst->set(opt_src); - } - } + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": change preset %1% inherit %2% 's value to %3% 's values")%original_name %inherits %path; + cfg.update_non_diff_values_to_base_config(iter->config, keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); } } else if (found && it->is_system && (different_settings_list.size() > 0)) { - for (auto &opt : keys) { - if (different_settings_list.find(opt) != different_settings_list.end()) - continue; - ConfigOption *opt_src = it->config.option(opt); - ConfigOption *opt_dst = cfg.option(opt); - if (opt_src && opt_dst && (*opt_src != *opt_dst)) { - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" change key %1% from old_value %2% to new_value %3%, preset_name %4%") - %opt %(opt_dst->serialize()) %(opt_src->serialize()) %original_name; - opt_dst->set(opt_src); - } - } + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": change preset %1% 's value to %2% 's values")%original_name %path; + cfg.update_non_diff_values_to_base_config(it->config, keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); } //QDS: add config related logs @@ -2138,7 +2238,7 @@ std::pair PresetCollection::load_external_preset( return std::make_pair(&preset, false); } -Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select, Semver file_version, bool is_custom_defined) +Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select, Semver file_version) { lock(); auto it = this->find_preset_internal(name); @@ -2153,7 +2253,7 @@ Preset& PresetCollection::load_preset(const std::string &path, const std::string preset.config = std::move(config); preset.loaded = true; preset.is_dirty = false; - preset.custom_defined = is_custom_defined ? "1": "0"; + //QDS if (file_version.valid()) preset.version = file_version; @@ -2501,13 +2601,15 @@ const Preset* PresetCollection::get_preset_parent(const Preset& child) const const Preset *PresetCollection::get_preset_base(const Preset &child) const { - if (child.is_system || child.is_default) - return &child; - // Handle user preset - if (child.inherits().empty()) - return &child; // this is user root - auto inherits = find_preset(child.inherits()); - return inherits ? get_preset_base(*inherits) : nullptr; + //y60 + //if (child.is_system || child.is_default) + // return &child; + //// Handle user preset + //if (child.inherits().empty()) + // return &child; // this is user root + //auto inherits = find_preset(child.inherits()); + //return inherits ? get_preset_base(*inherits) : nullptr; + return &child; } // Return vendor of the first parent profile, for which the vendor is defined, or null if such profile does not exist. @@ -2611,6 +2713,17 @@ size_t PresetCollection::first_visible_idx() const return first_visible; } +std::vector PresetCollection::diameters_of_selected_printer() +{ + std::set diameters; + auto printer_model = m_edited_preset.config.opt_string("printer_model"); + for (auto &preset : m_presets) { + if (preset.config.opt_string("printer_model") == printer_model) + diameters.insert(preset.config.opt_string("printer_variant")); + } + return std::vector{diameters.begin(), diameters.end()}; +} + void PresetCollection::set_default_suppressed(bool default_suppressed) { if (m_default_suppressed != default_suppressed) { @@ -2627,8 +2740,13 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil config.set_key_value("printer_preset", new ConfigOptionString(active_printer.preset.name)); const ConfigOption *opt = active_printer.preset.config.option("nozzle_diameter"); if (opt) - config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); - bool some_compatible = false; + config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); + int some_compatible = 0; + + if (active_print) + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": active printer %1%, print %2%, unselect_if_incompatible %3%")%active_printer.preset.name %active_print->preset.name % (int)unselect_if_incompatible; + else + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": active printer %1%, unselect_if_incompatible %2%")%active_printer.preset.name % (int)unselect_if_incompatible; for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++ idx_preset) { bool selected = idx_preset == m_idx_selected; Preset &preset_selected = m_presets[idx_preset]; @@ -2637,19 +2755,29 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil const PresetWithVendorProfile this_preset_with_vendor_profile = this->get_preset_with_vendor_profile(preset_edited); bool was_compatible = preset_edited.is_compatible; preset_edited.is_compatible = is_compatible_with_printer(this_preset_with_vendor_profile, active_printer, &config); - some_compatible |= preset_edited.is_compatible; + if (preset_edited.is_compatible) + some_compatible++; if (active_print != nullptr) preset_edited.is_compatible &= is_compatible_with_print(this_preset_with_vendor_profile, *active_print, active_printer); if (! preset_edited.is_compatible && selected && - (unselect_if_incompatible == PresetSelectCompatibleType::Always || (unselect_if_incompatible == PresetSelectCompatibleType::OnlyIfWasCompatible && was_compatible))) + (unselect_if_incompatible == PresetSelectCompatibleType::Always || (unselect_if_incompatible == PresetSelectCompatibleType::OnlyIfWasCompatible && was_compatible))) + { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": type %1% , previous selected %2% becomes uncompatible, will select later")%Preset::get_type_string(m_type) %m_idx_selected; m_idx_selected = size_t(-1); + } if (selected) preset_selected.is_compatible = preset_edited.is_compatible; } // Update visibility of the default profiles here if the defaults are suppressed, the current profile is not compatible and we don't want to select another compatible profile. if (m_idx_selected >= m_num_default_presets && m_default_suppressed) - for (size_t i = 0; i < m_num_default_presets; ++ i) - m_presets[i].is_visible = ! some_compatible; + { + for (size_t i = 0; i < m_num_default_presets; ++ i) + { + m_presets[i].is_visible = (some_compatible == 0); + } + } + + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": type %1% returned m_idx_selected %2%, some_compatible %3%")%Preset::get_type_string(m_type) %m_idx_selected %some_compatible; return m_idx_selected; } @@ -2666,7 +2794,7 @@ bool PresetCollection::update_dirty() } template -void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys& vec, const ConfigBase &other, const ConfigBase &this_c) +void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys& vec, const ConfigBase &other, const ConfigBase &this_c, bool strict) { const T* opt_init = static_cast(other.option(opt_key)); const T* opt_cur = static_cast(this_c.option(opt_key)); @@ -2680,16 +2808,35 @@ void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys& for (int i = 0; i < int(opt_cur->values.size()); i++) { int init_id = i <= opt_init_max_id ? i : 0; - if (opt_cur->values[i] != opt_init->values[init_id]) - vec.emplace_back(opt_key + "#" + std::to_string(i)); + if (opt_cur->values[i] != opt_init->values[init_id]) { + if (opt_cur->nullable()) { + if (opt_cur->is_nil(i)) { + if (strict && !opt_init->is_nil(init_id)) + vec.emplace_back(opt_key + "#" + std::to_string(i)); + } else { + if (strict || !opt_init->is_nil(init_id)) + vec.emplace_back(opt_key + "#" + std::to_string(i)); + } + } else { + vec.emplace_back(opt_key + "#" + std::to_string(i)); + } + } } } // Use deep_diff to correct return of changed options, considering individual options for each extruder. -inline t_config_option_keys deep_diff(const ConfigBase &config_this, const ConfigBase &config_other) +inline t_config_option_keys deep_diff(const ConfigBase &config_this, const ConfigBase &config_other, bool strict = true) { t_config_option_keys diff; - for (const t_config_option_key &opt_key : config_this.keys()) { + t_config_option_keys keys; + if (strict) { + t_config_option_keys keys_this = config_this.keys(); + t_config_option_keys keys_other = config_other.keys(); + std::set_union(keys_this.begin(), keys_this.end(), keys_other.begin(), keys_other.end(), std::back_inserter(keys)); + } else { + keys = config_this.keys(); + } + for (const t_config_option_key &opt_key : keys) { const ConfigOption *this_opt = config_this.option(opt_key); const ConfigOption *other_opt = config_other.option(opt_key); if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt) @@ -2711,19 +2858,35 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi diff.emplace_back(opt_key); } else { switch (other_opt->type()) { - case coInts: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; - case coBools: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; - case coFloats: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; - case coStrings: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; - case coPercents:add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; - case coFloatsOrPercents: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; - case coPoints: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; + case coInts: add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); break; + case coBools: add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); break; + case coFloats: add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); break; + case coStrings: add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); break; + case coPercents:add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); break; + case coFloatsOrPercents: add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); break; + case coPoints: add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); break; // QDS - case coEnums: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; + case coEnums: add_correct_opts_to_diff(opt_key, diff, config_other, config_this, strict); break; default: diff.emplace_back(opt_key); break; } } } + else if (strict) { + const ConfigOption *opt = nullptr; + if (this_opt != nullptr && other_opt == nullptr) + opt = this_opt; + else if (this_opt == nullptr && other_opt != nullptr) + opt = other_opt; + if (opt) { + if (opt->type() & coVectorType) { + auto vec = dynamic_cast(opt); + for (size_t i = 0; i < vec->size(); i++) + diff.push_back(opt_key + "#" + std::to_string(i)); + } else { + diff.push_back(opt_key); + } + } + } } return diff; } @@ -2800,7 +2963,7 @@ std::vector PresetCollection::dirty_options_without_option_list(con Preset& PresetCollection::select_preset(size_t idx) { //QDS: add config related logs - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": %1% try to select preset %2%")%Preset::get_type_string(m_type) %idx; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1% try to select preset %2%")%Preset::get_type_string(m_type) %idx; for (Preset &preset : m_presets) preset.is_dirty = false; if (idx >= m_presets.size()) @@ -2811,15 +2974,21 @@ Preset& PresetCollection::select_preset(size_t idx) bool default_visible = ! m_default_suppressed || m_idx_selected < m_num_default_presets; for (size_t i = 0; i < m_num_default_presets; ++i) m_presets[i].is_visible = default_visible; + + //set this preset to true + if (!m_presets[idx].is_visible) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1% set %2%, idx %3% to visible") % Preset::get_type_string(m_type) % m_presets[idx].name % idx; + m_presets[idx].is_visible = true; + } //QDS: add config related logs - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": %1% select success, m_idx_selected %2%, name %3%, is_system %4%, is_default %5%")%Preset::get_type_string(m_type) % m_idx_selected % m_edited_preset.name % m_edited_preset.is_system % m_edited_preset.is_default; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1% select success, m_idx_selected %2%, name %3%, is_system %4%, is_default %5%")%Preset::get_type_string(m_type) % m_idx_selected % m_edited_preset.name % m_edited_preset.is_system % m_edited_preset.is_default; return m_presets[idx]; } bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, bool force) { //QDS: add config related logs - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": %1%, try to select by name %2%, force %3%")%Preset::get_type_string(m_type) %name_w_suffix %force; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, try to select by name %2%, force %3%")%Preset::get_type_string(m_type) %name_w_suffix %force; std::string name = Preset::remove_suffix_modified(name_w_suffix); // 1) Try to find the preset by its name. auto it = this->find_preset_internal(name); @@ -2841,19 +3010,22 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b if (m_idx_selected != idx || force) { this->select_preset(idx); //QDS: add config related logs - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": %1%, select %2%, success")%Preset::get_type_string(m_type) %name_w_suffix; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, select %2%, success")%Preset::get_type_string(m_type) %name_w_suffix; return true; } //QDS: add config related logs - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": %1%, select %2%, failed")%Preset::get_type_string(m_type) %name_w_suffix; + if (m_idx_selected == idx) + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, already selected before") % Preset::get_type_string(m_type); + else + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, select %2%, failed")%Preset::get_type_string(m_type) %name_w_suffix; return false; } bool PresetCollection::select_preset_by_name_strict(const std::string &name) { //QDS: add config related logs - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": %1%, try to select by name %2%")%Preset::get_type_string(m_type) %name; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, try to select by name %2%")%Preset::get_type_string(m_type) %name; // 1) Try to find the preset by its name. auto it = this->find_preset_internal(name); @@ -2865,12 +3037,12 @@ bool PresetCollection::select_preset_by_name_strict(const std::string &name) if (idx != (size_t)-1) { this->select_preset(idx); //QDS: add config related logs - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": %1%, select %2%, success")%Preset::get_type_string(m_type) %name; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, select %2%, success")%Preset::get_type_string(m_type) %name; return true; } m_idx_selected = idx; //QDS: add config related logs - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": %1%, select %2%, failed")%Preset::get_type_string(m_type) %name; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, select %2%, failed")%Preset::get_type_string(m_type) %name; return false; } diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index f7e3caa..1f41956 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -60,9 +60,11 @@ #define QDT_JSON_KEY_FAMILY "family" #define QDT_JSON_KEY_BED_MODEL "bed_model" #define QDT_JSON_KEY_BED_TEXTURE "bed_texture" +#define QDT_JSON_KEY_IMAGE_BED_TYPE "image_bed_type" #define QDT_JSON_KEY_DEFAULT_BED_TYPE "default_bed_type" #define QDT_JSON_KEY_HOTEND_MODEL "hotend_model" #define QDT_JSON_KEY_DEFAULT_MATERIALS "default_materials" +#define QDT_JSON_KEY_NOT_SUPPORT_BED_TYPE "not_support_bed_type" #define QDT_JSON_KEY_MODEL_ID "model_id" //QDT: json path @@ -113,12 +115,13 @@ public: std::string family; std::vector variants; std::vector default_materials; + std::vector not_support_bed_types; // Vendor & Printer Model specific print bed model & texture. std::string bed_model; std::string bed_texture; + std::string image_bed_type; std::string default_bed_type; std::string hotend_model; - PrinterVariant* variant(const std::string &name) { for (auto &v : this->variants) if (v.name == name) @@ -243,8 +246,7 @@ public: std::string user_id; // preset user_id std::string base_id; // base id of preset std::string sync_info; // enum: "delete", "create", "update", "" - std::string custom_defined; // enum: "1", "0", "" - std::string description; // + std::string description; // long long updated_time{0}; //last updated time std::map key_values; @@ -316,6 +318,8 @@ public: std::string get_printer_type(PresetBundle *preset_bundle); // get edited preset type std::string get_current_printer_type(PresetBundle *preset_bundle); // get current preset type + static void get_extruder_names_and_keysets(Type type, std::string& extruder_id_name, std::string& extruder_variant_name, std::set** p_key_set1, std::set** p_key_set2); + bool has_lidar(PresetBundle *preset_bundle); bool is_custom_defined(); @@ -460,8 +464,8 @@ public: // Load a preset from an already parsed config file, insert it into the sorted sequence of presets // and select it, losing previous modifications. - Preset& load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select = true, Semver file_version = Semver(), bool is_custom_defined = false); - Preset& load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select = true, Semver file_version = Semver(), bool is_custom_defined = false); + Preset& load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select = true, Semver file_version = Semver()); + Preset& load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select = true, Semver file_version = Semver()); bool clone_presets(std::vector const &presets, std::vector &failures, std::function modifier, bool force_rewritten = false); bool clone_presets_for_printer( @@ -603,7 +607,7 @@ public: int match_quality = -1; for (; i < n; ++ i) // Since we use the filament selection from Wizard, it's needed to control the preset visibility too - if (m_presets[i].is_compatible && m_presets[i].is_visible) { + if (m_presets[i].is_compatible) { int this_match_quality = prefered_condition(m_presets[i]); if (this_match_quality > match_quality) { if (match_quality == std::numeric_limits::max()) @@ -623,6 +627,8 @@ public: // Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible. size_t first_compatible_idx() const { return this->first_compatible_idx([](const Preset&) -> int { return 0; }); } + std::vector diameters_of_selected_printer(); + // Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible. // Return the first visible preset. Certainly at least the '- default -' preset shall be visible. Preset& first_visible() { return this->preset(this->first_visible_idx()); } @@ -640,9 +646,11 @@ public: template void update_compatible(const PresetWithVendorProfile &active_printer, const PresetWithVendorProfile *active_print, PresetSelectCompatibleType select_other_if_incompatible, PreferedCondition prefered_condition) { - if (this->update_compatible_internal(active_printer, active_print, select_other_if_incompatible) == (size_t)-1) + if (this->update_compatible_internal(active_printer, active_print, select_other_if_incompatible) == (size_t)-1) { // Find some other compatible preset, or the "-- default --" preset. - this->select_preset(this->first_compatible_idx(prefered_condition)); + size_t index = this->first_compatible_idx(prefered_condition); + this->select_preset(index); + } } void update_compatible(const PresetWithVendorProfile &active_printer, const PresetWithVendorProfile *active_print, PresetSelectCompatibleType select_other_if_incompatible) { this->update_compatible(active_printer, active_print, select_other_if_incompatible, [](const Preset&) -> int { return 0; }); } diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index b009a63..9fed152 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -2,6 +2,7 @@ #include "PresetBundle.hpp" #include "libslic3r.h" +#include "I18N.hpp" #include "Utils.hpp" #include "Model.hpp" #include "format.hpp" @@ -24,6 +25,8 @@ #include #include +// Mark string for localization and translate. +#define L(s) Slic3r::I18N::translate(s) // Store the print/filament/printer presets into a "presets" subdirectory of the Slic3rPE config dir. // This breaks compatibility with the upstream Slic3r if the --datadir is used to switch between the two versions. @@ -41,6 +44,9 @@ static std::vector s_project_options { "wipe_tower_rotation_angle", "curr_bed_type", "flush_multiplier", + "nozzle_volume_type", + "filament_map_mode", + "filament_map" }; //QDS: add QDT as default @@ -235,7 +241,7 @@ void PresetBundle::copy_files(const std::string& from) } } -PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule substitution_rule, +std::pair PresetBundle::load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule substitution_rule, const PresetPreferences& preferred_selection/* = PresetPreferences()*/) { // First load the vendor specific system presets. @@ -265,7 +271,8 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward //QDS: add config related logs BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" finished, returned substitutions %1%")%substitutions.size(); - return substitutions; + + return std::make_pair(std::move(substitutions), errors_cummulative); } //QDS: add function to generate differed preset for save @@ -327,6 +334,43 @@ Semver PresetBundle::get_vendor_profile_version(std::string vendor_name) return result_ver; } +std::optional PresetBundle::get_filament_by_filament_id(const std::string& filament_id) const +{ + if (filament_id.empty()) + return std::nullopt; + + // basic filament info should be same in the parent preset and child preset + // so just match the filament id is enough + + for (auto iter = filaments.begin(); iter != filaments.end(); ++iter) { + const Preset& filament_preset = *iter; + const auto& config = filament_preset.config; + if (filament_preset.filament_id == filament_id) { + FilamentBaseInfo info; + info.filament_id = filament_id; + info.is_system = filament_preset.is_system; + info.filament_name = filament_preset.alias; + if (config.has("filament_is_support")) + info.is_support = config.option("filament_is_support")->values[0]; + if (config.has("filament_type")) + info.filament_type = config.option("filament_type")->values[0]; + if (config.has("filament_vendor")) + info.vendor = config.option("filament_vendor")->values[0]; + if (config.has("nozzle_temperature_range_high")) + info.nozzle_temp_range_high = config.option("nozzle_temperature_range_high")->values[0]; + if (config.has("nozzle_temperature_range_low")) + info.nozzle_temp_range_low = config.option("nozzle_temperature_range_low")->values[0]; + //y58 + if (config.has("box_temperature_range_high")) + info.box_temp_range_high = config.option("box_temperature_range_high")->values[0]; + if (config.has("box_temperature_range_low")) + info.box_temp_range_low = config.option("box_temperature_range_low")->values[0]; + return info; + } + } + return std::nullopt; +} + //QDS: load project embedded presets PresetsConfigSubstitutions PresetBundle::load_project_embedded_presets(std::vector project_presets, ForwardCompatibilitySubstitutionRule substitution_rule) { @@ -531,7 +575,6 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For if (!fs::exists(user_folder)) fs::create_directory(user_folder); std::string dir_user_presets = data_dir() + "/" + PRESET_USER_DIR + "/" + user; - fs::path folder(user_folder / user); if (!fs::exists(folder)) fs::create_directory(folder); @@ -759,7 +802,6 @@ bool PresetBundle::import_json_presets(PresetsConfigSubstitutions & s boost::optional version = Semver::parse(version_str); if (!version) return false; Semver app_version = *(Semver::parse(SLIC3R_VERSION)); - //1.9.5 if (version->maj() > app_version.maj()) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " Preset incompatibla, not loading: " << name; return false; @@ -1488,6 +1530,7 @@ void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset:: void PresetBundle::load_installed_filaments(AppConfig &config) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": enter, printer size %1%")%printers.size(); //if (! config.has_section(AppConfig::SECTION_FILAMENTS) // || config.get_section(AppConfig::SECTION_FILAMENTS).empty()) { // Compatibility with the PrusaSlicer 2.1.1 and older, where the filament profiles were not installable yet. @@ -1507,6 +1550,7 @@ void PresetBundle::load_installed_filaments(AppConfig &config) { //already has compatible filament add_default_materials = false; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": printer %1% vendor %2% already has default filament %3%")%printer.name %printer.vendor %filament_iter.first; break; } } @@ -1520,6 +1564,7 @@ void PresetBundle::load_installed_filaments(AppConfig &config) BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": can not find printer_model for printer %1%")%printer.name; continue; } + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": printer %1% vendor %2% don't have filament visible, will add %3% default filaments")%printer.name %printer.vendor %printer_model->default_materials.size(); for (auto default_filament: printer_model->default_materials) { Preset* filament = filaments.find_preset(default_filament, false, true); @@ -1532,12 +1577,15 @@ void PresetBundle::load_installed_filaments(AppConfig &config) // compatible_filaments.insert(&filament); } // and mark these filaments as installed, therefore this code will not be executed at the next start of the application. - for (const auto &filament: compatible_filaments) + for (const auto &filament: compatible_filaments) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": set filament %1% to visible by default")%filament->name; config.set(AppConfig::SECTION_FILAMENTS, filament->name, "true"); + } //} for (auto &preset : filaments) preset.set_visible_from_appconfig(config); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": exit."); } void PresetBundle::load_installed_sla_materials(AppConfig &config) @@ -1567,7 +1615,7 @@ void PresetBundle::load_installed_sla_materials(AppConfig &config) // This is done on application start up or after updates are applied. void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& preferred_selection/* = PresetPreferences()*/) { - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": enter, preferred printer_model_id %1%")%preferred_selection.printer_model_id; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": enter, preferred printer_model_id %1%")%preferred_selection.printer_model_id; // Update visibility of presets based on application vendor / model / variant configuration. this->load_installed_printers(config); @@ -1632,6 +1680,16 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p } filament_colors.resize(filament_presets.size(), "#4479FB"); // y6 project_config.option("filament_colour")->values = filament_colors; + + std::vector filament_maps(filament_colors.size(), 1); + project_config.option("filament_map")->values = filament_maps; + + std::vector extruder_ams_count_str; + if (config.has("presets", "extruder_ams_count")) { + boost::algorithm::split(extruder_ams_count_str, config.get("presets", "extruder_ams_count"), boost::algorithm::is_any_of(",")); + } + this->extruder_ams_counts = get_extruder_ams_count(extruder_ams_count_str); + std::vector matrix; if (config.has("presets", "flush_volumes_matrix")) { boost::algorithm::split(matrix, config.get("presets", "flush_volumes_matrix"), boost::algorithm::is_any_of("|")); @@ -1644,9 +1702,9 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p project_config.option("flush_volumes_vector")->values = std::vector(flush_volumes_vector.begin(), flush_volumes_vector.end()); } if (config.has("app", "flush_multiplier")) { - std::string str_flush_multiplier = config.get("app", "flush_multiplier"); - if (!str_flush_multiplier.empty()) - project_config.option("flush_multiplier")->set(new ConfigOptionFloat(std::stof(str_flush_multiplier))); + boost::algorithm::split(matrix, config.get("app", "flush_multiplier"), boost::algorithm::is_any_of("|")); + auto flush_multipliers = matrix | boost::adaptors::transformed(boost::lexical_cast); + project_config.option("flush_multiplier")->values = std::vector(flush_multipliers.begin(), flush_multipliers.end()); } // Update visibility of presets based on their compatibility with the active printer. @@ -1684,6 +1742,23 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p } } + const Preset& current_printer = printers.get_selected_preset(); + const Preset* base_printer = printers.get_preset_base(current_printer); + bool use_default_nozzle_volume_type = true; + if (base_printer) { + std::string prev_nozzle_volume_type = config.get_nozzle_volume_types_from_config(base_printer->name); + if (!prev_nozzle_volume_type.empty()) { + ConfigOptionEnumsGeneric* nozzle_volume_type_option = project_config.option("nozzle_volume_type"); + if (nozzle_volume_type_option->deserialize(prev_nozzle_volume_type)) { + use_default_nozzle_volume_type = false; + } + } + } + + if (use_default_nozzle_volume_type) { + project_config.option("nozzle_volume_type")->values = current_printer.config.option("default_nozzle_volume_type")->values; + } + // Parse the initial physical printer name. std::string initial_physical_printer_name = remove_ini_suffix(config.get("presets", "physical_printer")); @@ -1691,7 +1766,7 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p if (!initial_physical_printer_name.empty()) physical_printers.select_printer(initial_physical_printer_name); - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": finished, preferred printer_model_id %1%")%preferred_selection.printer_model_id; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": finished, preferred printer_model_id %1%")%preferred_selection.printer_model_id; } // Export selections (current print, current filaments, current printer) into config.ini @@ -1712,6 +1787,7 @@ void PresetBundle::export_selections(AppConfig &config) CNumericLocalesSetter locales_setter; std::string filament_colors = boost::algorithm::join(project_config.option("filament_colour")->values, ","); config.set("presets", "filament_colors", filament_colors); + std::string flush_volumes_matrix = boost::algorithm::join(project_config.option("flush_volumes_matrix")->values | boost::adaptors::transformed(static_cast(std::to_string)), "|"); @@ -1723,20 +1799,23 @@ void PresetBundle::export_selections(AppConfig &config) config.set("presets", PRESET_PRINTER_NAME, printers.get_selected_preset_name()); - auto flush_multi_opt = project_config.option("flush_multiplier"); - config.set("flush_multiplier", std::to_string(flush_multi_opt ? flush_multi_opt->getFloat() : 1.0f)); + std::string flush_multiplier_str = boost::algorithm::join(project_config.option("flush_multiplier")->values | + boost::adaptors::transformed(static_cast(std::to_string)), + "|"); + config.set("flush_multiplier", flush_multiplier_str); + // QDS //config.set("presets", "sla_print", sla_prints.get_selected_preset_name()); //config.set("presets", "sla_material", sla_materials.get_selected_preset_name()); //config.set("presets", "physical_printer", physical_printers.get_selected_full_printer_name()); //QDS: add config related log - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": printer %1%, print %2%, filaments[0] %3% ")%printers.get_selected_preset_name() % prints.get_selected_preset_name() %filament_presets[0]; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": printer %1%, print %2%, filaments[0] %3% ")%printers.get_selected_preset_name() % prints.get_selected_preset_name() %filament_presets[0]; } // QDS void PresetBundle::set_num_filaments(unsigned int n, std::string new_color) { - int old_filament_count = this->filament_presets.size(); + unsigned old_filament_count = this->filament_presets.size(); if (n > old_filament_count && old_filament_count != 0) filament_presets.resize(n, filament_presets.back()); else { @@ -1744,13 +1823,15 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color) } ConfigOptionStrings* filament_color = project_config.option("filament_colour"); + ConfigOptionInts* filament_map = project_config.option("filament_map"); filament_color->resize(n); + filament_map->values.resize(n, 1); ams_multi_color_filment.resize(n); //QDS set new filament color to new_color if (old_filament_count < n) { if (!new_color.empty()) { - for (int i = old_filament_count; i < n; i++) { + for (unsigned i = old_filament_count; i < n; i++) { filament_color->values[i] = new_color; } } @@ -1759,30 +1840,152 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color) update_multi_material_filament_presets(); } -unsigned int PresetBundle::sync_ams_list(unsigned int &unknowns) +void PresetBundle::update_num_filaments(unsigned int to_del_flament_id) { - std::vector filament_presets; - std::vector filament_colors; + unsigned old_filament_count = this->filament_presets.size(); + assert(to_del_flament_id < old_filament_count); + filament_presets.erase(filament_presets.begin() + to_del_flament_id); + + ConfigOptionStrings *filament_color = project_config.option("filament_colour"); + ConfigOptionInts* filament_map = project_config.option("filament_map"); + + if (filament_color->values.size() > to_del_flament_id) { + filament_color->values.erase(filament_color->values.begin() + to_del_flament_id); + if (filament_map->values.size() > to_del_flament_id) { + filament_map->values.erase(filament_map->values.begin() + to_del_flament_id); + } + } + else { + filament_color->values.resize(to_del_flament_id); + filament_map->values.resize(to_del_flament_id, 1); + } + + if (ams_multi_color_filment.size() > to_del_flament_id){ + ams_multi_color_filment.erase(ams_multi_color_filment.begin() + to_del_flament_id); + } + else { + ams_multi_color_filment.resize(to_del_flament_id); + } + + update_multi_material_filament_presets(to_del_flament_id); +} + + +void PresetBundle::get_ams_cobox_infos(AMSComboInfo& combox_info) +{ + combox_info.clear(); + for (auto &entry : filament_ams_list) { + auto &ams = entry.second; + auto filament_id = ams.opt_string("filament_id", 0u); + auto filament_color = ams.opt_string("filament_colour", 0u); + auto ams_name = ams.opt_string("tray_name", 0u); + auto filament_changed = !ams.has("filament_changed") || ams.opt_bool("filament_changed"); + auto filament_multi_color = ams.opt("filament_multi_colors")->values; + if (filament_id.empty()) { + continue; + } + if (!filament_changed && this->filament_presets.size() > combox_info.ams_filament_presets.size()) { + combox_info.ams_filament_presets.push_back(this->filament_presets[combox_info.ams_filament_presets.size()]); + combox_info.ams_filament_colors.push_back(filament_color); + combox_info.ams_multi_color_filment.push_back(filament_multi_color); + combox_info.ams_names.push_back(ams_name); + continue; + } + auto iter = std::find_if(filaments.begin(), filaments.end(), + [this, &filament_id](auto &f) { return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; }); + if (iter == filaments.end()) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": filament_id %1% not found or system or compatible") % filament_id; + auto filament_type = ams.opt_string("filament_type", 0u); + if (!filament_type.empty()) { + filament_type = "Generic " + filament_type; + iter = std::find_if(filaments.begin(), filaments.end(), + [&filament_type](auto &f) { return f.is_compatible && f.is_system && boost::algorithm::starts_with(f.name, filament_type); }); + } + if (iter == filaments.end()) { + // Prefer old selection + if (combox_info.ams_filament_presets.size() < this->filament_presets.size()) { + combox_info.ams_filament_presets.push_back(this->filament_presets[combox_info.ams_filament_presets.size()]); + combox_info.ams_filament_colors.push_back(filament_color); + combox_info.ams_multi_color_filment.push_back(filament_multi_color); + combox_info.ams_names.push_back(ams_name); + continue; + } + iter = std::find_if(filaments.begin(), filaments.end(), [&filament_type](auto &f) { return f.is_compatible && f.is_system; }); + if (iter == filaments.end()) + continue; + } + filament_id = iter->filament_id; + } + combox_info.ams_filament_presets.push_back(iter->name); + combox_info.ams_filament_colors.push_back(filament_color); + combox_info.ams_multi_color_filment.push_back(filament_multi_color); + combox_info.ams_names.push_back(ams_name); + } +} + +unsigned int PresetBundle::sync_ams_list(std::vector> &unknowns, bool use_map, std::map &maps,bool enable_append, MergeFilamentInfo &merge_info) +{ + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "use_map:" << use_map << " enable_append:" << enable_append; + std::vector ams_filament_presets; + std::vector ams_filament_colors; + std::vector ams_array_maps; ams_multi_color_filment.clear(); - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": filament_ams_list size: %1%") % filament_ams_list.size(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": filament_ams_list size: %1%") % filament_ams_list.size(); + struct AmsInfo + { + bool valid{false}; + bool is_map{false}; + std::string filament_color = ""; + std::string filament_preset = ""; + std::vector mutli_filament_color; + }; + auto is_double_extruder = get_printer_extruder_count() == 2; + std::vector ams_infos; + int index = 0; for (auto &entry : filament_ams_list) { auto & ams = entry.second; auto filament_id = ams.opt_string("filament_id", 0u); auto filament_color = ams.opt_string("filament_colour", 0u); auto filament_changed = !ams.has("filament_changed") || ams.opt_bool("filament_changed"); auto filament_multi_color = ams.opt("filament_multi_colors")->values; - if (filament_id.empty()) continue; - if (!filament_changed && this->filament_presets.size() > filament_presets.size()) { - filament_presets.push_back(this->filament_presets[filament_presets.size()]); - filament_colors.push_back(filament_color); + //y59 + auto ams_id = std::to_string(std::stoi(ams.opt_string("slot_id", 0u)) / 4 + 1); + + auto slot_id = ams.opt_string("slot_id", 0u); + ams_infos.push_back({filament_id.empty() ? false : true,false, filament_color}); + AMSMapInfo temp = {ams_id, slot_id}; + ams_array_maps.push_back(temp); + index++; + if (filament_id.empty()) { + if (use_map) { + for (int j = maps.size() - 1; j >= 0; j--) { + if (maps[j].slot_id == slot_id && maps[j].ams_id == ams_id) { + maps.erase(j); + } + } + ams_filament_presets.push_back("Generic PLA");//for unknow matieral + auto default_unknown_color = "#CECECE"; + ams_filament_colors.push_back(default_unknown_color); + if (filament_multi_color.size() == 0) { + filament_multi_color.push_back(default_unknown_color); + } + ams_multi_color_filment.push_back(filament_multi_color); + } + continue; + } + if (!filament_changed && this->filament_presets.size() > ams_filament_presets.size()) { + ams_filament_presets.push_back(this->filament_presets[ams_filament_presets.size()]); + ams_filament_colors.push_back(filament_color); ams_multi_color_filment.push_back(filament_multi_color); continue; } - auto iter = std::find_if(filaments.begin(), filaments.end(), [this, &filament_id](auto &f) { + bool has_type = false; + auto filament_type = ams.opt_string("filament_type", 0u); + auto iter = std::find_if(filaments.begin(), filaments.end(), [this, &filament_id, &has_type, filament_type](auto &f) { + has_type |= f.config.opt_string("filament_type", 0u) == filament_type; return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; }); if (iter == filaments.end()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": filament_id %1% not found or system or compatible") % filament_id; - auto filament_type = ams.opt_string("filament_type", 0u); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": filament_id %1% not found or system or compatible") % filament_id; if (!filament_type.empty()) { filament_type = "Generic " + filament_type; iter = std::find_if(filaments.begin(), filaments.end(), [&filament_type](auto &f) { @@ -1792,34 +1995,181 @@ unsigned int PresetBundle::sync_ams_list(unsigned int &unknowns) } if (iter == filaments.end()) { // Prefer old selection - if (filament_presets.size() < this->filament_presets.size()) { - filament_presets.push_back(this->filament_presets[filament_presets.size()]); - filament_colors.push_back(filament_color); + if (ams_filament_presets.size() < this->filament_presets.size()) { + ams_filament_presets.push_back(this->filament_presets[ams_filament_presets.size()]); + ams_filament_colors.push_back(filament_color); ams_multi_color_filment.push_back(filament_multi_color); - ++unknowns; + unknowns.emplace_back(&ams, has_type ? L("The filament may not be compatible with the current machine settings. Generic filament presets will be used.") : + L("The filament model is unknown. Still using the previous filament preset.")); continue; } - iter = std::find_if(filaments.begin(), filaments.end(), [&filament_type](auto &f) { - return f.is_compatible && f.is_system; + iter = std::find_if(filaments.begin(), filaments.end(), [](auto &f) { + return f.is_compatible && f.is_system; }); if (iter == filaments.end()) continue; } - ++unknowns; + unknowns.emplace_back(&ams, boost::algorithm::starts_with(iter->name, filament_type) ? + (has_type ? L("The filament may not be compatible with the current machine settings. Generic filament presets will be used.") : + L("The filament model is unknown. Generic filament presets will be used.")) : + (has_type ? L("The filament may not be compatible with the current machine settings. A random filament preset will be used.") : + L("The filament model is unknown. A random filament preset will be used."))); filament_id = iter->filament_id; } - filament_presets.push_back(iter->name); - filament_colors.push_back(filament_color); + ams_filament_presets.push_back(iter->name); + ams_filament_colors.push_back(filament_color); ams_multi_color_filment.push_back(filament_multi_color); } - if (filament_presets.empty()) + if (ams_filament_presets.empty()) return 0; - this->filament_presets = filament_presets; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "get filament_colour and from config"; ConfigOptionStrings *filament_color = project_config.option("filament_colour"); - filament_color->resize(filament_presets.size()); - filament_color->values = filament_colors; + ConfigOptionInts * filament_map = project_config.option("filament_map"); + if (use_map) { + auto check_has_merge_info = [](std::map &maps, MergeFilamentInfo &merge_info, int exist_colors_size) { + std::map done; + for (int i = 0; i < maps.size(); i++) { + std::vector same_ams; + same_ams.emplace_back(i); + for (size_t j = i + 1; j < maps.size(); j++) { + if (done.find(j) != done.end()) { + continue; + } + if (maps[i].slot_id == "" || maps[i].ams_id == ""){//reserve + continue; + } + if (maps[i].slot_id == maps[j].slot_id && maps[i].ams_id == maps[j].ams_id) { + same_ams.emplace_back(j); + done[j] =true; + } + } + if (same_ams.size() > 1) { + merge_info.merges.emplace_back(same_ams); + } + } + }; + check_has_merge_info(maps, merge_info,filament_color->values.size()); + auto get_map_index = [&ams_infos](const std::vector &infos, const AMSMapInfo &temp) { + for (int i = 0; i < infos.size(); i++) { + if (infos[i].slot_id == temp.slot_id && infos[i].ams_id == temp.ams_id) { + ams_infos[i].is_map = true; + return i; + } + } + return -1; + }; + std::vector need_append_colors; + auto exist_colors = filament_color->values; + auto exist_filament_presets = this->filament_presets; + std::vector> exist_multi_color_filment; + exist_multi_color_filment.resize(exist_colors.size()); + for (int i = 0; i < exist_colors.size(); i++) { + exist_multi_color_filment[i] = {exist_colors[i]}; + } + for (size_t i = 0; i < exist_colors.size(); i++) { + if (maps.find(i) != maps.end()) {//mapping exist + auto valid_index = get_map_index(ams_array_maps, maps[i]); + if (valid_index >= 0 && valid_index < ams_filament_presets.size()) { + exist_colors[i] = ams_filament_colors[valid_index]; + exist_filament_presets[i] = ams_filament_presets[valid_index]; + exist_multi_color_filment[i] = ams_multi_color_filment[valid_index]; + } else { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "check error: array bound (mapping exist)"; + } + } + } + for (size_t i = 0; i < ams_infos.size(); i++) {// check append + if (ams_infos[i].valid) { + if (i >= ams_filament_presets.size()) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "check error: array bound (check append)"; + continue; + } + ams_infos[i].filament_preset = ams_filament_presets[i]; + ams_infos[i].mutli_filament_color = ams_multi_color_filment[i]; + if (!ams_infos[i].is_map) { + need_append_colors.emplace_back(ams_infos[i]); + ams_filament_colors[i] = ""; + ams_filament_presets[i] = ""; + ams_multi_color_filment[i] = std::vector(); + } + } + else { + ams_filament_colors[i] = ""; + ams_filament_presets[i] = ""; + ams_multi_color_filment[i] = std::vector(); + } + } + //delete redundant color + ams_filament_colors.erase(std::remove_if(ams_filament_colors.begin(), ams_filament_colors.end(), [](std::string &value) { return value.empty(); }), + ams_filament_colors.end()); + ams_filament_presets.erase(std::remove_if(ams_filament_presets.begin(), ams_filament_presets.end(), [](std::string &value) { return value.empty(); }), + ams_filament_presets.end()); + ams_multi_color_filment.erase(std::remove_if(ams_multi_color_filment.begin(), ams_multi_color_filment.end(), + [](std::vector &value) { return value.empty(); }), + ams_multi_color_filment.end()); + if (need_append_colors.size() > 0 && enable_append) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "need_append_colors.size() > 0 && enable_append"; + auto get_idx_in_array = [](std::vector &presets, std::vector &colors, const std::string &preset, const std::string &color) -> int { + for (size_t i = 0; i < presets.size(); i++) { + if (presets[i] == preset && colors[i] == color) { + return i; + } + } + return -1; + }; + for (size_t i = 0; i < need_append_colors.size(); i++){ + if (exist_filament_presets.size() >= size_t(EnforcerBlockerType::ExtruderMax)){ + break; + } + auto idx = get_idx_in_array(exist_filament_presets, exist_colors, need_append_colors[i].filament_preset, need_append_colors[i].filament_color); + if (idx >= 0) { + continue; + } + exist_filament_presets.push_back(need_append_colors[i].filament_preset); + exist_colors.push_back(need_append_colors[i].filament_color); + exist_multi_color_filment.push_back(need_append_colors[i].mutli_filament_color); + } + } + filament_color->resize(exist_colors.size()); + filament_color->values = exist_colors; + ams_multi_color_filment = exist_multi_color_filment; + this->filament_presets = exist_filament_presets; + filament_map->values.resize(exist_filament_presets.size(), 1); + } + else {//overwrite + filament_color->resize(ams_filament_presets.size()); + filament_color->values = ams_filament_colors; + this->filament_presets = ams_filament_presets; + filament_map->values.resize(ams_filament_colors.size(), 1); + } + update_multi_material_filament_presets(); - return filament_presets.size(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "finish sync ams list"; + return this->filament_presets.size(); +} + +std::vector PresetBundle::get_used_tpu_filaments(const std::vector &used_filaments) +{ + std::vector tpu_filaments; + for (size_t i = 0; i < this->filament_presets.size(); ++i) { + auto iter = std::find(used_filaments.begin(), used_filaments.end(), i + 1); + if (iter == used_filaments.end()) continue; + + std::string filament_name = this->filament_presets[i]; + for (int f_index = 0; f_index < this->filaments.size(); f_index++) { + PresetCollection *filament_presets = &this->filaments; + Preset *preset = &filament_presets->preset(f_index); + int size = this->filaments.size(); + if (preset && filament_name.compare(preset->name) == 0) { + std::string display_filament_type; + std::string filament_type = preset->config.get_filament_type(display_filament_type); + if (display_filament_type == "TPU") { + tpu_filaments.push_back(i); + } + } + } + } + return tpu_filaments; } //w42 @@ -1893,7 +2243,7 @@ void PresetBundle::set_calibrate_printer(std::string name) DynamicPrintConfig config; config.set_key_value("printer_preset", new ConfigOptionString(active_printer.preset.name)); const ConfigOption *opt = active_printer.preset.config.option("nozzle_diameter"); - if (opt) config.set_key_value("num_extruders", new ConfigOptionInt((int) static_cast(opt)->values.size())); + if (opt) config.set_key_value("num_extruders", new ConfigOptionInt((int) static_cast(opt)->values.size())); calibrate_filaments.clear(); for (size_t i = filaments.num_default_presets(); i < filaments.size(); ++i) { const Preset & preset = filaments.m_presets[i]; @@ -1918,8 +2268,26 @@ std::set PresetBundle::get_vendors() return qidiVendors; } -std::set PresetBundle::get_printer_names_by_printer_type_and_nozzle(const std::string &printer_type, std::string nozzle_diameter_str) +std::vector> PresetBundle::get_extruder_filament_info() const { + std::vector> filament_infos; + int extruder_nums = get_printer_extruder_count(); + if (extruder_nums > 1) { + filament_infos.resize(extruder_nums, std::vector()); + for (auto ams_item : filament_ams_list) { + if (ams_item.first & 0x10000) { // right + filament_infos[1].push_back(ams_item.second); + } else { // left + filament_infos[0].push_back(ams_item.second); + } + } + } + return filament_infos; +} + +std::set PresetBundle::get_printer_names_by_printer_type_and_nozzle(const std::string &printer_type, std::string nozzle_diameter_str, bool system_only) +{ + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << __LINE__ << "printer_type: " << printer_type << "nozzle_diameter_str" << nozzle_diameter_str; std::set printer_names; /* unknown or empty printer type */ @@ -1932,7 +2300,7 @@ std::set PresetBundle::get_printer_names_by_printer_type_and_nozzle std::ostringstream stream; for (auto printer_it = this->printers.begin(); printer_it != this->printers.end(); printer_it++) { - if (!printer_it->is_system) continue; + if (system_only && !printer_it->is_system) continue; ConfigOption * printer_model_opt = printer_it->config.option("printer_model"); ConfigOptionString *printer_model_str = dynamic_cast(printer_model_opt); @@ -2015,10 +2383,41 @@ bool PresetBundle::check_filament_temp_equation_by_printer_type_and_nozzle_for_m return is_equation; } +Preset *PresetBundle::get_similar_printer_preset(std::string printer_model, std::string printer_variant) +{ + if (printer_model.empty()) + printer_model = printers.get_selected_preset().config.opt_string("printer_model"); + auto printer_variant_old = printers.get_selected_preset().config.opt_string("printer_variant"); + std::map printer_presets; + for (auto &preset : printers.m_presets) { + if (printer_variant.empty() && !preset.is_system) + continue; + if (preset.config.opt_string("printer_model") == printer_model) + printer_presets.insert({preset.name, &preset}); + } + if (printer_presets.empty()) + return nullptr; + auto prefer_printer = printers.get_selected_preset().name; + if (!printer_variant.empty()) + boost::replace_all(prefer_printer, printer_variant_old, printer_variant); + else if (auto n = prefer_printer.find(printer_variant_old); n != std::string::npos) + prefer_printer = printer_model + " " + printer_variant_old + prefer_printer.substr(n + printer_variant_old.length()); + if (auto iter = printer_presets.find(prefer_printer); iter != printer_presets.end()) { + return iter->second; + } + if (printer_variant.empty()) + printer_variant = printer_variant_old; + for (auto& preset : printer_presets) { + if (preset.second->config.opt_string("printer_variant") == printer_variant) + return preset.second; + } + return printer_presets.begin()->second; +} + //QDS: check whether this is the only edited filament bool PresetBundle::is_the_only_edited_filament(unsigned int filament_index) { - int n = this->filament_presets.size(); + unsigned n = this->filament_presets.size(); if (filament_index >= n) return false; @@ -2027,7 +2426,7 @@ bool PresetBundle::is_the_only_edited_filament(unsigned int filament_index) if (edited_preset.name != name) return false; - int index = 0; + unsigned index = 0; while (index < n) { if (index == filament_index) { @@ -2043,16 +2442,40 @@ bool PresetBundle::is_the_only_edited_filament(unsigned int filament_index) return true; } -DynamicPrintConfig PresetBundle::full_config() const +void PresetBundle::reset_default_nozzle_volume_type() +{ + Preset& current_printer = this->printers.get_edited_preset(); + this->project_config.option("nozzle_volume_type")->values = current_printer.config.option("default_nozzle_volume_type")->values; +} + +int PresetBundle::get_printer_extruder_count() const +{ + const Preset& printer_preset = this->printers.get_edited_preset(); + + int count = printer_preset.config.option("nozzle_diameter")->values.size(); + + return count; +} + +bool PresetBundle::support_different_extruders() +{ + Preset& printer_preset = this->printers.get_edited_preset(); + int extruder_count; + bool supported = printer_preset.config.support_different_extruders(extruder_count); + + return supported; +} + +DynamicPrintConfig PresetBundle::full_config(bool apply_extruder, std::optional>filament_maps) const { return (this->printers.get_edited_preset().printer_technology() == ptFFF) ? - this->full_fff_config() : + this->full_fff_config(apply_extruder, filament_maps) : this->full_sla_config(); } -DynamicPrintConfig PresetBundle::full_config_secure() const +DynamicPrintConfig PresetBundle::full_config_secure(std::optional>filament_maps) const { - DynamicPrintConfig config = this->full_config(); + DynamicPrintConfig config = this->full_fff_config(false, filament_maps); //QDS example: config.erase("print_host"); config.erase("print_host_webui"); config.erase("printhost_apikey"); @@ -2068,7 +2491,7 @@ const std::set ignore_settings_list ={ "print_settings_id", "filament_settings_id", "printer_settings_id" }; -DynamicPrintConfig PresetBundle::full_fff_config() const +DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optional> filament_maps_new) const { DynamicPrintConfig out; out.apply(FullPrintConfig::defaults()); @@ -2080,7 +2503,16 @@ DynamicPrintConfig PresetBundle::full_fff_config() const // QDS size_t num_filaments = this->filament_presets.size(); - auto* extruder_diameter = dynamic_cast(out.option("nozzle_diameter")); + + std::vector filament_maps = out.option("filament_map")->values; + if (filament_maps_new.has_value()) + filament_maps = *filament_maps_new; + //in some middle state, they may be different + if (filament_maps.size() != num_filaments) { + filament_maps.resize(num_filaments, 1); + } + + auto* extruder_diameter = dynamic_cast(out.option("nozzle_diameter")); // Collect the "compatible_printers_condition" and "inherits" values over all presets (print, filaments, printers) into a single vector. std::vector compatible_printers_condition; std::vector compatible_prints_condition; @@ -2107,8 +2539,20 @@ DynamicPrintConfig PresetBundle::full_fff_config() const } different_settings.emplace_back(different_print_settings); + //QDS: update printer config related with variants + if (apply_extruder) { + out.update_values_to_printer_extruders(out, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant"); + out.update_values_to_printer_extruders(out, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2); + //update print config related with variants + out.update_values_to_printer_extruders(out, print_options_with_variant, "print_extruder_id", "print_extruder_variant"); + } + if (num_filaments <= 1) { - out.apply(this->filaments.get_edited_preset().config); + //QDS: update filament config related with variants + DynamicPrintConfig filament_config = this->filaments.get_edited_preset().config; + if (apply_extruder) + filament_config.update_values_to_printer_extruders(out, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0]); + out.apply(filament_config); compatible_printers_condition.emplace_back(this->filaments.get_edited_preset().compatible_printers_condition()); compatible_prints_condition .emplace_back(this->filaments.get_edited_preset().compatible_prints_condition()); //QDS: add logic for settings check between different system presets @@ -2129,6 +2573,10 @@ DynamicPrintConfig PresetBundle::full_fff_config() const } different_settings.emplace_back(different_filament_settings); + + std::vector& filament_self_indice = out.option("filament_self_index", true)->values; + int index_size = out.option("filament_extruder_variant")->size(); + filament_self_indice.resize(index_size, 1); } else { // Retrieve filament presets and build a single config object for them. // First collect the filament configurations based on the user selection of this->filament_presets. @@ -2192,7 +2640,16 @@ DynamicPrintConfig PresetBundle::full_fff_config() const different_settings.emplace_back(different_filament_settings); } + std::vector filament_temp_configs; + filament_temp_configs.resize(num_filaments); + for (size_t i = 0; i < num_filaments; ++i) { + filament_temp_configs[i] = *(filament_configs[i]); + if (apply_extruder) + filament_temp_configs[i].update_values_to_printer_extruders(out, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i]); + } + // loop through options and apply them to the resulting config. + std::vector filament_variant_count(num_filaments, 1); for (const t_config_option_key &key : this->filaments.default_preset().config.keys()) { if (key == "compatible_prints" || key == "compatible_printers") continue; @@ -2200,18 +2657,45 @@ DynamicPrintConfig PresetBundle::full_fff_config() const ConfigOption *opt_dst = out.option(key, false); if (opt_dst->is_scalar()) { // Get an option, do not create if it does not exist. - const ConfigOption *opt_src = filament_configs.front()->option(key); + const ConfigOption *opt_src = filament_temp_configs.front().option(key); if (opt_src != nullptr) opt_dst->set(opt_src); } else { // QDS ConfigOptionVectorBase* opt_vec_dst = static_cast(opt_dst); { - std::vector filament_opts(num_filaments, nullptr); - // Setting a vector value from all filament_configs. - for (size_t i = 0; i < filament_opts.size(); ++i) - filament_opts[i] = filament_configs[i]->option(key); - opt_vec_dst->set(filament_opts); + if (apply_extruder) { + std::vector filament_opts(num_filaments, nullptr); + // Setting a vector value from all filament_configs. + for (size_t i = 0; i < filament_opts.size(); ++i) + filament_opts[i] = filament_temp_configs[i].option(key); + opt_vec_dst->set(filament_opts); + } + else { + for (size_t i = 0; i < num_filaments; ++i) { + const ConfigOptionVectorBase* filament_option = static_cast(filament_temp_configs[i].option(key)); + if (i == 0) + opt_vec_dst->set(filament_option); + else + opt_vec_dst->append(filament_option); + + if (key == "filament_extruder_variant") + filament_variant_count[i] = filament_option->size(); + } + } + } + } + } + + if (!apply_extruder) { + //append filament_self_index + std::vector& filament_self_indice = out.option("filament_self_index", true)->values; + int index_size = out.option("filament_extruder_variant")->size(); + filament_self_indice.resize(index_size, 1); + int k = 0; + for (size_t i = 0; i < num_filaments; i++) { + for (size_t j = 0; j < filament_variant_count[i]; j++) { + filament_self_indice[k++] = i + 1; } } } @@ -2251,6 +2735,7 @@ DynamicPrintConfig PresetBundle::full_fff_config() const out.option("filament_settings_id", true)->values = this->filament_presets; out.option("printer_settings_id", true)->value = this->printers.get_selected_preset_name(); out.option("filament_ids", true)->values = filament_ids; + out.option("filament_map", true)->values = filament_maps; // Serialize the collected "compatible_printers_condition" and "inherits" fields. // There will be 1 + num_exturders fields for "inherits" and 2 + num_extruders for "compatible_printers_condition" stored. // The vector will not be stored if all fields are empty strings. @@ -2270,6 +2755,7 @@ DynamicPrintConfig PresetBundle::full_fff_config() const //QDS: add logic for settings check between different system presets add_if_some_non_empty(std::move(different_settings), "different_settings_to_system"); add_if_some_non_empty(std::move(print_compatible_printers), "print_compatible_printers"); + out.option("extruder_ams_count", true)->values = save_extruder_ams_count_to_string(this->extruder_ams_counts); out.option("printer_technology", true)->value = ptFFF; return out; @@ -2399,7 +2885,7 @@ ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, Forw // Load a config file from a boost property_tree. This is a private method called from load_config_file. // is_external == false on if called from ConfigWizard -void PresetBundle::load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config, Semver file_version, bool selected, bool is_custom_defined) +void PresetBundle::load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config, Semver file_version, bool selected) { PrinterTechnology printer_technology = Preset::printer_technology(config); @@ -2421,7 +2907,10 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool 1; #else // QDS: use filament_colour insteadof filament_settings_id, filament_settings_id sometimes is not generated - size_t num_filaments = config.option("filament_colour")->size(); + ConfigOptionStrings* filament_colour_option = config.option("filament_colour"); + size_t num_filaments = filament_colour_option?filament_colour_option->size():0; + if (num_filaments == 0) + throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + name_or_path); #endif //QDS: add config related logs @@ -2458,6 +2947,48 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool default: break; } + bool process_multi_extruder = false; + std::vector filament_variant_index; + size_t extruder_variant_count; + if (!config.option("filament_self_index")) { + std::vector& filament_self_indice = config.option("filament_self_index", true)->values; + filament_self_indice.resize(num_filaments); + for (int index = 0; index < num_filaments; index++) + filament_self_indice[index] = index + 1; + } + std::vector filament_self_indice = std::move(config.option("filament_self_index")->values); + if (config.option("extruder_variant_list")) { + //3mf support multiple extruder logic + size_t extruder_count = config.option("nozzle_diameter")->values.size(); + extruder_variant_count = config.option("filament_extruder_variant", true)->size(); + if ((extruder_variant_count != filament_self_indice.size()) + || (extruder_variant_count < num_filaments)) { + assert(false); + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": invalid config file %1%, can not find suitable filament_extruder_variant or filament_self_index") % name_or_path; + throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + name_or_path); + } + if (num_filaments != extruder_variant_count) { + process_multi_extruder = true; + filament_variant_index.resize(num_filaments, 0); + + size_t cur_filament_id = 1; + for (size_t index = 0; index < filament_self_indice.size(); index++) { + if (filament_self_indice[index] == cur_filament_id) { + filament_variant_index[cur_filament_id - 1] = index; + cur_filament_id++; + if (cur_filament_id > num_filaments) + break; + } + } + } + } + //no need to parse extruder_ams_count + std::vector extruder_ams_count = std::move(config.option("extruder_ams_count", true)->values); + config.erase("extruder_ams_count"); + if (this->extruder_ams_counts.empty()) + this->extruder_ams_counts = get_extruder_ams_count(extruder_ams_count); + + // 1) Create a name from the file name. // Keep the suffix (.ini, .gcode, .amf, .3mf etc) to differentiate it from the normal profiles. std::string name = is_external ? boost::filesystem::path(name_or_path).filename().string() : name_or_path; @@ -2469,7 +3000,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool [&config, &inherits, &inherits_values, &compatible_printers_condition, &compatible_printers_condition_values, &compatible_prints_condition, &compatible_prints_condition_values, - is_external, &name, &name_or_path, file_version, selected, is_custom_defined] + is_external, &name, &name_or_path, file_version, selected] (PresetCollection &presets, size_t idx, const std::string &key, const std::set &different_keys, std::string filament_id) { // Split the "compatible_printers_condition" and "inherits" values one by one from a single vector to the print & printer profiles. inherits = inherits_values[idx]; @@ -2481,7 +3012,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool if (is_external) presets.load_external_preset(name_or_path, name, config.opt_string(key, true), config, different_keys, PresetCollection::LoadAndSelect::Always, file_version, filament_id); else - presets.load_preset(presets.path_from_name(name, inherits.empty()), name, config, selected, file_version, is_custom_defined).save(nullptr); + presets.load_preset(presets.path_from_name(name, inherits.empty()), name, config, selected, file_version).save(nullptr); }; switch (Preset::printer_technology(config)) { @@ -2550,7 +3081,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool loaded = this->filaments.load_external_preset(name_or_path, name, old_filament_profile_names->values.front(), config, filament_different_keys_set, PresetCollection::LoadAndSelect::Always, file_version, filament_id).first; else { // called from Config Wizard. - loaded= &this->filaments.load_preset(this->filaments.path_from_name(name, inherits.empty()), name, config, true, file_version, is_custom_defined); + loaded= &this->filaments.load_preset(this->filaments.path_from_name(name, inherits.empty()), name, config, true, file_version); loaded->save(nullptr); } this->filament_presets.clear(); @@ -2561,7 +3092,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool std::vector configs(num_filaments, this->filaments.default_preset().config); // loop through options and scatter them into configs. for (const t_config_option_key &key : this->filaments.default_preset().config.keys()) { - const ConfigOption *other_opt = config.option(key); + ConfigOption *other_opt = config.option(key); if (other_opt == nullptr) continue; if (other_opt->is_scalar()) { @@ -2569,8 +3100,18 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool configs[i].option(key, false)->set(other_opt); } else if (key != "compatible_printers" && key != "compatible_prints") { - for (size_t i = 0; i < configs.size(); ++i) - static_cast(configs[i].option(key, false))->set_at(other_opt, 0, i); + for (size_t i = 0; i < configs.size(); ++i) { + if (process_multi_extruder && (filament_options_with_variant.find(key) != filament_options_with_variant.end())) { + ConfigOptionVectorBase* other_opt_vec = static_cast(other_opt); + if (other_opt_vec->size() != extruder_variant_count) { + other_opt_vec->resize(extruder_variant_count); + } + size_t next_index = (i < (configs.size() - 1)) ? filament_variant_index[i + 1] : extruder_variant_count; + static_cast(configs[i].option(key, false))->set(other_opt, filament_variant_index[i], next_index - filament_variant_index[i]); + } + else + static_cast(configs[i].option(key, false))->set_at(other_opt, 0, i); + } } } // Load the configs into this->filaments and make them active. @@ -3330,6 +3871,10 @@ std::pair PresetBundle::load_vendor_configs_ for (auto& machine_model : machine_model_subfiles) { std::string subfile = path + "/" + vendor_name + "/" + machine_model.second; + if (!boost::filesystem::exists(subfile)) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format("%1% file not exist.") % subfile; + continue; + } VendorProfile::PrinterModel model; model.id = machine_model.first; try { @@ -3382,6 +3927,8 @@ std::pair PresetBundle::load_vendor_configs_ } else if (boost::iequals(it.key(), QDT_JSON_KEY_DEFAULT_BED_TYPE)) { // get bed type model.default_bed_type = it.value(); + } else if (boost::iequals(it.key(), QDT_JSON_KEY_IMAGE_BED_TYPE)) { + model.image_bed_type = it.value(); } else if (boost::iequals(it.key(), QDT_JSON_KEY_BED_TEXTURE)) { //get bed texture @@ -3401,6 +3948,18 @@ std::pair PresetBundle::load_vendor_configs_ } else { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": invalid default_materials %1% for Vendor %1%") % default_materials_field % vendor_name; } + } else if (boost::iequals(it.key(), QDT_JSON_KEY_NOT_SUPPORT_BED_TYPE)) { + // get machine list + std::string not_support_bed_type_field = it.value(); + if (Slic3r::unescape_strings_cstyle(not_support_bed_type_field, model.not_support_bed_types)) { + Slic3r::sort_remove_duplicates(model.not_support_bed_types); + if (!model.not_support_bed_types.empty() && model.not_support_bed_types.front().empty()) + // An empty material was inserted into the list of default materials. Remove it. + model.not_support_bed_types.erase(model.not_support_bed_types.begin()); + } else { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ + << boost::format(": invalid not_support_bed_types %1% for Vendor %1%") % not_support_bed_type_field % vendor_name; + } } } } @@ -3581,7 +4140,7 @@ std::pair PresetBundle::load_vendor_configs_ loaded.description = description; loaded.setting_id = setting_id; loaded.filament_id = filament_id; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << __LINE__ << ", " << loaded.name << " load filament_id: " << filament_id; + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " " << __LINE__ << ", " << loaded.name << " load filament_id: " << filament_id; if (presets_collection->type() == Preset::TYPE_FILAMENT) { if (filament_id.empty() && "Template" != vendor_name) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": can not find filament_id for " << preset_name; @@ -3677,7 +4236,15 @@ std::pair PresetBundle::load_vendor_configs_ return std::make_pair(std::move(substitutions), presets_loaded); } -void PresetBundle::update_multi_material_filament_presets() +void PresetBundle::on_extruders_count_changed(int extruders_count) +{ + printers.get_edited_preset().set_num_extruders(extruders_count); + update_multi_material_filament_presets(); + reset_default_nozzle_volume_type(); + extruder_ams_counts.resize(extruders_count); +} + +void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filament_id) { if (printers.get_edited_preset().printer_technology() != ptFFF) return; @@ -3695,10 +4262,19 @@ void PresetBundle::update_multi_material_filament_presets() #else size_t num_filaments = this->filament_presets.size(); #endif + if (to_delete_filament_id == -1) + to_delete_filament_id = num_filaments; // Now verify if flush_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("flush_volumes_matrix")->values; - size_t old_number_of_filaments = size_t(sqrt(old_matrix.size())+EPSILON); + size_t old_nozzle_nums = this->project_config.option("flush_multiplier")->values.size(); + size_t old_number_of_filaments = size_t(sqrt(old_matrix.size() / old_nozzle_nums) + EPSILON); + size_t nozzle_nums = get_printer_extruder_count(); + if (old_nozzle_nums != nozzle_nums) { + std::vector& f_multiplier = this->project_config.option("flush_multiplier")->values; + f_multiplier.resize(nozzle_nums, 1.f); + } + if (num_filaments != old_number_of_filaments) { // First verify if purging volumes presets for each extruder matches number of extruders std::vector& filaments = this->project_config.option("flush_volumes_vector")->values; @@ -3711,16 +4287,24 @@ void PresetBundle::update_multi_material_filament_presets() filaments.pop_back(); } - std::vector new_matrix; - for (unsigned int i=0;i< num_filaments;++i) - for (unsigned int j=0;j< num_filaments;++j) { - // append the value for this pair from the old matrix (if it's there): - if (i < old_number_of_filaments && j < old_number_of_filaments) - new_matrix.push_back(old_matrix[i* old_number_of_filaments + j]); - else - new_matrix.push_back( i == j ? 0. : filaments[2 * i] + filaments[2 * j + 1]); // so it matches new extruder volumes + size_t old_matrix_size = old_number_of_filaments * old_number_of_filaments; + size_t new_matrix_size = num_filaments * num_filaments; + std::vector new_matrix(new_matrix_size * nozzle_nums, 0); + for (unsigned int i = 0; i < num_filaments; ++i) + for (unsigned int j = 0; j < num_filaments; ++j) { + if (i < old_number_of_filaments && j < old_number_of_filaments) { + unsigned int old_i = i >= to_delete_filament_id ? i + 1 : i; + unsigned int old_j = j >= to_delete_filament_id ? j + 1 : j; + for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) { + new_matrix[i * num_filaments + j + new_matrix_size * nozzle_id] = old_matrix[old_i * old_number_of_filaments + old_j + old_matrix_size * nozzle_id]; + } + } else { + for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) { + new_matrix[i * num_filaments + j + new_matrix_size * nozzle_id] = (i == j ? 0. : filaments[2 * i] + filaments[2 * j + 1]); + } + } } - this->project_config.option("flush_volumes_matrix")->values = new_matrix; + this->project_config.option("flush_volumes_matrix")->values = new_matrix; } } @@ -3768,6 +4352,8 @@ void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_pri int match_quality = PreferedProfileMatch::operator()(preset); if (match_quality < std::numeric_limits::max()) { match_quality += 1; + if (preset.is_visible) + match_quality += 1; if (m_prefered_layer_height > 0. && std::abs(preset.config.opt_float("layer_height") - m_prefered_layer_height) < 0.0005) match_quality *= 10; } @@ -3794,6 +4380,8 @@ void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_pri int match_quality = PreferedProfileMatch::operator()(preset); if (match_quality < std::numeric_limits::max()) { match_quality += 1; + if(preset.is_visible) + match_quality += 1; if (! m_prefered_filament_type.empty() && m_prefered_filament_type == preset.config.opt_string("filament_type", 0)) match_quality *= 10; } @@ -3834,7 +4422,7 @@ void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_pri const std::vector &m_prefered_names; }; - BOOST_LOG_TRIVIAL(info) << boost::format("update_compatibility for all presets enter"); + BOOST_LOG_TRIVIAL(info) << boost::format("update_compatibility for all presets enter, select_other_print_if_incompatible %1%, select_other_filament_if_incompatible %2%")%(int)select_other_print_if_incompatible %(int)select_other_filament_if_incompatible; switch (printer_preset.printer_technology()) { case ptFFF: { @@ -3851,6 +4439,14 @@ void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_pri filament_preset_was_compatible[idx] = preset != nullptr && preset->is_compatible; } // First select a first compatible profile for the preset editor. + BOOST_LOG_TRIVIAL(info) << boost::format("prefered filaments: size %1%, previous selected %2%") %prefered_filament_profiles.size() % this->filaments.get_selected_idx(); + if (this->filaments.get_selected_idx() != size_t(-1)) + { + BOOST_LOG_TRIVIAL(info) << boost::format("previous selected filament: %1%") % this->filaments.get_edited_preset().name; + } + for (size_t idx = 0; idx < prefered_filament_profiles.size(); ++idx) { + BOOST_LOG_TRIVIAL(info) << boost::format("prefered filament: %1%") % prefered_filament_profiles[idx]; + } this->filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_filament_if_incompatible, PreferedFilamentsProfileMatch(this->filaments.get_selected_idx() == size_t(-1) ? nullptr : &this->filaments.get_edited_preset(), prefered_filament_profiles)); if (select_other_filament_if_incompatible != PresetSelectCompatibleType::Never) { diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index b9b82c0..ae75f0e 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #define DEFAULT_USER_FOLDER_NAME "default" @@ -20,6 +21,50 @@ namespace Slic3r { +struct AMSMapInfo +{ + /*for new ams mapping*/ // from struct FilamentInfo + std::string ams_id{""}; + std::string slot_id{""}; +}; +struct AMSComboInfo +{ + std::vector ams_filament_colors; + std::vector> ams_multi_color_filment; + std::vector ams_filament_presets; + std::vector ams_names; + void clear() { + ams_filament_colors.clear(); + ams_multi_color_filment.clear(); + ams_filament_presets.clear(); + ams_names.clear(); + } + bool empty() { + return ams_names.empty(); + } +}; +struct MergeFilamentInfo { + std::vector> merges; + bool is_empty() { return merges.empty();} +}; + + +struct FilamentBaseInfo +{ + std::string filament_name; + std::string filament_id; + std::string filament_type; + std::string vendor; + int nozzle_temp_range_low{ 220 }; + int nozzle_temp_range_high{ 220 }; + //y58 + int box_temp_range_low{ 0 }; + int box_temp_range_high{ 35 }; + + bool is_support{ false }; + bool is_system{ true }; +}; + // Bundle of Print + Filament + Printer presets. class PresetBundle { @@ -45,7 +90,7 @@ public: // Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets. // Load selections (current print, current filaments, current printer) from config.ini // select preferred presets, if any exist - PresetsConfigSubstitutions load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule rule, + std::pair load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule rule, const PresetPreferences& preferred_selection = PresetPreferences()); // Load selections (current print, current filaments, current printer) from config.ini @@ -80,6 +125,8 @@ public: //QDS: get vendor's current version Semver get_vendor_profile_version(std::string vendor_name); + std::optional get_filament_by_filament_id(const std::string& filament_id) const; + //QDS: project embedded preset logic PresetsConfigSubstitutions load_project_embedded_presets(std::vector project_presets, ForwardCompatibilitySubstitutionRule substitution_rule); std::vector get_current_project_embedded_presets(); @@ -95,19 +142,27 @@ public: // QDS void set_num_filaments(unsigned int n, std::string new_col = ""); - unsigned int sync_ams_list(unsigned int & unknowns); + void update_num_filaments(unsigned int to_del_flament_id); + + void get_ams_cobox_infos(AMSComboInfo &combox_info); + unsigned int sync_ams_list(std::vector> &unknowns, bool use_map, std::map &maps,bool enable_append, MergeFilamentInfo& merge_info); //w42 unsigned int sync_box_list(unsigned int& unknowns); //QDS: check whether this is the only edited filament bool is_the_only_edited_filament(unsigned int filament_index); + void reset_default_nozzle_volume_type(); + + std::vector get_used_tpu_filaments(const std::vector &used_filaments); void set_calibrate_printer(std::string name); // y10 std::set get_vendors(); - std::set get_printer_names_by_printer_type_and_nozzle(const std::string &printer_type, std::string nozzle_diameter_str); + std::vector> get_extruder_filament_info() const; + + std::set get_printer_names_by_printer_type_and_nozzle(const std::string &printer_type, std::string nozzle_diameter_str, bool system_only = true); bool check_filament_temp_equation_by_printer_type_and_nozzle_for_mas_tray(const std::string &printer_type, std::string & nozzle_diameter_str, std::string & setting_id, @@ -115,6 +170,7 @@ public: std::string & nozzle_temp_min, std::string & nozzle_temp_max, std::string & preset_setting_id); + Preset * get_similar_printer_preset(std::string printer_model, std::string printer_variant); PresetCollection prints; PresetCollection sla_prints; @@ -130,6 +186,9 @@ public: // QDS: ams std::map filament_ams_list; std::vector> ams_multi_color_filment; + + std::vector> extruder_ams_counts; + // Calibrate Preset const * calibrate_printer = nullptr; std::set calibrate_filaments; @@ -155,14 +214,18 @@ public: bool has_defauls_only() const { return prints.has_defaults_only() && filaments.has_defaults_only() && printers.has_defaults_only(); } - DynamicPrintConfig full_config() const; + DynamicPrintConfig full_config(bool apply_extruder = true, std::optional>filament_maps = std::nullopt) const; // full_config() with the some "useless" config removed. - DynamicPrintConfig full_config_secure() const; + DynamicPrintConfig full_config_secure(std::optional>filament_maps = std::nullopt) const; + + //QDS: add some functions for multiple extruders + int get_printer_extruder_count() const; + bool support_different_extruders(); // Load user configuration and store it into the user profiles. // This method is called by the configuration wizard. - void load_config_from_wizard(const std::string &name, DynamicPrintConfig config, Semver file_version, bool is_custom_defined = false) - { this->load_config_file_config(name, false, std::move(config), file_version, true, is_custom_defined); } + void load_config_from_wizard(const std::string &name, DynamicPrintConfig config, Semver file_version) + { this->load_config_file_config(name, false, std::move(config), file_version, true); } // Load configuration that comes from a model file containing configuration, such as 3MF et al. // This method is called by the Plater. @@ -217,7 +280,9 @@ public: // Read out the number of extruders from an active printer preset, // update size and content of filament_presets. - void update_multi_material_filament_presets(); + void update_multi_material_filament_presets(size_t to_delete_filament_id = size_t(-1)); + + void on_extruders_count_changed(int extruder_count); // Update the is_compatible flag of all print and filament presets depending on whether they are marked // as compatible with the currently selected printer (and print in case of filament presets). @@ -269,11 +334,11 @@ private: // Load print, filament & printer presets from a config. If it is an external config, then the name is extracted from the external path. // and the external config is just referenced, not stored into user profile directory. // If it is not an external config, then the config will be stored into the user profile directory. - void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config, Semver file_version = Semver(), bool selected = false, bool is_custom_defined = false); + void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config, Semver file_version = Semver(), bool selected = false); /*ConfigSubstitutions load_config_file_config_bundle( const std::string &path, const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);*/ - DynamicPrintConfig full_fff_config() const; + DynamicPrintConfig full_fff_config(bool apply_extruder, std::optional> filament_maps=std::nullopt) const; DynamicPrintConfig full_sla_config() const; }; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 6f48295..63a9bf3 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -64,6 +64,18 @@ void Print::clear() m_objects.clear(); m_print_regions.clear(); m_model.clear_objects(); + m_statistics_by_extruder_count.clear(); +} + +bool Print::has_tpu_filament() const +{ + for (unsigned int filament_id : m_wipe_tower_data.tool_ordering.all_extruders()) { + std::string filament_name = m_config.filament_type.get_at(filament_id); + if (filament_name == "TPU") { + return true; + } + } + return false; } // Called by Print::apply(). @@ -90,10 +102,10 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "pressure_advance", "enable_overhang_bridge_fan" "overhang_fan_speed", + "pre_start_fan_time", "overhang_fan_threshold", "overhang_threshold_participating_cooling", "slow_down_for_layer_cooling", - "default_acceleration", "deretraction_speed", "close_fan_the_first_x_layers", "machine_end_gcode", @@ -188,6 +200,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "retraction_distances_when_cut", "filament_long_retractions_when_cut", "filament_retraction_distances_when_cut", + "grab_length", + "bed_temperature_formula", //w13 "additional_cooling_fan_speed_unseal", //w14 @@ -196,6 +210,10 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n ,"resonance_avoidance", "min_resonance_avoidance_speed", "max_resonance_avoidance_speed" //w17 ,"seal" + //y58 + ,"box_temperature" + //y60 + ,"is_support_3mf" }; static std::unordered_set steps_ignore; @@ -227,6 +245,20 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "resolution" || opt_key == "precise_z_height" || opt_key == "filament_shrink" + || opt_key == "enable_circle_compensation" + || opt_key == "circle_compensation_manual_offset" + || opt_key == "circle_compensation_speed" + || opt_key == "counter_coef_1" + || opt_key == "counter_coef_2" + || opt_key == "counter_coef_3" + || opt_key == "hole_coef_1" + || opt_key == "hole_coef_2" + || opt_key == "hole_coef_3" + || opt_key == "counter_limit_min" + || opt_key == "counter_limit_max" + || opt_key == "hole_limit_min" + || opt_key == "hole_limit_max" + || opt_key == "diameter_limit" // Spiral Vase forces different kind of slicing than the normal model: // In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer. // Therefore toggling the Spiral Vase on / off requires complete reslicing. @@ -238,9 +270,12 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "nozzle_temperature_initial_layer" || opt_key == "filament_minimal_purge_on_wipe_tower" || opt_key == "filament_max_volumetric_speed" + || opt_key == "filament_ramming_volumetric_speed" || opt_key == "gcode_flavor" || opt_key == "single_extruder_multi_material" || opt_key == "nozzle_temperature" + || opt_key == "filament_pre_cooling_temperature" + || opt_key == "filament_ramming_travel_time" // QDS || opt_key == "supertack_plate_temp" || opt_key == "cool_plate_temp" @@ -248,21 +283,37 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "hot_plate_temp" || opt_key == "textured_plate_temp" || opt_key == "enable_prime_tower" + || opt_key == "prime_tower_enable_framework" || opt_key == "prime_tower_width" + || opt_key == "prime_tower_max_speed" + || opt_key == "prime_tower_lift_speed" + || opt_key == "prime_tower_lift_height" || opt_key == "prime_tower_brim_width" + || opt_key == "prime_tower_skip_points" + || opt_key == "prime_tower_rib_wall" + || opt_key == "prime_tower_extra_rib_length" + || opt_key == "prime_tower_rib_width" + || opt_key == "prime_tower_fillet_wall" || opt_key == "first_layer_print_sequence" || opt_key == "other_layers_print_sequence" || opt_key == "other_layers_print_sequence_nums" + || opt_key == "extruder_ams_count" + || opt_key == "filament_map_mode" + || opt_key == "filament_map" + || opt_key == "filament_adhesiveness_category" //|| opt_key == "wipe_tower_bridging" || opt_key == "wipe_tower_no_sparse_layers" || opt_key == "flush_volumes_matrix" - || opt_key == "prime_volume" + || opt_key == "filament_prime_volume" || opt_key == "flush_into_infill" || opt_key == "flush_into_support" || opt_key == "initial_layer_infill_speed" || opt_key == "travel_speed" || opt_key == "travel_speed_z" || opt_key == "initial_layer_speed" + || opt_key == "default_acceleration" + || opt_key == "travel_acceleration" + || opt_key == "initial_layer_travel_acceleration" //w34 || opt_key == "support_multi_bed_types") { //|| opt_key == "z_offset") { @@ -270,10 +321,12 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n steps.emplace_back(psSkirtBrim); } else if (opt_key == "filament_soluble" || opt_key == "filament_is_support" + || opt_key == "impact_strength_z" || opt_key == "filament_scarf_seam_type" || opt_key == "filament_scarf_height" || opt_key == "filament_scarf_gap" || opt_key == "filament_scarf_length" + || opt_key == "filament_change_length" || opt_key == "independent_support_layer_height") { steps.emplace_back(psWipeTower); // Soluble support interface / non-soluble base interface produces non-soluble interface layers below soluble interface layers. @@ -288,7 +341,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n //|| opt_key == "resolution" //QDS: when enable arc fitting, we must re-generate perimeter || opt_key == "enable_arc_fitting" - || opt_key == "wall_sequence") { + || opt_key == "wall_sequence" + || opt_key == "z_direction_outwall_speed_continuous") { osteps.emplace_back(posPerimeters); osteps.emplace_back(posInfill); osteps.emplace_back(posSupportMaterial); @@ -536,6 +590,10 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print return -1; }; std::vector print_instance_with_bounding_box; + + + bool all_objects_are_short = print.is_all_objects_are_short(); + float obj_distance = all_objects_are_short ? scale_(0.5*MAX_OUTER_NOZZLE_RADIUS-0.1) : scale_(0.5*print.config().extruder_clearance_max_radius.value-0.1); { // sequential_print_horizontal_clearance_valid Polygons convex_hulls_other; @@ -543,11 +601,8 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print polygons->clear(); std::vector intersecting_idxs; - bool all_objects_are_short = print.is_all_objects_are_short(); // Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. - float obj_distance = all_objects_are_short ? scale_(0.5*MAX_OUTER_NOZZLE_RADIUS-0.1) : scale_(0.5*print.config().extruder_clearance_max_radius.value-0.1); - for (const PrintObject *print_object : print.objects()) { assert(! print_object->model_object()->instances.empty()); assert(! print_object->instances().empty()); @@ -561,9 +616,10 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print // FIXME: Arrangement has different parameters for offsetting (jtMiter, limit 2) // which causes that the warning will be showed after arrangement with the // appropriate object distance. Even if I set this to jtMiter the warning still shows up. + Geometry::Transformation new_trans(model_instance0->get_transformation()); + new_trans.set_offset({0.0, 0.0, model_instance0->get_offset().z()}); it_convex_hull = map_model_object_to_convex_hull.emplace_hint(it_convex_hull, model_object_id, - print_object->model_object()->convex_hull_2d(Geometry::assemble_transform( - { 0.0, 0.0, model_instance0->get_offset().z() }, model_instance0->get_rotation(), model_instance0->get_scaling_factor(), model_instance0->get_mirror()))); + print_object->model_object()->convex_hull_2d(new_trans.get_matrix())); } // Make a copy, so it may be rotated for instances. Polygon convex_hull0 = it_convex_hull->second; @@ -749,6 +805,12 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print //print_instance_with_bounding_box.pop_back(); /*bool has_interlaced_objects = false; for (int k = 0; k < print_instance_count; k++) + { + // 只需要考虑喷嘴到滑杆的偏移量,这个比整个工具头的碰撞半径要小得多 + BoundingBox& bbox = print_instance_with_bounding_box[k].bounding_box; + bbox.offset( scale_(print_config.extruder_clearance_dist_to_rod.value*0.5) - obj_distance); + } + for (int k = 0; k < print_instance_count; k++) { auto inst = print_instance_with_bounding_box[k].print_instance; auto bbox = print_instance_with_bounding_box[k].bounding_box; @@ -778,8 +840,7 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print for (int k = 0; k < print_instance_count; k++) { auto inst = print_instance_with_bounding_box[k].print_instance; - // 只需要考虑喷嘴到滑杆的偏移量,这个比整个工具头的碰撞半径要小得多 - auto bbox = print_instance_with_bounding_box[k].bounding_box.inflated(scale_(print_config.extruder_clearance_dist_to_rod.value - print_config.extruder_clearance_max_radius.value)); + auto bbox = print_instance_with_bounding_box[k].bounding_box; auto iy1 = bbox.min.y(); auto iy2 = bbox.max.y(); (const_cast(inst->model_instance))->arrange_order = k+1; @@ -910,15 +971,25 @@ static StringObjectException layered_print_cleareance_valid(const Print &print, float depth = print.wipe_tower_data(filaments_count).depth; //float brim_width = print.wipe_tower_data(filaments_count).brim_width; + if (config.prime_tower_rib_wall.value) + width = depth; + Polygons convex_hulls_temp; if (print.has_wipe_tower()) { - Polygon wipe_tower_convex_hull; - wipe_tower_convex_hull.points.emplace_back(scale_(x), scale_(y)); - wipe_tower_convex_hull.points.emplace_back(scale_(x + width), scale_(y)); - wipe_tower_convex_hull.points.emplace_back(scale_(x + width), scale_(y + depth)); - wipe_tower_convex_hull.points.emplace_back(scale_(x), scale_(y + depth)); - wipe_tower_convex_hull.rotate(a); - convex_hulls_temp.push_back(wipe_tower_convex_hull); + if (!print.is_step_done(psWipeTower)) { + Polygon wipe_tower_convex_hull; + wipe_tower_convex_hull.points.emplace_back(scale_(x), scale_(y)); + wipe_tower_convex_hull.points.emplace_back(scale_(x + width), scale_(y)); + wipe_tower_convex_hull.points.emplace_back(scale_(x + width), scale_(y + depth)); + wipe_tower_convex_hull.points.emplace_back(scale_(x), scale_(y + depth)); + wipe_tower_convex_hull.rotate(a); + convex_hulls_temp.push_back(wipe_tower_convex_hull); + } else { + //here, wipe_tower_polygon is not always convex. + Polygon wipe_tower_polygon = print.wipe_tower_data().wipe_tower_mesh_data->bottom; + wipe_tower_polygon.translate(Point(scale_(x), scale_(y))); + convex_hulls_temp.push_back(wipe_tower_polygon); + } } if (!intersection(convex_hulls_other, convex_hulls_temp).empty()) { if (warning) { @@ -935,22 +1006,29 @@ static StringObjectException layered_print_cleareance_valid(const Print &print, return {}; } -bool Print::check_multi_filaments_compatibility(const std::vector& filament_types) +FilamentCompatibilityType Print::check_multi_filaments_compatibility(const std::vector& filament_types) { bool has_high_temperature_filament = false; bool has_low_temperature_filament = false; + bool has_mid_temperature_filament = false; for (const auto& type : filament_types) { if (get_filament_temp_type(type) ==FilamentTempType::HighTemp) has_high_temperature_filament = true; else if (get_filament_temp_type(type) == FilamentTempType::LowTemp) has_low_temperature_filament = true; + else if (get_filament_temp_type(type) == FilamentTempType::HighLowCompatible) + has_mid_temperature_filament = true; } if (has_high_temperature_filament && has_low_temperature_filament) - return false; - - return true; + return FilamentCompatibilityType::HighLowMixed; + else if (has_high_temperature_filament && has_mid_temperature_filament) + return FilamentCompatibilityType::HighMidMixed; + else if (has_low_temperature_filament && has_mid_temperature_filament) + return FilamentCompatibilityType::LowMidMixed; + else + return FilamentCompatibilityType::Compatible; } bool Print::is_filaments_compatible(const std::vector& filament_types) @@ -1002,10 +1080,36 @@ StringObjectException Print::check_multi_filament_valid(const Print& print) for (const auto& extruder_idx : extruders) filament_types.push_back(print_config.filament_type.get_at(extruder_idx)); - if (!check_multi_filaments_compatibility(filament_types)) - return { L("Can not print multiple filaments which have large difference of temperature together. Otherwise, the extruder and nozzle may be blocked or damaged during printing") }; + auto compatibility = check_multi_filaments_compatibility(filament_types); + bool enable_mix_printing = !print.need_check_multi_filaments_compatibility(); - return {std::string()}; + StringObjectException ret; + + std::string hypertext = "filament_mix_print"; + + if(compatibility == FilamentCompatibilityType::HighLowMixed){ + if(enable_mix_printing){ + ret.string =L("Printing high-temp and low-temp filaments together may cause nozzle clogging or printer damage."); + ret.is_warning = true; + ret.hypetext = hypertext; + } + else{ + ret.string =L("Printing high-temp and low-temp filaments together may cause nozzle clogging or printer damage. If you still want to print, you can enable the option in Preferences."); + } + } + else if (compatibility == FilamentCompatibilityType::HighMidMixed) { + ret.is_warning = true; + ret.hypetext = hypertext; + ret.string =L("Printing high-temp and mid-temp filaments together may cause nozzle clogging or printer damage."); + + } + else if (compatibility == FilamentCompatibilityType::LowMidMixed) { + ret.is_warning = true; + ret.hypetext = hypertext; + ret.string = L("Printing mid-temp and low-temp filaments together may cause nozzle clogging or printer damage."); + } + + return ret; } // Precondition: Print::validate() requires the Print::apply() to be called its invocation. @@ -1025,6 +1129,10 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* if (!ret.string.empty()) { ret.type = STRING_EXCEPT_FILAMENTS_DIFFERENT_TEMP; + if (ret.is_warning && warning != nullptr) { + *warning = ret; + return {}; + } return ret; } } @@ -1688,14 +1796,16 @@ void Print::process(std::unordered_map* slice_time, bool BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": total object counts %1% in current print, need to slice %2%")%m_objects.size()%need_slicing_objects.size(); BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info(); + const AutoContourHolesCompensationParams &auto_contour_holes_compensation_params = AutoContourHolesCompensationParams(m_config); if (!use_cache) { - //1.9.5 + if (slice_time) { start_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); } for (PrintObject* obj : m_objects) { if (need_slicing_objects.count(obj) != 0) { + obj->set_auto_circle_compenstaion_params(auto_contour_holes_compensation_params); obj->make_perimeters(); } else { @@ -1810,21 +1920,42 @@ void Print::process(std::unordered_map* slice_time, bool } if (this->set_started(psWipeTower)) { + { + std::vector> geometric_unprintables(m_config.nozzle_diameter.size()); + for (PrintObject* obj : m_objects) { + std::vector> obj_geometric_unprintables = obj->detect_extruder_geometric_unprintables(); + for (size_t idx = 0; idx < obj_geometric_unprintables.size(); ++idx) { + if (idx < geometric_unprintables.size()) { + geometric_unprintables[idx].insert(obj_geometric_unprintables[idx].begin(), obj_geometric_unprintables[idx].end()); + } + } + } + this->set_geometric_unprintable_filaments(geometric_unprintables); + } + m_wipe_tower_data.clear(); m_tool_ordering.clear(); if (this->has_wipe_tower()) { this->_make_wipe_tower(); - } else if (this->config().print_sequence != PrintSequence::ByObject) { - // Initialize the tool ordering, so it could be used by the G-code preview slider for planning tool changes and filament switches. - m_tool_ordering = ToolOrdering(*this, -1, false); + } + else if (this->config().print_sequence != PrintSequence::ByObject) { + // Initialize the tool ordering, so it could be used by the G-code preview slider for planning tool changes and filament switches. + m_tool_ordering = ToolOrdering(*this, -1, false); + m_tool_ordering.sort_and_build_data(*this, -1, false); if (m_tool_ordering.empty() || m_tool_ordering.last_extruder() == unsigned(-1)) throw Slic3r::SlicingError("The print is empty. The model is not printable with current print settings."); + } this->set_done(psWipeTower); } + + if (this->has_wipe_tower()) { + m_fake_wipe_tower.set_pos({ m_config.wipe_tower_x.get_at(m_plate_index), m_config.wipe_tower_y.get_at(m_plate_index) }); + } + if (this->set_started(psSkirtBrim)) { this->set_status(70, L("Generating skirt & brim")); - //1.9.5 + if (slice_time) { start_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); } @@ -1852,10 +1983,45 @@ void Print::process(std::unordered_map* slice_time, bool if (this->config().print_sequence == PrintSequence::ByObject) { // Order object instances for sequential print. print_object_instances_ordering = sort_object_instances_by_model_order(*this); + std::vector first_layer_used_filaments; + std::vector> all_filaments; + for (print_object_instance_sequential_active = print_object_instances_ordering.begin(); print_object_instance_sequential_active != print_object_instances_ordering.end(); ++print_object_instance_sequential_active) { + tool_ordering = ToolOrdering(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id); + for (size_t idx = 0; idx < tool_ordering.layer_tools().size(); ++idx) { + auto& layer_filament = tool_ordering.layer_tools()[idx].extruders; + all_filaments.emplace_back(layer_filament); + if (idx == 0) + first_layer_used_filaments.insert(first_layer_used_filaments.end(), layer_filament.begin(), layer_filament.end()); + } + } + sort_remove_duplicates(first_layer_used_filaments); + auto used_filaments = collect_sorted_used_filaments(all_filaments); + this->set_slice_used_filaments(first_layer_used_filaments,used_filaments); + + auto physical_unprintables = this->get_physical_unprintable_filaments(used_filaments); + auto geometric_unprintables = this->get_geometric_unprintable_filaments(); + std::vectorfilament_maps = this->get_filament_maps(); + auto map_mode = get_filament_map_mode(); + // get recommended filament map + if (map_mode < FilamentMapMode::fmmManual) { + filament_maps = ToolOrdering::get_recommended_filament_maps(all_filaments, this, map_mode, physical_unprintables, geometric_unprintables); + std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value + 1; }); + update_filament_maps_to_config(filament_maps); + } + // check map valid both in auto and mannual mode + std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) {return value - 1; }); + if (!ToolOrdering::check_tpu_group(used_filaments, filament_maps, &m_config)) { + int master_extruder_id = m_config.master_extruder_id.value - 1; // to 0 based + std::string nozzle_name = master_extruder_id == 0 ? L("left") : L("right"); + std::string exception_str = L("TPU is incompatible with AMS and must be printed seperately in the ") + nozzle_name + L(" nozzle.\nPlease adjust the filament group accordingly."); + throw Slic3r::RuntimeError(exception_str); + } + // print_object_instances_ordering = sort_object_instances_by_max_z(print); print_object_instance_sequential_active = print_object_instances_ordering.begin(); for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++print_object_instance_sequential_active) { tool_ordering = ToolOrdering(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id); + tool_ordering.sort_and_build_data(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id); if ((initial_extruder_id = tool_ordering.first_extruder()) != static_cast(-1)) { append(printExtruders, tool_ordering.tools_for_layer(layers_to_print.front().first).extruders); } @@ -1864,6 +2030,12 @@ void Print::process(std::unordered_map* slice_time, bool else { tool_ordering = this->tool_ordering(); tool_ordering.assign_custom_gcodes(*this); + + std::vector first_layer_used_filaments; + if (!tool_ordering.layer_tools().empty()) + first_layer_used_filaments = tool_ordering.layer_tools().front().extruders; + + this->set_slice_used_filaments(first_layer_used_filaments, tool_ordering.all_extruders()); has_wipe_tower = this->has_wipe_tower() && tool_ordering.has_wipe_tower(); //QDS: have no single_extruder_multi_material_priming #if 0 @@ -1943,8 +2115,7 @@ void Print::process(std::unordered_map* slice_time, bool break; } } - // TODO adaptive layer height won't work with conflict checker because m_fake_wipe_tower's path is generated using fixed layer height - if(!m_no_check && !has_adaptive_layer_height) + if(!m_no_check /*&& !has_adaptive_layer_height*/) { using Clock = std::chrono::high_resolution_clock; auto startTime = Clock::now(); @@ -1992,8 +2163,10 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor const Vec3d origin = this->get_plate_origin(); gcode.set_gcode_offset(origin(0), origin(1)); gcode.do_export(this, path.c_str(), result, thumbnail_cb); + gcode.export_layer_filaments(result); //QDS - result->conflict_result = m_conflict_result; + if (result != nullptr) + result->conflict_result = m_conflict_result; return path.c_str(); } @@ -2204,9 +2377,9 @@ std::vector Print::first_layer_wipe_tower_corners(bool check_wipe_tower_e if (check_wipe_tower_existance && (!has_wipe_tower() || m_wipe_tower_data.tool_changes.empty())) return corners; { - double width = m_config.prime_tower_width + 2*m_wipe_tower_data.brim_width; - double depth = m_wipe_tower_data.depth + 2*m_wipe_tower_data.brim_width; - Vec2d pt0(-m_wipe_tower_data.brim_width, -m_wipe_tower_data.brim_width); + double width = m_wipe_tower_data.bbx.max.x() - m_wipe_tower_data.bbx.min.x(); + double depth = m_wipe_tower_data.bbx.max.y() -m_wipe_tower_data.bbx.min.y(); + Vec2d pt0 = m_wipe_tower_data.bbx.min + m_wipe_tower_data.rib_offset.cast(); for (Vec2d pt : { pt0, Vec2d(pt0.x()+width, pt0.y() ), @@ -2315,6 +2488,130 @@ void Print::finalize_first_layer_convex_hull() m_first_layer_convex_hull = Geometry::convex_hull(m_first_layer_convex_hull.points); } +void Print::update_filament_maps_to_config(std::vector f_maps) +{ + if (m_config.filament_map.values != f_maps) + { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": filament maps changed after pre-slicing."); + m_ori_full_print_config.option("filament_map", true)->values = f_maps; + m_config.filament_map.values = f_maps; + + m_full_print_config = m_ori_full_print_config; + m_full_print_config.update_values_to_printer_extruders_for_multiple_filaments(m_full_print_config, filament_options_with_variant, "filament_self_index", "filament_extruder_variant"); + + const std::vector &extruder_retract_keys = print_config_def.extruder_retract_keys(); + const std::string filament_prefix = "filament_"; + t_config_option_keys print_diff; + DynamicPrintConfig filament_overrides; + for (auto& opt_key: extruder_retract_keys) + { + const ConfigOption *opt_new_filament = m_full_print_config.option(filament_prefix + opt_key); + const ConfigOption *opt_new_machine = m_full_print_config.option(opt_key); + const ConfigOption *opt_old_machine = m_config.option(opt_key); + + if (opt_new_filament) + compute_filament_override_value(opt_key, opt_old_machine, opt_new_machine, opt_new_filament, m_full_print_config, print_diff, filament_overrides, f_maps); + } + + t_config_option_keys keys(filament_options_with_variant.begin(), filament_options_with_variant.end()); + m_config.apply_only(m_full_print_config, keys, true); + if (!print_diff.empty()) { + m_placeholder_parser.apply_config(filament_overrides); + m_config.apply(filament_overrides); + } + } + m_has_auto_filament_map_result = true; +} + + +std::vector Print::get_filament_maps() const +{ + return m_config.filament_map.values; +} + +FilamentMapMode Print::get_filament_map_mode() const +{ + return m_config.filament_map_mode; +} + +std::vector> Print::get_physical_unprintable_filaments(const std::vector& used_filaments) const +{ + int extruder_num = m_config.nozzle_diameter.size(); + std::vector>physical_unprintables(extruder_num); + if (extruder_num < 2) + return physical_unprintables; + + auto get_unprintable_extruder_id = [&](unsigned int filament_idx)->int { + if (m_config.unprintable_filament_types.empty()) + return -1; + for (int eid = 0; eid < m_config.unprintable_filament_types.values.size(); ++eid) { + std::vector extruder_unprintables = split_string(m_config.unprintable_filament_types.values[eid], ','); + auto iter = std::find(extruder_unprintables.begin(), extruder_unprintables.end(), m_config.filament_type.values[filament_idx]); + if (iter != extruder_unprintables.end()) + return eid; + } + return -1; + }; + + + std::set tpu_filaments; + for (auto f : used_filaments) { + if (m_config.filament_type.get_at(f) == "TPU") + tpu_filaments.insert(f); + } + if (tpu_filaments.size() > 1) { + throw Slic3r::RuntimeError(_u8L("Only supports up to one TPU filament.")); + } + + for (auto f : used_filaments) { + int extruder_id = get_unprintable_extruder_id(f); + if (extruder_id == -1) + continue; + physical_unprintables[extruder_id].insert(f); + } + + return physical_unprintables; +} + + +std::vector Print::get_extruder_printable_height() const +{ + return m_config.extruder_printable_height.values; +} + +std::vector Print::get_extruder_printable_polygons() const +{ + std::vector extruder_printable_polys; + std::vector> extruder_printable_areas = m_config.extruder_printable_area.values; + for (const auto &e_printable_area : extruder_printable_areas) { + Polygons ploys = {Polygon::new_scale(e_printable_area)}; + extruder_printable_polys.emplace_back(ploys); + } + return std::move(extruder_printable_polys); +} + +std::vector Print::get_extruder_unprintable_polygons() const +{ + std::vector printable_area = m_config.printable_area.values; + Polygon printable_poly = Polygon::new_scale(printable_area); + std::vector> extruder_printable_areas = m_config.extruder_printable_area.values; + std::vector extruder_unprintable_polys; + for (const auto &e_printable_area : extruder_printable_areas) { + Polygons ploys = diff(printable_poly, Polygon::new_scale(e_printable_area)); + extruder_unprintable_polys.emplace_back(ploys); + } + return std::move(extruder_unprintable_polys); +} + +size_t Print::get_extruder_id(unsigned int filament_id) const +{ + std::vector filament_map = get_filament_maps(); + if (filament_id < filament_map.size()) { + return filament_map[filament_id] - 1; + } + return 0; +} + // Wipe tower support. bool Print::has_wipe_tower() const { @@ -2330,19 +2627,42 @@ bool Print::has_wipe_tower() const const WipeTowerData& Print::wipe_tower_data(size_t filaments_cnt) const { // If the wipe tower wasn't created yet, make sure the depth and brim_width members are set to default. - if (! is_step_done(psWipeTower) && filaments_cnt !=0) { - // QDS - double width = m_config.prime_tower_width; - double layer_height = 0.2; // hard code layer height - double wipe_volume = m_config.prime_volume; - if (filaments_cnt == 1 && enable_timelapse_print()) { - const_cast(this)->m_wipe_tower_data.depth = wipe_volume / (layer_height * width); - } else { - const_cast(this)->m_wipe_tower_data.depth = wipe_volume * (filaments_cnt - 1) / (layer_height * width); - } - const_cast(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width; + double max_height = 0; + for (size_t obj_idx = 0; obj_idx < m_objects.size(); obj_idx++) { + double object_z = (double) m_objects[obj_idx]->size().z(); + max_height = std::max(unscale_(object_z), max_height); } + if (max_height < EPSILON) return m_wipe_tower_data; + if (! is_step_done(psWipeTower) && filaments_cnt !=0) { + std::vector filament_wipe_volume = m_config.filament_prime_volume.values; + double wipe_volume = get_max_element(filament_wipe_volume); + if (m_config.prime_tower_rib_wall.value) { + double layer_height = 0.08f; // hard code layer height + layer_height = m_objects.front()->config().layer_height.value; + int filament_depth_count = m_config.nozzle_diameter.values.size() == 2 ? filaments_cnt : filaments_cnt - 1; + if (filaments_cnt == 1 && enable_timelapse_print()) + filament_depth_count = 1; + double depth = std::sqrt(wipe_volume * filament_depth_count / layer_height); + + float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height); + depth = std::max((double) min_wipe_tower_depth, depth); + const_cast(this)->m_wipe_tower_data.depth = depth; + const_cast(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width; + } + else { + // QDS + double width = m_config.prime_tower_width; + double layer_height = 0.2; // hard code layer height + if (filaments_cnt == 1 && enable_timelapse_print()) { + const_cast(this)->m_wipe_tower_data.depth = wipe_volume / (layer_height * width); + } else { + const_cast(this)->m_wipe_tower_data.depth = wipe_volume * (filaments_cnt - 1) / (layer_height * width); + } + const_cast(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width; + } + if (m_config.prime_tower_brim_width < 0) const_cast(this)->m_wipe_tower_data.brim_width = WipeTower::get_auto_brim_by_height(max_height); + } return m_wipe_tower_data; } @@ -2355,19 +2675,14 @@ void Print::_make_wipe_tower() { m_wipe_tower_data.clear(); - // Get wiping matrix to get number of extruders and convert vector to vector: - std::vector flush_matrix(cast(m_config.flush_volumes_matrix.values)); // QDS - const unsigned int number_of_extruders = (unsigned int)(sqrt(flush_matrix.size()) + EPSILON); - // Extract purging volumes for each extruder pair: - std::vector> wipe_volumes; - for (unsigned int i = 0; i(flush_matrix.begin()+i*number_of_extruders, flush_matrix.begin()+(i+1)*number_of_extruders)); + const unsigned int number_of_extruders = (unsigned int)(m_config.filament_colour.values.size()); // Let the ToolOrdering class know there will be initial priming extrusions at the start of the print. // QDS: priming logic is removed, so don't consider it in tool ordering m_wipe_tower_data.tool_ordering = ToolOrdering(*this, (unsigned int)-1, false); + m_wipe_tower_data.tool_ordering.sort_and_build_data(*this, (unsigned int)-1, false); if (!m_wipe_tower_data.tool_ordering.has_wipe_tower()) // Don't generate any wipe tower. @@ -2412,57 +2727,80 @@ void Print::_make_wipe_tower() // Initialize the wipe tower. // QDS: in QDT machine, wipe tower is only use to prime extruder. So just use a global wipe volume. - WipeTower wipe_tower(m_config, m_plate_index, m_origin, m_config.prime_volume, m_wipe_tower_data.tool_ordering.first_extruder(), + WipeTower wipe_tower(m_config, m_plate_index, m_origin, m_wipe_tower_data.tool_ordering.first_extruder(), m_wipe_tower_data.tool_ordering.empty() ? 0.f : m_wipe_tower_data.tool_ordering.back().print_z); - - //wipe_tower.set_retract(); - //wipe_tower.set_zhop(); - + wipe_tower.set_has_tpu_filament(this->has_tpu_filament()); + wipe_tower.set_filament_map(this->get_filament_maps()); // Set the extruder & material properties at the wipe tower object. for (size_t i = 0; i < number_of_extruders; ++ i) wipe_tower.set_extruder(i, m_config); - + wipe_tower.set_need_reverse_travel(m_wipe_tower_data.tool_ordering.all_extruders()); // QDS: remove priming logic //m_wipe_tower_data.priming = Slic3r::make_unique>( // wipe_tower.prime((float)this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); + std::set used_filament_ids; + // Lets go through the wipe tower layers and determine pairs of extruder changes for each // to pass to wipe_tower (so that it can use it for planning the layout of the tower) { - // QDS: priming logic is removed, so get the initial extruder by first_extruder() - unsigned int current_extruder_id = m_wipe_tower_data.tool_ordering.first_extruder(); - for (auto &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers + // Get wiping matrix to get number of extruders and convert vector to vector: + bool is_mutli_extruder = m_config.nozzle_diameter.values.size() > 1; + size_t nozzle_nums = m_config.nozzle_diameter.values.size(); + using FlushMatrix = std::vector>; + std::vector multi_extruder_flush; + for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) { + std::vector flush_matrix(cast(get_flush_volumes_matrix(m_config.flush_volumes_matrix.values, nozzle_id, nozzle_nums))); + std::vector> wipe_volumes; + for (unsigned int i = 0; i < number_of_extruders; ++i) + wipe_volumes.push_back(std::vector(flush_matrix.begin() + i * number_of_extruders, flush_matrix.begin() + (i + 1) * number_of_extruders)); + + multi_extruder_flush.emplace_back(wipe_volumes); + } + + std::vectorfilament_maps = get_filament_maps(); + + std::vector nozzle_cur_filament_ids(nozzle_nums, -1); + unsigned int current_filament_id = m_wipe_tower_data.tool_ordering.first_extruder(); + size_t cur_nozzle_id = filament_maps[current_filament_id] - 1; + nozzle_cur_filament_ids[cur_nozzle_id] = current_filament_id; + + for (auto& layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers if (!layer_tools.has_wipe_tower) continue; bool first_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.front(); - wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id); + wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_filament_id, current_filament_id); - for (const auto extruder_id : layer_tools.extruders) { - // QDS: priming logic is removed, so no need to do toolchange for first extruder - if (/*(first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back()) || */extruder_id != current_extruder_id) { - float volume_to_purge = wipe_volumes[current_extruder_id][extruder_id]; - volume_to_purge *= m_config.flush_multiplier; + used_filament_ids.insert(layer_tools.extruders.begin(), layer_tools.extruders.end()); - // Not all of that can be used for infill purging: - //volume_to_purge -= (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); + for (const auto filament_id : layer_tools.extruders) { + if (filament_id == current_filament_id) + continue; - // try to assign some infills/objects for the wiping: - volume_to_purge = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, volume_to_purge); + int nozzle_id = filament_maps[filament_id] - 1; + unsigned int pre_filament_id = nozzle_cur_filament_ids[nozzle_id]; - // add back the minimal amount toforce on the wipe tower: - //volume_to_purge += (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); - - // request a toolchange at the wipe tower with at least volume_to_wipe purging amount - wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, - current_extruder_id, extruder_id, m_config.prime_volume, volume_to_purge); - current_extruder_id = extruder_id; + float volume_to_purge = 0; + if (pre_filament_id != (unsigned int)(-1) && pre_filament_id != filament_id) { + volume_to_purge = multi_extruder_flush[nozzle_id][pre_filament_id][filament_id]; + volume_to_purge *= m_config.flush_multiplier.get_at(nozzle_id); + volume_to_purge = pre_filament_id == -1 ? 0 : + layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_filament_id, filament_id, volume_to_purge); } + + //During the filament change, the extruder will extrude an extra length of grab_length for the corresponding detection, so the purge can reduce this length. + float grab_purge_volume = m_config.grab_length.get_at(nozzle_id) * 2.4; //(diameter/2)^2*PI=2.4 + volume_to_purge = std::max(0.f, volume_to_purge - grab_purge_volume); + + wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_filament_id, filament_id, + m_config.filament_prime_volume.values[filament_id], volume_to_purge); + current_filament_id = filament_id; + nozzle_cur_filament_ids[nozzle_id] = filament_id; } layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this); // if enable timelapse, slice all layer if (enable_timelapse_print()) { - if (layer_tools.wipe_tower_partitions == 0) - wipe_tower.set_last_layer_extruder_fill(false); + if (layer_tools.wipe_tower_partitions == 0) wipe_tower.set_last_layer_extruder_fill(false); continue; } @@ -2471,11 +2809,21 @@ void Print::_make_wipe_tower() } } + wipe_tower.set_used_filament_ids(std::vector(used_filament_ids.begin(), used_filament_ids.end())); + + std::vector categories; + for (size_t i = 0; i < m_config.filament_adhesiveness_category.values.size(); ++i) { + categories.push_back(m_config.filament_adhesiveness_category.get_at(i)); + } + wipe_tower.set_filament_categories(categories); + // Generate the wipe tower layers. m_wipe_tower_data.tool_changes.reserve(m_wipe_tower_data.tool_ordering.layer_tools().size()); - wipe_tower.generate(m_wipe_tower_data.tool_changes); + wipe_tower.generate_new(m_wipe_tower_data.tool_changes); m_wipe_tower_data.depth = wipe_tower.get_depth(); m_wipe_tower_data.brim_width = wipe_tower.get_brim_width(); + m_wipe_tower_data.bbx = wipe_tower.get_bbx(); + m_wipe_tower_data.rib_offset = wipe_tower.get_rib_offset(); // Unload the current filament over the purge tower. coordf_t layer_height = m_objects.front()->config().layer_height.value; @@ -2498,9 +2846,14 @@ void Print::_make_wipe_tower() m_wipe_tower_data.used_filament = wipe_tower.get_used_filament(); m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges(); + m_wipe_tower_data.construct_mesh(wipe_tower.width(), wipe_tower.get_depth(), wipe_tower.get_height(), wipe_tower.get_brim_width(), config().prime_tower_rib_wall.value, + wipe_tower.get_rib_width(), wipe_tower.get_rib_length(), config().prime_tower_fillet_wall.value); const Vec3d origin = this->get_plate_origin(); - m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position(), wipe_tower.width(), wipe_tower.get_height(), wipe_tower.get_layer_height(), m_wipe_tower_data.depth, + m_fake_wipe_tower.rib_offset = wipe_tower.get_rib_offset(); + m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position() + m_fake_wipe_tower.rib_offset, wipe_tower.width(), wipe_tower.get_height(), wipe_tower.get_layer_height(), + m_wipe_tower_data.depth, m_wipe_tower_data.brim_width, {scale_(origin.x()), scale_(origin.y())}); + m_fake_wipe_tower.outer_wall = wipe_tower.get_outer_wall(); } // Generate a recommended G-code output file name based on the format template, default extension, and template parameters @@ -3160,7 +3513,7 @@ static void convert_layer_region_from_json(const json& j, LayerRegion& layer_reg if (!ret) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(":error parsing thin_fills found at layer %1%, print_z %2%") %layer_region.layer()->id() %layer_region.layer()->print_z; char error_buf[1024]; - ::sprintf(error_buf, "Error while parsing thin_fills at layer %d, print_z %f", layer_region.layer()->id(), layer_region.layer()->print_z); + ::sprintf(error_buf, "Error while parsing thin_fills at layer %zu, print_z %f", layer_region.layer()->id(), layer_region.layer()->print_z); throw Slic3r::FileIOError(error_buf); } } @@ -3215,7 +3568,7 @@ static void convert_layer_region_from_json(const json& j, LayerRegion& layer_reg if (!ret) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": error parsing perimeters found at layer %1%, print_z %2%") %layer_region.layer()->id() %layer_region.layer()->print_z; char error_buf[1024]; - ::sprintf(error_buf, "Error while parsing perimeters at layer %d, print_z %f", layer_region.layer()->id(), layer_region.layer()->print_z); + ::sprintf(error_buf, "Error while parsing perimeters at layer %zu, print_z %f", layer_region.layer()->id(), layer_region.layer()->print_z); throw Slic3r::FileIOError(error_buf); } } @@ -3230,7 +3583,7 @@ static void convert_layer_region_from_json(const json& j, LayerRegion& layer_reg if (!ret) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": error parsing fills found at layer %1%, print_z %2%") %layer_region.layer()->id() %layer_region.layer()->print_z; char error_buf[1024]; - ::sprintf(error_buf, "Error while parsing fills at layer %d, print_z %f", layer_region.layer()->id(), layer_region.layer()->print_z); + ::sprintf(error_buf, "Error while parsing fills at layer %zu, print_z %f", layer_region.layer()->id(), layer_region.layer()->print_z); throw Slic3r::FileIOError(error_buf); } } @@ -3310,7 +3663,7 @@ void extract_support_layer(const json& support_layer_json, SupportLayer& support if (!ret) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": error parsing fills found at support_layer %1%, print_z %2%")%support_layer.id() %support_layer.print_z; char error_buf[1024]; - ::sprintf(error_buf, "Error while parsing fills at support_layer %d, print_z %f", support_layer.id(), support_layer.print_z); + ::sprintf(error_buf, "Error while parsing fills at support_layer %zu, print_z %f", support_layer.id(), support_layer.print_z); throw Slic3r::FileIOError(error_buf); } } @@ -3847,4 +4200,59 @@ Point PrintInstance::shift_without_plate_offset() const return shift - Point(scaled(plate_offset.x()), scaled(plate_offset.y())); } +ExtrusionLayers FakeWipeTower::getTrueExtrusionLayersFromWipeTower() const +{ + ExtrusionLayers wtels; + wtels.type = ExtrusionLayersType::WIPE_TOWER; + std::vector layer_heights; + layer_heights.reserve(outer_wall.size()); + auto pre = outer_wall.begin(); + for (auto it = outer_wall.begin(); it != outer_wall.end(); ++it) { + if (it == outer_wall.begin()) + layer_heights.push_back(it->first); + else { + layer_heights.push_back(it->first - pre->first); + ++pre; + } + } + Point trans = {scale_(pos.x()), scale_(pos.y())}; + for (auto it = outer_wall.begin(); it != outer_wall.end(); ++it) { + int index = std::distance(outer_wall.begin(), it); + ExtrusionLayer el; + ExtrusionPaths paths; + paths.reserve(it->second.size()); + for (auto &polyline : it->second) { + ExtrusionPath path(ExtrusionRole::erWipeTower, 0.0, 0.0, layer_heights[index]); + path.polyline = polyline; + for (auto &p : path.polyline.points) p += trans; + paths.push_back(path); + } + el.paths = std::move(paths); + el.bottom_z = it->first - layer_heights[index]; + el.layer = nullptr; + wtels.push_back(el); + } + return wtels; +} +void WipeTowerData::construct_mesh(float width, float depth, float height, float brim_width, bool is_rib_wipe_tower, float rib_width, float rib_length,bool fillet_wall) +{ + wipe_tower_mesh_data = WipeTowerMeshData{}; + float first_layer_height=0.08; //brim height + if (!is_rib_wipe_tower) { + wipe_tower_mesh_data->real_wipe_tower_mesh = make_cube(width, depth, height); + wipe_tower_mesh_data->real_brim_mesh = make_cube(width + 2 * brim_width, depth + 2 * brim_width, first_layer_height); + wipe_tower_mesh_data->real_brim_mesh.translate({-brim_width, -brim_width, 0}); + wipe_tower_mesh_data->bottom = {scaled(Vec2f{-brim_width, -brim_width}), scaled(Vec2f{width + brim_width, 0}), scaled(Vec2f{width + brim_width, depth + brim_width}), + scaled(Vec2f{0, depth})}; + } else { + wipe_tower_mesh_data->real_wipe_tower_mesh = WipeTower::its_make_rib_tower(width, depth, height, rib_length, rib_width, fillet_wall); + wipe_tower_mesh_data->bottom = WipeTower::rib_section(width, depth, rib_length, rib_width, fillet_wall); + wipe_tower_mesh_data->bottom = offset(wipe_tower_mesh_data->bottom, scaled(brim_width)).front(); + wipe_tower_mesh_data->real_brim_mesh = WipeTower::its_make_rib_brim(wipe_tower_mesh_data->bottom, first_layer_height); + wipe_tower_mesh_data->real_wipe_tower_mesh.translate(Vec3f(rib_offset[0], rib_offset[1],0)); + wipe_tower_mesh_data->real_brim_mesh.translate(Vec3f(rib_offset[0], rib_offset[1], 0)); + wipe_tower_mesh_data->bottom.translate(scaled(Vec2f(rib_offset[0], rib_offset[1]))); + } +} + } // namespace Slic3r diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index eb472c4..50429e4 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -36,6 +36,7 @@ class SupportLayer; // QDS class TreeSupportData; class TreeSupport; +struct ExtrusionLayers; #define MARGIN_HEIGHT 1.5 #define MAX_OUTER_NOZZLE_RADIUS 4 @@ -298,6 +299,44 @@ private: size_t m_ref_cnt{ 0 }; }; +struct AutoContourHolesCompensationParams +{ + AutoContourHolesCompensationParams(const PrintConfig &config) + { + counter_speed_coef = config.counter_coef_1.values; + counter_diameter_coef = config.counter_coef_2.values; + counter_compensate_coef = config.counter_coef_3.values; + hole_speed_coef = config.hole_coef_1.values; + hole_diameter_coef = config.hole_coef_2.values; + hole_compensate_coef = config.hole_coef_3.values; + counter_limit_min_value = config.counter_limit_min.values; + counter_limit_max_value = config.counter_limit_max.values; + hole_limit_min_value = config.hole_limit_min.values; + hole_limit_max_value = config.hole_limit_max.values; + circle_compensation_speed = config.circle_compensation_speed.values; + diameter_limit = config.diameter_limit.values; + } + + AutoContourHolesCompensationParams(){} + + // QDS: params for auto contour and holes compensation + std::vector counter_speed_coef; + std::vector counter_diameter_coef; + std::vector counter_compensate_coef; + + std::vector hole_speed_coef; + std::vector hole_diameter_coef; + std::vector hole_compensate_coef; + + std::vector counter_limit_min_value; + std::vector counter_limit_max_value; + std::vector hole_limit_min_value; + std::vector hole_limit_max_value; + + std::vector circle_compensation_speed; + std::vector diameter_limit; +}; + class PrintObject : public PrintObjectBaseWithState { private: // Prevents erroneous use by other classes. @@ -348,7 +387,9 @@ public: std::vector& firstLayerObjGroupsMod() { return firstLayerObjSliceByGroups; } bool has_brim() const { - return ((this->config().brim_type != btNoBrim && this->config().brim_width.value > 0.) || this->config().brim_type == btAutoBrim) + return ((this->config().brim_type != btNoBrim && this->config().brim_width.value > 0.) || + this->config().brim_type == btAutoBrim || + this->config().brim_type == btBrimEars) && ! this->has_raft(); } @@ -404,7 +445,7 @@ public: // The slicing parameters are dependent on various configuration values // (layer height, first layer height, raft settings, print nozzle diameter etc). const SlicingParameters& slicing_parameters() const { return m_slicing_params; } - static SlicingParameters slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z); + static SlicingParameters slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z, std::vector variant_index = std::vector()); size_t num_printing_regions() const throw() { return m_shared_regions->all_regions.size(); } const PrintRegion& printing_region(size_t idx) const throw() { return *m_shared_regions->all_regions[idx].get(); } @@ -435,7 +476,7 @@ public: //QDS BoundingBox get_first_layer_bbox(float& area, float& layer_height, std::string& name); void get_certain_layers(float start, float end, std::vector &out, std::vector &boundingbox_objects); - std::vector get_instances_shift_without_plate_offset(); + std::vector get_instances_shift_without_plate_offset() const; PrintObject* get_shared_object() const { return m_shared_object; } void set_shared_object(PrintObject *object); void clear_shared_object(); @@ -454,6 +495,9 @@ public: size_t get_klipper_object_id() const { return m_klipper_object_id; } void set_klipper_object_id(size_t id) { m_klipper_object_id = id; } + void set_auto_circle_compenstaion_params(const AutoContourHolesCompensationParams ¶ms){auto_contour_holes_compensation_params = params;}; + AutoContourHolesCompensationParams get_auto_circle_compenstaion_params() { return auto_contour_holes_compensation_params; }; + private: // to be called from Print only. friend class Print; @@ -475,7 +519,7 @@ private: // If ! m_slicing_params.valid, recalculate. void update_slicing_parameters(); - static PrintObjectConfig object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders); + static PrintObjectConfig object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders, std::vector& variant_index); private: void make_perimeters(); @@ -485,6 +529,17 @@ private: void generate_support_material(); void simplify_extrusion_path(); + /** + * @brief Determines the unprintable filaments for each extruder based on its printable area. + * + * The returned array will always have the same size as the number of extruders. + * If extruder num is 1, just return an empty vector. + * If an extruder has no unprintable filaments, an empty set will also be returned + * + * @return A vector of sets representing unprintable filaments for each extruder + */ + std::vector> detect_extruder_geometric_unprintables() const; + void slice_volumes(); //QDS ExPolygons _shrink_contour_holes(double contour_delta, double hole_delta, const ExPolygons& polys) const; @@ -496,9 +551,11 @@ private: void detect_surfaces_type(); void process_external_surfaces(); void discover_vertical_shells(); + void discover_shell_for_perimeters(); void bridge_over_infill(); void clip_fill_surfaces(); void discover_horizontal_shells(); + void merge_infill_types(); void combine_infill(); void _generate_support_material(); std::pair prepare_adaptive_infill_data( @@ -507,11 +564,12 @@ private: // QDS SupportNecessaryType is_support_necessary(); - + void merge_layer_node(const size_t layer_id, int &max_merged_id, std::map>> &node_record); // XYZ in scaled coordinates Vec3crd m_size; double m_max_z; PrintObjectConfig m_config; + AutoContourHolesCompensationParams auto_contour_holes_compensation_params; // Translation in Z + Rotation + Scaling / Mirroring. Transform3d m_trafo = Transform3d::Identity(); // Slic3r::Point objects in scaled G-code coordinates @@ -568,6 +626,8 @@ struct FakeWipeTower float depth; float brim_width; Vec2d plate_origin; + Vec2f rib_offset{0.f,0.f}; + std::map outer_wall; //wipe tower's true outer wall and brim void set_fake_extrusion_data(Vec2f p, float w, float h, float lh, float d, float bd, Vec2d o) { @@ -580,7 +640,7 @@ struct FakeWipeTower plate_origin = o; } - void set_pos(Vec2f p) { pos = p; } + void set_pos(Vec2f p) { pos = p+rib_offset; } std::vector getFakeExtrusionPathsFromWipeTower() const { @@ -606,10 +666,18 @@ struct FakeWipeTower } return paths; } + + ExtrusionLayers getTrueExtrusionLayersFromWipeTower() const; }; struct WipeTowerData { + struct WipeTowerMeshData + { + Polygon bottom; + TriangleMesh real_wipe_tower_mesh; + TriangleMesh real_brim_mesh; + }; // Following section will be consumed by the GCodeGenerator. // Tool ordering of a non-sequential print has to be known to calculate the wipe tower. // Cache it here, so it does not need to be recalculated during the G-code generation. @@ -624,7 +692,9 @@ struct WipeTowerData // Depth of the wipe tower to pass to GLCanvas3D for exact bounding box: float depth; float brim_width; - + BoundingBoxf bbx;//including brim + Vec2f rib_offset; + std::optional wipe_tower_mesh_data;//added rib_offset void clear() { priming.reset(nullptr); tool_changes.clear(); @@ -633,7 +703,9 @@ struct WipeTowerData number_of_toolchanges = -1; depth = 0.f; brim_width = 0.f; + wipe_tower_mesh_data = std::nullopt; } + void construct_mesh(float width, float depth, float height, float brim_width, bool is_rib_wipe_tower, float rib_width, float rib_length, bool fillet_wall); private: // Only allow the WipeTowerData to be instantiated internally by Print, @@ -695,12 +767,33 @@ class ConstPrintRegionPtrsAdaptor : public ConstVectorOfPtrsAdaptor }; */ +struct StatisticsByExtruderCount +{ + // flush weight comes first,then comes filament change time + FilamentChangeStats stats_by_single_extruder; + FilamentChangeStats stats_by_multi_extruder_best; + FilamentChangeStats stats_by_multi_extruder_curr; + void clear() { + stats_by_single_extruder.clear(); + stats_by_multi_extruder_best.clear(); + stats_by_multi_extruder_curr.clear(); + } +}; + enum FilamentTempType { HighTemp=0, LowTemp, HighLowCompatible, Undefine }; + +enum FilamentCompatibilityType { + Compatible, + HighLowMixed, + HighMidMixed, + LowMidMixed +}; + // The complete print tray with possibly multiple objects. class Print : public PrintBaseWithState { @@ -725,7 +818,7 @@ public: // List of existing PrintObject IDs, to remove notifications for non-existent IDs. std::vector print_object_ids() const override; - ApplyStatus apply(const Model &model, DynamicPrintConfig config) override; + ApplyStatus apply(const Model &model, DynamicPrintConfig config, bool extruder_applied = false) override; //1.9.5 void process(std::unordered_map* slice_time = nullptr, bool use_cache = false) override; @@ -801,11 +894,50 @@ public: const PrintStatistics& print_statistics() const { return m_print_statistics; } PrintStatistics& print_statistics() { return m_print_statistics; } + const StatisticsByExtruderCount statistics_by_extruder() const { return m_statistics_by_extruder_count; } + StatisticsByExtruderCount& statistics_by_extruder() { return m_statistics_by_extruder_count; } + // Wipe tower support. bool has_wipe_tower() const; const WipeTowerData& wipe_tower_data(size_t filaments_cnt = 0) const; const ToolOrdering& tool_ordering() const { return m_tool_ordering; } + void update_filament_maps_to_config(std::vector f_maps); + // 1 based group ids + std::vector get_filament_maps() const; + FilamentMapMode get_filament_map_mode() const; + // get the group label of filament + size_t get_extruder_id(unsigned int filament_id) const; + + const std::vector>& get_extruder_filament_info() const { return m_extruder_filament_info; } + void set_extruder_filament_info(const std::vector>& filament_info) { m_extruder_filament_info = filament_info; } + + void set_geometric_unprintable_filaments(const std::vector> &unprintables_filament_ids) { m_geometric_unprintable_filaments = unprintables_filament_ids; } + std::vector> get_geometric_unprintable_filaments() const { return m_geometric_unprintable_filaments;} + + void set_slice_used_filaments(const std::vector &first_layer_used_filaments, const std::vector &used_filaments){ + m_slice_used_filaments_first_layer = first_layer_used_filaments; + m_slice_used_filaments = used_filaments; + } + std::vector get_slice_used_filaments(bool first_layer) const { return first_layer ? m_slice_used_filaments_first_layer : m_slice_used_filaments;} + + /** + * @brief Determines the unprintable filaments for each extruder based on its physical attributes + * + * Currently, the criteria for determining unprintable filament include the following: + * 1. TPU filaments can only be placed in the master extruder and must be grouped alone. + * 2. We only support at most 1 tpu filament. + * 3. An extruder can only accommodate filament with a hardness requirement lower than that of its nozzle. + * + * @param used_filaments Totally used filaments when slicing + * @return A vector of sets representing unprintable filaments for each extruder.Return an empty vecto if extruder num is 1 + */ + std::vector> get_physical_unprintable_filaments(const std::vector& used_filaments) const; + + std::vector get_extruder_printable_height() const; + std::vector get_extruder_printable_polygons() const; + std::vector get_extruder_unprintable_polygons() const; + bool enable_timelapse_print() const; std::string output_filename(const std::string &filename_base = std::string()) const override; @@ -847,11 +979,19 @@ public: void set_calib_params(const Calib_Params ¶ms); const Calib_Params& calib_params() const { return m_calib_params; } Vec2d translate_to_print_space(const Vec2d& point) const; + float get_wipe_tower_depth() const { return m_wipe_tower_data.depth; } + BoundingBoxf get_wipe_tower_bbx() const { return m_wipe_tower_data.bbx; } + Vec2f get_rib_offset() const { return m_wipe_tower_data.rib_offset; } + const FakeWipeTower& get_fake_wipe_tower() const { return m_fake_wipe_tower; } + + void set_check_multi_filaments_compatibility(bool check) { m_need_check_multi_filaments_compatibility = check; } + bool need_check_multi_filaments_compatibility() const { return m_need_check_multi_filaments_compatibility; } + // scaled point Vec2d translate_to_print_space(const Point& point) const; static FilamentTempType get_filament_temp_type(const std::string& filament_type); static int get_hrc_by_nozzle_type(const NozzleType& type); - static bool check_multi_filaments_compatibility(const std::vector& filament_types); + static FilamentCompatibilityType check_multi_filaments_compatibility(const std::vector& filament_types); // similar to check_multi_filaments_compatibility, but the input is int, and may be negative (means unset) static bool is_filaments_compatible(const std::vector& types); // get the compatible filament type of a multi-material object @@ -874,6 +1014,7 @@ private: //QDS static StringObjectException check_multi_filament_valid(const Print &print); + bool has_tpu_filament() const; bool invalidate_state_by_config_options(const ConfigOptionResolver &new_config, const std::vector &opt_keys); void _make_skirt(); @@ -903,6 +1044,8 @@ private: Polygon m_first_layer_convex_hull; Points m_skirt_convex_hull; + std::vector> m_extruder_filament_info; + // Following section will be consumed by the GCodeGenerator. ToolOrdering m_tool_ordering; WipeTowerData m_wipe_tower_data {m_tool_ordering}; @@ -910,6 +1053,10 @@ private: // Estimated print time, filament consumed. PrintStatistics m_print_statistics; bool m_support_used {false}; + StatisticsByExtruderCount m_statistics_by_extruder_count; + + std::vector m_slice_used_filaments; + std::vector m_slice_used_filaments_first_layer; //QDS: plate's origin Vec3d m_origin; @@ -918,10 +1065,15 @@ private: //QDS ConflictResultOpt m_conflict_result; FakeWipeTower m_fake_wipe_tower; + bool m_has_auto_filament_map_result{false}; + + std::vector> m_geometric_unprintable_filaments; // OrcaSlicer: calibration Calib_Params m_calib_params; + bool m_need_check_multi_filaments_compatibility{true}; + // To allow GCode to set the Print's GCodeExport step status. friend class GCode; // Allow PrintObject to access m_mutex and m_cancel_callback. diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index d7d14be..6c8a199 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -217,7 +217,8 @@ static t_config_option_keys print_config_diffs( const PrintConfig ¤t_config, const DynamicPrintConfig &new_full_config, DynamicPrintConfig &filament_overrides, - int plate_index) + int plate_index, + std::vector& filament_maps) { const std::vector &extruder_retract_keys = print_config_def.extruder_retract_keys(); const std::string filament_prefix = "filament_"; @@ -231,26 +232,9 @@ static t_config_option_keys print_config_diffs( //FIXME This may happen when executing some test cases. continue; const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr; - if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) { - // An extruder retract override is available at some of the filament presets. - bool overriden = opt_new->overriden_by(opt_new_filament); - if (overriden || *opt_old != *opt_new) { - auto opt_copy = opt_new->clone(); - if (!((opt_key == "long_retractions_when_cut" || opt_key == "retraction_distances_when_cut") - && new_full_config.option("enable_long_retraction_when_cut")->value != LongRectrationLevel::EnableFilament)) // ugly code, remove it later if firmware supports - opt_copy->apply_override(opt_new_filament); - bool changed = *opt_old != *opt_copy; - if (changed) - print_diff.emplace_back(opt_key); - if (changed || overriden) { - if ((opt_key == "long_retractions_when_cut" || opt_key == "retraction_distances_when_cut") - && new_full_config.option("enable_long_retraction_when_cut")->value != LongRectrationLevel::EnableFilament) - continue; - // filament_overrides will be applied to the placeholder parser, which layers these parameters over full_print_config. - filament_overrides.set_key_value(opt_key, opt_copy); - } else - delete opt_copy; - } + + if (opt_new_filament != nullptr) { + compute_filament_override_value(opt_key, opt_old, opt_new, opt_new_filament, new_full_config, print_diff, filament_overrides, filament_maps); } else if (*opt_new != *opt_old) { //QDS: add plate_index logic for wipe_tower_x/wipe_tower_y if (!opt_key.compare("wipe_tower_x") || !opt_key.compare("wipe_tower_y")) { @@ -689,7 +673,7 @@ PrintObjectRegions::BoundingBox find_modifier_volume_extents(const PrintObjectRe return out; } -PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_or_parent_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders); +PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_or_parent_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders, std::vector& variant_index); void print_region_ref_inc(PrintRegion &r) { ++ r.m_ref_cnt; } void print_region_ref_reset(PrintRegion &r) { r.m_ref_cnt = 0; } @@ -704,7 +688,8 @@ bool verify_update_print_object_regions( size_t num_extruders, const std::vector &painting_extruders, PrintObjectRegions &print_object_regions, - const std::function &callback_invalidate) + const std::function &callback_invalidate, + std::vector& variant_index) { // Sort by ModelVolume ID. model_volumes_sort_by_id(model_volumes); @@ -749,7 +734,7 @@ bool verify_update_print_object_regions( } else if (PrintObjectRegions::BoundingBox parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id); parent_bbox.intersects(*bbox)) // Such parent region does not exist. If it is needed, then we need to reslice. // Only create new region for a modifier, which actually modifies config of it's parent. - if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, **it_model_volume, num_extruders); + if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, **it_model_volume, num_extruders, variant_index); config != parent_region.region->config()) // This modifier newly overrides a region, which it did not before. We need to reslice. return false; @@ -757,8 +742,8 @@ bool verify_update_print_object_regions( } } PrintRegionConfig cfg = region.parent == -1 ? - region_config_from_model_volume(default_region_config, layer_range.config, **it_model_volume, num_extruders) : - region_config_from_model_volume(layer_range.volume_regions[region.parent].region->config(), nullptr, **it_model_volume, num_extruders); + region_config_from_model_volume(default_region_config, layer_range.config, **it_model_volume, num_extruders, variant_index) : + region_config_from_model_volume(layer_range.volume_regions[region.parent].region->config(), nullptr, **it_model_volume, num_extruders, variant_index); if (cfg != region.region->config()) { // Region configuration changed. if (print_region_ref_cnt(*region.region) == 0) { @@ -903,7 +888,8 @@ static PrintObjectRegions* generate_print_object_regions( const Transform3d &trafo, size_t num_extruders, const float xy_contour_compensation, - const std::vector & painting_extruders) + const std::vector & painting_extruders, + std::vector & variant_index) { // Reuse the old object or generate a new one. auto out = print_object_regions_old ? std::unique_ptr(print_object_regions_old) : std::make_unique(); @@ -960,7 +946,7 @@ static PrintObjectRegions* generate_print_object_regions( // Add a model volume, assign an existing region or generate a new one. layer_range.volume_regions.push_back({ &volume, -1, - get_create_region(region_config_from_model_volume(default_region_config, layer_range.config, volume, num_extruders)), + get_create_region(region_config_from_model_volume(default_region_config, layer_range.config, volume, num_extruders, variant_index)), bbox }); } else if (volume.is_negative_volume()) { @@ -977,7 +963,7 @@ static PrintObjectRegions* generate_print_object_regions( if (parent_volume.is_model_part() || parent_volume.is_modifier()) if (PrintObjectRegions::BoundingBox parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id); parent_bbox.intersects(*bbox)) { // Only create new region for a modifier, which actually modifies config of it's parent. - if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, volume, num_extruders); + if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, volume, num_extruders, variant_index); config != parent_region.region->config()) { added = true; layer_range.volume_regions.push_back({ &volume, parent_region_id, get_create_region(std::move(config)), bbox }); @@ -1016,7 +1002,7 @@ static PrintObjectRegions* generate_print_object_regions( return out.release(); } -Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_config) +Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_config, bool extruder_applied) { #ifdef _DEBUG check_model_ids_validity(model); @@ -1029,11 +1015,12 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ new_full_config.option("filament_settings_id", true); new_full_config.option("printer_settings_id", true); // QDS - int used_filaments = this->extruders(true).size(); + std::vector used_filaments = this->extruders(true); + std::unordered_set used_filament_set(used_filaments.begin(), used_filaments.end()); //new_full_config.normalize_fdm(used_filaments); new_full_config.normalize_fdm_1(); - t_config_option_keys changed_keys = new_full_config.normalize_fdm_2(objects().size(), used_filaments); + t_config_option_keys changed_keys = new_full_config.normalize_fdm_2(objects().size(), used_filaments.size()); if (changed_keys.size() > 0) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", got changed_keys, size=%1%")%changed_keys.size(); for (int i = 0; i < changed_keys.size(); i++) @@ -1105,15 +1092,91 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ } } + { //check scarf seam setting + const auto &o = model.objects; + const bool has_scarf_joint_seam = std::any_of(o.begin(), o.end(), [&new_full_config](ModelObject *obj) { + return obj->get_config_value(new_full_config, "apply_scarf_seam_on_circles")->value; + }); + + if (has_scarf_joint_seam) { + new_full_config.set("has_scarf_joint_seam", true); + } + + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", has_scarf_joint_seam:" << has_scarf_joint_seam; + } + + //apply extruder related values + std::vector print_variant_index; + if (!extruder_applied) { + new_full_config.update_values_to_printer_extruders(new_full_config, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant"); + new_full_config.update_values_to_printer_extruders(new_full_config, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2); + //update print config related with variants + print_variant_index = new_full_config.update_values_to_printer_extruders(new_full_config, print_options_with_variant, "print_extruder_id", "print_extruder_variant"); + + m_ori_full_print_config = new_full_config; + new_full_config.update_values_to_printer_extruders_for_multiple_filaments(new_full_config, filament_options_with_variant, "filament_self_index", "filament_extruder_variant"); + } + else { + int extruder_count; + bool different_extruder = new_full_config.support_different_extruders(extruder_count); + print_variant_index.resize(extruder_count); + for (int e_index = 0; e_index < extruder_count; e_index++) + { + print_variant_index[e_index] = e_index; + } + } + std::vector filament_maps = new_full_config.option("filament_map")->values; + // Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles. DynamicPrintConfig filament_overrides; //QDS: add plate index - t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides, this->m_plate_index); + t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides, this->m_plate_index, filament_maps); t_config_option_keys full_config_diff = full_print_config_diffs(m_full_print_config, new_full_config, this->m_plate_index); // Collect changes to object and region configs. t_config_option_keys object_diff = m_default_object_config.diff(new_full_config); t_config_option_keys region_diff = m_default_region_config.diff(new_full_config); + //QDS: process the filament_map related logic + std::unordered_set print_diff_set(print_diff.begin(), print_diff.end()); + if (print_diff_set.find("filament_map_mode") == print_diff_set.end()) + { + FilamentMapMode map_mode = new_full_config.option>("filament_map_mode", true)->value; + if (map_mode < fmmManual) { + if (print_diff_set.find("filament_map") != print_diff_set.end()) { + print_diff_set.erase("filament_map"); + //full_config_diff.erase("filament_map"); + ConfigOptionInts* old_opt = m_full_print_config.option("filament_map", true); + ConfigOptionInts* new_opt = new_full_config.option("filament_map", true); + old_opt->set(new_opt); + m_config.filament_map = *new_opt; + } + } + else { + print_diff_set.erase("extruder_ams_count"); + std::vector old_filament_map = m_config.filament_map.values; + std::vector new_filament_map = new_full_config.option("filament_map", true)->values; + + if (old_filament_map.size() == new_filament_map.size()) + { + bool same_map = true; + for (size_t index = 0; index < old_filament_map.size(); index++) + { + if ((old_filament_map[index] == new_filament_map[index]) + || (used_filament_set.find(index) == used_filament_set.end())) + continue; + else { + same_map = false; + break; + } + } + if (same_map) + print_diff_set.erase("filament_map"); + } + } + if (print_diff_set.size() != print_diff.size()) + print_diff.assign(print_diff_set.begin(), print_diff_set.end()); + } + // Do not use the ApplyStatus as we will use the max function when updating apply_status. unsigned int apply_status = APPLY_STATUS_UNCHANGED; auto update_apply_status = [&apply_status](bool invalidated) @@ -1351,7 +1414,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ if (object_config_changed) model_object.config.assign_config(model_object_new.config); if (! object_diff.empty() || object_config_changed || num_extruders_changed ) { - PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders ); + PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders, print_variant_index); for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(model_object)) { t_config_option_keys diff = print_object_status.print_object->config().diff(new_config); if (! diff.empty()) { @@ -1413,10 +1476,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // Generate a list of trafos and XY offsets for instances of a ModelObject // Producing the config for PrintObject on demand, caching it at print_object_last. const PrintObject *print_object_last = nullptr; - auto print_object_apply_config = [this, &print_object_last, model_object, num_extruders ](PrintObject *print_object) { + auto print_object_apply_config = [this, &print_object_last, model_object, num_extruders, &print_variant_index](PrintObject *print_object) { print_object->config_apply(print_object_last ? print_object_last->config() : - PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_extruders )); + PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_extruders, print_variant_index)); print_object_last = print_object; }; if (old.empty()) { @@ -1449,8 +1512,13 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ } else { // The PrintObject already exists and the copies differ. PrintBase::ApplyStatus status = (*it_old)->print_object->set_instances(std::move(new_instances.instances)); - if (status != PrintBase::APPLY_STATUS_UNCHANGED) - update_apply_status(status == PrintBase::APPLY_STATUS_INVALIDATED); + if (status != PrintBase::APPLY_STATUS_UNCHANGED) { + size_t extruder_num = new_full_config.option("nozzle_diameter")->size(); + if (extruder_num > 1) { + update_apply_status(this->invalidate_steps({ psWipeTower,psSkirtBrim, psGCodeExport })); + } + update_apply_status(status == PrintBase::APPLY_STATUS_INVALIDATED); + } print_objects_new.emplace_back((*it_old)->print_object); const_cast(*it_old)->status = PrintObjectStatus::Reused; } @@ -1573,7 +1641,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ for (auto it = it_print_object; it != it_print_object_end; ++it) if ((*it)->m_shared_regions != nullptr) update_apply_status((*it)->invalidate_state_by_config_options(old_config, new_config, diff_keys)); - })) { + }, + print_variant_index)) { // Regions are valid, just keep them. } else { // Regions were reshuffled. @@ -1594,7 +1663,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ model_object_status.print_instances.front().trafo, num_extruders , print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_contour_compensation.value), - painting_extruders); + painting_extruders, + print_variant_index); } for (auto it = it_print_object; it != it_print_object_end; ++it) if ((*it)->m_shared_regions) { diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index 150a823..0ee8803 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -33,7 +33,9 @@ struct StringObjectException ObjectBase const *object = nullptr; std::string opt_key; StringExceptionType type; // warning type for tips + bool is_warning = false; std::vector params; // warning params for tips + std::string hypetext; }; class CanceledException : public std::exception @@ -406,7 +408,7 @@ public: // Some data was changed, which in turn invalidated already calculated steps. APPLY_STATUS_INVALIDATED, }; - virtual ApplyStatus apply(const Model &model, DynamicPrintConfig config) = 0; + virtual ApplyStatus apply(const Model &model, DynamicPrintConfig config, bool extruder_applied = false) = 0; const Model& model() const { return m_model; } struct TaskParams { @@ -552,6 +554,7 @@ protected: Model m_model; DynamicPrintConfig m_full_print_config; + DynamicPrintConfig m_ori_full_print_config; //original full print config without extruder applied PlaceholderParser m_placeholder_parser; //QDS: add plate id into print base diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 8b9ac57..349dcff 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -7,8 +7,9 @@ #include "GCode/Thumbnails.hpp" #include -#include #include +#include +#include #include #include #include @@ -57,6 +58,14 @@ namespace Slic3r { #define L(s) (s) #define _(s) Slic3r::I18N::translate(s) +size_t get_extruder_index(const GCodeConfig& config, unsigned int filament_id) +{ + if (filament_id < config.filament_map.size()) { + return config.filament_map.get_at(filament_id)-1; + } + return 0; +} + static t_config_enum_names enum_names_from_keys_map(const t_config_enum_values &enum_keys_map) { t_config_enum_names names; @@ -117,6 +126,12 @@ static t_config_enum_values s_keys_map_GCodeFlavor { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(GCodeFlavor) +static t_config_enum_values s_keys_map_BedTempFormula { + { "by_first_filament",int(BedTempFormula::btfFirstFilament) }, + { "by_highest_temp", int(BedTempFormula::btfHighestTemp)} +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BedTempFormula) + static t_config_enum_values s_keys_map_FuzzySkinType { { "none", int(FuzzySkinType::None) }, { "external", int(FuzzySkinType::External) }, @@ -145,7 +160,9 @@ static t_config_enum_values s_keys_map_InfillPattern { { "octagramspiral", ipOctagramSpiral }, { "supportcubic", ipSupportCubic }, { "lightning", ipLightning }, - { "crosshatch", ipCrossHatch} + { "crosshatch", ipCrossHatch}, + { "zigzag", ipZigZag }, + { "crosszag", ipCrossZag } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(InfillPattern) @@ -184,6 +201,13 @@ static t_config_enum_values s_keys_map_WallSequence { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(WallSequence) +static t_config_enum_values s_keys_map_EnsureVerticalThicknessLevel { + { "disabled", int(EnsureVerticalThicknessLevel::evtDisabled) }, + { "partial", int(EnsureVerticalThicknessLevel::evtPartial) }, + { "enabled", int(EnsureVerticalThicknessLevel::evtEnabled)} +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(EnsureVerticalThicknessLevel) + //QDS static t_config_enum_values s_keys_map_PrintSequence { { "by layer", int(PrintSequence::ByLayer) }, @@ -374,11 +398,94 @@ static const t_config_enum_values s_keys_map_ZHopType = { CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ZHopType) static const t_config_enum_values s_keys_map_ExtruderType = { - { "DirectDrive", etDirectDrive }, + { "Direct Drive", etDirectDrive }, { "Bowden", etBowden } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ExtruderType) +static const t_config_enum_values s_keys_map_NozzleVolumeType = { + { "Standard", nvtStandard }, + { "High Flow", nvtHighFlow } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NozzleVolumeType) + +static const t_config_enum_values s_keys_map_FilamentMapMode = { + { "Auto For Flush", fmmAutoForFlush }, + { "Auto For Match", fmmAutoForMatch }, + { "Manual", fmmManual } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FilamentMapMode) + + +//QDS +std::string get_extruder_variant_string(ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type) +{ + std::string variant_string; + + if (extruder_type > etMaxExtruderType) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", unsupported ExtruderType=%1%")%extruder_type; + //extruder_type = etDirectDrive; + return variant_string; + } + if (nozzle_volume_type > nvtMaxNozzleVolumeType) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", unsupported NozzleVolumeType=%1%")%nozzle_volume_type; + //extruder_type = etDirectDrive; + return variant_string; + } + variant_string = s_keys_names_ExtruderType[extruder_type]; + variant_string+= " "; + variant_string+= s_keys_names_NozzleVolumeType[nozzle_volume_type]; + return variant_string; +} + +std::string get_nozzle_volume_type_string(NozzleVolumeType nozzle_volume_type) +{ + if (nozzle_volume_type > nvtMaxNozzleVolumeType) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", unsupported NozzleVolumeType=%1%") % nozzle_volume_type; + return ""; + } + return s_keys_names_NozzleVolumeType[nozzle_volume_type]; +} + +std::vector> get_extruder_ams_count(const std::vector& strs) +{ + std::vector> extruder_ams_counts; + for (const std::string& str : strs) { + std::map ams_count_info; + if (str.empty()) { + extruder_ams_counts.emplace_back(ams_count_info); + continue; + } + std::vector ams_infos; + boost::algorithm::split(ams_infos, str, boost::algorithm::is_any_of("|")); + for (const std::string& ams_info : ams_infos) { + std::vector numbers; + boost::algorithm::split(numbers, ams_info, boost::algorithm::is_any_of("#")); + assert(numbers.size() == 2); + ams_count_info.insert(std::make_pair(stoi(numbers[0]), stoi(numbers[1]))); + } + extruder_ams_counts.emplace_back(ams_count_info); + } + return extruder_ams_counts; +} + +std::vector save_extruder_ams_count_to_string(const std::vector> &extruder_ams_count) +{ + std::vector extruder_ams_count_str; + for (size_t i = 0; i < extruder_ams_count.size(); ++i) { + std::ostringstream oss; + const auto &item = extruder_ams_count[i]; + for (auto it = item.begin(); it != item.end(); ++it) { + oss << it->first << "#" << it->second; + if (std::next(it) != item.end()) { + oss << "|"; + } + } + extruder_ams_count_str.push_back(oss.str()); + } + return extruder_ams_count_str; +} + //w12 static const t_config_enum_values s_keys_map_GCodeThumbnailsFormat = { { "PNG", int(GCodeThumbnailsFormat::PNG) }, @@ -424,6 +531,12 @@ void PrintConfigDef::init_common_params() def->gui_type = ConfigOptionDef::GUIType::one_string; def->set_default_value(new ConfigOptionPoints{ Vec2d(0, 0), Vec2d(200, 0), Vec2d(200, 200), Vec2d(0, 200) }); + def = this->add("extruder_printable_area", coPointsGroups); + def->label = L("Extruder printable area"); + def->mode = comAdvanced; + def->gui_type = ConfigOptionDef::GUIType::one_string; + def->set_default_value(new ConfigOptionPointsGroups{}); + //QDS: add "bed_exclude_area" def = this->add("bed_exclude_area", coPoints); def->label = L("Bed exclude area"); @@ -471,6 +584,21 @@ void PrintConfigDef::init_common_params() def->mode = comSimple; def->set_default_value(new ConfigOptionFloat(100.0)); + def = this->add("extruder_printable_height", coFloats); + def->label = L("Extruder printable height"); + def->tooltip = L("Maximum printable height of this extruder which is limited by mechanism of printer"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 1000; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatsNullable{0}); + + def = this->add("unprintable_filament_types", coStrings); + def->label = L("Unprintable filament type"); + def->tooltip = L("Unprintable filament type"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionStrings{""}); + // Options used by physical printers def = this->add("preset_names", coStrings); @@ -586,6 +714,13 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("z_direction_outwall_speed_continuous", coBool); + def->label = L("Smoothing wall speed along Z(experimental)"); + def->category = L("Quality"); + def->tooltip = L("Smoothing outwall speed in z direction to get better surface quality. Print time will increases. It is not work on spiral vase mode."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("max_travel_detour_distance", coFloatOrPercent); def->label = L("Avoid crossing wall - Max detour length"); def->category = L("Quality"); @@ -801,6 +936,16 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionInts { 100 }); + def = this->add("pre_start_fan_time", coFloats); + def->label = L("Pre start fan time"); + def->tooltip = L("Force fan start early(0-5 second) when encountering overhangs. " + "This is because the fan needs time to physically increase its speed."); + def->sidetext = L("s"); + def->min = 0.; + def->max = 5.; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats{0.0}); + def = this->add("overhang_fan_threshold", coEnums); def->label = L("Cooling overhang threshold"); def->tooltip = L("Force cooling fan to be specific speed when overhang degree of printed part exceeds this value. " @@ -898,12 +1043,12 @@ void PrintConfigDef::init_fff_params() def = this->add("top_area_threshold", coPercent); def->label = L("Top area threshold"); - def->tooltip = L("This factor affects the acreage of top area. The small the number the big the top area."); + def->tooltip = L("The min width of top areas in percentage of perimeter line width."); def->sidetext = "%"; def->min = 0; def->max = 500; def->mode = comDevelop; - def->set_default_value(new ConfigOptionPercent(100)); + def->set_default_value(new ConfigOptionPercent(200)); def = this->add("only_one_wall_first_layer", coBool); def->label = L("Only one wall on first layer"); @@ -911,73 +1056,81 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Use only one wall on the first layer of model"); def->set_default_value(new ConfigOptionBool(false)); - def = this->add("enable_overhang_speed", coBool); + def = this->add("enable_overhang_speed", coBools); def->label = L("Slow down for overhang"); def->category = L("Speed"); def->tooltip = L("Enable this option to slow printing down for different overhang degree"); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool{ true }); + def->nullable = true; + def->set_default_value(new ConfigOptionBoolsNullable{ true }); - def = this->add("overhang_1_4_speed", coFloat); - def->label = "(10%, 25%)"; + def = this->add("overhang_1_4_speed", coFloats); + def->label = "10%"; def->category = L("Speed"); - def->full_label = "(10%, 25%)"; + def->full_label = "10%"; //def->tooltip = L("Speed for line of wall which has degree of overhang between 10% and 25% line width. " // "0 means using original wall speed"); def->sidetext = L("mm/s"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0}); - def = this->add("overhang_2_4_speed", coFloat); - def->label = "[25%, 50%)"; + def = this->add("overhang_2_4_speed", coFloats); + def->label = "25%"; def->category = L("Speed"); - def->full_label = "[25%, 50%)"; + def->full_label = "25%"; //def->tooltip = L("Speed for line of wall which has degree of overhang between 25% and 50% line width. " // "0 means using original wall speed"); def->sidetext = L("mm/s"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0}); - def = this->add("overhang_3_4_speed", coFloat); - def->label = "[50%, 75%)"; + def = this->add("overhang_3_4_speed", coFloats); + def->label = "50%"; def->category = L("Speed"); - def->full_label = "[50%, 75%)"; + def->full_label = "50%"; //def->tooltip = L("Speed for line of wall which has degree of overhang between 50% and 75% line width. 0 means using original wall speed"); def->sidetext = L("mm/s"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0}); - def = this->add("overhang_4_4_speed", coFloat); - def->label = "[75%, 100%)"; + def = this->add("overhang_4_4_speed", coFloats); + def->label = "75%"; def->category = L("Speed"); - def->full_label = "[75%, 100%)"; + def->full_label = "75%"; //def->tooltip = L("Speed for line of wall which has degree of overhang between 75% and 100% line width. 0 means using original wall speed"); def->sidetext = L("mm/s"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0}); //1.9.5 - def = this->add("overhang_totally_speed", coFloat); - def->label = L("over 100% wall (not bridge)"); - def->category = L("Speed"); - def->tooltip = L("Speed for line of wall which has degree of overhang over 100% line width, but the wall is not a bridge wall."); + def = this->add("overhang_totally_speed", coFloats); + def->label = L("100%"); + def->category = L("Speed"); + def->full_label = "100%"; + def->tooltip = L("Speed of 100% overhang wall which has 0 overlap with the lower layer."); def->sidetext = L("mm/s"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat{24}); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{ 10 }); - def = this->add("bridge_speed", coFloat); + def = this->add("bridge_speed", coFloats); def->label = L("Bridge"); def->category = L("Speed"); def->tooltip = L("Speed of bridge and completely overhang wall"); def->sidetext = L("mm/s"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(25)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{25}); def = this->add("brim_width", coFloat); def->label = L("Brim width"); @@ -986,7 +1139,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->min = 0; def->max = 100; - def->mode = comSimple; + def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.)); def = this->add("brim_type", coEnum); @@ -1101,13 +1254,32 @@ void PrintConfigDef::init_fff_params() "This can improve the cooling quality for needle and small details"); def->set_default_value(new ConfigOptionBools { true }); - def = this->add("default_acceleration", coFloat); + def = this->add("default_acceleration", coFloats); def->label = L("Normal printing"); def->tooltip = L("The default acceleration of both normal printing and travel except initial layer"); def->sidetext = "mm/s²"; def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(500.0)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{500.0}); + + def = this->add("travel_acceleration", coFloats); + def->label = L("Travel"); + def->tooltip = L("The acceleration of travel except initial layer"); + def->sidetext = "mm/s²"; + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{500.0}); + + def = this->add("initial_layer_travel_acceleration", coFloats); + def->label = L("Initial layer travel"); + def->tooltip = L("The acceleration of travel of initial layer"); + def->sidetext = "mm/s²"; + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{500.0}); def = this->add("default_filament_profile", coStrings); def->label = L("Default filament profile"); @@ -1207,13 +1379,38 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionStrings { " " }); - def = this->add("ensure_vertical_shell_thickness", coBool); + def = this->add("ensure_vertical_shell_thickness", coEnum); def->label = L("Ensure vertical shell thickness"); def->category = L("Strength"); def->tooltip = L("Add solid infill near sloping surfaces to guarantee the vertical shell thickness " "(top+bottom solid layers)"); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(true)); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("disabled"); + def->enum_values.push_back("partial"); + def->enum_values.push_back("enabled"); + def->enum_labels.push_back(L("Disabled")); + def->enum_labels.push_back(L("Partial")); + def->enum_labels.push_back(L("Enabled")); + def->set_default_value(new ConfigOptionEnum(EnsureVerticalThicknessLevel::evtEnabled)); + + def = this->add("vertical_shell_speed",coFloatsOrPercents); + def->label = L("Vertical shell speed"); + def->tooltip = L("Speed for vertical shells with overhang regions. If expressed as percentage (for example: 80%) it will be calculated on" + "the internal solid infill speed above"); + def->category = L("Speed"); + def->sidetext = L("mm/s or %"); + def->ratio_over = "internal_solid_infill_speed"; + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsOrPercentsNullable{FloatOrPercent(80, true)}); + + def = this->add("detect_floating_vertical_shell", coBool); + def->label = L("Detect floating vertical shells"); + def->tooltip = L("Detect overhang paths in vertical shells and slow them by bridge speed."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool{true}); //1.9.5 def = this->add("smooth_speed_discontinuity_area", coBool); @@ -1294,7 +1491,7 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0)); - def = this->add("outer_wall_speed", coFloat); + def = this->add("outer_wall_speed", coFloats); def->label = L("Outer wall"); def->category = L("Speed"); def->tooltip = L("Speed of outer wall which is outermost and visible. " @@ -1302,10 +1499,11 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(60)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{60}); - def = this->add("small_perimeter_speed", coFloatOrPercent); + def = this->add("small_perimeter_speed", coFloatsOrPercents); def->label = L("Small perimeters"); def->category = L("Speed"); def->tooltip = L("This setting will affect the speed of perimeters having radius <= small perimeter threshold" @@ -1315,16 +1513,19 @@ void PrintConfigDef::init_fff_params() def->ratio_over = "outer_wall_speed"; def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloatOrPercent(50, true)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsOrPercentsNullable{FloatOrPercent(50, true)}); - def = this->add("small_perimeter_threshold", coFloat); + + def = this->add("small_perimeter_threshold", coFloats); def->label = L("Small perimter threshold"); def->category = L("Speed"); def->tooltip = L("This sets the threshold for small perimeter length. Default threshold is 0mm"); def->sidetext = L("mm"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0}); def = this->add("wall_sequence", coEnum); def->label = L("Order of walls"); @@ -1405,6 +1606,13 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(68)); + def = this->add("grab_length",coFloats); + def->label = L("Grab length"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionFloats({0})); + def = this->add("extruder_colour", coStrings); def->label = L("Extruder Color"); def->tooltip = L("Only used as a visual help on UI"); @@ -1433,7 +1641,8 @@ void PrintConfigDef::init_fff_params() "Maybe you can tune this value to get nice flat surface when there has slight overflow or underflow"); def->max = 2; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats { 1. }); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable { 1. }); def = this->add("print_flow_ratio", coFloat); def->label = L("Object flow ratio"); @@ -1548,6 +1757,33 @@ void PrintConfigDef::init_fff_params() def->mode = comDevelop; def->set_default_value(new ConfigOptionInts{0}); + def = this->add("filament_map", coInts); + def->label = L("Filament map to extruder"); + def->tooltip = L("Filament map to extruder"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInts{1}); + + def = this->add("physical_extruder_map",coInts); + def->label = "Map the logical extruder to physical extruder"; + def->tooltip = "Map the logical extruder to physical extruder"; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInts{0}); + + def = this->add("filament_map_mode", coEnum); + def->label = L("filament mapping mode"); + def->tooltip = ("filament mapping mode used as plate param"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("Auto For Flush"); + def->enum_values.push_back("Auto For Match"); + def->enum_values.push_back("Manual"); + def->enum_values.push_back("Default"); + def->enum_labels.push_back(L("Auto For Flush")); + def->enum_labels.push_back(L("Auto For Match")); + def->enum_labels.push_back(L("Manual")); + def->enum_labels.push_back(L("Default")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(fmmAutoForFlush)); + def = this->add("filament_max_volumetric_speed", coFloats); def->label = L("Max volumetric speed"); def->tooltip = L("This setting stands for how much volume of filament can be melted and extruded per second. " @@ -1555,9 +1791,20 @@ void PrintConfigDef::init_fff_params() "Can't be zero"); def->sidetext = L("mm³/s"); def->min = 0; - def->max = 70; + def->max = 200; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats { 2. }); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable { 2. }); + + def = this->add("filament_ramming_volumetric_speed", coFloats); + def->label = L("Ramming volumetric speed"); + def->tooltip = L("The maximum volumetric speed for ramming, where -1 means using the maximum volumetric speed."); + def->sidetext = L("mm³/s"); + def->min = -1; + def->max = 200; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{-1}); def = this->add("filament_minimal_purge_on_wipe_tower", coFloats); def->label = L("Minimal purge on wipe tower"); @@ -1586,6 +1833,36 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.0)); + def = this->add("machine_switch_extruder_time", coFloat); + def->label = L("Extruder switch time"); + def->tooltip = L("Time to switch extruder. For statistics only"); + def->min = 0; + def->sidetext = L("s"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(5)); + + def = this->add("hotend_cooling_rate", coFloats); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{2}); + + def = this->add("hotend_heating_rate", coFloats); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{2}); + + def = this->add("enable_pre_heating", coBool); + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("bed_temperature_formula", coEnum); + def->label = L("Bed temperature type"); + def->tooltip = L("This option determines how the bed temperature is set during slicing: based on the temperature of the first filament or the highest temperature of the printed filaments."); + def->mode = comDevelop; + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("by_first_filament"); + def->enum_values.push_back("by_highest_temp"); + def->enum_labels.push_back(L("By First filament")); + def->enum_labels.push_back(L("By Highest Temp")); + def->set_default_value(new ConfigOptionEnum(BedTempFormula::btfFirstFilament)); + def = this->add("filament_diameter", coFloats); def->label = L("Diameter"); def->tooltip = L("Filament diameter is used to calculate extrusion in gcode, so it's important and should be accurate"); @@ -1606,6 +1883,13 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionPercents{ 100 }); + def = this->add("filament_adhesiveness_category", coInts); + def->label = L("Adhesiveness Category"); + def->tooltip = L("Filament category"); + def->min = 0; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInts{0}); + def = this->add("filament_density", coFloats); def->label = L("Density"); def->tooltip = L("Filament density. For statistics only"); @@ -1643,7 +1927,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("PPA-CF"); def->enum_values.push_back("PPA-GF"); def->enum_values.push_back("ABS-GF"); - def->enum_values.push_back("ASA-Aero"); + def->enum_values.push_back("ASA-AERO"); def->enum_values.push_back("PE"); def->enum_values.push_back("PP"); def->enum_values.push_back("EVA"); @@ -1701,12 +1985,28 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats{10}); + def = this->add("filament_change_length", coFloats); + def->label = L("Filament ramming length"); + def->tooltip = L("When changing the extruder, it is recommended to extrude a certain length of filament from the original extruder. This helps minimize nozzle oozing."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats{10}); + def = this->add("filament_is_support", coBools); def->label = L("Support material"); def->tooltip = L("Support material is commonly used to print support and support interface"); def->mode = comDevelop; def->set_default_value(new ConfigOptionBools{false}); + def = this->add("filament_prime_volume", coFloats); + def->label = L("Filament prime volume"); + def->tooltip = L("The volume of material to prime extruder on tower."); + def->sidetext = L("mm³"); + def->min = 1.0; + def->mode = comSimple; + def->set_default_value(new ConfigOptionFloats{45.}); + // QDS def = this->add("temperature_vitrification", coInts); def->label = L("Softening temperature"); @@ -1714,6 +2014,28 @@ void PrintConfigDef::init_fff_params() def->mode = comSimple; def->set_default_value(new ConfigOptionInts{ 100 }); + def = this->add("filament_ramming_travel_time", coFloats); + def->label = L("Travel time after ramming"); + def->tooltip = L("To prevent oozing, the nozzle will perform a reverse travel movement for a certain period after " + "the ramming is complete. The setting define the travel time."); + def->mode = comAdvanced; + def->sidetext = "s"; + def->min = 0; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0}); + + def = this->add("filament_pre_cooling_temperature", coInts); + def->label = L("Precooling target temperature"); + def->tooltip = L("To prevent oozing, the nozzle temperature will be cooled during ramming. Therefore, the ramming time must be greater than the cooldown time. 0 means disabled."); + //def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->mode = comAdvanced; + def->sidetext = "°C"; + def->min = 0; + //def->enum_values.push_back("-1"); + //def->enum_labels.push_back(L("None")); + def->nullable = true; + def->set_default_value(new ConfigOptionIntsNullable{0}); + def = this->add("filament_cost", coFloats); def->label = L("Price"); def->tooltip = L("Filament price. For statistics only"); @@ -1780,6 +2102,8 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("supportcubic"); def->enum_values.push_back("lightning"); def->enum_values.push_back("crosshatch"); + def->enum_values.push_back("zigzag"); + def->enum_values.push_back("crosszag"); def->enum_labels.push_back(L("Concentric")); def->enum_labels.push_back(L("Rectilinear")); def->enum_labels.push_back(L("Grid")); @@ -1798,48 +2122,55 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Support Cubic")); def->enum_labels.push_back(L("Lightning")); def->enum_labels.push_back(L("Cross Hatch")); + def->enum_labels.push_back(L("Zig Zag")); + def->enum_labels.push_back(L("Cross Zag")); def->set_default_value(new ConfigOptionEnum(ipCubic)); - def = this->add("top_surface_acceleration", coFloat); + def = this->add("top_surface_acceleration", coFloats); def->label = L("Top surface"); def->tooltip = L("Acceleration of top surface infill. Using a lower value may improve top surface quality"); def->sidetext = "mm/s²"; def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(500)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{500}); - def = this->add("outer_wall_acceleration", coFloat); + def = this->add("outer_wall_acceleration", coFloats); def->label = L("Outer wall"); def->tooltip = L("Acceleration of outer wall. Using a lower value can improve quality"); def->sidetext = "mm/s²"; def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(500)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{500}); - def = this->add("inner_wall_acceleration", coFloat); + def = this->add("inner_wall_acceleration", coFloats); def->label = L("Inner wall"); def->tooltip = L("Acceleration of inner walls. 0 means using normal printing acceleration"); def->sidetext = "mm/s²"; def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0}); - def = this->add("sparse_infill_acceleration", coFloatOrPercent); + def = this->add("sparse_infill_acceleration", coFloatsOrPercents); def->label = L("Sparse infill"); def->tooltip = L("Acceleration of sparse infill. If the value is expressed as a percentage (e.g. 100%), it will be calculated based on the default acceleration."); def->sidetext = L("mm/s² or %"); def->min = 0; def->mode = comAdvanced; def->ratio_over = "default_acceleration"; - def->set_default_value(new ConfigOptionFloatOrPercent(100, true)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsOrPercentsNullable{FloatOrPercent(100, true)}); - def = this->add("initial_layer_acceleration", coFloat); + def = this->add("initial_layer_acceleration", coFloats); def->label = L("Initial layer"); def->tooltip = L("Acceleration of initial layer. Using a lower value can improve build plate adhensive"); def->sidetext = "mm/s²"; def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(300)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{300}); //w20 def = this->add("accel_to_decel_enable", coBool); @@ -1939,21 +2270,23 @@ void PrintConfigDef::init_fff_params() // "Note that this option only takes effect if no prime tower is generated in current plate."); //def->set_default_value(new ConfigOptionBool(0)); - def = this->add("initial_layer_speed", coFloat); + def = this->add("initial_layer_speed", coFloats); def->label = L("Initial layer"); def->tooltip = L("Speed of initial layer except the solid infill part"); def->sidetext = L("mm/s"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(30)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{30}); - def = this->add("initial_layer_infill_speed", coFloat); + def = this->add("initial_layer_infill_speed", coFloats); def->label = L("Initial layer infill"); def->tooltip = L("Speed of solid infill part of initial layer"); def->sidetext = L("mm/s"); def->min = 1; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(60.0)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{60.0}); def = this->add("nozzle_temperature_initial_layer", coInts); def->label = L("Initial layer"); @@ -1962,7 +2295,8 @@ void PrintConfigDef::init_fff_params() def->sidetext = "°C"; def->min = 0; def->max = max_temp; - def->set_default_value(new ConfigOptionInts { 200 }); + def->nullable = true; + def->set_default_value(new ConfigOptionIntsNullable { 200 }); def = this->add("full_fan_speed_layer", coInts); def->label = L("Full fan speed at layer"); @@ -2018,41 +2352,46 @@ void PrintConfigDef::init_fff_params() def->mode = comDevelop; def->set_default_value(new ConfigOptionFloat(0)); - def = this->add("gap_infill_speed", coFloat); + def = this->add("gap_infill_speed", coFloats); def->label = L("Gap infill"); def->category = L("Speed"); def->tooltip = L("Speed of gap infill. Gap usually has irregular line width and should be printed more slowly"); def->sidetext = L("mm/s"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(30)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{30}); //w16 - def = this->add("resonance_avoidance", coBool); + def = this->add("resonance_avoidance", coBools); def->label = L("Resonance avoidance"); def->category = L("Speed"); def->tooltip = L("By reducing the speed of the outer wall to avoid the resonance zone of the printer, ringing on the surface of the model are avoided.\n" "Please turn this option off when testing ringing."); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(true)); + def->set_default_value(new ConfigOptionBools{ true }); - def = this->add("min_resonance_avoidance_speed", coFloat); + //y58 + def = this->add("min_resonance_avoidance_speed", coFloats); def->label = L("Min"); def->category = L("Speed"); def->tooltip = L("Minimum speed of resonance avoidance."); def->sidetext = L("mm/s"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(70)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{70}); - def = this->add("max_resonance_avoidance_speed", coFloat); + //y58 + def = this->add("max_resonance_avoidance_speed", coFloats); def->label = L("Max"); def->category = L("Speed"); def->tooltip = L("Maximum speed of resonance avoidance."); def->sidetext = L("mm/s"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(120)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{120}); // QDS def = this->add("precise_z_height", coBool); @@ -2085,13 +2424,19 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionBool(false)); // QDS - //w13 + //w13 y60 def = this->add("thumbnail_size", coString); def->label = L("G-code thumbnails"); def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 / .sl1s files, in the following format: \"XxY, XxY, ...\""); def->mode = comAdvanced; + def->readonly = true; def->gui_type = ConfigOptionDef::GUIType::one_string; - def->set_default_value(new ConfigOptionString("48x48/PNG,300x300/PNG")); + def->set_default_value(new ConfigOptionString("50x50")); + + //y60 + def = this->add("is_support_3mf", coBool); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); //QDS // def = this->add("spaghetti_detector", coBool); @@ -2100,7 +2445,7 @@ void PrintConfigDef::init_fff_params() // def->mode = comSimple; // def->set_default_value(new ConfigOptionBool(false)); - def = this->add("nozzle_type", coEnum); + def = this->add("nozzle_type", coEnums); def->label = L("Nozzle type"); def->tooltip = L("The metallic material of nozzle. This determines the abrasive resistance of nozzle, and " "what kind of filament can be printed"); @@ -2114,7 +2459,8 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Stainless steel")); def->enum_labels.push_back(L("Brass")); def->mode = comDevelop; - def->set_default_value(new ConfigOptionEnum(ntUndefine)); + def->nullable = true; + def->set_default_value(new ConfigOptionEnumsGenericNullable({ ntUndefine })); def = this->add("printer_structure", coEnum); def->label = L("Printer structure"); @@ -2152,6 +2498,14 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionBool(false)); def->readonly=false; + //y58 + def =this->add("support_box_temp_control",coBool); + def->label=L("Support control box temperature"); + def->tooltip=L("This option is enabled if machine support controlling box temperature"); + def->mode=comDevelop; + def->set_default_value(new ConfigOptionBool(false)); + def->readonly=false; + def =this->add("support_air_filtration",coBool); def->label=L("Air filtration enhancement"); def->tooltip=L("Enable this if printer support air filtration enhancement."); @@ -2215,6 +2569,34 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("infill_shift_step", coFloat); + def->label = L("Infill shift step"); + def->category = L("Strength"); + def->tooltip = L("This parameter adds a slight displacement to each layer of infill to create a cross texture."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 10; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.4)); + + def = this->add("infill_rotate_step", coFloat); + def->label = L("Infill rotate step"); + def->category = L("Strength"); + def->tooltip = L("This parameter adds a slight rotation to each layer of infill to create a cross texture."); + def->sidetext = L("°"); + def->min = 0; + def->max = 360; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("symmetric_infill_y_axis", coBool); + def->label = L("Symmetric infill y axis"); + def->category = L("Strength"); + def->tooltip = L("If the model has two parts that are symmetric about the y-axis," + " and you want these parts to have symmetric textures, please click this option on one of the parts."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + auto def_infill_anchor_min = def = this->add("sparse_infill_anchor", coFloatOrPercent); def->label = L("Length of sparse infill anchor"); def->category = L("Strength"); @@ -2297,14 +2679,15 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionPercent(15)); - def = this->add("sparse_infill_speed", coFloat); + def = this->add("sparse_infill_speed", coFloats); def->label = L("Sparse infill"); def->category = L("Speed"); def->tooltip = L("Speed of internal sparse infill"); def->sidetext = L("mm/s"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(100)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{100}); def = this->add("inherits", coString); //def->label = L("Inherits profile"); @@ -2353,6 +2736,56 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("interlocking_beam", coBool); + def->label = L("Use beam interlocking"); + def->tooltip = L("Generate interlocking beam structure at the locations where different filaments touch. This improves the adhesion between filaments, especially models printed in different materials."); + def->category = L("Advanced"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("interlocking_beam_width", coFloat); + def->label = L("Interlocking beam width"); + def->tooltip = L("The width of the interlocking structure beams."); + def->sidetext = L("mm"); + def->min = 0.01; + def->category = L("Advanced"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.8)); + + def = this->add("interlocking_orientation", coFloat); + def->label = L("Interlocking direction"); + def->tooltip = L("Orientation of interlock beams."); + def->sidetext = L("°"); + def->min = 0; + def->max = 360; + def->category = L("Advanced"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(22.5)); + + def = this->add("interlocking_beam_layer_count", coInt); + def->label = L("Interlocking beam layers"); + def->tooltip = L("The height of the beams of the interlocking structure, measured in number of layers. Less layers is stronger, but more prone to defects."); + def->min = 1; + def->category = L("Advanced"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(2)); + + def = this->add("interlocking_depth", coInt); + def->label = L("Interlocking depth"); + def->tooltip = L("The distance from the boundary between filaments to generate interlocking structure, measured in cells. Too few cells will result in poor adhesion."); + def->min = 1; + def->category = L("Advanced"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(2)); + + def = this->add("interlocking_boundary_avoidance", coInt); + def->label = L("Interlocking boundary avoidance"); + def->tooltip = L("The distance from the outside of a model where interlocking structures will not be generated, measured in cells."); + def->min = 0; + def->category = L("Advanced"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(2)); + def = this->add("ironing_type", coEnum); def->label = L("Ironing Type"); def->category = L("Quality"); @@ -2510,7 +2943,8 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s"); def->min = 0; def->mode = comSimple; - def->set_default_value(new ConfigOptionFloats(axis.max_feedrate)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable(axis.max_feedrate)); // Add the machine acceleration limits for XYZE axes (M201) def = this->add("machine_max_acceleration_" + axis.name, coFloats); def->full_label = (boost::format("Maximum acceleration %1%") % axis_upper).str(); @@ -2528,7 +2962,8 @@ void PrintConfigDef::init_fff_params() def->sidetext = "mm/s²"; def->min = 0; def->mode = comSimple; - def->set_default_value(new ConfigOptionFloats(axis.max_acceleration)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable(axis.max_acceleration)); // Add the machine jerk limits for XYZE axes (M205) def = this->add("machine_max_jerk_" + axis.name, coFloats); def->full_label = (boost::format("Maximum jerk %1%") % axis_upper).str(); @@ -2546,7 +2981,8 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s"); def->min = 0; def->mode = comSimple; - def->set_default_value(new ConfigOptionFloats(axis.max_jerk)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable(axis.max_jerk)); } } @@ -2558,7 +2994,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s"); def->min = 0; def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloats{ 0., 0. }); + def->set_default_value(new ConfigOptionFloatsNullable{ 0., 0. }); // M205 T... [mm/sec] def = this->add("machine_min_travel_rate", coFloats); @@ -2568,7 +3004,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s"); def->min = 0; def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloats{ 0., 0. }); + def->set_default_value(new ConfigOptionFloatsNullable{ 0., 0. }); // M204 P... [mm/sec^2] def = this->add("machine_max_acceleration_extruding", coFloats); @@ -2581,7 +3017,8 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->readonly = false; def->mode = comSimple; - def->set_default_value(new ConfigOptionFloats{ 1500., 1250. }); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{ 1500., 1250. }); // M204 R... [mm/sec^2] @@ -2593,7 +3030,8 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->readonly = false; def->mode = comSimple; - def->set_default_value(new ConfigOptionFloats{ 1500., 1250. }); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{ 1500., 1250. }); // M204 T... [mm/sec^2] def = this->add("machine_max_acceleration_travel", coFloats); @@ -2604,7 +3042,8 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->readonly = true; def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloats{ 1500., 1250. }); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{ 1500., 1250. }); def = this->add("fan_max_speed", coInts); def->label = L("Fan speed"); @@ -2623,7 +3062,8 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats { 0. }); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable { 0. }); #ifdef HAS_PRESSURE_EQUALIZER //def = this->add("max_volumetric_extrusion_rate_slope_positive", coFloat); @@ -2686,7 +3126,8 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats { 0.07 }); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable { 0.07 }); def = this->add("slow_down_min_speed", coFloats); def->label = L("Min print speed"); @@ -2702,7 +3143,8 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->mode = comAdvanced; def->max = 1.0; - def->set_default_value(new ConfigOptionFloats { 0.4 }); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable { 0.4 }); def = this->add("host_type", coEnum); def->label = L("Host Type"); @@ -2727,13 +3169,14 @@ void PrintConfigDef::init_fff_params() def->cli = ConfigOptionDef::nocli; def->set_default_value(new ConfigOptionEnum(htOctoPrint)); - def = this->add("nozzle_volume", coFloat); + def = this->add("nozzle_volume", coFloats); def->label = L("Nozzle volume"); def->tooltip = L("Volume of nozzle between the cutter and the end of nozzle"); def->sidetext = L("mm³"); def->mode = comAdvanced; def->readonly = true; - def->set_default_value(new ConfigOptionFloat { 0.0 }); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable { {0.0} }); def = this->add("start_end_points", coPoints); def->label = L("Start end points"); @@ -2794,7 +3237,7 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.4)); - def = this->add("inner_wall_speed", coFloat); + def = this->add("inner_wall_speed", coFloats); def->label = L("Inner wall"); def->category = L("Speed"); def->tooltip = L("Speed of inner wall"); @@ -2802,7 +3245,8 @@ void PrintConfigDef::init_fff_params() def->aliases = { "perimeter_feed_rate" }; def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(60)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{60}); def = this->add("wall_loops", coInt); def->label = L("Wall loops"); @@ -2913,20 +3357,23 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Only trigger retraction when the travel distance is longer than this threshold"); def->sidetext = L("mm"); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats { 2. }); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable { 2. }); def = this->add("retract_before_wipe", coPercents); def->label = L("Retract amount before wipe"); def->tooltip = L("The length of fast retraction before wipe, relative to retraction length"); def->sidetext = "%"; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionPercents { 100 }); + def->nullable = true; + def->set_default_value(new ConfigOptionPercentsNullable { 100 }); def = this->add("retract_when_changing_layer", coBools); def->label = L("Retract when change layer"); def->tooltip = L("Force a retraction when changes layer"); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBools { false }); + def->nullable = true; + def->set_default_value(new ConfigOptionBoolsNullable { false }); def = this->add("retraction_length", coFloats); def->label = L("Length"); @@ -2935,7 +3382,8 @@ void PrintConfigDef::init_fff_params() "Set zero to disable retraction"); def->sidetext = L("mm"); def->mode = comSimple; - def->set_default_value(new ConfigOptionFloats { 0.8 }); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable { 0.8 }); def = this->add("enable_long_retraction_when_cut",coInt); def->mode = comDevelop; @@ -2946,7 +3394,8 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Experimental feature.Retracting and cutting off the filament at a longer distance during changes to minimize purge." "While this reduces flush significantly, it may also raise the risk of nozzle clogs or other printing problems."); def->mode = comDevelop; - def->set_default_value(new ConfigOptionBools {false}); + def->nullable = true; + def->set_default_value(new ConfigOptionBoolsNullable {false}); def = this->add("retraction_distances_when_cut",coFloats); def->label = L("Retraction distance when cut"); @@ -2954,7 +3403,8 @@ void PrintConfigDef::init_fff_params() def->mode = comDevelop; def->min = 10; def->max = 18; - def->set_default_value(new ConfigOptionFloats {18}); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable {18}); def = this->add("retract_length_toolchange", coFloats); def->label = L("Length"); @@ -2965,7 +3415,8 @@ void PrintConfigDef::init_fff_params() // "the extruder)."); def->sidetext = L("mm"); def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloats { 10. }); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable { 10. }); def = this->add("z_hop", coFloats); def->label = L("Z hop when retract"); @@ -2976,7 +3427,8 @@ void PrintConfigDef::init_fff_params() def->mode = comSimple; def->min = 0; def->max = 5; - def->set_default_value(new ConfigOptionFloats { 0.4 }); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable { 0.4 }); def = this->add("retract_lift_above", coFloats); def->label = L("Z hop lower boundary"); @@ -2984,7 +3436,8 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->mode = comAdvanced; def->min = 0; - def->set_default_value(new ConfigOptionFloats{0.}); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0.}); def = this->add("retract_lift_below", coFloats); def->label = L("Z hop upper boundary"); @@ -2992,7 +3445,8 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->mode = comAdvanced; def->min = 0; - def->set_default_value(new ConfigOptionFloats{0.}); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0.}); def = this->add("z_hop_types", coEnums); @@ -3008,19 +3462,101 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Slope")); def->enum_labels.push_back(L("Spiral")); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnumsGeneric{ ZHopType::zhtSpiral }); + def->nullable = true; + def->set_default_value(new ConfigOptionEnumsGenericNullable{ ZHopType::zhtSpiral }); def = this->add("extruder_type", coEnums); def->label = L("Type"); def->tooltip = ("This setting is only used for initial value of manual calibration of pressure advance. Bowden extruder usually has larger pa value. This setting doesn't influence normal slicing"); def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("DirectDrive"); + def->enum_values.push_back("Direct Drive"); def->enum_values.push_back("Bowden"); - def->enum_labels.push_back(L("Direct drive")); + def->enum_labels.push_back(L("Direct Drive")); def->enum_labels.push_back(L("Bowden")); def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnumsGeneric{ ExtruderType::etDirectDrive }); + //QDS + def = this->add("nozzle_volume_type", coEnums); + def->label = L("Nozzle Volume Type"); + def->tooltip = ("Nozzle volume type"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back(L("Standard")); + def->enum_values.push_back(L("High Flow")); + def->enum_labels.push_back(L("Standard")); + def->enum_labels.push_back(L("High Flow")); + def->mode = comSimple; + def->set_default_value(new ConfigOptionEnumsGeneric{ NozzleVolumeType::nvtStandard }); + + def = this->add("default_nozzle_volume_type", coEnums); + def->label = L("Default Nozzle Volume Type"); + def->tooltip = ("Default Nozzle volume type for extruders in this printer"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back(L("Standard")); + def->enum_values.push_back(L("High Flow")); + def->enum_labels.push_back(L("Standard")); + def->enum_labels.push_back(L("High Flow")); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionEnumsGeneric{ NozzleVolumeType::nvtStandard }); + + def = this->add("extruder_variant_list", coStrings); + def->label = "Extruder variant list"; + def->tooltip = "Extruder variant list"; + def->set_default_value(new ConfigOptionStrings { "Direct Drive Standard" }); + def->cli = ConfigOptionDef::nocli; + + def = this->add("extruder_ams_count", coStrings); + def->label = "Extruder ams count"; + def->tooltip = "Ams counts of per extruder"; + def->set_default_value(new ConfigOptionStrings { }); + + def = this->add("printer_extruder_id", coInts); + def->label = "Printer extruder id"; + def->tooltip = "Printer extruder id"; + def->set_default_value(new ConfigOptionInts { 1 }); + def->cli = ConfigOptionDef::nocli; + + def = this->add("printer_extruder_variant", coStrings); + def->label = "Printer's extruder variant"; + def->tooltip = "Printer's extruder variant"; + def->set_default_value(new ConfigOptionStrings { "Direct Drive Standard" }); + def->cli = ConfigOptionDef::nocli; + + def = this->add("master_extruder_id", coInt); + def->label = "Master extruder id"; + def->tooltip = "Default extruder id to place filament"; + def->set_default_value(new ConfigOptionInt{ 1 }); + + def = this->add("print_extruder_id", coInts); + def->label = "Print extruder id"; + def->tooltip = "Print extruder id"; + def->set_default_value(new ConfigOptionInts { 1 }); + def->cli = ConfigOptionDef::nocli; + + def = this->add("print_extruder_variant", coStrings); + def->label = "Print's extruder variant"; + def->tooltip = "Print's extruder variant"; + def->set_default_value(new ConfigOptionStrings { "Direct Drive Standard" }); + def->cli = ConfigOptionDef::nocli; + + /*def = this->add("filament_extruder_id", coInts); + def->label = "Filament extruder id"; + def->tooltip = "Filament extruder id"; + def->set_default_value(new ConfigOptionInts { 1 }); + def->cli = ConfigOptionDef::nocli;*/ + + def = this->add("filament_extruder_variant", coStrings); + def->label = "Filament's extruder variant"; + def->tooltip = "Filament's extruder variant"; + def->set_default_value(new ConfigOptionStrings { "Direct Drive Standard" }); + def->cli = ConfigOptionDef::nocli; + + def = this->add("filament_self_index", coInts); + def->label = "Filament self index"; + def->tooltip = "Filament self index"; + def->set_default_value(new ConfigOptionInts { 1 }); + def->cli = ConfigOptionDef::nocli; + def = this->add("retract_restart_extra", coFloats); def->label = L("Extra length on restart"); //def->label = "Extra length on restart"; @@ -3028,7 +3564,7 @@ void PrintConfigDef::init_fff_params() // "this additional amount of filament. This setting is rarely needed."); def->sidetext = L("mm"); def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloats { 0. }); + def->set_default_value(new ConfigOptionFloatsNullable { 0. }); def = this->add("retract_restart_extra_toolchange", coFloats); def->label = L("Extra length on restart"); @@ -3037,7 +3573,8 @@ void PrintConfigDef::init_fff_params() // "this additional amount of filament."); def->sidetext = L("mm"); def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloats { 0. }); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable { 0. }); def = this->add("retraction_speed", coFloats); def->label = L("Retraction Speed"); @@ -3045,7 +3582,8 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Speed of retractions"); def->sidetext = L("mm/s"); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats { 30. }); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable { 30. }); def = this->add("deretraction_speed", coFloats); def->label = L("Deretraction Speed"); @@ -3053,7 +3591,8 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Speed for reloading filament into extruder. Zero means the same speed as retraction"); def->sidetext = L("mm/s"); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats { 0. }); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable { 0. }); def = this->add("seam_position", coEnum); def->label = L("Seam position"); @@ -3210,14 +3749,15 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.4)); - def = this->add("internal_solid_infill_speed", coFloat); + def = this->add("internal_solid_infill_speed", coFloats); def->label = L("Internal solid infill"); def->category = L("Speed"); def->tooltip = L("Speed of internal solid infill, not the top and bottom surface"); def->sidetext = L("mm/s"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(100)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{100}); def = this->add("spiral_mode", coBool); def->label = L("Spiral vase"); @@ -3544,14 +4084,15 @@ void PrintConfigDef::init_fff_params() def->mode = comDevelop; def->set_default_value(new ConfigOptionFloat(0.5)); - def = this->add("support_interface_speed", coFloat); + def = this->add("support_interface_speed", coFloats); def->label = L("Support interface"); def->category = L("Speed"); def->tooltip = L("Speed of support interface"); def->sidetext = L("mm/s"); def->min = 1; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(80)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{80}); def = this->add("support_base_pattern", coEnum); def->label = L("Base pattern"); @@ -3577,8 +4118,7 @@ void PrintConfigDef::init_fff_params() def->label = L("Interface pattern"); def->category = L("Support"); def->tooltip = L("Line pattern of support interface. " - "Default pattern for non-soluble support interface is Rectilinear, " - "while default pattern for soluble support interface is Concentric"); + "Default pattern for support interface is Rectilinear Interlaced"); def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("auto"); def->enum_values.push_back("rectilinear"); @@ -3610,25 +4150,27 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0)); - def = this->add("support_speed", coFloat); + def = this->add("support_speed", coFloats); def->label = L("Support"); def->category = L("Speed"); def->tooltip = L("Speed of support"); def->sidetext = L("mm/s"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(80)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{ 80 }); def = this->add("support_style", coEnum); def->label = L("Style"); def->category = L("Support"); - def->tooltip = L("Style and shape of the support. For normal support, projecting the supports into a regular grid " + def->tooltip = L("Style and shape of the support. For normal support, projecting the supports into a regular grid " "will create more stable supports (default), while snug support towers will save material and reduce " "object scarring.\n" "For tree support, slim style will merge branches more aggressively and save " "a lot of material, strong style will make larger and stronger support structure and use more materials, " "while hybrid style is the combination of slim tree and normal support with normal nodes " - "under large flat overhangs (default)."); + "under large flat overhangs. Organic style will produce more organic shaped tree structure and less interfaces which makes it easer to be removed. " + "The default style is organic tree for most cases, and hybrid tree if adaptive layer height or soluble interface is enabled."); def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("default"); def->enum_values.push_back("grid"); @@ -3672,7 +4214,7 @@ void PrintConfigDef::init_fff_params() "If the angle is increased, the branches can be printed more horizontally, allowing them to reach farther."); def->sidetext = L("°"); def->min = 0; - def->max = 60; + def->max = 80; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(40.)); @@ -3730,6 +4272,16 @@ void PrintConfigDef::init_fff_params() def->max = 65; def->set_default_value(new ConfigOptionInts{0}); + //y58 + def = this->add("box_temperature", coInts); + def->label = L("Box temperature"); + def->tooltip = L("The Tips!!"); + def->sidetext = "°C"; + def->full_label = L("Box temperature"); + def->min = 0; + def->max = 65; + def->set_default_value(new ConfigOptionInts{0}); + def = this->add("nozzle_temperature", coInts); def->label = L("Other layers"); def->tooltip = L("Nozzle temperature for layers after the initial one"); @@ -3737,7 +4289,8 @@ void PrintConfigDef::init_fff_params() def->full_label = L("Nozzle temperature"); def->min = 0; def->max = max_temp; - def->set_default_value(new ConfigOptionInts { 200 }); + def->nullable = true; + def->set_default_value(new ConfigOptionIntsNullable { 200 }); def = this->add("nozzle_temperature_range_low", coInts); def->label = L("Min"); @@ -3755,11 +4308,33 @@ void PrintConfigDef::init_fff_params() def->max = max_temp; def->set_default_value(new ConfigOptionInts { 240 }); + //y58 + def = this->add("box_temperature_range_low", coInts); + def->label = L("Min"); + //def->tooltip = ""; + def->sidetext = "°C"; + def->min = 0; + def->max = max_temp; + def->set_default_value(new ConfigOptionInts { 0 }); + + def = this->add("box_temperature_range_high", coInts); + def->label = L("Max"); + //def->tooltip = ""; + def->sidetext = "°C"; + def->min = 0; + def->max = max_temp; + def->set_default_value(new ConfigOptionInts { 35 }); + def = this->add("head_wrap_detect_zone", coPoints); def->label ="Head wrap detect zone"; //do not need translation def->mode = comDevelop; def->set_default_value(new ConfigOptionPoints{}); + def = this->add("impact_strength_z", coFloats); + def->label = L("Impact Strength Z"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionFloats{0}); + def = this->add("detect_thin_wall", coBool); def->label = L("Detect thin wall"); def->category = L("Strength"); @@ -3786,14 +4361,15 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.4)); - def = this->add("top_surface_speed", coFloat); + def = this->add("top_surface_speed", coFloats); def->label = L("Top surface"); def->category = L("Speed"); def->tooltip = L("Speed of top surface infill which is solid"); def->sidetext = L("mm/s"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(100)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{100}); def = this->add("top_shell_layers", coInt); def->label = L("Top shell layers"); @@ -3816,15 +4392,30 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->set_default_value(new ConfigOptionFloat(0.6)); - def = this->add("travel_speed", coFloat); + def = this->add("top_color_penetration_layers", coInt); + def->label = L("Top paint penetration layers"); + def->category = L("Strength"); + def->tooltip = L("This is the number of layers of top paint penetration."); + def->min = 1; + def->set_default_value(new ConfigOptionInt(4)); + + def = this->add("bottom_color_penetration_layers", coInt); + def->label = L("Bottom paint penetration layers"); + def->category = L("Strength"); + def->tooltip = L("This is the number of layers of top bottom penetration."); + def->min = 1; + def->set_default_value(new ConfigOptionInt(3)); + + def = this->add("travel_speed", coFloats); def->label = L("Travel"); def->tooltip = L("Speed of travel which is faster and without extrusion"); def->sidetext = L("mm/s"); def->min = 1; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(120)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{120}); - def = this->add("travel_speed_z", coFloat); + def = this->add("travel_speed_z", coFloats); //def->label = L("Z travel"); //def->tooltip = L("Speed of vertical travel along z axis. " // "This is typically lower because build plate or gantry is hard to be moved. " @@ -3832,7 +4423,8 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s"); def->min = 0; def->mode = comDevelop; - def->set_default_value(new ConfigOptionFloat(0.)); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{0.}); def = this->add("use_relative_e_distances", coBool); def->label = L("Use relative E distances"); @@ -3852,7 +4444,8 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Move nozzle along the last extrusion path when retracting to clean leaked material on nozzle. " "This can minimize blob when printing new part after travel"); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBools { false }); + def->nullable = true; + def->set_default_value(new ConfigOptionBoolsNullable { false }); def = this->add("wipe_distance", coFloats); def->label = L("Wipe Distance"); @@ -3860,7 +4453,8 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats { 2. }); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable { 2. }); def = this->add("enable_prime_tower", coBool); def->label = L("Enable"); @@ -3869,6 +4463,102 @@ void PrintConfigDef::init_fff_params() def->mode = comSimple; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("prime_tower_enable_framework", coBool); + def->label = L("Internal ribs"); + def->tooltip = L("Enable internal ribs to increase the stability of the prime tower."); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("enable_circle_compensation", coBool); + def->label = L("Auto circle contour-hole compensation"); + def->tooltip = L("Expirment feature to compensate the circle holes and circle contour. " + "This feature is used to improve the accuracy of the circle holes and contour within the diameter below 50mm. " + "Only support PLA Basic, PLA CF, PET CF, PETG CF and PETG HF."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("circle_compensation_manual_offset", coFloat); + def->label = L("User Customized Offset"); + def->sidetext = L("mm"); + def->tooltip = L("If you want to have tighter or looser assemble, you can set this value. When it is positive, it indicates tightening, otherwise, it indicates loosening"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.0)); + + def = this->add("apply_scarf_seam_on_circles", coBool); + def->label = L("Scarf Seam On Compensation Circles"); + def->tooltip = L("Scarf seam will be applied on circles for better dimensional accuracy."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("circle_compensation_speed", coFloats); + def->label = L("Circle Compensation Speed"); + def->tooltip = L("circle_compensation_speed"); + def->sidetext = L("mm/s"); + def->min = 0; + def->set_default_value(new ConfigOptionFloats{200}); + + def = this->add("counter_coef_1", coFloats); + def->label = L("Counter Coef 1"); + def->tooltip = L("counter_coef_1"); + def->set_default_value(new ConfigOptionFloats{0}); + + def = this->add("counter_coef_2", coFloats); + def->label = L("Contour Coef 2"); + def->tooltip = L("counter_coef_2"); + def->set_default_value(new ConfigOptionFloats{0.025}); + + def = this->add("counter_coef_3", coFloats); + def->label = L("Contour Coef 3"); + def->tooltip = L("counter_coef_3"); + def->sidetext = L("mm"); + def->set_default_value(new ConfigOptionFloats{-0.11}); + + def = this->add("hole_coef_1", coFloats); + def->label = L("Hole Coef 1"); + def->tooltip = L("hole_coef_1"); + def->set_default_value(new ConfigOptionFloats{0}); + + def = this->add("hole_coef_2", coFloats); + def->label = L("Hole Coef 2"); + def->tooltip = L("hole_coef_2"); + def->set_default_value(new ConfigOptionFloats{-0.025}); + + def = this->add("hole_coef_3", coFloats); + def->label = L("Hole Coef 3"); + def->tooltip = L("hole_coef_3"); + def->sidetext = L("mm"); + def->set_default_value(new ConfigOptionFloats{0.28}); + + def = this->add("counter_limit_min", coFloats); + def->label = L("Contour limit min"); + def->tooltip = L("counter_limit_min"); + def->sidetext = L("mm"); + def->set_default_value(new ConfigOptionFloats{-0.04}); + + def = this->add("counter_limit_max", coFloats); + def->label = L("Contour limit max"); + def->tooltip = L("counter_limit_max"); + def->sidetext = L("mm"); + def->set_default_value(new ConfigOptionFloats{0.05}); + + def = this->add("hole_limit_min", coFloats); + def->label = L("Hole limit min"); + def->tooltip = L("hole_limit_min"); + def->sidetext = L("mm"); + def->set_default_value(new ConfigOptionFloats{0.08}); + + def = this->add("hole_limit_max", coFloats); + def->label = L("Hole limit max"); + def->tooltip = L("hole_limit_max"); + def->sidetext = L("mm"); + def->set_default_value(new ConfigOptionFloats{0.25}); + + def = this->add("diameter_limit", coFloats); + def->label = L("Diameter limit"); + def->tooltip = L("diameter_limit"); + def->sidetext = L("mm"); + def->set_default_value(new ConfigOptionFloats{50}); + def = this->add("flush_volumes_vector", coFloats); // QDS: remove _L() def->label = ("Purging volumes - load/unload volumes"); @@ -3889,21 +4579,21 @@ void PrintConfigDef::init_fff_params() 280.f, 280.f, 0.f, 280.f, 280.f, 280.f, 280.f, 0.f }); - def = this->add("flush_multiplier", coFloat); + def = this->add("flush_multiplier", coFloats); def->label = L("Flush multiplier"); def->tooltip = L("The actual flushing volumes is equal to the flush multiplier multiplied by the flushing volumes in the table."); def->sidetext = ""; - def->set_default_value(new ConfigOptionFloat(1.0)); + def->set_default_value(new ConfigOptionFloats{1.0}); - // QDS - def = this->add("prime_volume", coFloat); - def->label = L("Prime volume"); - def->tooltip = L("The volume of material to prime extruder on tower."); - def->sidetext = L("mm³"); - def->min = 1.0; - def->mode = comSimple; - def->set_default_value(new ConfigOptionFloat(45.)); + // // QDS + // def = this->add("prime_volume", coFloat); + // def->label = L("Prime volume"); + // def->tooltip = L("The volume of material to prime extruder on tower."); + // def->sidetext = L("mm³"); + // def->min = 1.0; + // def->mode = comSimple; + // def->set_default_value(new ConfigOptionFloat(45.)); def = this->add("wipe_tower_x", coFloats); //def->label = L("Position X"); @@ -3936,14 +4626,75 @@ void PrintConfigDef::init_fff_params() def->mode = comDevelop; def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("prime_tower_max_speed", coFloat); + def->label = L("Max speed"); + def->tooltip = L("The maximum printing speed on the prime tower excluding ramming."); + def->sidetext = L("mm/s"); + def->mode = comAdvanced; + def->min = 10; + def->set_default_value(new ConfigOptionFloat(90.)); + + def = this->add("prime_tower_lift_speed", coFloat); + def->set_default_value(new ConfigOptionFloat(90.)); + + def = this->add("prime_tower_lift_height", coFloat); + def->set_default_value(new ConfigOptionFloat(-1)); + def = this->add("prime_tower_brim_width", coFloat); + def->gui_type = ConfigOptionDef::GUIType::f_enum_open; def->label = L("Brim width"); - def->tooltip = L("Brim width"); + def->tooltip = L("Brim width of prime tower, negative number means auto calculated width based on the height of prime tower."); def->sidetext = L("mm"); def->mode = comAdvanced; - def->min = 0.; + def->min = -1; + def->enum_values.push_back("-1"); + def->enum_labels.push_back(L("Auto")); def->set_default_value(new ConfigOptionFloat(3.)); + def = this->add("prime_tower_extra_rib_length", coFloat); + def->label = L("Extra rib length"); + def->tooltip = L("Positive values can increase the size of the rib wall, while negative values can reduce the size." + "However, the size of the rib wall can not be smaller than that determined by the cleaning volume."); + def->sidetext = L("mm"); + def->max = 300; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("prime_tower_rib_width", coFloat); + def->label = L("Rib width"); + def->tooltip = L("Rib width"); + def->sidetext = L("mm"); + def->mode = comAdvanced; + def->min = 0; + def->set_default_value(new ConfigOptionFloat(8)); + + def = this->add("prime_tower_skip_points", coBool); + def->label = L("Skip points"); + def->tooltip = L("The wall of prime tower will skip the start points of wipe path"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("prime_tower_rib_wall", coBool); + def->label = L("Rib wall"); + def->tooltip = L("The wall of prime tower will add four ribs and make its " + "cross-section as close to a square as possible, so the width will be fixed."); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("prime_tower_fillet_wall", coBool); + def->label = L("Fillet wall"); + def->tooltip = L("The wall of prime tower will fillet"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("prime_tower_infill_gap", coPercent); + def->label = L("Infill gap"); + def->tooltip = L("Infill gap"); + def->sidetext = L("%"); + def->mode = comAdvanced; + def->min = 100; + def->set_default_value(new ConfigOptionPercent(150)); + def = this->add("flush_into_infill", coBool); def->category = L("Flush options"); def->label = L("Flush into objects' infill"); @@ -4129,10 +4880,10 @@ void PrintConfigDef::init_fff_params() else def->mode = comAdvanced; switch (def->type) { - case coFloats : def->set_default_value(new ConfigOptionFloatsNullable (static_cast(it_opt->second.default_value.get())->values)); break; - case coPercents : def->set_default_value(new ConfigOptionPercentsNullable(static_cast(it_opt->second.default_value.get())->values)); break; - case coBools : def->set_default_value(new ConfigOptionBoolsNullable (static_cast(it_opt->second.default_value.get())->values)); break; - case coEnums : def->set_default_value(new ConfigOptionEnumsGenericNullable(static_cast(it_opt->second.default_value.get())->values)); break; + case coFloats: def->set_default_value(new ConfigOptionFloatsNullable(static_cast(it_opt->second.default_value.get())->values)); break; + case coPercents: def->set_default_value(new ConfigOptionPercentsNullable(static_cast(it_opt->second.default_value.get())->values)); break; + case coBools: def->set_default_value(new ConfigOptionBoolsNullable(static_cast(it_opt->second.default_value.get())->values)); break; + case coEnums: def->set_default_value(new ConfigOptionEnumsGenericNullable(static_cast(it_opt->second.default_value.get())->values)); break; default: assert(false); } } @@ -4151,7 +4902,7 @@ void PrintConfigDef::init_extruder_option_keys() { // ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings m_extruder_option_keys = { - "extruder_type", "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset", + "extruder_type", "nozzle_diameter", "default_nozzle_volume_type", "min_layer_height", "max_layer_height", "extruder_offset", "retraction_length", "z_hop", "z_hop_types", "retraction_speed", "retract_lift_above", "retract_lift_below","deretraction_speed", "retract_before_wipe", "retract_restart_extra", "retraction_minimum_travel", "wipe", "wipe_distance", "retract_when_changing_layer", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", @@ -4847,8 +5598,12 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va } else if (opt_key == "bottom_solid_infill_flow_ratio") { opt_key = "initial_layer_flow_ratio"; } else if (opt_key == "wiping_volume") { - opt_key = "prime_volume"; - } else if (opt_key == "wipe_tower_brim_width") { + opt_key = "filament_prime_volume"; + } + else if (opt_key == "prime_volume") { + opt_key = "filament_prime_volume"; + } + else if (opt_key == "wipe_tower_brim_width") { opt_key = "prime_tower_brim_width"; } else if (opt_key == "tool_change_gcode") { opt_key = "change_filament_gcode"; @@ -4931,6 +5686,50 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va } else { opt_key = "wall_sequence"; } + } else if (opt_key == "nozzle_volume_type" + || opt_key == "default_nozzle_volume_type" + || opt_key == "printer_extruder_variant" + || opt_key == "print_extruder_variant" + || opt_key == "filament_extruder_variant" + || opt_key == "extruder_variant_list") { + ReplaceString(value, "Normal", "Standard"); + ReplaceString(value, "Big Traffic", "High Flow"); + } + else if (opt_key == "extruder_type") { + ReplaceString(value, "DirectDrive", "Direct Drive"); + } + else if (opt_key == "ensure_vertical_shell_thickness") { + auto kvmap=ConfigOptionEnum::get_enum_names(); + // handle old values + if (value == "1") + value = ConfigOptionEnum::get_enum_names()[EnsureVerticalThicknessLevel::evtEnabled]; + else if (value == "0") + value = ConfigOptionEnum::get_enum_names()[EnsureVerticalThicknessLevel::evtPartial]; + } else if (opt_key == "filament_map_mode") { + if (value == "Auto") value = "Auto For Flush"; + } + else if (opt_key == "filament_type"){ + std::vector type_list; + std::stringstream ss(value); + std::string token; + bool rebuild_value = false; + while (std::getline(ss, token, ';')) { + if (token.size() >= 2 && token.front() == '"' && token.back() == '"') + token = token.substr(1, token.size() - 2); + if (token == "ASA-Aero") { + token = "ASA-AERO"; + rebuild_value = true; + } + type_list.emplace_back(token); + } + if (rebuild_value) { + value.clear(); + for (size_t idx = 0; idx < type_list.size(); ++idx) { + if (idx != 0) + value += ';'; + value += "\"" + type_list[idx] + "\""; + } + } } // Ignore the following obsolete configuration keys: @@ -4952,7 +5751,8 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va "can_switch_nozzle_type", "can_add_auxiliary_fan", "extra_flush_volume", "spaghetti_detector", "adaptive_layer_height", "z_hop_type","nozzle_hrc","chamber_temperature","only_one_wall_top","bed_temperature_difference","long_retraction_when_cut", "retraction_distance_when_cut", - "seam_slope_type","seam_slope_start_height","seam_slope_gap", "seam_slope_min_length" + "seam_slope_type","seam_slope_start_height","seam_slope_gap", "seam_slope_min_length", + "prime_volume" }; if (ignore.find(opt_key) != ignore.end()) { @@ -5000,6 +5800,127 @@ void PrintConfigDef::handle_legacy_composite(DynamicPrintConfig& config) const PrintConfigDef print_config_def; +//todo +std::set print_options_with_variant = { + "initial_layer_speed", + "initial_layer_infill_speed", + "outer_wall_speed", + "inner_wall_speed", + "small_perimeter_speed", //coFloatsOrPercents + "small_perimeter_threshold", + "sparse_infill_speed", + "internal_solid_infill_speed", + "vertical_shell_speed", + "top_surface_speed", + "enable_overhang_speed", //coBools + "overhang_1_4_speed", + "overhang_2_4_speed", + "overhang_3_4_speed", + "overhang_4_4_speed", + "overhang_totally_speed", + "bridge_speed", + "gap_infill_speed", + "support_speed", + "support_interface_speed", + "travel_speed", + "travel_speed_z", + "default_acceleration", + "travel_acceleration", + "initial_layer_travel_acceleration", + "initial_layer_acceleration", + "outer_wall_acceleration", + "inner_wall_acceleration", + "sparse_infill_acceleration", //coFloatsOrPercents + "top_surface_acceleration", + "print_extruder_id", //coInts + "print_extruder_variant" //coStrings +}; + +std::set filament_options_with_variant = { + "filament_flow_ratio", + "filament_max_volumetric_speed", + "filament_ramming_volumetric_speed", + "filament_pre_cooling_temperature", + "filament_ramming_travel_time", + //"filament_extruder_id", + "filament_extruder_variant", + "filament_retraction_length", + "filament_z_hop", + "filament_z_hop_types", + "filament_retraction_speed", + "filament_deretraction_speed", + "filament_retraction_minimum_travel", + "filament_retract_when_changing_layer", + "filament_wipe", + //QDS + "filament_wipe_distance", + "filament_retract_before_wipe", + "filament_long_retractions_when_cut", + "filament_retraction_distances_when_cut", + "nozzle_temperature_initial_layer", + "nozzle_temperature" + +}; + +// Parameters that are the same as the number of extruders +std::set printer_extruder_options = { + "extruder_type", + "nozzle_diameter", + "default_nozzle_volume_type", + "extruder_printable_area", + "extruder_printable_height", + "min_layer_height", + "max_layer_height" +}; + +std::set printer_options_with_variant_1 = { + "nozzle_volume", + "retraction_length", + "z_hop", + "retract_lift_above", + "retract_lift_below", + "z_hop_types", + "retraction_speed", + "deretraction_speed", + "retraction_minimum_travel", + "retract_when_changing_layer", + "wipe", + "wipe_distance", + "retract_before_wipe", + "retract_length_toolchange", + "retract_restart_extra", + "retract_restart_extra_toolchange", + "long_retractions_when_cut", + "retraction_distances_when_cut", + "nozzle_volume", + "nozzle_type", + "printer_extruder_id", + "printer_extruder_variant", + "hotend_cooling_rate", + "hotend_heating_rate" +}; + +//options with silient mode +std::set printer_options_with_variant_2 = { + "machine_max_acceleration_x", + "machine_max_acceleration_y", + "machine_max_acceleration_z", + "machine_max_acceleration_e", + "machine_max_acceleration_extruding", + "machine_max_acceleration_retracting", + "machine_max_acceleration_travel", + "machine_max_speed_x", + "machine_max_speed_y", + "machine_max_speed_z", + "machine_max_speed_e", + "machine_max_jerk_x", + "machine_max_jerk_y", + "machine_max_jerk_z", + "machine_max_jerk_e" +}; + +std::set empty_options; + DynamicPrintConfig DynamicPrintConfig::full_print_config() { return DynamicPrintConfig((const PrintRegionConfig&)FullPrintConfig::defaults()); @@ -5069,7 +5990,7 @@ void DynamicPrintConfig::normalize_fdm(int used_filaments) if (this->has("spiral_mode") && this->opt("spiral_mode", true)->value) { { // this should be actually done only on the spiral layers instead of all - auto* opt = this->opt("retract_when_changing_layer", true); + auto* opt = this->opt("retract_when_changing_layer", true); opt->values.assign(opt->values.size(), false); // set all values to false // Disable retract on layer change also for filament overrides. auto* opt_n = this->opt("filament_retract_when_changing_layer", true); @@ -5141,7 +6062,7 @@ void DynamicPrintConfig::normalize_fdm_1() if (this->has("spiral_mode") && this->opt("spiral_mode", true)->value) { { // this should be actually done only on the spiral layers instead of all - auto* opt = this->opt("retract_when_changing_layer", true); + auto* opt = this->opt("retract_when_changing_layer", true); opt->values.assign(opt->values.size(), false); // set all values to false // Disable retract on layer change also for filament overrides. auto* opt_n = this->opt("filament_retract_when_changing_layer", true); @@ -5234,6 +6155,34 @@ void handle_legacy_sla(DynamicPrintConfig &config) } } +size_t DynamicPrintConfig::get_parameter_size(const std::string& param_name, size_t extruder_nums) +{ + if (extruder_nums > 1) { + size_t volume_type_size = 2; + auto nozzle_volume_type_opt = dynamic_cast(this->option("nozzle_volume_type")); + if (nozzle_volume_type_opt) { + volume_type_size = nozzle_volume_type_opt->values.size(); + } + bool flag = (param_name == "nozzle_volume"); + if (printer_options_with_variant_1.count(param_name) > 0) { + return extruder_nums * volume_type_size; + } + else if (printer_options_with_variant_2.count(param_name) > 0) { + return extruder_nums * volume_type_size * 2; + } + else if (filament_options_with_variant.count(param_name) > 0) { + return extruder_nums * volume_type_size; + } + else if (print_options_with_variant.count(param_name) > 0) { + return extruder_nums * volume_type_size; + } + else { + return extruder_nums; + } + } + return extruder_nums; +} + void DynamicPrintConfig::set_num_extruders(unsigned int num_extruders) { const auto &defaults = FullPrintConfig::defaults(); @@ -5245,8 +6194,9 @@ void DynamicPrintConfig::set_num_extruders(unsigned int num_extruders) auto *opt = this->option(key, false); assert(opt != nullptr); assert(opt->is_vector()); - if (opt != nullptr && opt->is_vector()) - static_cast(opt)->resize(num_extruders, defaults.option(key)); + if (opt != nullptr && opt->is_vector()) { + static_cast(opt)->resize(get_parameter_size(key, num_extruders), defaults.option(key)); + } } } @@ -5359,14 +6309,1023 @@ std::string DynamicPrintConfig::get_filament_type(std::string &displayed_filamen return "PLA"; } -bool DynamicPrintConfig::is_custom_defined() +bool DynamicPrintConfig::is_using_different_extruders() { - auto* is_custom_defined = dynamic_cast(this->option("is_custom_defined")); - if (!is_custom_defined || is_custom_defined->empty()) - return false; - if (is_custom_defined->get_at(0) == "1") - return true; - return false; + bool ret = false; + + auto nozzle_diameters_opt = dynamic_cast(this->option("nozzle_diameter")); + if (nozzle_diameters_opt != nullptr) { + int size = nozzle_diameters_opt->size(); + if (size > 1) { + auto extruder_type_opt = dynamic_cast(this->option("extruder_type")); + auto nozzle_volume_type_opt = dynamic_cast(this->option("nozzle_volume_type")); + if (extruder_type_opt && nozzle_volume_type_opt) { + ExtruderType extruder_type = (ExtruderType)(extruder_type_opt->get_at(0)); + NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(nozzle_volume_type_opt->get_at(0)); + for (int index = 1; index < size; index++) + { + ExtruderType extruder_type_1 = (ExtruderType)(extruder_type_opt->get_at(index)); + NozzleVolumeType nozzle_volume_type_1 = (NozzleVolumeType)(nozzle_volume_type_opt->get_at(index)); + if ((extruder_type_1 != extruder_type) || (nozzle_volume_type_1 != nozzle_volume_type)) { + ret = true; + break; + } + } + } + } + } + + return ret; +} + +bool DynamicPrintConfig::support_different_extruders(int& extruder_count) +{ + std::set variant_set; + + auto nozzle_diameters_opt = dynamic_cast(this->option("nozzle_diameter")); + if (nozzle_diameters_opt != nullptr) { + int size = nozzle_diameters_opt->size(); + extruder_count = size; + auto extruder_variant_opt = dynamic_cast(this->option("extruder_variant_list")); + if (extruder_variant_opt != nullptr) { + for (int index = 0; index < size; index++) { + std::string variant = extruder_variant_opt->get_at(index); + std::vector variants_list; + boost::split(variants_list, variant, boost::is_any_of(","), boost::token_compress_on); + if (!variants_list.empty()) + variant_set.insert(variants_list.begin(), variants_list.end()); + } + } + } + + return (variant_set.size() > 1); +} + +int DynamicPrintConfig::get_index_for_extruder(int extruder_or_filament_id, std::string id_name, ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type, std::string variant_name, unsigned int stride) const +{ + int ret = -1; + + auto variant_opt = dynamic_cast(this->option(variant_name)); + const ConfigOptionInts* id_opt = id_name.empty()?nullptr: dynamic_cast(this->option(id_name)); + if (variant_opt != nullptr) { + int v_size = variant_opt->values.size(); + //int i_size = id_opt->values.size(); + std::string extruder_variant = get_extruder_variant_string(extruder_type, nozzle_volume_type); + for (int index = 0; index < v_size; index++) + { + const std::string variant = variant_opt->get_at(index); + if (extruder_variant == variant) { + if (id_opt) { + const int id = id_opt->get_at(index); + if (id == extruder_or_filament_id) { + ret = index * stride; + break; + } + } + else { + ret = index * stride; + break; + } + + } + } + } + return ret; +} + +//only used for cli +//update values in single extruder process config to values in multi-extruder process +//limit the new values +int DynamicPrintConfig::update_values_from_single_to_multi(DynamicPrintConfig& multi_config, std::set& key_set, std::string id_name, std::string variant_name) +{ + auto print_variant_opt = dynamic_cast(multi_config.option(variant_name)); + if (!print_variant_opt) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%:%2%, can not get %3% from config")%__FUNCTION__ %__LINE__ % variant_name; + return -1; + } + int variant_count = print_variant_opt->size(); + + const ConfigDef *config_def = this->def(); + if (!config_def) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define")%__LINE__; + return -1; + } + for (auto& key: key_set) + { + const ConfigOptionDef *optdef = config_def->get(key); + if (!optdef) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key; + continue; + } + switch (optdef->type) { + case coStrings: + { + ConfigOptionStrings* src_opt = multi_config.option(key); + if (src_opt) { + ConfigOptionStrings* opt = this->option(key, true); + + opt->values = src_opt->values; + } + break; + } + case coInts: + { + ConfigOptionInts* src_opt = multi_config.option(key); + if (src_opt) { + ConfigOptionInts* opt = this->option(key, true); + + opt->values = src_opt->values; + } + break; + } + case coFloats: + { + ConfigOptionFloats * src_opt = multi_config.option(key); + if (src_opt) { + ConfigOptionFloats * opt = this->option(key, true); + + assert(variant_count == src_opt->size()); + opt->resize(variant_count, opt); + + for (int index = 0; index < variant_count; index++) + { + if (opt->values[index] > src_opt->values[index]) + opt->values[index] = src_opt->values[index]; + } + } + break; + } + case coFloatsOrPercents: + { + ConfigOptionFloatsOrPercents * src_opt = multi_config.option(key); + if (src_opt) { + ConfigOptionFloatsOrPercents * opt = this->option(key, true); + + assert(variant_count == src_opt->size()); + opt->resize(variant_count, opt); + + for (int index = 0; index < variant_count; index++) + { + if (opt->values[index].value > src_opt->values[index].value) + opt->values[index] = src_opt->values[index]; + } + } + break; + } + case coBools: + { + ConfigOptionBools * src_opt = multi_config.option(key); + if (src_opt) + { + ConfigOptionBools * opt = this->option(key, true); + + assert(variant_count == src_opt->size()); + opt->resize(variant_count, opt); + } + + break; + } + default: + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key; + break; + } + } + + return 0; +} + +//used for object/region config +//duplicate single to multiple +int DynamicPrintConfig::update_values_from_single_to_multi_2(DynamicPrintConfig& multi_config, std::set& key_set) +{ + const ConfigDef *config_def = this->def(); + if (!config_def) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define")%__LINE__; + return -1; + } + + t_config_option_keys keys = this->keys(); + for (auto& key: keys) + { + if (key_set.find(key) == key_set.end()) + continue; + + const ConfigOptionDef *optdef = config_def->get(key); + if (!optdef) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key; + continue; + } + switch (optdef->type) { + case coFloats: + { + ConfigOptionFloatsNullable * opt = this->option(key); + ConfigOptionFloatsNullable* src_opt = multi_config.option(key); + + if (src_opt && !opt->is_nil(0)) + opt->values.resize(src_opt->size(), opt->values[0]); + break; + } + case coFloatsOrPercents: + { + ConfigOptionFloatsOrPercentsNullable* opt = this->option(key); + ConfigOptionFloatsOrPercentsNullable* src_opt = multi_config.option(key); + + if (src_opt &&!opt->is_nil(0)) + opt->values.resize(src_opt->size(), opt->values[0]); + break; + } + case coBools: + { + ConfigOptionBoolsNullable* opt = this->option(key); + ConfigOptionBoolsNullable* src_opt = multi_config.option(key); + + if (src_opt &&!opt->is_nil(0)) + opt->values.resize(src_opt->size(), opt->values[0]); + + break; + } + default: + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key; + break; + } + } + + return 0; +} + +int DynamicPrintConfig::update_values_from_multi_to_single(DynamicPrintConfig& single_config, std::set& key_set, std::string id_name, std::string variant_name, std::vector& extruder_variants) +{ + int extruder_count = extruder_variants.size(); + std::vector extruder_index(extruder_count, -1); + + auto print_variant_opt = dynamic_cast(this->option(variant_name)); + if (!print_variant_opt) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%:%2%, can not get %3% from config")%__FUNCTION__ %__LINE__ % variant_name; + return -1; + } + int variant_count = print_variant_opt->size(); + + auto print_id_opt = dynamic_cast(this->option(id_name)); + if (!print_id_opt) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%:%2%, can not get %3% from config")%__FUNCTION__ %__LINE__ % id_name; + return -1; + } + + for (int i = 0; i < extruder_count; i++) + { + for (int j = 0; j < variant_count; j++) + { + if ((i+1 == print_id_opt->values[j]) && (extruder_variants[i] == print_variant_opt->values[j])) { + extruder_index[i] = j; + break; + } + } + } + + const ConfigDef* config_def = this->def(); + if (!config_def) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define") % __LINE__; + return -1; + } + for (auto& key : key_set) + { + const ConfigOptionDef* optdef = config_def->get(key); + if (!optdef) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%") % __LINE__ % key; + continue; + } + switch (optdef->type) { + case coStrings: + { + ConfigOptionStrings* src_opt = single_config.option(key); + if (src_opt) { + ConfigOptionStrings* opt = this->option(key, true); + + //assert(variant_count == opt->size()); + opt->values = src_opt->values; + } + break; + } + case coInts: + { + ConfigOptionInts* src_opt = single_config.option(key); + if (src_opt) { + ConfigOptionInts* opt = this->option(key, true); + + //assert(variant_count == opt->size()); + opt->values = src_opt->values; + } + break; + } + case coFloats: + { + ConfigOptionFloats* src_opt = single_config.option(key); + if (src_opt) { + ConfigOptionFloats* opt = this->option(key, true); + + std::vector old_values = opt->values; + int old_count = old_values.size(); + + //assert(variant_count == opt->size()); + opt->values = src_opt->values; + + for (int i = 0; i < extruder_count; i++) + { + assert(extruder_index[i] != -1); + if ((old_count > extruder_index[i]) && (old_values[extruder_index[i]] < opt->values[0])) + opt->values[0] = old_values[extruder_index[i]]; + } + } + break; + } + case coFloatsOrPercents: + { + ConfigOptionFloatsOrPercents* src_opt = single_config.option(key); + if (src_opt) { + ConfigOptionFloatsOrPercents* opt = this->option(key, true); + + std::vector old_values = opt->values; + int old_count = old_values.size(); + + //assert(variant_count == opt->size()); + opt->values = src_opt->values; + + for (int i = 0; i < extruder_count; i++) + { + assert(extruder_index[i] != -1); + if ((old_count > extruder_index[i]) && (old_values[extruder_index[i]] < opt->values[0])) + opt->values[0] = old_values[extruder_index[i]]; + } + } + break; + } + case coBools: + { + ConfigOptionBools* src_opt = single_config.option(key); + if (src_opt) { + ConfigOptionBools* opt = this->option(key, true); + + //assert(variant_count == opt->size()); + opt->values = src_opt->values; + } + + break; + } + default: + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%") % __LINE__ % key; + break; + } + } + + return 0; +} + +//used for object/region config +//use the smallest of multiple to single +int DynamicPrintConfig::update_values_from_multi_to_single_2(std::set& key_set) +{ + const ConfigDef *config_def = this->def(); + if (!config_def) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define")%__LINE__; + return -1; + } + + t_config_option_keys keys = this->keys(); + for (auto& key: keys) + { + if (key_set.find(key) == key_set.end()) + continue; + + const ConfigOptionDef *optdef = config_def->get(key); + if (!optdef) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key; + continue; + } + switch (optdef->type) { + case coFloats: + { + ConfigOptionFloatsNullable* opt = this->option(key); + double min = 9999.0; + bool has_value = false; + + for (int index = 0; index < opt->values.size(); index++) + { + if (!opt->is_nil(index) && (opt->values[index] < min)) { + min = opt->values[index]; + has_value = true; + } + } + + opt->values.erase(opt->values.begin() + 1, opt->values.end()); + if (has_value) + opt->values[0] = min; + break; + } + case coFloatsOrPercents: + { + ConfigOptionFloatsOrPercentsNullable * opt = this->option(key); + FloatOrPercent min(9999.f, true); + bool has_value = false; + + for (int index = 0; index < opt->values.size(); index++) + { + if (!opt->is_nil(index) && (opt->values[index].value < min.value)) { + min = opt->values[index]; + has_value = true; + } + } + + opt->values.erase(opt->values.begin() + 1, opt->values.end()); + if (has_value) + opt->values[0] = min; + break; + } + case coBools: + { + ConfigOptionBoolsNullable* opt = this->option(key); + + bool min, has_value = false; + for (int index = 0; index < opt->values.size(); index++) + { + if (!opt->is_nil(index)) { + min = opt->values[index]; + has_value = true; + break; + } + } + + opt->values.erase(opt->values.begin() + 1, opt->values.end()); + if (has_value) + opt->values[0] = min; + break; + } + default: + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key; + break; + } + } + + return 0; +} + +std::string +DynamicPrintConfig::get_filament_vendor() const +{ + const ConfigOptionStrings* opt = dynamic_cast (option("filament_vendor")); + if (opt && !opt->values.empty()) + { + return opt->values[0]; + } + + return std::string(); +} + + +std::string +DynamicPrintConfig::get_filament_type() const +{ + const ConfigOptionStrings* opt = dynamic_cast (option("filament_type")); + if (opt && !opt->values.empty()) + { + return opt->values[0]; + } + + return std::string(); +} + +std::vector DynamicPrintConfig::update_values_to_printer_extruders(DynamicPrintConfig& printer_config, std::set& key_set, std::string id_name, std::string variant_name, unsigned int stride, unsigned int extruder_id) +{ + int extruder_count; + bool different_extruder = printer_config.support_different_extruders(extruder_count); + std::vector variant_index; + + if ((extruder_count > 1) || different_extruder) + { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: different extruders processing")%__LINE__; + //apply process settings + //auto opt_nozzle_diameters = this->option("nozzle_diameter"); + //int extruder_count = opt_nozzle_diameters->size(); + auto opt_extruder_type = dynamic_cast(printer_config.option("extruder_type")); + auto opt_nozzle_volume_type = dynamic_cast(printer_config.option("nozzle_volume_type")); + + + if (extruder_id > 0 && extruder_id <= static_cast (extruder_count)) { + variant_index.resize(1); + ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(extruder_id - 1)); + NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(extruder_id - 1)); + + //variant index + variant_index[0] = get_index_for_extruder(extruder_id, id_name, extruder_type, nozzle_volume_type, variant_name); + + if (variant_index[0] < 0) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: could not found extruder_type %2%, nozzle_volume_type %3%, for filament") + % __LINE__ % s_keys_names_ExtruderType[extruder_type] % s_keys_names_NozzleVolumeType[nozzle_volume_type]; + assert(false); + } + + extruder_count = 1; + } + else { + variant_index.resize(extruder_count); + + for (int e_index = 0; e_index < extruder_count; e_index++) + { + ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(e_index)); + NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(e_index)); + + //variant index + variant_index[e_index] = get_index_for_extruder(e_index+1, id_name, extruder_type, nozzle_volume_type, variant_name); + if (variant_index[e_index] < 0) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: could not found extruder_type %2%, nozzle_volume_type %3%, extruder_index %4%") + %__LINE__ %s_keys_names_ExtruderType[extruder_type] % s_keys_names_NozzleVolumeType[nozzle_volume_type] % (e_index+1); + assert(false); + //for some updates happens in a invalid state(caused by popup window) + //we need to avoid crash + variant_index[e_index] = 0; + } + } + } + + const ConfigDef *config_def = this->def(); + if (!config_def) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define")%__LINE__; + return variant_index; + } + for (auto& key: key_set) + { + const ConfigOptionDef *optdef = config_def->get(key); + if (!optdef) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key; + continue; + } + switch (optdef->type) { + case coStrings: + { + ConfigOptionStrings * opt = this->option(key); + std::vector new_values; + + new_values.resize(extruder_count * stride); + for (int e_index = 0; e_index < extruder_count; e_index++) + { + for (unsigned int i = 0; i < stride; i++) + new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + } + opt->values = new_values; + break; + } + case coInts: + { + ConfigOptionInts * opt = this->option(key); + std::vector new_values; + + new_values.resize(extruder_count * stride); + for (int e_index = 0; e_index < extruder_count; e_index++) + { + for (unsigned int i = 0; i < stride; i++) + new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + } + opt->values = new_values; + break; + } + case coFloats: + { + ConfigOptionFloats * opt = this->option(key); + std::vector new_values; + + new_values.resize(extruder_count * stride); + for (int e_index = 0; e_index < extruder_count; e_index++) + { + for (unsigned int i = 0; i < stride; i++) + new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + } + opt->values = new_values; + break; + } + case coPercents: + { + ConfigOptionPercents * opt = this->option(key); + std::vector new_values; + + new_values.resize(extruder_count * stride); + for (int e_index = 0; e_index < extruder_count; e_index++) + { + for (unsigned int i = 0; i < stride; i++) + new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + } + opt->values = new_values; + break; + } + case coFloatsOrPercents: + { + ConfigOptionFloatsOrPercents * opt = this->option(key); + std::vector new_values; + + new_values.resize(extruder_count * stride); + for (int e_index = 0; e_index < extruder_count; e_index++) + { + for (unsigned int i = 0; i < stride; i++) + new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + } + opt->values = new_values; + break; + } + case coBools: + { + ConfigOptionBools * opt = this->option(key); + std::vector new_values; + + new_values.resize(extruder_count * stride); + for (int e_index = 0; e_index < extruder_count; e_index++) + { + for (unsigned int i = 0; i < stride; i++) + new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + } + opt->values = new_values; + break; + } + case coEnums: + { + ConfigOptionEnumsGeneric * opt = this->option(key); + std::vector new_values; + + new_values.resize(extruder_count * stride); + for (int e_index = 0; e_index < extruder_count; e_index++) + { + for (unsigned int i = 0; i < stride; i++) + new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + } + opt->values = new_values; + break; + } + default: + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key; + break; + } + } + } + + return variant_index; +} + +void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filaments(DynamicPrintConfig& printer_config, std::set& key_set, std::string id_name, std::string variant_name) +{ + int extruder_count; + bool different_extruder = printer_config.support_different_extruders(extruder_count); + if ((extruder_count > 1) || different_extruder) + { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: extruder_count=%2%, different_extruder=%3%")%__LINE__ %extruder_count %different_extruder; + std::vector filament_maps = printer_config.option("filament_map")->values; + size_t filament_count = filament_maps.size(); + //apply process settings + //auto opt_nozzle_diameters = this->option("nozzle_diameter"); + //int extruder_count = opt_nozzle_diameters->size(); + auto opt_extruder_type = dynamic_cast(printer_config.option("extruder_type")); + auto opt_nozzle_volume_type = dynamic_cast(printer_config.option("nozzle_volume_type")); + std::vector variant_index; + + + variant_index.resize(filament_count, -1); + + for (int f_index = 0; f_index < filament_count; f_index++) + { + ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(filament_maps[f_index] - 1)); + NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(filament_maps[f_index] - 1)); + + //variant index + variant_index[f_index] = get_index_for_extruder(f_index+1, id_name, extruder_type, nozzle_volume_type, variant_name); + if (variant_index[f_index] < 0) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: could not found extruder_type %2%, nozzle_volume_type %3%, filament_index %4%, extruder index %5%") + %__LINE__ %s_keys_names_ExtruderType[extruder_type] % s_keys_names_NozzleVolumeType[nozzle_volume_type] % (f_index+1) %filament_maps[f_index]; + assert(false); + //for some updates happens in a invalid state(caused by popup window) + //we need to avoid crash + variant_index[f_index] = 0; + } + } + + const ConfigDef *config_def = this->def(); + if (!config_def) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define")%__LINE__; + return; + } + for (auto& key: key_set) + { + const ConfigOptionDef *optdef = config_def->get(key); + if (!optdef) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key; + continue; + } + switch (optdef->type) { + case coStrings: + { + ConfigOptionStrings * opt = this->option(key); + std::vector new_values; + + new_values.resize(filament_count); + for (int f_index = 0; f_index < filament_count; f_index++) + { + new_values[f_index] = opt->get_at(variant_index[f_index]); + } + opt->values = new_values; + break; + } + case coInts: + { + ConfigOptionInts * opt = this->option(key); + std::vector new_values; + + new_values.resize(filament_count); + for (int f_index = 0; f_index < filament_count; f_index++) + { + new_values[f_index] = opt->get_at(variant_index[f_index]); + } + opt->values = new_values; + break; + } + case coFloats: + { + ConfigOptionFloats * opt = this->option(key); + std::vector new_values; + + new_values.resize(filament_count); + for (int f_index = 0; f_index < filament_count; f_index++) + { + new_values[f_index] = opt->get_at(variant_index[f_index]); + } + opt->values = new_values; + break; + } + case coPercents: + { + ConfigOptionPercents * opt = this->option(key); + std::vector new_values; + + new_values.resize(filament_count); + for (int f_index = 0; f_index < filament_count; f_index++) + { + new_values[f_index] = opt->get_at(variant_index[f_index]); + } + opt->values = new_values; + break; + } + case coFloatsOrPercents: + { + ConfigOptionFloatsOrPercents * opt = this->option(key); + std::vector new_values; + + new_values.resize(filament_count); + for (int f_index = 0; f_index < filament_count; f_index++) + { + new_values[f_index] = opt->get_at(variant_index[f_index]); + } + opt->values = new_values; + break; + } + case coBools: + { + ConfigOptionBools * opt = this->option(key); + std::vector new_values; + + new_values.resize(filament_count); + for (int f_index = 0; f_index < filament_count; f_index++) + { + new_values[f_index] = opt->get_at(variant_index[f_index]); + } + opt->values = new_values; + break; + } + case coEnums: + { + ConfigOptionEnumsGeneric * opt = this->option(key); + std::vector new_values; + + new_values.resize(filament_count); + for (int f_index = 0; f_index < filament_count; f_index++) + { + new_values[f_index] = opt->get_at(variant_index[f_index]); + } + opt->values = new_values; + break; + } + default: + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key; + break; + } + } + } +} + + +void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfig& new_config, const t_config_option_keys& keys, const std::set& different_keys, + std::string extruder_id_name, std::string extruder_variant_name, std::set& key_set1, std::set& key_set2) +{ + std::vector cur_extruder_ids, target_extruder_ids, variant_index; + std::vector cur_extruder_variants, target_extruder_variants; + + if (!extruder_id_name.empty()) { + if (this->option(extruder_id_name)) + cur_extruder_ids = this->option(extruder_id_name)->values; + if (new_config.option(extruder_id_name)) + target_extruder_ids = new_config.option(extruder_id_name)->values; + } + if (this->option(extruder_variant_name)) + cur_extruder_variants = this->option(extruder_variant_name, true)->values; + if (new_config.option(extruder_variant_name)) + target_extruder_variants = new_config.option(extruder_variant_name, true)->values; + + int cur_variant_count = cur_extruder_variants.size(); + int target_variant_count = target_extruder_variants.size(); + + variant_index.resize(target_variant_count, -1); + if (cur_variant_count == 0) { + variant_index[0] = 0; + } + else if ((cur_extruder_ids.size() > 0) && cur_variant_count != cur_extruder_ids.size()){ + //should not happen + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" size of %1% = %2%, not equal to size of %3% = %4%") + %extruder_variant_name %cur_variant_count %extruder_id_name %cur_extruder_ids.size(); + } + else if ((target_extruder_ids.size() > 0) && target_variant_count != target_extruder_ids.size()){ + //should not happen + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" size of %1% = %2%, not equal to size of %3% = %4%") + %extruder_variant_name %target_variant_count %extruder_id_name %target_extruder_ids.size(); + } + else { + for (int i = 0; i < target_variant_count; i++) + { + for (int j = 0; j < cur_variant_count; j++) + { + if ((target_extruder_variants[i] == cur_extruder_variants[j]) + &&(target_extruder_ids.empty() || (target_extruder_ids[i] == cur_extruder_ids[j]))) + { + variant_index[i] = j; + break; + } + } + } + } + + for (auto& opt : keys) { + ConfigOption *opt_src = this->option(opt); + const ConfigOption *opt_target = new_config.option(opt); + if (opt_src && opt_target && (*opt_src != *opt_target)) { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" change key %1% from old_value %2% to inherit's value %3%") + %opt %(opt_src->serialize()) %(opt_target->serialize()); + if (different_keys.find(opt) == different_keys.end()) { + opt_src->set(opt_target); + } + else { + if (opt_target->is_scalar() + || ((key_set1.find(opt) == key_set1.end()) && (key_set2.empty() || (key_set2.find(opt) == key_set2.end())))) { + //nothing to do, keep the original one + } + else { + ConfigOptionVectorBase* opt_vec_src = static_cast(opt_src); + const ConfigOptionVectorBase* opt_vec_dest = static_cast(opt_target); + int stride = 1; + if (key_set2.find(opt) != key_set2.end()) + stride = 2; + opt_vec_src->set_with_restore(opt_vec_dest, variant_index, stride); + } + } + } + } + return; +} + +void DynamicPrintConfig::update_diff_values_to_child_config(DynamicPrintConfig& new_config, std::string extruder_id_name, std::string extruder_variant_name, std::set& key_set1, std::set& key_set2) +{ + std::vector cur_extruder_ids, target_extruder_ids, variant_index; + std::vector cur_extruder_variants, target_extruder_variants; + + if (!extruder_id_name.empty()) { + if (this->option(extruder_id_name)) + cur_extruder_ids = this->option(extruder_id_name)->values; + if (new_config.option(extruder_id_name)) + target_extruder_ids = new_config.option(extruder_id_name)->values; + } + if (this->option(extruder_variant_name)) + cur_extruder_variants = this->option(extruder_variant_name, true)->values; + if (new_config.option(extruder_variant_name)) + target_extruder_variants = new_config.option(extruder_variant_name, true)->values; + + int cur_variant_count = cur_extruder_variants.size(); + int target_variant_count = target_extruder_variants.size(); + + if (cur_variant_count > 0) + variant_index.resize(cur_variant_count, -1); + else + variant_index.resize(1, 0); + + if (target_variant_count == 0) { + variant_index[0] = 0; + } + else if ((cur_extruder_ids.size() > 0) && cur_variant_count != cur_extruder_ids.size()){ + //should not happen + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" size of %1% = %2%, not equal to size of %3% = %4%") + %extruder_variant_name %cur_variant_count %extruder_id_name %cur_extruder_ids.size(); + } + else if ((target_extruder_ids.size() > 0) && target_variant_count != target_extruder_ids.size()){ + //should not happen + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" size of %1% = %2%, not equal to size of %3% = %4%") + %extruder_variant_name %target_variant_count %extruder_id_name %target_extruder_ids.size(); + } + else { + for (int i = 0; i < cur_variant_count; i++) + { + for (int j = 0; j < target_variant_count; j++) + { + if ((cur_extruder_variants[i] == target_extruder_variants[j]) + &&(cur_extruder_ids.empty() || (cur_extruder_ids[i] == target_extruder_ids[j]))) + { + variant_index[i] = j; + break; + } + } + } + } + + const t_config_option_keys &keys = new_config.keys(); + for (auto& opt : keys) { + if ((opt == extruder_id_name) || (opt == extruder_variant_name)) + continue; + ConfigOption *opt_src = this->option(opt); + const ConfigOption *opt_target = new_config.option(opt); + if (opt_src && opt_target && (*opt_src != *opt_target)) { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" change key %1% from base_value %2% to child's value %3%") + %opt %(opt_src->serialize()) %(opt_target->serialize()); + if (opt_target->is_scalar() + || ((key_set1.find(opt) == key_set1.end()) && (key_set2.empty() || (key_set2.find(opt) == key_set2.end())))) { + //nothing to do, keep the original one + opt_src->set(opt_target); + } + else { + ConfigOptionVectorBase* opt_vec_src = static_cast(opt_src); + const ConfigOptionVectorBase* opt_vec_dest = static_cast(opt_target); + int stride = 1; + if (key_set2.find(opt) != key_set2.end()) + stride = 2; + opt_vec_src->set_only_diff(opt_vec_dest, variant_index, stride); + } + } + } + return; +} + +void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector variant_index, std::set& key_set1, int stride) +{ + if (variant_index.size() > 0) { + const t_config_option_keys &keys = dest_config.keys(); + for (auto& opt : keys) { + ConfigOption *opt_src = config.option(opt); + const ConfigOption *opt_dest = dest_config.option(opt); + if (opt_src && opt_dest && (*opt_src != *opt_dest)) { + if (opt_dest->is_scalar() || (key_set1.find(opt) == key_set1.end())) + opt_src->set(opt_dest); + else { + ConfigOptionVectorBase* opt_vec_src = static_cast(opt_src); + const ConfigOptionVectorBase* opt_vec_dest = static_cast(opt_dest); + opt_vec_src->set_to_index(opt_vec_dest, variant_index, stride); + } + } + } + } + else + config.apply(dest_config, true); +} + +void compute_filament_override_value(const std::string& opt_key, const ConfigOption *opt_old_machine, const ConfigOption *opt_new_machine, const ConfigOption *opt_new_filament, const DynamicPrintConfig& new_full_config, + t_config_option_keys& diff_keys, DynamicPrintConfig& filament_overrides, std::vector& f_maps) +{ + bool is_nil = opt_new_filament->is_nil(); + + // ugly code, for these params, we should ignore the value in filament params + ConfigOptionBoolsNullable opt_long_retraction_default; + if (opt_key == "long_retractions_when_cut" && new_full_config.option("enable_long_retraction_when_cut")->value != LongRectrationLevel::EnableFilament) { + auto ptr = dynamic_cast(opt_new_filament); + for (size_t idx = 0; idx < ptr->values.size(); ++idx) + opt_long_retraction_default.values.push_back(ptr->nil_value()); + opt_new_filament = &opt_long_retraction_default; + } + + ConfigOptionFloatsNullable opt_retraction_distance_default; + if (opt_key == "retraction_distances_when_cut" && new_full_config.option("enable_long_retraction_when_cut")->value != LongRectrationLevel::EnableFilament) { + auto ptr = dynamic_cast(opt_new_filament); + for (size_t idx = 0; idx < ptr->values.size(); ++idx) + opt_long_retraction_default.values.push_back(ptr->nil_value()); + opt_new_filament = &opt_retraction_distance_default; + } + + auto opt_copy = opt_new_machine->clone(); + opt_copy->apply_override(opt_new_filament, f_maps); + bool changed = *opt_old_machine != *opt_copy; + + if (changed) { + diff_keys.emplace_back(opt_key); + filament_overrides.set_key_value(opt_key, opt_copy); + } + else + delete opt_copy; } //QDS: pass map to recording all invalid valies @@ -6086,6 +8045,12 @@ CLIMiscConfigDef::CLIMiscConfigDef() def->tooltip = "Allow 3mf with newer version to be sliced"; def->cli_params = "option"; def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("allow_mix_temp", coBool); + def->label = "Allow filaments with high/low temperature to be printed together"; + def->tooltip = "Allow filaments with high/low temperature to be printed together"; + def->cli_params = "option"; + def->set_default_value(new ConfigOptionBool(false)); } const CLIActionsConfigDef cli_actions_config_def; @@ -6113,9 +8078,26 @@ static Points to_points(const std::vector &dpts) return pts; } -Points get_bed_shape(const DynamicPrintConfig &config) +Polygon get_shared_poly(const std::vector& extruder_polys) { - const auto *bed_shape_opt = config.opt("printable_area"); + Polygon result; + for (int index = 0; index < extruder_polys.size(); index++) + { + const Pointfs& extruder_area = extruder_polys[index]; + if (index == 0) + result.points = to_points(extruder_area); + else { + Polygon extruer_poly; + extruer_poly.points = to_points(extruder_area); + Polygons result_polygon = intersection(extruer_poly, result); + result = result_polygon[0]; + } + } + return result; +} +Points get_bed_shape(const DynamicPrintConfig &config, bool use_share) +{ + const ConfigOptionPoints *bed_shape_opt = config.opt("printable_area"); if (!bed_shape_opt) { // Here, it is certain that the bed shape is missing, so an infinite one @@ -6126,20 +8108,45 @@ Points get_bed_shape(const DynamicPrintConfig &config) return {}; } - return to_points(bed_shape_opt->values); + Polygon bed_poly; + if (use_share) { + const ConfigOptionPointsGroups *extruder_area_opt = config.opt("extruder_printable_area"); + if (extruder_area_opt && (extruder_area_opt->size() > 0)) { + const std::vector& extruder_areas = extruder_area_opt->values; + bed_poly = get_shared_poly(extruder_areas); + } + else + bed_poly.points = to_points(bed_shape_opt->values); + } + else + bed_poly.points = to_points(bed_shape_opt->values); + + return bed_poly.points; } -Points get_bed_shape(const PrintConfig &cfg) +Points get_bed_shape(const PrintConfig &cfg, bool use_share) { - return to_points(cfg.printable_area.values); + Polygon bed_poly; + if (use_share) { + const std::vector& extruder_areas = cfg.extruder_printable_area.values; + if (extruder_areas.size() > 0) { + bed_poly = get_shared_poly(extruder_areas); + } + else + bed_poly.points = to_points(cfg.printable_area.values); + } + else + bed_poly.points = to_points(cfg.printable_area.values); + + return bed_poly.points; } Points get_bed_shape(const SLAPrinterConfig &cfg) { return to_points(cfg.printable_area.values); } -Polygon get_bed_shape_with_excluded_area(const PrintConfig& cfg) +Polygon get_bed_shape_with_excluded_area(const PrintConfig& cfg, bool use_share) { Polygon bed_poly; - bed_poly.points = get_bed_shape(cfg); + bed_poly.points = get_bed_shape(cfg, use_share); Points excluse_area_points = to_points(cfg.bed_exclude_area.values); Polygons exclude_polys; diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 62e39b7..fcbdc6b 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -55,10 +55,16 @@ enum AuthorizationType { enum InfillPattern : int { ipConcentric, ipRectilinear, ipGrid, ipLine, ipCubic, ipTriangles, ipStars, ipGyroid, ipHoneycomb, ipAdaptiveCubic, ipMonotonic, ipMonotonicLine, ipAlignedRectilinear, ip3DHoneycomb, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipSupportCubic, ipSupportBase, ipConcentricInternal, - ipLightning, ipCrossHatch, + ipLightning, ipCrossHatch, ipZigZag, ipCrossZag,ipFloatingConcentric, ipCount, }; +enum EnsureVerticalThicknessLevel{ + evtDisabled, + evtPartial, + evtEnabled +}; + enum class IroningType { NoIroning, TopSurfaces, @@ -77,6 +83,12 @@ enum class WallInfillOrder { Count, }; +enum class BedTempFormula { + btfFirstFilament, + btfHighestTemp, + count, +}; + // QDS enum class WallSequence { InnerOuter, @@ -235,9 +247,16 @@ enum BedType { btCount }; +enum class ExtruderOnlyAreaType:unsigned char { + btNoArea= 0, + Engilish, + Chinese, + btAreaCount +}; + // QDS enum LayerSeq { - flsAuto, + flsAuto, flsCutomize }; @@ -257,6 +276,13 @@ static std::unordered_mapNozzleTypeEumnToStr = { {NozzleType::ntBrass, "brass"} }; +static std::unordered_mapNozzleTypeStrToEumn = { + {"undefine", NozzleType::ntUndefine}, + {"hardened_steel", NozzleType::ntHardenedSteel}, + {"stainless_steel", NozzleType::ntStainlessSteel}, + {"brass", NozzleType::ntBrass} +}; + // QDS enum PrinterStructure { psUndefine=0, @@ -278,9 +304,27 @@ enum ZHopType { // QDS enum ExtruderType { etDirectDrive = 0, - etBowden + etBowden, + etMaxExtruderType = etBowden }; +enum NozzleVolumeType { + nvtStandard = 0, + nvtHighFlow, + nvtMaxNozzleVolumeType = nvtHighFlow +}; + +enum FilamentMapMode { + fmmAutoForFlush, + fmmAutoForMatch, + fmmManual, + fmmDefault +}; + +extern std::string get_extruder_variant_string(ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type); + +std::string get_nozzle_volume_type_string(NozzleVolumeType nozzle_volume_type); + //w12 enum class GCodeThumbnailsFormat { PNG, Qidi @@ -354,6 +398,10 @@ static std::string get_bed_temp_1st_layer_key(const BedType type) return ""; } +// for parse extruder_ams_count +extern std::vector> get_extruder_ams_count(const std::vector &strs); +extern std::vector save_extruder_ams_count_to_string(const std::vector> &extruder_ams_count); + #define CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NAME) \ template<> const t_config_enum_names& ConfigOptionEnum::get_enum_names(); \ template<> const t_config_enum_values& ConfigOptionEnum::get_enum_values(); @@ -463,6 +511,7 @@ public: //return the changed param set t_config_option_keys normalize_fdm_2(int num_objects, int used_filaments = 0); + size_t get_parameter_size(const std::string& param_name, size_t extruder_nums); void set_num_extruders(unsigned int num_extruders); // QDS @@ -485,8 +534,38 @@ public: //QDS special case Support G/ Support W std::string get_filament_type(std::string &displayed_filament_type, int id = 0); - bool is_custom_defined(); + //QDS + bool is_using_different_extruders(); + bool support_different_extruders(int& extruder_count); + int get_index_for_extruder(int extruder_or_filament_id, std::string id_name, ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type, std::string variant_name, unsigned int stride = 1) const; + std::vector update_values_to_printer_extruders(DynamicPrintConfig& printer_config, std::set& key_set, std::string id_name, std::string variant_name, unsigned int stride = 1, unsigned int extruder_id = 0); + void update_values_to_printer_extruders_for_multiple_filaments(DynamicPrintConfig& printer_config, std::set& key_set, std::string id_name, std::string variant_name); + + void update_non_diff_values_to_base_config(DynamicPrintConfig& new_config, const t_config_option_keys& keys, const std::set& different_keys, std::string extruder_id_name, std::string extruder_variant_name, + std::set& key_set1, std::set& key_set2); + void update_diff_values_to_child_config(DynamicPrintConfig& new_config, std::string extruder_id_name, std::string extruder_variant_name, std::set& key_set1, std::set& key_set2); + + int update_values_from_single_to_multi(DynamicPrintConfig& multi_config, std::set& key_set, std::string id_name, std::string variant_name); + int update_values_from_multi_to_single(DynamicPrintConfig& single_config, std::set& key_set, std::string id_name, std::string variant_name, std::vector& extruder_variants); + + int update_values_from_single_to_multi_2(DynamicPrintConfig& multi_config, std::set& key_set); + int update_values_from_multi_to_single_2(std::set& key_set); + +public: + // query filament + std::string get_filament_vendor() const; + std::string get_filament_type() const; }; +extern std::set printer_extruder_options; +extern std::set print_options_with_variant; +extern std::set filament_options_with_variant; +extern std::set printer_options_with_variant_1; +extern std::set printer_options_with_variant_2; +extern std::set empty_options; + +extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector variant_index, std::set& key_set1, int stride = 1); +extern void compute_filament_override_value(const std::string& opt_key, const ConfigOption *opt_old_machine, const ConfigOption *opt_new_machine, const ConfigOption *opt_new_filament, const DynamicPrintConfig& new_full_config, + t_config_option_keys& diff_keys, DynamicPrintConfig& filament_overrides, std::vector& f_maps); void handle_legacy_sla(DynamicPrintConfig &config); @@ -755,13 +834,13 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionInt, support_interface_bottom_layers)) // Spacing between interface lines (the hatching distance). Set zero to get a solid interface. ((ConfigOptionFloat, support_interface_spacing)) - ((ConfigOptionFloat, support_interface_speed)) + ((ConfigOptionFloatsNullable, support_interface_speed)) ((ConfigOptionEnum, support_base_pattern)) ((ConfigOptionEnum, support_interface_pattern)) // Spacing between support material lines (the hatching distance). ((ConfigOptionFloat, support_base_pattern_spacing)) ((ConfigOptionFloat, support_expansion)) - ((ConfigOptionFloat, support_speed)) + ((ConfigOptionFloatsNullable, support_speed)) ((ConfigOptionEnum, support_style)) // QDS //((ConfigOptionBool, independent_support_layer_height)) @@ -772,6 +851,10 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, support_object_first_layer_gap)) ((ConfigOptionFloat, xy_hole_compensation)) ((ConfigOptionFloat, xy_contour_compensation)) + //QDS auto hole contour compensation + ((ConfigOptionBool, enable_circle_compensation)) + ((ConfigOptionFloat, circle_compensation_manual_offset)) + ((ConfigOptionBool, apply_scarf_seam_on_circles)) ((ConfigOptionBool, flush_into_objects)) // QDS ((ConfigOptionBool, flush_into_infill)) @@ -784,6 +867,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, tree_support_branch_diameter_angle)) ((ConfigOptionInt, tree_support_wall_count)) ((ConfigOptionBool, detect_narrow_internal_solid_infill)) + ((ConfigOptionBool, detect_floating_vertical_shell)) // ((ConfigOptionBool, adaptive_layer_height)) ((ConfigOptionFloat, support_bottom_interface_spacing)) ((ConfigOptionFloat, internal_bridge_support_thickness)) @@ -802,35 +886,49 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionPercent, wipe_speed)) ((ConfigOptionBool, role_base_wipe_speed)) ((ConfigOptionBool, precise_z_height)) // QDS + + ((ConfigOptionBool, interlocking_beam)) + ((ConfigOptionFloat,interlocking_beam_width)) + ((ConfigOptionFloat,interlocking_orientation)) + ((ConfigOptionInt, interlocking_beam_layer_count)) + ((ConfigOptionInt, interlocking_depth)) + ((ConfigOptionInt, interlocking_boundary_avoidance)) + ((ConfigOptionInt, scarf_angle_threshold)) + ) // This object is mapped to Perl as Slic3r::Config::PrintRegion. PRINT_CONFIG_CLASS_DEFINE( PrintRegionConfig, + ((ConfigOptionInts, print_extruder_id)) + ((ConfigOptionStrings, print_extruder_variant)) ((ConfigOptionInt, bottom_shell_layers)) ((ConfigOptionFloat, bottom_shell_thickness)) ((ConfigOptionFloat, bridge_angle)) ((ConfigOptionFloat, bridge_flow)) - ((ConfigOptionFloat, overhang_totally_speed)) //1.9.5 - ((ConfigOptionFloat, bridge_speed)) - ((ConfigOptionBool, ensure_vertical_shell_thickness)) + ((ConfigOptionFloatsNullable, overhang_totally_speed)) + ((ConfigOptionFloatsNullable, bridge_speed)) + ((ConfigOptionEnum, ensure_vertical_shell_thickness)) ((ConfigOptionEnum, top_surface_pattern)) ((ConfigOptionEnum, bottom_surface_pattern)) ((ConfigOptionEnum, internal_solid_infill_pattern)) ((ConfigOptionFloat, outer_wall_line_width)) - ((ConfigOptionFloat, outer_wall_speed)) + ((ConfigOptionFloatsNullable, outer_wall_speed)) ((ConfigOptionFloat, infill_direction)) + ((ConfigOptionBool, symmetric_infill_y_axis)) + ((ConfigOptionFloat, infill_shift_step)) + ((ConfigOptionFloat, infill_rotate_step)) ((ConfigOptionPercent, sparse_infill_density)) ((ConfigOptionEnum, sparse_infill_pattern)) ((ConfigOptionEnum, fuzzy_skin)) ((ConfigOptionFloat, fuzzy_skin_thickness)) ((ConfigOptionFloat, fuzzy_skin_point_distance)) - ((ConfigOptionFloat, gap_infill_speed)) + ((ConfigOptionFloatsNullable, gap_infill_speed)) ((ConfigOptionInt, sparse_infill_filament)) ((ConfigOptionFloat, sparse_infill_line_width)) ((ConfigOptionPercent, infill_wall_overlap)) - ((ConfigOptionFloat, sparse_infill_speed)) + ((ConfigOptionFloatsNullable, sparse_infill_speed)) //QDS ((ConfigOptionBool, infill_combination)) // Ironing options @@ -847,27 +945,30 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, smooth_coefficient)) //1.9.5 ((ConfigOptionInt, wall_filament)) ((ConfigOptionFloat, inner_wall_line_width)) - ((ConfigOptionFloat, inner_wall_speed)) + ((ConfigOptionFloatsNullable, inner_wall_speed)) // Total number of perimeters. ((ConfigOptionInt, wall_loops)) ((ConfigOptionFloat, minimum_sparse_infill_area)) ((ConfigOptionInt, solid_infill_filament)) ((ConfigOptionFloat, internal_solid_infill_line_width)) - ((ConfigOptionFloat, internal_solid_infill_speed)) + ((ConfigOptionFloatsNullable, internal_solid_infill_speed)) // Detect thin walls. ((ConfigOptionBool, detect_thin_wall)) ((ConfigOptionFloat, top_surface_line_width)) ((ConfigOptionInt, top_shell_layers)) ((ConfigOptionFloat, top_shell_thickness)) - ((ConfigOptionFloat, top_surface_speed)) - ((ConfigOptionFloatOrPercent, small_perimeter_speed)) - ((ConfigOptionFloat, small_perimeter_threshold)) + ((ConfigOptionFloatsNullable, top_surface_speed)) + ((ConfigOptionFloatsOrPercentsNullable, small_perimeter_speed)) + ((ConfigOptionFloatsNullable, small_perimeter_threshold)) + ((ConfigOptionFloatsOrPercentsNullable, vertical_shell_speed)) + ((ConfigOptionInt, top_color_penetration_layers)) + ((ConfigOptionInt, bottom_color_penetration_layers)) //QDS - ((ConfigOptionBool, enable_overhang_speed)) - ((ConfigOptionFloat, overhang_1_4_speed)) - ((ConfigOptionFloat, overhang_2_4_speed)) - ((ConfigOptionFloat, overhang_3_4_speed)) - ((ConfigOptionFloat, overhang_4_4_speed)) + ((ConfigOptionBoolsNullable, enable_overhang_speed)) + ((ConfigOptionFloatsNullable, overhang_1_4_speed)) + ((ConfigOptionFloatsNullable, overhang_2_4_speed)) + ((ConfigOptionFloatsNullable, overhang_3_4_speed)) + ((ConfigOptionFloatsNullable, overhang_4_4_speed)) ((ConfigOptionFloatOrPercent, sparse_infill_anchor)) ((ConfigOptionFloatOrPercent, sparse_infill_anchor_max)) //OrcaSlicer @@ -879,80 +980,99 @@ PRINT_CONFIG_CLASS_DEFINE( // Orca: seam slopes // ((ConfigOptionEnum, seam_slope_type)) ((ConfigOptionBool, seam_slope_conditional)) - ((ConfigOptionInt, scarf_angle_threshold)) + //((ConfigOptionInt, scarf_angle_threshold)) // ((ConfigOptionFloatOrPercent, seam_slope_start_height)) //((ConfigOptionFloatOrPercent, seam_slope_gap)) ((ConfigOptionBool, seam_slope_entire_loop)) // ((ConfigOptionFloat, seam_slope_min_length)) ((ConfigOptionInt, seam_slope_steps)) ((ConfigOptionBool, seam_slope_inner_walls)) - //w16 - ((ConfigOptionBool, resonance_avoidance)) - ((ConfigOptionFloat, min_resonance_avoidance_speed)) - ((ConfigOptionFloat, max_resonance_avoidance_speed)) + //w16 //y58 + ((ConfigOptionBools, resonance_avoidance)) + ((ConfigOptionFloatsNullable, min_resonance_avoidance_speed)) + ((ConfigOptionFloatsNullable, max_resonance_avoidance_speed)) ) PRINT_CONFIG_CLASS_DEFINE( MachineEnvelopeConfig, // M201 X... Y... Z... E... [mm/sec^2] - ((ConfigOptionFloats, machine_max_acceleration_x)) - ((ConfigOptionFloats, machine_max_acceleration_y)) - ((ConfigOptionFloats, machine_max_acceleration_z)) - ((ConfigOptionFloats, machine_max_acceleration_e)) + ((ConfigOptionFloatsNullable, machine_max_acceleration_x)) + ((ConfigOptionFloatsNullable, machine_max_acceleration_y)) + ((ConfigOptionFloatsNullable, machine_max_acceleration_z)) + ((ConfigOptionFloatsNullable, machine_max_acceleration_e)) // M203 X... Y... Z... E... [mm/sec] - ((ConfigOptionFloats, machine_max_speed_x)) - ((ConfigOptionFloats, machine_max_speed_y)) - ((ConfigOptionFloats, machine_max_speed_z)) - ((ConfigOptionFloats, machine_max_speed_e)) + ((ConfigOptionFloatsNullable, machine_max_speed_x)) + ((ConfigOptionFloatsNullable, machine_max_speed_y)) + ((ConfigOptionFloatsNullable, machine_max_speed_z)) + ((ConfigOptionFloatsNullable, machine_max_speed_e)) // M204 P... R... T...[mm/sec^2] - ((ConfigOptionFloats, machine_max_acceleration_extruding)) - ((ConfigOptionFloats, machine_max_acceleration_retracting)) - ((ConfigOptionFloats, machine_max_acceleration_travel)) + ((ConfigOptionFloatsNullable, machine_max_acceleration_extruding)) + ((ConfigOptionFloatsNullable, machine_max_acceleration_retracting)) + ((ConfigOptionFloatsNullable, machine_max_acceleration_travel)) // M205 X... Y... Z... E... [mm/sec] - ((ConfigOptionFloats, machine_max_jerk_x)) - ((ConfigOptionFloats, machine_max_jerk_y)) - ((ConfigOptionFloats, machine_max_jerk_z)) - ((ConfigOptionFloats, machine_max_jerk_e)) + ((ConfigOptionFloatsNullable, machine_max_jerk_x)) + ((ConfigOptionFloatsNullable, machine_max_jerk_y)) + ((ConfigOptionFloatsNullable, machine_max_jerk_z)) + ((ConfigOptionFloatsNullable, machine_max_jerk_e)) // M205 T... [mm/sec] - ((ConfigOptionFloats, machine_min_travel_rate)) + ((ConfigOptionFloatsNullable, machine_min_travel_rate)) // M205 S... [mm/sec] - ((ConfigOptionFloats, machine_min_extruding_rate)) + ((ConfigOptionFloatsNullable, machine_min_extruding_rate)) ) // This object is mapped to Perl as Slic3r::Config::GCode. PRINT_CONFIG_CLASS_DEFINE( GCodeConfig, - ((ConfigOptionString, before_layer_change_gcode)) - ((ConfigOptionString, printing_by_object_gcode)) - ((ConfigOptionFloats, deretraction_speed)) + ((ConfigOptionString, before_layer_change_gcode)) + ((ConfigOptionString, printing_by_object_gcode)) + ((ConfigOptionFloatsNullable, deretraction_speed)) //QDS ((ConfigOptionBool, enable_arc_fitting)) ((ConfigOptionString, machine_end_gcode)) ((ConfigOptionStrings, filament_end_gcode)) - ((ConfigOptionFloats, filament_flow_ratio)) + ((ConfigOptionFloatsNullable, filament_flow_ratio)) ((ConfigOptionBools, enable_pressure_advance)) ((ConfigOptionFloats, pressure_advance)) ((ConfigOptionFloats, filament_diameter)) + ((ConfigOptionInts, filament_adhesiveness_category)) ((ConfigOptionFloats, filament_density)) ((ConfigOptionStrings, filament_type)) ((ConfigOptionBools, filament_soluble)) + ((ConfigOptionStrings, filament_ids)) ((ConfigOptionBools, filament_is_support)) ((ConfigOptionEnumsGeneric, filament_scarf_seam_type)) ((ConfigOptionFloatsOrPercents, filament_scarf_height)) ((ConfigOptionFloatsOrPercents, filament_scarf_gap)) ((ConfigOptionFloats, filament_scarf_length)) + ((ConfigOptionFloats, filament_change_length)) ((ConfigOptionFloats, filament_cost)) + ((ConfigOptionFloats, impact_strength_z)) ((ConfigOptionString, filament_notes)) ((ConfigOptionStrings, default_filament_colour)) ((ConfigOptionInts, temperature_vitrification)) //QDS - ((ConfigOptionFloats, filament_max_volumetric_speed)) + ((ConfigOptionFloatsNullable, filament_ramming_travel_time)) //QDS + ((ConfigOptionIntsNullable, filament_pre_cooling_temperature))// QDS + ((ConfigOptionFloatsNullable, filament_max_volumetric_speed)) + ((ConfigOptionFloatsNullable, filament_ramming_volumetric_speed)) + ((ConfigOptionFloat, prime_tower_lift_speed)) + ((ConfigOptionFloat, prime_tower_lift_height)) ((ConfigOptionInts, required_nozzle_HRC)) + ((ConfigOptionEnum, filament_map_mode)) + ((ConfigOptionInts, filament_map)) + //((ConfigOptionInts, filament_extruder_id)) + ((ConfigOptionStrings, filament_extruder_variant)) ((ConfigOptionFloat, machine_load_filament_time)) ((ConfigOptionFloat, machine_unload_filament_time)) + ((ConfigOptionFloat, machine_switch_extruder_time)) + ((ConfigOptionBool, enable_pre_heating)) + ((ConfigOptionEnum, bed_temperature_formula)) + ((ConfigOptionInts, physical_extruder_map)) + ((ConfigOptionFloatsNullable, hotend_cooling_rate)) + ((ConfigOptionFloatsNullable, hotend_heating_rate)) ((ConfigOptionFloats, filament_minimal_purge_on_wipe_tower)) // QDS ((ConfigOptionBool, scan_first_layer)) @@ -968,45 +1088,54 @@ PRINT_CONFIG_CLASS_DEFINE( // ((ConfigOptionFloat, max_volumetric_extrusion_rate_slope_positive)) // ((ConfigOptionFloat, max_volumetric_extrusion_rate_slope_negative)) //#endif - ((ConfigOptionPercents, retract_before_wipe)) - ((ConfigOptionFloats, retraction_length)) - ((ConfigOptionFloats, retract_length_toolchange)) + ((ConfigOptionPercentsNullable, retract_before_wipe)) + ((ConfigOptionFloatsNullable, retraction_length)) + ((ConfigOptionFloatsNullable, retract_length_toolchange)) ((ConfigOptionInt, enable_long_retraction_when_cut)) - ((ConfigOptionFloats, retraction_distances_when_cut)) - ((ConfigOptionBools, long_retractions_when_cut)) - ((ConfigOptionFloats, z_hop)) + ((ConfigOptionFloatsNullable, retraction_distances_when_cut)) + ((ConfigOptionBoolsNullable, long_retractions_when_cut)) + ((ConfigOptionFloatsNullable, z_hop)) // QDS - ((ConfigOptionEnumsGeneric, z_hop_types)) - ((ConfigOptionFloats, retract_restart_extra)) - ((ConfigOptionFloats, retract_restart_extra_toolchange)) - ((ConfigOptionFloats, retraction_speed)) - ((ConfigOptionFloats, retract_lift_above)) - ((ConfigOptionFloats, retract_lift_below)) + ((ConfigOptionEnumsGenericNullable,z_hop_types)) + ((ConfigOptionFloatsNullable, retract_restart_extra)) + ((ConfigOptionFloatsNullable, retract_restart_extra_toolchange)) + ((ConfigOptionFloatsNullable, retraction_speed)) + ((ConfigOptionFloatsNullable, retract_lift_above)) + ((ConfigOptionFloatsNullable, retract_lift_below)) ((ConfigOptionString, machine_start_gcode)) ((ConfigOptionStrings, filament_start_gcode)) ((ConfigOptionBool, single_extruder_multi_material)) ((ConfigOptionBool, wipe_tower_no_sparse_layers)) ((ConfigOptionString, change_filament_gcode)) - ((ConfigOptionFloat, travel_speed)) - ((ConfigOptionFloat, travel_speed_z)) + ((ConfigOptionFloatsNullable, travel_speed)) + ((ConfigOptionFloatsNullable, travel_speed_z)) ((ConfigOptionBool, use_relative_e_distances)) ((ConfigOptionBool, use_firmware_retraction)) ((ConfigOptionBool, silent_mode)) ((ConfigOptionString, machine_pause_gcode)) ((ConfigOptionString, template_custom_gcode)) //QDS - ((ConfigOptionEnum, nozzle_type)) + ((ConfigOptionEnumsGenericNullable,nozzle_type)) ((ConfigOptionEnum,printer_structure)) ((ConfigOptionBool, auxiliary_fan)) ((ConfigOptionBool, support_chamber_temp_control)) + //y58 + ((ConfigOptionBool, support_box_temp_control)) ((ConfigOptionBool, support_air_filtration)) ((ConfigOptionBool, accel_to_decel_enable)) ((ConfigOptionPercent, accel_to_decel_factor)) ((ConfigOptionEnumsGeneric, extruder_type)) + ((ConfigOptionEnumsGeneric, nozzle_volume_type)) + ((ConfigOptionStrings, extruder_ams_count)) + ((ConfigOptionInts, printer_extruder_id)) + ((ConfigOptionInt, master_extruder_id)) + ((ConfigOptionStrings, printer_extruder_variant)) //Orca ((ConfigOptionBool, has_scarf_joint_seam)) //w34 ((ConfigOptionBool, support_multi_bed_types)) + //y60 + ((ConfigOptionBool, is_support_3mf)) ) // This object is mapped to Perl as Slic3r::Config::Print. @@ -1019,12 +1148,15 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( //w13 ((ConfigOptionInts, additional_cooling_fan_speed_unseal)) ((ConfigOptionBool, reduce_crossing_wall)) + ((ConfigOptionBool, z_direction_outwall_speed_continuous)) ((ConfigOptionFloatOrPercent, max_travel_detour_distance)) ((ConfigOptionPoints, printable_area)) + ((ConfigOptionPointsGroups, extruder_printable_area)) //QDS: add bed_exclude_area ((ConfigOptionPoints, bed_exclude_area)) ((ConfigOptionPoints, head_wrap_detect_zone)) // QDS + ((ConfigOptionStrings, unprintable_filament_types)) ((ConfigOptionString, bed_custom_texture)) ((ConfigOptionString, bed_custom_model)) ((ConfigOptionEnum, curr_bed_type)) @@ -1040,6 +1172,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionInts, textured_plate_temp_initial_layer)) ((ConfigOptionBools, enable_overhang_bridge_fan)) ((ConfigOptionInts, overhang_fan_speed)) + ((ConfigOptionFloats, pre_start_fan_time)) ((ConfigOptionEnumsGeneric, overhang_fan_threshold)) ((ConfigOptionEnumsGeneric, overhang_threshold_participating_cooling)) ((ConfigOptionEnum,print_sequence)) @@ -1047,9 +1180,11 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionInts, other_layers_print_sequence)) ((ConfigOptionInt, other_layers_print_sequence_nums)) ((ConfigOptionBools, slow_down_for_layer_cooling)) - ((ConfigOptionFloat, default_acceleration)) - ((ConfigOptionFloat, inner_wall_acceleration)) - ((ConfigOptionFloatOrPercent, sparse_infill_acceleration)) + ((ConfigOptionFloatsNullable, default_acceleration)) + ((ConfigOptionFloatsNullable, travel_acceleration)) + ((ConfigOptionFloatsNullable, initial_layer_travel_acceleration)) + ((ConfigOptionFloatsNullable, inner_wall_acceleration)) + ((ConfigOptionFloatsOrPercentsNullable, sparse_infill_acceleration)) ((ConfigOptionBools, activate_air_filtration)) ((ConfigOptionInts, during_print_exhaust_fan_speed)) ((ConfigOptionInts, complete_print_exhaust_fan_speed)) @@ -1065,25 +1200,26 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionBools, reduce_fan_stop_start_freq)) ((ConfigOptionInts, fan_cooling_layer_time)) ((ConfigOptionStrings, filament_colour)) - ((ConfigOptionFloat, top_surface_acceleration)) - ((ConfigOptionFloat, outer_wall_acceleration)) - ((ConfigOptionFloat, initial_layer_acceleration)) + ((ConfigOptionFloatsNullable, top_surface_acceleration)) + ((ConfigOptionFloatsNullable, outer_wall_acceleration)) + ((ConfigOptionFloatsNullable, initial_layer_acceleration)) ((ConfigOptionFloat, initial_layer_line_width)) ((ConfigOptionFloat, initial_layer_print_height)) - ((ConfigOptionFloat, initial_layer_speed)) + ((ConfigOptionFloatsNullable, initial_layer_speed)) //QDS - ((ConfigOptionFloat, initial_layer_infill_speed)) - ((ConfigOptionInts, nozzle_temperature_initial_layer)) + ((ConfigOptionFloatsNullable, initial_layer_infill_speed)) + ((ConfigOptionIntsNullable, nozzle_temperature_initial_layer)) ((ConfigOptionInts, full_fan_speed_layer)) ((ConfigOptionInts, fan_max_speed)) - ((ConfigOptionFloats, max_layer_height)) + ((ConfigOptionFloatsNullable, max_layer_height)) ((ConfigOptionInts, fan_min_speed)) - ((ConfigOptionFloats, min_layer_height)) + ((ConfigOptionFloatsNullable, min_layer_height)) ((ConfigOptionString, printer_notes)) ((ConfigOptionFloat, printable_height)) + ((ConfigOptionFloatsNullable, extruder_printable_height)) ((ConfigOptionPoint, best_object_pos)) ((ConfigOptionFloats, slow_down_min_speed)) - ((ConfigOptionFloats, nozzle_diameter)) + ((ConfigOptionFloatsNullable, nozzle_diameter)) ((ConfigOptionBool, reduce_infill_retraction)) ((ConfigOptionBool, ooze_prevention)) ((ConfigOptionString, filename_format)) @@ -1091,8 +1227,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionString, printer_model)) ((ConfigOptionString, process_notes)) ((ConfigOptionFloat, resolution)) - ((ConfigOptionFloats, retraction_minimum_travel)) - ((ConfigOptionBools, retract_when_changing_layer)) + ((ConfigOptionFloatsNullable, retraction_minimum_travel)) + ((ConfigOptionBoolsNullable, retract_when_changing_layer)) ((ConfigOptionFloat, skirt_distance)) ((ConfigOptionInt, skirt_height)) ((ConfigOptionInt, skirt_loops)) @@ -1101,14 +1237,19 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionBool, spiral_mode_smooth)) ((ConfigOptionFloatOrPercent, spiral_mode_max_xy_smoothing)) ((ConfigOptionInt, standby_temperature_delta)) - ((ConfigOptionInts, nozzle_temperature)) + ((ConfigOptionIntsNullable, nozzle_temperature)) ((ConfigOptionInts, chamber_temperatures)) - ((ConfigOptionBools, wipe)) + ((ConfigOptionBoolsNullable, wipe)) // QDS ((ConfigOptionInts, nozzle_temperature_range_low)) ((ConfigOptionInts, nozzle_temperature_range_high)) - ((ConfigOptionFloats, wipe_distance)) + //y58 + ((ConfigOptionInts, box_temperature)) + ((ConfigOptionInts, box_temperature_range_low)) + ((ConfigOptionInts, box_temperature_range_high)) + ((ConfigOptionFloatsNullable, wipe_distance)) ((ConfigOptionBool, enable_prime_tower)) + ((ConfigOptionBool, prime_tower_enable_framework)) // QDS: change wipe_tower_x and wipe_tower_y data type to floats to add partplate logic ((ConfigOptionFloats, wipe_tower_x)) ((ConfigOptionFloats, wipe_tower_y)) @@ -1116,18 +1257,24 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloat, wipe_tower_per_color_wipe)) ((ConfigOptionFloat, wipe_tower_rotation_angle)) ((ConfigOptionFloat, prime_tower_brim_width)) + ((ConfigOptionFloat, prime_tower_max_speed)) + ((ConfigOptionFloat, prime_tower_extra_rib_length)) + ((ConfigOptionFloat, prime_tower_rib_width)) + ((ConfigOptionPercent, prime_tower_infill_gap)) + ((ConfigOptionBool, prime_tower_skip_points)) + ((ConfigOptionBool, prime_tower_rib_wall)) + ((ConfigOptionBool, prime_tower_fillet_wall)) //((ConfigOptionFloat, wipe_tower_bridging)) ((ConfigOptionFloats, flush_volumes_matrix)) ((ConfigOptionFloats, flush_volumes_vector)) // QDS: wipe tower is only used for priming - ((ConfigOptionFloat, prime_volume)) - ((ConfigOptionFloat, flush_multiplier)) + ((ConfigOptionFloats, flush_multiplier)) //((ConfigOptionFloat, z_offset)) // QDS: project filaments ((ConfigOptionFloats, filament_colour_new)) // QDS: not in any preset, calculated before slicing ((ConfigOptionBool, has_prime_tower)) - ((ConfigOptionFloat, nozzle_volume)) + ((ConfigOptionFloatsNullable, nozzle_volume)) ((ConfigOptionPoints, start_end_points)) ((ConfigOptionEnum, timelapse_type)) ((ConfigOptionFloat, default_jerk)) @@ -1146,7 +1293,21 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionBool, seal)) //w14 ((ConfigOptionBools, dont_slow_down_outer_wall)) -) + ((ConfigOptionFloats, grab_length)) + //QDsS + ((ConfigOptionFloats, circle_compensation_speed)) + ((ConfigOptionFloats, diameter_limit)) + ((ConfigOptionFloats, counter_coef_1)) + ((ConfigOptionFloats, counter_coef_2)) + ((ConfigOptionFloats, counter_coef_3)) + ((ConfigOptionFloats, hole_coef_1)) + ((ConfigOptionFloats, hole_coef_2)) + ((ConfigOptionFloats, hole_coef_3)) + ((ConfigOptionFloats, counter_limit_min)) + ((ConfigOptionFloats, counter_limit_max)) + ((ConfigOptionFloats, hole_limit_min)) + ((ConfigOptionFloats, hole_limit_max)) + ((ConfigOptionFloats, filament_prime_volume))) // This object is mapped to Perl as Slic3r::Config::Full. PRINT_CONFIG_CLASS_DERIVED_DEFINE0( @@ -1440,10 +1601,11 @@ private: static PrintAndCLIConfigDef s_def; }; -Points get_bed_shape(const DynamicPrintConfig &cfg); -Points get_bed_shape(const PrintConfig &cfg); +Polygon get_shared_poly(const std::vector& extruder_polys); +Points get_bed_shape(const DynamicPrintConfig &cfg, bool use_share = true); +Points get_bed_shape(const PrintConfig &cfg, bool use_share = false); Points get_bed_shape(const SLAPrinterConfig &cfg); -Slic3r::Polygon get_bed_shape_with_excluded_area(const PrintConfig& cfg); +Slic3r::Polygon get_bed_shape_with_excluded_area(const PrintConfig& cfg, bool use_share = false); bool has_skirt(const DynamicPrintConfig& cfg); float get_real_skirt_dist(const DynamicPrintConfig& cfg); @@ -1538,6 +1700,35 @@ private: static uint64_t s_last_timestamp; }; +// const std::vector &fv_matrix: origin matrix from json +// size_t extruder_id: -1 means single-nozzle for old file, 0 means the 1st extruder, 1 means the 2nd extruder +template +static std::vector get_flush_volumes_matrix(const std::vector &fv_matrix, size_t extruder_id = -1, size_t nozzle_nums = 1) +{ + if (extruder_id != -1 && nozzle_nums != 1) { + return std::vector(fv_matrix.begin() + size_t(fv_matrix.size() / nozzle_nums * extruder_id + EPSILON), + fv_matrix.begin() + size_t(fv_matrix.size() / nozzle_nums * (extruder_id + 1) + EPSILON)); + } + return fv_matrix; +} + +// std::vector &out_matrix: +// const std::vector &fv_matrix: the matrix of one nozzle +// size_t extruder_id: -1 means single-nozzle for old file, 0 means the 1st extruder, 1 means the 2nd extruder +template +static void set_flush_volumes_matrix(std::vector &out_matrix, const std::vector &fv_matrix, size_t extruder_id = -1, size_t nozzle_nums = 1) +{ + bool is_multi_extruder = false; + if (extruder_id != -1 && nozzle_nums != 1) { + std::copy(fv_matrix.begin(), fv_matrix.end(), out_matrix.begin() + size_t(out_matrix.size() / nozzle_nums * extruder_id + EPSILON)); + } + else { + out_matrix = std::vector(fv_matrix.begin(), fv_matrix.end()); + } +} + +size_t get_extruder_index(const GCodeConfig& config, unsigned int filament_id); + } // namespace Slic3r // Serialization through the Cereal library diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 6ce0403..c2fd911 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -28,6 +28,7 @@ #include #include +#include #include @@ -54,6 +55,8 @@ using namespace std::literals; #include #endif +#define USE_TBB_IN_INFILL 1 + namespace Slic3r { // Constructor is called from the main thread, therefore all Model / ModelObject / ModelIntance data are valid. @@ -129,6 +132,189 @@ std::vector> PrintObject::all_regions( return out; } +void PrintObject::merge_layer_node(const size_t layer_id, int &max_merged_id, std::map>> &node_record) +{ + Layer *this_layer = m_layers[layer_id]; + std::vector &loop_nodes = this_layer->loop_nodes; + for (size_t idx = 0; idx < loop_nodes.size(); ++idx) { + //new cool node + if (loop_nodes[idx].lower_node_id.empty()) { + max_merged_id++; + loop_nodes[idx].merged_id = max_merged_id; + std::vector> node_pos; + node_pos.emplace_back(layer_id, idx); + node_record.emplace(max_merged_id, node_pos); + continue; + } + + //it should finds key in map + if (loop_nodes[idx].lower_node_id.size() == 1) { + loop_nodes[idx].merged_id = m_layers[layer_id - 1]->loop_nodes[loop_nodes[idx].lower_node_id.front()].merged_id; + node_record[loop_nodes[idx].merged_id].emplace_back(layer_id, idx); + continue; + } + + //min index + int min_merged_id = -1; + std::vector appear_id; + for (size_t lower_idx = 0; lower_idx < loop_nodes[idx].lower_node_id.size(); ++lower_idx) { + int id = m_layers[layer_id - 1]->loop_nodes[loop_nodes[idx].lower_node_id[lower_idx]].merged_id; + if (min_merged_id == -1 || min_merged_id > id) + min_merged_id = id; + appear_id.push_back(id); + } + + loop_nodes[idx].merged_id = min_merged_id; + node_record[min_merged_id].emplace_back(layer_id, idx); + + //update other node merged id + for (size_t appear_node_idx = 0; appear_node_idx < appear_id.size(); ++appear_node_idx) { + if (appear_id[appear_node_idx] == min_merged_id) + continue; + + auto it = node_record.find(appear_id[appear_node_idx]); + //protect + if (it == node_record.end()) + continue; + + std::vector> &appear_node_pos = it->second; + + for (size_t node_idx = 0; node_idx < appear_node_pos.size(); ++node_idx) { + int node_layer = appear_node_pos[node_idx].first; + int node_pos = appear_node_pos[node_idx].second; + + LoopNode &node = m_layers[node_layer]->loop_nodes[node_pos]; + + node.merged_id = min_merged_id; + node_record[min_merged_id].emplace_back(node_layer, node_pos); + } + node_record.erase(it); + } + } +} + +std::vector> PrintObject::detect_extruder_geometric_unprintables() const +{ + int extruder_size = m_print->config().nozzle_diameter.size(); + if(extruder_size == 1) + return std::vector>(1, std::set()); + + std::vector> geometric_unprintables(extruder_size); // the container to return + + std::vector printable_height_per_extruder = m_print->config().extruder_printable_height.values; + assert(printable_height_per_extruder.size() == extruder_size); + + // check unprintable filaments caused by printable height limit + for (size_t extruder_id = 0; extruder_id < printable_height_per_extruder.size(); ++extruder_id) { + double printable_height = printable_height_per_extruder[extruder_id]; + for (size_t layer_idx = 0; layer_idx < m_layers.size(); ++layer_idx) { + auto layer = m_layers[layer_idx]; + if (layer->print_z <= printable_height) + continue; + for (auto layerm : layer->regions()) { + auto region = layerm->region(); + int wall_filament = region.config().wall_filament; + int solid_infill_filament = region.config().solid_infill_filament; + int sparse_infill_filament = region.config().sparse_infill_filament; + + if (!layerm->fills.entities.empty()) { + if (solid_infill_filament > 0) + geometric_unprintables[extruder_id].insert(solid_infill_filament - 1); + if (sparse_infill_filament > 0) + geometric_unprintables[extruder_id].insert(sparse_infill_filament - 1); + } + if (!layerm->perimeters.entities.empty() && wall_filament > 0) + geometric_unprintables[extruder_id].insert(wall_filament - 1); + } + } + } + + std::vector> tbb_geometric_unprintables(extruder_size); // the container used in tbb + + std::vector unprintable_area_in_obj_coord = m_print->get_extruder_unprintable_polygons(); + std::vector unprintable_area_bbox; + + // transform the unprintable areas to obj coord is cheaper than thransform obj into world coord + for (auto& polys : unprintable_area_in_obj_coord) { + for (auto& poly : polys) { + poly.translate(-m_instances.front().shift_without_plate_offset()); + } + unprintable_area_bbox.emplace_back(get_extents(polys)); + } + + // check unprintbale filaments caused by printable area limit + tbb::parallel_for(tbb::blocked_range(0, m_layers.size()), + [this, &tbb_geometric_unprintables, &unprintable_area_in_obj_coord, &unprintable_area_bbox](const tbb::blocked_range& range) { + for (int j = range.begin(); j < range.end(); ++j) { + auto layer = m_layers[j]; + for (auto layerm : layer->regions()) { + const auto& region = layerm->region(); + int wall_filament = region.config().wall_filament; + int solid_infill_filament = region.config().solid_infill_filament; + int sparse_infill_filament = region.config().sparse_infill_filament; + std::optional fill_expolys; + BoundingBox fill_bbox; + std::optional wall_expolys; + BoundingBox wall_bbox; + + for (size_t idx = 0; idx < unprintable_area_in_obj_coord.size(); ++idx) { + bool do_infill_filament_detect = (solid_infill_filament > 0 && tbb_geometric_unprintables[idx].count(solid_infill_filament - 1) == 0) || + (sparse_infill_filament > 0 && tbb_geometric_unprintables[idx].count(sparse_infill_filament-1) == 0); + + bool infill_unprintable = !layerm->fills.entities.empty() && + ((solid_infill_filament > 0 && tbb_geometric_unprintables[idx].count(solid_infill_filament - 1) > 0) || + (sparse_infill_filament > 0 && tbb_geometric_unprintables[idx].count(sparse_infill_filament - 1) > 0)); + + if (!layerm->fills.entities.empty() && do_infill_filament_detect) { + if (!fill_expolys) { + fill_expolys = layerm->fill_expolygons; + fill_bbox = get_extents(*fill_expolys); + } + if (fill_bbox.overlap(unprintable_area_bbox[idx]) && + !intersection(*fill_expolys, unprintable_area_in_obj_coord[idx]).empty()) { + if (solid_infill_filament > 0) + tbb_geometric_unprintables[idx].insert(solid_infill_filament - 1); + if (sparse_infill_filament > 0) + tbb_geometric_unprintables[idx].insert(sparse_infill_filament - 1); + infill_unprintable = true; + } + } + + bool do_wall_filament_detect = wall_filament > 0 && tbb_geometric_unprintables[idx].count(wall_filament - 1) == 0; + if (!layerm->perimeters.entities.empty() && do_wall_filament_detect) { + // if infill is unprintable, no need to check wall since wall contour surrounds infill contour + if (infill_unprintable) { + tbb_geometric_unprintables[idx].insert(wall_filament - 1); + continue; + } + + if (!wall_expolys) { + if (!fill_expolys) { + fill_expolys = layerm->fill_expolygons; + fill_bbox = get_extents(*fill_expolys); + } + wall_expolys = diff_ex(layerm->raw_slices, *fill_expolys); + wall_bbox = get_extents(*wall_expolys); + } + + if (wall_bbox.overlap(unprintable_area_bbox[idx]) && + !intersection(*wall_expolys, unprintable_area_in_obj_coord[idx]).empty()) { + tbb_geometric_unprintables[idx].insert(wall_filament - 1); + } + } + } + } + } + }); + + // add the elems in tbb container to final contianer + for (size_t idx = 0; idx < extruder_size; ++idx) { + geometric_unprintables[idx].insert(tbb_geometric_unprintables[idx].begin(), tbb_geometric_unprintables[idx].end()); + } + + return geometric_unprintables; +} + // 1) Merges typed region slices into stInternal type. // 2) Increases an "extra perimeters" counter at region slices where needed. // 3) Generates perimeters, gap fills and fill regions (fill regions of type stInternal). @@ -223,6 +409,19 @@ void PrintObject::make_perimeters() BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - end"; } +#if 0 + { // for debug + for (size_t layer_idx = 0; layer_idx < m_layers.size(); ++layer_idx) { + auto regions = m_layers[layer_idx]->regions(); + for (size_t region_idx = 0; region_idx < regions.size(); ++region_idx) { + LayerRegion *layer_region = regions[region_idx]; + std::string name = "before_make_perimeter_layer-" + std::to_string(layer_idx) + "-region-" + std::to_string(region_idx) + ".svg"; + layer_region->slices.export_to_svg(debug_out_path(name.c_str()).c_str(), true); + } + } + } +#endif + BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), @@ -236,6 +435,49 @@ void PrintObject::make_perimeters() m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end"; + if (this->m_print->m_config.z_direction_outwall_speed_continuous) { + // QDS: get continuity of nodes + BOOST_LOG_TRIVIAL(debug) << "Calculating perimeters connection in parallel - start"; + tbb::parallel_for(tbb::blocked_range(0, m_layers.size()), [this](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + m_print->throw_if_canceled(); + if (layer_idx > 1) { + Layer &prev_layer = *m_layers[layer_idx - 1]; + m_layers[layer_idx]->calculate_perimeter_continuity(m_layers[layer_idx - 1]->loop_nodes); + } + } + }); + + m_print->throw_if_canceled(); + BOOST_LOG_TRIVIAL(debug) << "Calculating perimeters connection in parallel - end"; + + BOOST_LOG_TRIVIAL(debug) << "Calculating cooling nodes - start"; + + int max_merged_id = -1; + std::map>> node_record; + for (size_t layer_idx = 1; layer_idx < m_layers.size(); ++layer_idx) { + m_print->throw_if_canceled(); + merge_layer_node(layer_idx, max_merged_id, node_record); + } + m_print->throw_if_canceled(); + BOOST_LOG_TRIVIAL(debug) << "Calculating cooling nodes - end"; + + + //write merged node to each perimeter + BOOST_LOG_TRIVIAL(debug) << "Recrod cooling_node id for each extrusion in parallel - start"; + tbb::parallel_for(tbb::blocked_range(0, m_layers.size()), [this](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + m_print->throw_if_canceled(); + if (layer_idx > 1) { + Layer &prev_layer = *m_layers[layer_idx - 1]; + m_layers[layer_idx]->recrod_cooling_node_for_each_extrusion(); + } + } + }); + + m_print->throw_if_canceled(); + BOOST_LOG_TRIVIAL(debug) << "Recrod cooling_node id for each extrusion in parallel - end"; + } this->set_done(posPerimeters); } @@ -317,6 +559,8 @@ void PrintObject::prepare_infill() } // for each region #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + if (m_config.interlocking_beam.value) + discover_shell_for_perimeters(); // Only active if config->infill_only_where_needed. This step trims the sparse infill, // so it acts as an internal support. It maintains all other infill types intact. @@ -374,7 +618,7 @@ void PrintObject::infill() const auto& adaptive_fill_octree = this->m_adaptive_fill_octrees.first; const auto& support_fill_octree = this->m_adaptive_fill_octrees.second; - BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start"; + //BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), [this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree](const tbb::blocked_range& range) { @@ -710,17 +954,18 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "only_one_wall_first_layer" || opt_key == "initial_layer_line_width" || opt_key == "inner_wall_line_width" - || opt_key == "infill_wall_overlap") { + || opt_key == "infill_wall_overlap" + || opt_key == "apply_scarf_seam_on_circles") { steps.emplace_back(posPerimeters); } else if (opt_key == "gap_infill_speed" || opt_key == "filter_out_gap_fill") { // Return true if gap-fill speed has changed from zero value to non-zero or from non-zero value to zero. auto is_gap_fill_changed_state_due_to_speed = [&opt_key, &old_config, &new_config]() -> bool { if (opt_key == "gap_infill_speed") { - const auto *old_gap_fill_speed = old_config.option(opt_key); - const auto *new_gap_fill_speed = new_config.option(opt_key); + const auto *old_gap_fill_speed = old_config.option(opt_key); + const auto *new_gap_fill_speed = new_config.option(opt_key); assert(old_gap_fill_speed && new_gap_fill_speed); - return (old_gap_fill_speed->value > 0.f && new_gap_fill_speed->value == 0.f) || - (old_gap_fill_speed->value == 0.f && new_gap_fill_speed->value > 0.f); + return (old_gap_fill_speed->values.size() != new_gap_fill_speed->values.size()) + || (old_gap_fill_speed->values != new_gap_fill_speed->values); } return false; }; @@ -738,7 +983,13 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "raft_layers" || opt_key == "raft_contact_distance" || opt_key == "slice_closing_radius" - || opt_key == "slicing_mode") { + || opt_key == "slicing_mode" + || opt_key == "interlocking_beam" + || opt_key == "interlocking_orientation" + || opt_key == "interlocking_beam_layer_count" + || opt_key == "interlocking_depth" + || opt_key == "interlocking_boundary_avoidance" + || opt_key == "interlocking_beam_width") { steps.emplace_back(posSlice); } else if ( opt_key == "elefant_foot_compensation" @@ -795,7 +1046,9 @@ bool PrintObject::invalidate_state_by_config_options( steps.emplace_back(posSupportMaterial); } else if ( opt_key == "bottom_shell_layers" - || opt_key == "top_shell_layers") { + || opt_key == "top_shell_layers" + || opt_key == "top_color_penetration_layers" + || opt_key == "bottom_color_penetration_layers") { steps.emplace_back(posSlice); #if (0) @@ -838,9 +1091,13 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "sparse_infill_anchor" || opt_key == "sparse_infill_anchor_max" || opt_key == "top_surface_line_width" - || opt_key == "initial_layer_line_width") { + || opt_key == "initial_layer_line_width" + || opt_key == "detect_floating_vertical_shell") { steps.emplace_back(posInfill); - } else if (opt_key == "sparse_infill_pattern") { + } else if (opt_key == "sparse_infill_pattern" + || opt_key == "symmetric_infill_y_axis" + || opt_key == "infill_shift_step" + || opt_key == "infill_rotate_step") { steps.emplace_back(posPrepareInfill); } else if (opt_key == "sparse_infill_density") { // One likely wants to reslice only when switching between zero infill to simulate boolean difference (subtracting volumes), @@ -914,7 +1171,8 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "sparse_infill_speed" || opt_key == "inner_wall_speed" || opt_key == "internal_solid_infill_speed" - || opt_key == "top_surface_speed") { + || opt_key == "top_surface_speed" + || opt_key == "vertical_shell_speed") { invalidated |= m_print->invalidate_step(psGCodeExport); } else if ( opt_key == "flush_into_infill" @@ -1011,6 +1269,7 @@ void PrintObject::detect_surfaces_type() if (interface_shells) surfaces_new.assign(num_layers, Surfaces()); + // interface_shell 启用与否,决定着是否区分不同材料。开启后,不同材料间的接触面都会被识别为顶面、底面 tbb::parallel_for( tbb::blocked_range(0, spiral_mode ? @@ -1072,7 +1331,7 @@ void PrintObject::detect_surfaces_type() surfaces_append( bottom, opening_ex( - diff_ex(layerm->slices.surfaces, lower_layer->lslices, ApplySafetyOffset::Yes), + diff_ex(layerm->slices.surfaces, lower_layer->lslices, ApplySafetyOffset::Yes),//完全悬空 offset), surface_type_bottom_other); // if user requested internal shells, we need to identify surfaces @@ -1084,8 +1343,8 @@ void PrintObject::detect_surfaces_type() bottom, opening_ex( diff_ex( - intersection(layerm->slices.surfaces, lower_layer->lslices), // supported - lower_layer->m_regions[region_id]->slices.surfaces, + intersection(layerm->slices.surfaces, lower_layer->lslices), // 先扣掉完全悬空 + lower_layer->m_regions[region_id]->slices.surfaces,//再扣掉同材料的区域 ApplySafetyOffset::Yes), offset), stBottom); @@ -1105,9 +1364,6 @@ void PrintObject::detect_surfaces_type() // and top surfaces; let's do an intersection to discover them and consider them // as bottom surfaces (to allow for bridge detection) if (! top.empty() && ! bottom.empty()) { - // Polygons overlapping = intersection(to_polygons(top), to_polygons(bottom)); - // Slic3r::debugf " layer %d contains %d membrane(s)\n", $layerm->layer->id, scalar(@$overlapping) - // if $Slic3r::debug; Polygons top_polygons = to_polygons(std::move(top)); top.clear(); surfaces_append(top, diff_ex(top_polygons, bottom), stTop); @@ -1143,9 +1399,6 @@ void PrintObject::detect_surfaces_type() surfaces_append(surfaces_out, std::move(top)); surfaces_append(surfaces_out, std::move(bottom)); - // Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n", - // $layerm->layer->id, scalar(@bottom), scalar(@top), scalar(@internal) if $Slic3r::debug; - #ifdef SLIC3R_DEBUG_SLICE_PROCESSING layerm->export_region_slices_to_svg_debug("detect_surfaces_type-final"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ @@ -1294,7 +1547,7 @@ void PrintObject::discover_vertical_shells() bool has_extra_layers = false; for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { const PrintRegionConfig &config = this->printing_region(region_id).config(); - if (config.ensure_vertical_shell_thickness.value) { + if (config.ensure_vertical_shell_thickness.value!=EnsureVerticalThicknessLevel::evtDisabled) { has_extra_layers = true; break; } @@ -1306,6 +1559,8 @@ void PrintObject::discover_vertical_shells() BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - start : cache top / bottom"; //FIXME Improve the heuristics for a grain size. size_t grain_size = std::max(num_layers / 16, size_t(1)); + // 关闭interface_shell,不区分不同材料,所以遍历顺序是按层遍历,层中遍历region + // top区域包含墙,bottom区域包含墙,holes为稀疏填充区域 tbb::parallel_for( tbb::blocked_range(0, num_layers, grain_size), [this, &cache_top_botom_regions](const tbb::blocked_range& range) { @@ -1318,21 +1573,12 @@ void PrintObject::discover_vertical_shells() // Simulate single set of perimeters over all merged regions. float perimeter_offset = 0.f; float perimeter_min_spacing = FLT_MAX; -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - static size_t debug_idx = 0; - ++debug_idx; -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ for (size_t region_id = 0; region_id < num_regions; ++region_id) { LayerRegion& layerm = *layer.m_regions[region_id]; float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff; // Top surfaces. append(cache.top_surfaces, offset(layerm.slices.filter_by_type(stTop), top_bottom_expansion)); - // append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), top_bottom_expansion)); - // Bottom surfaces. append(cache.bottom_surfaces, offset(layerm.slices.filter_by_types(surfaces_bottom), top_bottom_expansion)); - // append(cache.bottom_surfaces, offset(layerm.fill_surfaces().filter_by_types(surfaces_bottom), top_bottom_expansion)); - // Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only. - // First find the maxium number of perimeters per region slice. unsigned int perimeters = 0; for (const Surface& s : layerm.slices.surfaces) perimeters = std::max(perimeters, s.extra_perimeters); @@ -1347,38 +1593,33 @@ void PrintObject::discover_vertical_shells() } polygons_append(cache.holes, to_polygons(layerm.fill_expolygons)); } - // Save some computing time by reducing the number of polygons. - cache.top_surfaces = union_(cache.top_surfaces); - cache.bottom_surfaces = union_(cache.bottom_surfaces); // For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print. if (perimeter_offset > 0.) { // The layer.lslices are forced to merge by expanding them first. + // 对于多材料,按照最大墙宽度,再算一次稀疏区域 polygons_append(cache.holes, offset2(layer.lslices, 0.3f * perimeter_min_spacing, -perimeter_offset - 0.3f * perimeter_min_spacing)); -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.lslices)); - svg.draw(layer.lslices, "blue"); - svg.draw(union_ex(cache.holes), "red"); - svg.draw_outline(union_ex(cache.holes), "black", "blue", scale_(0.05)); - svg.Close(); - } -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } + // Save some computing time by reducing the number of polygons. + cache.top_surfaces = union_(cache.top_surfaces); + cache.bottom_surfaces = union_(cache.bottom_surfaces); cache.holes = union_(cache.holes); }}); m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - end : cache top / bottom"; } + // 逐region遍历 for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { //FIXME Improve the heuristics for a grain size. const PrintRegion ®ion = this->printing_region(region_id); - if (!region.config().ensure_vertical_shell_thickness.value) + if (region.config().ensure_vertical_shell_thickness.value==EnsureVerticalThicknessLevel::evtDisabled) // This region will be handled by discover_horizontal_shells(). continue; size_t grain_size = std::max(num_layers / 16, size_t(1)); + // 开启了interface_shell,代表顶底面计算时只有同region可以视为covered + // 所以此时,对于某一个region,先逐层计算cache的top,bottom,由于稀疏填充是共用的,所以算一次即可 if (! top_bottom_surfaces_all_regions) { // This is either a single material print, or a multi-material print and interface_shells are enabled, meaning that the vertical shell thickness // is calculated over a single material. @@ -1412,12 +1653,17 @@ void PrintObject::discover_vertical_shells() BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : ensure vertical wall thickness"; grain_size = 1; + // 从第低到高按层遍历 +#if USE_TBB_IN_INFILL tbb::parallel_for( tbb::blocked_range(0, num_layers, grain_size), [this, region_id, &cache_top_botom_regions] (const tbb::blocked_range& range) { // printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end()); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { +#else + for (size_t idx_layer = 0; idx_layer < num_layers; ++idx_layer) { +#endif m_print->throw_if_canceled(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING static size_t debug_idx = 0; @@ -1434,7 +1680,7 @@ void PrintObject::discover_vertical_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ Flow solid_infill_flow = layerm->flow(frSolidInfill); - coord_t infill_line_spacing = solid_infill_flow.scaled_spacing(); + coord_t infill_line_spacing = solid_infill_flow.scaled_spacing(); // Find a union of perimeters below / above this surface to guarantee a minimum shell thickness. Polygons shell; Polygons holes; @@ -1442,28 +1688,6 @@ void PrintObject::discover_vertical_shells() ExPolygons shell_ex; #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f; -#if 0 -// #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - Slic3r::SVG svg_cummulative(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d.svg", debug_idx), this->bounding_box()); - for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) { - if (n < 0 || n >= (int)m_layers.size()) - continue; - ExPolygons &expolys = m_layers[n]->perimeter_expolygons; - for (size_t i = 0; i < expolys.size(); ++ i) { - Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d-layer%d-expoly%d.svg", debug_idx, n, i), get_extents(expolys[i])); - svg.draw(expolys[i]); - svg.draw_outline(expolys[i].contour, "black", scale_(0.05)); - svg.draw_outline(expolys[i].holes, "blue", scale_(0.05)); - svg.Close(); - - svg_cummulative.draw(expolys[i]); - svg_cummulative.draw_outline(expolys[i].contour, "black", scale_(0.05)); - svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05)); - } - } - } -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ polygons_append(holes, cache_top_botom_regions[idx_layer].holes); auto combine_holes = [&holes](const Polygons &holes2) { if (holes.empty() || holes2.empty()) @@ -1476,7 +1700,7 @@ void PrintObject::discover_vertical_shells() shell = std::move(shells2); else if (! shells2.empty()) { polygons_append(shell, shells2); - // Running the union_ using the Clipper library piece by piece is cheaper + // Running the union_ using the Clipper library piece by piece is cheaper // than running the union_ all at once. shell = union_(shell); } @@ -1493,7 +1717,9 @@ void PrintObject::discover_vertical_shells() ++ i) { at_least_one_top_projected = true; const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; - combine_holes(cache.holes); + if (region_config.ensure_vertical_shell_thickness.value != EnsureVerticalThicknessLevel::evtPartial) { + combine_holes(cache.holes); + } combine_shells(cache.top_surfaces); } if (!at_least_one_top_projected && i < int(cache_top_botom_regions.size())) { @@ -1522,7 +1748,9 @@ void PrintObject::discover_vertical_shells() -- i) { at_least_one_bottom_projected = true; const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; - combine_holes(cache.holes); + if (region_config.ensure_vertical_shell_thickness.value != EnsureVerticalThicknessLevel::evtPartial) { + combine_holes(cache.holes); + } combine_shells(cache.bottom_surfaces); } @@ -1538,18 +1766,6 @@ void PrintObject::discover_vertical_shells() (i > ibottom || bottom_z - m_layers[i]->print_z < region_config.bottom_shell_thickness - EPSILON)) combine_holes(cache_top_botom_regions[i].holes); } -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell)); - svg.draw(shell); - svg.draw_outline(shell, "black", scale_(0.05)); - svg.Close(); - } -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ -#if 0 -// shell = union_(shell, true); - shell = union_(shell, false); -#endif #ifdef SLIC3R_DEBUG_SLICE_PROCESSING shell_ex = union_safety_offset_ex(shell); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ @@ -1557,42 +1773,6 @@ void PrintObject::discover_vertical_shells() //if (shell.empty()) // continue; -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-after-union-%d.svg", debug_idx), get_extents(shell)); - svg.draw(shell_ex); - svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); - svg.Close(); - } -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internal-wshell-%d.svg", debug_idx), get_extents(shell)); - svg.draw(layerm->fill_surfaces().filter_by_type(stInternal), "yellow", 0.5); - svg.draw_outline(layerm->fill_surfaces().filter_by_type(stInternal), "black", "blue", scale_(0.05)); - svg.draw(shell_ex, "blue", 0.5); - svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); - svg.Close(); - } - { - Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internalvoid-wshell-%d.svg", debug_idx), get_extents(shell)); - svg.draw(layerm->fill_surfaces().filter_by_type(stInternalVoid), "yellow", 0.5); - svg.draw_outline(layerm->fill_surfaces().filter_by_type(stInternalVoid), "black", "blue", scale_(0.05)); - svg.draw(shell_ex, "blue", 0.5); - svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); - svg.Close(); - } - { - Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internalsolid-wshell-%d.svg", debug_idx), get_extents(shell)); - svg.draw(layerm->fill_surfaces().filter_by_type(stInternalSolid), "yellow", 0.5); - svg.draw_outline(layerm->fill_surfaces().filter_by_type(stInternalSolid), "black", "blue", scale_(0.05)); - svg.draw(shell_ex, "blue", 0.5); - svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); - svg.Close(); - } -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - // Trim the shells region by the internal & internal void surfaces. const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces.filter_by_types({ stInternal, stInternalVoid, stInternalSolid })); shell = intersection(shell, polygonsInternal, ApplySafetyOffset::Yes); @@ -1658,10 +1838,12 @@ void PrintObject::discover_vertical_shells() }), regularized_shell.end()); } + if (regularized_shell.empty()) continue; ExPolygons new_internal_solid = intersection_ex(polygonsInternal, regularized_shell); + #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-regularized-%d.svg", debug_idx), get_extents(shell_before)); @@ -1671,10 +1853,9 @@ void PrintObject::discover_vertical_shells() svg.draw_outline(union_safety_offset_ex(shell), "black", "blue", scale_(0.05)); // Regularized infill region. svg.draw_outline(new_internal_solid, "red", "magenta", scale_(0.05)); - svg.Close(); + svg.Close(); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - // Trim the internal & internalvoid by the shell. Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces.filter_by_type(stInternal), regularized_shell); Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces.filter_by_type(stInternalVoid), regularized_shell); @@ -1693,7 +1874,9 @@ void PrintObject::discover_vertical_shells() layerm->fill_surfaces.append(new_internal_void, stInternalVoid); layerm->fill_surfaces.append(new_internal_solid, stInternalSolid); } // for each layer +#if USE_TBB_IN_INFILL }); +#endif m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - end"; @@ -1711,181 +1894,63 @@ void PrintObject::discover_vertical_shells() // PROFILE_OUTPUT(debug_out_path("discover_vertical_shells-profile.txt").c_str()); } -#if 0 -/* This method applies bridge flow to the first internal solid layer above - sparse infill */ -void PrintObject::bridge_over_infill() +void PrintObject::discover_shell_for_perimeters() { - BOOST_LOG_TRIVIAL(info) << "Bridge over infill..." << log_memory_info(); + const size_t num_regions = this->num_printing_regions(); - for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { - const PrintRegion ®ion = this->printing_region(region_id); + tbb::parallel_for(tbb::blocked_range(0, m_layers.size()), + [this,num_regions](const tbb::blocked_range &range){ + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { + Layer* layer = m_layers[idx_layer]; + if (!layer->lower_layer) + continue; + Layer* lower_layer = layer->lower_layer; - // skip bridging in case there are no voids - if (region.config().sparse_infill_density.value == 100) - continue; - - for (LayerPtrs::iterator layer_it = m_layers.begin(); layer_it != m_layers.end(); ++ layer_it) { - // skip first layer - if (layer_it == m_layers.begin()) - continue; - - Layer *layer = *layer_it; - LayerRegion *layerm = layer->m_regions[region_id]; - const PrintObjectConfig& object_config = layer->object()->config(); - //QDS: enable thick bridge for internal bridge only - Flow bridge_flow = layerm->bridging_flow(frSolidInfill, true); - - // extract the stInternalSolid surfaces that might be transformed into bridges - Polygons internal_solid; - layerm->fill_surfaces.filter_by_type(stInternalSolid, &internal_solid); - - // check whether the lower area is deep enough for absorbing the extra flow - // (for obvious physical reasons but also for preventing the bridge extrudates - // from overflowing in 3D preview) - ExPolygons to_bridge; - { - Polygons to_bridge_pp = internal_solid; - - // iterate through lower layers spanned by bridge_flow - double bottom_z = layer->print_z - bridge_flow.height() - EPSILON; - for (int i = int(layer_it - m_layers.begin()) - 1; i >= 0; --i) { - const Layer* lower_layer = m_layers[i]; - - // stop iterating if layer is lower than bottom_z - if (lower_layer->print_z < bottom_z) break; - - // iterate through regions and collect internal surfaces - Polygons lower_internal; - for (LayerRegion *lower_layerm : lower_layer->m_regions) - lower_layerm->fill_surfaces.filter_by_type(stInternal, &lower_internal); - - // intersect such lower internal surfaces with the candidate solid surfaces - to_bridge_pp = intersection(to_bridge_pp, lower_internal); + ExPolygons perimeter_areas; + ExPolygons infill_areas; + float max_line_width = 0; + for (size_t region_id = 0; region_id < num_regions; ++region_id) { + LayerRegion* layerm = layer->m_regions[region_id]; + Flow extflow = layerm->flow(frExternalPerimeter); + infill_areas.insert(infill_areas.end(), layerm->fill_expolygons.begin(), layerm->fill_expolygons.end()); + max_line_width = std::max(max_line_width, 0.5f * float(extflow.scaled_width() + extflow.scaled_spacing())); } + infill_areas = union_ex(infill_areas); + perimeter_areas = offset_ex(diff_ex(layer->lslices, infill_areas), max_line_width); - // QDS: expand to make avoid gap between bridge and inner wall - to_bridge_pp = expand(to_bridge_pp, bridge_flow.scaled_width()); - to_bridge_pp = intersection(to_bridge_pp, internal_solid); + for (size_t region_id = 0; region_id < num_regions; ++region_id) { + LayerRegion* lower_layerm = lower_layer->m_regions[region_id]; - // there's no point in bridging too thin/short regions - //FIXME Vojtech: The offset2 function is not a geometric offset, - // therefore it may create 1) gaps, and 2) sharp corners, which are outside the original contour. - // The gaps will be filled by a separate region, which makes the infill less stable and it takes longer. - { - float min_width = float(bridge_flow.scaled_width()) * 3.f; - to_bridge_pp = opening(to_bridge_pp, min_width); - } + ExPolygons new_perimeter_solid = intersection_ex(perimeter_areas, lower_layerm->fill_expolygons); + new_perimeter_solid.erase(std::remove_if(new_perimeter_solid.begin(), new_perimeter_solid.end(), [max_line_width](auto& expoly) { + return is_narrow_expolygon(expoly, 3 * max_line_width); + }), new_perimeter_solid.end()); + if (new_perimeter_solid.empty()) + continue; - if (to_bridge_pp.empty()) continue; + ExPolygons old_internal = to_expolygons(lower_layerm->fill_surfaces.filter_by_type(stInternal)); + ExPolygons old_internal_void = to_expolygons(lower_layerm->fill_surfaces.filter_by_type(stInternalVoid)); + ExPolygons old_internal_solid = to_expolygons(lower_layerm->fill_surfaces.filter_by_type(stInternalSolid)); - // convert into ExPolygons - to_bridge = union_ex(to_bridge_pp); - } + lower_layerm->fill_surfaces.remove_types({ stInternal,stInternalVoid,stInternalSolid }); - #ifdef SLIC3R_DEBUG - printf("Bridging %zu internal areas at layer %zu\n", to_bridge.size(), layer->id()); - #endif - - // compute the remaning internal solid surfaces as difference - ExPolygons not_to_bridge = diff_ex(internal_solid, to_bridge, ApplySafetyOffset::Yes); - to_bridge = intersection_ex(to_bridge, internal_solid, ApplySafetyOffset::Yes); - // build the new collection of fill_surfaces - layerm->fill_surfaces.remove_type(stInternalSolid); - for (ExPolygon &ex : to_bridge) { - layerm->fill_surfaces.surfaces.push_back(Surface(stInternalBridge, ex)); - // QDS: detect angle for internal bridge infill - InternalBridgeDetector ibd(ex, layerm->fill_no_overlap_expolygons, bridge_flow.scaled_spacing()); - if (ibd.detect_angle()) { - (layerm->fill_surfaces.surfaces.end() - 1)->bridge_angle = ibd.angle; + ExPolygons new_internal_solid = union_ex(old_internal_solid, new_perimeter_solid); + ExPolygons new_internal = diff_ex(old_internal, new_perimeter_solid); + ExPolygons new_internal_void = diff_ex(old_internal_void, new_perimeter_solid); + lower_layerm->fill_surfaces.append(new_internal, stInternal); + lower_layerm->fill_surfaces.append(new_internal_void, stInternalVoid); + lower_layerm->fill_surfaces.append(new_internal_solid, stInternalSolid); } } - - for (ExPolygon &ex : not_to_bridge) - layerm->fill_surfaces.surfaces.push_back(Surface(stInternalSolid, ex)); - - //QDS: modify stInternal to be stInternalWithLoop to give better support to internal bridge - if (!to_bridge.empty()){ - float internal_loop_thickness = object_config.internal_bridge_support_thickness.value; - double bottom_z = layer->print_z - layer->height - internal_loop_thickness + EPSILON; - //QDS: lighting infill doesn't support this feature. Don't need to add loop when infill density is high than 50% - if (region.config().sparse_infill_pattern != InfillPattern::ipLightning && region.config().sparse_infill_density.value < 50) - for (int i = int(layer_it - m_layers.begin()) - 1; i >= 0; --i) { - const Layer* lower_layer = m_layers[i]; - - if (lower_layer->print_z < bottom_z) break; - - for (LayerRegion* lower_layerm : lower_layer->m_regions) { - Polygons lower_internal; - lower_layerm->fill_surfaces.filter_by_type(stInternal, &lower_internal); - ExPolygons internal_with_loop = intersection_ex(lower_internal, to_bridge); - ExPolygons internal = diff_ex(lower_internal, to_bridge); - if (internal_with_loop.empty()) { - //QDS: don't need to do anything - } - else if (internal.empty()) { - lower_layerm->fill_surfaces.change_to_new_type(stInternal, stInternalWithLoop); - } - else { - lower_layerm->fill_surfaces.remove_type(stInternal); - for (ExPolygon& ex : internal_with_loop) - lower_layerm->fill_surfaces.surfaces.push_back(Surface(stInternalWithLoop, ex)); - for (ExPolygon& ex : internal) - lower_layerm->fill_surfaces.surfaces.push_back(Surface(stInternal, ex)); - } - } - } - } - - /* - # exclude infill from the layers below if needed - # see discussion at https://github.com/alexrj/Slic3r/issues/240 - # Update: do not exclude any infill. Sparse infill is able to absorb the excess material. - if (0) { - my $excess = $layerm->extruders->{infill}->bridge_flow->width - $layerm->height; - for (my $i = $layer_id-1; $excess >= $self->get_layer($i)->height; $i--) { - Slic3r::debugf " skipping infill below those areas at layer %d\n", $i; - foreach my $lower_layerm (@{$self->get_layer($i)->regions}) { - my @new_surfaces = (); - # subtract the area from all types of surfaces - foreach my $group (@{$lower_layerm->fill_surfaces->group}) { - push @new_surfaces, map $group->[0]->clone(expolygon => $_), - @{diff_ex( - [ map $_->p, @$group ], - [ map @$_, @$to_bridge ], - )}; - push @new_surfaces, map Slic3r::Surface->new( - expolygon => $_, - surface_type => stInternalVoid, - ), @{intersection_ex( - [ map $_->p, @$group ], - [ map @$_, @$to_bridge ], - )}; - } - $lower_layerm->fill_surfaces->clear; - $lower_layerm->fill_surfaces->append($_) for @new_surfaces; - } - - $excess -= $self->get_layer($i)->height; - } - } - */ - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - layerm->export_region_slices_to_svg_debug("7_bridge_over_infill"); - layerm->export_region_fill_surfaces_to_svg_debug("7_bridge_over_infill"); -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - m_print->throw_if_canceled(); - } - } + }); } -#else // This method applies bridge flow to the first internal solid layer above sparse infill. // This method applies bridge flow to the first internal solid layer above sparse infill. void PrintObject::bridge_over_infill() { BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Start" << log_memory_info(); + // CandidateSurface存放一个需要桥接的区域 struct CandidateSurface { CandidateSurface(const Surface *original_surface, @@ -1899,29 +1964,37 @@ void PrintObject::bridge_over_infill() , region(region) , bridge_angle(bridge_angle) {} - const Surface *original_surface; - int layer_index; - Polygons new_polys; - const LayerRegion *region; - double bridge_angle; + const Surface *original_surface; // 下方需要生成桥接的surface + int layer_index; // 下方生成桥接的层号 + Polygons new_polys; // 下方需要生成桥接的实心区域 + const LayerRegion *region; // 下方需要生成桥接的region,主要提供参数 + double bridge_angle; // 桥接方向 }; + // 按层存放surface,存放着待桥接的信息 std::map> surfaces_by_layer; // SECTION to gather and filter surfaces for expanding, and then cluster them by layer { tbb::concurrent_vector candidate_surfaces; +#if USE_TBB_IN_INFILL tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = static_cast(this), &candidate_surfaces](tbb::blocked_range r) { + // 按层并行 for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { +#else + auto po = static_cast(this); + for(size_t lidx =0;lidxlayers().size();++lidx){ +#endif const Layer *layer = po->get_layer(lidx); if (layer->lower_layer == nullptr) { continue; } double spacing = layer->regions().front()->flow(frSolidInfill).scaled_spacing(); // unsupported area will serve as a filter for polygons worth bridging. - Polygons unsupported_area; - Polygons lower_layer_solids; + Polygons unsupported_area; // 下一层不提供支撑的区域 + Polygons lower_layer_solids; // 下一层的实心区域,可以提供支撑 + // 取当前层和下一层的数据 for (const LayerRegion *region : layer->lower_layer->regions()) { Polygons fill_polys = to_polygons(region->fill_expolygons); // initially consider the whole layer unsupported, but also gather solid layers to later cut off supported parts @@ -1943,11 +2016,11 @@ void PrintObject::bridge_over_infill() unsupported_area = diff(unsupported_area, lower_layer_solids); for (LayerRegion *region : layer->regions()) { - SurfacesPtr region_internal_solids = region->fill_surfaces.filter_by_type(stInternalSolid); + auto region_internal_solids = region->fill_surfaces.filter_by_type(stInternalSolid); // 取当前层的实心区域 for (const Surface *s : region_internal_solids) { - Polygons unsupported = intersection(to_polygons(s->expolygon), unsupported_area); - // The following flag marks those surfaces, which overlap with unuspported area, but at least part of them is supported. - // These regions can be filtered by area, because they for sure are touching solids on lower layers, and it does not make sense to bridge their tiny overhangs + Polygons unsupported = intersection(to_polygons(s->expolygon), unsupported_area); // 当前层需要生成桥接的区域,通过当前层的实心区域与下一层的非实心区域求交得到 + // The following flag marks those surfaces, which overlap with unuspported area, but at least part of them is supported. + // These regions can be filtered by area, because they for sure are touching solids on lower layers, and it does not make sense to bridge their tiny overhangs bool partially_supported = area(unsupported) < area(to_polygons(s->expolygon)) - EPSILON; if (!unsupported.empty() && (!partially_supported || area(unsupported) > 3 * 3 * spacing * spacing)) { Polygons worth_bridging = intersection(to_polygons(s->expolygon), expand(unsupported, 4 * spacing)); @@ -1959,6 +2032,7 @@ void PrintObject::bridge_over_infill() } } worth_bridging = intersection(closing(worth_bridging, float(SCALED_EPSILON)), s->expolygon); + // 对应哪个region下的那个surface需要生成桥接 candidate_surfaces.push_back(CandidateSurface(s, lidx, worth_bridging, region, 0)); #ifdef DEBUG_BRIDGE_OVER_INFILL @@ -1968,22 +2042,25 @@ void PrintObject::bridge_over_infill() #endif #ifdef DEBUG_BRIDGE_OVER_INFILL debug_draw(std::to_string(lidx) + "_candidate_processing_" + std::to_string(area(unsupported)), - to_lines(unsupported), to_lines(intersection(to_polygons(s->expolygon), expand(unsupported, 5 * spacing))), - to_lines(diff(to_polygons(s->expolygon), expand(worth_bridging, spacing))), + to_lines(unsupported), to_lines(intersection(to_polygons(s->expolygon), expand(unsupported, 5 * spacing))), + to_lines(diff(to_polygons(s->expolygon), expand(worth_bridging, spacing))), to_lines(unsupported_area)); #endif } } } - } - }); + } +#if USE_TBB_IN_INFILL + }); +#endif + // 按层重新存储 for (const CandidateSurface &c : candidate_surfaces) { surfaces_by_layer[c.layer_index].push_back(c); } } - // LIGHTNING INFILL SECTION - If lightning infill is used somewhere, we check the areas that are going to be bridges, and those that rely on the + // LIGHTNING INFILL SECTION - If lightning infill is used somewhere, we check the areas that are going to be bridges, and those that rely on the // lightning infill under them get expanded. This somewhat helps to ensure that most of the extrusions are anchored to the lightning infill at the ends. // It requires modifying this instance of print object in a specific way, so that we do not invalidate the pointers in our surfaces_by_layer structure. bool has_lightning_infill = false; @@ -2115,8 +2192,8 @@ void PrintObject::bridge_over_infill() std::vector> clustered_layers_for_threads; float target_flow_height_factor = 0.9f; { - std::vector layers_with_candidates; - std::map layer_area_covered_by_candidates; + std::vector layers_with_candidates; // 存储所有需要生成桥接的层号 + std::map layer_area_covered_by_candidates; // 存储每一层,需要生成桥接区域的bbox的并集 for (const auto& pair : surfaces_by_layer) { layers_with_candidates.push_back(pair.first); layer_area_covered_by_candidates[pair.first] = {}; @@ -2127,6 +2204,7 @@ void PrintObject::bridge_over_infill() tbb::parallel_for(tbb::blocked_range(0, layers_with_candidates.size()), [&layers_with_candidates, &surfaces_by_layer, &layer_area_covered_by_candidates]( tbb::blocked_range r) { + // 按层并行 for (size_t job_idx = r.begin(); job_idx < r.end(); job_idx++) { size_t lidx = layers_with_candidates[job_idx]; for (const auto &candidate : surfaces_by_layer.at(lidx)) { @@ -2138,7 +2216,8 @@ void PrintObject::bridge_over_infill() }); // note: surfaces_by_layer is ordered map - for (auto pair : surfaces_by_layer) { + for (const auto &pair : surfaces_by_layer) { + // 初次操作 || z方向距离较远 || 桥接区域无交集, 那么就可以重新划分一个组,否则分配到前一个组 if (clustered_layers_for_threads.empty() || this->get_layer(clustered_layers_for_threads.back().back())->print_z < this->get_layer(pair.first)->print_z - @@ -2188,6 +2267,7 @@ void PrintObject::bridge_over_infill() } } } + // 收集一定z范围内的稀疏和实心区域,判断有没有交集,如果有交集,则不能使用thick bridge(thick bridge的流量会侵占实心区域) layers_sparse_infill = union_ex(layers_sparse_infill); layers_sparse_infill = closing_ex(layers_sparse_infill, float(SCALED_EPSILON)); not_sparse_infill = union_ex(not_sparse_infill); @@ -2495,6 +2575,7 @@ void PrintObject::bridge_over_infill() coordf_t spacing = surfaces_by_layer[lidx].front().region->bridging_flow(frSolidInfill, true).scaled_spacing(); coordf_t target_flow_height = surfaces_by_layer[lidx].front().region->bridging_flow(frSolidInfill, true).height() * target_flow_height_factor; + // 收集当前层中可以应用thick_bridge的区域 Polygons deep_infill_area = gather_areas_w_depth(po, lidx, target_flow_height); { @@ -2516,16 +2597,17 @@ void PrintObject::bridge_over_infill() } } } + // 再减去别的层已经生成的桥接区域 deep_infill_area = diff(deep_infill_area, filled_polyons_on_lower_layers); } - + // 得到thick_bridge区域,bridge区域扩1.5倍 deep_infill_area = expand(deep_infill_area, spacing * 1.5); // Now gather expansion polygons - internal infill on current layer, from which we can cut off anchors Polygons lightning_area; - Polygons expansion_area; - Polygons total_fill_area; - Polygons top_area; + Polygons expansion_area; // 可以提供扩张的区域 + Polygons total_fill_area; // 所有填充区域 + Polygons top_area; // 顶面区域 for (LayerRegion *region : layer->regions()) { Polygons internal_polys = to_polygons(region->fill_surfaces.filter_by_types({stInternal, stInternalSolid})); @@ -2554,7 +2636,7 @@ void PrintObject::bridge_over_infill() expanded_surfaces.reserve(surfaces_by_layer[lidx].size()); for (const CandidateSurface &candidate : surfaces_by_layer[lidx]) { const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true); - Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing()); + Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing()); // 待生成桥接区域 area_to_be_bridge = intersection(area_to_be_bridge, deep_infill_area); ExPolygons area_to_be_bridge_ex = union_ex(area_to_be_bridge); area_to_be_bridge_ex.erase(std::remove_if(area_to_be_bridge_ex.begin(), area_to_be_bridge_ex.end(), @@ -2565,7 +2647,7 @@ void PrintObject::bridge_over_infill() area_to_be_bridge = to_polygons(area_to_be_bridge_ex); - Polygons limiting_area = union_(area_to_be_bridge, expansion_area); + Polygons limiting_area = union_(area_to_be_bridge, expansion_area); // 桥接区域 + 可扩张区域 if (area_to_be_bridge.empty()) continue; @@ -2645,18 +2727,19 @@ void PrintObject::bridge_over_infill() tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, &surfaces_by_layer](tbb::blocked_range r) { for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + // 如果既不需要生成桥接,也不是桥接的下一层,不处理 if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end() && surfaces_by_layer.find(lidx + 1) == surfaces_by_layer.end()) continue; Layer *layer = po->get_layer(lidx); - Polygons cut_from_infill{}; + Polygons cut_from_infill{}; // 桥接区域 if (surfaces_by_layer.find(lidx) != surfaces_by_layer.end()) { for (const auto &surface : surfaces_by_layer.at(lidx)) { cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end()); } } - Polygons additional_ensuring_areas{}; + Polygons additional_ensuring_areas{}; // 下一层为上一层桥接需要生成的区域 if (surfaces_by_layer.find(lidx + 1) != surfaces_by_layer.end()) { for (const auto &surface : surfaces_by_layer.at(lidx + 1)) { auto additional_area = diff(surface.new_polys, @@ -2668,13 +2751,13 @@ void PrintObject::bridge_over_infill() for (LayerRegion *region : layer->regions()) { Surfaces new_surfaces; - Polygons near_perimeters = to_polygons(union_safety_offset_ex(to_polygons(region->fill_surfaces.surfaces))); + Polygons near_perimeters = to_polygons(union_safety_offset_ex(to_polygons(region->fill_surfaces.surfaces))); // 填充区域中,紧靠着外墙的区域 near_perimeters = diff(near_perimeters, shrink(near_perimeters, region->flow(frSolidInfill).scaled_spacing())); - ExPolygons additional_ensuring = intersection_ex(additional_ensuring_areas, near_perimeters); + ExPolygons additional_ensuring = intersection_ex(additional_ensuring_areas, near_perimeters); // 紧靠着外墙,能够给上一层的桥接提供支撑的区域 SurfacesPtr internal_infills = region->fill_surfaces.filter_by_type(stInternal); - ExPolygons new_internal_infills = diff_ex(internal_infills, cut_from_infill); - new_internal_infills = diff_ex(new_internal_infills, additional_ensuring); + ExPolygons new_internal_infills = diff_ex(internal_infills, cut_from_infill); // 新的稀疏填充区域,去掉生成的桥接区域 + new_internal_infills = diff_ex(new_internal_infills, additional_ensuring); for (const ExPolygon &ep : new_internal_infills) { new_surfaces.emplace_back(stInternal, ep); } @@ -2695,8 +2778,8 @@ void PrintObject::bridge_over_infill() } } } + ExPolygons new_internal_solids = to_expolygons(internal_solids); - new_internal_solids.insert(new_internal_solids.end(), additional_ensuring.begin(), additional_ensuring.end()); new_internal_solids = diff_ex(new_internal_solids, cut_from_infill); new_internal_solids = union_safety_offset_ex(new_internal_solids); for (const ExPolygon &ep : new_internal_solids) { @@ -2722,10 +2805,6 @@ void PrintObject::bridge_over_infill() } // void PrintObject::bridge_over_infill() - - - -#endif static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders) { if (opt.value > (int)num_extruders) @@ -2733,13 +2812,13 @@ static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders opt.value = 1; } -PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders) +PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders, std::vector& variant_index) { PrintObjectConfig config = default_object_config; { DynamicPrintConfig src_normalized(object.config.get()); src_normalized.normalize_fdm(); - config.apply(src_normalized, true); + update_static_print_config_from_dynamic(config, src_normalized, variant_index, print_options_with_variant, 1); } // Clamp invalid extruders to the default extruder (with index 1). clamp_exturder_to_default(config.support_filament, num_extruders); @@ -2750,7 +2829,7 @@ PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObject const std::string key_extruder { "extruder" }; static constexpr const std::initializer_list keys_extruders { "sparse_infill_filament"sv, "solid_infill_filament"sv, "wall_filament"sv }; -static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPrintConfig &in) +static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPrintConfig &in, std::vector& variant_index) { // 1) Copy the "extruder key to sparse_infill_filament and wall_filament. auto *opt_extruder = in.opt(key_extruder); @@ -2770,28 +2849,38 @@ static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPr int extruder = static_cast(it->second.get())->value; if (extruder > 0) my_opt->setInt(extruder); - } else - my_opt->set(it->second.get()); + } else { + if (*my_opt != *(it->second)) { + if (my_opt->is_scalar() || variant_index.empty() || (print_options_with_variant.find(it->first) == print_options_with_variant.end())) + my_opt->set(it->second.get()); + //my_opt->set(it->second.get()); + else { + ConfigOptionVectorBase* opt_vec_src = static_cast(my_opt); + const ConfigOptionVectorBase* opt_vec_dest = static_cast(it->second.get()); + opt_vec_src->set_to_index(opt_vec_dest, variant_index, 1); + } + } + } } } -PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_or_parent_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders) +PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_or_parent_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders, std::vector& variant_index) { PrintRegionConfig config = default_or_parent_region_config; if (volume.is_model_part()) { // default_or_parent_region_config contains the Print's PrintRegionConfig. // Override with ModelObject's PrintRegionConfig values. - apply_to_print_region_config(config, volume.get_object()->config.get()); + apply_to_print_region_config(config, volume.get_object()->config.get(), variant_index); } else { // default_or_parent_region_config contains parent PrintRegion config, which already contains ModelVolume's config. } - apply_to_print_region_config(config, volume.config.get()); + apply_to_print_region_config(config, volume.config.get(), variant_index); if (! volume.material_id().empty()) - apply_to_print_region_config(config, volume.material()->config.get()); + apply_to_print_region_config(config, volume.material()->config.get(), variant_index); if (layer_range_config != nullptr) { // Not applicable to modifiers. assert(volume.is_model_part()); - apply_to_print_region_config(config, *layer_range_config); + apply_to_print_region_config(config, *layer_range_config, variant_index); } // Clamp invalid extruders to the default extruder (with index 1). clamp_exturder_to_default(config.sparse_infill_filament, num_extruders); @@ -2835,7 +2924,7 @@ void PrintObject::update_slicing_parameters() this->print()->config(), m_config, this->model_object()->bounding_box().max.z(), this->object_extruders()); } -SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full_config, const ModelObject& model_object, float object_max_z) +SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full_config, const ModelObject& model_object, float object_max_z, std::vector variant_index) { PrintConfig print_config; PrintObjectConfig object_config; @@ -2845,14 +2934,14 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full default_region_config.apply(full_config, true); // QDS size_t filament_extruders = print_config.filament_diameter.size(); - object_config = object_config_from_model_object(object_config, model_object, filament_extruders); + object_config = object_config_from_model_object(object_config, model_object, filament_extruders, variant_index); std::vector object_extruders; for (const ModelVolume* model_volume : model_object.volumes) if (model_volume->is_model_part()) { PrintRegion::collect_object_printing_extruders( print_config, - region_config_from_model_volume(default_region_config, nullptr, *model_volume, filament_extruders), + region_config_from_model_volume(default_region_config, nullptr, *model_volume, filament_extruders, variant_index), object_config.brim_type != btNoBrim && object_config.brim_width > 0., object_extruders); for (const std::pair &range_and_config : model_object.layer_config_ranges) @@ -2861,7 +2950,7 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full range_and_config.second.has("solid_infill_filament")) PrintRegion::collect_object_printing_extruders( print_config, - region_config_from_model_volume(default_region_config, &range_and_config.second.get(), *model_volume, filament_extruders), + region_config_from_model_volume(default_region_config, &range_and_config.second.get(), *model_volume, filament_extruders, variant_index), object_config.brim_type != btNoBrim && object_config.brim_width > 0., object_extruders); } @@ -2938,7 +3027,7 @@ void PrintObject::get_certain_layers(float start, float end, std::vector PrintObject::get_instances_shift_without_plate_offset() +std::vector PrintObject::get_instances_shift_without_plate_offset() const { std::vector out; out.reserve(m_instances.size()); @@ -3052,18 +3141,8 @@ void PrintObject::discover_horizontal_shells() Layer* layer = m_layers[i]; LayerRegion* layerm = layer->regions()[region_id]; const PrintRegionConfig& region_config = layerm->region().config(); -#if 0 - if (region_config.solid_infill_every_layers.value > 0 && region_config.sparse_infill_density.value > 0 && - (i % region_config.solid_infill_every_layers) == 0) { - // Insert a solid internal layer. Mark stInternal surfaces as stInternalSolid or stInternalBridge. - SurfaceType type = (region_config.sparse_infill_density == 100 || region_config.solid_infill_every_layers == 1) ? stInternalSolid : stInternalBridge; - for (Surface& surface : layerm->fill_surfaces.surfaces) - if (surface.surface_type == stInternal) - surface.surface_type = type; - } -#endif // If ensure_vertical_shell_thickness, then the rest has already been performed by discover_vertical_shells(). - if (region_config.ensure_vertical_shell_thickness.value) + if (region_config.ensure_vertical_shell_thickness.value!=EnsureVerticalThicknessLevel::evtDisabled) continue; coordf_t print_z = layer->print_z; @@ -3134,7 +3213,7 @@ void PrintObject::discover_horizontal_shells() // No internal solid needed on this layer. In order to decide whether to continue // searching on the next neighbor (thus enforcing the configured number of solid // layers, use different strategies according to configured infill density: - if (region_config.sparse_infill_density.value == 0) { + if (region_config.sparse_infill_density.value == 0 || region_config.ensure_vertical_shell_thickness.value == EnsureVerticalThicknessLevel::evtDisabled) { // If user expects the object to be void (for example a hollow sloping vase), // don't continue the search. In this case, we only generate the external solid // shell if the object would otherwise show a hole (gap between perimeters of diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 4d4e02b..6f08383 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -4,6 +4,7 @@ #include "MultiMaterialSegmentation.hpp" #include "Print.hpp" #include "ClipperUtils.hpp" +#include "Interlocking/InterlockingGenerator.hpp" //QDS #include "ShortestPath.hpp" @@ -1016,20 +1017,6 @@ void PrintObject::slice_volumes() PrintObject::clip_multipart_objects, throw_on_cancel_callback); - // SuperSlicer: filament shrink - for (const std::unique_ptr &pr : m_shared_regions->all_regions) { - if (pr.get()) { - std::vector ®ion_polys = region_slices[pr->print_object_region_id()]; - const size_t extruder_id = pr->extruder(FlowRole::frPerimeter) - 1; - double scale = print->config().filament_shrink.values[extruder_id] * 0.01; - if (scale != 1) { - scale = 1 / scale; - for (ExPolygons &polys : region_polys) - for (ExPolygon &poly : polys) poly.scale(scale); - } - } - } - for (size_t region_id = 0; region_id < region_slices.size(); ++ region_id) { std::vector &by_layer = region_slices[region_id]; for (size_t layer_id = 0; layer_id < by_layer.size(); ++ layer_id) @@ -1068,6 +1055,25 @@ void PrintObject::slice_volumes() apply_mm_segmentation(*this, [print]() { print->throw_if_canceled(); }); } + InterlockingGenerator::generate_interlocking_structure(this); + m_print->throw_if_canceled(); + + // SuperSlicer: filament shrink + for (Layer *layer : m_layers) { + for (size_t i = 0; i < layer->region_count(); ++i) { + LayerRegion *region = layer->get_region(i); + ExPolygons ex_polys = to_expolygons(region->slices.surfaces); + int filament_id = region->region().extruder(FlowRole::frPerimeter) - 1; + double scale = print->config().filament_shrink.values[filament_id] * 0.01; + if (scale != 1) { + scale = 1 / scale; + for (ExPolygon &poly : ex_polys) + poly.scale(scale); + } + + region->slices.set(std::move(ex_polys), stInternal); + } + } BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin"; { @@ -1183,7 +1189,9 @@ void PrintObject::slice_volumes() } } // Merge all regions' slices to get islands, chain them by a shortest path. - layer->make_slices(); + if (this->config().enable_circle_compensation) + layer->apply_auto_circle_compensation(); + layer->make_slices(); } }); if (elephant_foot_compensation_scaled > 0.f && ! m_layers.empty()) { diff --git a/src/libslic3r/ProjectTask.hpp b/src/libslic3r/ProjectTask.hpp index ddb1126..63f201d 100644 --- a/src/libslic3r/ProjectTask.hpp +++ b/src/libslic3r/ProjectTask.hpp @@ -38,18 +38,61 @@ enum MappingResult { struct FilamentInfo { - int id; // filament id = extruder id, start with 0. + int id{0}; // filament id = extruder id, start with 0. std::string type; std::string color; std::string filament_id; std::string brand; - float used_m; - float used_g; - int tray_id; // start with 0 - float distance; + float used_m{0.f}; + float used_g{0.f}; + int tray_id{0}; // start with 0 + float distance{0.f}; int ctype = 0; std::vector colors = std::vector(); int mapping_result = 0; + + /*for new ams mapping*/ + std::string ams_id; + std::string slot_id; + +public: + int get_ams_id() const + { + if (ams_id.empty()) { return -1; }; + + try + { + return stoi(ams_id); + } + catch (...) {}; + + return -1; + }; + + int get_slot_id() const + { + if (slot_id.empty()) { return -1; }; + + try { + return stoi(slot_id); + } catch (...) {}; + + return -1; + }; + + /*copied from AmsTray::get_display_filament_type()*/ + std::string get_display_filament_type() + { + if (type == "PLA-S") + return "Sup.PLA"; + else if (type == "PA-S") + return "Sup.PA"; + else if (type == "ABS-S") + return "Sup.ABS"; + else + return type; + return type; + } }; class QDTSliceInfo { diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 4c58c13..713c9bf 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -184,7 +184,7 @@ std::vector SLAPrint::print_object_ids() const return out; } -SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig config) +SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig config, bool extruder_applied) { #ifdef _DEBUG check_model_ids_validity(model); diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 9114544..aad2c50 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -449,7 +449,7 @@ public: bool empty() const override { return m_objects.empty(); } // List of existing PrintObject IDs, to remove notifications for non-existent IDs. std::vector print_object_ids() const override; - ApplyStatus apply(const Model &model, DynamicPrintConfig config) override; + ApplyStatus apply(const Model &model, DynamicPrintConfig config, bool extruder_applied = false) override; void set_task(const TaskParams ¶ms) override; //1.9.5 void process(std::unordered_map* slice_time = nullptr, bool use_cache = false) override; diff --git a/src/libslic3r/SVG.cpp b/src/libslic3r/SVG.cpp index 6b42b69..1bc5084 100644 --- a/src/libslic3r/SVG.cpp +++ b/src/libslic3r/SVG.cpp @@ -1,7 +1,9 @@ #include "SVG.hpp" +#include #include - +// #include "pugixml/pugixml.hpp" #include +#include "nlohmann/json.hpp" namespace Slic3r { @@ -331,6 +333,132 @@ void SVG::add_comment(const std::string comment) fprintf(this->f, "\n", comment.c_str()); } +// Function to parse the SVG path data +Points ParseSVGPath(const std::string &pathData) +{ + Points points; + Vec2d currentPoint = {0, 0}; + char command = 0; + std::istringstream stream(pathData); + + while (stream) { + // Read the command or continue with the previous command + if (!std::isdigit(stream.peek()) && stream.peek() != '-' && stream.peek() != '.') { stream >> command; } + + if (command == 'M' || command == 'm') { // Move to + double x, y; + stream >> x; + stream.ignore(1, ','); // Skip the comma, if present + stream >> y; + + if (command == 'm') { // Relative + currentPoint.x() += x; + currentPoint.y() += y; + } else { // Absolute + currentPoint.x() = x; + currentPoint.y() = y; + } + points.push_back(scaled(currentPoint)); + } else if (command == 'L' || command == 'l') { // Line to + double x, y; + stream >> x; + stream.ignore(1, ','); // Skip the comma, if present + stream >> y; + + if (command == 'l') { // Relative + currentPoint.x() += x; + currentPoint.y() += y; + } else { // Absolute + currentPoint.x() = x; + currentPoint.y() = y; + } + points.push_back(scaled(currentPoint)); + } else if (command == 'Z' || command == 'z') { // Close path + if (!points.empty()) { + points.push_back(points.front()); // Close the polygon by returning to the start + } + } else if (command == 'H' || command == 'h') { // Horizontal line + double x; + stream >> x; + + if (command == 'h') { // Relative + currentPoint.x() += x; + } else { // Absolute + currentPoint.x() = x; + } + points.push_back(scaled(currentPoint)); + } else if (command == 'V' || command == 'v') { // Vertical line + double y; + stream >> y; + + if (command == 'v') { // Relative + currentPoint.y() += y; + } else { // Absolute + currentPoint.y() = y; + } + points.push_back(scaled(currentPoint)); + } else if (command == 'z') { + if (!points.empty()) { + points.push_back(points.front()); // Close path + } + } else { + stream.ignore(1); // Skip invalid commands or extra spaces + } + } + + return points; +} + +// Convert SVG path to ExPolygon +ExPolygon ConvertToExPolygon(const std::vector &svgPaths) +{ + ExPolygon exPolygon; + + for (const auto &pathData : svgPaths) { + auto points = ParseSVGPath(pathData); + if (exPolygon.contour.empty()) { + exPolygon.contour.points = points; // First path is outer + } else { + exPolygon.holes.emplace_back(points); // Subsequent paths are holes + } + } + + return exPolygon; +} + +// Function to load SVG and convert paths to ExPolygons +std::vector SVG::load(const std::string &svgFilePath) +{ + std::vector polygons; +/* pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(svgFilePath.c_str()); + if (!result) { + std::cerr << "Failed to load SVG file: " << result.description() << "\n"; + return polygons; + } + + + // Find the root element + pugi::xml_node svgNode = doc.child("svg"); + if (!svgNode) { + std::cerr << "No element found in file.\n"; + return polygons; + } + + // Iterate over elements + for (pugi::xml_node pathNode : svgNode.children("path")) { + const char *pathData = pathNode.attribute("d").value(); + if (pathData) { + std::vector paths = {std::string(pathData)}; // For simplicity, assuming one path per element. You could extract more complex paths if necessary. + ExPolygon exPolygon = ConvertToExPolygon(paths); + polygons.push_back(exPolygon); + } + } +*/ + return polygons; +} + + void SVG::Close() { fprintf(this->f, "\n"); @@ -411,4 +539,234 @@ void SVG::export_expolygons(const char *path, const std::vector(); + p.y() = j[1].get(); + } else { + throw std::runtime_error("Invalid Point JSON format. Expected [x, y]."); + } +} + +// Serialization for Polygon +void to_json(nlohmann::json &j, const Polygon &polygon) +{ + j = nlohmann::json::array(); + for (const auto &point : polygon.points) { + j.push_back(point); // Push each point (serialized as [x, y]) + } +} + +void from_json(const nlohmann::json &j, Polygon &polygon) +{ + if (j.is_array()) { + polygon.clear(); + for (const auto &item : j) { polygon.append(item.get()); } + } else { + throw std::runtime_error("Invalid Polygon JSON format. Expected array of points."); + } +} + + +// Serialization for ExPolygon +void to_json(nlohmann::json &j, const ExPolygon &exPolygon) { + j = nlohmann::json{{"contour", exPolygon.contour}, {"holes", exPolygon.holes}}; +} + +void from_json(const nlohmann::json &j, ExPolygon &exPolygon) +{ + if (j.contains("contour")) { + j.at("contour").get_to(exPolygon.contour); + if (j.contains("holes")) { + j.at("holes").get_to(exPolygon.holes); + } + } else { + throw std::runtime_error("Invalid ExPolygon JSON format. Missing 'contour' or 'holes'."); + } +} + +// Serialization for ExPolygons +void to_json(nlohmann::json &j, const std::vector &exPolygons) +{ + j = nlohmann::json::array(); + for (const auto &exPolygon : exPolygons) { + j.push_back(exPolygon); // Serialize each ExPolygon + } +} + +void from_json(const nlohmann::json& j, std::vector& exPolygons) +{ + if (j.is_array()) { + exPolygons.clear(); + for (const auto& item : j) { + exPolygons.push_back(item.get()); + } + } + else { + throw std::runtime_error("Invalid ExPolygons JSON format. Expected array of ExPolygons."); + } +} + +// Function to dump ExPolygons to JSON +void dumpExPolygonToJson(const ExPolygon &exPolygon, const std::string &filePath) +{ + nlohmann::json j = exPolygon; + + // Write JSON to a file + std::ofstream file(filePath); + if (!file) { + std::cerr << "Error: Cannot open file for writing: " << filePath << "\n"; + return; + } + file << j.dump(4); // Pretty print with 4 spaces of indentation + file.close(); + + std::cout << "ExPolygons dumped to " << filePath << "\n"; +} + +// Function to dump ExPolygons to JSON +void dumpExPolygonsToJson(const std::vector &exPolygons, const std::string &filePath) +{ + nlohmann::json j = exPolygons; + + // Write JSON to a file + std::ofstream file(filePath); + if (!file) { + std::cerr << "Error: Cannot open file for writing: " << filePath << "\n"; + return; + } + file << j.dump(4); // Pretty print with 4 spaces of indentation + file.close(); + + std::cout << "ExPolygons dumped to " << filePath << "\n"; +} + +// Function to load ExPolygons from JSON +std::vector loadExPolygonsFromJson(const std::string &filePath) +{ + std::vector exPolygons; + + std::ifstream file(filePath); + if (!file) { + std::cerr << "Error: Cannot open file for reading: " << filePath << "\n"; + return exPolygons; + } + + std::stringstream buffer; + buffer << file.rdbuf(); + std::string content = buffer.str(); // Read entire file into string + + nlohmann::json j; + try { + j = nlohmann::json::parse(content); + //file >> j; // Parse JSON from file + } catch (const nlohmann::json::parse_error &e) { + std::cerr << "JSON parsing error: " << e.what() << std::endl; + return exPolygons; // Return empty vector on failure + } + catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << "\n"; + file.close(); + return exPolygons; + } + file.close(); + + // Deserialize JSON to std::vector + //exPolygons = j.get>(); + if (j.is_array()) { + for (const auto& item : j) { + exPolygons.push_back(item.get()); + } + } else if (j.is_object()) { + exPolygons.push_back(j.get()); + } + else { + throw std::runtime_error("Invalid ExPolygons JSON format. Expected array of ExPolygons."); + } + + return exPolygons; +} + +// Save ExPolygons to a file +void dumpExPolygonsToTxt(const std::vector &exPolygons, const std::string &filePath) +{ + std::ofstream file(filePath); + if (!file) { + std::cerr << "Error: Cannot open file for writing: " << filePath << std::endl; + return; + } + + for (size_t i = 0; i < exPolygons.size(); ++i) { + const auto &exPolygon = exPolygons[i]; + file << "# ExPolygon " << i + 1 << "\n"; + + // Save the outer contour + file << "contour:"; + for (const auto &point : exPolygon.contour) { file << " " << point.x() << " " << point.y(); } + file << "\n"; + + // Save the holes + for (const auto &hole : exPolygon.holes) { + file << "hole:"; + for (const auto &point : hole) { file << " " << point.x() << " " << point.y(); } + file << "\n"; + } + } + + file.close(); + std::cout << "ExPolygons saved to " << filePath << std::endl; +} + +// Load ExPolygons from a file +std::vector loadExPolygonsFromTxt(const std::string &filePath) +{ + std::vector exPolygons; + + std::ifstream file(filePath); + if (!file) { + std::cerr << "Error: Cannot open file for reading: " << filePath << std::endl; + return exPolygons; + } + + std::string line; + ExPolygon currentPolygon; + while (std::getline(file, line)) { + if (line.empty() || line[0] == '#') { + // Start of a new polygon + if (!currentPolygon.contour.empty() || !currentPolygon.holes.empty()) { + exPolygons.push_back(currentPolygon); + currentPolygon = ExPolygon(); + } + continue; + } + + std::istringstream stream(line); + std::string keyword; + stream >> keyword; + + if (keyword == "contour:") { + currentPolygon.contour.clear(); + coord_t x, y; + while (stream >> x >> y) { currentPolygon.contour.append({x, y}); } + } else if (keyword == "hole:") { + Polygon hole; + coord_t x, y; + while (stream >> x >> y) { hole.append({x, y}); } + currentPolygon.holes.push_back(hole); + } + } + + // Add the last polygon if any + if (!currentPolygon.contour.empty() || !currentPolygon.holes.empty()) { exPolygons.push_back(currentPolygon); } + + file.close(); + std::cout << "Loaded " << exPolygons.size() << " ExPolygons from " << filePath << std::endl; + return exPolygons; +} + } diff --git a/src/libslic3r/SVG.hpp b/src/libslic3r/SVG.hpp index 94dafc1..78e6ce1 100644 --- a/src/libslic3r/SVG.hpp +++ b/src/libslic3r/SVG.hpp @@ -80,6 +80,8 @@ public: void draw_grid(const BoundingBox& bbox, const std::string& stroke = "black", coordf_t stroke_width = scale_(0.05), coordf_t step=scale_(1.0)); void add_comment(const std::string comment); + static ExPolygons load(const std::string& filename); + void Close(); private: @@ -177,6 +179,12 @@ private: float to_svg_y(float x) const throw() { return flipY ? this->height - to_svg_coord(x) : to_svg_coord(x); } }; +void dumpExPolygonToJson(const ExPolygon &exPolygon, const std::string &filePath); +void dumpExPolygonsToJson(const std::vector &exPolygons, const std::string &filePath); +std::vector loadExPolygonsFromJson(const std::string &filePath); + +void dumpExPolygonsToTxt(const std::vector &exPolygons, const std::string &filePath); +std::vector loadExPolygonsFromTxt(const std::string &filePath); } #endif diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index 2d93b97..546d038 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -45,7 +45,7 @@ inline coordf_t max_layer_height_from_nozzle(const PrintConfig &print_config, in // Minimum layer height for the variable layer height algorithm. coordf_t Slicing::min_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle) { - coordf_t min_layer_height = print_config.opt_float("min_layer_height", idx_nozzle - 1); + coordf_t min_layer_height = print_config.opt_float_nullable("min_layer_height", idx_nozzle - 1); return (min_layer_height == 0.) ? MIN_LAYER_HEIGHT_DEFAULT : std::max(MIN_LAYER_HEIGHT, min_layer_height); } @@ -54,8 +54,8 @@ coordf_t Slicing::min_layer_height_from_nozzle(const DynamicPrintConfig &print_c coordf_t Slicing::max_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle) { coordf_t min_layer_height = min_layer_height_from_nozzle(print_config, idx_nozzle); - coordf_t max_layer_height = print_config.opt_float("max_layer_height", idx_nozzle - 1); - coordf_t nozzle_dmr = print_config.opt_float("nozzle_diameter", idx_nozzle - 1); + coordf_t max_layer_height = print_config.opt_float_nullable("max_layer_height", idx_nozzle - 1); + coordf_t nozzle_dmr = print_config.opt_float_nullable("nozzle_diameter", idx_nozzle - 1); return std::max(min_layer_height, (max_layer_height == 0.) ? (0.75 * nozzle_dmr) : max_layer_height); } @@ -110,19 +110,17 @@ SlicingParameters SlicingParameters::create_from_config( params.min_layer_height = std::min(params.min_layer_height, params.layer_height); params.max_layer_height = std::max(params.max_layer_height, params.layer_height); - if (! soluble_interface) { - params.gap_raft_object = object_config.raft_contact_distance.value; - //QDS - params.gap_object_support = object_config.support_bottom_z_distance.value; - params.gap_support_object = object_config.support_top_z_distance.value; - if (params.gap_object_support <= 0) - params.gap_object_support = params.gap_support_object; + params.gap_raft_object = object_config.raft_contact_distance.value; + //QDS + params.gap_object_support = object_config.support_bottom_z_distance.value; + params.gap_support_object = object_config.support_top_z_distance.value; + if (params.gap_object_support <= 0) + params.gap_object_support = params.gap_support_object; - if (!print_config.independent_support_layer_height) { - params.gap_raft_object = std::round(params.gap_raft_object / object_config.layer_height + EPSILON) * object_config.layer_height; - params.gap_object_support = std::round(params.gap_object_support / object_config.layer_height + EPSILON) * object_config.layer_height; - params.gap_support_object = std::round(params.gap_support_object / object_config.layer_height + EPSILON) * object_config.layer_height; - } + if (!print_config.independent_support_layer_height) { + params.gap_raft_object = std::round(params.gap_raft_object / object_config.layer_height + EPSILON) * object_config.layer_height; + params.gap_object_support = std::round(params.gap_object_support / object_config.layer_height + EPSILON) * object_config.layer_height; + params.gap_support_object = std::round(params.gap_support_object / object_config.layer_height + EPSILON) * object_config.layer_height; } if (params.base_raft_layers > 0) { diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index 68f9106..9eb0f72 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -145,10 +145,10 @@ std::pair generate_interfa if (support_params.num_top_base_interface_layers > 0) // Some top base interface layers will be generated. top_inteface_z = support_params.num_top_interface_layers_only() == 0 ? - // Only base interface layers to generate. + // Only base interface layers to generate. - std::numeric_limits::max() : intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(support_params.num_top_interface_layers_only()) - 1)]->print_z; - // Move idx_top_contact_first up until above the current print_z. + // Move idx_top_contact_first up until above the current print_z. idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const SupportGeneratorLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON // Collect the top contact areas above this intermediate layer, below top_z. for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) { @@ -208,6 +208,17 @@ std::pair generate_interfa } }); + tbb::parallel_for(tbb::blocked_range(1, int(base_interface_layers.size())), [&base_interface_layers](const tbb::blocked_range &range) { + for (int layer_id = range.begin(); layer_id < range.end(); ++layer_id) { + auto &base_interface_layer = base_interface_layers[layer_id]; + if (!base_interface_layer) return; + + auto &lower_layer = base_interface_layers[layer_id - 1]; + if (!lower_layer) return; + if (base_interface_layer->polygons == lower_layer->polygons) base_interface_layer->up = true; + } + }); + // Compress contact_out, remove the nullptr items. // The parallel_for above may not have merged all the interface and base_interface layers // generated by the Organic supports code, do it here. @@ -1424,6 +1435,11 @@ void generate_support_toolpaths( if (config.support_base_pattern == smpRectilinearGrid) angles.push_back(support_params.interface_angle); + std::vector interface_angles; + if (config.support_interface_pattern == smipRectilinearInterlaced) + interface_angles.push_back(support_params.base_angle); + interface_angles.push_back(support_params.interface_angle); + BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); // const coordf_t link_max_length_factor = 3.; @@ -1542,7 +1558,7 @@ void generate_support_toolpaths( tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), [&config, &slicing_params, &support_params, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, - &bbox_object, &angles, n_raft_layers, link_max_length_factor] + &bbox_object, &angles, &interface_angles, n_raft_layers, link_max_length_factor] (const tbb::blocked_range& range) { // Indices of the 1st layer in their respective container at the support layer height. size_t idx_layer_bottom_contact = size_t(-1); @@ -1577,8 +1593,6 @@ void generate_support_toolpaths( { SupportLayer &support_layer = *support_layers[support_layer_id]; LayerCache &layer_cache = layer_caches[support_layer_id]; - const float support_interface_angle = (support_params.support_style == smsGrid || config.support_interface_pattern == smipRectilinear) ? - support_params.interface_angle : support_params.raft_interface_angle(support_layer.interface_id()); // Find polygons with the same print_z. SupportGeneratorLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; @@ -1662,13 +1676,10 @@ void generate_support_toolpaths( (raft_contact ? &support_params.raft_interface_flow : interface_as_base ? &support_params.support_material_flow : &support_params.support_material_interface_flow) ->with_height(float(layer_ex.layer->height)); - filler->angle = interface_as_base ? - // If zero interface layers are configured, use the same angle as for the base layers. - angles[support_layer_id % angles.size()] : - // Use interface angle for the interface layers. - raft_contact ? - support_params.raft_interface_angle(support_layer.interface_id()) : - support_interface_angle; + // If zero interface layers are configured, use the same angle as for the base layers. + filler->angle = interface_as_base ? angles[support_layer_id % angles.size()] : + raft_contact ? support_params.raft_interface_angle(support_layer.interface_id()) : + interface_angles[support_layer_id % interface_angles.size()]; // Use interface angle for the interface layers. double density = raft_contact ? support_params.raft_interface_density : interface_as_base ? support_params.support_density : support_params.interface_density; filler->spacing = raft_contact ? support_params.raft_interface_flow.spacing() : interface_as_base ? support_params.support_material_flow.spacing() : support_params.support_material_interface_flow.spacing(); @@ -1697,7 +1708,8 @@ void generate_support_toolpaths( // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) assert(! base_interface_layer.layer->bridging); Flow interface_flow = support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); - filler->angle = support_interface_angle; + filler->angle = base_interface_layer.layer->up ? interface_angles[(support_layer_id + 1) % interface_angles.size()] + M_PI_2 : + (angles[(support_layer_id - 1) % angles.size()] + M_PI_2); filler->spacing = support_params.support_material_interface_flow.spacing(); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.interface_density)); fill_expolygons_generate_paths( @@ -1709,7 +1721,7 @@ void generate_support_toolpaths( // Filler and its parameters filler, float(support_params.interface_density), // Extrusion parameters - ExtrusionRole::erSupportMaterial, interface_flow); + ExtrusionRole::erSupportTransition, interface_flow); } // Base support or flange. @@ -1948,5 +1960,123 @@ sub clip_with_shape { } } */ +/*! + * \brief Unions two Polygons. Ensures that if the input is non empty that the output also will be non empty. + * \param first[in] The first Polygon. + * \param second[in] The second Polygon. + * \return The union of both Polygons + */ +[[nodiscard]] Polygons safe_union(const Polygons first, const Polygons second) +{ + // unionPolygons can slowly remove Polygons under certain circumstances, because of rounding issues (Polygons that have a thin area). + // This does not cause a problem when actually using it on large areas, but as influence areas (representing centerpoints) can be very thin, this does occur so this ugly + // workaround is needed Here is an example of a Polygons object that will loose vertices when unioning, and will be gone after a few times unionPolygons was called: + /* + Polygons example; + Polygon exampleInner; + exampleInner.add(Point(120410,83599));//A + exampleInner.add(Point(120384,83643));//B + exampleInner.add(Point(120399,83618));//C + exampleInner.add(Point(120414,83591));//D + exampleInner.add(Point(120423,83570));//E + exampleInner.add(Point(120419,83580));//F + example.add(exampleInner); + for(int i=0;i<10;i++){ + log("Iteration %d Example area: %f\n",i,area(example)); + example=example.unionPolygons(); + } +*/ + + Polygons result; + if (!first.empty() || !second.empty()) { + result = union_(first, second); + if (result.empty()) { + BOOST_LOG_TRIVIAL(debug) << "Caught an area destroying union, enlarging areas a bit."; + // just take the few lines we have, and offset them a tiny bit. Needs to be offsetPolylines, as offset may aleady have problems with the area. + result = union_(offset(to_polylines(first), scaled(0.002), jtMiter, 1.2), offset(to_polylines(second), scaled(0.002), jtMiter, 1.2)); + } + } + + return result; +} +[[nodiscard]] ExPolygons safe_union(const ExPolygons first, const ExPolygons second) +{ + ExPolygons result; + if (!first.empty() || !second.empty()) { + result = union_ex(first, second); + if (result.empty()) { + BOOST_LOG_TRIVIAL(debug) << "Caught an area destroying union, enlarging areas a bit."; + // just take the few lines we have, and offset them a tiny bit. Needs to be offsetPolylines, as offset may aleady have problems with the area. + Polygons result_polys = union_(offset(to_polylines(first), scaled(0.002), jtMiter, 1.2), offset(to_polylines(second), scaled(0.002), jtMiter, 1.2)); + for (auto &poly : result_polys) result.emplace_back(ExPolygon(poly)); + } + } + + return result; +} + +/*! + * \brief Offsets (increases the area of) a polygons object in multiple steps to ensure that it does not lag through over a given obstacle. + * \param me[in] Polygons object that has to be offset. + * \param distance[in] The distance by which me should be offset. Expects values >=0. + * \param collision[in] The area representing obstacles. + * \param last_step_offset_without_check[in] The most it is allowed to offset in one step. + * \param min_amount_offset[in] How many steps have to be done at least. As this uses round offset this increases the amount of vertices, which may be required if Polygons get + * very small. Required as arcTolerance is not exposed in offset, which should result with a similar result. \return The resulting Polygons object. + */ +[[nodiscard]] Polygons safe_offset_inc( + const Polygons &me, coord_t distance, const Polygons &collision, coord_t safe_step_size, coord_t last_step_offset_without_check, size_t min_amount_offset) +{ + bool do_final_difference = last_step_offset_without_check == 0; + Polygons ret = safe_union(me); // ensure sane input + + // Trim the collision polygons with the region of interest for diff() efficiency. + Polygons collision_trimmed_buffer; + auto collision_trimmed = [&collision_trimmed_buffer, &collision, &ret, distance]() -> const Polygons &{ + if (collision_trimmed_buffer.empty() && !collision.empty()) + collision_trimmed_buffer = ClipperUtils::clip_clipper_polygons_with_subject_bbox(collision, get_extents(ret).inflated(std::max(0, distance) + SCALED_EPSILON)); + return collision_trimmed_buffer; + }; + + if (distance == 0) return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret); + if (safe_step_size < 0 || last_step_offset_without_check < 0) { + BOOST_LOG_TRIVIAL(error) << "Offset increase got invalid parameter!"; + return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret); + } + + coord_t step_size = safe_step_size; + int steps = distance > last_step_offset_without_check ? (distance - last_step_offset_without_check) / step_size : 0; + if (distance - steps * step_size > last_step_offset_without_check) { + if ((steps + 1) * step_size <= distance) + // This will be the case when last_step_offset_without_check >= safe_step_size + ++steps; + else + do_final_difference = true; + } + if (steps + (distance < last_step_offset_without_check || (distance % step_size) != 0) < int(min_amount_offset) && min_amount_offset > 1) { + // yes one can add a bool as the standard specifies that a result from compare operators has to be 0 or 1 + // reduce the stepsize to ensure it is offset the required amount of times + step_size = distance / min_amount_offset; + if (step_size >= safe_step_size) { + // effectivly reduce last_step_offset_without_check + step_size = safe_step_size; + steps = min_amount_offset; + } else + steps = distance / step_size; + } + // offset in steps + for (int i = 0; i < steps; ++i) { + ret = diff(offset(ret, step_size, ClipperLib::jtRound, scaled(0.01)), collision_trimmed()); + // ensure that if many offsets are done the performance does not suffer extremely by the new vertices of jtRound. + if (i % 10 == 7) ret = polygons_simplify(ret, scaled(0.015)); + } + // offset the remainder + float last_offset = distance - steps * step_size; + if (last_offset > SCALED_EPSILON) ret = offset(ret, distance - steps * step_size, ClipperLib::jtRound, scaled(0.01)); + ret = polygons_simplify(ret, scaled(0.015)); + + if (do_final_difference) ret = diff(ret, collision_trimmed()); + return union_(ret); +} } // namespace Slic3r diff --git a/src/libslic3r/Support/SupportCommon.hpp b/src/libslic3r/Support/SupportCommon.hpp index f4a6b68..8cca96a 100644 --- a/src/libslic3r/Support/SupportCommon.hpp +++ b/src/libslic3r/Support/SupportCommon.hpp @@ -148,6 +148,80 @@ int idx_lower_or_equal(const std::vector &vec, int idx, FN_LOWER_EQUAL fn_lo return idx_lower_or_equal(vec.begin(), vec.end(), idx, fn_lower_equal); } +[[nodiscard]] Polygons safe_union(const Polygons first, const Polygons second = {}); + +[[nodiscard]] ExPolygons safe_union(const ExPolygons first, const ExPolygons second = {}); + +[[nodiscard]] Polygons safe_offset_inc( + const Polygons &me, coord_t distance, const Polygons &collision, coord_t safe_step_size, coord_t last_step_offset_without_check, size_t min_amount_offset); + + +/*! + * \brief Offsets (increases the area of) a polygons object in multiple steps to ensure that it does not lag through over a given obstacle. + * \param me[in] Polygons object that has to be offset. + * \param distance[in] The distance by which me should be offset. Expects values >=0. + * \param CollisionPolyType collision[in] The area representing obstacles. CollisionPolyType may be ExPolygons or Polygons. + * \param last_step_offset_without_check[in] The most it is allowed to offset in one step. + * \param min_amount_offset[in] How many steps have to be done at least. As this uses round offset this increases the amount of vertices, which may be required if Polygons get + * very small. Required as arcTolerance is not exposed in offset, which should result with a similar result. \return The resulting Polygons object. + */ +template +[[nodiscard]] ExPolygons safe_offset_inc( + const ExPolygons &me, coord_t distance, const CollisionPolyType &collision, coord_t safe_step_size, coord_t last_step_offset_without_check, size_t min_amount_offset) +{ + bool do_final_difference = last_step_offset_without_check == 0; + ExPolygons ret = safe_union(me); // ensure sane input + + // Trim the collision polygons with the region of interest for diff() efficiency. + Polygons collision_trimmed_buffer; + auto collision_trimmed = [&collision_trimmed_buffer, &collision, &ret, distance]() -> const Polygons &{ + if (collision_trimmed_buffer.empty() && !collision.empty()) + collision_trimmed_buffer = ClipperUtils::clip_clipper_polygons_with_subject_bbox(collision, get_extents(ret).inflated(std::max(0, distance) + SCALED_EPSILON)); + return collision_trimmed_buffer; + }; + + if (distance == 0) return do_final_difference ? diff_ex(ret, collision_trimmed()) : union_ex(ret); + if (safe_step_size < 0 || last_step_offset_without_check < 0) { + BOOST_LOG_TRIVIAL(error) << "Offset increase got invalid parameter!"; + return do_final_difference ? diff_ex(ret, collision_trimmed()) : union_ex(ret); + } + + coord_t step_size = safe_step_size; + int steps = distance > last_step_offset_without_check ? (distance - last_step_offset_without_check) / step_size : 0; + if (distance - steps * step_size > last_step_offset_without_check) { + if ((steps + 1) * step_size <= distance) + // This will be the case when last_step_offset_without_check >= safe_step_size + ++steps; + else + do_final_difference = true; + } + if (steps + (distance < last_step_offset_without_check || (distance % step_size) != 0) < int(min_amount_offset) && min_amount_offset > 1) { + // yes one can add a bool as the standard specifies that a result from compare operators has to be 0 or 1 + // reduce the stepsize to ensure it is offset the required amount of times + step_size = distance / min_amount_offset; + if (step_size >= safe_step_size) { + // effectivly reduce last_step_offset_without_check + step_size = safe_step_size; + steps = min_amount_offset; + } else + steps = distance / step_size; + } + // offset in steps + for (int i = 0; i < steps; ++i) { + ret = diff_ex(offset_ex(ret, step_size, ClipperLib::jtRound, scaled(0.01)), collision_trimmed()); + // ensure that if many offsets are done the performance does not suffer extremely by the new vertices of jtRound. + if (i % 10 == 7) ret = expolygons_simplify(ret, scaled(0.015)); + } + // offset the remainder + float last_offset = distance - steps * step_size; + if (last_offset > SCALED_EPSILON) ret = offset_ex(ret, distance - steps * step_size, ClipperLib::jtRound, scaled(0.01)); + ret = expolygons_simplify(ret, scaled(0.015)); + + if (do_final_difference) ret = diff_ex(ret, collision_trimmed()); + return union_ex(ret); +} + + } // namespace Slic3r #endif /* slic3r_SupportCommon_hpp_ */ diff --git a/src/libslic3r/Support/SupportLayer.hpp b/src/libslic3r/Support/SupportLayer.hpp index ac820f7..cc2888a 100644 --- a/src/libslic3r/Support/SupportLayer.hpp +++ b/src/libslic3r/Support/SupportLayer.hpp @@ -110,6 +110,8 @@ namespace Slic3r { size_t idx_object_layer_below{ size_t(-1) }; // Use a bridging flow when printing this support layer. bool bridging{ false }; + //order of the transition layers + bool up{false}; // Polygons to be filled by the support pattern. Polygons polygons; diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index 2f1750c..0766841 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -29,13 +29,13 @@ struct SupportParameters { { this->num_top_interface_layers = std::max(0, object_config.support_interface_top_layers.value); - this->num_bottom_interface_layers = object_config.support_interface_bottom_layers < 0 ? + this->num_bottom_interface_layers = object_config.support_interface_bottom_layers < 0 ? num_top_interface_layers : object_config.support_interface_bottom_layers; this->has_top_contacts = num_top_interface_layers > 0; this->has_bottom_contacts = num_bottom_interface_layers > 0; if (this->soluble_interface_non_soluble_base) { // Try to support soluble dense interfaces with non-soluble dense interfaces. - this->num_top_base_interface_layers = size_t(std::min(int(num_top_interface_layers) / 2, 2)); + this->num_top_base_interface_layers = num_top_interface_layers > 0 ? 2 : 0; this->num_bottom_base_interface_layers = size_t(std::min(int(num_bottom_interface_layers) / 2, 2)); } else { // QDS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion @@ -43,7 +43,7 @@ struct SupportParameters { // support_filament==0 bool differnt_support_interface_filament = object_config.support_interface_filament != 0 && object_config.support_interface_filament != object_config.support_filament; - this->num_top_base_interface_layers = differnt_support_interface_filament ? 1 : 0; + this->num_top_base_interface_layers = differnt_support_interface_filament && num_top_interface_layers > 0 ? 2 : 0; this->num_bottom_base_interface_layers = differnt_support_interface_filament ? 1 : 0; } } @@ -107,21 +107,49 @@ struct SupportParameters { this->interface_density = this->support_density; } - SupportMaterialPattern support_pattern = object_config.support_base_pattern; + support_style = object_config.support_style; + if (support_style != smsDefault) { + if ((support_style == smsSnug || support_style == smsGrid) && is_tree(object_config.support_type)) support_style = smsDefault; + if ((support_style == smsTreeSlim || support_style == smsTreeStrong || support_style == smsTreeHybrid || support_style == smsTreeOrganic) && + !is_tree(object_config.support_type)) + support_style = smsDefault; + } + if (support_style == smsDefault) { + if (is_tree(object_config.support_type)) { + // organic support doesn't work with variable layer heights (including adaptive layer height and height range modifier, see #4313) + if (!object.has_variable_layer_heights && !slicing_params.soluble_interface) { + BOOST_LOG_TRIVIAL(warning) << "tree support default to organic support"; + support_style = smsTreeOrganic; + } else { + BOOST_LOG_TRIVIAL(warning) << "tree support default to hybrid tree due to adaptive layer height"; + support_style = smsTreeHybrid; + } + } else { + support_style = smsGrid; + } + } + + support_base_pattern = object_config.support_base_pattern; + if (support_base_pattern == smpDefault) { + if (is_tree(object_config.support_type)) + support_base_pattern = support_style == smsTreeHybrid ? smpRectilinear : smpNone; + else + support_base_pattern = smpRectilinear; + } + this->with_sheath = object_config.tree_support_wall_count > 0; - this->base_fill_pattern = - support_pattern == smpHoneycomb ? ipHoneycomb : - this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase; + this->base_fill_pattern = support_base_pattern == smpLightning ? ipLightning : + support_base_pattern == smpHoneycomb ? ipHoneycomb : + this->support_density > 0.95 || this->with_sheath ? ipRectilinear : + ipSupportBase; this->interface_fill_pattern = (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); this->raft_interface_fill_pattern = this->raft_interface_density > 0.95 ? ipRectilinear : ipSupportBase; if (object_config.support_interface_pattern == smipGrid) this->contact_fill_pattern = ipGrid; - else if (object_config.support_interface_pattern == smipRectilinearInterlaced) + else if (object_config.support_interface_pattern == smipRectilinearInterlaced || object_config.support_interface_pattern == smipAuto) this->contact_fill_pattern = ipRectilinear; else - this->contact_fill_pattern = - (object_config.support_interface_pattern == smipAuto && slicing_params.soluble_interface) || - object_config.support_interface_pattern == smipConcentric ? + this->contact_fill_pattern = object_config.support_interface_pattern == smipConcentric ? ipConcentric : (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); @@ -163,34 +191,27 @@ struct SupportParameters { support_extrusion_width = Flow::auto_extrusion_width(FlowRole::frSupportMaterial, (float) nozzle_diameter); } - independent_layer_height = print_config.independent_support_layer_height; - - // force double walls everywhere if wall count is larger than 1 - tree_branch_diameter_double_wall_area_scaled = object_config.tree_support_wall_count.value > 1 ? 0.1 : - object_config.tree_support_wall_count.value == 0 ? 0.25 * sqr(scaled(5.0)) * M_PI : - std::numeric_limits::max(); - - support_style = object_config.support_style; - if (support_style != smsDefault) { - if ((support_style == smsSnug || support_style == smsGrid) && is_tree(object_config.support_type)) support_style = smsDefault; - if ((support_style == smsTreeSlim || support_style == smsTreeStrong || support_style == smsTreeHybrid || support_style == smsTreeOrganic) && - !is_tree(object_config.support_type)) - support_style = smsDefault; - } - if (support_style == smsDefault) { - if (is_tree(object_config.support_type)) { - // organic support doesn't work with variable layer heights (including adaptive layer height and height range modifier, see #4313) - if (!object.has_variable_layer_heights) { - BOOST_LOG_TRIVIAL(warning) << "tree support default to organic support"; - support_style = smsTreeOrganic; - } else { - BOOST_LOG_TRIVIAL(warning) << "tree support default to hybrid tree due to adaptive layer height"; - support_style = smsTreeHybrid; - } - } else { - support_style = smsGrid; + double tree_support_branch_diameter_double_wall = 3.0; + // get support filament strength and decide the thresh of double wall area + float support_filament_strength = print_config.impact_strength_z.get_at(object_config.support_filament-1); + if(object_config.support_filament==0){ + // find the weakest filament + support_filament_strength = std::numeric_limits::max(); + for(auto extruder:object.object_extruders()){ + float strength = print_config.impact_strength_z.get_at(extruder); + if(strengthtree_branch_diameter_double_wall_area_scaled = 0.25*sqr(scaled(tree_support_branch_diameter_double_wall))*M_PI; + }else{ + // force double walls everywhere if wall count is larger than 1 + this->tree_branch_diameter_double_wall_area_scaled = object_config.tree_support_wall_count.value>1? 0.1: std::numeric_limits::max(); + } + + independent_layer_height = print_config.independent_support_layer_height; } // Both top / bottom contacts and interfaces are soluble. bool soluble_interface; @@ -214,7 +235,7 @@ struct SupportParameters { bool has_contacts() const { return this->has_top_contacts || this->has_bottom_contacts; } bool has_interfaces() const { return this->num_top_interface_layers + this->num_bottom_interface_layers > 0; } bool has_base_interfaces() const { return this->num_top_base_interface_layers + this->num_bottom_base_interface_layers > 0; } - size_t num_top_interface_layers_only() const { return this->num_top_interface_layers - this->num_top_base_interface_layers; } + size_t num_top_interface_layers_only() const { return std::max(0, int(this->num_top_interface_layers) - int(this->num_top_base_interface_layers)); } size_t num_bottom_interface_layers_only() const { return this->num_bottom_interface_layers - this->num_bottom_base_interface_layers; } Flow first_layer_flow; Flow support_material_flow; @@ -242,6 +263,7 @@ struct SupportParameters { coordf_t support_spacing; coordf_t support_density; SupportMaterialStyle support_style = smsDefault; + SupportMaterialPattern support_base_pattern = smpDefault; InfillPattern base_fill_pattern; InfillPattern interface_fill_pattern; @@ -257,10 +279,10 @@ struct SupportParameters { float raft_angle_interface; // Produce a raft interface angle for a given SupportLayer::interface_id() - float raft_interface_angle(size_t interface_id) const + float raft_interface_angle(size_t interface_id) const { return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); } bool independent_layer_height = false; - const double thresh_big_overhang = Slic3r::sqr(scale_(10)); + const double thresh_big_overhang = /*Slic3r::sqr(scale_(10))*/scale_(10); }; } // namespace Slic3r \ No newline at end of file diff --git a/src/libslic3r/Support/TreeModelVolumes.cpp b/src/libslic3r/Support/TreeModelVolumes.cpp index f87d694..9d0faee 100644 --- a/src/libslic3r/Support/TreeModelVolumes.cpp +++ b/src/libslic3r/Support/TreeModelVolumes.cpp @@ -67,6 +67,14 @@ TreeModelVolumes::TreeModelVolumes( m_machine_border{ calculateMachineBorderCollision(build_volume.polygon()) } { m_bed_area = build_volume.polygon(); + Polygons machine_borders; + if (!m_bed_area.empty()) { + Polygon hole(m_bed_area); + hole.reverse(); + ExPolygon machine_outline(offset(m_bed_area, scale_(1000))[0], hole); + ExPolygons outlines = machine_outline.split_expoly_with_holes(scale_(1.), {}); + for (const auto &outline : outlines) machine_borders.emplace_back(outline.contour); + } #if 0 std::unordered_map mesh_to_layeroutline_idx; for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); ++ mesh_idx) { @@ -110,8 +118,10 @@ TreeModelVolumes::TreeModelVolumes( outlines.assign(num_layers, Polygons{}); tbb::parallel_for(tbb::blocked_range(num_raft_layers, num_layers, std::min(1, std::max(16, num_layers / (8 * tbb::this_task_arena::max_concurrency())))), [&](const tbb::blocked_range &range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) - outlines[layer_idx] = polygons_simplify(to_polygons(print_object.get_layer(layer_idx - num_raft_layers)->lslices), mesh_settings.resolution); + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + outlines[layer_idx] = polygons_simplify(to_polygons(print_object.get_layer(layer_idx - num_raft_layers)->lslices), mesh_settings.resolution); + outlines[layer_idx].insert(outlines[layer_idx].end(), machine_borders.begin(), machine_borders.end()); + } }); } #endif @@ -662,6 +672,7 @@ void TreeModelVolumes::calculateAvoidance(const std::vector &ke // Limiting the offset step so that unioning the shrunk latest_avoidance with the current layer collisions // will not create gaps in the resulting avoidance region letting a tree support branch tunneling through an object wall. float move_step = 1.9 * std::max(task.radius, m_current_min_xy_dist); + if (move_step < EPSILON) return; int move_steps = round_up_divide(max_move, move_step); assert(move_steps > 0); float last_move_step = max_move - (move_steps - 1) * move_step; diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index 9570620..1bbef6f 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -2,7 +2,9 @@ #include #include "format.hpp" +#include "BuildVolume.hpp" #include "ClipperUtils.hpp" +#include "Clipper2Utils.hpp" #include "Fill/FillBase.hpp" #include "I18N.hpp" #include "Layer.hpp" @@ -24,6 +26,13 @@ #include #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include #ifndef M_PI #define M_PI 3.1415926535897932384626433832795 @@ -42,6 +51,7 @@ #endif namespace Slic3r { +extern bool compSecondMoment(const ExPolygons &expolys, double &smExpolysX, double &smExpolysY); // Brim.cpp #define unscale_(val) ((val) * SCALING_FACTOR) extern void generate_tree_support_3D(PrintObject& print_object, TreeSupport* tree_support, std::function throw_on_cancel); @@ -603,7 +613,7 @@ TreeSupport::TreeSupport(PrintObject& object, const SlicingParameters &slicing_p SupportMaterialPattern support_pattern = m_object_config->support_base_pattern; if (m_support_params.support_style == smsTreeHybrid && support_pattern == smpDefault) - support_pattern = smpRectilinear; + support_pattern = smpRectilinear;//smpLightning; if(support_pattern == smpLightning) m_support_params.base_fill_pattern = ipLightning; @@ -612,13 +622,16 @@ TreeSupport::TreeSupport(PrintObject& object, const SlicingParameters &slicing_p is_strong = is_tree(support_type) && m_support_params.support_style == smsTreeStrong; base_radius = std::max(MIN_BRANCH_RADIUS, m_object_config->tree_support_branch_diameter.value / 2); // by default tree support needs no infill, unless it's tree hybrid which contains normal nodes. - with_infill = support_pattern != smpNone && support_pattern != smpDefault; + with_infill = m_support_params.support_base_pattern != smpNone; m_machine_border.contour = get_bed_shape_with_excluded_area(*m_print_config); Vec3d plate_offset = m_object->print()->get_plate_origin(); // align with the centered object in current plate (may not be the 1st plate, so need to add the plate offset) m_machine_border.translate(Point(scale_(plate_offset(0)), scale_(plate_offset(1))) - m_object->instances().front().shift); + + m_ts_data = m_object->alloc_tree_support_preview_cache(); top_z_distance = m_object_config->support_top_z_distance.value; - if (top_z_distance > EPSILON) top_z_distance = std::max(top_z_distance, float(m_slicing_params.min_layer_height)); + if (top_z_distance > EPSILON) + top_z_distance = std::max(top_z_distance, float(m_slicing_params.min_layer_height)); #if USE_TREESUPPRT3D m_support_params.independent_layer_height = false; // do not use independent layer height for 3D tree support #endif @@ -626,8 +639,24 @@ TreeSupport::TreeSupport(PrintObject& object, const SlicingParameters &slicing_p SVG svg(debug_out_path("machine_boarder.svg"), m_object->bounding_box()); if (svg.is_opened()) svg.draw(m_machine_border, "yellow"); #endif +BOOST_LOG_TRIVIAL(debug) << "tree support construct finish"; } +void add_overhang(Layer *layer, const ExPolygon &overhang, int type) +{ + layer->loverhangs_with_type.emplace_back(overhang, type); + layer->loverhangs.emplace_back(overhang); +} + +bool is_stable(float height, const ExPolygon &overhang, float strength_z) +{ + float stability = 1. * height; + double Ixx = 0, Iyy = 0; + auto props = compSecondMoment({overhang}, Ixx, Iyy); + // stability += 0.5 * props.centroid.norm() * SCALING_FACTOR; + double moment = std::min(Ixx * pow(SCALING_FACTOR, 4), Iyy * pow(SCALING_FACTOR, 4)); + return moment / stability > 3.; +} #define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) @@ -655,6 +684,8 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) const int enforce_support_layers = config.enforce_support_layers.value; const double area_thresh_well_supported = SQ(scale_(6)); const double length_thresh_well_supported = scale_(6); + const double length_thresh_small_overhang = scale_(2); + const double radius_thresh_small_overhang = 2.5*extrusion_width_scaled; static const double sharp_tail_max_support_height = 16.f; // a region is considered well supported if the number of layers below it exceeds this threshold const int thresh_layers_below = 10 / config.layer_height; @@ -668,20 +699,33 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) // for small overhang removal struct OverhangCluster { - std::map layer_overhangs; + std::map layer_overhangs; ExPolygons merged_poly; BoundingBox merged_bbox; int min_layer = 1e7; int max_layer = 0; coordf_t offset = 0; - bool is_cantilever = false; - bool is_sharp_tail = false; - bool is_small_overhang = false; + + OverhangType type = Normal; + bool is_cantilever() { return type & Cantilever; } + bool is_sharp_tail() { return type & SharpTail; } + bool is_type(OverhangType type_mask) { return type & type_mask; } + void set_type(OverhangType type_mask, bool is) + { + if (is) + type = OverhangType(type | type_mask); + else + type = OverhangType(type & (~type_mask)); + } OverhangCluster(const ExPolygon* expoly, int layer_nr) { push_back(expoly, layer_nr); } void push_back(const ExPolygon* expoly, int layer_nr) { - layer_overhangs.emplace(layer_nr, expoly); + auto it = layer_overhangs.find(layer_nr); + if (it == layer_overhangs.end()) + layer_overhangs.emplace(layer_nr, ExPolygons{*expoly}); + else + layer_overhangs[layer_nr].emplace_back(*expoly); auto dilate1 = offset_ex(*expoly, offset); if (!dilate1.empty()) merged_poly = union_ex(merged_poly, dilate1); @@ -696,36 +740,40 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) if (layer_nr < 1) return false; auto it = layer_overhangs.find(layer_nr - 1); if (it == layer_overhangs.end()) return false; - const ExPolygon* overhang = it->second; + const ExPolygons overhangs = it->second; this->offset = offset; auto dilate1 = offset_ex(region, offset); BoundingBox bbox = get_extents(dilate1); if (!merged_bbox.overlap(bbox)) return false; - return overlaps({ *overhang }, dilate1); + return overlaps(overhangs, dilate1); } // it's basically the combination of push_back and intersects, but saves an offset_ex bool push_back_if_intersects(const ExPolygon& region, int layer_nr, coordf_t offset) { bool is_intersect = false; + this->offset = offset; ExPolygons dilate1; BoundingBox bbox; do { if (layer_nr < 1) break; auto it = layer_overhangs.find(layer_nr - 1); if (it == layer_overhangs.end()) break; - const ExPolygon* overhang = it->second; + const ExPolygons overhangs = it->second; - this->offset = offset; dilate1 = offset_ex(region, offset); if (dilate1.empty()) break; bbox = get_extents(dilate1); if (!merged_bbox.overlap(bbox)) break; - is_intersect = overlaps({ *overhang }, dilate1); + is_intersect = overlaps(overhangs, dilate1); } while (0); if (is_intersect) { - layer_overhangs.emplace(layer_nr, ®ion); + auto it = layer_overhangs.find(layer_nr); + if (it == layer_overhangs.end()) + layer_overhangs.emplace(layer_nr, ExPolygons{region}); + else + layer_overhangs[layer_nr].emplace_back(region); merged_poly = union_ex(merged_poly, dilate1); min_layer = std::min(min_layer, layer_nr); max_layer = std::max(max_layer, layer_nr); @@ -733,6 +781,56 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) } return is_intersect; } + void check_small_overhang(coordf_t extrusion_width_scaled, double length_thresh_small_overhang, double radius_thresh_small_overhang) + { + bool is_small_overhang = false; + if ((type & SharpTail) || (type & Cantilever)) { + set_type(Small, false); + return; + } + + bool all_layers_are_small = true; + float avg_width = 0; + float avg_area = 0; + int num_layers = 0; + for (auto it = layer_overhangs.begin(); it != layer_overhangs.end(); it++) { + const ExPolygons overhangs = it->second; + ExPolygons erodel = offset_ex(overhangs, -extrusion_width_scaled / 2); + coord_t narrow_width = 0; + for (auto &poly : erodel) { + Point bbox_sz = get_extents(poly).size(); + narrow_width = std::max(narrow_width, std::min(bbox_sz.x(), bbox_sz.y())); + } + if (narrow_width > extrusion_width_scaled) all_layers_are_small = false; + avg_width += narrow_width; + avg_area += area(overhangs); + num_layers++; + } + avg_width /= num_layers; + avg_area /= num_layers; + is_small_overhang = all_layers_are_small && avg_width < extrusion_width_scaled / 2 && avg_area < SQ(length_thresh_small_overhang) && this->height() < 3; + if (is_small_overhang) { + set_type(Small, true); + return; + } + + ExPolygons erodel = offset_ex(merged_poly, -radius_thresh_small_overhang); + Point bbox_sz = get_extents(erodel).size(); + if (erodel.empty() || + (bbox_sz.x() < length_thresh_small_overhang && bbox_sz.y() < length_thresh_small_overhang && this->height() < length_thresh_small_overhang / 2)) { + is_small_overhang = true; + set_type(Small, true); + } + } + + void check_polygon_node(float thresh_big_overhang, const ExPolygons &m_layer_outlines_below) + { + ExPolygons outlines_below_eroded = offset_ex(m_layer_outlines_below, -this->offset); + if (area(merged_poly) > SQ(thresh_big_overhang)) + set_type(BigFlat, true); + else if (merged_bbox.size().any_comp(thresh_big_overhang, ">") && min_layer < 20) + if (!overlaps(merged_poly, outlines_below_eroded)) set_type(ThinPlate, true); + } }; std::vector overhangClusters; @@ -766,6 +864,10 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) // Filter out areas whose diameter that is smaller than extrusion_width, but we don't want to lose any details. layer->lslices_extrudable = intersection_ex(layer->lslices, offset2_ex(layer->lslices, -extrusion_width_scaled / 2, extrusion_width_scaled)); layer->loverhangs.clear(); + layer->loverhangs_with_type.clear(); + layer->sharp_tails.clear(); + layer->sharp_tails_height.clear(); + layer->cantilevers.clear(); } }); @@ -788,7 +890,7 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) if (layer->lower_layer == nullptr) { for (auto& slice : layer->lslices_extrudable) { auto bbox_size = get_extents(slice).size(); - if (!((bbox_size.x() > length_thresh_well_supported && bbox_size.y() > length_thresh_well_supported)) + if (!((bbox_size.x() > length_thresh_well_supported || bbox_size.y() > length_thresh_well_supported)) && g_config_support_sharp_tails) { layer->sharp_tails.push_back(slice); layer->sharp_tails_height.push_back(layer->height); @@ -799,7 +901,7 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) Layer* lower_layer = layer->lower_layer; coordf_t lower_layer_offset = layer_nr < enforce_support_layers ? -0.15 * extrusion_width : (float)lower_layer->height / tan(threshold_rad); - //lower_layer_offset = std::min(lower_layer_offset, extrusion_width); + lower_layer_offset = std::min(lower_layer_offset, extrusion_width); coordf_t support_offset_scaled = scale_(lower_layer_offset); ExPolygons& curr_polys = layer->lslices_extrudable; ExPolygons& lower_polys = lower_layer->lslices_extrudable; @@ -822,10 +924,13 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) bool is_sharp_tail = false; // 1. nothing below // this is a sharp tail region if it's floating and non-ignorable - if (!overlaps(offset_ex(expoly, 0.1 * extrusion_width_scaled), lower_polys)) { + if (!overlaps(offset_ex(expoly, 0.1 * extrusion_width_scaled), lower_polys) && area(expoly)lower_layer) { + if (overlaps(offset_ex(expoly, 0.1 * extrusion_width_scaled), lower_layer->lower_layer->lslices_extrudable)) + is_sharp_tail = false; + } if (is_sharp_tail) { layer->sharp_tails.push_back(expoly); layer->sharp_tails_height.push_back(0); @@ -840,22 +945,48 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) // check cantilever + auto lower_layer_support_thresh = offset_ex(lower_polys, extrusion_width_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS); // lower_layer_offset may be very small, so we need to do max and then add 0.1 - lower_layer_offseted = offset_ex(lower_layer_offseted, scale_(std::max(extrusion_width - lower_layer_offset, 0.) + 0.1)); - for (ExPolygon& poly : overhangs_all_layers[layer_nr]) { - auto cluster_boundary_ex = intersection_ex(poly, lower_layer_offseted); - Polygons cluster_boundary = to_polygons(cluster_boundary_ex); - if (cluster_boundary.empty()) continue; + lower_layer_offseted = offset_ex(lower_layer_offseted, scale_(std::max(extrusion_width - lower_layer_offset, 0.) + 0.1)); + for (ExPolygon &poly : overhangs_all_layers[layer_nr]) { + // check if there is some contour that is totally floating + bool is_cantilever = false; double dist_max = 0; - for (auto& pt : poly.contour.points) { - double dist_pt = std::numeric_limits::max(); - for (auto& ply : cluster_boundary) { - double d = ply.distance_to(pt); - dist_pt = std::min(dist_pt, d); + for (size_t i = 0; i < poly.num_contours(); i++) { + Polygon contour = poly.contour_or_hole(i); + bool is_floating = true; + double tmp_dist_max = 0; + for (const auto &pt : contour.points) { + if (is_inside_ex(lower_layer_support_thresh, pt)) { + is_floating = false; + break; + } else + tmp_dist_max = std::max(tmp_dist_max, (projection_onto(lower_layer_support_thresh, pt) - pt).cast().norm()); + } + if (is_floating) { + is_cantilever = true; + dist_max = tmp_dist_max; + break; } - dist_max = std::max(dist_max, dist_pt); } - if (dist_max > scale_(3)) { // is cantilever if the farmost point is larger than 3mm away from base + if (!is_cantilever) { + dist_max = 0; + auto cluster_boundary_ex = intersection_ex(poly, lower_layer_offseted); + Polygons cluster_boundary = to_polygons(cluster_boundary_ex); + if (cluster_boundary.empty()) continue; + + for (auto &pt : poly.contour.points) { + double dist_pt = std::numeric_limits::max(); + for (auto &ply : cluster_boundary) { + double d = ply.distance_to(pt); + dist_pt = std::min(dist_pt, d); + } + dist_max = std::max(dist_max, dist_pt); + } + is_cantilever = dist_max > scale_(3); + } + // is cantilever if the farmost point is larger than 3mm away from base or some contour is totally floating + if (is_cantilever) { max_cantilever_dist = std::max(max_cantilever_dist, dist_max); layer->cantilevers.emplace_back(poly); BOOST_LOG_TRIVIAL(debug) << "found a cantilever cluster. layer_nr=" << layer_nr << dist_max; @@ -953,8 +1084,7 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) Layer* layer = m_object->get_layer(layer_nr); for (auto& overhang : overhangs_all_layers[layer_nr]) { OverhangCluster* cluster = find_and_insert_cluster(overhangClusters, overhang, layer_nr, extrusion_width_scaled); - if (overlaps({ overhang }, layer->cantilevers)) - cluster->is_cantilever = true; + if (overlaps({overhang}, layer->cantilevers)) cluster->set_type(Cantilever, true); } } @@ -964,49 +1094,40 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) m_object->project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers, &m_vertical_enforcer_points); m_object->project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers); - if (is_auto(stype) && config_remove_small_overhangs) { + for (auto &cluster : overhangClusters) { // remove small overhangs - for (auto& cluster : overhangClusters) { + if (is_auto(stype) && config_remove_small_overhangs) { // 3. check whether the small overhang is sharp tail - cluster.is_sharp_tail = false; + cluster.set_type(SharpTail, false); for (size_t layer_id = cluster.min_layer; layer_id <= cluster.max_layer; layer_id++) { - Layer* layer = m_object->get_layer(layer_id); - if (overlaps(layer->sharp_tails, cluster.merged_poly)) { - cluster.is_sharp_tail = true; + Layer *layer = m_object->get_layer(layer_id); + if (overlaps(layer->sharp_tails, cluster.layer_overhangs[layer_id])) { + cluster.set_type(SharpTail, true); break; } } - if (!cluster.is_sharp_tail && !cluster.is_cantilever) { - // 2. check overhang cluster size is smaller than 3.0 * fw_scaled - auto erode1 = offset_ex(cluster.merged_poly, -1 * extrusion_width_scaled); - Point bbox_sz = get_extents(erode1).size(); - if (bbox_sz.x() < 2 * extrusion_width_scaled || bbox_sz.y() < 2 * extrusion_width_scaled) { - cluster.is_small_overhang = true; - } - } + cluster.check_small_overhang(extrusion_width_scaled, length_thresh_small_overhang, radius_thresh_small_overhang); #ifdef SUPPORT_TREE_DEBUG_TO_SVG - const Layer* layer1 = m_object->get_layer(cluster.min_layer); - std::string fname = debug_out_path("overhangCluster_%d-%d_%.2f-%.2f_tail=%d_cantilever=%d_small=%d.svg", - cluster.min_layer, cluster.max_layer, layer1->print_z, m_object->get_layer(cluster.max_layer)->print_z, - cluster.is_sharp_tail, cluster.is_cantilever, cluster.is_small_overhang); - SVG::export_expolygons(fname, { - { layer1->lslices, {"min_layer_lslices","red",0.5} }, - { m_object->get_layer(cluster.max_layer)->lslices, {"max_layer_lslices","yellow",0.5} }, - { cluster.merged_poly,{"overhang", "blue", 0.5} }, - { cluster.is_cantilever? layer1->cantilevers: offset_ex(cluster.merged_poly, -1 * extrusion_width_scaled), {cluster.is_cantilever ? "cantilever":"erode1","green",0.5}} }); + const Layer *layer1 = m_object->get_layer(cluster.min_layer); + std::string fname = debug_out_path("overhangCluster_%d-%d_%.2f-%.2f_tail=%d_cantilever=%d_small=%d.svg", cluster.min_layer, cluster.max_layer, layer1->print_z, + m_object->get_layer(cluster.max_layer)->print_z, cluster.is_sharp_tail(), cluster.is_cantilever(), cluster.is_type(Small)); + SVG::export_expolygons(fname, {{layer1->lslices, {"min_layer_lslices", "red", 0.5}}, + {m_object->get_layer(cluster.max_layer)->lslices, {"max_layer_lslices", "yellow", 0.5}}, + {cluster.merged_poly, {"overhang", "blue", 0.5}}, + {cluster.is_cantilever() ? layer1->cantilevers : offset_ex(cluster.merged_poly, -1 * extrusion_width_scaled), + {cluster.is_cantilever() ? "cantilever" : "erode1", "green", 0.5}}}); #endif } - } - for (auto& cluster : overhangClusters) { - if (cluster.is_small_overhang) continue; - // collect overhangs that's not small overhangs - for (auto it = cluster.layer_overhangs.begin(); it != cluster.layer_overhangs.end(); it++) { - int layer_nr = it->first; - auto p_overhang = it->second; - m_object->get_layer(layer_nr)->loverhangs.emplace_back(*p_overhang); + if (!cluster.is_type(Small)) { + cluster.check_polygon_node(m_support_params.thresh_big_overhang, m_ts_data->m_layer_outlines_below[cluster.min_layer - 1]); + for (auto it = cluster.layer_overhangs.begin(); it != cluster.layer_overhangs.end(); it++) { + int layer_nr = it->first; + ExPolygons overhangs = it->second; + for (const auto &overhang : overhangs) add_overhang(m_object->get_layer(layer_nr), overhang, cluster.type); + } } } @@ -1019,6 +1140,12 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) auto layer = m_object->get_layer(layer_nr); auto lower_layer = layer->lower_layer; + if (support_critical_regions_only && is_auto(stype)) { + layer->loverhangs.clear(); // remove oridinary overhangs, only keep cantilevers and sharp tails (added later) + layer->loverhangs_with_type.clear(); + for (auto &cantilever : layer->cantilevers) add_overhang(layer, cantilever, OverhangType::Cantilever); + } + // add support for every 1mm height for sharp tails ExPolygons sharp_tail_overhangs; if (lower_layer == nullptr) @@ -1031,6 +1158,8 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) if (!areas.empty() && int(accum_height * 10) % 5 == 0) { append(sharp_tail_overhangs, areas); has_sharp_tails = true; + for (auto &area : areas) + add_overhang(layer, area, accum_height < EPSILON ? (OverhangType::SharpTail | OverhangType::SharpTailLowesst) : OverhangType::SharpTail); #ifdef SUPPORT_TREE_DEBUG_TO_SVG SVG::export_expolygons(debug_out_path("sharp_tail_%.02f.svg", layer->print_z), areas); #endif @@ -1042,14 +1171,13 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) // Arthur: union_ is a must because after mirroring, the blocker polygons are in left-hand coordinates, ie clockwise, // which are not valid polygons, and will be removed by offset_ex. union_ can make these polygons right. ExPolygons blocker = offset_ex(union_(blockers[layer_nr]), scale_(radius_sample_resolution)); - layer->loverhangs = diff_ex(layer->loverhangs, blocker); - layer->cantilevers = diff_ex(layer->cantilevers, blocker); - sharp_tail_overhangs = diff_ex(sharp_tail_overhangs, blocker); - } - - if (support_critical_regions_only && is_auto(stype)) { - layer->loverhangs.clear(); // remove oridinary overhangs, only keep cantilevers and sharp tails (added later) - append(layer->loverhangs, layer->cantilevers); + auto old_overhangs_with_type = layer->loverhangs_with_type; + layer->loverhangs.clear(); + layer->loverhangs_with_type.clear(); + for (auto &poly : old_overhangs_with_type) { + ExPolygons polydiff = diff_ex(poly.first, blocker); + for (auto &diff : polydiff) add_overhang(layer, diff, OverhangType(poly.second)); + } } if (max_bridge_length > 0 && layer->loverhangs.size() > 0 && lower_layer) { @@ -1065,24 +1193,24 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) if (!enforced_overhangs.empty()) { // FIXME this is a hack to make enforcers work on steep overhangs. See STUDIO-7538. enforced_overhangs = diff_ex(offset_ex(enforced_overhangs, enforcer_overhang_offset), lower_layer->lslices_extrudable); - append(layer->loverhangs, enforced_overhangs); + for (auto &poly : enforced_overhangs) add_overhang(layer, poly, OverhangType::Normal); } } int nEnforced = layer->loverhangs.size(); - // add sharp tail overhangs - append(layer->loverhangs, sharp_tail_overhangs); + //// add sharp tail overhangs + //append(layer->loverhangs, sharp_tail_overhangs); - // fill overhang_types - for (size_t i = 0; i < layer->loverhangs.size(); i++) - overhang_types.emplace(&layer->loverhangs[i], i < nDetected ? OverhangType::Detected : - i < nEnforced ? OverhangType::Enforced : OverhangType::SharpTail); + //// fill overhang_types + //for (size_t i = 0; i < layer->loverhangs.size(); i++) + // overhang_types.emplace(&layer->loverhangs[i], i < nDetected ? OverhangType::Detected : + // i < nEnforced ? OverhangType::Enforced : OverhangType::SharpTail); - if (!layer->loverhangs.empty()) { + if (!layer->loverhangs_with_type.empty()) { layers_with_overhangs++; m_highest_overhang_layer = std::max(m_highest_overhang_layer, size_t(layer_nr)); } - if (nEnforced > 0) layers_with_enforcers++; + //if (nEnforced > 0) layers_with_enforcers++; if (!layer->cantilevers.empty()) has_cantilever = true; } @@ -1124,6 +1252,7 @@ void TreeSupport::create_tree_support_layers() if (m_raft_layers > 0) { //create raft layers coordf_t raft_print_z = 0.f; coordf_t raft_slice_z = 0.f; + if (m_slicing_params.base_raft_layers > 0) { // Do not add the raft contact layer, 1st layer should use first_print_layer_height coordf_t height = m_slicing_params.first_print_layer_height; @@ -1317,7 +1446,7 @@ void TreeSupport::generate_toolpaths() // coconut: use same intensity settings as SupportMaterial.cpp auto m_support_material_interface_flow = support_material_interface_flow(m_object, float(m_slicing_params.layer_height)); coordf_t interface_spacing = object_config.support_interface_spacing.value + m_support_material_interface_flow.spacing(); - coordf_t bottom_interface_spacing = object_config.support_bottom_interface_spacing.value + m_support_material_interface_flow.spacing(); + coordf_t bottom_interface_spacing = interface_spacing; coordf_t interface_density = std::min(1., m_support_material_interface_flow.spacing() / interface_spacing); coordf_t bottom_interface_density = std::min(1., m_support_material_interface_flow.spacing() / bottom_interface_spacing); @@ -1427,14 +1556,6 @@ void TreeSupport::generate_toolpaths() if (m_object->support_layer_count() <= m_raft_layers) return; - BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); - - std::shared_ptr filler_interface = std::shared_ptr(Fill::new_from_type(m_support_params.contact_fill_pattern)); - std::shared_ptr filler_Roof1stLayer = std::shared_ptr(Fill::new_from_type(ipRectilinear)); - filler_interface->set_bounding_box(bbox_object); - filler_Roof1stLayer->set_bounding_box(bbox_object); - filler_interface->angle = Geometry::deg2rad(object_config.support_angle.value + 90.); - filler_Roof1stLayer->angle = Geometry::deg2rad(object_config.support_angle.value + 90.); // generate tree support tool paths tbb::parallel_for( @@ -1445,6 +1566,10 @@ void TreeSupport::generate_toolpaths() if (m_object->print()->canceled()) break; + BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); + std::shared_ptr filler_interface = std::shared_ptr(Fill::new_from_type(m_support_params.contact_fill_pattern)); + std::shared_ptr filler_Roof1stLayer = std::shared_ptr(Fill::new_from_type(ipRectilinear)); + //m_object->print()->set_status(70, (boost::format(_u8L("Support: generate toolpath at layer %d")) % layer_id).str()); SupportLayer* ts_layer = m_object->get_support_layer(layer_id); @@ -1455,6 +1580,12 @@ void TreeSupport::generate_toolpaths() ts_layer->support_fills.no_sort = false; for (auto& area_group : ts_layer->area_groups) { + + filler_interface->set_bounding_box(bbox_object); + filler_Roof1stLayer->set_bounding_box(bbox_object); + filler_interface->angle = Geometry::deg2rad(object_config.support_angle.value + 90.); + filler_Roof1stLayer->angle = Geometry::deg2rad(object_config.support_angle.value + 90.); + ExPolygon& poly = *area_group.area; ExPolygons polys; FillParams fill_params; @@ -1475,13 +1606,34 @@ void TreeSupport::generate_toolpaths() fill_params.dont_adjust = true; } if (area_group.type == SupportLayer::Roof1stLayer) { - // roof_1st_layer - fill_params.density = interface_density; + // use higher flow for roof_1st_layer ? + Flow roof_1st_layer_flow = support_material_interface_flow(m_object, ts_layer->height)/*.with_flow_ratio(1.5)*/; + // Note: spacing means the separation between two lines as if they are tightly extruded - filler_Roof1stLayer->spacing = interface_flow.spacing(); + filler_Roof1stLayer->spacing = roof_1st_layer_flow.spacing(); + + auto interface_layer_above = m_object->get_support_layer(layer_id + m_support_params.num_top_interface_layers); + if (interface_layer_above) { + Layer *bridge_layer = m_object->get_layer_at_bottomz(interface_layer_above->print_z + object_config.support_top_z_distance.value, layer_height / 10.); + if (bridge_layer) { + for (size_t region_id = 0; region_id < bridge_layer->regions().size(); ++region_id) { + LayerRegion *layerm = bridge_layer->regions()[region_id]; + bool bridge_found = false; + for (const auto surface : layerm->fill_surfaces.surfaces) { + if (surface.surface_type == stBottomBridge && overlaps(polys, surface.expolygon)) { + filler_Roof1stLayer->angle = surface.bridge_angle + (m_support_params.num_top_interface_layers - 1) * M_PI_2; + bridge_found = true; + break; + } + } + if (bridge_found) break; + } + } + } + // generate a perimeter first to support interface better ExtrusionEntityCollection* temp_support_fills = new ExtrusionEntityCollection(); - make_perimeter_and_infill(temp_support_fills->entities, poly, 1, interface_flow, erSupportMaterial, + make_perimeter_and_infill(temp_support_fills->entities, poly, 1, roof_1st_layer_flow, erSupportTransition, filler_Roof1stLayer.get(), interface_density, false); temp_support_fills->no_sort = true; // make sure loops are first if (!temp_support_fills->entities.empty()) @@ -1502,8 +1654,33 @@ void TreeSupport::generate_toolpaths() filler_interface->angle = Geometry::deg2rad(object_config.support_angle.value); fill_params.dont_sort = true; } - if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) - filler_interface->layer_id = area_group.interface_id; + if (m_support_params.contact_fill_pattern = ipRectilinear) { + bool bridge_found = false; + for (size_t i = 0; i < m_support_params.num_top_interface_layers; i++) { + auto cur_ts_layer = m_object->get_support_layer(layer_id + i); + if (cur_ts_layer == nullptr) break; + Layer *obj_layer = m_object->get_layer_at_bottomz(cur_ts_layer->print_z + object_config.support_top_z_distance.value, layer_height / 10.); + + if (obj_layer) { + for (size_t region_id = 0; region_id < obj_layer->regions().size(); ++region_id) { + LayerRegion *layerm = obj_layer->regions()[region_id]; + for (const auto surface : layerm->fill_surfaces.surfaces) { + if (surface.surface_type == stBottomBridge && overlaps(polys, surface.expolygon)) { + filler_interface->angle = surface.bridge_angle + (i + 1) * M_PI_2; + bridge_found = true; + break; + } + } + if (bridge_found) break; + } + } + if (bridge_found) break; + } + if (!bridge_found) + filler_interface->layer_id = area_group.interface_id; + else + filler_interface->layer_id = 0; + } fill_expolygons_generate_paths(ts_layer->support_fills.entities, polys, filler_interface.get(), fill_params, erSupportMaterialInterface, interface_flow); @@ -1531,7 +1708,7 @@ void TreeSupport::generate_toolpaths() // allow infill-only mode if support is thick enough (so min_wall_count is 0); // otherwise must draw 1 wall // Don't need extra walls if we have infill. Extra walls may overlap with the infills. - size_t min_wall_count = offset(poly, -scale_(support_spacing * 1.5)).empty() ? 1 : 0; + size_t min_wall_count = 1; make_perimeter_and_infill(ts_layer->support_fills.entities, poly, std::max(min_wall_count, wall_count), flow, erSupportMaterial, filler_support.get(), support_density); } @@ -1661,7 +1838,7 @@ void TreeSupport::generate() profiler.stage_finish(STAGE_DETECT_OVERHANGS); create_tree_support_layers(); - m_ts_data = m_object->alloc_tree_support_preview_cache(); + //m_ts_data = m_object->alloc_tree_support_preview_cache(); m_ts_data->is_slim = is_slim; // // get the ring of outside plate // auto tmp= diff_ex(offset_ex(m_machine_border, scale_(100)), m_machine_border); @@ -1672,8 +1849,10 @@ void TreeSupport::generate() profiler.stage_start(STAGE_GENERATE_CONTACT_NODES); //m_object->print()->set_status(56, _u8L("Support: precalculate avoidance")); - Points bedpts = m_machine_border.contour.points; - BuildVolume build_volume{ Pointfs{ unscaled(bedpts[0]), unscaled(bedpts[1]),unscaled(bedpts[2]),unscaled(bedpts[3])}, m_print_config->printable_height }; + Points bedpts = m_machine_border.contour.points; + Pointfs bedptsf; + std::transform(bedpts.begin(), bedpts.end(), std::back_inserter(bedptsf), [](const Point &p) { return unscale(p); }); + BuildVolume build_volume{bedptsf, m_print_config->printable_height, {}}; TreeSupport3D::TreeSupportSettings tree_support_3d_config{ TreeSupport3D::TreeSupportMeshGroupSettings{ *m_object }, m_slicing_params }; m_model_volumes = std::make_unique( *m_object, build_volume, tree_support_3d_config.maximum_move_distance, tree_support_3d_config.maximum_move_distance_slow, 1); // ### Precalculate avoidances, collision etc. @@ -1987,12 +2166,11 @@ void TreeSupport::draw_circles() } // generate areas - const coordf_t layer_height = config.layer_height.value; const size_t top_interface_layers = config.support_interface_top_layers.value; - const size_t bottom_interface_layers = config.support_interface_bottom_layers.value; + const size_t bottom_interface_layers = 0; //config.support_interface_bottom_layers.value; const bool with_lightning_infill = m_support_params.base_fill_pattern == ipLightning; coordf_t support_extrusion_width = m_support_params.support_extrusion_width; - const coordf_t line_width_scaled = scale_(support_extrusion_width); + const coordf_t line_width_scaled = scale_(support_extrusion_width); const float tree_brim_width = config.raft_first_layer_expansion.value; if (m_object->support_layer_count() <= m_raft_layers) @@ -2035,7 +2213,6 @@ void TreeSupport::draw_circles() coordf_t max_layers_above_roof1 = 0; int interface_id = 0; bool has_circle_node = false; - bool need_extra_wall = false; ExPolygons collision_sharp_tails; ExPolygons collision_base; auto get_collision = [&](bool sharp_tail) -> ExPolygons &{ @@ -2061,6 +2238,7 @@ void TreeSupport::draw_circles() //Draw the support areas and add the roofs appropriately to the support roof instead of normal areas. ts_layer->support_islands.reserve(curr_layer_nodes.size()); ExPolygons area_poly; // the polygon node area which will be printed as normal support + ExPolygons extra_wall_area; //where nodes would have extra walls for (const SupportNode* p_node : curr_layer_nodes) { if (print->canceled()) @@ -2068,17 +2246,23 @@ void TreeSupport::draw_circles() const SupportNode& node = *p_node; ExPolygons area; + double brim_width = tree_brim_width; // Generate directly from overhang polygon if one of the following is true: // 1) node is a normal part of hybrid support // 2) node is virtual if (node.type == ePolygon || (node.distance_to_top<0 && !node.is_sharp_tail)) { - if (node.overhang.contour.size() > 100 || node.overhang.holes.size()>1) - area.emplace_back(node.overhang); - else { - area = offset_ex({ node.overhang }, scale_(m_ts_data->m_xy_distance)); - } + // if (node.overhang.contour.size() > 100 || node.overhang.holes.size()>1) + // area.emplace_back(node.overhang); + // else { + // area = offset_ex({ node.overhang }, scale_(m_ts_data->m_xy_distance)); + //} + area = offset_ex({node.overhang}, scale_(m_ts_data->m_xy_distance)); area = diff_clipped(area, get_collision(node.is_sharp_tail && node.distance_to_top <= 0)); - if (node.type == ePolygon) append(area_poly, area); + if (node.type == ePolygon) { + append(area_poly, area); + if (node.need_extra_wall) append(extra_wall_area, area); + } + if (tree_brim_width <= 0) brim_width = node.type == ePolygon ? 1 : 3; } else { Polygon circle(branch_circle); @@ -2103,39 +2287,40 @@ void TreeSupport::draw_circles() circle.points[i] = circle.points[i] * scale + node.position; } } - if (obj_layer_nr == 0 && m_raft_layers == 0) { - double brim_width = tree_brim_width > 0 ? tree_brim_width : std::max(MIN_BRANCH_RADIUS_FIRST_LAYER, std::min(node.radius + node.dist_mm_to_top / (scale * branch_radius) * 0.5, MAX_BRANCH_RADIUS_FIRST_LAYER) - node.radius); - auto tmp=offset(circle, scale_(brim_width)); - if(!tmp.empty()) - circle = tmp[0]; - } + brim_width = tree_brim_width > 0 ? + tree_brim_width : + std::max(MIN_BRANCH_RADIUS_FIRST_LAYER, + std::min(node.radius + node.dist_mm_to_top / (scale * branch_radius) * 0.5, MAX_BRANCH_RADIUS_FIRST_LAYER) - node.radius); area = avoid_object_remove_extra_small_parts(ExPolygon(circle), get_collision(node.is_sharp_tail && node.distance_to_top <= 0)); // area = diff_clipped({ ExPolygon(circle) }, get_collision(node.is_sharp_tail && node.distance_to_top <= 0)); if (!area.empty()) has_circle_node = true; - if (node.need_extra_wall) need_extra_wall = true; + if (node.need_extra_wall) append(extra_wall_area, area); // merge overhang to get a smoother interface surface // Do not merge when buildplate_only is on, because some underneath nodes may have been deleted. - if (top_interface_layers > 0 && node.support_roof_layers_below > 0 && !on_buildplate_only && !node.is_sharp_tail) { + if (top_interface_layers > 0 && (node.support_roof_layers_below > 1 || (node.support_roof_layers_below >= 0 && !node.is_sharp_tail)) && + !on_buildplate_only) { ExPolygons overhang_expanded; - if (node.overhang.contour.size() > 100 || node.overhang.holes.size()>1) + if (node.overhang.contour.size() > 100 || node.overhang.holes.size() > 1) overhang_expanded.emplace_back(node.overhang); else { - overhang_expanded = offset_ex({ node.overhang }, scale_(m_ts_data->m_xy_distance)); + overhang_expanded = offset_ex({node.overhang}, scale_(m_ts_data->m_xy_distance)); } append(area, overhang_expanded); } } + if (layer_nr == 0 && m_raft_layers == 0) + area = safe_offset_inc(area, scale_(brim_width), get_collision(false), scale_(MIN_BRANCH_RADIUS * 0.5), 0, 1); if (obj_layer_nr>0 && node.distance_to_top < 0) append(roof_gap_areas, area); - else if (obj_layer_nr > 0 && node.support_roof_layers_below == 1 && node.is_sharp_tail==false) - { + else if (m_support_params.num_top_interface_layers > 0 && obj_layer_nr > 0 && (node.support_roof_layers_below == 0 || node.support_roof_layers_below == 1) && + node.distance_to_top >= m_support_params.num_top_interface_layers) { append(roof_1st_layer, area); max_layers_above_roof1 = std::max(max_layers_above_roof1, node.dist_mm_to_top); } - else if (obj_layer_nr > 0 && node.support_roof_layers_below > 0 && node.is_sharp_tail == false) + else if (obj_layer_nr > 0 && node.support_roof_layers_below > 0) { append(roof_areas, area); max_layers_above_roof = std::max(max_layers_above_roof, node.dist_mm_to_top); @@ -2161,6 +2346,10 @@ void TreeSupport::draw_circles() roof_1st_layer = intersection_ex(roof_1st_layer, m_machine_border); ExPolygons roofs; append(roofs, roof_1st_layer); append(roofs, roof_areas);append(roofs, roof_gap_areas); + + // slightly slow down the slicing speed + base_areas = diff_ex(base_areas, collision_base.empty() ? m_ts_data->get_collision(0, obj_layer_nr) : collision_base); + base_areas = diff_ex(base_areas, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roofs, get_extents(base_areas))); base_areas = intersection_ex(base_areas, m_machine_border); @@ -2205,6 +2394,7 @@ void TreeSupport::draw_circles() //if (area(expoly) < SQ(scale_(1))) continue; area_groups.emplace_back(&expoly, SupportLayer::BaseType, max_layers_above_base); area_groups.back().need_infill = overlaps({ expoly }, area_poly); + bool need_extra_wall = overlaps({expoly},extra_wall_area); area_groups.back().need_extra_wall = need_extra_wall && !area_groups.back().need_infill; } for (auto& expoly : ts_layer->roof_areas) { @@ -2248,8 +2438,6 @@ void TreeSupport::draw_circles() if (with_lightning_infill) { - const bool global_lightning_infill = true; - std::vector contours; std::vector overhangs; for (int layer_nr = 1; layer_nr < contact_nodes.size(); layer_nr++) { @@ -2274,33 +2462,25 @@ void TreeSupport::draw_circles() SupportLayer* lower_layer = m_object->get_support_layer(layer_nr_lower + m_raft_layers); ExPolygons& base_areas_lower = lower_layer->base_areas; - ExPolygons overhang; - if (global_lightning_infill) - { - //search overhangs globally - overhang = std::move(diff_ex(offset_ex(base_areas_lower, -2.0 * scale_(support_extrusion_width)), base_areas)); - } - else - { - //search overhangs only on floating islands - for (auto& base_area : base_areas) - for (auto& hole : base_area.holes) - { - Polygon rev_hole = hole; - rev_hole.make_counter_clockwise(); - ExPolygons ex_hole; - ex_hole.emplace_back(std::move(ExPolygon(rev_hole))); - for (auto& other_area : base_areas) - //if (&other_area != &base_area) - ex_hole = std::move(diff_ex(ex_hole, other_area)); - overhang = std::move(union_ex(overhang, ex_hole)); - } - overhang = std::move(intersection_ex(overhang, offset_ex(base_areas_lower, -0.5 * scale_(support_extrusion_width)))); + ExPolygons overhang = diff_ex(offset_ex_2(base_areas_lower, -2.5 * line_width_scaled), base_areas); + for (auto it = overhang.begin(); it != overhang.end();) { + it->remove_colinear_points(); + if (it->empty()) + it = overhang.erase(it); + else ++it; } + overhangs.emplace_back(to_polygons(overhang)); contours.emplace_back(to_polygons(base_areas_lower)); printZ_to_lightninglayer[lower_layer->print_z] = overhangs.size() - 1; + + #ifdef SUPPORT_TREE_DEBUG_TO_SVG + if (!overhang.empty() && !base_areas_lower.empty()) { + std::string fname = debug_out_path("lightning_%d_%.2f.svg", layer_nr, ts_layer->print_z); + SVG::export_expolygons(fname, {{base_areas_lower, {"base_areas_lower", "red", 0.5}}, {overhang, {"overhang", "blue", 0.5}}}); + } +#endif } @@ -2366,22 +2546,23 @@ void TreeSupport::draw_circles() for (const auto& hole : area->holes) { // auto hole_bbox = get_extents(hole).polygon(); for (auto& area_group_lower : area_groups_lower) { - if (area_group.type != SupportLayer::BaseType) continue; auto& base_area_lower = *area_group_lower.area; Point pt_on_poly, pt_on_expoly, pt_far_on_poly; // if a hole doesn't intersect with lower layer's contours, add a hole to lower layer and move it slightly to the contour - if (base_area_lower.contour.contains(hole.points.front()) && !intersects_contour(hole, base_area_lower, pt_on_poly, pt_on_expoly, pt_far_on_poly)) { + if (base_area_lower.contour.contains(hole.points.front()) && + !intersects_contour(hole, base_area_lower, pt_on_poly, pt_on_expoly, pt_far_on_poly, + 0.01 + unscale_(line_width_scaled) * (area_group.need_extra_wall ? 2. : 0.5))) { Polygon hole_lower = hole; - Point direction = normal(pt_on_expoly - pt_on_poly, line_width_scaled / 2); + Point direction = normal(pt_on_expoly - pt_on_poly, line_width_scaled / 2); hole_lower.translate(direction); // note to expand a hole, we need to do negative offset auto hole_expanded = offset(hole_lower, -line_width_scaled / 4, ClipperLib::JoinType::jtSquare); if (!hole_expanded.empty()) { base_area_lower.holes.push_back(std::move(hole_expanded[0])); - holePropagationInfos.insert({ &base_area_lower.holes.back(), {25, direction, pt_far_on_poly} }); - } - break; + holePropagationInfos.insert({&base_area_lower.holes.back(), {25, direction, pt_far_on_poly}}); } + break; + } else if (holePropagationInfos.find(&hole) != holePropagationInfos.end() && std::get<0>(holePropagationInfos[&hole]) > 0 && base_area_lower.contour.contains(std::get<2>(holePropagationInfos[&hole]))) { // after the hole connects to contour, shrink it gradually until it vanishes while moving it outwards. The moving direction is defined in the previous step @@ -2454,6 +2635,50 @@ void TreeSupport::draw_circles() double SupportNode::diameter_angle_scale_factor; +static void smooth_filter(const Polygon &poly, int filter_size, std::unordered_map &movements, double max_move) +{ + const Points &pts = poly.points; + int n = pts.size(); + Point center = poly.centroid(); + Polygon newpoly(poly); + for (int i = 0; i < n; i++) { + Point filter_pt(0, 0); + for (int j = (i + n - filter_size) % n; j != (i + filter_size + 1) % n; j = (j + 1) % n) filter_pt += pts[j]; + newpoly[i] = filter_pt / (2 * filter_size + 1); + newpoly[i] -= center; + } + if (newpoly.area() < SQ(scale_(10.))) newpoly.scale(SQ(scale_(10.)) / newpoly.area()); + + for (int i = 0; i < n; i++) { + auto &pt = pts[i]; + Point move = newpoly[i] - (pt - center); + if (vsize2_with_unscale(move) > SQ(max_move)) move = normal(move, scale_(max_move)); + movements[pt] = move; + } +} +static void densify_polygon(Polygon &poly, double max_edge_length) +{ + Points dense; + Points &pts = poly.points; + int n = pts.size(); + for (int i = 0; i < n; i++) { + int i2 = (i + 1) % n; + dense.emplace_back(pts[i]); + Point prev = pts[i]; + Point curr; + auto edge = pts[i2] - pts[i]; + int n_frag = std::ceil(std::sqrt(vsize2_with_unscale(edge)) / max_edge_length); + for (int j = 1; j < n_frag; j++) { + curr = pts[i] + edge * (double(j) / double(n_frag)); + if (curr != prev) { + prev = curr; + dense.emplace_back(curr); + } + } + } + poly.points = std::move(dense); +} + void TreeSupport::drop_nodes() { const PrintObjectConfig &config = m_object->config(); @@ -2468,7 +2693,6 @@ void TreeSupport::drop_nodes() const size_t tip_layers = base_radius / layer_height; //The number of layers to be shrinking the circle to create a tip. This produces a 45 degree angle. const coordf_t radius_sample_resolution = m_ts_data->m_radius_sample_resolution; const bool support_on_buildplate_only = config.support_on_build_plate_only.value; - const size_t bottom_interface_layers = config.support_interface_bottom_layers.value; const size_t top_interface_layers = config.support_interface_top_layers.value; SupportNode::diameter_angle_scale_factor = diameter_angle_scale_factor; @@ -2580,10 +2804,17 @@ void TreeSupport::drop_nodes() { const SupportNode& node = *p_node; - if (support_on_buildplate_only && !node.to_buildplate) //Can't rest on model and unable to reach the build plate. Then we must drop the node and leave parts unsupported. - { - unsupported_branch_leaves.push_front({ layer_nr, p_node }); - continue; + if (!node.to_buildplate) { + // Can't rest on model and unable to reach the build plate. Then we must drop the node and leave parts unsupported. + if (support_on_buildplate_only) { + unsupported_branch_leaves.push_front({layer_nr, p_node}); + continue; + } + //If permitted to fall on the model, downward growth ceases. + auto overlap_with_circle = shrink_ex(get_collision(0, obj_layer_nr), scale_(node.radius)); + if (!overlap_with_circle.empty() && is_inside_ex(overlap_with_circle, node.position)) { + continue; + } } if (node.to_buildplate || parts.empty()) //It's outside, so make it go towards the build plate. { @@ -2639,6 +2870,17 @@ void TreeSupport::drop_nodes() } profiler.stage_add(STAGE_MinimumSpanningTree); + //for (size_t i = 0; i < layer_contact_nodes.size(); i++) { + // SupportNode *p_node = layer_contact_nodes[i]; + // if (!p_node->valid) continue; + // if (p_node->type == ePolygon) { + // if (p_node->distance_to_top < 0 && p_node->overhang.area() < SQ(scale_(1.))) { + // p_node->type = eCircle; + // } else if (p_node->overhang.area() < SQ(scale_(1.))) + // p_node->valid = false; + // } + //} + #ifdef SUPPORT_TREE_DEBUG_TO_SVG coordf_t branch_radius_temp = 0; coordf_t max_y = std::numeric_limits::min(); @@ -2657,21 +2899,54 @@ void TreeSupport::drop_nodes() { return; //Delete this node (don't create a new node for it on the next layer). } + if (node.fading) return; const std::vector& neighbours = mst.adjacent_nodes(node.position); if (node.type == ePolygon) { // Remove all circle neighbours that are completely inside the polygon and merge them into this node. for (const Point &neighbour : neighbours) { - SupportNode * neighbour_node = nodes_this_part[neighbour]; + SupportNode *neighbour_node = nodes_this_part[neighbour]; + bool can_merge = false; if (neighbour_node->valid == false) continue; - if (neighbour_node->type == ePolygon) continue; - coord_t neighbour_radius = scale_(neighbour_node->radius); - Point pt_north = neighbour + Point(0, neighbour_radius), pt_south = neighbour - Point(0, neighbour_radius), - pt_west = neighbour - Point(neighbour_radius, 0), pt_east = neighbour + Point(neighbour_radius, 0); - if (is_inside_ex(node.overhang, neighbour) && is_inside_ex(node.overhang, pt_north) && is_inside_ex(node.overhang, pt_south) - && is_inside_ex(node.overhang, pt_west) && is_inside_ex(node.overhang, pt_east)){ - node.distance_to_top = std::max(node.distance_to_top, neighbour_node->distance_to_top); - node.support_roof_layers_below = std::max(node.support_roof_layers_below, neighbour_node->support_roof_layers_below); - node.dist_mm_to_top = std::max(node.dist_mm_to_top, neighbour_node->dist_mm_to_top); + if (neighbour_node->fading) continue; + if (neighbour_node->type == ePolygon) { + if ((node.distance_to_top < 0 && neighbour_node->distance_to_top < 0) || + (node.distance_to_top > m_support_params.num_top_interface_layers + 1 && + neighbour_node->distance_to_top > m_support_params.num_top_interface_layers + 1)) { + auto overhang_shrinked = shrink_ex({node.overhang}, scale_(support_extrusion_width)); + if (!overhang_shrinked.empty() && overlaps(overhang_shrinked, {neighbour_node->overhang})) { + auto tmp = union_ex({node.overhang}, {neighbour_node->overhang}); + if (!tmp.empty() && tmp.size() == 1) { + Point next_pt = tmp[0].contour.centroid(); + SupportNode *next_node = m_ts_data->create_node(next_pt, std::max(node.distance_to_top, neighbour_node->distance_to_top) + 1, + obj_layer_nr_next, + std::max(node.support_roof_layers_below, neighbour_node->support_roof_layers_below) - 1, + true, p_node, print_z_next, height_next); + next_node->max_move_dist = 0; + next_node->overhang = std::move(tmp[0]); + next_node->origin_area = next_node->overhang.area(); + m_ts_data->m_mutex.lock(); + contact_nodes[layer_nr_next].emplace_back(next_node); + p_node->valid = false; + neighbour_node->valid = false; + m_ts_data->m_mutex.unlock(); + return; + } + } + } + } else { + coord_t neighbour_radius = scale_(neighbour_node->radius); + Point pt_north = neighbour + Point(0, neighbour_radius), pt_south = neighbour - Point(0, neighbour_radius), + pt_west = neighbour - Point(neighbour_radius, 0), pt_east = neighbour + Point(neighbour_radius, 0); + can_merge = is_inside_ex(node.overhang, neighbour) && is_inside_ex(node.overhang, pt_north) && is_inside_ex(node.overhang, pt_south) && + is_inside_ex(node.overhang, pt_west) && is_inside_ex(node.overhang, pt_east); + if (!can_merge && is_inside_ex(node.overhang, neighbour)) { + //ExPolygon neighbor_circle(make_circle(neighbour_radius, scale_(0.1))); + //neighbor_circle.translate(neighbour); + //node.overhang = union_ex({node.overhang}, {neighbor_circle})[0]; + neighbour_node->fading = true; + } + } + if (p_node->valid && can_merge) { node.merged_neighbours.push_front(neighbour_node); node.merged_neighbours.insert(node.merged_neighbours.end(), neighbour_node->merged_neighbours.begin(), neighbour_node->merged_neighbours.end()); neighbour_node->valid = false; @@ -2745,21 +3020,74 @@ void TreeSupport::drop_nodes() { return; } + if (node.fading) { + coordf_t next_radius = node.radius - max_move_distance; + if (next_radius < EPSILON) return; + SupportNode *next_node = m_ts_data->create_node(node.position, p_node->distance_to_top + 1, obj_layer_nr_next, p_node->support_roof_layers_below - 1, node.to_buildplate, + p_node, print_z_next, height_next); + next_node->max_move_dist = 0; + next_node->radius = next_radius; + next_node->fading = true; + m_ts_data->m_mutex.lock(); + contact_nodes[layer_nr_next].emplace_back(next_node); + m_ts_data->m_mutex.unlock(); + return; + } if (node.type == ePolygon) { // polygon node do not merge or move + if (node.overhang.empty()) { + p_node->valid = false; + return; + } const bool to_buildplate = true; // keep only the part that won't be removed by the next layer ExPolygons overhangs_next = diff_clipped({ node.overhang }, get_collision(0, obj_layer_nr_next)); + if (node.distance_to_top == 0) { + overhangs_next = offset2_ex(overhangs_next, scale_(max_move_distance), -scale_(max_move_distance)); + p_node->origin_area = node.overhang.area(); + densify_polygon(p_node->overhang.contour, 2.); + } for(auto& overhang:overhangs_next) { - Point next_pt = overhang.contour.centroid(); - SupportNode *next_node = m_ts_data->create_node(next_pt, p_node->distance_to_top + 1, obj_layer_nr_next, p_node->support_roof_layers_below - 1, - to_buildplate, p_node, print_z_next, height_next); - next_node->max_move_dist = 0; - next_node->overhang = std::move(overhang); - m_ts_data->m_mutex.lock(); - contact_nodes[layer_nr_next].emplace_back(next_node); - m_ts_data->m_mutex.unlock(); + if (overhang.empty()) continue; + if (overhang.area() > node.origin_area / 2. && overhang.area() > SQ(scale_(10.))) { + Polygon contour = overhang.contour; + std::unordered_map movements; + smooth_filter(overhang.contour, 2, movements, max_move_distance / 2.); + for (auto &pt : overhang.contour.points) { + auto tmp = pt + movements.at(pt); + if (!is_inside_ex(to_expolygons({contour}), tmp) && !is_inside_ex(m_ts_data->m_layer_outlines_below[obj_layer_nr], tmp)) + pt = tmp; + } + } + // if the part would fall straight to th buildplate, shrink it a little + if (overhang.area() > node.origin_area / 2. && overhang.area() > double(SQ(scale_(10.)))) { + ExPolygons shrink_overhangs = union_ex(shrink_ex(safe_union({overhang}), double(scale_(max_move_distance / 2.)))); + if (shrink_overhangs.size() == 1 && shrink_overhangs[0].area() > double(SQ(scale_(10.))) && + !overlaps({overhang}, m_ts_data->m_layer_outlines_below[obj_layer_nr_next])) { + if (diff_ex({overhang}, offset_ex(shrink_overhangs[0],scale_(max_move_distance))).empty()) + overhang = shrink_overhangs[0]; + } + Point next_pt = overhang.contour.centroid(); + SupportNode *next_node = m_ts_data->create_node(next_pt, p_node->distance_to_top + 1, obj_layer_nr_next, p_node->support_roof_layers_below - 1, + to_buildplate, p_node, print_z_next, height_next); + next_node->max_move_dist = 0; + next_node->overhang = std::move(overhang); + next_node->origin_area = node.origin_area; + m_ts_data->m_mutex.lock(); + contact_nodes[layer_nr_next].emplace_back(next_node); + m_ts_data->m_mutex.unlock(); + } else { + Point next_pt = overhang.contour.centroid(); + SupportNode *next_node = m_ts_data->create_node(next_pt, p_node->distance_to_top + 1, obj_layer_nr_next, p_node->support_roof_layers_below - 1, + to_buildplate, p_node, print_z_next, height_next); + next_node->max_move_dist = 0; + next_node->overhang = std::move(overhang); + next_node->origin_area = node.origin_area; + m_ts_data->m_mutex.lock(); + contact_nodes[layer_nr_next].emplace_back(next_node); + m_ts_data->m_mutex.unlock(); + } } return; } @@ -2798,7 +3126,9 @@ void TreeSupport::drop_nodes() // 2. Only merge node with single neighbor in distance between [max_move_distance, 10mm/layer_height] float dist2_to_first_neighbor = neighbours.empty() ? 0 : vsize2_with_unscale(neighbours[0] - node.position); if (node.print_z > DO_NOT_MOVER_UNDER_MM && - (neighbours.size() > 1 || (neighbours.size() == 1 && dist2_to_first_neighbor >= get_max_move_dist(p_node, 2)))) // Only nodes that aren't about to collapse. + (neighbours.size() > 1 || + (neighbours.size() == 1/* && nodes_this_part[neighbours[0]]->type != ePolygon */&& dist2_to_first_neighbor >= get_max_move_dist(p_node, 2))/* || + (neighbours.size() == 1 && nodes_this_part[neighbours[0]]->type == ePolygon)*/)) // Only nodes that aren't about to collapse. { // Move towards the average position of all neighbours. Point sum_direction(0, 0); @@ -2806,8 +3136,13 @@ void TreeSupport::drop_nodes() // do not move to the neighbor to be deleted SupportNode *neighbour_node = nodes_this_part[neighbour]; if (!neighbour_node->valid) continue; - - Point direction = neighbour - node.position; + Point direction; + if (neighbour_node->type == ePolygon && neighbour_node->overhang.is_valid()) { + Point contact_point = projection_onto({neighbour_node->overhang}, node.position); + direction = contact_point - node.position; + } else { + direction = neighbour - node.position; + } // do not move to neighbor that's too far away (即使以最大速度移动,在接触热床之前都无法汇聚) float dist2_to_neighbor = vsize2_with_unscale(direction); @@ -2846,6 +3181,9 @@ void TreeSupport::drop_nodes() Point to_outside = projection_onto(avoidance_next, node.position); Point direction_to_outer = to_outside - node.position; + if (node.skin_direction != Point(0, 0) && node.dist_mm_to_top < 3) { + direction_to_outer = move_to_neighbor_center = normal(node.skin_direction, scale_(max_move_distance)); + } double dist2_to_outer = vsize2_with_unscale(direction_to_outer); // don't move if // 1) line of node and to_outside is cut by contour (means supports may intersect with object) @@ -2878,13 +3216,14 @@ void TreeSupport::drop_nodes() movement = move_to_neighbor_center; // otherwise move to neighbor center first } - if (node.is_sharp_tail && node.dist_mm_to_top < 3) { - movement = normal(node.skin_direction, scale_(get_max_move_dist(&node))); - } - else if (dist2_to_outer > 0) - movement = normal(direction_to_outer, scale_(get_max_move_dist(&node))); - else - movement = normal(move_to_neighbor_center, scale_(get_max_move_dist(&node))); + //if (node.is_sharp_tail && node.dist_mm_to_top < 3) { + // movement = normal(node.skin_direction, scale_(get_max_move_dist(&node))); + //} + //else if (dist2_to_outer > 0) + // movement = normal(direction_to_outer, scale_(get_max_move_dist(&node))); + //else + // movement = normal(move_to_neighbor_center, scale_(get_max_move_dist(&node))); + if (vsize2_with_unscale(movement) > get_max_move_dist(&node, 2)) movement = normal(movement, scale_(get_max_move_dist(&node))); next_layer_vertex += movement; @@ -3039,14 +3378,19 @@ void TreeSupport::smooth_nodes() std::swap(pts, pts1); std::swap(radii, radii1); } - else { - // interpolate need_extra_wall in the end - for (size_t i = 1; i < branch.size() - 1; i++) { - if (branch[i - 1]->need_extra_wall && branch[i + 1]->need_extra_wall) - branch[i]->need_extra_wall = true; - } + } + // interpolate need_extra_wall in the end + int idx_first_double_wall = -1; + int idx_last_double_wall = -1; + for (size_t i = 0; i < pts.size(); i++) { + if (branch[i]->need_extra_wall) { + if (idx_first_double_wall < 0) idx_first_double_wall = i; + idx_last_double_wall = i; } } + if (idx_last_double_wall >= 0 && idx_first_double_wall >= 0 && idx_last_double_wall > idx_first_double_wall) { + for (size_t i = idx_first_double_wall + 1; i < idx_last_double_wall; i++) branch[i]->need_extra_wall = true; + } } } } @@ -3312,6 +3656,7 @@ void TreeSupport::generate_contact_points() bool on_buildplate_only = m_object_config->support_on_build_plate_only.value; const bool roof_enabled = config.support_interface_top_layers.value > 0; const bool force_tip_to_roof = roof_enabled && m_support_params.soluble_interface; + const coordf_t extrusion_width = config.line_width.value; //First generate grid points to cover the entire area of the print. BoundingBox bounding_box = m_object->bounding_box(); @@ -3355,7 +3700,9 @@ void TreeSupport::generate_contact_points() if (support_roof_layers > 0) support_roof_layers += 1; // QDS: add a normal support layer below interface (if we have interface) coordf_t thresh_angle = std::min(89.f, config.support_threshold_angle.value < EPSILON ? 30.f : config.support_threshold_angle.value); - coordf_t half_overhang_distance = scale_(tan(thresh_angle * M_PI / 180.0) * layer_height / 2); + coordf_t half_overhang_distance = scale_(tan(thresh_angle * M_PI / 180.0) * layer_height / 2); + coordf_t xy_expansion = scale_(config.support_expansion.value); + if (m_support_params.soluble_interface && xy_expansion < EPSILON) xy_expansion = scale_(2); // fix bug of generating support for very thin objects if (m_object->layers().size() <= z_distance_top_layers + 1) @@ -3395,12 +3742,6 @@ void TreeSupport::generate_contact_points() bool added = false; // Did we add a point this way? bool is_sharp_tail = false; - // take the least restrictive avoidance possible - ExPolygons relevant_forbidden = offset_ex(m_ts_data->m_layer_outlines[layer_nr - 1], scale_(MIN_BRANCH_RADIUS)); - // prevent rounding errors down the line, points placed directly on the line of the forbidden area may not be added otherwise. - relevant_forbidden = offset_ex(union_ex(relevant_forbidden), scaled(0.005), jtMiter, 1.2); - - auto insert_point = [&](Point pt, const ExPolygon& overhang, double radius, bool force_add = false, bool add_interface=true) { Point hash_pos = pt / ((radius_scaled + 1) / 1); SupportNode* contact_node = nullptr; @@ -3413,8 +3754,9 @@ void TreeSupport::generate_contact_points() // print_z=object_layer->bottom_z: it directly contacts the bottom // height=z_distance_top: it's height is exactly the gap distance // dist_mm_to_top=0: it directly contacts the bottom - contact_node = m_ts_data->create_node(pt, -gap_layers, layer_nr-1, roof_layers + 1, to_buildplate, SupportNode::NO_PARENT, bottom_z, z_distance_top, 0, - radius); + if (bottom_z - z_distance_top < m_object->get_layer(0)->print_z) return contact_node; // dont add it if overlapping with the initial layer + contact_node = m_ts_data->create_node(pt, -gap_layers, layer_nr - 1, roof_layers + (gap_layers > 0 ? 1 : 0), to_buildplate, SupportNode::NO_PARENT, bottom_z, + z_distance_top, 0, radius); contact_node->overhang = overhang; contact_node->is_sharp_tail = is_sharp_tail; curr_nodes.emplace_back(contact_node); @@ -3423,33 +3765,56 @@ void TreeSupport::generate_contact_points() return contact_node; }; - for (const auto& overhang_part : layer->loverhangs) { - const auto& overhang_type = this->overhang_types[&overhang_part]; - is_sharp_tail = overhang_type == OverhangType::SharpTail; - ExPolygons overhangs_regular; - if (m_support_params.support_style == smsTreeHybrid && overhang_part.area() > m_support_params.thresh_big_overhang && !is_sharp_tail) { - overhangs_regular = offset_ex(intersection_ex({overhang_part}, m_ts_data->m_layer_outlines_below[layer_nr - 1]), radius_scaled); - ExPolygons overhangs_normal = diff_ex({overhang_part}, overhangs_regular); - if (area(overhangs_normal) > m_support_params.thresh_big_overhang) { - // if the outside area is still big, we can need normal nodes - for (auto &overhang : overhangs_normal) { - BoundingBox overhang_bounds = get_extents(overhang); - double radius = unscale_(overhang_bounds.radius()); - Point candidate = overhang_bounds.center(); - SupportNode *contact_node = insert_point(candidate, overhang, radius, true, true); - contact_node->type = ePolygon; - curr_nodes.emplace_back(contact_node); - } - }else{ - // otherwise, all nodes should be circle nodes - overhangs_regular = ExPolygons{overhang_part}; + for (const auto& overhang_with_type : layer->loverhangs_with_type) { + const auto &overhang_part = overhang_with_type.first; + const auto &overhang_type = overhang_with_type.second; + is_sharp_tail = overhang_type & OverhangType::SharpTail; + bool add_interface = (force_tip_to_roof || area(overhang_part) > minimum_roof_area); + const auto &relevant_forbidden = get_collision(0, layer_nr - 1); + ExPolygons overhangs{overhang_part}; + ExPolygons overhangs_regular, overhangs_no_extra_expand; + if (add_interface && xy_expansion > EPSILON && !is_sharp_tail) { + overhangs = safe_offset_inc(overhangs, xy_expansion, relevant_forbidden, scale_(MIN_BRANCH_RADIUS * 1.75), 0, 1); + } + overhangs_no_extra_expand = (!is_sharp_tail && (unscale_(xy_expansion) - config.support_expansion.value > EPSILON) && (config.support_expansion.value > EPSILON)) ? + safe_offset_inc({overhang_part}, scale_(config.support_expansion.value), relevant_forbidden, scale_(MIN_BRANCH_RADIUS * 1.75), 0, 1) : + overhangs; + if (m_support_params.support_style == smsTreeHybrid && + (overhang_type & (BigFlat | ThinPlate))) { + overhangs_regular = offset_ex(intersection_ex(overhangs, m_ts_data->m_layer_outlines_below[layer_nr - 1]), radius_scaled); + ExPolygons overhangs_normal = offset2_ex(diff_ex(overhangs, overhangs_regular),scale_(extrusion_width),-scale_(extrusion_width)); + overhangs_regular = intersection_ex(overhangs_regular, overhangs_no_extra_expand); + // if the outside area is still big, we can need normal nodes + coord_t gap_width = scale_(extrusion_width / 2.) + scale_(m_ts_data->m_xy_distance); + ExPolygons overhangs_normal_split; + for (auto &overhang : overhangs_normal) { + ExPolygons sub_overhangs = overhang.split_expoly_with_holes(gap_width, get_collision(0, layer_nr)); + if (sub_overhangs.size() > 0) + for (auto &sub : sub_overhangs) overhangs_normal_split.emplace_back(sub); + else + overhangs_normal_split.emplace_back(overhang); } - } else { - overhangs_regular = ExPolygons{overhang_part}; + for (auto &overhang : overhangs_normal_split) { + if (!is_stable(layer->bottom_z(), overhang, 0) || overhang.area() < SQ(scale_(2.))) { + ExPolygons unstable_overhangs = intersection_ex({overhang}, overhangs_no_extra_expand); + overhangs_regular.insert(overhangs_regular.end(), unstable_overhangs.begin(), unstable_overhangs.end()); + continue; + } + BoundingBox overhang_bounds = get_extents(overhang); + double radius = unscale_(overhang_bounds.radius()); + Point candidate = overhang_bounds.center(); + SupportNode *contact_node = insert_point(candidate, overhang, radius, true, true); + if (!contact_node) continue; + contact_node->type = ePolygon; + curr_nodes.emplace_back(contact_node); + } + } + else{ + overhangs_regular = overhangs_no_extra_expand; } for (auto &overhang : overhangs_regular) { - bool add_interface = (force_tip_to_roof || area(overhang) > minimum_roof_area) && !is_sharp_tail; + if (is_sharp_tail && !m_support_params.soluble_interface && overhang.area() < SQ(scale_(2.))) add_interface = false; BoundingBox overhang_bounds = get_extents(overhang); double radius = std::clamp(unscale_(overhang_bounds.radius()), MIN_BRANCH_RADIUS, base_radius); // add supports at corners for both auto and manual overhangs, github #2008 @@ -3480,7 +3845,10 @@ void TreeSupport::generate_contact_points() } // don't add inner supports for sharp tails - if (is_sharp_tail) continue; + if (is_sharp_tail) { + SupportNode *contact_node = insert_point(overhang.contour.centroid(), overhang, radius, false, add_interface); + continue; + } // add inner supports overhang_bounds.inflated(-radius_scaled); @@ -3555,20 +3923,40 @@ TreeSupportData::TreeSupportData(const PrintObject &object, coordf_t xy_distance branch_scale_factor = tan(object.config().tree_support_branch_angle.value * M_PI / 180.); clear_nodes(); m_max_move_distances.resize(object.layers().size(), 0); - for (std::size_t layer_nr = 0; layer_nr < object.layers().size(); ++layer_nr) - { - const Layer* layer = object.get_layer(layer_nr); + m_layer_outlines.resize(object.layers().size()); + m_layer_outlines_below.resize(object.layer_count()); + ExPolygons machine_border; + ExPolygon m_machine_border; + + //cal m_machine_border twice, this may happen before TreeSupport builds + m_machine_border.contour = get_bed_shape_with_excluded_area(object.print()->config()); + Vec3d plate_offset = object.print()->get_plate_origin(); + // align with the centered object in current plate (may not be the 1st plate, so need to add the plate offset) + m_machine_border.translate(Point(scale_(plate_offset(0)), scale_(plate_offset(1))) - object.instances().front().shift); + + if (!m_machine_border.empty()) { + Polygon hole(m_machine_border.contour); + hole.reverse(); + ExPolygon machine_outline(offset(m_machine_border.contour, scale_(1000))[0], hole); + machine_border = machine_outline.split_expoly_with_holes(scale_(1.), {m_machine_border}); + } + + for (std::size_t layer_nr = 0; layer_nr < object.layers().size(); ++layer_nr) { + const Layer *layer = object.get_layer(layer_nr); m_max_move_distances[layer_nr] = layer->height * branch_scale_factor; - m_layer_outlines.push_back(ExPolygons()); - ExPolygons& outline = m_layer_outlines.back(); - for (const ExPolygon& poly : layer->lslices) { - poly.simplify(scale_(m_radius_sample_resolution), &outline); - } + ExPolygons &outline = m_layer_outlines[layer_nr]; + outline.clear(); + outline.reserve(layer->lslices.size()); + for (const ExPolygon &poly : layer->lslices) { append(outline, union_ex_2( poly.simplify_p(scale_(m_radius_sample_resolution)))); } + append(outline, machine_border); if (layer_nr == 0) - m_layer_outlines_below.push_back(outline); - else - m_layer_outlines_below.push_back(union_ex(m_layer_outlines_below.end()[-1], outline)); + m_layer_outlines_below[layer_nr] = outline; + else { + m_layer_outlines_below[layer_nr] = m_layer_outlines_below[layer_nr - 1]; + m_layer_outlines_below[layer_nr].insert(m_layer_outlines_below[layer_nr].end(), outline.begin(), outline.end()); + if (layer_nr % 10 == 0) m_layer_outlines_below[layer_nr] = union_ex_2(m_layer_outlines_below[layer_nr]); + } } } @@ -3682,6 +4070,10 @@ const ExPolygons& TreeSupportData::calculate_avoidance(const RadiusLayerPair& ke avoidance_areas = std::move(union_ex(avoidance_areas)); auto ret = m_avoidance_cache.insert({key, std::move(avoidance_areas)}); //assert(ret.second); + // BOOST_LOG_TRIVIAL(debug) << format("calculate_avoidance: radius=%.2f, layer_nr=%d, recursions=%d, avoidance_areas=%d", radius, layer_nr, key.recursions, + // avoidance_areas.size()); + // boost::log::core::get()->flush(); + return ret.first->second; } diff --git a/src/libslic3r/Support/TreeSupport.hpp b/src/libslic3r/Support/TreeSupport.hpp index d2aaad0..dc7bc79 100644 --- a/src/libslic3r/Support/TreeSupport.hpp +++ b/src/libslic3r/Support/TreeSupport.hpp @@ -124,7 +124,9 @@ struct SupportNode bool need_extra_wall = false; bool is_sharp_tail = false; bool valid = true; + bool fading = false; ExPolygon overhang; // when type==ePolygon, set this value to get original overhang area + coordf_t origin_area; /*! * \brief The direction of the skin lines above the tip of the branch. @@ -411,7 +413,7 @@ public: */ ExPolygon m_machine_border; - enum OverhangType { Detected = 0, Enforced, SharpTail }; + enum OverhangType : uint8_t { Normal = 0, SharpTail = 1, Cantilever = 1 << 1, Small = 1 << 2, BigFlat = 1 << 3, ThinPlate = 1 << 4, SharpTailLowesst = 1 << 5 }; std::map overhang_types; std::vector> m_vertical_enforcer_points; diff --git a/src/libslic3r/Support/TreeSupport3D.cpp b/src/libslic3r/Support/TreeSupport3D.cpp index 9f3aeff..1db55f0 100644 --- a/src/libslic3r/Support/TreeSupport3D.cpp +++ b/src/libslic3r/Support/TreeSupport3D.cpp @@ -565,7 +565,7 @@ static std::optional> polyline_sample_next_point_at_dis if (part.front() == part.back()) { size_t optimal_start_index = 0; - // If the polyline was a polygon, there is a high chance it was an overhang. Overhangs that are <60� tend to be very thin areas, so lets get the beginning and end of them and ensure that they are supported. + // If the polyline was a polygon, there is a high chance it was an overhang. Overhangs that are <60?tend to be very thin areas, so lets get the beginning and end of them and ensure that they are supported. // The first point of the line will always be supported, so rotate the order of points in this polyline that one of the two corresponding points that are furthest from each other is in the beginning. // The other will be manually added (optimal_end_index) coord_t max_dist2_between_vertecies = 0; @@ -727,114 +727,6 @@ static std::optional> polyline_sample_next_point_at_dis #endif } -/*! - * \brief Unions two Polygons. Ensures that if the input is non empty that the output also will be non empty. - * \param first[in] The first Polygon. - * \param second[in] The second Polygon. - * \return The union of both Polygons - */ -[[nodiscard]] static Polygons safe_union(const Polygons first, const Polygons second = {}) -{ - // unionPolygons can slowly remove Polygons under certain circumstances, because of rounding issues (Polygons that have a thin area). - // This does not cause a problem when actually using it on large areas, but as influence areas (representing centerpoints) can be very thin, this does occur so this ugly workaround is needed - // Here is an example of a Polygons object that will loose vertices when unioning, and will be gone after a few times unionPolygons was called: - /* - Polygons example; - Polygon exampleInner; - exampleInner.add(Point(120410,83599));//A - exampleInner.add(Point(120384,83643));//B - exampleInner.add(Point(120399,83618));//C - exampleInner.add(Point(120414,83591));//D - exampleInner.add(Point(120423,83570));//E - exampleInner.add(Point(120419,83580));//F - example.add(exampleInner); - for(int i=0;i<10;i++){ - log("Iteration %d Example area: %f\n",i,area(example)); - example=example.unionPolygons(); - } -*/ - - Polygons result; - if (! first.empty() || ! second.empty()) { - result = union_(first, second); - if (result.empty()) { - BOOST_LOG_TRIVIAL(debug) << "Caught an area destroying union, enlarging areas a bit."; - // just take the few lines we have, and offset them a tiny bit. Needs to be offsetPolylines, as offset may aleady have problems with the area. - result = union_(offset(to_polylines(first), scaled(0.002), jtMiter, 1.2), offset(to_polylines(second), scaled(0.002), jtMiter, 1.2)); - } - } - - return result; -} - -/*! - * \brief Offsets (increases the area of) a polygons object in multiple steps to ensure that it does not lag through over a given obstacle. - * \param me[in] Polygons object that has to be offset. - * \param distance[in] The distance by which me should be offset. Expects values >=0. - * \param collision[in] The area representing obstacles. - * \param last_step_offset_without_check[in] The most it is allowed to offset in one step. - * \param min_amount_offset[in] How many steps have to be done at least. As this uses round offset this increases the amount of vertices, which may be required if Polygons get very small. Required as arcTolerance is not exposed in offset, which should result with a similar result. - * \return The resulting Polygons object. - */ -[[nodiscard]] static Polygons safe_offset_inc(const Polygons& me, coord_t distance, const Polygons& collision, coord_t safe_step_size, coord_t last_step_offset_without_check, size_t min_amount_offset) -{ - bool do_final_difference = last_step_offset_without_check == 0; - Polygons ret = safe_union(me); // ensure sane input - - // Trim the collision polygons with the region of interest for diff() efficiency. - Polygons collision_trimmed_buffer; - auto collision_trimmed = [&collision_trimmed_buffer, &collision, &ret, distance]() -> const Polygons& { - if (collision_trimmed_buffer.empty() && ! collision.empty()) - collision_trimmed_buffer = ClipperUtils::clip_clipper_polygons_with_subject_bbox(collision, get_extents(ret).inflated(std::max(0, distance) + SCALED_EPSILON)); - return collision_trimmed_buffer; - }; - - if (distance == 0) - return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret); - if (safe_step_size < 0 || last_step_offset_without_check < 0) { - BOOST_LOG_TRIVIAL(warning) << "Offset increase got invalid parameter!"; - tree_supports_show_error("Negative offset distance... How did you manage this ?"sv, true); - return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret); - } - - coord_t step_size = safe_step_size; - int steps = distance > last_step_offset_without_check ? (distance - last_step_offset_without_check) / step_size : 0; - if (distance - steps * step_size > last_step_offset_without_check) { - if ((steps + 1) * step_size <= distance) - // This will be the case when last_step_offset_without_check >= safe_step_size - ++ steps; - else - do_final_difference = true; - } - if (steps + (distance < last_step_offset_without_check || (distance % step_size) != 0) < int(min_amount_offset) && min_amount_offset > 1) { - // yes one can add a bool as the standard specifies that a result from compare operators has to be 0 or 1 - // reduce the stepsize to ensure it is offset the required amount of times - step_size = distance / min_amount_offset; - if (step_size >= safe_step_size) { - // effectivly reduce last_step_offset_without_check - step_size = safe_step_size; - steps = min_amount_offset; - } else - steps = distance / step_size; - } - // offset in steps - for (int i = 0; i < steps; ++ i) { - ret = diff(offset(ret, step_size, ClipperLib::jtRound, scaled(0.01)), collision_trimmed()); - // ensure that if many offsets are done the performance does not suffer extremely by the new vertices of jtRound. - if (i % 10 == 7) - ret = polygons_simplify(ret, scaled(0.015)); - } - // offset the remainder - float last_offset = distance - steps * step_size; - if (last_offset > SCALED_EPSILON) - ret = offset(ret, distance - steps * step_size, ClipperLib::jtRound, scaled(0.01)); - ret = polygons_simplify(ret, scaled(0.015)); - - if (do_final_difference) - ret = diff(ret, collision_trimmed()); - return union_(ret); -} - class RichInterfacePlacer : public InterfacePlacer { public: RichInterfacePlacer( @@ -4173,9 +4065,9 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons const int num_layers = int(print_object.layer_count()) + num_raft_layers; overhangs.resize(num_layers); for (size_t i = 0; i < print_object.layer_count(); i++) { - for (ExPolygon& expoly : print_object.get_layer(i)->loverhangs) { - Polygons polys = to_polygons(expoly); - if (tree_support->overhang_types[&expoly] == TreeSupport::SharpTail) { polys = offset(polys, scale_(0.2)); + for (auto& expoly_type : print_object.get_layer(i)->loverhangs_with_type) { + Polygons polys = to_polygons(expoly_type.first); + if (expoly_type.second & TreeSupport::SharpTail) { polys = offset(polys, scale_(0.2)); } append(overhangs[i + num_raft_layers], polys); } @@ -4341,7 +4233,7 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons print.set_status(69, _L("Generating support")); generate_support_toolpaths(print_object.support_layers(), print_object.config(), support_params, print_object.slicing_parameters(), raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); - + auto t_end = std::chrono::high_resolution_clock::now(); BOOST_LOG_TRIVIAL(info) << "Total time of organic tree support: " << 0.001 * std::chrono::duration_cast(t_end - t_start).count() << " ms"; #if 0 @@ -4817,7 +4709,7 @@ void generate_tree_support_3D(PrintObject &print_object, TreeSupport* tree_suppo Points bedpts = tree_support->m_machine_border.contour.points; Pointfs bedptsf; std::transform(bedpts.begin(), bedpts.end(), std::back_inserter(bedptsf), [](const Point &p) { return unscale(p); }); - BuildVolume build_volume{ bedptsf, tree_support->m_print_config->printable_height }; + BuildVolume build_volume{ bedptsf, tree_support->m_print_config->printable_height, {}, {} }; TreeSupport3D::generate_support_areas(*print_object.print(), tree_support, build_volume, { idx }, throw_on_cancel); } diff --git a/src/libslic3r/Support/TreeSupportCommon.hpp b/src/libslic3r/Support/TreeSupportCommon.hpp index 8819674..67d62d7 100644 --- a/src/libslic3r/Support/TreeSupportCommon.hpp +++ b/src/libslic3r/Support/TreeSupportCommon.hpp @@ -61,7 +61,7 @@ struct TreeSupportMeshGroupSettings { config.support_interface_top_layers.value) * this->layer_height : 0; this->support_material_buildplate_only = config.support_on_build_plate_only; - this->support_xy_distance = scaled(config.support_object_xy_distance.value); + this->support_xy_distance = scaled(config.support_object_xy_distance.value); this->support_xy_distance_1st_layer = scaled(config.support_object_first_layer_gap.value); // Separation of interfaces, it is likely smaller than support_xy_distance. this->support_xy_distance_overhang = std::min(this->support_xy_distance, scaled(0.5 * external_perimeter_width)); @@ -72,7 +72,6 @@ struct TreeSupportMeshGroupSettings { this->support_floor_enable = config.support_interface_bottom_layers.value > 0; this->support_floor_layers = config.support_interface_bottom_layers.value; this->support_roof_pattern = config.support_interface_pattern; - this->support_pattern = config.support_base_pattern; this->support_line_spacing = scaled(config.support_base_pattern_spacing.value); this->support_wall_count = std::max(1, config.tree_support_wall_count.value); // at least 1 wall for organic tree support this->support_roof_line_distance = scaled(config.support_interface_spacing.value) + this->support_roof_line_width; @@ -165,9 +164,6 @@ struct TreeSupportMeshGroupSettings { // Support Roof Pattern (aka top interface) // The pattern with which the roofs of the support are printed. SupportMaterialInterfacePattern support_roof_pattern { smipAuto }; - // Support Pattern - // The pattern of the support structures of the print. The different options available result in sturdy or easy to remove support. - SupportMaterialPattern support_pattern { smpRectilinear }; // Support Line Distance // Distance between the printed support structure lines. This setting is calculated by the support density. coord_t support_line_spacing { scaled(2.66 - 0.4) }; @@ -283,7 +279,6 @@ struct TreeSupportSettings // support_infill_angles(mesh_group_settings.support_infill_angles), support_roof_angles(mesh_group_settings.support_roof_angles), roof_pattern(mesh_group_settings.support_roof_pattern), - support_pattern(mesh_group_settings.support_pattern), support_roof_line_width(mesh_group_settings.support_roof_line_width), support_line_spacing(mesh_group_settings.support_line_spacing), support_bottom_offset(mesh_group_settings.support_bottom_offset), diff --git a/src/libslic3r/Surface.cpp b/src/libslic3r/Surface.cpp index 58ac729..cf6e861 100644 --- a/src/libslic3r/Surface.cpp +++ b/src/libslic3r/Surface.cpp @@ -38,6 +38,7 @@ const char* surface_type_to_color_name(const SurfaceType surface_type) case stBottom: return "rgb(0,255,0)"; // "green"; case stBottomBridge: return "rgb(0,0,255)"; // "blue"; case stInternal: return "rgb(255,255,128)"; // yellow + case stFloatingVerticalShell: case stInternalSolid: return "rgb(255,0,255)"; // magenta case stInternalBridge: return "rgb(0,255,255)"; case stInternalVoid: return "rgb(128,128,128)"; diff --git a/src/libslic3r/Surface.hpp b/src/libslic3r/Surface.hpp index 96b4e7b..f8ef6d6 100644 --- a/src/libslic3r/Surface.hpp +++ b/src/libslic3r/Surface.hpp @@ -15,6 +15,7 @@ enum SurfaceType { stBottomBridge, // Normal sparse infill. stInternal, + stFloatingVerticalShell, // Full infill, supporting the top surfaces and/or defining the verticall wall thickness. stInternalSolid, // 1st layer of dense infill over sparse infill, printed with a bridging extrusion flow. @@ -37,6 +38,8 @@ public: unsigned short thickness_layers; // in layers double bridge_angle; // in radians, ccw, 0 = East, only 0+ (negative means undefined) unsigned short extra_perimeters; + bool counter_circle_compensation{false}; + std::vector holes_circle_compensation; // hole index Surface(SurfaceType _surface_type = stInternal) : surface_type(_surface_type), @@ -104,7 +107,8 @@ public: bool is_bridge() const { return this->surface_type == stBottomBridge || this->surface_type == stInternalBridge; } bool is_external() const { return this->is_top() || this->is_bottom(); } bool is_internal() const { return ! this->is_external(); } - bool is_solid() const { return this->is_external() || this->surface_type == stInternalSolid || this->surface_type == stInternalBridge; } + bool is_floating_vertical_shell() const { return this->surface_type == stFloatingVerticalShell; } + bool is_solid() const { return this->is_external() || this->is_floating_vertical_shell() || this->surface_type == stInternalSolid || this->surface_type == stInternalBridge; } bool is_solid_infill() const { return this->surface_type == stInternalSolid; } }; diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 469aca1..1cf1838 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -206,6 +206,9 @@ bool TriangleMesh::from_stl(stl_file& stl, bool repair) stl_generate_shared_vertices(&stl, this->its); fill_initial_stats(this->its, this->m_stats); + if (m_stats.volume < 0) { + flip_triangles(); + } return true; } @@ -1720,8 +1723,7 @@ float its_volume(const indexed_triangle_set &its) float height = normal.dot(triangle[0] - p0); volume += (area * height) / 3.0f; } - - return std::abs(volume); + return volume; } float its_average_edge_length(const indexed_triangle_set &its) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index b57d092..b08810b 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -1094,6 +1094,17 @@ bool TriangleSelector::Circle::is_edge_inside_cursor(const Triangle &tr, const s return false; } +TriangleSelector::HeightRange::HeightRange(float z_world_, const Vec3f &source_, float height_, const Transform3d &trafo_, const ClippingPlane &clipping_plane_) + : SinglePointCursor(Vec3f(0.f, 0.f, 0.f), source_, 1.f, trafo_, clipping_plane_), m_z_world(z_world_), m_height(height_) +{ + uniform_scaling = false;//HeightRange must use world cs + // overwrite base + source = trafo * source; + radius = height_; + radius_sqr = Slic3r::sqr(height_); + trafo_normal = trafo.linear().inverse().transpose(); +} + // QDS bool TriangleSelector::HeightRange::is_pointer_in_triangle(const Vec3f& p1_, const Vec3f& p2_, const Vec3f& p3_) const { @@ -1558,7 +1569,7 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i &vertices, const this->get_facets_split_by_tjoints( { vertices(0), midpoints(0), midpoints(2) }, { this->neighbor_child(neighbors(0), vertices(1), vertices(0), Partition::Second), - -1, + -1, this->neighbor_child(neighbors(2), vertices(0), vertices(2), Partition::First) }, out_triangles); this->get_facets_split_by_tjoints( @@ -1667,14 +1678,15 @@ std::pair>, std::vector> TriangleSelector: // In case this is leaf, we better save information about its state. int n = int(tr.get_state()); if (n >= 3) { - assert(n <= 16); - if (n <= 16) { - // Store "11" plus 4 bits of (n-3). - data.second.insert(data.second.end(), { true, true }); - n -= 3; - for (size_t bit_idx = 0; bit_idx < 4; ++bit_idx) - data.second.push_back(n & (uint64_t(0b0001) << bit_idx)); + data.second.insert(data.second.end(), {true, true}); + n -= 3; + while (n >= 15) { + data.second.insert(data.second.end(), {true, true, true, true}); + n -= 15; } + + for (size_t bit_idx = 0; bit_idx < 4; ++bit_idx) + data.second.push_back(n & (uint64_t(0b0001) << bit_idx)); } else { // Simple case, compatible with PrusaSlicer 2.3.1 and older for storing paint on supports and seams. // Store 2 bits of n. @@ -1700,7 +1712,11 @@ std::pair>, std::vector> TriangleSelector: return out.data; } -void TriangleSelector::deserialize(const std::pair>, std::vector> &data, bool needs_reset, EnforcerBlockerType max_ebt) +void TriangleSelector::deserialize(const std::pair>, std::vector> &data, + bool needs_reset, + EnforcerBlockerType max_ebt, + EnforcerBlockerType to_delete_filament, + EnforcerBlockerType replace_filament) { if (needs_reset) reset(); // dump any current state @@ -1731,7 +1747,7 @@ void TriangleSelector::deserialize(const std::pair> 2); + // Only valid if not is_split. Value of the second nibble was subtracted by 3, so it is added back. + auto state = EnforcerBlockerType::NONE; + if (!is_split) { + if ((code & 0b1100) == 0b1100){ + int next_code = next_nibble(); + int num = 0; + while (next_code == 0b1111) { + num++; + next_code = next_nibble(); + } + state = EnforcerBlockerType(next_code + 15 * num + 3);//old:next_nibble() + 3; + } + else { + state = EnforcerBlockerType(code >> 2); + } + } // QDS - if (state > max_ebt) + if (state == to_delete_filament) + state = replace_filament; + else if (to_delete_filament != EnforcerBlockerType::NONE && state != EnforcerBlockerType::NONE) { + state = state > to_delete_filament ? EnforcerBlockerType((int)state - 1) : state; + } + + if (state > max_ebt) { + assert(false); state = EnforcerBlockerType::NONE; + } // Only valid if is_split. int special_side = code >> 2; @@ -1822,7 +1860,7 @@ bool TriangleSelector::has_facets(const std::pair &triangle_id_and_ibit : data.first) { int ibit = triangle_id_and_ibit.second; assert(ibit < int(data.second.size())); - auto next_niqdte = [&data, &ibit = ibit]() { + auto next_nibble = [&data, &ibit = ibit]() { int n = 0; for (int i = 0; i < 4; ++ i) n |= data.second[ibit ++] << i; @@ -1830,12 +1868,26 @@ bool TriangleSelector::has_facets(const std::pair negative of a number of children // >= 0 -> state - auto num_children_or_state = [&next_niqdte]() -> int { - int code = next_niqdte(); + auto num_children_or_state = [&next_nibble]() -> int { + int code = next_nibble(); int num_of_split_sides = code & 0b11; - return num_of_split_sides == 0 ? - ((code & 0b1100) == 0b1100 ? next_niqdte() + 3 : code >> 2) : - - num_of_split_sides - 1; + if (num_of_split_sides == 0) { + int state = 0; + if ((code & 0b1100) == 0b1100) { + int next_code = next_nibble(); + int num = 0; + while (next_code == 0b1111) { + num++; + next_code = next_nibble(); + } + state = next_code + 15 * num + 3; // old:next_nibble() + 3; + } else { + state = code >> 2; + } + return state; + } else { + return -num_of_split_sides - 1;// < 0 -> negative of a number of children + } }; int state = num_children_or_state(); diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 21a5ae8..54b2b1e 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -161,8 +161,7 @@ public: public: HeightRange() = delete; // QDS: set cursor_radius to 0.1 for high smooth edge - explicit HeightRange(float z_world_, const Vec3f& source_, float height_, const Transform3d& trafo_, const ClippingPlane& clipping_plane_) - : SinglePointCursor(Vec3f(0.f, 0.f, 0.f), source_, 1.f, trafo_, clipping_plane_), m_z_world(z_world_), m_height(height_) {} + explicit HeightRange(float z_world_, const Vec3f &source_, float height_, const Transform3d &trafo_, const ClippingPlane &clipping_plane_); ~HeightRange() override = default; bool is_pointer_in_triangle(const Vec3f& p1, const Vec3f& p2, const Vec3f& p3) const override; @@ -276,7 +275,11 @@ public: std::pair>, std::vector> serialize() const; // Load serialized data. Assumes that correct mesh is loaded. - void deserialize(const std::pair>, std::vector>& data, bool needs_reset = true, EnforcerBlockerType max_ebt = EnforcerBlockerType::ExtruderMax); + void deserialize(const std::pair>, std::vector> &data, + bool needs_reset = true, + EnforcerBlockerType max_ebt = EnforcerBlockerType::ExtruderMax, + EnforcerBlockerType to_delete_filament = EnforcerBlockerType::NONE, + EnforcerBlockerType replace_filament = EnforcerBlockerType::NONE); // For all triangles, remove the flag indicating that the triangle was selected by seed fill. void seed_fill_unselect_all_triangles(); diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index c11c976..af19b13 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -60,9 +60,13 @@ #define CLI_OBJECT_COLLISION_IN_SEQ_PRINT -63 #define CLI_OBJECT_COLLISION_IN_LAYER_PRINT -64 #define CLI_SPIRAL_MODE_INVALID_PARAMS -65 +#define CLI_FILAMENT_CAN_NOT_MAP -66 +#define CLI_ONLY_ONE_TPU_SUPPORTED -67 +#define CLI_FILAMENTS_NOT_SUPPORTED_BY_EXTRUDER -68 #define CLI_SLICING_ERROR -100 #define CLI_GCODE_PATH_CONFLICTS -101 +#define CLI_GCODE_PATH_IN_UNPRINTABLE_AREA -102 #define CLI_FILAMENT_UNPRINTABLE_ON_FIRST_LAYER -103 namespace boost { namespace filesystem { class directory_entry; }} @@ -129,6 +133,39 @@ inline DataType round_up_divide(DataType dividend, DataType divisor) //!< Return return (dividend + divisor - 1) / divisor; } +template +T get_max_element(const std::vector &vec) +{ + static_assert(std::is_arithmetic::value, "T must be of numeric type."); + if (vec.empty()) + return static_cast(0); + + return *std::max_element(vec.begin(), vec.end()); +} + + +template +std::vector convert_vector(const std::vector& src) { + std::vector dst; + dst.reserve(src.size()); + for (const auto& elem : src) { + if constexpr (std::is_signed_v) { + if (elem > static_cast(std::numeric_limits::max())) { + throw std::overflow_error("Source value exceeds destination maximum"); + } + if (elem < static_cast(std::numeric_limits::min())) { + throw std::underflow_error("Source value below destination minimum"); + } + } + else { + if (elem < 0) { + throw std::invalid_argument("Negative value in source for unsigned destination"); + } + } + dst.push_back(static_cast(elem)); + } + return dst; +} // Set a path with GUI localization files. void set_local_dir(const std::string &path); @@ -169,6 +206,7 @@ typedef std::string local_encoded_string; extern local_encoded_string encode_path(const char *src); extern std::string decode_path(const char *src); extern std::string normalize_utf8_nfc(const char *src); +extern std::vector split_string(const std::string &str, char delimiter); // Safely rename a file even if the target exists. // On Windows, the file explorer (or anti-virus or whatever else) often locks the file @@ -190,7 +228,7 @@ CopyFileResult copy_file_inner(const std::string &from, const std::string &to, s // of the source file before renaming. // Additional error info is passed in error message. extern CopyFileResult copy_file(const std::string &from, const std::string &to, std::string& error_message, const bool with_check = false); - +extern bool copy_framework(const std::string &from, const std::string &to); // Compares two files if identical. extern CopyFileResult check_copy(const std::string& origin, const std::string& copy); diff --git a/src/libslic3r/VectorFormatter.hpp b/src/libslic3r/VectorFormatter.hpp new file mode 100644 index 0000000..bf7bba8 --- /dev/null +++ b/src/libslic3r/VectorFormatter.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +// custom vector wrapper for outputting to log +template struct VectorFormatter +{ + const Container &vec; + explicit VectorFormatter(const Container& v) : vec(v) {} + + friend std::ostream &operator<<(std::ostream &os, const VectorFormatter &vf) + { + os << "["; + for (auto it = vf.vec.begin(); it != vf.vec.end();it++) { + os << *it; + if (std::next(it) != vf.vec.end()) { os << ", "; } + } + os << "]"; + return os; + } +}; + +// custom vector wrapper for outputting to log +template struct MapFormatter +{ + const std::map &vec; + explicit MapFormatter(const std::map &v) : vec(v) {} + + friend std::ostream &operator<<(std::ostream &os, const MapFormatter &vf) + { + os << "["; + for (auto it = vf.vec.begin(); it != vf.vec.end(); it++) { + os << it->first << " : " << it->second; + if (std::next(it) != vf.vec.end()) { os << ", "; } + } + os << "]"; + return os; + } +}; diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 49fb8bd..ad6077c 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -50,12 +50,12 @@ using coordf_t = double; // for a trheshold in a difference of radians, // for a threshold of a cross product of two non-normalized vectors etc. static constexpr double EPSILON = 1e-4; -// Scaling factor for a conversion from coord_t to coordf_t: 10e-6 +// Scaling factor for a conversion from coord_t to coordf_t: 1e-5 // This scaling generates a following fixed point representation with for a 32bit integer: // 0..4294mm with 1nm resolution // int32_t fits an interval of (-2147.48mm, +2147.48mm) // with int64_t we don't have to worry anymore about the size of the int. -static constexpr double SCALING_FACTOR = 0.000001; +static constexpr double SCALING_FACTOR = 0.00001; static constexpr double PI = 3.141592653589793238; #define POLY_SIDE_COUNT 24 // for brim ear circle // When extruding a closed loop, the loop is interrupted and shortened a bit to reduce the seam. diff --git a/src/libslic3r/libslic3r_version.h.in b/src/libslic3r/libslic3r_version.h.in index 84812a4..3ab5bab 100644 --- a/src/libslic3r/libslic3r_version.h.in +++ b/src/libslic3r/libslic3r_version.h.in @@ -9,5 +9,6 @@ //#define SLIC3R_RC_VERSION "@SLIC3R_VERSION@" #define QDT_RELEASE_TO_PUBLIC @QDT_RELEASE_TO_PUBLIC@ #define QDT_INTERNAL_TESTING @QDT_INTERNAL_TESTING@ +#define QDT_RELEASE_TO_DEVELOPER @QDT_RELEASE_TO_DEVELOPER@ #endif /* __SLIC3R_VERSION_H */ diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 443d91f..7456d2d 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -943,6 +943,34 @@ __finished: #endif } +bool copy_framework(const std::string &from, const std::string &to) +{ + boost::filesystem::path src(from), dst(to); + try { + if (!boost::filesystem::is_directory(src)) { + std::cerr << "Error: Source is not a directory: " << src << std::endl; + return false; + } + boost::filesystem::create_directories(dst); + for (boost::filesystem::directory_iterator it(src); it != boost::filesystem::directory_iterator(); ++it) { + const auto &entry = it->path(); + const auto dest_path = dst / entry.filename(); + + if (boost::filesystem::is_symlink(entry)) { + boost::filesystem::copy_symlink(entry, dest_path); + } else if (boost::filesystem::is_directory(entry)) { + copy_framework(it->path().string(), dest_path.string()); + } else { + boost::filesystem::copy(entry, dest_path, boost::filesystem::copy_options::overwrite_existing); + } + } + return true; + } catch (const boost::filesystem::filesystem_error &e) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Filesystem error: " << e.what(); + } + return false; +} + CopyFileResult check_copy(const std::string &origin, const std::string ©) { boost::nowide::ifstream f1(origin, std::ifstream::in | std::ifstream::binary | std::ifstream::ate); @@ -1089,6 +1117,18 @@ std::string normalize_utf8_nfc(const char *src) return boost::locale::normalize(src, boost::locale::norm_nfc, locale_utf8); } +std::vector split_string(const std::string &str, char delimiter) +{ + std::vector result; + std::stringstream ss(str); + std::string substr; + + while (std::getline(ss, substr, delimiter)) { + result.push_back(substr); + } + return result; +} + namespace PerlUtils { // Get a file name including the extension. std::string path_to_filename(const char *src) { return boost::filesystem::path(src).filename().string(); }