From 50572b196ef173ca518c109f810d062511aa8ec0 Mon Sep 17 00:00:00 2001 From: wjyLearn <93930815+wjyLearn@users.noreply.github.com> Date: Sat, 20 Dec 2025 19:15:57 +0800 Subject: [PATCH] update src\libslic3r --- src/libslic3r/AppConfig.cpp | 43 +- src/libslic3r/AppConfig.hpp | 3 + src/libslic3r/CMakeLists.txt | 2 + .../CSGMesh/PerformCSGMeshBooleans.hpp | 2 +- src/libslic3r/Calib.hpp | 10 + src/libslic3r/Config.hpp | 46 +- src/libslic3r/Extruder.cpp | 1 - src/libslic3r/Extruder.hpp | 7 +- src/libslic3r/FilamentGroup.cpp | 782 +++++++++++++++--- src/libslic3r/FilamentGroup.hpp | 123 ++- src/libslic3r/FilamentGroupUtils.cpp | 88 ++ src/libslic3r/FilamentGroupUtils.hpp | 26 +- src/libslic3r/Fill/Fill.cpp | 18 +- src/libslic3r/Fill/Fill3DHoneycomb.cpp | 19 +- src/libslic3r/Fill/FillAdaptive.cpp | 4 + src/libslic3r/Fill/FillBase.cpp | 63 ++ src/libslic3r/Fill/FillBase.hpp | 6 + src/libslic3r/Fill/FillCrossHatch.cpp | 6 +- src/libslic3r/Fill/FillGyroid.cpp | 8 +- src/libslic3r/Fill/FillHoneycomb.cpp | 5 +- src/libslic3r/Fill/FillLightning.cpp | 11 +- src/libslic3r/Fill/FillRectilinear.cpp | 30 +- src/libslic3r/Format/OBJ.cpp | 27 +- src/libslic3r/Format/OBJ.hpp | 4 + src/libslic3r/Format/objparser.cpp | 24 +- src/libslic3r/Format/objparser.hpp | 2 + src/libslic3r/Format/qds_3mf.cpp | 184 ++++- src/libslic3r/Format/qds_3mf.hpp | 4 +- src/libslic3r/GCode.cpp | 286 +++++-- src/libslic3r/GCode.hpp | 1 + src/libslic3r/GCode/GCodeEditor.cpp | 2 +- src/libslic3r/GCode/GCodeProcessor.cpp | 375 ++++++--- src/libslic3r/GCode/GCodeProcessor.hpp | 37 +- src/libslic3r/GCode/TimelapsePosPicker.cpp | 6 +- src/libslic3r/GCode/ToolOrderUtils.cpp | 540 +++++++++++- src/libslic3r/GCode/ToolOrderUtils.hpp | 45 +- src/libslic3r/GCode/ToolOrdering.cpp | 294 +++++-- src/libslic3r/GCode/ToolOrdering.hpp | 11 +- src/libslic3r/GCode/WipeTower.cpp | 300 +++++-- src/libslic3r/GCode/WipeTower.hpp | 50 +- src/libslic3r/GCodeWriter.cpp | 33 +- src/libslic3r/GCodeWriter.hpp | 1 + .../Interlocking/InterlockingGenerator.cpp | 157 ++++ .../Interlocking/InterlockingGenerator.hpp | 10 +- src/libslic3r/Layer.cpp | 16 +- src/libslic3r/Layer.hpp | 4 +- src/libslic3r/MeshBoolean.cpp | 139 +++- src/libslic3r/MeshBoolean.hpp | 9 +- src/libslic3r/Model.cpp | 45 +- src/libslic3r/Model.hpp | 3 +- src/libslic3r/MultiMaterialSegmentation.cpp | 2 +- src/libslic3r/MultiNozzleUtils.cpp | 487 +++++++++++ src/libslic3r/MultiNozzleUtils.hpp | 135 +++ src/libslic3r/ObjColorUtils.hpp | 155 ++-- src/libslic3r/PerimeterGenerator.cpp | 2 +- src/libslic3r/Preset.cpp | 59 +- src/libslic3r/Preset.hpp | 3 + src/libslic3r/PresetBundle.cpp | 318 +++++-- src/libslic3r/PresetBundle.hpp | 44 +- src/libslic3r/Print.cpp | 263 ++++-- src/libslic3r/Print.hpp | 27 +- src/libslic3r/PrintApply.cpp | 74 +- src/libslic3r/PrintBase.hpp | 4 +- src/libslic3r/PrintConfig.cpp | 503 ++++++++--- src/libslic3r/PrintConfig.hpp | 48 +- src/libslic3r/PrintObject.cpp | 119 ++- src/libslic3r/PrintObjectSlice.cpp | 3 + src/libslic3r/ProjectTask.hpp | 5 + src/libslic3r/Support/SupportParameters.hpp | 5 +- src/libslic3r/Support/TreeSupport.cpp | 15 +- src/libslic3r/Support/TreeSupportCommon.hpp | 5 +- src/libslic3r/TriangleMesh.cpp | 14 + src/libslic3r/TriangleMesh.hpp | 1 + src/libslic3r/Utils.hpp | 12 +- src/libslic3r/utils.cpp | 9 + 75 files changed, 5255 insertions(+), 969 deletions(-) create mode 100644 src/libslic3r/MultiNozzleUtils.cpp create mode 100644 src/libslic3r/MultiNozzleUtils.hpp diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 420e2c7..4819984 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -1,5 +1,6 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" +#include "libslic3r/MultiNozzleUtils.hpp" #include "AppConfig.hpp" //QDS #include "Preset.hpp" @@ -226,8 +227,8 @@ void AppConfig::set_defaults() if (get("enable_advanced_antialiasing").empty()) set_bool("enable_advanced_antialiasing", false); - if (get("enable_advanced_antialiasing").empty()) - set_bool("enable_advanced_antialiasing", false); + if (get("enable_advanced_gcode_viewer_").empty()) + set_bool("enable_advanced_gcode_viewer_", true); if (get("gizmo_keep_screen_size").empty()) set_bool("gizmo_keep_screen_size", true); @@ -585,7 +586,7 @@ std::string AppConfig::load() auto path = AppConfig::loading_path(); ifs.open(path); if (!ifs.is_open()) { - BOOST_LOG_TRIVIAL(info) << "AppConfig::load() open fail:" << AppConfig::loading_path(); + BOOST_LOG_TRIVIAL(info) << "AppConfig::load() open fail:" << PathSanitizer::sanitize(path); return "Line break format may be incorrect."; } #ifdef WIN32 @@ -593,7 +594,7 @@ std::string AppConfig::load() input_stream << ifs.rdbuf(); std::string total_string = input_stream.str(); if (total_string.empty()) { - BOOST_LOG_TRIVIAL(info) << "AppConfig::load() read fail:" << AppConfig::loading_path(); + BOOST_LOG_TRIVIAL(info) << "AppConfig::load() read fail:" << PathSanitizer::sanitize(path); return "read fail."; } else { size_t last_pos = total_string.find_last_of('}'); @@ -604,7 +605,7 @@ std::string AppConfig::load() 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() + BOOST_LOG_TRIVIAL(info) << "The configuration file " << PathSanitizer::sanitize(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); @@ -718,6 +719,14 @@ std::string AppConfig::load() preset_info.nozzle_volume_type = NozzleVolumeType(cali_it.value()["nozzle_volume_type"].get()); if (cali_it.value().contains("bed_type")) preset_info.bed_type = BedType(cali_it.value()["bed_type"].get()); + if (cali_it.value().contains("nozzle_pos_id")) + preset_info.nozzle_pos_id = cali_it.value()["nozzle_pos_id"].get(); + if (cali_it.value().contains("nozzle_sn")) + preset_info.nozzle_sn = cali_it.value()["nozzle_sn"].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(); + preset_info.name = cali_it.value()["name"].get(); cali_info.selected_presets.push_back(preset_info); } } @@ -752,7 +761,7 @@ std::string AppConfig::load() } } } catch(std::exception err) { - BOOST_LOG_TRIVIAL(info) << format("parse app config \"%1%\", error: %2%", AppConfig::loading_path(), err.what()); + BOOST_LOG_TRIVIAL(info) << format("parse app config \"%1%\", error: %2%", PathSanitizer::sanitize(AppConfig::loading_path()), err.what()); return err.what(); } @@ -794,7 +803,7 @@ void AppConfig::save() // The config is first written to a file with a PID suffix and then moved // to avoid race conditions with multiple instances of Slic3r const auto path = config_path(); - std::string path_pid = (boost::format("%1%.%2%") % path % get_current_pid()).str(); + std::string path_pid = (boost::format("%1%.%2%") % PathSanitizer::sanitize(path) % get_current_pid()).str(); json j; @@ -845,6 +854,8 @@ void AppConfig::save() preset_json["nozzle_volume_type"] = int(filament_preset.nozzle_volume_type); preset_json["bed_type"] = int(filament_preset.bed_type); preset_json["nozzle_diameter"] = filament_preset.nozzle_diameter; + preset_json["nozzle_pos_id"] = filament_preset.nozzle_pos_id; + preset_json["nozzle_sn"] = filament_preset.nozzle_sn; preset_json["filament_id"] = filament_preset.filament_id; preset_json["setting_id"] = filament_preset.setting_id; preset_json["name"] = filament_preset.name; @@ -927,7 +938,7 @@ void AppConfig::save() std::string backup_path = (boost::format("%1%.bak") % path).str(); // Copy configuration file with PID suffix into the configuration file with "bak" suffix. if (copy_file(path_pid, backup_path, error_message, false) != SUCCESS) - BOOST_LOG_TRIVIAL(error) << "Copying from " << path_pid << " to " << backup_path << " failed. Failed to create a backup configuration."; + BOOST_LOG_TRIVIAL(error) << "Copying from " << path_pid << " to " << PathSanitizer::sanitize(backup_path) << " failed. Failed to create a backup configuration."; #endif // Rename the config atomically. @@ -952,7 +963,7 @@ std::string AppConfig::load() #ifdef WIN32 // Verify the checksum of the config file without taking just for debugging purpose. if (!verify_config_file_checksum(ifs)) - BOOST_LOG_TRIVIAL(info) << "The configuration file " << AppConfig::loading_path() << + BOOST_LOG_TRIVIAL(info) << "The configuration file " << PathSanitizer::sanitize(AppConfig::loading_path()) << " has a wrong MD5 checksum or the checksum is missing. This may indicate a file corruption or a harmless user edit."; ifs.seekg(0, boost::nowide::ifstream::beg); @@ -968,17 +979,17 @@ std::string AppConfig::load() // Compute checksum of the configuration backup file and try to load configuration from it when the checksum is correct. boost::nowide::ifstream backup_ifs(backup_path); if (!verify_config_file_checksum(backup_ifs)) { - BOOST_LOG_TRIVIAL(error) << format("Both \"%1%\" and \"%2%\" are corrupted. It isn't possible to restore configuration from the backup.", AppConfig::loading_path(), backup_path); + BOOST_LOG_TRIVIAL(error) << format("Both \"%1%\" and \"%2%\" are corrupted. It isn't possible to restore configuration from the backup.", PathSanitizer::sanitize(AppConfig::loading_path()), PathSanitizer::sanitize(backup_path)); backup_ifs.close(); boost::filesystem::remove(backup_path); } else if (std::string error_message; copy_file(backup_path, AppConfig::loading_path(), error_message, false) != SUCCESS) { - BOOST_LOG_TRIVIAL(error) << format("Configuration file \"%1%\" is corrupted. Failed to restore from backup \"%2%\": %3%", AppConfig::loading_path(), backup_path, error_message); + BOOST_LOG_TRIVIAL(error) << format("Configuration file \"%1%\" is corrupted. Failed to restore from backup \"%2%\": %3%", PathSanitizer::sanitize(AppConfig::loading_path()), PathSanitizer::sanitize(backup_path), error_message); backup_ifs.close(); boost::filesystem::remove(backup_path); } else { - BOOST_LOG_TRIVIAL(info) << format("Configuration file \"%1%\" was corrupted. It has been succesfully restored from the backup \"%2%\".", AppConfig::loading_path(), backup_path); + BOOST_LOG_TRIVIAL(info) << format("Configuration file \"%1%\" was corrupted. It has been succesfully restored from the backup \"%2%\".", PathSanitizer::sanitize(AppConfig::loading_path()), PathSanitizer::sanitize(backup_path)); // Try parse configuration file after restore from backup. try { ifs.open(AppConfig::loading_path()); @@ -986,13 +997,13 @@ std::string AppConfig::load() recovered = true; } catch (pt::ptree_error& ex) { - BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\" after it has been restored from backup: %2%", AppConfig::loading_path(), ex.what()); + BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\" after it has been restored from backup: %2%", PathSanitizer::sanitize(AppConfig::loading_path()), ex.what()); } } } else #endif // WIN32 - BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\": %2%", AppConfig::loading_path(), ex.what()); + BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\": %2%", PathSanitizer::sanitize(AppConfig::loading_path()), ex.what()); if (!recovered) { // Report the initial error of parsing PrusaSlicer.ini. // Error while parsing config file. We'll customize the error message and rethrow to be displayed. @@ -1078,7 +1089,7 @@ void AppConfig::save() // The config is first written to a file with a PID suffix and then moved // to avoid race conditions with multiple instances of Slic3r const auto path = config_path(); - std::string path_pid = (boost::format("%1%.%2%") % path % get_current_pid()).str(); + std::string path_pid = (boost::format("%1%.%2%") % PathSanitizer::sanitize(path) % get_current_pid()).str(); std::stringstream config_ss; if (m_mode == EAppMode::Editor) @@ -1132,7 +1143,7 @@ void AppConfig::save() std::string backup_path = (boost::format("%1%.bak") % path).str(); // Copy configuration file with PID suffix into the configuration file with "bak" suffix. if (copy_file(path_pid, backup_path, error_message, false) != SUCCESS) - BOOST_LOG_TRIVIAL(error) << "Copying from " << path_pid << " to " << backup_path << " failed. Failed to create a backup configuration."; + BOOST_LOG_TRIVIAL(error) << "Copying from " << path_pid << " to " << PathSanitizer::sanitize(backup_path) << " failed. Failed to create a backup configuration."; #endif // Rename the config atomically. diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index c414818..6c8fd25 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -24,6 +24,9 @@ using namespace nlohmann; namespace Slic3r { +namespace MultiNozzleUtils{ + struct NozzleGroupInfo; +} class AppConfig { public: diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 384f644..eee60a4 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -467,6 +467,8 @@ set(lisbslic3r_sources Interlocking/InterlockingGenerator.cpp Interlocking/VoxelUtils.hpp Interlocking/VoxelUtils.cpp + MultiNozzleUtils.hpp + MultiNozzleUtils.cpp GCode/Thumbnails.cpp GCode/Thumbnails.hpp ) diff --git a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp index 5b71c8e..a235c70 100644 --- a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp +++ b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp @@ -131,7 +131,7 @@ namespace detail_mcut { MeshBoolean::mcut::do_boolean(*dst, *src,"UNION"); break; case CSGType::Difference: - MeshBoolean::mcut::do_boolean(*dst, *src,"SUBSTRACTION"); + MeshBoolean::mcut::do_boolean(*dst, *src,"A_NOT_B"); break; case CSGType::Intersection: MeshBoolean::mcut::do_boolean(*dst, *src,"INTERSECTION"); diff --git a/src/libslic3r/Calib.hpp b/src/libslic3r/Calib.hpp index 79bc675..227305a 100644 --- a/src/libslic3r/Calib.hpp +++ b/src/libslic3r/Calib.hpp @@ -83,6 +83,8 @@ public: NozzleVolumeType nozzle_volume_type; BedType bed_type; float nozzle_diameter; + int nozzle_pos_id{-1}; + std::string nozzle_sn; std::string filament_id; std::string setting_id; std::string name; @@ -93,6 +95,8 @@ public: this->extruder_id = other.extruder_id; this->nozzle_volume_type = other.nozzle_volume_type; this->nozzle_diameter = other.nozzle_diameter; + this->nozzle_pos_id = other.nozzle_pos_id; + this->nozzle_sn = other.nozzle_sn; this->filament_id = other.filament_id; this->setting_id = other.setting_id; this->name = other.name; @@ -123,7 +127,9 @@ public: int ams_id = 0; int slot_id = 0; int cali_idx = -1; + int nozzle_pos_id = -1; //-1 means no nozzle pos float nozzle_diameter; + std::string nozzle_sn; std::string filament_id; std::string setting_id; std::string name; @@ -140,7 +146,9 @@ struct PACalibIndexInfo int ams_id = 0; int slot_id = 0; int cali_idx = -1; // -1 means default + int nozzle_pos_id = -1; //-1 means no nozzle pos float nozzle_diameter; + std::string nozzle_sn; std::string filament_id; }; @@ -148,7 +156,9 @@ struct PACalibExtruderInfo { int extruder_id = 0; NozzleVolumeType nozzle_volume_type; + int nozzle_pos_id = -1; //-1 means no nozzle pos float nozzle_diameter; + std::string nozzle_sn; std::string filament_id = ""; bool use_extruder_id{true}; bool use_nozzle_volume_type{true}; diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index c5bd2e9..2ee0654 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -356,10 +356,11 @@ public: 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_with_restore_2(const std::string key, const ConfigOptionVectorBase* rhs, std::vector& restore_index, int start, int len) = 0; + virtual void set_with_restore_2(const std::string key, const ConfigOptionVectorBase* rhs, std::vector& restore_index, int start, int len, bool skip_error = false) = 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; + virtual void set_with_default(const ConfigOptionVectorBase* inherits) = 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. @@ -515,7 +516,7 @@ public: //restore_index: which index in this vector need to be restored //start: which index in this vector need to be replaced //count: how many items in this vector need to be replaced - virtual void set_with_restore_2(const std::string key, const ConfigOptionVectorBase* rhs, std::vector& restore_index, int start, int len) override + virtual void set_with_restore_2(const std::string key, const ConfigOptionVectorBase* rhs, std::vector& restore_index, int start, int len, bool skip_error = false) override { if (rhs->type() == this->type()) { //backup original ones @@ -536,11 +537,17 @@ public: } // Assign the new value from the rhs vector. - auto other = static_cast*>(rhs); + auto other = const_cast*>(static_cast*>(rhs)); if (other->values.size() != (restore_index.size())) { - std::string error_message = "ConfigOptionVector::set_with_restore_2(): Assigning from an vector with invalid restore_index size: key="+key; - throw ConfigurationError(error_message); + if (skip_error) { + T default_v = other->values.front(); + other->values.resize(restore_index.size(), default_v); + } + else { + std::string error_message = "ConfigOptionVector::set_with_restore_2(): Assigning from an vector with invalid restore_index size: key="+key; + throw ConfigurationError(error_message); + } } for (size_t i = 0; i < restore_index.size(); i++) { @@ -597,7 +604,7 @@ public: 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])) + if (dest_index[i] < other->values.size() && !other->is_nil(dest_index[i])) this->values[i * stride +j] = other->values[dest_index[i] * stride +j]; } } @@ -643,6 +650,28 @@ public: throw ConfigurationError("ConfigOptionVector::set_with_nil(): Assigning an incompatible type"); } + //set a item related with extruder variants when load user config, set the missed value of some extruder to default ones from inherits + //this item has missed value with old user config + //inherits: item from inherit config + virtual void set_with_default(const ConfigOptionVectorBase* inherits) override + { + if (inherits->type() == this->type()) { + auto inherits_opt = static_cast*>(inherits); + + if (inherits->size() <= this->size()) + return; + size_t delta = inherits->size() - this->size(); + this->values.resize(inherits->size(), this->values.front()); + + for (size_t i = 0; i < delta; i++) { + size_t index = inherits->size() - delta + i; + this->values[index] = inherits_opt->values[index]; + } + } + else + throw ConfigurationError("ConfigOptionVector::set_with_default(): Assigning an incompatible type"); + } + const T& get_at(size_t i) const { assert(! this->values.empty()); @@ -734,6 +763,7 @@ public: } // Apply an override option, possibly a nullable one. // assume lhs is not nullable even it is nullable + //default_index are 0 based bool apply_override(const ConfigOption *rhs, std::vector& default_index) override { //if (this->nullable()) // throw ConfigurationError("Cannot override a nullable ConfigOption."); @@ -769,8 +799,8 @@ public: this->values[i] = rhs_vec->values[i]; modified = true; } else { - if ((i < default_index.size()) && (default_index[i] - 1 < default_value.size())) - this->values[i] = default_value[default_index[i] - 1]; + if ((i < default_index.size()) && (default_index[i] < default_value.size())) + this->values[i] = default_value[default_index[i]]; else this->values[i] = default_value[0]; } diff --git a/src/libslic3r/Extruder.cpp b/src/libslic3r/Extruder.cpp index a72a5d6..763582d 100644 --- a/src/libslic3r/Extruder.cpp +++ b/src/libslic3r/Extruder.cpp @@ -12,7 +12,6 @@ Extruder::Extruder(unsigned int id, GCodeConfig *config, bool share_extruder) : m_share_extruder(share_extruder) { reset(); - // cache values that are going to be called often m_e_per_mm3 = this->filament_flow_ratio(); m_e_per_mm3 /= this->filament_crossection(); diff --git a/src/libslic3r/Extruder.hpp b/src/libslic3r/Extruder.hpp index 2a94da1..c9179f7 100644 --- a/src/libslic3r/Extruder.hpp +++ b/src/libslic3r/Extruder.hpp @@ -22,9 +22,8 @@ public: } else { m_E = 0; m_retracted = 0; - m_restart_extra = 0; } - + m_restart_extra = 0; m_absolute_E = 0; } @@ -57,6 +56,10 @@ public: double retract_length_toolchange() const; double retract_restart_extra_toolchange() const; + bool is_share_extruder() const { return m_share_extruder; } + double get_single_retracted_length() const { return m_retracted; } + double get_share_retracted_length() const { return m_share_retracted[extruder_id()]; } + private: // Private constructor to create a key for a search in std::set. Extruder(unsigned int id) : m_id(id) {} diff --git a/src/libslic3r/FilamentGroup.cpp b/src/libslic3r/FilamentGroup.cpp index 7e5d8c2..795df4d 100644 --- a/src/libslic3r/FilamentGroup.cpp +++ b/src/libslic3r/FilamentGroup.cpp @@ -9,6 +9,8 @@ namespace Slic3r { using namespace FilamentGroupUtils; + constexpr uint32_t GOLDEN_RATIO_32 = 0x9e3779b9; + // 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) { @@ -36,88 +38,30 @@ namespace Slic3r 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) + static uint64_t fnv_hash_two_ints(const int a, const int b) { - 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; + constexpr uint64_t FNV_OFFSET_BASIS = 14695981039346656037ULL; + constexpr uint64_t FNV_PRIME = 1099511628211ULL; + constexpr uint64_t SALT_A = 0xA5A5A5A5A5A5A5A5ULL; + constexpr uint64_t SALT_B = 0x5A5A5A5A5A5A5A5AULL; + + uint64_t h = FNV_OFFSET_BASIS; + h ^= static_cast(a) + SALT_A; + h *= FNV_PRIME; + h ^= static_cast(b) + SALT_B; + h *= FNV_PRIME; + + return h; } - 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]); + static double evaluate_score(const double flush, const double time, const bool with_time = false) { + if (!with_time) return flush; - 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; + double approx_density = 1.26; // g/cm^3 + double approx_flush_speed = 180; // s/g + double correction_factor = 2; + double flush_score = flush * approx_density * approx_flush_speed * correction_factor / 1000; + return flush_score + time; } /** @@ -128,15 +72,17 @@ namespace Slic3r * considered valid. * * @param map_lists Group list with similar flush count + * @param nozzle_lists nozzle_id -> extruder_id * @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, + std::vector select_best_group_for_ams(const std::vector>& filament_to_nozzles, + const std::vector& nozzle_list, const std::vector& used_filaments, - const std::vector& used_filament_info, + const std::vector& used_filament_info, const std::vector>& machine_filament_info_, const double color_threshold) { @@ -151,12 +97,13 @@ namespace Slic3r int best_cost = std::numeric_limits::max(); std::vectorbest_map; - for (auto& map : map_lists) { + for (auto &filament_to_nozzle : filament_to_nozzles) { 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; + auto &nozzle = nozzle_list[filament_to_nozzle[used_filaments[i]]]; + int target_group = nozzle.extruder_id == 0 ? 0 : 1; group_colors[target_group].emplace_back(used_filament_info[i].color); group_filaments[target_group].emplace_back(i); } @@ -211,7 +158,7 @@ namespace Slic3r if (best_map.empty() || group_cost < best_cost) { best_cost = group_cost; - best_map = map; + best_map = filament_to_nozzle; } } @@ -228,7 +175,7 @@ namespace Slic3r return; } double gap_rate = (double)std::abs(elem.cost - best.cost) / (double)best.cost; - if (gap_rate < gap_threshold) + if (gap_rate <= gap_threshold) heap.push(elem); }; @@ -271,11 +218,11 @@ namespace Slic3r 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()); + sort_remove_duplicates(used_filaments); return used_filaments; } - FlushDistanceEvaluator::FlushDistanceEvaluator(const FlushMatrix& flush_matrix, const std::vector& used_filaments, const std::vector>& layer_filaments, double p) + FlushDistanceEvaluator::FlushDistanceEvaluator(const std::vector& 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())); @@ -296,30 +243,45 @@ namespace Slic3r } } - m_distance_matrix.resize(used_filaments.size(), std::vector(used_filaments.size())); + m_distance_matrix.resize(flush_matrix.size(), std::vector>(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 { + for (size_t k = 0; k < flush_matrix.size(); k++) { + if (i == j) + m_distance_matrix[k][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]; + float max_val = std::max(flush_matrix[k][used_filaments[i]][used_filaments[j]], flush_matrix[k][used_filaments[j]][used_filaments[i]]); + float min_val = std::min(flush_matrix[k][used_filaments[i]][used_filaments[j]], flush_matrix[k][used_filaments[j]][used_filaments[i]]); + m_distance_matrix[k][i][j] = (max_val * p + min_val * (1 - p)) * (std::max(count_matrix[i][j], 1)); + } } } } } - double FlushDistanceEvaluator::get_distance(int idx_a, int idx_b) const + double FlushDistanceEvaluator::get_distance(int idx_a, int idx_b, int extruder_id) const { - assert(0 <= idx_a && idx_a < m_distance_matrix.size()); - assert(0 <= idx_b && idx_b < m_distance_matrix.size()); + assert(0 <= idx_a && idx_a < m_distance_matrix[extruder_id].size()); + assert(0 <= idx_b && idx_b < m_distance_matrix[extruder_id].size()); - return m_distance_matrix[idx_a][idx_b]; + return m_distance_matrix[extruder_id][idx_a][idx_b]; } + double TimeEvaluator::get_estimated_time(const std::vector& filament_map) const + { + double time = 0; + for(auto &elem : m_speed_info.filament_print_time){ + int filament_idx = elem.first; + auto extruder_time = elem.second; + int filament_extruder_id = filament_map[filament_idx]; + time += extruder_time[filament_extruder_id]; + } + return time; + } + + std::vector KMediods2::cluster_small_data(const std::map& unplaceable_limits, const std::vector& group_size) { std::vectorlabels(m_elem_count, -1); @@ -376,8 +338,8 @@ namespace Slic3r 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]); + int distance_to_0 = m_evaluator->get_distance(i, center[0], 0); + int distance_to_1 = m_evaluator->get_distance(i, center[1], 1); min_heap.push({ i,distance_to_0 - distance_to_1 }); } @@ -423,7 +385,7 @@ namespace Slic3r { int total_cost = 0; for (int i = 0; i < m_elem_count; ++i) - total_cost += m_evaluator->get_distance(i, medoids[labels[i]]); + total_cost += m_evaluator->get_distance(i, medoids[labels[i]], labels[i]); return total_cost; } @@ -481,6 +443,363 @@ namespace Slic3r this->m_cluster_labels = best_labels; } + void KMediods::set_cluster_group_size(const std::vector, int>> &cluster_group_size) + { + m_cluster_group_size = cluster_group_size; + m_nozzle_to_extruder.resize(m_k, 0); + for (int i = 0; i < m_cluster_group_size.size(); i++) { + for (auto nozzle_id : m_cluster_group_size[i].first) m_nozzle_to_extruder[nozzle_id] = i; + } + } + + int KMediods::calc_cost(const std::vector& cluster_labels, const std::vector& cluster_centers,int cluster_id) + { + assert(m_evaluator); + int total_cost = 0; + + std::vector> nozzle_cost(m_k,{0,0.0}); + std::vector nozzle_filaments(m_k, 0); + for (int i = 0; i < m_elem_count; ++i) { + if (cluster_id != -1 && cluster_labels[i] != cluster_id) + continue; + if (cluster_centers[cluster_labels[i]] == -1) + continue; + + nozzle_filaments[cluster_labels[i]]++; + for (int j = i + 1; j < m_elem_count; ++j) { + int nozzle_i = cluster_labels[i]; + int nozzle_j = cluster_labels[j]; + if (nozzle_i == nozzle_j) { + nozzle_cost[nozzle_i].first++; + nozzle_cost[nozzle_j].second += m_evaluator->get_distance(i, j, m_nozzle_to_extruder[nozzle_i]); + } + } + } + for (size_t i = 0; i < nozzle_cost.size(); ++i) { + if (nozzle_filaments[i] > 0 && nozzle_cost[i].second > 0) + total_cost += nozzle_cost[i].second / nozzle_cost[i].first * (nozzle_filaments[i] - 1); + } + + return total_cost; + } + + bool KMediods::have_enough_size(const std::vector& cluster_size, const std::vector, int>>& cluster_group_size,int elem_count) + { + bool have_enough_size = true; + std::optionalcluster_sum; + std::optionalcluster_group_sum; + + if (!cluster_size.empty()) + cluster_sum = std::accumulate(cluster_size.begin(), cluster_size.end(), 0); + if (!cluster_group_size.empty()) + cluster_group_sum = std::accumulate(cluster_group_size.begin(), cluster_group_size.end(), 0, [](int a, const std::pair, int>& p) {return a + p.second; }); + if (cluster_sum.has_value()) + have_enough_size &= (cluster_sum >= elem_count); + if (cluster_group_sum.has_value()) + have_enough_size &= (cluster_group_sum >= elem_count); + return have_enough_size; + } + + std::vector KMediods::cluster_small_data(const FilamentGroupContext &context) { + + //1.Determine the groups each filament is allowed to be assigned to + std::vector> candidates(m_elem_count); + for (int i = 0; i < m_elem_count; i++) { + std::unordered_set group_set; + if (auto it = m_placeable_limits.find(i); it != m_placeable_limits.end()) { + group_set.insert(it->second.begin(), it->second.end()); + } else { + for (int g = 0; g < m_k; g++) group_set.insert(g); + } + if (auto it = m_unplaceable_limits.find(i); it != m_unplaceable_limits.end()) { + for (int g : it->second) group_set.erase(g); + } + candidates[i].assign(group_set.begin(), group_set.end()); + } + + // Store the hash value of each nozzle and its filaments in each group + auto vector_equal = [](const std::vector &a, const std::vector &b) -> bool { + if (a.size() != b.size()) return false; + for (size_t i = 0; i < a.size(); i++) { + if (a[i] != b[i]) return false; + } + return true; + }; + auto vector_hash = [](const std::vector &k) -> size_t { + size_t h = 0; + for (auto v : k) { h ^= v + GOLDEN_RATIO_32 + (h << 6) + (h >> 2); } + return h; + }; + std::unordered_set, decltype(vector_hash), decltype(vector_equal)> group_set(0, vector_hash, vector_equal); + std::vector group_hashs; + + //2.Compute hash value based on nozzle type left/right extruder + high/standard flow + std::vector nozzles_hash(m_k); + for (auto nozzle : context.nozzle_info.nozzle_list) { + nozzles_hash[nozzle.group_id] = fnv_hash_two_ints(nozzle.volume_type, nozzle.group_id > 0); + } + + //3.Enumerate group assignments + const std::vector used_filaments = collect_sorted_used_filaments(context.model_info.layer_filaments); + std::vector labels(context.group_info.total_filament_num, 0); + std::vector used_labels(used_filaments.size(), 0); + if (!memoryed_groups.empty()) memoryed_groups = FilamentGroupUtils::MemoryedGroupHeap(); + + const long long total = std::pow(m_k, m_elem_count); + for (long long mask = 0; mask < total; mask++) { + long long num = mask; + std::unordered_map> nozzles_filaments; + std::vector groups(m_cluster_group_size.size()); + + std::vector>> used_nozzles(m_cluster_group_size.size()); + std::vector prefer_vs(3, 0); + + //4.calculate group info + for (int i = 0; i < m_elem_count; i++) { + int n_id = num % m_k; + num /= m_k; + int ex_id = m_nozzle_to_extruder[n_id]; + + labels[used_filaments[i]] = n_id; + used_labels[i] = n_id; + nozzles_filaments[n_id].emplace_back(i); + groups[ex_id]++; + + used_nozzles[ex_id][context.nozzle_info.nozzle_list[n_id].volume_type].insert(n_id); + + if (std::find(candidates[i].begin(), candidates[i].end(), n_id) != candidates[i].end()) prefer_vs[0] += 1; + } + for (int i = 0; i < groups.size(); i++) prefer_vs[1] += std::min(groups[i], int(m_cluster_group_size[i].first.size())); + + std::unordered_set nozzles_type; + for (int i = 0; i < used_nozzles.size(); i++) { + const auto &nozzles = used_nozzles[i]; + if (nozzles.size() == 1 && nozzles.begin()->second.size() == groups[i]) { + prefer_vs[2] += 1; + nozzles_type.insert(nozzles.begin()->first); + } + } + if (nozzles_type.size() == 1) prefer_vs[2] += 1; + + int weight_for_placeable = 10000; + int weight_for_extruder_filaments = 100; + int weight_for_nozzle_type = 1; + int prefer_level = prefer_vs[0] * weight_for_placeable + prefer_vs[1] * weight_for_extruder_filaments + prefer_vs[2] * weight_for_nozzle_type; + + //5.compute group hash + group_hashs.clear(); + for (auto &nozzle_filaments : nozzles_filaments) { + uint64_t filament_mask = 0; + for (int filament : nozzle_filaments.second) filament_mask |= (1ULL << filament); + size_t group_hash = nozzles_hash[nozzle_filaments.first]; + group_hash ^= (std::hash{}(filament_mask) + GOLDEN_RATIO_32 + (group_hash << 6) + (group_hash >> 2)); + group_hashs.emplace_back(group_hash); + } + std::sort(group_hashs.begin(), group_hashs.end()); + if (group_set.find(group_hashs) != group_set.end()) continue; + group_set.insert(group_hashs); + + //6.Evaluate group scores + MultiNozzleUtils::MultiNozzleGroupResult group_res(labels, context.nozzle_info.nozzle_list, used_filaments); + auto change_count = get_estimate_extruder_filament_change_count(context.model_info.layer_filaments, group_res); + auto flush_volume = calc_cost(used_labels,std::vector(m_k,0),-1); + double time = change_count.first *context.speed_info.extruder_change_time + change_count.second *context.speed_info.filament_change_time; + double score = evaluate_score(flush_volume, time, true); + + int master_ex_id = context.machine_info.master_extruder_id; + if (master_ex_id < groups.size() && groups[master_ex_id] < (used_filaments.size() + 1) / 2) + score += ABSOLUTE_FLUSH_GAP_TOLERANCE; //slightly prefer master extruders with more flush + MemoryedGroup group(used_labels, score, prefer_level); + update_memoryed_groups(group, memory_threshold, memoryed_groups); + } + return memoryed_groups.top().group; + } + + // make sure each cluster at least have one elements + std::vector KMediods::init_cluster_center(const std::unordered_map>& placeable_limits, const std::unordered_map>& unplaceable_limits,const std::vector& cluster_size,const std::vector,int>>& cluster_group_size, int seed) + { + // max flow network + std::vector l_nodes(m_elem_count); // represent the filament idx, to be shuffled + std::vector r_nodes(m_k); // represent the group idx + std::iota(l_nodes.begin(), l_nodes.end(), 0); + std::iota(r_nodes.begin(), r_nodes.end(), 0); + + std::unordered_map> shuffled_placeable_limits; + std::unordered_map> shuffled_unplaceable_limits; + // shuffle the filaments and transfer placeable,unplaceable limits + { + std::mt19937 rng(seed); + std::shuffle(l_nodes.begin(), l_nodes.end(), rng); + + std::unordered_mapidx_transfer; + for (size_t idx = 0; idx < l_nodes.size(); ++idx){ + int new_idx = std::find(l_nodes.begin(),l_nodes.end(), idx) - l_nodes.begin(); + idx_transfer[idx] = new_idx; + } + for (auto& elem : placeable_limits) + shuffled_placeable_limits[idx_transfer[elem.first]] = elem.second; + for (auto& elem : unplaceable_limits) + shuffled_unplaceable_limits[idx_transfer[elem.first]] = elem.second; + } + + + MaxFlowSolver M(l_nodes, r_nodes, shuffled_placeable_limits, shuffled_unplaceable_limits); + auto ret = M.solve(); + + // if still has -1,it means there has some filaments that cannot be placed under the limit. We neglect the -1 here since we + // are deciding the cluster center, -1 can be handled in later steps + std::vector cluster_center(m_k, -1); + for (size_t idx = 0; idx < ret.size(); ++idx) { + if (ret[idx] != -1) { + cluster_center[ret[idx]] = l_nodes[idx]; + } + } + + return cluster_center; + } + + std::vector KMediods::assign_cluster_label(const std::vector& center, const std::unordered_map>& placeable_limits, const std::unordered_map>& unplaceable_limits, const std::vector& cluster_size, const std::vector, int>>& cluster_group_size) + { + std::vector labels(m_elem_count, -1); + std::vector l_nodes(m_elem_count); + std::vector r_nodes(m_k); + std::iota(l_nodes.begin(), l_nodes.end(), 0); + std::iota(r_nodes.begin(), r_nodes.end(), 0); + + std::vector> distance_matrix(m_elem_count, std::vector(m_k)); + for (int i = 0; i < m_elem_count; ++i) { + for (int j = 0; j < m_k; ++j) { + if (center[j] == -1) + distance_matrix[i][j] = 0; + else + distance_matrix[i][j] = m_evaluator->get_distance(i, center[j], m_nozzle_to_extruder[j]); + } + } + + // only consider the size limit if the group can contain all of the filaments + std::vector r_nodes_capacity = {}; + std::vector, int>> r_nodes_group_capacity = {}; + if (have_enough_size(cluster_size, cluster_group_size, m_elem_count)) { + r_nodes_capacity = cluster_size; + r_nodes_group_capacity = cluster_group_size; + } + else { + // TODO: xcr:throw exception here? + // adjust group size to elem count if the group cannot contain all of the filamnts + r_nodes_capacity = std::vector(m_k, m_elem_count); + } + std::vector l_nodes_capacity(l_nodes.size(),1); + //for (size_t idx = 0; idx < center.size(); ++idx) + // if (center[idx] != -1) + // l_nodes_capacity[center[idx]] = 0; + + + // Each group can receive up to m_elem_count materials at most, so the flow from r_nodes to sink is adjusted to m_elem_count. + MinFlushFlowSolver M(distance_matrix, l_nodes, r_nodes, placeable_limits, unplaceable_limits, l_nodes_capacity, r_nodes_capacity, r_nodes_group_capacity); + auto ret = M.solve(); + + for (size_t idx = 0; idx < ret.size(); ++idx) { + if (ret[idx] != MaxFlowGraph::INVALID_ID) { + labels[l_nodes[idx]] = r_nodes[ret[idx]]; + } + } + + for (size_t idx = 0; idx < center.size(); ++idx) + if (center[idx] != -1) + assert(labels[center[idx]] == idx); + + //for (size_t idx = 0; idx < center.size(); ++idx) { + // if (center[idx] != -1) { + // labels[center[idx]] = idx; + // } + //} + + // If there are materials that have not been grouped in the last step, assign them to the default group. + for (size_t idx = 0; idx < labels.size(); ++idx) { + if (labels[idx] == -1) { + labels[idx] = m_default_group_id; + } + } + + return labels; + } + + /* + 1.Select initial medoids randomly + 2.Iterate while the cost decreases: + 2.1 In each cluster, make the point that minimizes the sum of distances within the cluster the medoid + 2.2 Reassign each point to the cluster defined by the closest medoid determined in the previous step + */ + void KMediods::do_clustering(const FilamentGroupContext &context, int timeout_ms, int retry) + { + FlushTimeMachine T; + T.time_machine_start(); + + if (m_elem_count <= m_k) { + m_cluster_labels = cluster_small_data(context); + return; + } + + std::vector best_cluster_centers = std::vector(m_k, 0); + std::vector best_cluster_labels = std::vector(m_elem_count, m_default_group_id); + int best_cluster_cost = std::numeric_limits::max(); + int retry_count = 0; + + while (retry_count < retry && T.time_machine_end() < timeout_ms) { + std::vector curr_cluster_centers = init_cluster_center(m_placeable_limits, m_unplaceable_limits, m_max_cluster_size, m_cluster_group_size, retry_count); + std::vector curr_cluster_labels = assign_cluster_label(curr_cluster_centers, m_placeable_limits, m_unplaceable_limits, m_max_cluster_size, m_cluster_group_size); + int curr_cluster_cost = calc_cost(curr_cluster_labels, curr_cluster_centers); + + MemoryedGroup g(curr_cluster_labels, curr_cluster_cost, 1); + update_memoryed_groups(g, memory_threshold, memoryed_groups); + + bool mediods_changed = true; + while (mediods_changed && T.time_machine_end() < timeout_ms) { + mediods_changed = false; + int best_swap_cost = curr_cluster_cost; + int best_swap_cluster = -1; + int best_swap_elem = -1; + + for (size_t cluster_id = 0; cluster_id < m_k; ++cluster_id) { + if (curr_cluster_centers[cluster_id] == -1) continue; // skip the empty cluster + for (int elem = 0; elem < m_elem_count; ++elem) { + if (std::find(curr_cluster_centers.begin(), curr_cluster_centers.end(), elem) != curr_cluster_centers.end() || + std::find(m_unplaceable_limits[cluster_id].begin(), m_unplaceable_limits[cluster_id].end(), elem) != m_unplaceable_limits[cluster_id].end()) + continue; + std::vector tmp_centers = curr_cluster_centers; + tmp_centers[cluster_id] = elem; // swap the mediod + std::vector tmp_labels = assign_cluster_label(tmp_centers, m_placeable_limits, m_unplaceable_limits, m_max_cluster_size, m_cluster_group_size); + int tmp_cost = calc_cost(tmp_labels, tmp_centers); + + if (tmp_cost < best_swap_cost) { + best_swap_cost = tmp_cost; + best_swap_cluster = cluster_id; + best_swap_elem = elem; + mediods_changed = true; + } + } + } + + if (mediods_changed) { + curr_cluster_centers[best_swap_cluster] = best_swap_elem; + curr_cluster_labels = assign_cluster_label(curr_cluster_centers, m_placeable_limits, m_unplaceable_limits, m_max_cluster_size, m_cluster_group_size); + curr_cluster_cost = calc_cost(curr_cluster_labels, curr_cluster_centers); + + MemoryedGroup g(curr_cluster_labels, curr_cluster_cost, 1); // in non enum mode, we use the same prefer level + update_memoryed_groups(g, memory_threshold, memoryed_groups); + } + } + + if (curr_cluster_cost < best_cluster_cost) { + best_cluster_centers = curr_cluster_centers; + best_cluster_cost = curr_cluster_cost; + best_cluster_labels = curr_cluster_labels; + } + retry_count += 1; + } + m_cluster_labels = best_cluster_labels; + } + std::vector FilamentGroup::calc_min_flush_group(int* cost) { auto used_filaments = collect_sorted_used_filaments(ctx.model_info.layer_filaments); @@ -595,7 +914,7 @@ namespace Slic3r using namespace FlushPredict; auto used_filaments = collect_sorted_used_filaments(ctx.model_info.layer_filaments); - std::vector used_filament_list; + std::vector used_filament_list; for (auto f : used_filaments) used_filament_list.emplace_back(ctx.model_info.filament_info[f]); @@ -609,7 +928,7 @@ namespace Slic3r } if (machine_filament_list.empty()) - throw FilamentGroupException(FilamentGroupException::EmptyAmsFilaments,"Empty box filament in For-Match mode."); + 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); @@ -760,16 +1079,12 @@ namespace Slic3r 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); + ret = select_best_group_for_ams(memoryed_maps, ctx.nozzle_info.nozzle_list, used_filaments, used_filament_info, ctx.machine_info.machine_filament_info); return ret; } @@ -777,9 +1092,10 @@ namespace Slic3r // 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 + static constexpr int UNPLACEABLE_LIMIT_REWARD = 10000; // reward value if the group result follows the unprintable limit + static constexpr int MAX_SIZE_LIMIT_REWARD = 5000; // reward value if the group result follows the max size per extruder + static constexpr int SUPPORT_PREFER_REWARD = 100; // reward value if the group result follow the supoport limit + static constexpr int BEST_FIT_LIMIT_REWARD = 10; // reward value if the group result try to fill the max size per extruder MemoryedGroupHeap memoryed_groups; @@ -800,9 +1116,16 @@ namespace Slic3r 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; + struct CachedGroup{ + std::vector filament_map; + double flush{0}; + double time{0}; + int prefer_level {0}; + double score{1}; + }; + + CachedGroup best_group; + std::vector cached_groups; for (uint64_t i = 0; i < max_group_num; ++i) { std::vector>groups(2); @@ -822,6 +1145,18 @@ namespace Slic3r 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; + int prefer_filament_count = 0; + for (int gidx = 0; gidx < 2; ++gidx) { + if (ctx.machine_info.prefer_non_model_filament[gidx]) { + for (int fidx : groups[gidx]) { + if (ctx.model_info.filament_info[fidx].usage_type == FilamentGroupUtils::SupportOnly) + prefer_filament_count += 1; + } + } + } + + prefer_level += prefer_filament_count * SUPPORT_PREFER_REWARD; + std::vectorfilament_maps(used_filament_num); for (int i = 0; i < used_filament_num; ++i) { if (groups[0].find(i) != groups[0].end()) @@ -839,29 +1174,58 @@ namespace Slic3r 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; - } + if (groups[ctx.machine_info.master_extruder_id].size() < (used_filaments.size() + 1) / 2) + total_cost += ABSOLUTE_FLUSH_GAP_TOLERANCE; // slightly prefer master extruders with more flush - { - MemoryedGroup mg(filament_maps, total_cost, prefer_level); - update_memoryed_groups(mg, ctx.group_info.max_gap_threshold, memoryed_groups); + CachedGroup curr_group; + curr_group.flush = total_cost; + curr_group.prefer_level = prefer_level; + curr_group.filament_map.resize(ctx.group_info.total_filament_num, 0); + for (size_t i = 0; i < filament_maps.size(); ++i) + curr_group.filament_map[used_filaments[i]] = filament_maps[i]; + + TimeEvaluator time_evaluator(ctx.speed_info); + curr_group.time = time_evaluator.get_estimated_time(curr_group.filament_map); + cached_groups.emplace_back(std::move(curr_group)); + } + + // 如果归一化,没法处理边界情况 + { + // double min_flush = std::min_element(cached_groups.begin(),cached_groups.end(),[](const CachedGroup& a, const CachedGroup& b) {return a.flush < b.flush;})->flush; + // double max_flush = std::max_element(cached_groups.begin(),cached_groups.end(),[](const CachedGroup& a, const CachedGroup& b) {return a.flush < b.flush;})->flush; + // double min_time = std::min_element(cached_groups.begin(),cached_groups.end(),[](const CachedGroup& a, const CachedGroup& b) {return a.time < b.time;})->time; + // double max_time = std::max_element(cached_groups.begin(),cached_groups.end(),[](const CachedGroup& a, const CachedGroup& b) {return a.time < b.time;})->time; + + int count = 0; + for (CachedGroup& cached_group : cached_groups) { + //double norm_flush = (max_flush - min_flush == 0) ? 0 : (cached_group.flush - min_flush) / (max_flush - min_flush); + //double norm_time = (max_time - min_time == 0) ? 0 : (cached_group.time - min_time) / (max_time - min_time); + cached_group.score = evaluate_score(cached_group.flush, cached_group.time, ctx.speed_info.group_with_time); + + if(cached_group.prefer_level > best_group.prefer_level || (cached_group.prefer_level == best_group.prefer_level && cached_group.score < best_group.score)){ + best_group = cached_group; + } + + { + MemoryedGroup mg(cached_group.filament_map, cached_group.score, cached_group.prefer_level); + update_memoryed_groups(mg, ctx.group_info.max_gap_threshold, memoryed_groups); + } + BOOST_LOG_TRIVIAL(info) << "Filament group" << count++ << ", score : " << cached_group.score << " , flush : " << cached_group.flush << " , time : " << cached_group.time << " , prefer : " << cached_group.prefer_level; } } + if (cost) - *cost = best_cost; + *cost =best_group.flush; - 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]; + m_memoryed_groups.clear(); + while(!memoryed_groups.empty()){ + auto top = memoryed_groups.top(); + memoryed_groups.pop(); + m_memoryed_groups.push_back(top.group); + } - - change_memoryed_heaps_to_arrays(memoryed_groups, ctx.group_info.total_filament_num, used_filaments, m_memoryed_groups); - - return filament_labels; + return best_group.filament_map; } // sorted used_filaments @@ -872,7 +1236,7 @@ namespace Slic3r 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); + auto distance_evaluator = std::make_shared(ctx.model_info.flush_matrix, 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); @@ -894,6 +1258,164 @@ namespace Slic3r return filament_labels_ret; } + std::unordered_map> FilamentGroupMultiNozzle::rebuild_nozzle_unprintables(const std::vector& used_filaments, const std::unordered_map>& extruder_unprintables, const std::vector& filament_volume_map) + { + std::unordered_map> nozzle_unprintables; + + for(size_t fidx = 0 ;fidx unexpected_extruders; + if(extruder_unprintables.find(fidx) != extruder_unprintables.end()){ + unexpected_extruders = extruder_unprintables.at(fidx); + } + + std::vector unprintable_nozzles; + for(size_t nozzle_idx =0 ;nozzle_idx < m_context.nozzle_info.nozzle_list.size(); ++nozzle_idx){ + auto nozzle_info = m_context.nozzle_info.nozzle_list[nozzle_idx]; + + if(std::find(unexpected_extruders.begin(), unexpected_extruders.end(), nozzle_info.extruder_id)!= unexpected_extruders.end() || (expected_volume!=nvtHybrid && expected_volume != nozzle_info.volume_type)) + unprintable_nozzles.push_back(nozzle_idx); + } + if(unprintable_nozzles.empty()) + continue; + + sort_remove_duplicates(unprintable_nozzles); + nozzle_unprintables[fidx] = unprintable_nozzles; + } + + return nozzle_unprintables; + } + + std::vector FilamentGroupMultiNozzle::calc_filament_group_by_mcmf() + { + std::vector used_filaments = collect_sorted_used_filaments(m_context.model_info.layer_filaments); + + std::mapunplaceable_limits; + extract_unprintable_limit_indices(m_context.model_info.unprintable_filaments, used_filaments, unplaceable_limits); + + auto distance_evaluator = std::make_shared(m_context.model_info.flush_matrix, used_filaments, m_context.model_info.layer_filaments); + std::vector>groups(2); + + // first cluster + { + KMediods2 PAM((int)used_filaments.size(), distance_evaluator); + PAM.set_max_cluster_size({(int)m_context.nozzle_info.extruder_nozzle_list[0].size(),(int)m_context.nozzle_info.extruder_nozzle_list[1].size()}); + PAM.set_unplaceable_limits(unplaceable_limits); + PAM.do_clustering(FGStrategy::BestFit); + auto first_clustered_labels = PAM.get_cluster_labels(); + int total_nozzle_num = m_context.nozzle_info.nozzle_list.size(); + + if (total_nozzle_num > used_filaments.size()) { + std::vectorret(m_context.group_info.total_filament_num); + for (size_t idx = 0; idx < first_clustered_labels.size(); ++idx) { + ret[used_filaments[idx]] = first_clustered_labels[idx]; + } + return ret; + } + + // first place the elem if it follows the limit + for (size_t idx = 0; idx < first_clustered_labels.size(); ++idx) { + if (unplaceable_limits.count(idx) > 0) + groups[first_clustered_labels[idx]].insert(idx); + } + // then fullfill the nozzle with other filaments + for (size_t idx = 0; idx < first_clustered_labels.size(); ++idx) { + // place the elem in first cluster if the elem follow the limit + int gidx = first_clustered_labels[idx]; + if (groups[gidx].size() < m_context.nozzle_info.extruder_nozzle_list[gidx].size()) + groups[gidx].insert(idx); + } + } + + std::vectorret_map(m_context.group_info.total_filament_num); + // second cluster + { + std::mapunplaceable_limits; + for (size_t idx = 0; idx < groups.size(); ++idx) { + for (auto& f : groups[idx]) + unplaceable_limits.emplace(f, (int)(1 - idx)); + } + KMediods2 PAM((int)used_filaments.size(), distance_evaluator); + PAM.set_max_cluster_size(m_context.machine_info.max_group_size); + PAM.set_unplaceable_limits(unplaceable_limits); + PAM.do_clustering(FGStrategy::BestFit); + auto labels = PAM.get_cluster_labels(); + + for (size_t idx = 0; idx < labels.size(); ++idx) + ret_map[used_filaments[idx]] = labels[idx]; + } + return ret_map; + } + + std::vector FilamentGroupMultiNozzle::calc_filament_group_by_pam() + { + std::vector used_filaments = collect_sorted_used_filaments(m_context.model_info.layer_filaments); + std::unordered_map>unplaceable_limits; + extract_unprintable_limit_indices(m_context.model_info.unprintable_filaments, used_filaments, unplaceable_limits); // turn filament idx to idx in used filaments + unplaceable_limits = rebuild_nozzle_unprintables(used_filaments,unplaceable_limits,m_context.group_info.filament_volume_map); + + int k = m_context.nozzle_info.nozzle_list.size(); + auto distance_evaluator = std::make_shared(m_context.model_info.flush_matrix, used_filaments, m_context.model_info.layer_filaments); + + KMediods PAM(k, (int)used_filaments.size(), distance_evaluator); + PAM.set_unplacable_limits(unplaceable_limits); + + std::vector, int>> cluster_size_limit; + + //TODO: 全改成map + for (auto& extruder_nozzles : m_context.nozzle_info.extruder_nozzle_list) { + auto& extruder_id = extruder_nozzles.first; + auto& nozzles = extruder_nozzles.second; + std::pair, int> clusters; + clusters.first = std::set(nozzles.begin(), nozzles.end()); + clusters.second = m_context.machine_info.max_group_size.at(extruder_id); + cluster_size_limit.emplace_back(clusters); + } + + PAM.set_cluster_group_size(cluster_size_limit); + PAM.set_memory_threshold(m_context.group_info.max_gap_threshold); + PAM.do_clustering(m_context, 1500); + + auto memoryed_groups = PAM.get_memoryed_groups(); + std::vector> filament_to_nozzles; + change_memoryed_heaps_to_arrays(memoryed_groups, m_context.group_info.total_filament_num, used_filaments, filament_to_nozzles); + + std::vector used_filament_info; + for (auto f : used_filaments) { used_filament_info.emplace_back(m_context.model_info.filament_info[f]); } + + auto ret = select_best_group_for_ams(filament_to_nozzles, m_context.nozzle_info.nozzle_list, used_filaments, used_filament_info, m_context.machine_info.machine_filament_info); + + return ret; + } + + + std::vector calc_filament_group_for_match_multi_nozzle(const FilamentGroupContext& ctx) + { + FilamentGroup fg1(ctx); + auto filament_extruder_map = fg1.calc_filament_group_for_match(); + + FilamentGroupContext new_ctx = ctx; + auto used_filaments = collect_sorted_used_filaments(ctx.model_info.layer_filaments); + for(size_t idx = 0; idx < used_filaments.size(); ++idx) + new_ctx.model_info.unprintable_filaments[1 - filament_extruder_map[used_filaments[idx]]].insert(used_filaments[idx]); + new_ctx.machine_info.max_group_size.assign(new_ctx.machine_info.max_group_size.size(), std::numeric_limits::max()); + FilamentGroupMultiNozzle fg(new_ctx); + return fg.calc_filament_group_by_pam(); + } + + std::vector calc_filament_group_for_manual_multi_nozzle(const std::vector& filament_map_manual, const FilamentGroupContext& ctx) + { + FilamentGroupContext new_ctx = ctx; + auto used_filaments = collect_sorted_used_filaments(ctx.model_info.layer_filaments); + for(size_t idx = 0; idx < used_filaments.size(); ++idx) + new_ctx.model_info.unprintable_filaments[1 - filament_map_manual[used_filaments[idx]]].insert(used_filaments[idx]); + + new_ctx.machine_info.max_group_size.assign(new_ctx.machine_info.max_group_size.size(), std::numeric_limits::max()); + FilamentGroupMultiNozzle fg(new_ctx); + return fg.calc_filament_group_by_pam(); + } + + } diff --git a/src/libslic3r/FilamentGroup.hpp b/src/libslic3r/FilamentGroup.hpp index ce1aedf..a61a874 100644 --- a/src/libslic3r/FilamentGroup.hpp +++ b/src/libslic3r/FilamentGroup.hpp @@ -13,7 +13,7 @@ const static int DEFAULT_CLUSTER_SIZE = 16; -const static int ABSOLUTE_FLUSH_GAP_TOLERANCE = 5; +const static int ABSOLUTE_FLUSH_GAP_TOLERANCE = 10; namespace Slic3r { @@ -29,6 +29,8 @@ namespace Slic3r MatchMode }; + class FilamentGroupMultiNozzle; + namespace FilamentGroupUtils { struct FlushTimeMachine @@ -82,40 +84,59 @@ namespace Slic3r double max_gap_threshold; FGMode mode; FGStrategy strategy; - bool ignore_ext_filament; //wai gua filament + bool ignore_ext_filament; + std::vector filament_volume_map; } group_info; struct MachineInfo { std::vector max_group_size; std::vector> machine_filament_info; - std::vector, int>> extruder_group_size; + std::vector prefer_non_model_filament; int master_extruder_id; } machine_info; + + struct SpeedInfo{ + std::unordered_map> filament_print_time; + double extruder_change_time; + double filament_change_time; + bool group_with_time; + } speed_info; + + struct NozzleInfo { + std::map> extruder_nozzle_list; + std::vector nozzle_list; + } nozzle_info; }; - std::vector select_best_group_for_ams(const std::vector>& map_lists, + std::vector select_best_group_for_ams(const std::vector> &filament_to_nozzles, + const std::vector& nozzle_list, 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(const std::vector& 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; + double get_distance(int idx_a, int idx_b, int extruder_id) const; private: - std::vector>m_distance_matrix; + std::vector>>m_distance_matrix; }; + + class TimeEvaluator + { + public: + TimeEvaluator(const FilamentGroupContext::SpeedInfo& speed_info) : m_speed_info(speed_info) {} + double get_estimated_time(const std::vector& filament_map) const; + private: + FilamentGroupContext::SpeedInfo m_speed_info; + }; + class FilamentGroup { using MemoryedGroup = FilamentGroupUtils::MemoryedGroup; @@ -134,6 +155,7 @@ namespace Slic3r 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::vector calc_min_flush_group_by_pam (const std::vector& used_filaments, int* cost = nullptr, int timeout_ms = 300, int retry = 15); std::unordered_map> try_merge_filaments(); void rebuild_context(const std::unordered_map>& merged_filaments); @@ -142,11 +164,29 @@ namespace Slic3r private: FilamentGroupContext ctx; std::vector> m_memoryed_groups; + friend FilamentGroupMultiNozzle; public: std::optional&)>> get_custom_seq; }; + class FilamentGroupMultiNozzle + { + public: + FilamentGroupMultiNozzle(const FilamentGroupContext& context) :m_context(context) {} + public: + std::vector calc_filament_group_by_mcmf(); + std::vector calc_filament_group_by_pam(); + + private: + std::unordered_map> rebuild_nozzle_unprintables(const std::vector& used_filaments, const std::unordered_map>& extruder_unprintables, const std::vector& filament_volume_map); + private: + FilamentGroupContext m_context; + }; + + std::vector calc_filament_group_for_manual_multi_nozzle(const std::vector& filament_map_manual,const FilamentGroupContext& ctx); + + std::vector calc_filament_group_for_match_multi_nozzle(const FilamentGroupContext& ctx); class KMediods2 { @@ -196,5 +236,64 @@ namespace Slic3r int m_default_group_id{ 0 }; double memory_threshold{ 0 }; }; + + // non_zero_clusters + class KMediods + { + protected: + using MemoryedGroupHeap = FilamentGroupUtils::MemoryedGroupHeap; + using MemoryedGroup = FilamentGroupUtils::MemoryedGroup; + public: + KMediods(const int k, const int elem_count, const std::shared_ptr& evaluator, int default_group_id = 0) { + m_k = k; + m_evaluator = evaluator; + m_max_cluster_size = std::vector(k, DEFAULT_CLUSTER_SIZE); + m_elem_count = elem_count; + m_default_group_id = default_group_id; + } + // set max group size + void set_max_cluster_size(const std::vector& group_size) { m_max_cluster_size = group_size; } + + void set_cluster_group_size(const std::vector,int>>& cluster_group_size); + + // key stores elem, value stores the cluster id that the elem must be placed + void set_placable_limits(const std::unordered_map>& placable_limits) { m_placeable_limits = placable_limits; } + + // key stores elem, value stores the cluster id that the elem cannot be placed + void set_unplacable_limits(const std::unordered_map>& unplacable_limits) { m_unplaceable_limits = unplacable_limits; } + + void set_memory_threshold(double threshold) { memory_threshold = threshold; } + MemoryedGroupHeap get_memoryed_groups()const { return memoryed_groups; } + + void do_clustering(const FilamentGroupContext& context, int timeout_ms = 100, int retry = 10); + std::vector get_cluster_labels()const { return m_cluster_labels; } + + protected: + bool have_enough_size(const std::vector& cluster_size, const std::vector, int>>& cluster_group_size,int elem_count); + // calculate cluster distance + int calc_cost(const std::vector& clusters, const std::vector& cluster_centers, int cluster_id = -1); + + std::vector cluster_small_data(const FilamentGroupContext& context); + // get initial cluster center + std::vectorinit_cluster_center(const std::unordered_map>& placeable_limits, const std::unordered_map>& unplaceable_limits, const std::vector& cluster_size, const std::vector, int>>& cluster_group_size, int seed); + // assign each elem to the cluster + std::vector assign_cluster_label(const std::vector& center, const std::unordered_map>& placeable_limits, const std::unordered_map>& unplaceable_limits, const std::vector& group_size, const std::vector, int>>& cluster_group_size); + + protected: + MemoryedGroupHeap memoryed_groups; + std::shared_ptrm_evaluator; + std::unordered_map> m_unplaceable_limits; // 材料不允许分配到特定喷嘴 + std::unordered_map> m_placeable_limits; // 材料必须分配到特定喷嘴 + std::vectorm_max_cluster_size; // 每个喷嘴能够分配的最大耗材数量 + std::vectorm_cluster_labels; // 分配结果,细化到喷嘴id + std::vector,int>> m_cluster_group_size; + std::vector m_nozzle_to_extruder; + + + int m_k; + 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 index 1b16a13..6f97905 100644 --- a/src/libslic3r/FilamentGroupUtils.cpp +++ b/src/libslic3r/FilamentGroupUtils.cpp @@ -1,4 +1,5 @@ #include "FilamentGroupUtils.hpp" +#include "Print.hpp" #include #include @@ -274,5 +275,92 @@ namespace FilamentGroupUtils } return true; } + int get_estimate_extruder_change_count(const std::vector>& layer_filaments, const MultiNozzleUtils::MultiNozzleGroupResult& extruder_nozzle_info) + { + int ret = 0; + for (size_t layer_id = 0; layer_id < layer_filaments.size(); ++layer_id) { + auto& filament_list = layer_filaments[layer_id]; + int extruder_count = extruder_nozzle_info.get_used_extruders(filament_list).size(); + ret += (extruder_count - 1); + } + return ret; + } + + int get_estimate_nozzle_change_count(const std::vector>& layer_filaments, const MultiNozzleUtils::MultiNozzleGroupResult& extruder_nozzle_info) + { + int ret = 0; + for (size_t layer_id = 0; layer_id < layer_filaments.size(); ++layer_id) { + auto& filament_list = layer_filaments[layer_id]; + auto extruder_list = extruder_nozzle_info.get_extruder_list(); + for (auto extruder_id : extruder_list) { + int nozzle_count = extruder_nozzle_info.get_used_nozzles(filament_list, extruder_id).size(); + if (nozzle_count > 1) ret += (nozzle_count - 1); + } + } + return ret; + } + + std::pair get_estimate_extruder_filament_change_count(const std::vector> &layer_filaments, const MultiNozzleUtils::MultiNozzleGroupResult &extruder_nozzle_info) + { + std::pair ret{0,0}; + for (auto &filament_list : layer_filaments) { + auto extruder_nozzle = extruder_nozzle_info.get_used_extruders_nozzles_count(filament_list); + + ret.first += (extruder_nozzle.first - 1); + ret.second += std::max(0, int(filament_list.size()) - extruder_nozzle.first); + } + return ret; + } + + std::map> build_extruder_nozzle_list(const std::vector& nozzle_list) + { + std::map> ret; + for (auto& nozzle : nozzle_list) { + ret[nozzle.extruder_id].emplace_back(nozzle.group_id); + } + + for (auto& elem : ret) + std::sort(elem.second.begin(), elem.second.end()); + return ret; + } + + + std::vector build_filament_usage_type_list(const PrintConfig& config, const std::vector& objects) + { + std::vector filament_usage_types; + for(int idx = 0; idx< config.filament_type.size(); ++idx){ + if(config.filament_is_support.values[idx]) + filament_usage_types.push_back(FilamentUsageType::SupportOnly); + else{ + bool is_support = false; + bool is_model = false; + for(auto obj : objects){ + if (!is_model) { + auto obj_filaments = obj->object_extruders(); + is_model = std::find(obj_filaments.begin(), obj_filaments.end(), idx) != obj_filaments.end(); + } + if(obj->config().support_filament - 1 == idx || obj->config().support_interface_filament - 1 == idx){ + is_support = true; + } + } + if(is_model && is_support) + filament_usage_types.emplace_back(FilamentUsageType::Hybrid); + else if(is_support) + filament_usage_types.emplace_back(FilamentUsageType::SupportOnly); + else + filament_usage_types.emplace_back(FilamentUsageType::ModelOnly); + } + } + return filament_usage_types; + } + + std::vector update_used_filament_values(const std::vector& old_values, const std::vector& new_values, const std::vector& used_filaments) + { + std::vector res = old_values; + for (size_t i = 0; i < used_filaments.size(); ++i) { + res[used_filaments[i]] = new_values[used_filaments[i]]; + } + return res; + } } } \ No newline at end of file diff --git a/src/libslic3r/FilamentGroupUtils.hpp b/src/libslic3r/FilamentGroupUtils.hpp index 71839c2..85eb112 100644 --- a/src/libslic3r/FilamentGroupUtils.hpp +++ b/src/libslic3r/FilamentGroupUtils.hpp @@ -7,9 +7,12 @@ #include #include "PrintConfig.hpp" +#include "MultiNozzleUtils.hpp" + namespace Slic3r { + class PrintObject; namespace FilamentGroupUtils { struct Color @@ -26,11 +29,18 @@ namespace Slic3r std::string to_hex_str(bool include_alpha = false) const; }; + enum FilamentUsageType { + SupportOnly, + ModelOnly, + Hybrid + }; + struct FilamentInfo { Color color; std::string type; bool is_support; + FilamentUsageType usage_type; }; struct MachineFilamentInfo: public FilamentInfo { @@ -39,7 +49,6 @@ namespace Slic3r bool operator<(const MachineFilamentInfo& other) const; }; - class FilamentGroupException: public std::exception { public: enum ErrorCode { @@ -80,7 +89,20 @@ namespace Slic3r 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); - } + + int get_estimate_extruder_change_count(const std::vector>& layer_filaments, const MultiNozzleUtils::MultiNozzleGroupResult& extruder_nozzle_info); + + int get_estimate_nozzle_change_count(const std::vector>& layer_filaments, const MultiNozzleUtils::MultiNozzleGroupResult& extruder_nozzle_info); + + std::pair get_estimate_extruder_filament_change_count(const std::vector> &layer_filaments, const MultiNozzleUtils::MultiNozzleGroupResult &extruder_nozzle_info); + + std::map> build_extruder_nozzle_list(const std::vector& nozzle_list); + + std::vector build_filament_usage_type_list(const PrintConfig& config, const std::vector& objects); + + std::vector update_used_filament_values(const std::vector& old_values, const std::vector& new_values, const std::vector& used_filaments); + +} } diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index eaed637..542eabf 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -44,6 +44,7 @@ struct SurfaceFillParams // FillParams float density = 0.f; + int multiline = 1; // Don't adjust spacing to fill the space evenly. // bool dont_adjust = false; // Length of the infill anchor along the perimeter line. @@ -84,6 +85,7 @@ struct SurfaceFillParams RETURN_COMPARE_NON_EQUAL(overlap); RETURN_COMPARE_NON_EQUAL(angle); RETURN_COMPARE_NON_EQUAL(density); + RETURN_COMPARE_NON_EQUAL(multiline); // RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_adjust); RETURN_COMPARE_NON_EQUAL(anchor_length); RETURN_COMPARE_NON_EQUAL(anchor_length_max); @@ -112,6 +114,7 @@ struct SurfaceFillParams this->bridge == rhs.bridge && // this->bridge_angle == rhs.bridge_angle && this->density == rhs.density && + this->multiline == rhs.multiline && // this->dont_adjust == rhs.dont_adjust && this->anchor_length == rhs.anchor_length && this->anchor_length_max == rhs.anchor_length_max && @@ -189,6 +192,7 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p params.extruder = layerm.region().extruder(extrusion_role); params.pattern = region_config.sparse_infill_pattern.value; params.density = float(region_config.sparse_infill_density); + params.multiline = int(region_config.fill_multiline); if (params.pattern == ipLockedZag) { params.skin_pattern = region_config.locked_skin_infill_pattern.value; params.skeleton_pattern = region_config.locked_skeleton_infill_pattern.value; @@ -224,6 +228,7 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p erInternalInfill); params.bridge_angle = float(surface.bridge_angle); params.angle = float(Geometry::deg2rad(region_config.infill_direction.value)); + params.multiline = params.extrusion_role == erInternalInfill ? int(region_config.fill_multiline) : 1; // Calculate the actual flow we'll be using for this infill. params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern); @@ -234,13 +239,13 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p //QDS: record speed params if (!params.bridge) { if (params.extrusion_role == erInternalInfill) - params.sparse_infill_speed = region_config.sparse_infill_speed.get_at(layer.get_extruder_id(params.extruder)); + params.sparse_infill_speed = region_config.sparse_infill_speed.get_at(layer.get_config_idx_for_filament(params.extruder)); else if (params.extrusion_role == erTopSolidInfill) - params.top_surface_speed = region_config.top_surface_speed.get_at(layer.get_extruder_id(params.extruder)); + params.top_surface_speed = region_config.top_surface_speed.get_at(layer.get_config_idx_for_filament(params.extruder)); else if (params.extrusion_role == erSolidInfill) - params.solid_infill_speed = region_config.internal_solid_infill_speed.get_at(layer.get_extruder_id(params.extruder)); + params.solid_infill_speed = region_config.internal_solid_infill_speed.get_at(layer.get_config_idx_for_filament(params.extruder)); else if (params.extrusion_role == erFloatingVerticalShell) - params.solid_infill_speed = region_config.bridge_speed.get_at(layer.get_extruder_id(params.extruder)); + params.solid_infill_speed = region_config.bridge_speed.get_at(layer.get_config_idx_for_filament(params.extruder)); } // Calculate flow spacing for infill pattern generation. if (surface.is_solid() || is_bridge) { @@ -541,7 +546,7 @@ void Layer::set_outlook_range(LockRegionParam &lock_param){ for (size_t region_id = 0; region_id < this->regions().size(); ++region_id) { LayerRegion &layerm = *this->regions()[region_id]; - if (layerm.region().config().sparse_infill_pattern == ipLockedZag) { + if (layerm.region().config().infill_instead_top_bottom_surfaces && layerm.region().config().sparse_infill_pattern == ipLockedZag) { for (const Surface &surface : layerm.fill_surfaces.surfaces) { if (surface.surface_type != stInternal) lock_param.outlook.push_back(surface.expolygon); @@ -665,6 +670,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: // apply half spacing using this flow's own spacing and generate infill FillParams params; params.density = float(0.01 * surface_fill.params.density); + params.multiline = surface_fill.params.multiline; + params.pattern = surface_fill.params.pattern; params.dont_adjust = false; // surface_fill.params.dont_adjust; params.anchor_length = surface_fill.params.anchor_length; params.anchor_length_max = surface_fill.params.anchor_length_max; @@ -807,6 +814,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc // apply half spacing using this flow's own spacing and generate infill FillParams params; params.density = float(0.01 * surface_fill.params.density); + params.multiline = surface_fill.params.multiline; params.dont_adjust = false; // surface_fill.params.dont_adjust; params.anchor_length = surface_fill.params.anchor_length; params.anchor_length_max = surface_fill.params.anchor_length_max; diff --git a/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/src/libslic3r/Fill/Fill3DHoneycomb.cpp index 5d24c87..7ec9d85 100644 --- a/src/libslic3r/Fill/Fill3DHoneycomb.cpp +++ b/src/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -212,7 +212,7 @@ void Fill3DHoneycomb::_fill_surface_single( // = 4 * integrate(func=4*x(sqrt(2) - 1) + 1, from=0, to=0.25) // = (sqrt(2) + 1) / 2 [... I think] // make a first guess at the preferred grid Size - coordf_t gridSize = (scale_(this->spacing) * ((zScale + 1.) / 2.) / params.density); + coordf_t gridSize = (scale_(this->spacing) * ((zScale + 1.) / 2.) * params.multiline/ params.density); // This density calculation is incorrect for many values > 25%, possibly // due to quantisation error, so this value is used as a first guess, then the @@ -228,7 +228,7 @@ void Fill3DHoneycomb::_fill_surface_single( layersPerModule = 2; // re-adjust the grid size for a partial octahedral path // (scale of 1.1 guessed based on modeling) - gridSize = (scale_(this->spacing) * 1.1 / params.density); + gridSize = (scale_(this->spacing) * 1.1 * params.multiline/ params.density); // re-adjust zScale to make layering consistent zScale = (gridSize * 2) / (layersPerModule * layerHeight); } else { @@ -238,7 +238,7 @@ void Fill3DHoneycomb::_fill_surface_single( // re-adjust zScale to make layering consistent zScale = (gridSize * 2) / (layersPerModule * layerHeight); // re-adjust the grid size to account for the new zScale - gridSize = (scale_(this->spacing) * ((zScale + 1.) / 2.) / params.density); + gridSize = (scale_(this->spacing) * ((zScale + 1.) / 2.) * params.multiline/ params.density); // re-calculate layersPerModule and zScale layersPerModule = floor((gridSize * 2) / (zScale * layerHeight) + 0.05); if(layersPerModule < 2){ @@ -264,11 +264,22 @@ void Fill3DHoneycomb::_fill_surface_single( // move pattern in place for (Polyline &pl : polylines){ pl.translate(bb.min); + pl.simplify(5 * spacing); } - + // Apply multiline offset if needed + multiline_fill(polylines, params, spacing); // clip pattern to boundaries, chain the clipped polylines polylines = intersection_pl(polylines, to_polygons(expolygon)); + if (! polylines.empty()) { + // Remove very small bits, but be careful to not remove infill lines connecting thin walls! + // The infill perimeter lines should be separated by around a single infill line width. + const double minlength = scale_(0.8 * this->spacing); + polylines.erase( + std::remove_if(polylines.begin(), polylines.end(), [minlength](const Polyline &pl) { return pl.length() < minlength; }), + polylines.end()); + } + // copy from fliplines if (!polylines.empty()) { int infill_start_idx = polylines_out.size(); // only rotate what belongs to us. diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 692b5ce..15ddbe9 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -1369,6 +1369,10 @@ void Filler::_fill_surface_single( // Convert lines to polylines. all_polylines.reserve(lines.size()); std::transform(lines.begin(), lines.end(), std::back_inserter(all_polylines), [](const Line& l) { return Polyline{ l.a, l.b }; }); + + // Apply multiline offset if needed + multiline_fill(all_polylines, params, spacing); + // Crop all polylines all_polylines = intersection_pl(std::move(all_polylines), expolygon); #endif diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index cf5de4c..13478e4 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -2610,4 +2610,67 @@ void Fill::connect_base_support(Polylines &&infill_ordered, const Polygons &boun connect_base_support(std::move(infill_ordered), polygons_src, bbox, polylines_out, spacing, params); } +//Fill MultiLine +void multiline_fill(Polylines& polylines, const FillParams& params, float spacing) +{ + if (params.multiline > 1) { + const int n_lines = params.multiline; + const int n_polylines = static_cast(polylines.size()); + Polylines all_polylines; + all_polylines.reserve(n_lines * n_polylines); + + const float center = (n_lines - 1) / 2.0f; + + //current polyline as the center line, offset to both sides + for (int line = 0; line < n_lines; ++line) { + float offset = (static_cast(line) - center) * spacing; + + for (const Polyline& pl : polylines) { + const size_t n = pl.points.size(); + if (n < 2) { + all_polylines.emplace_back(pl); + continue; + } + + Points new_points; + new_points.reserve(n); + // Offset each point along the normal direction + for (size_t i = 0; i < n; ++i) { + Vec2f tangent; + if (i == 0) + tangent = Vec2f(pl.points[1].x() - pl.points[0].x(), pl.points[1].y() - pl.points[0].y()); + else if (i == n - 1) + tangent = Vec2f(pl.points[n - 1].x() - pl.points[n - 2].x(), pl.points[n - 1].y() - pl.points[n - 2].y()); + else + { + tangent = Vec2f(pl.points[i + 1].x() - pl.points[i - 1].x(), pl.points[i + 1].y() - pl.points[i - 1].y()); + /*Vec2f prev_tangent(pl.points[i].x() - pl.points[i - 1].x(), pl.points[i].y() - pl.points[i - 1].y()); + Vec2f next_tangent(pl.points[i + 1].x() - pl.points[i].x(), pl.points[i + 1].y() - pl.points[i].y()); + prev_tangent.normalize(); + next_tangent.normalize(); + tangent = (prev_tangent + next_tangent) / 2.0f; + + float sin_half_angle = std::sqrt((1 - prev_tangent.dot(next_tangent)) / 2.0f); + if (sin_half_angle > 1e-6f) + offset /= sin_half_angle;*/ + } + float len = std::hypot(tangent.x(), tangent.y()); + if (len == 0) + len = 1.0f; + tangent /= len; + Vec2f normal(-tangent.y(), tangent.x()); + + Point p = pl.points[i]; + p.x() += scale_(normal.x() * offset); + p.y() += scale_(normal.y() * offset); + new_points.push_back(p); + } + + all_polylines.emplace_back(std::move(new_points)); + } + } + polylines = std::move(all_polylines); + } +} + } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 4d354ca..ff92105 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -56,6 +56,7 @@ struct FillParams double filter_out_gap_fill { 0.0 }; // Fill density, fraction in <0, 1> float density { 0.f }; + int multiline { 1 }; // Length of an infill anchor along the perimeter. // 1000mm is roughly the maximum length line that fits into a 32bit coord_t. @@ -81,6 +82,8 @@ struct FillParams // Layer height for Concentric infill with Arachne. coordf_t layer_height { 0.f }; + InfillPattern pattern{ ipRectilinear }; + // QDS Flow flow; ExtrusionRole extrusion_role{ ExtrusionRole(0) }; @@ -212,6 +215,9 @@ public: static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance); }; +//Fill Multiline +void multiline_fill(Polylines& polylines, const FillParams& params, float spacing); + } // namespace Slic3r #endif // slic3r_FillBase_hpp_ diff --git a/src/libslic3r/Fill/FillCrossHatch.cpp b/src/libslic3r/Fill/FillCrossHatch.cpp index bbefd22..0c6e2f8 100644 --- a/src/libslic3r/Fill/FillCrossHatch.cpp +++ b/src/libslic3r/Fill/FillCrossHatch.cpp @@ -186,7 +186,8 @@ void FillCrossHatch ::_fill_surface_single( BoundingBox bb = expolygon.contour.bounding_box(); // linespace modifier - coord_t line_spacing = coord_t(scale_(this->spacing) / params.density); + double density_adjusted = params.density / params.multiline; + coord_t line_spacing = coord_t(scale_(this->spacing) / density_adjusted); // reduce density if (params.density < 0.999) line_spacing *= 1.08; @@ -204,6 +205,9 @@ void FillCrossHatch ::_fill_surface_single( // shift the pattern to the actual space for (Polyline &pl : polylines) { pl.translate(bb.min); } + // Apply multiline offset if needed + multiline_fill(polylines, params, spacing); + polylines = intersection_pl(polylines, to_polygons(expolygon)); // --- remove small remains from gyroid infill diff --git a/src/libslic3r/Fill/FillGyroid.cpp b/src/libslic3r/Fill/FillGyroid.cpp index 5c555df..01056e9 100644 --- a/src/libslic3r/Fill/FillGyroid.cpp +++ b/src/libslic3r/Fill/FillGyroid.cpp @@ -161,7 +161,7 @@ void FillGyroid::_fill_surface_single( BoundingBox bb = expolygon.contour.bounding_box(); // Density adjusted to have a good %of weight. - double density_adjusted = std::max(0., params.density * DensityAdjust); + double density_adjusted = std::max(0., params.density * DensityAdjust / params.multiline); // Distance between the gyroid waves in scaled coordinates. coord_t distance = coord_t(scale_(this->spacing) / density_adjusted); @@ -179,8 +179,10 @@ void FillGyroid::_fill_surface_single( // shift the polyline to the grid origin for (Polyline &pl : polylines) pl.translate(bb.min); - - polylines = intersection_pl(polylines, expolygon); + // Apply multiline offset if needed + multiline_fill(polylines, params, spacing); + + polylines = intersection_pl(polylines, expolygon); if (! polylines.empty()) { // Remove very small bits, but be careful to not remove infill lines connecting thin walls! diff --git a/src/libslic3r/Fill/FillHoneycomb.cpp b/src/libslic3r/Fill/FillHoneycomb.cpp index 5dc2ed5..41dadad 100644 --- a/src/libslic3r/Fill/FillHoneycomb.cpp +++ b/src/libslic3r/Fill/FillHoneycomb.cpp @@ -19,7 +19,7 @@ void FillHoneycomb::_fill_surface_single( if (it_m == this->cache.end()) { it_m = this->cache.insert(it_m, std::pair(cache_id, CacheData())); CacheData &m = it_m->second; - coord_t min_spacing = coord_t(scale_(this->spacing)); + coord_t min_spacing = coord_t(scale_(this->spacing)) * params.multiline; m.distance = coord_t(min_spacing / params.density); m.hex_side = coord_t(m.distance / (sqrt(3)/2)); m.hex_width = m.distance * 2; // $m->{hex_width} == $m->{hex_side} * sqrt(3); @@ -69,9 +69,12 @@ void FillHoneycomb::_fill_surface_single( x += m.distance; } p.rotate(-direction.first, m.hex_center); + p.simplify(5 * spacing); // simplify to 5x line width all_polylines.push_back(p); } } + // Apply multiline offset if needed + multiline_fill(all_polylines, params, 1.1 * spacing); all_polylines = intersection_pl(std::move(all_polylines), expolygon); if (params.dont_connect() || all_polylines.size() <= 1) diff --git a/src/libslic3r/Fill/FillLightning.cpp b/src/libslic3r/Fill/FillLightning.cpp index cc2ea33..ddd472b 100644 --- a/src/libslic3r/Fill/FillLightning.cpp +++ b/src/libslic3r/Fill/FillLightning.cpp @@ -16,11 +16,14 @@ void Filler::_fill_surface_single( 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)); - - if (params.dont_connect() || fill_lines.size() <= 1) { - append(polylines_out, chain_polylines(std::move(fill_lines))); + + // Apply multiline offset if needed + multiline_fill(fill_lines, params, spacing); + Polylines all_polylines = intersection_pl(std::move(fill_lines), expolygon); + if (params.dont_connect() || all_polylines.size() <= 1) { + append(polylines_out, chain_polylines(std::move(all_polylines))); } else - connect_infill(std::move(fill_lines), expolygon, polylines_out, this->spacing, params); + connect_infill(std::move(all_polylines), expolygon, polylines_out, this->spacing, params); } void GeneratorDeleter::operator()(Generator *p) { diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index 7c3c18f..17e18c2 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -3031,9 +3031,10 @@ void make_fill_lines(const ExPolygonWithOffset &poly_with_offset, Point refpt, d bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list &sweep_params, Polylines &polylines_out) { - assert(sweep_params.size() > 1); + assert(sweep_params.size() >= 1); assert(! params.full_infill()); params.density /= double(sweep_params.size()); + int n_multilines = params.multiline; assert(params.density > 0.0001f && params.density <= 1.f); ExPolygonWithOffset poly_with_offset_base(surface->expolygon, 0, float(scale_(this->overlap - 0.5 * this->spacing))); @@ -3043,12 +3044,21 @@ bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillPar Polylines fill_lines; coord_t line_width = coord_t(scale_(this->spacing)); - coord_t line_spacing = coord_t(scale_(this->spacing) / params.density); + coord_t line_spacing = coord_t(scale_(this->spacing) * params.multiline / params.density); std::pair rotate_vector = this->_infill_direction(surface); for (const SweepParams &sweep : sweep_params) { // Rotate polygons so that we can work with vertical lines here float angle = rotate_vector.first + sweep.angle_base; - make_fill_lines(ExPolygonWithOffset(poly_with_offset_base, - angle), rotate_vector.second.rotated(-angle), angle, line_width + coord_t(SCALED_EPSILON), line_spacing, coord_t(scale_(sweep.pattern_shift)), fill_lines); + //Fill Multiline + for (int i = 0; i < n_multilines; ++i) { + coord_t group_offset = i * line_spacing; + coord_t internal_offset = (i - (n_multilines - 1) / 2.0f) * line_width; + coord_t total_offset = group_offset + internal_offset; + coord_t pattern_shift = scale_(sweep.pattern_shift + unscale_(total_offset)); + + make_fill_lines(ExPolygonWithOffset(poly_with_offset_base, -angle), rotate_vector.second.rotated(-angle), angle, + line_width + coord_t(SCALED_EPSILON), line_spacing, pattern_shift, fill_lines); + } } if (params.dont_connect() || fill_lines.size() <= 1) { @@ -3064,8 +3074,16 @@ bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillPar Polylines FillRectilinear::fill_surface(const Surface *surface, const FillParams ¶ms) { Polylines polylines_out; - if (! fill_surface_by_lines(surface, params, 0.f, 0.f, polylines_out)) - BOOST_LOG_TRIVIAL(error) << "FillRectilinear::fill_surface() failed to fill a region."; + if (params.full_infill() || params.multiline == 1 || params.pattern == ipCrossZag || params.pattern == ipZigZag || params.pattern == ipLockedZag) + { + if (!fill_surface_by_lines(surface, params, 0.f, 0.f, polylines_out)) + BOOST_LOG_TRIVIAL(error) << "FillRectilinear::fill_surface() fill_surface_by_lines() failed to fill a region."; + } + else + { + if (!fill_surface_by_multilines(surface, params, {{0.f, 0.f}}, polylines_out)) + BOOST_LOG_TRIVIAL(error) << "FillRectilinear::fill_surface() fill_surface_by_multilines() failed to fill a region."; + } return polylines_out; } @@ -3121,7 +3139,7 @@ Polylines FillStars::fill_surface(const Surface *surface, const FillParams ¶ Polylines polylines_out; if (! this->fill_surface_by_multilines( surface, params, - { { 0.f, 0.f }, { float(M_PI / 3.), 0.f }, { float(2. * M_PI / 3.), float((3./2.) * this->spacing / params.density) } }, + { { 0.f, 0.f }, { float(M_PI / 3.), 0.f }, { float(2. * M_PI / 3.), float((3./2.) * this->spacing * params.multiline / params.density) } }, polylines_out)) BOOST_LOG_TRIVIAL(error) << "FillStars::fill_surface() failed to fill a region."; return polylines_out; diff --git a/src/libslic3r/Format/OBJ.cpp b/src/libslic3r/Format/OBJ.cpp index d54a66f..9d9fd70 100644 --- a/src/libslic3r/Format/OBJ.cpp +++ b/src/libslic3r/Format/OBJ.cpp @@ -151,19 +151,26 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo &obj_info, std::s // Insert one or two faces (triangulate a quad). 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, &gamma_correct](int face_index, const std::string mtl_name) { + auto get_face_color = [&mtl_data ,& gamma_correct](const std::string mtl_name, RGBA &face_color) { if (mtl_data.new_mtl_unmap.find(mtl_name) != mtl_data.new_mtl_unmap.end()) { - for (size_t n = 0; n < 3; n++) {//0.1 is light ambient - float object_ka = 0.f; + 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; + float temp = gamma_correct ? ColorRGBA::gamma_correct(value) : value; face_color[n] = std::clamp(temp, 0.f, 1.f); } 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 + return true; + } + return false; + }; + auto set_face_color = [&uvs, &data, &mtl_data, &obj_info, &get_face_color](int face_index, const std::string mtl_name) { + if (mtl_data.new_mtl_unmap.find(mtl_name) != mtl_data.new_mtl_unmap.end()) { + RGBA face_color; + get_face_color(mtl_name,face_color); 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; @@ -199,6 +206,16 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo &obj_info, std::s } }; if (exist_mtl) { + if (mtl_data.first_time_using_makerlab && obj_info.mtl_colors.empty()) { + obj_info.first_time_using_makerlab = true; + obj_info.mtl_colors.reserve(mtl_data.mtl_orders.size()); + for (int i = 0; i < mtl_data.mtl_orders.size(); i++) { + RGBA face_color; + if (get_face_color(mtl_data.mtl_orders[i],face_color)) { + obj_info.mtl_colors.emplace_back(face_color); + } + } + } set_face_color_by_mtl(face_index); } if (cnt == 4) { diff --git a/src/libslic3r/Format/OBJ.hpp b/src/libslic3r/Format/OBJ.hpp index a1dd206..fdf647e 100644 --- a/src/libslic3r/Format/OBJ.hpp +++ b/src/libslic3r/Format/OBJ.hpp @@ -12,6 +12,8 @@ class ModelObject; struct ObjInfo { std::vector vertex_colors; std::vector face_colors; + std::vector mtl_colors; + bool first_time_using_makerlab{false}; bool is_single_mtl{false}; std::string lost_material_name{""}; std::vector> uvs; @@ -33,6 +35,8 @@ struct ObjDialogInOut unsigned char first_extruder_id; bool deal_vertex_color; Model * model{nullptr}; + std::vector mtl_colors; + bool first_time_using_makerlab{false}; // ml std::string ml_region; std::string ml_name; diff --git a/src/libslic3r/Format/objparser.cpp b/src/libslic3r/Format/objparser.cpp index 2be5226..fedefc4 100644 --- a/src/libslic3r/Format/objparser.cpp +++ b/src/libslic3r/Format/objparser.cpp @@ -385,6 +385,16 @@ static bool mtl_parseline(const char *line, MtlData &data) char c1 = *line++; switch (c1) { case '#': {// Comment, ignore the rest of the line. + if (*(line++) == 'F' && *(line++) == 'i' && *(line++) == 'r' && *(line++) == 's' && *(line++) == 't') { // First + if (*(line++) == 'T' && *(line++) == 'i' && *(line++) == 'm' && *(line++) == 'e' ) { // Time + if (*(line++) == 'U' && *(line++) == 's' && *(line++) == 'i' && *(line++) == 'n' && *(line++) == 'g') { // Using + if (*(line++) == 'M' && *(line++) == 'a' && *(line++) == 'k' && *(line++) == 'e' && *(line++) == 'r' && *(line++) == 'L' && *(line++) == 'a' && + *(line++) == 'b') { // MakerLab + data.first_time_using_makerlab = true; + } + } + } + } break; } case 'n': { @@ -394,6 +404,7 @@ static bool mtl_parseline(const char *line, MtlData &data) ObjNewMtl new_mtl; cur_mtl_name = line; data.new_mtl_unmap[cur_mtl_name] = std::make_shared(); + data.mtl_orders.emplace_back(cur_mtl_name); break; } case 'm': { @@ -637,6 +648,7 @@ std::string parsemlinfo(const char* input, const char* condition) { return regionContent; } + bool mtlparse(const char *path, MtlData &data) { Slic3r::CNumericLocalesSetter locales_setter; @@ -812,10 +824,8 @@ bool loadvectornameidx(FILE *pFile, std::vector &v) bool objbinsave(const char *path, const ObjData &data) { FILE *pFile = boost::nowide::fopen(path, "wb"); - if (pFile == 0) { - ::fclose(pFile); - return false; - } + if (pFile == 0) + return false; size_t version = 1; ::fwrite(&version, 1, sizeof(version), pFile); @@ -839,8 +849,10 @@ bool objbinsave(const char *path, const ObjData &data) bool objbinload(const char *path, ObjData &data) { FILE *pFile = boost::nowide::fopen(path, "rb"); - if (pFile == 0) - return false; + if (pFile == 0) { + ::fclose(pFile); + return false; + } data.version = 0; if (::fread(&data.version, sizeof(data.version), 1, pFile) != 1) { diff --git a/src/libslic3r/Format/objparser.hpp b/src/libslic3r/Format/objparser.hpp index 8116c08..156d88e 100644 --- a/src/libslic3r/Format/objparser.hpp +++ b/src/libslic3r/Format/objparser.hpp @@ -125,7 +125,9 @@ struct MtlData { // Version of the data structure for load / store in the private binary format. int version; + bool first_time_using_makerlab{false}; std::unordered_map> new_mtl_unmap; + std::vector mtl_orders; }; extern bool objparse(const char *path, ObjData &data); extern bool mtlparse(const char *path, MtlData &data); diff --git a/src/libslic3r/Format/qds_3mf.cpp b/src/libslic3r/Format/qds_3mf.cpp index f78a946..145dc40 100644 --- a/src/libslic3r/Format/qds_3mf.cpp +++ b/src/libslic3r/Format/qds_3mf.cpp @@ -170,6 +170,7 @@ const std::string QDS_PROJECT_CONFIG_FILE = "Metadata/project_settings.config"; const std::string QDS_MODEL_CONFIG_FILE = "Metadata/model_settings.config"; const std::string QDS_MODEL_CONFIG_RELS_FILE = "Metadata/_rels/model_settings.config.rels"; const std::string SLICE_INFO_CONFIG_FILE = "Metadata/slice_info.config"; +const std::string FILAMENT_SEQUENCE_FILE = "Metadata/filament_sequence.json"; const std::string QDS_LAYER_HEIGHTS_PROFILE_FILE = "Metadata/layer_heights_profile.txt"; const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/layer_config_ranges.xml"; const std::string BRIM_EAR_POINTS_FILE = "Metadata/brim_ear_points.txt"; @@ -214,6 +215,9 @@ 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 *FILAMENT_NOZZLE_GROUP_ID_TAG = "group_id"; +static constexpr const char *FILAMENT_NOZZLE_DIAMETER_TAG = "nozzle_diameter"; +static constexpr const char *FILAMENT_NOZZLE_VOLUME_TYPE_TAG = "volume_type"; static constexpr const char* CONFIG_TAG = "config"; @@ -301,6 +305,7 @@ 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* FILAMENT_VOL_MAP_ATTR = "filament_volume_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"; @@ -623,6 +628,16 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) info.id = it->first; info.used_g = used_filament_g; info.used_m = used_filament_m; + + if (result && result->nozzle_group_result) { + auto nozzle_for_filament = result->nozzle_group_result->get_nozzle_for_filament(it->first); + if (nozzle_for_filament) { + info.nozzle_diameter = string_to_double_decimal_point(nozzle_for_filament->diameter.c_str()); + info.group_id = nozzle_for_filament->group_id; + info.nozzle_volume_type = get_nozzle_volume_type_string(nozzle_for_filament->volume_type); + } + } + slice_filaments_info.push_back(info); } @@ -1125,6 +1140,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) void _extract_file_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_embossed_svg_shape_file(const std::string &filename, mz_zip_archive &archive, const mz_zip_archive_file_stat &stat); + void _extract_filament_sequence_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat &stat); + // handlers to parse the .model file void _handle_start_model_xml_element(const char* name, const char** attributes); void _handle_end_model_xml_element(const char* name); @@ -1488,10 +1505,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) else if (boost::algorithm::iequals(name, QDS_MODEL_CONFIG_FILE)) { // extract slic3r model config file if (!_extract_xml_from_archive(archive, stat, _handle_start_config_xml_element, _handle_end_config_xml_element)) { - if (m_is_qdt_3mf) { - add_error("Archive does not contain a valid model config"); - return false; - } + 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); @@ -1502,6 +1517,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) _extract_xml_from_archive(archive, stat, _handle_start_config_xml_element, _handle_end_config_xml_element); m_parsing_slice_info = false; } + else if (boost::algorithm::iequals(name, FILAMENT_SEQUENCE_FILE)) { + _extract_filament_sequence_from_archive(archive, stat); + } } } @@ -1537,6 +1555,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) plate->nozzle_diameters = it->second->nozzle_diameters; plate->warnings = it->second->warnings; plate->thumbnail_file = it->second->thumbnail_file; + plate->filament_maps = it->second->filament_maps; if (plate->thumbnail_file.empty()) { plate->thumbnail_file = plate->gcode_file; boost::algorithm::replace_all(plate->thumbnail_file, ".gcode", ".png"); @@ -1547,6 +1566,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) plate->pick_file = it->second->pick_file.empty(); plate->pattern_bbox_file = it->second->pattern_bbox_file.empty(); plate->config = it->second->config; + plate->filament_change_sequence = it->second->filament_change_sequence; if (!plate->thumbnail_file.empty()) _extract_from_archive(archive, plate->thumbnail_file, [&pixels = plate_data_list[it->first - 1]->plate_thumbnail.pixels](auto &archive, auto const &stat) -> bool { @@ -1864,8 +1884,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) else if (boost::algorithm::iequals(name, QDS_MODEL_CONFIG_FILE)) { // extract slic3r model config file if (!_extract_xml_from_archive(archive, stat, _handle_start_config_xml_element, _handle_end_config_xml_element)) { - add_error("Archive does not contain a valid model config"); - return false; + if (m_is_qdt_3mf) { + 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); @@ -1876,6 +1898,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) _extract_xml_from_archive(archive, stat, _handle_start_config_xml_element, _handle_end_config_xml_element); m_parsing_slice_info = false; } + else if (!dont_load_config && boost::algorithm::iequals(name, FILAMENT_SEQUENCE_FILE)){ + _extract_filament_sequence_from_archive(archive,stat); + } else if (boost::algorithm::istarts_with(name, AUXILIARY_DIR)) { // extract auxiliary directory to temp directory, do nothing for restore if (m_load_aux && !m_load_restore) @@ -2201,6 +2226,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) plate_data_list[it->first-1]->pick_file = (m_load_restore || it->second->pick_file.empty()) ? it->second->pick_file : m_backup_path + "/" + it->second->pick_file; plate_data_list[it->first-1]->pattern_bbox_file = (m_load_restore || it->second->pattern_bbox_file.empty()) ? it->second->pattern_bbox_file : m_backup_path + "/" + it->second->pattern_bbox_file; plate_data_list[it->first-1]->config = it->second->config; + plate_data_list[it->first-1]->filament_maps = it->second->filament_maps; + plate_data_list[it->first-1]->filament_change_sequence = it->second->filament_change_sequence; current_plate_data = plate_data_list[it->first - 1]; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ @@ -2893,6 +2920,13 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) float(std::atof(object_data_points[i+1].c_str())), float(std::atof(object_data_points[i+2].c_str())), float(std::atof(object_data_points[i+3].c_str()))); + }else if (version == 1) { + for (unsigned int i=0; i sequence; + for (auto& item : plate_seq) { + sequence.push_back(item.get() - 1); // data stored in file is 1 based, change to 0 based when loading + } + plater_data->filament_change_sequence = sequence; + } + } + catch (const std::exception& e) { + add_error(std::string("Error while parsing filament sequence JSON: ") + e.what()); + return; + } + } + + void _QDS_3MF_Importer::_extract_custom_gcode_per_print_z_from_archive(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat) { //QDS: add plate tree related logic @@ -4188,6 +4257,12 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) } else if (key == OTHER_LAYERS_PRINT_SEQUENCE_ATTR) { m_curr_plater->config.set_key_value("other_layers_print_sequence", new ConfigOptionInts(get_vector_from_string(value))); + auto temp_other_layers_print_sequence = m_curr_plater->config.option("other_layers_print_sequence"); + if (temp_other_layers_print_sequence && temp_other_layers_print_sequence->values.size() > 2) { + if (temp_other_layers_print_sequence->values[0] > 0 && temp_other_layers_print_sequence->values[0] < INT_MAX && + temp_other_layers_print_sequence->values[1] == INT_MAX) + temp_other_layers_print_sequence->values[1] = INT_MAX - 1; + } } else if (key == OTHER_LAYERS_PRINT_SEQUENCE_NUMS_ATTR) { m_curr_plater->config.set_key_value("other_layers_print_sequence_nums", new ConfigOptionInt(stoi(value))); @@ -4215,6 +4290,18 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) } } m_curr_plater->config.set_key_value("filament_map", new ConfigOptionInts(filament_map)); + m_curr_plater->filament_maps = filament_map; + } + } + else if (key == FILAMENT_VOL_MAP_ATTR) { + if (m_curr_plater){ + auto filament_volume_map = get_vector_from_string(value); + for (size_t idx = 0; idx < filament_volume_map.size(); ++idx) { + if (filament_volume_map[idx] > 1) { + filament_volume_map[idx] = 0; + } + } + m_curr_plater->config.set_key_value("filament_volume_map", new ConfigOptionInts(filament_volume_map)); } } else if (key == GCODE_FILE_ATTR) @@ -4335,6 +4422,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) std::string used_m = qds_get_attribute_value_string(attributes, num_attributes, FILAMENT_USED_M_TAG); std::string used_g = qds_get_attribute_value_string(attributes, num_attributes, FILAMENT_USED_G_TAG); std::string filament_id = qds_get_attribute_value_string(attributes, num_attributes, FILAMENT_TRAY_INFO_ID_TAG); + std::string group_id = qds_get_attribute_value_string(attributes,num_attributes, FILAMENT_NOZZLE_GROUP_ID_TAG); + std::string nozzle_diameter = qds_get_attribute_value_string(attributes, num_attributes, FILAMENT_NOZZLE_DIAMETER_TAG); + std::string volume_type = qds_get_attribute_value_string(attributes, num_attributes, FILAMENT_NOZZLE_VOLUME_TYPE_TAG); FilamentInfo filament_info; filament_info.id = atoi(id.c_str()) - 1; filament_info.type = type; @@ -4342,6 +4432,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) filament_info.used_m = atof(used_m.c_str()); filament_info.used_g = atof(used_g.c_str()); filament_info.filament_id = filament_id; + if (!group_id.empty()) + filament_info.group_id = atoi(group_id.c_str()); + filament_info.nozzle_diameter = atof(nozzle_diameter.c_str()); + filament_info.nozzle_volume_type = volume_type; m_curr_plater->slice_filaments_info.push_back(filament_info); } return true; @@ -5658,6 +5752,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) bool _add_gcode_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, Export3mfProgressFn proFn = nullptr); bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config); bool _add_auxiliary_dir_to_archive(mz_zip_archive &archive, const std::string &aux_dir, PackingTemporaryData &data); + bool _add_filament_sequence_file_to_archive(mz_zip_archive& archive, const PlateDataPtrs& plate_data_list); static int convert_instance_id_to_resource_id(const Model& model, int obj_id, int instance_id) { @@ -6193,6 +6288,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) return false; } + if (!_add_filament_sequence_file_to_archive(archive,plate_data_list)) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", _add_filament_sequence_file_to_archive failed\n"); + return false; + } + //QDS progress point BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", before add auxiliary dir to 3mf\n"); if (proFn) { @@ -6532,6 +6632,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) std::string rating; std::string model_id; std::string region_code; + std::string profile_title; + std::string profile_cover; + std::string profile_description; if (model.design_info) { user_name = model.design_info->Designer; user_id = model.design_info->DesignerUserId; @@ -6553,6 +6656,12 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) origin = model.model_info->origin; BOOST_LOG_TRIVIAL(trace) << "design_info, save_3mf found designer_cover = " << design_cover; } + + if (model.profile_info) { + profile_title = model.profile_info->ProfileTile; + profile_cover = model.profile_info->ProfileCover; + profile_description = model.profile_info->ProfileDescription; + } // remember to use metadata_item_map to store metadata info std::map metadata_item_map; if (!sub_model) { @@ -6569,6 +6678,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) metadata_item_map[QDT_DESCRIPTION_TAG] = xml_escape(description); metadata_item_map[QDT_COPYRIGHT_NORMATIVE_TAG] = xml_escape(copyright); metadata_item_map[QDT_LICENSE_TAG] = xml_escape(license); + metadata_item_map[QDT_PROFILE_TITLE_TAG] = xml_escape(profile_title); + metadata_item_map[QDT_PROFILE_COVER_TAG] = xml_escape(profile_cover); + metadata_item_map[QDT_PROFILE_DESCRIPTION_TAG] = xml_escape(profile_description); /* save model info */ if (!model_id.empty()) { @@ -7270,7 +7382,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) // 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); + sprintf(buffer, (i==0 ? "%f %f %f %f %d" : " %f %f %f %f %d"), brim_points[i].pos(0), brim_points[i].pos(1), brim_points[i].pos(2), brim_points[i].head_front_radius, brim_points[i].volume_idx); out += buffer; } out += "\n"; @@ -7458,8 +7570,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) stream << FONT_NAME_ATTR << "=\"" << text_info.m_font_name << "\" "; stream << FONT_VERSION_ATTR << "=\"" << text_info.m_font_version << "\" "; stream << STYLE_NAME_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(text_info.text_configuration.style.name) << "\" "; - stream << BOLDNESS_ATTR << "=\"" << *text_info.text_configuration.style.prop.boldness << "\" "; - stream << SKEW_ATTR << "=\"" << *text_info.text_configuration.style.prop.skew << "\" "; + stream << BOLDNESS_ATTR << "=\"" << text_info.text_configuration.style.prop.boldness.value_or(0) << "\" "; + stream << SKEW_ATTR << "=\"" << text_info.text_configuration.style.prop.skew.value_or(0) << "\" "; stream << FONT_INDEX_ATTR << "=\"" << text_info.m_curr_font_idx << "\" "; @@ -7706,6 +7818,18 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) stream << "\"/>\n"; } + ConfigOptionInts* filament_volume_maps_opt = plate_data->config.option("filament_volume_map"); + if (filament_map_mode_opt != nullptr && filament_volume_maps_opt != nullptr) { + stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << FILAMENT_VOL_MAP_ATTR << "\" " << VALUE_ATTR << "=\""; + const std::vector& volume_values = filament_volume_maps_opt->values; + for (int i = 0; i < volume_values.size(); ++i) { + stream << volume_values[i]; + if (i != (volume_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()) { @@ -8028,11 +8152,14 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) for (auto it = plate_data->slice_filaments_info.begin(); it != plate_data->slice_filaments_info.end(); it++) { stream << " <" << FILAMENT_TAG << " " << FILAMENT_ID_TAG << "=\"" << std::to_string(it->id + 1) << "\" " - << FILAMENT_TRAY_INFO_ID_TAG <<"=\""<< it->filament_id <<"\" " - << FILAMENT_TYPE_TAG << "=\"" << it->type << "\" " - << FILAMENT_COLOR_TAG << "=\"" << it->color << "\" " - << FILAMENT_USED_M_TAG << "=\"" << it->used_m << "\" " - << FILAMENT_USED_G_TAG << "=\"" << it->used_g << "\" />\n"; + << FILAMENT_TRAY_INFO_ID_TAG << "=\"" << it->filament_id << "\" " + << FILAMENT_TYPE_TAG << "=\"" << it->type << "\" " + << FILAMENT_COLOR_TAG << "=\"" << it->color << "\" " + << FILAMENT_USED_M_TAG << "=\"" << it->used_m << "\" " + << FILAMENT_USED_G_TAG << "=\"" << it->used_g << "\" " + << FILAMENT_NOZZLE_GROUP_ID_TAG << "=\"" << it->group_id << "\" " + << FILAMENT_NOZZLE_DIAMETER_TAG << "=\"" << it->nozzle_diameter << "\" " + << FILAMENT_NOZZLE_VOLUME_TYPE_TAG << "=\"" << it->nozzle_volume_type << "\"/>\n"; } for (auto it = plate_data->warnings.begin(); it != plate_data->warnings.end(); it++) { @@ -8256,6 +8383,35 @@ bool _QDS_3MF_Exporter::_add_auxiliary_dir_to_archive(mz_zip_archive &archive, c return result; } +bool _QDS_3MF_Exporter::_add_filament_sequence_file_to_archive(mz_zip_archive& archive, const PlateDataPtrs& plate_data_list) +{ + std::string sequence_str; + json j; + + for(size_t idx =0 ;idx< plate_data_list.size();++idx){ + PlateData* plate_data = plate_data_list[idx]; + if(!plate_data) + continue; + + std::string plate_idx = "plate_"+std::to_string(idx+1); + std::vector sequence = plate_data->filament_change_sequence; + std::transform(sequence.begin(), sequence.end(), sequence.begin(), [](unsigned int v) { return v + 1; }); // to 1 based idx + j[plate_idx]["sequence"] = sequence; + } + + if(j.empty()) + return true; + + sequence_str = j.dump(); + + if(!mz_zip_writer_add_mem(&archive, FILAMENT_SEQUENCE_FILE.c_str(), sequence_str.c_str(), sequence_str.size(), MZ_DEFAULT_COMPRESSION)){ + add_error("Unable to add model config file to archive"); + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", store slice-info to 3mf, length %1%, failed\n") % sequence_str.length(); + return false; + } + return true; +} + // Perform conversions based on the config values available. //FIXME provide a version of PrusaSlicer that stored the project file (3MF). static void handle_legacy_project_loaded(unsigned int version_project_file, DynamicPrintConfig& config) diff --git a/src/libslic3r/Format/qds_3mf.hpp b/src/libslic3r/Format/qds_3mf.hpp index a32e31a..6386bed 100644 --- a/src/libslic3r/Format/qds_3mf.hpp +++ b/src/libslic3r/Format/qds_3mf.hpp @@ -98,7 +98,7 @@ struct PlateData std::vector filament_maps; // 1 base using LayerFilaments = std::unordered_map, std::vector>, GCodeProcessorResult::FilamentSequenceHash>; LayerFilaments layer_filaments; - + std::vector filament_change_sequence; // Hexadecimal number, // the 0th digit corresponds to extruder 1 // the 1th digit corresponds to extruder 2 @@ -153,7 +153,7 @@ inline bool operator & (SaveStrategy & lhs, SaveStrategy rhs) } enum { - brim_points_format_version = 0 + brim_points_format_version = 1 }; enum class LoadStrategy diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 0ba77eb..d16c22e 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -365,7 +365,7 @@ static std::vector get_path_of_change_filament(const Print& print) //OrcaSlicer 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.get_at(get_extruder_index(gcodegen.writer().config, gcodegen.writer().filament()->id())) * gcodegen.config().wipe_speed.value / 100; + gcodegen.writer().config.travel_speed.get_at(get_config_idx_for_filament(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; } @@ -652,7 +652,6 @@ static std::vector get_path_of_change_filament(const Print& print) int new_extruder_id = get_extruder_index(*m_print_config, new_filament_id); bool is_nozzle_change = !tcr.nozzle_change_result.gcode.empty() && (gcodegen.config().nozzle_diameter.size() > 1); - std::string gcode; // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines) @@ -698,6 +697,7 @@ static std::vector get_path_of_change_filament(const Print& print) gcode += gcodegen.unretract(); } + double current_z = gcodegen.writer().get_position().z(); if (z == -1.) // in case no specific z was provided, print at current_z pos z = current_z; @@ -751,7 +751,7 @@ static std::vector get_path_of_change_filament(const Print& print) 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; - + if (is_nozzle_change && !tcr.nozzle_change_result.is_extruder_change) is_used_travel_avoid_perimeter = false; // add nozzle change gcode into change filament gcode std::string nozzle_change_gcode_trans; if (is_nozzle_change) { @@ -799,8 +799,10 @@ static std::vector get_path_of_change_filament(const Print& print) 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); + float old_filament_retract_length_nc = full_config.filament_retract_length_nc.get_at(old_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); + float new_extruder_retracted_length = gcodegen.m_writer.get_extruder_retracted_length(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); @@ -827,8 +829,10 @@ static std::vector get_path_of_change_filament(const Print& print) config.set_key_value("fan_speed", new ConfigOptionInt((int)0)); config.set_key_value("old_retract_length", new ConfigOptionFloat(old_retract_length)); config.set_key_value("new_retract_length", new ConfigOptionFloat(new_retract_length)); + config.set_key_value("filament_retract_length_nc", new ConfigOptionFloat(old_filament_retract_length_nc)); config.set_key_value("old_retract_length_toolchange", new ConfigOptionFloat(old_retract_length_toolchange)); config.set_key_value("new_retract_length_toolchange", new ConfigOptionFloat(new_retract_length_toolchange)); + config.set_key_value("new_extruder_retracted_length", new ConfigOptionFloat(new_extruder_retracted_length)); 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(tool_change_start_pos(0))); @@ -861,6 +865,24 @@ static std::vector get_path_of_change_filament(const Print& print) 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)); + Vec2f stop_pos; // point for nozzle heating + { + stop_pos = tool_change_start_pos; + auto bbx = m_wipe_tower_bbx; + bbx.translate((m_wipe_tower_pos + m_rib_offset).cast()); + if (stop_pos.x() < bbx.center().x()) + stop_pos = Vec2f(stop_pos.x() - 2.f, stop_pos.y()); + else + stop_pos = Vec2f(stop_pos.x() + 2.f, stop_pos.y()); + BoundingBoxf printer_bbx = unscaled(get_extents(gcodegen.m_print->get_extruder_shared_printable_polygon())); + if (stop_pos.x() < printer_bbx.min[0]) stop_pos.x() = printer_bbx.min[0]; + if (stop_pos.x() > printer_bbx.max[0]) stop_pos.x() = printer_bbx.max[0]; + } + + config.set_key_value("wipe_tower_center_pos_x", new ConfigOptionFloat(stop_pos.x())); + config.set_key_value("wipe_tower_center_pos_y", new ConfigOptionFloat(stop_pos.y())); + config.set_key_value("wipe_tower_center_pos_valid", new ConfigOptionBool(true)); + int flush_count = std::min(g_max_flush_count, (int)std::round(purge_volume / g_purge_volume_one_time)); // handle cases for very small purge if (flush_count == 0 && purge_volume > 0) @@ -910,6 +932,12 @@ static std::vector get_path_of_change_filament(const Print& print) } if (need_travel_after_change_filament_gcode) { + // After a filament change, the travel path leading to the wipe tower: + // start_point inside the previous printed object, + // end_point at the tower’s start_pos or at the starting point of the tower’s detour path. + // In this case, disable “avoid crossing perimeters” to prevent inserting additional path points inside the previous printed object. + gcodegen.m_avoid_crossing_perimeters.disable_once(); + // move to start_pos for wiping after toolchange 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"); @@ -924,11 +952,11 @@ static std::vector get_path_of_change_filament(const Print& print) 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 printer_bbx + printer_bbx = get_extents(gcodegen.m_print->get_extruder_shared_printable_polygon()); + + printer_bbx.min = (wipe_tower_point_to_object_point(gcodegen, unscaled(printer_bbx.min) + plate_origin_2d)); + printer_bbx.max = (wipe_tower_point_to_object_point(gcodegen, unscaled(printer_bbx.max) + plate_origin_2d)); } { // set avoid_bbx @@ -1204,6 +1232,8 @@ static std::vector get_path_of_change_filament(const Print& print) #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()) +#define NOZZLE_CONFIG(OPT) m_config.OPT.get_at(m_config.filament_map_2.values[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) @@ -1537,7 +1567,6 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu GCodeProcessor::s_IsQDTPrinter = print->is_QDT_Printer(); m_writer.set_is_qdt_printer(print->is_QDT_Printer()); - print->set_started(psGCodeExport); // check if any custom gcode contains keywords used by the gcode processor to @@ -1572,6 +1601,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu path_tmp += ".tmp"; m_processor.initialize(path_tmp); + if(print->get_nozzle_group_result().has_value()) + m_processor.initialize_from_context(print->get_nozzle_group_result().value()); GCodeOutputStream file(boost::nowide::fopen(path_tmp.c_str(), "wb"), m_processor); if (! file.is_open()) { BOOST_LOG_TRIVIAL(error) << std::string("G-code export to ") + PathSanitizer::sanitize(path) + " failed.\nCannot open the file for writing.\n" << std::endl; @@ -1718,7 +1749,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.get_at(cur_extruder_index()) == 0 || + region.config().outer_wall_speed.get_at(cur_config_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 || @@ -1997,7 +2028,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // How many times will be change_layer() called? // change_layer() in turn increments the progress bar status. m_layer_count = 0; - if (print.config().print_sequence == PrintSequence::ByObject) { + if (print.config().print_sequence == PrintSequence::ByObject && print.objects().size() > 1) { // Add each of the object's layers separately. for (auto object : print.objects()) { std::vector zs; @@ -2219,7 +2250,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato first_non_support_filaments.resize(print.config().nozzle_diameter.size(), -1); first_filaments.resize(print.config().nozzle_diameter.size(), -1); float max_additional_fan = 0.f; - if (print.config().print_sequence == PrintSequence::ByObject) { + if (print.config().print_sequence == PrintSequence::ByObject && print.objects().size()>1) { // 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); @@ -2370,6 +2401,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato m_placeholder_parser.set("current_object_idx", 0); // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided. m_placeholder_parser.set("has_wipe_tower", has_wipe_tower); + // For the change_filament_gcode to Determine whether the current layer has a wipe tower + m_placeholder_parser.set("has_wipe_tower_this_layer", has_wipe_tower); //m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming); m_placeholder_parser.set("total_toolchanges", std::max(0, print.wipe_tower_data().number_of_toolchanges)); // Check for negative toolchanges (single extruder mode) and set to 0 (no tool change). Vec2f plate_offset = m_writer.get_xy_offset(); @@ -2516,6 +2549,29 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato return m_config.filament_vendor.values[idx] == "QIDI Tech"; })); + float wipe_tower_center_pos_x= -1.f, wipe_tower_center_pos_y = -1.f; + Vec2f stop_pos; // point of nozzle heating + bool wipe_tower_center_pos_valid = false; + if (has_wipe_tower) { + auto bbx = print.wipe_tower_data().bbx; + bbx.translate(print.get_fake_wipe_tower().pos.cast()); + BoundingBoxf printer_bed_bbx = get_extents(m_config.printable_area.values); + float printer_bed_mid_x = printer_bed_bbx.center().x(); + if (bbx.center().x() < printer_bed_mid_x) + stop_pos = Vec2f(bbx.max.x()+2.f, bbx.center().y()); + else + stop_pos = Vec2f(bbx.min.x()-2.f, bbx.center().y()); + + BoundingBoxf printer_bbx = unscaled(get_extents(m_print->get_extruder_shared_printable_polygon())); + if (stop_pos.x() < printer_bbx.min[0]) stop_pos.x() = printer_bbx.min[0]; + if (stop_pos.x() > printer_bbx.max[0]) stop_pos.x() = printer_bbx.max[0]; + + wipe_tower_center_pos_valid = true; + } + m_placeholder_parser.set("wipe_tower_center_pos_x", new ConfigOptionFloat(stop_pos.x())); + m_placeholder_parser.set("wipe_tower_center_pos_y", new ConfigOptionFloat(stop_pos.y())); + m_placeholder_parser.set("wipe_tower_center_pos_valid", new ConfigOptionBool(wipe_tower_center_pos_valid)); + //add during_print_exhaust_fan_speed std::vector during_print_exhaust_fan_speed_num; during_print_exhaust_fan_speed_num.reserve(m_config.during_print_exhaust_fan_speed.size()); @@ -2708,8 +2764,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato //if (print.calib_params().mode == CalibMode::Calib_PA_Line) { //std::string gcode; //gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change) + "\n"; - // 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 ((NOZZLE_CONFIG(default_acceleration) > 0 && NOZZLE_CONFIG(outer_wall_acceleration) > 0)) { + // m_writer.set_acceleration((unsigned int) floor(NOZZLE_CONFIG(outer_wall_acceleration) + 0.5)); // } //if (m_config.default_jerk.value > 0 && !this->is_QDT_Printer()) { @@ -2720,7 +2776,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // 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 fast_speed = std::min(print.default_region_config().outer_wall_speed.get_at(cur_config_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; @@ -2737,8 +2793,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato //if (print.is_QDT_Printer()) file.write("M981 S1 P20000 ;open spaghetti detector\n"); // Do all objects for each layer. - if (print.config().print_sequence == PrintSequence::ByObject && !has_wipe_tower) { - size_t finished_objects = 0; + if (print.config().print_sequence == PrintSequence::ByObject && !has_wipe_tower && print.objects().size() > 1) { + 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) { @@ -3068,6 +3124,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato throw Slic3r::SlicingError(_(L("Flush volumes matrix do not match to the correct size!"))); } } + print_cfg_temp.option("filament_map_2", true)->values = print.config().filament_map_2.values; append_full_config(print_cfg_temp, full_config); if (!full_config.empty()) file.write(full_config); @@ -3147,6 +3204,18 @@ void GCode::export_layer_filaments(GCodeProcessorResult* result) iter->second.emplace_back(idx, idx); } } + + { + result->filament_change_sequence.clear(); + std::optional prev_filament; + for(auto& layer : m_sorted_layer_filaments){ + for(auto fidx : layer){ + if(!prev_filament || prev_filament != fidx) + result->filament_change_sequence.emplace_back(fidx); + prev_filament = fidx; + } + } + } } //QDS @@ -3184,6 +3253,12 @@ size_t GCode::cur_extruder_index() const return get_extruder_id(m_writer.filament()->id()); } +size_t GCode::cur_config_index() const +{ + return m_config.filament_map_2.get_at(m_writer.filament()->id()); +} + + size_t GCode::get_extruder_id(unsigned int filament_id) const { if (m_print) { @@ -3195,9 +3270,9 @@ size_t GCode::get_extruder_id(unsigned int filament_id) const void GCode::set_extrude_acceleration(bool is_first_layer) { if (is_first_layer) { - m_writer.set_acceleration((unsigned int) floor(m_config.initial_layer_acceleration.get_at(cur_extruder_index()) + 0.5)); + m_writer.set_acceleration((unsigned int) floor(NOZZLE_CONFIG(initial_layer_acceleration) + 0.5)); } else { - m_writer.set_acceleration((unsigned int) floor(m_config.default_acceleration.get_at(cur_extruder_index()) + 0.5)); + m_writer.set_acceleration((unsigned int) floor(NOZZLE_CONFIG(default_acceleration) + 0.5)); } } @@ -4057,8 +4132,8 @@ GCode::LayerResult GCode::process_layer( //QDS if (first_layer) { //QDS: set first layer global acceleration - 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()); + if (NOZZLE_CONFIG(default_acceleration) > 0 && NOZZLE_CONFIG(initial_layer_acceleration) > 0) { + double acceleration = NOZZLE_CONFIG(initial_layer_acceleration); m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); } //w30 @@ -4085,8 +4160,8 @@ GCode::LayerResult GCode::process_layer( } //QDS: reset acceleration at sencond layer - 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()); + if (NOZZLE_CONFIG(default_acceleration) > 0 && NOZZLE_CONFIG(initial_layer_acceleration) > 0) { + double acceleration = NOZZLE_CONFIG(default_acceleration); m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); } //w30 @@ -4417,7 +4492,7 @@ GCode::LayerResult GCode::process_layer( ctx.curr_layer = this->layer(); ctx.curr_extruder_id = m_writer.filament()->extruder_id(); ctx.picture_extruder_id = most_used_extruder; - if (m_config.print_sequence == PrintSequence::ByObject) + if (m_config.print_sequence == PrintSequence::ByObject && print.objects().size() > 1) ctx.printed_objects = printed_objects; auto timelapse_pos=m_timelapse_pos_picker.pick_pos(ctx); @@ -4536,6 +4611,7 @@ GCode::LayerResult GCode::process_layer( gcode += insert_wrapping_detection_gcode(); has_insert_wrapping_detection_gcode = true; } + m_placeholder_parser.set("has_wipe_tower_this_layer", new ConfigOptionBool(true)); gcode += m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back()); } } else { @@ -4557,7 +4633,7 @@ GCode::LayerResult GCode::process_layer( gcode += insert_wrapping_detection_gcode(); has_insert_wrapping_detection_gcode = true; } - + m_placeholder_parser.set("has_wipe_tower_this_layer", new ConfigOptionBool(false)); gcode += this->set_extruder(extruder_id, print_z); } @@ -4579,7 +4655,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.get_at(cur_extruder_index())); + gcode += this->extrude_loop(loop, "skirt", NOZZLE_CONFIG(support_speed)); } 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). @@ -4604,7 +4680,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.get_at(cur_extruder_index())); + gcode += this->extrude_entity(*ee, "skirt", NOZZLE_CONFIG(support_speed)); } } @@ -4668,7 +4744,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.get_at(cur_extruder_index())); + gcode += this->extrude_entity(*ee, "brim", NOZZLE_CONFIG(support_speed)); } m_avoid_crossing_perimeters.use_external_mp(false); // Allow a straight travel move to the first object point. @@ -4709,7 +4785,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.get_at(cur_extruder_index())); + gcode += this->extrude_entity(*ee, "brim", NOZZLE_CONFIG(support_speed)); } m_avoid_crossing_perimeters.use_external_mp(false); // Allow a straight travel move to the first object point. @@ -4851,7 +4927,9 @@ GCode::LayerResult GCode::process_layer( 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)) { + && (writer().filament() && get_extruder_id(writer().filament()->id()) != most_used_extruder) + && !m_placeholder_parser.config().opt_bool("has_wipe_tower_this_layer")) + { m_support_traditional_timelapse = false; } if (FILAMENT_CONFIG(retract_when_changing_layer)) { @@ -5030,24 +5108,23 @@ double GCode::get_path_speed(const ExtrusionPath &path) // set speed double speed = 0; if (path.role() == erPerimeter) { - speed = m_config.inner_wall_speed.get_at(cur_extruder_index()); - if (m_config.enable_overhang_speed.get_at(cur_extruder_index())) { + speed = NOZZLE_CONFIG(inner_wall_speed); + if (NOZZLE_CONFIG(enable_overhang_speed)) { 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.outer_wall_speed.get_at(cur_extruder_index()); - if (m_config.enable_overhang_speed.get_at(cur_extruder_index())) { + speed = NOZZLE_CONFIG(outer_wall_speed); + if (NOZZLE_CONFIG(enable_overhang_speed)) { 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() == erOverhangPerimeter && path.overhang_degree == 5) - speed = m_config.overhang_totally_speed.get_at(cur_extruder_index()); + } else if (path.role() == erOverhangPerimeter && path.overhang_degree == 5) + speed = NOZZLE_CONFIG(overhang_totally_speed); else if (path.role() == erOverhangPerimeter || path.role() == erBridgeInfill || path.role() == erSupportTransition) { - speed = m_config.bridge_speed.get_at(cur_extruder_index()); + speed = NOZZLE_CONFIG(bridge_speed); } 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); @@ -5062,7 +5139,7 @@ double GCode::get_path_speed(const ExtrusionPath &path) 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.initial_layer_speed.get_at(cur_extruder_index()); + if (path.role() != erBottomSurface) speed = NOZZLE_CONFIG(initial_layer_speed); } if (filament_max_volumetric_speed > 0) { @@ -5126,12 +5203,11 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou double small_peri_speed=-1; // apply the small perimeter 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())); + if (loop.length() <= SMALL_PERIMETER_LENGTH(NOZZLE_CONFIG(small_perimeter_threshold))) + small_peri_speed = NOZZLE_CONFIG(small_perimeter_speed).get_abs_value(NOZZLE_CONFIG(outer_wall_speed)); // extrude along the path std::string gcode; - bool is_small_peri=false; //w16 const auto speed_for_path = [&speed, &small_peri_speed](const ExtrusionPath &path, bool &m_resonance_avoidance) { @@ -5140,7 +5216,8 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou (path.get_overhang_degree() == 0 || path.get_overhang_degree() > 5); if (is_small_peri) m_resonance_avoidance = false; - return is_small_peri ? small_peri_speed : speed; + return !is_small_peri ? speed : + (speed == -1) ? small_peri_speed : std::min(small_peri_speed, speed); }; //QDS: avoid overhang on conditional scarf mode @@ -5237,7 +5314,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 - m_writer.set_acceleration((unsigned int) (m_config.default_acceleration.get_at(cur_extruder_index()) + 0.5)); + m_writer.set_acceleration((unsigned int) (NOZZLE_CONFIG(default_acceleration) + 0.5)); //w30 if (this->is_QDT_Printer()) gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); @@ -5323,7 +5400,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 - m_writer.set_acceleration((unsigned int) floor(m_config.default_acceleration.get_at(cur_extruder_index()) + 0.5)); + m_writer.set_acceleration((unsigned int) floor(NOZZLE_CONFIG(default_acceleration) + 0.5)); //w30 if (this->is_QDT_Printer()) gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); @@ -5368,7 +5445,7 @@ std::string GCode::extrude_path(ExtrusionPath path, 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 - m_writer.set_acceleration((unsigned int) floor(m_config.default_acceleration.get_at(cur_extruder_index()) + 0.5)); + m_writer.set_acceleration((unsigned int) floor(NOZZLE_CONFIG(default_acceleration) + 0.5)); //w30 if (this->is_QDT_Printer()) gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); @@ -5437,8 +5514,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.get_at(cur_extruder_index()); - const double support_interface_speed = m_config.support_interface_speed.get_at(cur_extruder_index()); + const double support_speed = NOZZLE_CONFIG(support_speed); + const double support_interface_speed = NOZZLE_CONFIG(support_interface_speed); for (const ExtrusionEntity *ee : support_fills.entities) { ExtrusionRole role = ee->role(); assert(role == erSupportMaterial || role == erSupportMaterialInterface || role == erSupportTransition); @@ -5887,18 +5964,18 @@ bool GCode::slowDownByHeight(double& maxSpeed, double& maxAcc, const ExtrusionPa { double height1, height2, speed1, speed2, acc1, acc2, desiredMaxSpeed = 1000., desiredMaxAcc = 100000; double currentHeight = this->m_layer->print_z; - bool do_slowdown_by_height = m_config.enable_height_slowdown.get_at(cur_extruder_index()); + bool do_slowdown_by_height = NOZZLE_CONFIG(enable_height_slowdown); if (path.role() > erNone && path.role() <= erGapFill) {} else do_slowdown_by_height = false; if (do_slowdown_by_height) { - height1 = m_config.slowdown_start_height.get_at(cur_extruder_index()); - height2 = m_config.slowdown_end_height.get_at(cur_extruder_index()); - speed1 = m_config.slowdown_start_speed.get_at(cur_extruder_index()); - speed2 = m_config.slowdown_end_speed.get_at(cur_extruder_index()); - acc1 = m_config.slowdown_start_acc.get_at(cur_extruder_index()); - acc2 = m_config.slowdown_end_acc.get_at(cur_extruder_index()); + height1 = NOZZLE_CONFIG(slowdown_start_height); + height2 = NOZZLE_CONFIG(slowdown_end_height); + speed1 = NOZZLE_CONFIG(slowdown_start_speed); + speed2 = NOZZLE_CONFIG(slowdown_end_speed); + acc1 = NOZZLE_CONFIG(slowdown_start_acc); + acc2 = NOZZLE_CONFIG(slowdown_end_acc); if (height1 >= height2 || currentHeight > height2 || currentHeight < height1) do_slowdown_by_height = false; else { @@ -6004,29 +6081,29 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, m_config.apply(m_calib_config); // adjust acceleration - if (m_config.default_acceleration.get_at(cur_extruder_index()) > 0) { + if (NOZZLE_CONFIG(default_acceleration) > 0) { double acceleration; - 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 (this->on_first_layer() && NOZZLE_CONFIG(initial_layer_acceleration) > 0) { + acceleration = NOZZLE_CONFIG(initial_layer_acceleration); #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.get_at(cur_extruder_index()) > 0 + } else if (NOZZLE_CONFIG(outer_wall_acceleration) > 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.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()); + acceleration = NOZZLE_CONFIG(outer_wall_acceleration); + } else if (NOZZLE_CONFIG(top_surface_acceleration) > 0 && is_top_surface(path.role())) { + acceleration = NOZZLE_CONFIG(top_surface_acceleration); + } else if (NOZZLE_CONFIG(inner_wall_acceleration) > 0 && path.role() == erPerimeter) { + acceleration = NOZZLE_CONFIG(inner_wall_acceleration); + } else if (m_config.get_abs_value_at("sparse_infill_acceleration", cur_config_index()) > 0 && (path.role() == erInternalInfill)) { + acceleration = m_config.get_abs_value_at("sparse_infill_acceleration", cur_config_index()); } else { - acceleration = m_config.default_acceleration.get_at(cur_extruder_index()); + acceleration = NOZZLE_CONFIG(default_acceleration); } if (do_slowdown_by_height) acceleration = std::min(acceleration, desiredMaxAcc); @@ -6062,12 +6139,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double min_speed = double(m_config.slow_down_min_speed.get_at(m_writer.filament()->id())); // set speed - if (speed == -1) { + auto calc_speed_by_role = [&]() -> void { if (path.role() == erPerimeter) { - speed = m_config.inner_wall_speed.get_at(cur_extruder_index()); + speed = NOZZLE_CONFIG(inner_wall_speed); //reset speed by auto compensation speed if(use_seperate_speed) { - speed = m_config.circle_compensation_speed.get_at(cur_extruder_index()); + speed = NOZZLE_CONFIG(circle_compensation_speed); }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.get_at(cur_extruder_index())) { @@ -6076,10 +6153,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, speed = new_speed == 0.0 ? speed : new_speed; } } else if (path.role() == erExternalPerimeter) { - speed = m_config.outer_wall_speed.get_at(cur_extruder_index()); + speed = NOZZLE_CONFIG(outer_wall_speed); // reset speed by auto compensation speed if (use_seperate_speed) { - speed = m_config.circle_compensation_speed.get_at(cur_extruder_index()); + speed = NOZZLE_CONFIG(circle_compensation_speed); } 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.get_at(cur_extruder_index())) { @@ -6095,39 +6172,48 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, speed = new_speed == 0.0 ? speed : new_speed; } } else if (path.role() == erOverhangPerimeter && path.overhang_degree == 5) { - speed = m_config.overhang_totally_speed.get_at(cur_extruder_index()); + speed = NOZZLE_CONFIG(overhang_totally_speed); } else if (path.role() == erOverhangPerimeter || path.role() == erBridgeInfill || path.role() == erSupportTransition) { - speed = m_config.bridge_speed.get_at(cur_extruder_index()); + speed = NOZZLE_CONFIG(bridge_speed); } else if (path.role() == erInternalInfill) { - speed = m_config.sparse_infill_speed.get_at(cur_extruder_index()); + speed = NOZZLE_CONFIG(sparse_infill_speed); } else if (path.role() == erSolidInfill) { - speed = m_config.internal_solid_infill_speed.get_at(cur_extruder_index()); + speed = NOZZLE_CONFIG(internal_solid_infill_speed); } else if (path.role() == erFloatingVerticalShell){ if(use_seperate_speed){ - speed = m_config.bridge_speed.get_at(cur_extruder_index()); + speed = NOZZLE_CONFIG(bridge_speed); } 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())); + speed = NOZZLE_CONFIG(vertical_shell_speed).get_abs_value(NOZZLE_CONFIG(internal_solid_infill_speed)); } } else if (path.role() == erTopSolidInfill) { - speed = m_config.top_surface_speed.get_at(cur_extruder_index()); + speed = NOZZLE_CONFIG(top_surface_speed); } else if (path.role() == erIroning) { speed = m_config.get_abs_value("ironing_speed"); } else if (path.role() == erBottomSurface) { - speed = m_config.initial_layer_infill_speed.get_at(cur_extruder_index()); + speed = NOZZLE_CONFIG(initial_layer_infill_speed); } else if (path.role() == erGapFill) { - speed = m_config.gap_infill_speed.get_at(cur_extruder_index()); + speed = NOZZLE_CONFIG(gap_infill_speed); } else if (path.role() == erSupportMaterial || path.role() == erSupportMaterialInterface) { - 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()); + const double support_speed = NOZZLE_CONFIG(support_speed); + const double support_interface_speed = NOZZLE_CONFIG(support_interface_speed); speed = (path.role() == erSupportMaterial) ? support_speed : support_interface_speed; } else { throw Slic3r::InvalidArgument("Invalid speed"); } - } + }; + if (speed == -1) // not set speed, calc it + calc_speed_by_role(); + else if (speed > EPSILON && is_perimeter(path.role())) { + // for perimeter, use the min speed of small_perimeter_speed and path_role speed + double previous_speed = speed; + calc_speed_by_role(); + speed = std::min(previous_speed, speed); + } //otherwise, keep previous speed in other conditions + //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 (FILAMENT_CONFIG(filament_adaptive_volumetric_speed)){ @@ -6146,7 +6232,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, //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.initial_layer_speed.get_at(cur_extruder_index()); + speed = NOZZLE_CONFIG(initial_layer_speed); } //QDS: remove this config //else if (this->object_layer_over_raft()) @@ -6770,13 +6856,14 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo // QDS 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); + float new_extruder_retracted_length = m_writer.get_extruder_retracted_length(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(new_filament_id); Vec3d nozzle_pos = m_writer.get_position(); - float old_retract_length, old_retract_length_toolchange, wipe_volume; + float old_retract_length, old_retract_length_toolchange, old_filament_retract_length_nc, 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(new_filament_id), 2)); @@ -6795,19 +6882,30 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo 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_retract_length_nc = m_config.filament_retract_length_nc.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) + + auto switch_to_nozzle = [&](int filament_id, int nozzle_id){ + float wipe_volume = 0; + if(m_processor.get_nozzle_status().is_nozzle_empty(nozzle_id)) wipe_volume = 0; else { - wipe_volume = flush_matrix[old_filament_id_in_new_extruder * number_of_extruders + new_filament_id]; + int old_filament_id_in_nozzle = m_processor.get_nozzle_status().get_filament_in_nozzle(nozzle_id); + wipe_volume = flush_matrix[old_filament_id_in_nozzle * number_of_extruders + new_filament_id]; wipe_volume *= m_config.flush_multiplier.get_at(new_extruder_id); } + return wipe_volume; + }; + + assert(m_print->get_nozzle_group_result().has_value()); + + int new_nozzle_id = m_print->get_nozzle_group_result()->get_nozzle_for_filament(new_filament_id)->group_id; + + if (old_extruder_id != new_extruder_id || !m_print->get_nozzle_group_result()->are_filaments_same_nozzle(old_filament_id,new_filament_id)) { + wipe_volume = switch_to_nozzle(new_filament_id, new_nozzle_id); } else { wipe_volume = flush_matrix[old_filament_id * number_of_extruders + new_filament_id]; @@ -6822,6 +6920,7 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo } else { old_retract_length = 0.f; old_retract_length_toolchange = 0.f; + old_filament_retract_length_nc = 0.f; old_filament_temp = 0; wipe_volume = 0.f; old_filament_e_feedrate = 200; @@ -6847,8 +6946,10 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo dyn_config.set_key_value("fan_speed", new ConfigOptionInt((int)0)); dyn_config.set_key_value("old_retract_length", new ConfigOptionFloat(old_retract_length)); dyn_config.set_key_value("new_retract_length", new ConfigOptionFloat(new_retract_length)); + dyn_config.set_key_value("filament_retract_length_nc", new ConfigOptionFloat(old_filament_retract_length_nc)); dyn_config.set_key_value("old_retract_length_toolchange", new ConfigOptionFloat(old_retract_length_toolchange)); dyn_config.set_key_value("new_retract_length_toolchange", new ConfigOptionFloat(new_retract_length_toolchange)); + dyn_config.set_key_value("new_extruder_retracted_length", new ConfigOptionFloat(new_extruder_retracted_length)); dyn_config.set_key_value("old_filament_temp", new ConfigOptionInt(old_filament_temp)); dyn_config.set_key_value("new_filament_temp", new ConfigOptionInt(new_filament_temp)); dyn_config.set_key_value("x_after_toolchange", new ConfigOptionFloat(nozzle_pos(0))); @@ -6866,6 +6967,9 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo 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("wipe_tower_center_pos_x", new ConfigOptionFloat(0.f)); + dyn_config.set_key_value("wipe_tower_center_pos_y", new ConfigOptionFloat(0.f)); + dyn_config.set_key_value("wipe_tower_center_pos_valid", new ConfigOptionBool(false)); auto flush_v_speed = m_print->config().filament_flush_volumetric_speed.values; auto flush_temps =m_print->config().filament_flush_temp.values; diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 2444142..e5adc20 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -391,6 +391,7 @@ private: //QDS void check_placeholder_parser_failed(); size_t cur_extruder_index() const; + size_t cur_config_index() const; size_t get_extruder_id(unsigned int filament_id) const; void set_extrude_acceleration(bool is_first_layer); diff --git a/src/libslic3r/GCode/GCodeEditor.cpp b/src/libslic3r/GCode/GCodeEditor.cpp index d330f8f..e32b362 100644 --- a/src/libslic3r/GCode/GCodeEditor.cpp +++ b/src/libslic3r/GCode/GCodeEditor.cpp @@ -35,7 +35,7 @@ void GCodeEditor::reset(const Vec3d &position) 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_current_pos[4] = float(m_config.travel_speed.get_at(get_config_idx_for_filament(m_config, m_current_extruder))); m_fan_speed = -1; m_additional_fan_speed = -1; m_current_fan_speed = -1; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 00d09c6..697bd29 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -526,6 +526,7 @@ void GCodeProcessor::TimeProcessor::reset() filament_load_times = 0.0f; filament_unload_times = 0.0f; extruder_change_times = 0.0f; + hotend_change_times = 0.0f; for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { @@ -1110,6 +1111,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st context.filament_types, context.filament_maps, context.filament_nozzle_temp, + context.filament_nozzle_temp, context.physical_extruder_map, valid_machine_id, context.inject_time_threshold, @@ -1117,6 +1119,8 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st context.cooling_rate, context.heating_rate, skippable_blocks, + context.extruder_max_nozzle_count, + context.filament_cooling_before_tower, machine_start_gcode_end_line_id, machine_end_gcode_start_line_id ); @@ -1318,48 +1322,56 @@ void GCodeProcessor::UsedFilaments::process_color_change_cache() void GCodeProcessor::UsedFilaments::process_total_volume_cache(GCodeProcessor* processor) { - size_t active_filament_id = processor->get_filament_id(); + int active_filament_id = processor->get_filament_id(); if (total_volume_cache!= 0.0f) { - 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_filament[active_filament_id] = total_volume_cache; + if(active_filament_id != -1){ + 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_filament[active_filament_id] = total_volume_cache; + } total_volume_cache = 0.0f; } } void GCodeProcessor::UsedFilaments::process_model_cache(GCodeProcessor* processor) { - size_t active_filament_id = processor->get_filament_id(); + int active_filament_id = processor->get_filament_id(); if (model_extrude_cache != 0.0f) { - 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_filament[active_filament_id] = model_extrude_cache; + if(active_filament_id != -1){ + 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_filament[active_filament_id] = model_extrude_cache; + } model_extrude_cache = 0.0f; } } void GCodeProcessor::UsedFilaments::process_wipe_tower_cache(GCodeProcessor* processor) { - size_t active_filament_id = processor->get_filament_id(); + int active_filament_id = processor->get_filament_id(); if (wipe_tower_cache != 0.0f) { - 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_filament[active_filament_id] = wipe_tower_cache; + if(active_filament_id != -1){ + 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_filament[active_filament_id] = wipe_tower_cache; + } wipe_tower_cache = 0.0f; } } void GCodeProcessor::UsedFilaments::process_support_cache(GCodeProcessor* processor) { - size_t active_filament_id = processor->get_filament_id(); + int active_filament_id = processor->get_filament_id(false); if (support_volume_cache != 0.0f){ - 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_filament[active_filament_id] = support_volume_cache; + if(active_filament_id != -1){ + 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_filament[active_filament_id] = support_volume_cache; + } support_volume_cache = 0.0f; } } @@ -1468,6 +1480,7 @@ void GCodeProcessorResult::reset() { spiral_vase_layers = std::vector>>(); layer_filaments.clear(); filament_change_count_map.clear(); + filament_change_sequence.clear(); skippable_part_time.clear(); warnings.clear(); @@ -1740,7 +1753,6 @@ bool GCodeProcessor::check_multi_extruder_gcode_valid(const int m_result.gcode_check_result.print_height_error_infos[extruder_id].push_back(filament_to_object_id); valid = false; } - // if (wrapping_exclude_poly.is_valid()) { // if (wrapping_exclude_poly.bounding_box().overlap(bbox)) { // get into the wrapping area // m_result.gcode_check_result.error_code |= (1 << 4); @@ -1772,6 +1784,7 @@ bool GCodeProcessor::check_multi_extruder_gcode_valid(const int valid = false; } } + // check printable height if ((extruder_id < printable_heights.size()) && (iter->second.max_print_z > printable_heights[extruder_id])) { m_result.gcode_check_result.error_code |= (1 << 1); @@ -1816,7 +1829,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config) size_t filament_count = config.filament_diameter.values.size(); m_result.filaments_count = filament_count; - assert(config.nozzle_volume.size() == config.nozzle_diameter.size()); + //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]; @@ -1834,6 +1847,8 @@ void GCodeProcessor::apply_config(const PrintConfig& config) 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_max_nozzle_count = config.extruder_max_nozzle_count.values; + m_filament_cooling_before_tower = config.filament_cooling_before_tower.values; m_extruder_offsets.resize(filament_count); m_extruder_colors.resize(filament_count); @@ -1877,6 +1892,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config) 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); + m_time_processor.hotend_change_times = static_cast(config.machine_hotend_change_time.value); m_time_processor.prepare_compensation_time = static_cast(config.machine_prepare_compensation_time.value); for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { @@ -1903,6 +1919,8 @@ void GCodeProcessor::apply_config(const PrintConfig& config) std::transform(m_filament_maps.begin(), m_filament_maps.end(), m_filament_maps.begin(), [](int value) {return value - 1; }); } + m_config_idx_for_filament = config.filament_map_2.values; + const ConfigOptionBool* spiral_vase = config.option("spiral_mode"); if (spiral_vase != nullptr) m_detect_layer_based_on_tag = spiral_vase->value; @@ -1959,6 +1977,16 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) m_enable_pre_heating = enable_pre_heating->value; } + const ConfigOptionIntsNullable* extruder_max_nozzle_count = config.option("extruder_max_nozzle_count"); + if(extruder_max_nozzle_count != nullptr){ + m_extruder_max_nozzle_count = extruder_max_nozzle_count->values; + } + + const ConfigOptionFloatsNullable* filament_cooling_before_tower = config.option("filament_cooling_before_tower"); + if (filament_cooling_before_tower != nullptr) { + m_filament_cooling_before_tower = filament_cooling_before_tower->values; + } + const ConfigOptionInts* physical_extruder_map = config.option("physical_extruder_map"); if (physical_extruder_map != nullptr) { m_physical_extruder_map = physical_extruder_map->values; @@ -2052,6 +2080,11 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) std::transform(m_filament_maps.begin(), m_filament_maps.end(), m_filament_maps.begin(), [](int value) {return value - 1; }); } + auto config_idx_for_filament = config.option("filament_map_2"); + if (config_idx_for_filament != nullptr){ + m_config_idx_for_filament = config_idx_for_filament->values; + } + //QDS const ConfigOptionFloats* filament_costs = config.option("filament_cost"); if (filament_costs != nullptr) { @@ -2145,6 +2178,10 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) if (machine_switch_extruder_time != nullptr) m_time_processor.extruder_change_times = static_cast(machine_switch_extruder_time->value); + const ConfigOptionFloat* machine_hotend_change_time = config.option("machine_hotend_change_time"); + if(machine_hotend_change_time != nullptr) + m_time_processor.hotend_change_times = static_cast(machine_hotend_change_time->value); + const ConfigOptionFloat* machine_prepare_compensation_time = config.option("machine_prepare_compensation_time"); if (machine_prepare_compensation_time != nullptr) m_time_processor.prepare_compensation_time = static_cast(machine_prepare_compensation_time->value); @@ -2470,6 +2507,13 @@ void GCodeProcessor::initialize(const std::string& filename) m_result.moves.emplace_back(); } + +void GCodeProcessor::initialize_from_context(const MultiNozzleUtils::MultiNozzleGroupResult& nozzle_group_result) +{ + m_nozzle_group_result = nozzle_group_result; +} + + void GCodeProcessor::process_buffer(const std::string &buffer) { //FIXME maybe cache GCodeLine gline to be over multiple parse_buffer() invocations. @@ -2540,7 +2584,9 @@ void GCodeProcessor::finalize(bool post_process) m_hotend_heating_rate, m_filament_pre_cooling_temp, inject_time_threshold, - m_enable_pre_heating + m_enable_pre_heating, + m_extruder_max_nozzle_count, + m_filament_cooling_before_tower ); m_time_processor.post_process(m_result.filename, m_result.moves, m_result.lines_ends, context); } @@ -2927,10 +2973,22 @@ bool GCodeProcessor::get_last_z_from_gcode(const std::string& gcode_str, double& bool GCodeProcessor::get_last_position_from_gcode(const std::string &gcode_str, Vec3f &pos) { + auto parse_G387 = [](const std::string &line_str) { + if (line_str.find("G387 ") != 0) return 0; + if (line_str.find("J1") != std::string::npos) { + return -1;//min + } else if (line_str.find("J-1") != std::string::npos) { + return 1;//max + } + return 0; + }; + int str_size = gcode_str.size(); int start_index = 0; int end_index = 0; bool is_z_changed = false; + Vec3f pre_pos(0, 0, 0); + Vec3i pre_pos_valid(0,0,0); while (end_index < str_size) { // find a full line if (gcode_str[end_index] != '\n') { @@ -2945,8 +3003,10 @@ bool GCodeProcessor::get_last_position_from_gcode(const std::string &gcode_str, 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)) { - { + 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 || line_str.find("G387 ") == 0)) { + int g387_j = parse_G387(line_str); + { float &x = pos.x(); auto z_pos = line_str.find(" X"); float temp_z = 0; @@ -2963,6 +3023,11 @@ bool GCodeProcessor::get_last_position_from_gcode(const std::string &gcode_str, // The axis value has been parsed correctly. x = temp_z; is_z_changed = true; + if (g387_j != 0 && pre_pos_valid.x() != 0) { + x = g387_j ==-1 ? std::min(pre_pos.x(), x) : std::max(pre_pos.x(), x); + } + pre_pos.x() = x; + pre_pos_valid.x() = 1; } } } @@ -2984,6 +3049,9 @@ bool GCodeProcessor::get_last_position_from_gcode(const std::string &gcode_str, // The axis value has been parsed correctly. y = temp_z; is_z_changed = true; + if (g387_j != 0 && pre_pos_valid.y() != 0) { y = g387_j == -1 ? std::min(pre_pos.y(), y) : std::max(pre_pos.y(), y); } + pre_pos.y() = y; + pre_pos_valid.y() = 1; } } } @@ -3005,6 +3073,9 @@ bool GCodeProcessor::get_last_position_from_gcode(const std::string &gcode_str, // The axis value has been parsed correctly. z = temp_z; is_z_changed = true; + if (g387_j != 0 && pre_pos_valid.z()!=0) { z = g387_j == -1 ? std::min(pre_pos.z(), z) : std::max(pre_pos.z(), z); } + pre_pos.z() = z; + pre_pos_valid.z() = 1; } } } @@ -3734,7 +3805,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) process_helioadditive_comment(line); int filament_id = get_filament_id(); - int last_filament_id = get_last_filament_id(); + int last_filament_id = get_last_filament_id(false); 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); @@ -3865,11 +3936,13 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) 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); + if (last_filament_id != -1) + 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]); + if (last_filament_id != -1) + 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; } @@ -3964,7 +4037,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), get_extruder_id()); + float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a), get_config_idx_for_filament(get_filament_id())); if (axis_max_feedrate != 0.0f) min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]); } } @@ -3981,7 +4054,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), get_extruder_id()); + float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a), get_config_idx_for_filament(get_filament_id())); if (acceleration * std::abs(delta_pos[a]) * inv_distance > axis_max_acceleration) acceleration = axis_max_acceleration / (std::abs(delta_pos[a]) * inv_distance); } @@ -4132,7 +4205,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_VG1(const GCodeReader::GCodeLine& line) { int filament_id = get_filament_id(); - int last_filament_id = get_last_filament_id(); + int last_filament_id = get_last_filament_id(false); 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); @@ -4264,11 +4337,13 @@ void GCodeProcessor::process_VG1(const GCodeReader::GCodeLine& line) 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); + if (last_filament_id != -1) + 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]); + if (last_filament_id != -1) + 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; } @@ -4357,7 +4432,7 @@ void GCodeProcessor::process_VG1(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), get_extruder_id()); + float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a), get_config_idx_for_filament(get_filament_id())); if (axis_max_feedrate != 0.0f) min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]); } } @@ -4381,7 +4456,7 @@ void GCodeProcessor::process_VG1(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), get_extruder_id()); + float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a), get_config_idx_for_filament(get_filament_id())); if (acceleration * std::abs(delta_pos[a]) * inv_distance > axis_max_acceleration) acceleration = axis_max_acceleration / (std::abs(delta_pos[a]) * inv_distance); } @@ -4745,7 +4820,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), get_extruder_id()); + float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a), get_config_idx_for_filament(get_filament_id())); if (axis_max_feedrate != 0.0f) min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]); } } @@ -4772,7 +4847,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), get_extruder_id()); + float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a), get_config_idx_for_filament(get_filament_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]); } } @@ -5453,7 +5528,7 @@ void GCodeProcessor::process_SYNC(const GCodeReader::GCodeLine& line) { float time = 0; if (line.has_value('T', time) ) { - simulate_st_synchronize(time); + simulate_st_synchronize(time,erFlush); } } @@ -5538,52 +5613,110 @@ void GCodeProcessor::process_filament_change(int id) 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; + if(!m_nozzle_group_result.has_value()){ + 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 { - //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 + 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 if (m_last_filament_id[next_extruder_id] != next_filament_id) { - //need to change filament - m_filament_id[next_extruder_id] = 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_filament_changes += 1; + m_result.print_statistics.total_extruder_changes++; 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)); + extra_time += get_extruder_change_time(next_extruder_id); } - m_result.lock(); - m_result.print_statistics.total_extruder_changes++; - m_result.unlock(); + } + } + else { + auto old_extruder_opt = m_nozzle_group_result->get_nozzle_for_filament(prev_filament_id); + auto new_extruder_opt = m_nozzle_group_result->get_nozzle_for_filament(next_filament_id); + + int old_extruder_id = old_extruder_opt ? old_extruder_opt->extruder_id : -1; + int new_extruder_id = new_extruder_opt ? new_extruder_opt->extruder_id : -1; + + int old_filament_in_extruder = m_last_filament_id[next_extruder_id]; + auto old_nozzle_in_extruder_opt = m_nozzle_group_result->get_nozzle_for_filament(old_filament_in_extruder); + auto new_nozzle_in_extruder_opt = m_nozzle_group_result->get_nozzle_for_filament(next_filament_id); + + int old_nozzle_in_extruder = old_nozzle_in_extruder_opt ? old_nozzle_in_extruder_opt->group_id : -1; + int new_nozzle_in_extruder = new_nozzle_in_extruder_opt ? new_nozzle_in_extruder_opt->group_id : -1; + + int old_filament_in_nozzle = m_nozzle_status_recorder.get_filament_in_nozzle(new_nozzle_in_extruder); + + bool is_extruder_change = (old_extruder_id != new_extruder_id); + bool is_nozzle_change = (old_nozzle_in_extruder != new_nozzle_in_extruder); + bool is_filament_change = (old_filament_in_nozzle != next_filament_id); + + + if (is_extruder_change) { extra_time += get_extruder_change_time(next_extruder_id); } + if (is_nozzle_change) { + extra_time += get_filament_unload_time(static_cast(old_filament_in_nozzle)); + extra_time += get_hotend_change_time(); + m_time_processor.extruder_unloaded = false; + extra_time += get_filament_load_time(static_cast(next_filament_id)); + } + if (is_filament_change) { + extra_time += get_filament_unload_time(static_cast(old_filament_in_nozzle)); + m_time_processor.extruder_unloaded = false; + extra_time += get_filament_load_time(static_cast(next_filament_id)); + } + + m_result.lock(); + if (is_extruder_change || is_nozzle_change || is_filament_change) { + process_filaments(CustomGCode::ToolChange); + } + + if(is_extruder_change){ + m_result.print_statistics.total_extruder_changes++; + } + else if(is_nozzle_change){ + m_result.print_statistics.total_nozzle_changes++; + } + else if(is_filament_change){ + m_result.print_statistics.total_filament_changes++; + } + m_result.unlock(); + + m_filament_id[next_extruder_id] = next_filament_id; + m_extruder_id = new_extruder_id; + m_nozzle_status_recorder.set_nozzle_status(new_nozzle_in_extruder, next_filament_id); } m_cp_color.current = m_extruder_colors[next_filament_id]; // store tool change move @@ -5826,6 +5959,11 @@ float GCodeProcessor::get_extruder_change_time(size_t extruder_id) return m_time_processor.extruder_change_times; } +float GCodeProcessor::get_hotend_change_time() +{ + return m_time_processor.hotend_change_times; +} + //QDS int GCodeProcessor::get_filament_vitrification_temperature(size_t extrude_id) { @@ -6051,12 +6189,25 @@ int GCodeProcessor::get_extruder_id(bool force_initialize)const return static_cast(m_extruder_id); } +int GCodeProcessor::get_config_idx_for_filament(int filament_idx) const +{ + if (m_config_idx_for_filament.size() > filament_idx) + return m_config_idx_for_filament[filament_idx]; + else + return std::max(0, m_filament_maps[filament_idx] - 1); +} + 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) { + bool is_multiple_nozzle = std::any_of(extruder_max_nozzle_count.begin(), extruder_max_nozzle_count.end(), [](auto& elem) {return elem > 1; }); + auto get_nozzle_temp = [this, is_multiple_nozzle](int filament_id, bool is_first_layer, bool from_or_to, bool consider_cooling_before_tower) { if (filament_id == -1) return from_or_to ? 140 : 0; // default temp - return filament_nozzle_temps[filament_id]; + double temp = (is_first_layer ? filament_nozzle_temps_initial_layer[filament_id] : filament_nozzle_temps[filament_id]); + if(consider_cooling_before_tower) + return (int)(temp - filament_cooling_before_tower[filament_id]); + else + return (int)(temp); }; std::map> per_extruder_free_blocks; @@ -6071,8 +6222,8 @@ void GCodeProcessor::PreCoolingInjector::process_pre_cooling_and_heating(TimePro 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); + float curr_temp = get_nozzle_temp(iter->last_filament_id,false,true,false); + float target_temp = get_nozzle_temp(iter->next_filament_id,false, false,true); inject_cooling_heating_command(inserted_operation_lines, *iter, curr_temp, target_temp, apply_pre_cooling, apply_pre_heating); } } @@ -6088,27 +6239,19 @@ void GCodeProcessor::PreCoolingInjector::build_extruder_free_blocks(const std::v 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{ + bool is_multiple_nozzle = std::any_of(extruder_max_nozzle_count.begin(), extruder_max_nozzle_count.end(), [](auto& elem) {return elem > 1; }); + + auto get_partial_free_cooling_thres = [&](int idx) -> float{ if(idx < 0) return 30.f; - return nozzle_temps[idx] - (float)(pre_cooling_temps[idx]); + float temp_in_tower = filament_nozzle_temps[idx]; + return temp_in_tower - (float)(filament_pre_cooling_temps[idx]); }; auto gcode_move_comp = [](const GCodeProcessorResult::MoveVertex& a, unsigned int gcode_id) { @@ -6179,21 +6322,29 @@ void GCodeProcessor::PreCoolingInjector::inject_cooling_heating_command(TimeProc if (move_iter_lower == moves.end() || move_iter_upper == moves.begin()) return; --move_iter_upper; + float complete_free_time_gap = 0; // time of complete free + if (move_iter_lower == moves.begin()) + complete_free_time_gap = move_iter_upper->time[valid_machine_id]; + else + complete_free_time_gap = move_iter_upper->time[valid_machine_id] - std::prev(move_iter_lower)->time[valid_machine_id]; + 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; + float partial_free_time_gap = 0; // time of partial free + if (partial_free_move_lower == moves.begin()) + partial_free_time_gap = partial_free_move_upper->time[valid_machine_id]; + else + partial_free_time_gap = partial_free_move_upper->time[valid_machine_id] - std::prev(partial_free_move_lower)->time[valid_machine_id]; 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; @@ -6204,17 +6355,49 @@ void GCodeProcessor::PreCoolingInjector::inject_cooling_heating_command(TimeProc float ext_heating_rate = heating_rate[block.extruder_id]; float ext_cooling_rate = cooling_rate[block.extruder_id]; + std::vector line_buf; + auto add_M104_lines = [&](int gcode_id, int target_temp, int target_extruder, bool skippable, int next_filament_idx, TimeProcessor::InsertLineType type,const std::string& comment = std::string()){ + + auto format_line_M104 = [&](int target_temp, int target_extruder, bool skippable, int next_filament_idx, const std::string& comment = std::string())->std::vector { + std::vector buffer; + skippable &= (extruder_max_nozzle_count[target_extruder] > 1); + if (skippable) + buffer.emplace_back("M632 S " + std::to_string(next_filament_idx) + " N R\n"); + + std::string M104_line; + M104_line += "M104"; + if (target_extruder != -1) + M104_line += (" T" + std::to_string(physical_extruder_map[target_extruder])); + M104_line += " S" + std::to_string(target_temp) + " N0"; // N0 means the gcode is generated by slicer + if (!comment.empty()) + M104_line += " ;" + comment; + M104_line += '\n'; + + buffer.emplace_back(M104_line); + + if (skippable) + buffer.emplace_back("M633\n"); + + return buffer; + }; + + std::vector line_buf = format_line_M104(target_temp, target_extruder, skippable, next_filament_idx, comment); + for(auto& line : line_buf) + inserted_operation_lines[gcode_id].emplace_back(line, type); + }; + 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)); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": partial cooling for %1% %2%") % max_cooling_temp % curr_temp; 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); + add_M104_lines(block.partial_free_lower_id,curr_temp, block.extruder_id, false, block.next_filament_id,TimeProcessor::InsertLineType::PreCooling,"Multi extruder pre cooling in post extrusion"); } 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); + add_M104_lines(block.free_lower_gcode_id,target_temp, block.extruder_id, false, block.next_filament_id,TimeProcessor::InsertLineType::PreCooling,"Multi extruder pre cooling"); return; } if (!pre_cooling && pre_heating) { @@ -6224,12 +6407,12 @@ void GCodeProcessor::PreCoolingInjector::inject_cooling_heating_command(TimeProc 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); + add_M104_lines(block.free_lower_gcode_id,target_temp, block.extruder_id, true, block.next_filament_id,TimeProcessor::InsertLineType::PreHeating,"Multi extruder pre heating"); } 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); + add_M104_lines(heating_move_iter->gcode_id, target_temp, block.extruder_id, true, block.next_filament_id,TimeProcessor::InsertLineType::PreHeating, "Multi extruder pre heating"); } return; } @@ -6248,8 +6431,8 @@ void GCodeProcessor::PreCoolingInjector::inject_cooling_heating_command(TimeProc 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); + add_M104_lines(block.free_lower_gcode_id,curr_temp - real_delta_temp, block.extruder_id, false, block.next_filament_id,TimeProcessor::InsertLineType::PreCooling,"Multi extruder pre cooling"); + add_M104_lines(heating_move_iter->gcode_id,target_temp, block.extruder_id, true, block.next_filament_id,TimeProcessor::InsertLineType::PreHeating,"Multi extruder pre heating"); } void GCodeProcessor::PreCoolingInjector::build_by_filament_blocks(const std::vector& filament_usage_blocks_) diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 6ad58b2..6c6e4ab 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -7,6 +7,7 @@ #include "libslic3r/PrintConfig.hpp" #include "libslic3r/CustomGCode.hpp" #include "libslic3r/Extruder.hpp" +#include "libslic3r/MultiNozzleUtils.hpp" #include #include @@ -99,6 +100,7 @@ namespace Slic3r { std::array(ETimeMode::Count)> modes; unsigned int total_filament_changes; unsigned int total_extruder_changes; + unsigned int total_nozzle_changes; PrintEstimatedStatistics() { reset(); } @@ -116,6 +118,7 @@ namespace Slic3r { used_filaments_per_role.clear(); total_filament_changes = 0; total_extruder_changes = 0; + total_nozzle_changes = 0; } }; @@ -175,6 +178,7 @@ namespace Slic3r { GCodeCheckResult gcode_check_result; FilamentPrintableResult filament_printable_reuslt; float initial_layer_time; + std::optional nozzle_group_result; struct SettingsIds { @@ -276,6 +280,7 @@ namespace Slic3r { 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; + std::vector filament_change_sequence; // first key stores `from` filament, second keys stores the `to` filament std::map, int > filament_change_count_map; @@ -321,6 +326,7 @@ namespace Slic3r { filament_printable_reuslt = other.filament_printable_reuslt; layer_filaments = other.layer_filaments; filament_change_count_map = other.filament_change_count_map; + filament_change_sequence = other.filament_change_sequence; skippable_part_time = other.skippable_part_time; initial_layer_time = other.initial_layer_time; #if ENABLE_GCODE_VIEWER_STATISTICS @@ -723,9 +729,11 @@ namespace Slic3r { 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 filament_cooling_before_tower {10.f}; // temperature drop before entering wipe tower 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 }; + std::vector extruder_max_nozzle_count { 1 }; TimeProcessContext( const UsedFilaments& used_filaments_, @@ -739,7 +747,9 @@ namespace Slic3r { const std::vector& heating_rate_, const std::vector& pre_cooling_temp_, const float inject_time_threshold_, - const bool enable_pre_heating_ + const bool enable_pre_heating_, + const std::vector& extruder_max_nozzle_count_, + const std::vector& filament_cooling_before_tower_ ) : used_filaments(used_filaments_), filament_lists(filament_lists_), @@ -752,7 +762,9 @@ namespace Slic3r { heating_rate(heating_rate_), pre_cooling_temp(pre_cooling_temp_), enable_pre_heating(enable_pre_heating_), - inject_time_threshold(inject_time_threshold_) + inject_time_threshold(inject_time_threshold_), + extruder_max_nozzle_count(extruder_max_nozzle_count_), + filament_cooling_before_tower(filament_cooling_before_tower_) { } @@ -794,6 +806,7 @@ namespace Slic3r { float filament_load_times; float filament_unload_times; float extruder_change_times; + float hotend_change_times; float prepare_compensation_time; std::array(PrintEstimatedStatistics::ETimeMode::Count)> machines; @@ -840,6 +853,7 @@ namespace Slic3r { const std::vector& filament_types_, const std::vector& filament_maps_, const std::vector& filament_nozzle_temps_, + const std::vector& filament_nozzle_temps_initial_layer_, const std::vector& physical_extruder_map_, int valid_machine_id_, float inject_time_threshold_, @@ -847,6 +861,8 @@ namespace Slic3r { const std::vector& cooling_rate_, const std::vector& heating_rate_, const std::vector>& skippable_blocks_, + const std::vector& extruder_max_nozzle_count_, + const std::vector& filament_cooling_before_tower_, unsigned int machine_start_gcode_end_id_, unsigned int machine_end_gcode_start_id_ ) : @@ -854,6 +870,7 @@ namespace Slic3r { filament_types(filament_types_), filament_maps(filament_maps_), filament_nozzle_temps(filament_nozzle_temps_), + filament_nozzle_temps_initial_layer(filament_nozzle_temps_initial_layer_), physical_extruder_map(physical_extruder_map_), valid_machine_id(valid_machine_id_), inject_time_threshold(inject_time_threshold_), @@ -861,6 +878,8 @@ namespace Slic3r { cooling_rate(cooling_rate_), heating_rate(heating_rate_), skippable_blocks(skippable_blocks_), + extruder_max_nozzle_count(extruder_max_nozzle_count_), + filament_cooling_before_tower(filament_cooling_before_tower_), machine_start_gcode_end_id(machine_start_gcode_end_id_), machine_end_gcode_start_id(machine_end_gcode_start_id_) { @@ -872,6 +891,7 @@ namespace Slic3r { const std::vector& filament_types; const std::vector& filament_maps; const std::vector& filament_nozzle_temps; + const std::vector& filament_nozzle_temps_initial_layer; const std::vector& physical_extruder_map; const int valid_machine_id; const float inject_time_threshold; @@ -879,6 +899,8 @@ namespace Slic3r { const std::vector& heating_rate; const std::vector& filament_pre_cooling_temps; // target cooling temp during post extrusion const std::vector>& skippable_blocks; + const std::vector& extruder_max_nozzle_count; + const std::vector& filament_cooling_before_tower; const unsigned int machine_start_gcode_end_id; const unsigned int machine_end_gcode_start_id; @@ -1024,6 +1046,8 @@ namespace Slic3r { #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING private: + std::optional m_nozzle_group_result; + MultiNozzleUtils::NozzleStatusRecorder m_nozzle_status_recorder; CommandProcessor m_command_processor; GCodeReader m_parser; EUnits m_units; @@ -1053,6 +1077,8 @@ namespace Slic3r { std::vector m_filament_pre_cooling_temp{ 0 }; float m_enable_pre_heating{ false }; std::vector m_physical_extruder_map; + std::vector m_extruder_max_nozzle_count; + std::vector m_filament_cooling_before_tower; //QDS: x, y offset for gcode generated double m_x_offset{ 0 }; @@ -1073,6 +1099,7 @@ namespace Slic3r { float m_fan_speed; // percentage ExtrusionRole m_extrusion_role; std::vector m_filament_maps; + std::vector m_config_idx_for_filament; std::vector m_last_filament_id; std::vector m_filament_id; unsigned char m_extruder_id; @@ -1157,12 +1184,15 @@ namespace Slic3r { GCodeProcessorResult& result() { return m_result; } GCodeProcessorResult&& extract_result() { return std::move(m_result); } + const MultiNozzleUtils::NozzleStatusRecorder& get_nozzle_status() const { return m_nozzle_status_recorder; } + // Load a G-code into a stand-alone G-code viewer. // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). void process_file(const std::string& filename, std::function cancel_callback = nullptr); // Streaming interface, for processing G-codes just generated by PrusaSlicer in a pipelined fashion. void initialize(const std::string& filename); + void initialize_from_context(const MultiNozzleUtils::MultiNozzleGroupResult& nozzle_group_result); void process_buffer(const std::string& buffer); void finalize(bool post_process); @@ -1377,6 +1407,7 @@ namespace Slic3r { 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); + float get_hotend_change_time(); int get_filament_vitrification_temperature(size_t extrude_id); void process_custom_gcode_time(CustomGCode::Type code); void process_filaments(CustomGCode::Type code); @@ -1394,6 +1425,8 @@ namespace Slic3r { int get_last_filament_id(bool force_initialize = true) const; //get current used extruder int get_extruder_id(bool force_initialize = true)const; + + int get_config_idx_for_filament(int filament_idx) const; }; } /* namespace Slic3r */ diff --git a/src/libslic3r/GCode/TimelapsePosPicker.cpp b/src/libslic3r/GCode/TimelapsePosPicker.cpp index 030c46b..28d7ff1 100644 --- a/src/libslic3r/GCode/TimelapsePosPicker.cpp +++ b/src/libslic3r/GCode/TimelapsePosPicker.cpp @@ -139,7 +139,7 @@ namespace Slic3r { for (auto& obj : object_list) { for (auto& instance : obj->instances()) { auto instance_bbox = get_real_instance_bbox(instance); - bool higher_than_curr_layer = (layer->object() == obj) ? false : instance_bbox.max.z() > z_target; + bool higher_than_curr_layer = (obj == object_list.back()) ? false : instance_bbox.max.z() > z_target; if(range_intersect(instance_bbox.min.z(), instance_bbox.max.z(), z_low, z_high)){ ExPolygon expoly; expoly.contour = { @@ -498,7 +498,7 @@ namespace Slic3r { ExPolygons layer_slices = collect_object_slices_data(ctx.curr_layer, height_gap, object_list, by_object); Polygons camera_limit_areas = collect_limit_areas_for_camera(object_list); Polygons rod_limit_areas; - if (by_object) { + if (by_object && ctx.printed_objects && !ctx.printed_objects->empty()) { rod_limit_areas = collect_limit_areas_for_rod(object_list, ctx); } ExPolygons unplacable_area = union_ex(union_ex(layer_slices, camera_limit_areas), rod_limit_areas); @@ -514,7 +514,7 @@ namespace Slic3r { center_p = get_objects_center(object_list); ExPolygons path_collision_area; - if (by_object) { + if (by_object && ctx.printed_objects && !ctx.printed_objects->empty()) { auto object_without_curr = ctx.printed_objects; if (object_without_curr && !object_without_curr->empty()) object_without_curr->pop_back(); diff --git a/src/libslic3r/GCode/ToolOrderUtils.cpp b/src/libslic3r/GCode/ToolOrderUtils.cpp index 5a6e905..752b235 100644 --- a/src/libslic3r/GCode/ToolOrderUtils.cpp +++ b/src/libslic3r/GCode/ToolOrderUtils.cpp @@ -123,27 +123,40 @@ namespace Slic3r const std::unordered_map>& uv_link_limits, const std::unordered_map>& uv_unlink_limits, const std::vector& u_capacity, - const std::vector& v_capacity) + const std::vector& v_capacity, + const std::vector,int>>& v_group_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; + total_nodes = u_nodes.size() + v_nodes.size() + v_group_capacity.size() + 2; source_id = total_nodes - 2; sink_id = total_nodes - 1; adj.resize(total_nodes); + std::vectorv_node_to(v_nodes.size(), sink_id); + for (size_t gid = 0; gid < v_group_capacity.size(); ++gid) { + for (auto vid : v_group_capacity[gid].first) + v_node_to[vid] = l_nodes.size() + r_nodes.size() + gid; + } + // 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 + // add edge from right nodes to v_node_to(sink node or temp group 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(l_nodes.size() + idx, v_node_to[idx], capacity); + } + + // add edge from temp group node to sink node + for (int idx = 0; idx < v_group_capacity.size(); ++idx) { + int capacity = v_group_capacity[idx].second; + add_edge(l_nodes.size() + r_nodes.size() + idx, sink_id, capacity); } // add edge from left nodes to right nodes @@ -277,7 +290,8 @@ namespace Slic3r const std::unordered_map>& uv_link_limits, const std::unordered_map>& uv_unlink_limits, const std::vector& u_capacity, - const std::vector& v_capacity) + const std::vector& v_capacity, + const std::vector,int>>&v_group_capacity) { assert(u_capacity.empty() || u_capacity.size() == u_nodes.size()); assert(v_capacity.empty() || v_capacity.size() == v_nodes.size()); @@ -286,13 +300,19 @@ namespace Slic3r 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->total_nodes = u_nodes.size() + v_nodes.size() + v_group_capacity.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); + std::vector v_node_to(v_nodes.size(), m_solver->sink_id); + for (size_t gid = 0; gid < v_group_capacity.size(); ++gid) { + for (auto vid : v_group_capacity[gid].first) + v_node_to[vid] = m_solver->l_nodes.size() + m_solver->r_nodes.size() + gid; + } + // 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]; @@ -301,7 +321,12 @@ namespace Slic3r // 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); + m_solver->add_edge(m_solver->l_nodes.size() + i, v_node_to[i], capacity, 0); + } + // add edge from temp group node to sink node + for(int i=0;iadd_edge(m_solver->l_nodes.size() + m_solver->r_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) { @@ -425,7 +450,6 @@ namespace Slic3r 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(); int best_change = std::numeric_limits::max(); // add filament change check in case flush volume between different filament is 0 std::vectorbest_seq; @@ -455,6 +479,7 @@ namespace Slic3r } if (curr_layer_cost > best_cost) continue; + std::sort(next_layer_extruders.begin(), next_layer_extruders.end()); do { std::optionalprev_extruder_2 = prev_extruder_1; float total_cost = curr_layer_cost; @@ -559,7 +584,6 @@ namespace Slic3r } - template static std::vector collect_filaments_in_groups(const std::unordered_set& group, const std::vector& filament_list) { std::vectorret; @@ -602,6 +626,112 @@ namespace Slic3r } + + + // TODO: add cusotm sequence + static int reorder_filaments_for_minimum_flush_volume_base(const std::vector& filament_lists, + const std::vector>& layer_filaments, + const FlushMatrix& flush_matrix, + const std::function&)> get_custom_seq, + std::vector>* filament_sequences) + { + constexpr int max_n_with_forcast = 5; + using uint128_t = boost::multiprecision::uint128_t; + + if (filament_sequences) { + filament_sequences->clear(); + filament_sequences->reserve(layer_filaments.size()); + } + auto filament_list_to_hash_key = [](const std::vector& curr_layer_filaments, const std::vector& next_layer_filaments, + const std::optional& prev_filament, 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_filament) hash_key |= (uint128_t(1) << (64 + *prev_filament)); + + if (use_forcast) { + for (auto item : next_layer_filaments) { hash_key |= (uint128_t(1) << (32 + item)); } + } + + for (auto item : curr_layer_filaments) { hash_key |= (uint128_t(1) << item); } + return hash_key; + }; + + int cost = 0; + std::map> custom_layer_sequence_map; + std::unordered_map>> caches; + std::unordered_set filament_sets(filament_lists.begin(), filament_lists.end()); + std::optional curr_filament_id; + + for (size_t layer = 0; layer < layer_filaments.size(); ++layer){ + const auto& curr_lf = layer_filaments[layer]; + std::vector custom_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(layer_filaments[layer].begin(), layer_filaments[layer].end(), unsign_extruder); + if (it != layer_filaments[layer].end()) + unsign_custom_extruder_seq.emplace_back(unsign_extruder); + } + assert(layer_filaments[layer].size() == unsign_custom_extruder_seq.size()); + + custom_layer_sequence_map[layer] = unsign_custom_extruder_seq; + } + } + + 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(std::unordered_set(filament_lists.begin(),filament_lists.end()), iter->second); + + std::optional prev = curr_filament_id; + for (auto& f: sequence_in_group){ + if(prev) + cost += flush_matrix[*prev][f]; + prev = f; + } + + if(!sequence_in_group.empty()){ + curr_filament_id = sequence_in_group.back(); + } + + if(filament_sequences) + filament_sequences->emplace_back(sequence_in_group); + + continue; + } + + std::vector filament_used = collect_filaments_in_groups(filament_sets, curr_lf); + std::vector next_lf; + if (layer + 1 < layer_filaments.size()) next_lf = layer_filaments[layer + 1]; + std::vector filament_used_next_layer = collect_filaments_in_groups(filament_sets, next_lf); + + bool use_forcast = (filament_used.size() <= max_n_with_forcast && filament_used_next_layer.size() <= max_n_with_forcast); + float tmp_cost = 0; + std::vector sequence; + uint128_t hash_key = filament_list_to_hash_key(filament_used, filament_used_next_layer, curr_filament_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, filament_used, filament_used_next_layer, curr_filament_id, use_forcast, &tmp_cost); + caches[hash_key] = { tmp_cost,sequence }; + } + + if (filament_sequences) + filament_sequences->emplace_back(sequence); + + if (!sequence.empty()) + curr_filament_id = sequence.back(); + + cost += tmp_cost; + } + + return cost; + } + int reorder_filaments_for_minimum_flush_volume(const std::vector& filament_lists, const std::vector& filament_maps, const std::vector>& layer_filaments, @@ -775,4 +905,396 @@ namespace Slic3r return cost; } + +#if DEBUG_MULTI_NOZZLE_MCMF + + //solve the problem by min cost max flow + static std::vector> solve_extruder_order_with_MCMF(const std::vector>& wipe_volumes, + const std::vector& curr_layer_filaments, + const std::vector& nozzle_state, + float* min_cost) + { + std::vector>ret; + std::vectorl_nodes = nozzle_state; + std::vectorr_nodes = curr_layer_filaments; + std::sort(r_nodes.begin(), r_nodes.end()); + + int nozzle_num = nozzle_state.size(); + int filament_num = curr_layer_filaments.size(); + int epochs = std::ceil(filament_num / (double)(nozzle_num)); + for (int i = 0; i < epochs; ++i) { + GeneralMinCostSolver s(wipe_volumes, l_nodes, r_nodes); + auto match = s.solve(); + ret.emplace_back(match); + l_nodes = match; + + auto remove_iter = std::remove_if(r_nodes.begin(), r_nodes.end(), [match](auto item) { + return std::find(match.begin(), match.end(), item) != match.end(); + }); + r_nodes.erase(remove_iter, r_nodes.end()); + } + + if (min_cost) { + float cost = 0; + std::vectornozzle_filament = nozzle_state; + for (int seq = 0; seq < ret.size(); ++seq) { + for (int id = 0; id < ret[seq].size(); ++id) { + if (ret[seq][id] != -1) { + if (nozzle_filament[id] != -1) + cost += wipe_volumes[nozzle_filament[id]][ret[seq][id]]; + nozzle_filament[id] = ret[seq][id]; + } + } + } + *min_cost = cost; + } + + return ret; + } + + // get best filament order of multiple nozzle + std::vector> get_extruders_order(const std::vector>& wipe_volumes, + const std::vector& curr_layer_filaments, + const std::vector& nozzle_state, + float* min_cost) + { + return solve_extruder_order_with_MCMF(wipe_volumes, curr_layer_filaments, nozzle_state, min_cost); + } + + + // get nozzle match data when user set the filament sequence + static std::vector> get_nozzle_match_in_given_order(const std::vector>& wipe_volumes, + const std::vector& layer_filaments_sequence, + const std::vector& nozzle_state, + float* min_cost) + { + std::vector> ret; + std::vectorstate = nozzle_state; + std::vectorfilament_nozzles; //nozzles where each filament is placed + + float cost = 0; + + for (auto f : layer_filaments_sequence) { + float min_flush = std::numeric_limits::max(); + int min_flush_idx = -1; + for (size_t idx = 0; idx < state.size(); ++idx) { + float flush = (state[idx] == -1 ? 0 : wipe_volumes[state[idx]][f]); + if (flush < min_flush) { + min_flush = flush; + min_flush_idx = idx; + } + } + cost += min_flush; + state[min_flush_idx] = f; + filament_nozzles.emplace_back(min_flush_idx); + } + + assert(filament_nozzles.size() == layer_filaments_sequence.size()); + int prev = -1; + for (int idx = 0; idx < filament_nozzles.size(); ++idx) { + if (prev == -1 || filament_nozzles[idx] <= prev) { + ret.emplace_back(std::vector(nozzle_state.size(), -1)); + } + ret.back()[filament_nozzles[idx]] = layer_filaments_sequence[idx]; + prev = filament_nozzles[idx]; + } + + if (min_cost) + *min_cost = cost; + + return ret; + } + int reorder_filaments_for_multi_nozzle_extruder(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, + int multi_nozzle_extruder_id, + int multi_nozzle_num, + std::vector>* layer_sequences, + std::vector>>* nozzle_match_per_layer) + { + assert(0 <= multi_nozzle_extruder_id && multi_nozzle_extruder_id <= 1); + + int fixed_nozzle_extruder_id = 1 - multi_nozzle_extruder_id; + + std::vector>groups(2); //save the grouped filaments + std::map>custom_layer_sequence_map;// save the filament sequences of custom layer + + 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; + } + } + + float cost = 0; + std::vector>>nozzle_match_info; + std::vector>>temp_layer_sequence(2); + + // first deal with fixed nozzle + if (!groups[fixed_nozzle_extruder_id].empty()) + { + int idx = fixed_nozzle_extruder_id; + std::vector filament_used_in_group(groups[idx].begin(), groups[idx].end()); + std::vectorfilament_map_in_group(groups[idx].size(), idx); + cost += reorder_filaments_for_minimum_flush_volume(filament_used_in_group, filament_map_in_group, layer_filaments, flush_matrix, get_custom_seq, layer_sequences ? &temp_layer_sequence[idx] : nullptr); + } + + //then deal with multiple nozzle + if (!groups[multi_nozzle_extruder_id].empty()) + { + int idx = multi_nozzle_extruder_id; + if (layer_sequences) + temp_layer_sequence[idx].reserve(layer_filaments.size()); + if (nozzle_match_per_layer) + nozzle_match_info.reserve(layer_filaments.size()); + + std::vector nozzle_state(multi_nozzle_num, -1); + + auto update_nozzle_state = [&nozzle_state](const std::vector>& match_info) { + for (size_t i = 0; i < match_info.size(); ++i) { + for (size_t j = 0; j < match_info[i].size(); ++j) + if (match_info[i][j] != -1) + nozzle_state[j] = match_info[i][j]; + } + }; + + for (size_t layer = 0; layer < layer_filaments.size(); ++layer) { + if (auto iter = custom_layer_sequence_map.find(layer); iter != custom_layer_sequence_map.end()) { + const auto& sequence = iter->second; + std::vectorsequence_in_group = collect_filaments_in_groups(groups[idx], sequence); + + float tmp_cost = 0; + auto nozzle_match = get_nozzle_match_in_given_order(flush_matrix[idx], sequence_in_group, nozzle_state, &tmp_cost); + cost += tmp_cost; + update_nozzle_state(nozzle_match); + + if (layer_sequences) + temp_layer_sequence[idx].emplace_back(std::vector()); // insert an empty array as placeholder + if (nozzle_match_per_layer) + nozzle_match_info.emplace_back(nozzle_match); + continue; + } + + + const auto& curr_lf = layer_filaments[layer]; + std::vectorfilament_used_in_group = collect_filaments_in_groups(groups[idx], curr_lf); + + float tmp_cost = 0; + auto ret = solve_extruder_order_with_MCMF(flush_matrix[idx], filament_used_in_group, nozzle_state, &tmp_cost); + cost += tmp_cost; + update_nozzle_state(ret); + + if (layer_sequences) { + std::vector flatten; + for (size_t i = 0; i < ret.size(); ++i) { + for (size_t j = 0; j < ret[i].size(); ++j) + if (ret[i][j] != -1) + flatten.emplace_back(ret[i][j]); + } + temp_layer_sequence[idx].emplace_back(flatten); + } + if (nozzle_match_per_layer) + nozzle_match_info.emplace_back(ret); + } + } + + // save the final sequence and match info if necessary + if (layer_sequences) { + layer_sequences->clear(); + layer_sequences->resize(layer_filaments.size()); + bool last_group = 0; + //if last_group == 0,print group 0 first ,else print group 1 first + for (size_t i = 0; i < layer_filaments.size(); ++i) { + auto& curr_layer_seq = (*layer_sequences)[i]; + if (auto iter = custom_layer_sequence_map.find(i); iter != custom_layer_sequence_map.end()) { + curr_layer_seq = iter->second; + if (!curr_layer_seq.empty()) { + last_group = groups[0].count(curr_layer_seq.back()) ? 0 : 1; + } + continue; + } + if (last_group) { + curr_layer_seq.insert(curr_layer_seq.end(), temp_layer_sequence[1][i].begin(), temp_layer_sequence[1][i].end()); + curr_layer_seq.insert(curr_layer_seq.end(), temp_layer_sequence[0][i].begin(), temp_layer_sequence[0][i].end()); + } + else { + curr_layer_seq.insert(curr_layer_seq.end(), temp_layer_sequence[0][i].begin(), temp_layer_sequence[0][i].end()); + curr_layer_seq.insert(curr_layer_seq.end(), temp_layer_sequence[1][i].begin(), temp_layer_sequence[1][i].end()); + } + last_group = !last_group; + } + } + + if (nozzle_match_per_layer) + *nozzle_match_per_layer = nozzle_match_info; + + return cost; + } +#endif + + int reorder_filaments_for_multi_nozzle_extruder(const std::vector& filament_lists, + const MultiNozzleUtils::MultiNozzleGroupResult& nozzle_group_result, + const std::vector>& layer_filaments, + const std::vector& flush_matrix, + const std::function&)> get_custom_seq, + std::vector>* filament_sequences) + { + std::map> nozzle_filament_groups; + std::map> extruder_to_nozzle; + + for(auto filament_idx : filament_lists){ + auto nozzle_info = nozzle_group_result.get_nozzle_for_filament(filament_idx); + if (!nozzle_info) + continue; + nozzle_filament_groups[nozzle_info->group_id].insert(filament_idx); + extruder_to_nozzle[nozzle_info->extruder_id].insert(nozzle_info->group_id); + } + + std::map>custom_layer_sequence_map;// save the filament sequences of custom layer + for (size_t layer = 0; layer < layer_filaments.size(); ++layer){ + const auto& curr_lf = layer_filaments[layer]; + std::vector custom_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(layer_filaments[layer].begin(), layer_filaments[layer].end(), unsign_extruder); + if (it != layer_filaments[layer].end()) + unsign_custom_extruder_seq.emplace_back(unsign_extruder); + } + assert(layer_filaments[layer].size() == unsign_custom_extruder_seq.size()); + + custom_layer_sequence_map[layer] = unsign_custom_extruder_seq; + } + } + + + std::map>> nozzle_filament_sequences; + bool store_sequence = filament_sequences != nullptr; + + int cost = 0; + for(auto& group : nozzle_filament_groups){ + int nozzle_id = group.first; + auto& filament_in_nozzle = group.second; + + int extruder_id = 0; + for(auto& [ext, nozzle_set] : extruder_to_nozzle){ + if(nozzle_set.count(nozzle_id)){ + extruder_id = ext; + break; + } + } + + if(filament_in_nozzle.empty()) + continue; + + std::vector filament_vec_in_nozzle(filament_in_nozzle.begin(), filament_in_nozzle.end()); + + std::vector> filament_seq; + cost += reorder_filaments_for_minimum_flush_volume_base(filament_vec_in_nozzle, layer_filaments, flush_matrix[extruder_id], get_custom_seq, store_sequence ? &filament_seq : nullptr); + if(store_sequence) + nozzle_filament_sequences.emplace(nozzle_id, std::move(filament_seq)); + + } + + if(!store_sequence) + return cost; + + std::vector extruders; + std::map> nozzles_per_extruder; + for (auto& [extruder_id, nozzle_set] : extruder_to_nozzle) { + extruders.push_back(extruder_id); + nozzles_per_extruder[extruder_id] = std::vector( + nozzle_set.begin(), nozzle_set.end() + ); + } + + filament_sequences->clear(); + filament_sequences->resize(layer_filaments.size()); + + auto get_extruder_for_filament = [nozzle_group_result](unsigned int filament_idx) { + auto nozzle = nozzle_group_result.get_nozzle_for_filament(filament_idx); + if (!nozzle) + return -1; + return nozzle->extruder_id; + }; + + auto get_nozzle_idx_for_filament = [nozzles_per_extruder, nozzle_group_result](unsigned int filament_idx)->int { + auto nozzle = nozzle_group_result.get_nozzle_for_filament(filament_idx); + if (!nozzle) + return -1; + return std::find(nozzles_per_extruder.at(nozzle->extruder_id).begin(), nozzles_per_extruder.at(nozzle->extruder_id).end(), nozzle->group_id) - nozzles_per_extruder.at(nozzle->extruder_id).begin(); + }; + + int last_extruder_idx = 0; + // set size to max extruder_id in case extruder_id is not continuous + std::vector last_nozzle_idx(*std::max_element(extruders.begin(),extruders.end()) + 1,0); + + for (size_t layer = 0; layer < layer_filaments.size(); ++layer) { + auto& out_seq = (*filament_sequences)[layer]; + + if (custom_layer_sequence_map.find(layer) != custom_layer_sequence_map.end()) { + out_seq = custom_layer_sequence_map[layer]; + if (!out_seq.empty()) { + last_extruder_idx = get_extruder_for_filament(out_seq.back()); + for (auto filament : out_seq) { + int cur_ext_id = get_extruder_for_filament(filament); + last_nozzle_idx[cur_ext_id] = get_nozzle_idx_for_filament(filament); + } + } + continue; + } + + if (last_extruder_idx == -1) + last_extruder_idx = 0; + + int curr_last_extruder_idx = last_extruder_idx; + auto curr_last_nozzle_idx = last_nozzle_idx; + for (int i = 0; i < extruders.size(); ++i) { + int extruder_id = extruders[(last_extruder_idx + i) % extruders.size()]; + auto& base_nozzles = nozzles_per_extruder[extruder_id]; + + bool has_seq = false; + if (last_nozzle_idx[extruder_id] == -1) + last_nozzle_idx[extruder_id] = 0; + + for (int j = 0; j < base_nozzles.size(); ++j) { + int nozzle_idx = (last_nozzle_idx[extruder_id] + j) % base_nozzles.size(); + int nozzle_id = base_nozzles[nozzle_idx]; + const auto& frag = nozzle_filament_sequences[nozzle_id][layer]; + if (frag.empty()) + continue; + has_seq = true; + curr_last_nozzle_idx[extruder_id] = nozzle_idx; + out_seq.insert(out_seq.end(), frag.begin(), frag.end()); + } + + if (has_seq) + curr_last_extruder_idx = extruder_id; + } + last_extruder_idx = curr_last_extruder_idx; + last_nozzle_idx = curr_last_nozzle_idx; + } + return cost; + } + } diff --git a/src/libslic3r/GCode/ToolOrderUtils.hpp b/src/libslic3r/GCode/ToolOrderUtils.hpp index 26c9a01..f310af0 100644 --- a/src/libslic3r/GCode/ToolOrderUtils.hpp +++ b/src/libslic3r/GCode/ToolOrderUtils.hpp @@ -7,7 +7,9 @@ #include #include #include +#include "../MultiNozzleUtils.hpp" +#define DEBUG_MULTI_NOZZLE_MCMF 0 namespace Slic3r { using FlushMatrix = std::vector>; @@ -29,7 +31,8 @@ public: const std::unordered_map>& uv_link_limits = {}, const std::unordered_map>& uv_unlink_limits = {}, const std::vector& u_capacity = {}, - const std::vector& v_capacity = {} + const std::vector& v_capacity = {}, + const std::vector, int>>& v_group_capacity = {} ); std::vector solve(); @@ -71,8 +74,9 @@ public: const std::unordered_map>& uv_link_limits = {}, const std::unordered_map>& uv_unlink_limits = {}, const std::vector& u_capacity = {}, - const std::vector& v_capacity = {} - ); + const std::vector& v_capacity = {}, + const std::vector, int>>& v_group_capacity = {}); + std::vector solve(); ~MinFlushFlowSolver(); private: @@ -96,13 +100,6 @@ private: }; -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, @@ -110,5 +107,33 @@ int reorder_filaments_for_minimum_flush_volume(const std::vector & std::optional &)>> get_custom_seq, std::vector> *filament_sequences); +#if DEBUG_MULTI_NOZZLE_MCMF +int reorder_filaments_for_multi_nozzle_extruder(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, + int multi_nozzle_extruder_id, + int multi_nozzle_num, + std::vector> * layer_sequences = nullptr, + std::vector>>* nozzle_match_per_layer = nullptr); + +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); + +#endif + + +int reorder_filaments_for_multi_nozzle_extruder(const std::vector& filament_lists, + const MultiNozzleUtils::MultiNozzleGroupResult& nozzle_group_result, + const std::vector>& layer_filaments, + const std::vector& flush_matrix, + const std::function&)> 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 941d5b0..0a0d9e0 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -100,15 +100,51 @@ unsigned int LayerTools::solid_infill_filament(const PrintRegion ®ion) const // Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden. unsigned int LayerTools::extruder(const ExtrusionEntityCollection &extrusions, const PrintRegion ®ion) const { - assert(region.config().wall_filament.value > 0); - assert(region.config().sparse_infill_filament.value > 0); - assert(region.config().solid_infill_filament.value > 0); + assert(region.config().wall_filament.value >= 0); + assert(region.config().sparse_infill_filament.value >= 0); + assert(region.config().solid_infill_filament.value >= 0); // 1 based extruder ID. - unsigned int extruder = ((this->extruder_override == 0) ? - (is_infill(extrusions.role()) ? - (is_solid_infill(extrusions.entities.front()->role()) ? region.config().solid_infill_filament : region.config().sparse_infill_filament) : - region.config().wall_filament.value) : - this->extruder_override); + unsigned int extruder = 0; + // use separate filament for different features only if the potion was open + if (this->extruder_override == 0) { + ExtrusionRole extrusions_role = extrusions.role(); + if (is_perimeter(extrusions_role)) { + extruder = region.config().wall_filament.value; + } else if (is_solid_infill(extrusions_role)) { + extruder = region.config().solid_infill_filament.value; + } else if (is_infill(extrusions_role)) { + extruder = region.config().sparse_infill_filament.value; + } else if (extrusions_role == ExtrusionRole::erMixed) { + //this set of extrusions contain multi roles, follow a Priority to decide which to use + int curr_priority = 0; + for (const ExtrusionEntity *ee : extrusions.entities) + if (ee->role() == ExtrusionRole::erTopSolidInfill) { + if (curr_priority < 100) { // top surface 1st priority + extruder = region.config().solid_infill_filament.value; + curr_priority = 100; + } + } else if (is_perimeter(ee->role())) { + if (curr_priority < 90) { // perimeter 2st priority + extruder = region.config().wall_filament.value; + curr_priority = 90; + } + } else if (is_solid_infill(extrusions_role)) { + if (curr_priority < 70) { // solid infill 4st priority + extruder = region.config().solid_infill_filament.value; + curr_priority = 70; + } + } else if (is_infill(extrusions_role)) { + if (curr_priority < 60) { // infill 5st priority + extruder = region.config().sparse_infill_filament.value; + curr_priority = 60; + } + } + if (curr_priority == 0) // default + extruder = region.config().wall_filament.value; + }else + extruder = region.config().wall_filament.value; + }else + extruder = this->extruder_override; return (extruder == 0) ? 0 : extruder - 1; } @@ -127,24 +163,30 @@ static double calc_max_layer_height(const PrintConfig &config, double max_object } //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) +static FilamentChangeStats calc_filament_change_info_by_toolorder(const PrintConfig* config, const MultiNozzleUtils::MultiNozzleGroupResult& group_result, 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); + MultiNozzleUtils::NozzleStatusRecorder recorder; 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; + for (const auto& filament : ls) { + auto nozzle = group_result.get_nozzle_for_filament(filament); + if (!nozzle) + continue; + + int extruder_id = nozzle->extruder_id; + int nozzle_id = nozzle->group_id; + int last_filament = recorder.get_filament_in_nozzle(nozzle_id); + + if (last_filament != -1 && last_filament != filament) { + int flush_volume = flush_matrix[extruder_id][last_filament][filament]; + flush_volume_per_filament[filament] += flush_volume; total_filament_change_count += 1; } - last_filament_per_extruder[extruder_id] = item; + recorder.set_nozzle_status(nozzle_id, filament); } } @@ -629,6 +671,11 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto for (const auto& eec : layerm->perimeters.entities) // let's check if there are nonoverriddable entities if (!layer_tools.wiping_extrusions().is_overriddable_and_mark(dynamic_cast(*eec), *m_print_config_ptr, object, region)) something_nonoverriddable = true; + }else{ + something_nonoverriddable = false; + for (const auto &eec : layerm->perimeters.entities) // let's check if there are nonoverriddable entities + if (!layer_tools.wiping_extrusions().is_obj_overriddable_and_mark(dynamic_cast(*eec), object)) + something_nonoverriddable = true; } if (something_nonoverriddable){ @@ -650,16 +697,19 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto ExtrusionRole role = fill->entities.empty() ? erNone : fill->entities.front()->role(); if (is_solid_infill(role)) has_solid_infill = true; - else if (role != erNone) + else if (is_infill(role)) has_infill = true; if (m_print_config_ptr) { if (! layer_tools.wiping_extrusions().is_overriddable_and_mark(*fill, *m_print_config_ptr, object, region)) something_nonoverriddable = true; + }else{ + if (!layer_tools.wiping_extrusions().is_obj_overriddable_and_mark(*fill, object)) + something_nonoverriddable = true; } } - if (something_nonoverriddable || !m_print_config_ptr) { + if (something_nonoverriddable) { if (extruder_override == 0) { if (has_solid_infill) layer_tools.extruders.emplace_back(region.config().solid_infill_filament); @@ -1028,18 +1078,52 @@ float get_flush_volume(const std::vector &filament_maps, const std::vector< 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) + +MultiNozzleUtils::MultiNozzleGroupResult ToolOrdering::get_recommended_filament_maps(Print* print, const std::vector>& layer_filaments, const FilamentMapMode mode,const std::vector>&physical_unprintables,const std::vector>&geometric_unprintables) { using namespace FilamentGroupUtils; + using namespace MultiNozzleUtils; + if (!print || layer_filaments.empty()) - return std::vector(); + return MultiNozzleGroupResult(); const auto& print_config = print->config(); const unsigned int filament_nums = (unsigned int)(print_config.filament_colour.values.size() + EPSILON); + bool has_multiple_nozzle = std::any_of(print_config.extruder_max_nozzle_count.values.begin(), print_config.extruder_max_nozzle_count.values.end(), [](int v) { return v > 1; }); // get flush matrix std::vector nozzle_flush_mtx; size_t extruder_nums = print_config.nozzle_diameter.values.size(); + auto used_filaments = collect_sorted_used_filaments(layer_filaments); + + std::vector nozzle_list; + for(size_t idx =0; idx flush_matrix(cast(get_flush_volumes_matrix(print_config.flush_volumes_matrix.values, nozzle_id, extruder_nums))); std::vector> wipe_volumes; @@ -1073,25 +1157,50 @@ std::vector ToolOrdering::get_recommended_filament_maps(const 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) { + bool ignore_ext_filament = false; // TODO: read from config + + std::vector nozzle_groups; + auto extruder_nozzle_counts = get_extruder_nozzle_stats(print_config.extruder_nozzle_stats.values); + auto nozzle_volume_types = print_config.nozzle_volume_type.values; + for(size_t idx = 0; idx < extruder_nums; ++idx){ + if (idx >= extruder_nozzle_counts.size() || extruder_nozzle_counts[idx].empty()) { + nozzle_groups.emplace_back(format_diameter_to_str(print_config.nozzle_diameter.values[idx]), NozzleVolumeType(print_config.nozzle_volume_type.values[idx]), idx, + print_config.extruder_max_nozzle_count.values[idx]); + } + else { + NozzleVolumeType type = NozzleVolumeType(nozzle_volume_types[idx]); + if (type == nvtHybrid) { + for (auto [volume_type, count] : extruder_nozzle_counts[idx]) { + nozzle_groups.emplace_back(format_diameter_to_str(print_config.nozzle_diameter.values[idx]), volume_type, idx, count); + } + } + else + nozzle_groups.emplace_back(format_diameter_to_str(print_config.nozzle_diameter.values[idx]), type, idx, extruder_nozzle_counts[idx][type]); + } + } + + + 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); + std::vector prefer_non_model_filament(extruder_nums,false); + 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; + std::vector filament_usage_types = build_filament_usage_type_list(print_config, print->objects().vector()); // 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; + std::vector> ext_unprintable_filaments(2); collect_unprintable_limits(physical_unprintables, geometric_unprintables, ext_unprintable_filaments); FilamentGroupContext context; @@ -1101,28 +1210,63 @@ std::vector ToolOrdering::get_recommended_filament_maps(const std::vectorget_filament_print_time(); + context.speed_info.group_with_time = print->config().group_algo_with_time; + context.speed_info.filament_change_time = print->config().machine_load_filament_time + print->config().machine_unload_filament_time; + context.speed_info.extruder_change_time = print->config().machine_switch_extruder_time; + 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]; + info.usage_type = filament_usage_types[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.machine_info.prefer_non_model_filament = prefer_non_model_filament; 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(mode == FilamentMapMode::fmmManual) + context.group_info.filament_volume_map = print_config.filament_volume_map.values; + else // hrybid flow means no special request + context.group_info.filament_volume_map = std::vector(filament_nums,(int)(NozzleVolumeType::nvtHybrid)); } + context.nozzle_info.nozzle_list = build_nozzle_list(nozzle_groups); + context.nozzle_info.extruder_nozzle_list = build_extruder_nozzle_list(context.nozzle_info.nozzle_list); - if (!tpu_filaments.empty()) { - ret = calc_filament_group_for_tpu(tpu_filaments, context.group_info.total_filament_num, context.machine_info.master_extruder_id); + if (has_multiple_nozzle) { + if(mode == FilamentMapMode::fmmManual){ + auto manual_filament_map = print_config.filament_map.values; + std::transform(manual_filament_map.begin(), manual_filament_map.end(), manual_filament_map.begin(), [](int v) { return v - 1; }); + ret = calc_filament_group_for_manual_multi_nozzle(manual_filament_map,context); + } + else if(mode == FilamentMapMode::fmmAutoForMatch){ + ret = calc_filament_group_for_match_multi_nozzle(context); + } + else{ + FilamentGroupMultiNozzle fg(context); + ret = fg.calc_filament_group_by_pam(); + } + MultiNozzleUtils::MultiNozzleGroupResult result(ret, context.nozzle_info.nozzle_list, used_filaments); + if (mode == FilamentMapMode::fmmManual) { + auto result_map = result.get_extruder_map(); + for (auto fid : used_filaments) { + if (result_map[fid] != print_config.filament_map.values[fid] - 1) { + throw Slic3r::RuntimeError(_L("Group error in manual mode. Please check nozzle count or regroup.")); + } + } + } + return result; } else { FilamentGroup fg(context); @@ -1131,7 +1275,9 @@ std::vector ToolOrdering::get_recommended_filament_maps(const std::vectorget_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(m_print->get_nozzle_group_result().has_value()){ + filament_maps =m_print->get_nozzle_group_result()->get_extruder_map(); + } + else{ + auto group_result = ToolOrdering::get_recommended_filament_maps(m_print,layer_filaments,map_mode,physical_unprintables,geometric_unprintables); + m_print->set_nozzle_group_result(group_result); + filament_maps = group_result.get_extruder_map(); + } 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; }); + return; + auto group_result = m_print->get_nozzle_group_result(); + m_print->update_filament_maps_to_config( + FilamentGroupUtils::update_used_filament_values(print_config->filament_map.values,group_result->get_extruder_map(false),used_filaments), + FilamentGroupUtils::update_used_filament_values(print_config->filament_volume_map.values,group_result->get_volume_map(),used_filaments), + group_result->get_nozzle_map() + ); + + } check_filament_printable_after_group(used_filaments, filament_maps, print_config); } else { @@ -1257,16 +1413,31 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first return false; }; - reorder_filaments_for_minimum_flush_volume( - filament_lists, - filament_maps, - layer_filaments, - nozzle_flush_mtx, - get_custom_seq, - &filament_sequences - ); + bool support_multi_nozzle = std::any_of(print_config->extruder_max_nozzle_count.values.begin(), print_config->extruder_max_nozzle_count.values.end(), [](auto v) {return v > 1; }); - auto curr_flush_info = calc_filament_change_info_by_toolorder(print_config, filament_maps, nozzle_flush_mtx, filament_sequences); + if(support_multi_nozzle && m_print->get_nozzle_group_result().has_value()){ + reorder_filaments_for_multi_nozzle_extruder( + filament_lists, + m_print->get_nozzle_group_result().value(), + layer_filaments, + nozzle_flush_mtx, + get_custom_seq, + &filament_sequences + ); + } + else{ + reorder_filaments_for_minimum_flush_volume( + filament_lists, + filament_maps, + layer_filaments, + nozzle_flush_mtx, + get_custom_seq, + &filament_sequences + ); + } + + + auto curr_flush_info = calc_filament_change_info_by_toolorder(print_config, m_print->get_nozzle_group_result().value(), nozzle_flush_mtx, filament_sequences); if (nozzle_nums <= 1) m_stats_by_single_extruder = curr_flush_info; else { @@ -1283,30 +1454,39 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first auto maps_without_group = filament_maps; for (auto& item : maps_without_group) item = 0; - reorder_filaments_for_minimum_flush_volume( + + MultiNozzleUtils::NozzleInfo tmp; + tmp.diameter = print_config->nozzle_diameter.get_at(0); + tmp.group_id = 0; + tmp.extruder_id = 0; + tmp.volume_type = NozzleVolumeType::nvtStandard; + + MultiNozzleUtils::MultiNozzleGroupResult result(maps_without_group, { tmp }, used_filaments); + + reorder_filaments_for_multi_nozzle_extruder( filament_lists, - maps_without_group, + result, 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); + m_stats_by_single_extruder = calc_filament_change_info_by_toolorder(print_config, result, 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( + auto group_result_auto = get_recommended_filament_maps(m_print, layer_filaments, fmmAutoForFlush, physical_unprintables, geometric_unprintables); + reorder_filaments_for_multi_nozzle_extruder( filament_lists, - filament_maps_auto, + group_result_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); + m_stats_by_multi_extruder_best = calc_filament_change_info_by_toolorder(print_config, group_result_auto, nozzle_flush_mtx, filament_sequences_one_extruder); } } @@ -1523,6 +1703,16 @@ bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, con return true; } +bool WipingExtrusions::is_obj_overriddable(const ExtrusionEntityCollection &eec, const PrintObject &object) const +{ + if (object.config().flush_into_objects) + return true; + + if (object.config().flush_into_infill && eec.role() == erInternalInfill) + return true; + + return false; +} // QDS bool WipingExtrusions::is_support_overriddable(const ExtrusionRole role, const PrintObject& object) const diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index bf505f8..d855a31 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -50,6 +50,14 @@ public: this->something_overridable |= out; return out; } + //for byobject mode, there are no config, but object still have overridable setting + bool is_obj_overriddable(const ExtrusionEntityCollection &ee, const PrintObject &object) const; + bool is_obj_overriddable_and_mark(const ExtrusionEntityCollection &ee, const PrintObject &object) + { + bool out = this->is_obj_overriddable(ee, object); + this->something_overridable |= out; + return out; + } // QDS bool is_support_overriddable(const ExtrusionRole role, const PrintObject& object) const; @@ -241,7 +249,8 @@ public: * 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 MultiNozzleUtils::MultiNozzleGroupResult get_recommended_filament_maps(Print* print, const std::vector>& layer_filaments, const FilamentMapMode mode, const std::vector>& physical_unprintables, const std::vector>& geometric_unprintables); // should be called after doing reorder FilamentChangeStats get_filament_change_stats(FilamentChangeMode mode); diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 0587dbf..0a52821 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -23,7 +23,7 @@ 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 }; +enum class LimitFlow { None, LimitPrintFlow, LimitRammingFlow, LimitRammingFlowNC};//nc:nozzle change static const std::map nozzle_diameter_to_nozzle_change_width{{0.2f, 0.5f}, {0.4f, 1.0f}, {0.6f, 1.2f}, {0.8f, 1.4f}}; inline float align_round(float value, float base) @@ -431,7 +431,7 @@ Polylines remove_points_from_polygon(const Polygon &polygon, const std::vector()); points.pop_back(); } - + for (int i = 0; i < skip_points.size(); i++) { for (int j = 0; j < points.size(); j++) { Vec2f& p1 = points[j]; @@ -681,7 +681,8 @@ public: 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; + if (limit_flow == LimitFlow::LimitRammingFlow) tmp = m_filpar[m_current_tool].max_e_ramming_speed.first; + else if (limit_flow == LimitFlow::LimitRammingFlowNC) tmp = m_filpar[m_current_tool].max_e_ramming_speed.second; f /= std::max(1.f, e_speed / tmp); } m_gcode += set_format_F(f); @@ -757,7 +758,9 @@ public: 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; + if (limit_flow == LimitFlow::LimitRammingFlow) tmp = m_filpar[m_current_tool].max_e_ramming_speed.first; + else if (limit_flow == LimitFlow::LimitRammingFlowNC) + tmp = m_filpar[m_current_tool].max_e_ramming_speed.second; f /= std::max(1.f, e_speed / tmp); } m_gcode += set_format_F(f); @@ -1102,8 +1105,10 @@ public: return *this; if (speed == 0) m_gcode += "M107\n"; - else - m_gcode += "M106 S" + Slic3r::float_to_string_decimal_point(unsigned(255.0 * speed / 100.0)) + "\n"; //y70 + else{ + //y70 + m_gcode += "M106 S" + Slic3r::float_to_string_decimal_point(unsigned(255.0 * speed / 100.0)) + "\n"; + } m_last_fan_speed = speed; return *this; } @@ -1586,14 +1591,13 @@ 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.get_at(get_extruder_index(config, (unsigned int)initial_tool))), + m_travel_speed(config.travel_speed.get_at(get_config_idx_for_filament(config, (unsigned int)initial_tool))), m_current_tool(initial_tool), //wipe_volumes(flush_matrix) m_enable_timelapse_print(config.timelapse_type.value == TimelapseType::tlSmooth), m_enable_wrapping_detection(config.enable_wrapping_detection), m_wrapping_detection_layers(config.wrapping_detection_layers.value && (config.wrapping_exclude_area.values.size() > 2)), m_slice_used_filaments(slice_used_filaments.size()), - 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), @@ -1606,8 +1610,12 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi m_accel_to_decel_enable(config.accel_to_decel_enable.value), m_accel_to_decel_factor(config.accel_to_decel_factor.value), m_printable_height(config.extruder_printable_height.values), - m_flat_ironing(config.prime_tower_flat_ironing.value) + m_flat_ironing(config.prime_tower_flat_ironing.value), + m_physical_extruder_map(config.physical_extruder_map.values) { + m_filaments_change_length.first = config.filament_change_length.values; + m_filaments_change_length.second = config.filament_change_length_nc.values; + m_hotend_heating_rate = config.hotend_heating_rate.values; m_flat_ironing = (m_flat_ironing && m_use_gap_wall); m_normal_accels.clear(); for (auto value : config.default_acceleration.values) { @@ -1635,7 +1643,7 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi // 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.initial_layer_speed.get_at(get_extruder_index(config, (unsigned int)initial_tool)); + m_first_layer_speed = config.initial_layer_speed.get_at(get_config_idx_for_filament(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; @@ -1673,6 +1681,7 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi ? Vec2f(bed_points.front().x(), bed_points.front().y()) : Vec2f::Zero(); m_last_layer_id.resize(config.nozzle_diameter.size(), -1); + m_is_multiple_nozzle = std::any_of(config.extruder_max_nozzle_count.values.begin(), config.extruder_max_nozzle_count.values.end(), [](auto &elem) { return elem > 1; }); } @@ -1714,24 +1723,61 @@ 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)); + //set extruder change and nozzle change ramming speed + { + 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.first = (ramming_vol_speed / filament_area()); + float ramming_vol_speed_nc = float(config.filament_ramming_volumetric_speed_nc.get_at(idx)); + if (config.filament_ramming_volumetric_speed_nc.is_nil(idx) || is_approx(config.filament_ramming_volumetric_speed_nc.get_at(idx), -1.)) + ramming_vol_speed_nc = max_vol_speed; + m_filpar[idx].max_e_ramming_speed.second = (ramming_vol_speed_nc / filament_area()); + } + + //set precooling time/precooling target temp during extruder change and nozzle change + { + std::unordered_set uniqueElements(m_filament_map.begin(), m_filament_map.end()); + m_filpar[idx].precool_t.first.resize(uniqueElements.size(), 0.f); + m_filpar[idx].precool_t_first_layer.first.resize(uniqueElements.size(), 0.f); + m_filpar[idx].precool_t.second.resize(uniqueElements.size(), 0.f); + m_filpar[idx].precool_t_first_layer.second.resize(uniqueElements.size(), 0.f); + m_filpar[idx].precool_target_temp.first = 0; + m_filpar[idx].precool_target_temp.second = 0; + float nozzle_temp_first_layer = config.nozzle_temperature_initial_layer.is_nil(idx) ? -1.f : float(config.nozzle_temperature_initial_layer.get_at(idx)); + float nozzle_temp_other_layer = config.nozzle_temperature.is_nil(idx) ? -1.f : float(config.nozzle_temperature.get_at(idx)); + std::vector hotend_cooling_rates = config.hotend_cooling_rate.values; + auto is_need_precooling = [&](bool extruder_change) -> bool + { + bool res = config.enable_pre_heating.value; + if (extruder_change) return res &&!config.filament_pre_cooling_temperature.is_nil(idx) && config.filament_pre_cooling_temperature.get_at(idx) != 0; + return res &&!config.filament_pre_cooling_temperature_nc.is_nil(idx) && config.filament_pre_cooling_temperature_nc.get_at(idx) != 0; + }; + if (is_need_precooling(true)) { + for (int i = 0; i < m_filpar[idx].precool_t.first.size(); i++) { + if (config.hotend_cooling_rate.is_nil(i)) continue; + m_filpar[idx].precool_t.first[i] = std::max(0.f, nozzle_temp_other_layer - float(config.filament_pre_cooling_temperature.get_at(idx))) / float(hotend_cooling_rates[i]); + m_filpar[idx].precool_t_first_layer.first[i] = std::max(0.f, nozzle_temp_first_layer -float(config.filament_pre_cooling_temperature.get_at(idx))) /float(hotend_cooling_rates[i]); + } + m_filpar[idx].precool_target_temp.first = config.filament_pre_cooling_temperature.get_at(idx); + } + + if (is_need_precooling(false)) { + for (int i = 0; i < m_filpar[idx].precool_t.second.size(); i++) { + if (config.hotend_cooling_rate.is_nil(i)) continue; + m_filpar[idx].precool_t.second[i] = std::max(0.f, nozzle_temp_other_layer - float(config.filament_pre_cooling_temperature_nc.get_at(idx))) / float(hotend_cooling_rates[i]); + m_filpar[idx].precool_t_first_layer.second[i] = std::max(0.f, nozzle_temp_first_layer -float(config.filament_pre_cooling_temperature_nc.get_at(idx))) /float(hotend_cooling_rates[i]); + } + m_filpar[idx].precool_target_temp.second = config.filament_pre_cooling_temperature_nc.get_at(idx); + } + + } + //set ramming reverse travel time during extruder change and nozzle change + { + m_filpar[idx].ramming_travel_time = {0, 0}; + if (!config.filament_ramming_travel_time.is_nil(idx)) m_filpar[idx].ramming_travel_time.first = float(config.filament_ramming_travel_time.get_at(idx)); + if (!config.filament_ramming_travel_time_nc.is_nil(idx)) m_filpar[idx].ramming_travel_time.second = float(config.filament_ramming_travel_time_nc.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 = nozzle_diameter_to_nozzle_change_width.at(nozzle_diameter); // QDS: remove useless config @@ -1752,6 +1798,7 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config) 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); + m_filpar[idx].filament_cooling_before_tower = config.filament_cooling_before_tower.get_at(idx); } @@ -2302,12 +2349,12 @@ void WipeTower::toolchange_Wipe( float wipe_length) { // Increase flow on first layer, slow down print. - writer.set_extrusion_flow(m_extrusion_flow * (is_first_layer() ? 1.15f : 1.f)) + writer.set_extrusion_flow(m_extrusion_flow * (is_first_layer() ? m_first_layer_flow_ratio : 1.f)) .append("; CP TOOLCHANGE WIPE\n"); // 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"); + writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Width) + std::to_string(m_first_layer_flow_ratio * m_perimeter_width) + "\n"); } const float& xl = cleaning_box.ld.x(); @@ -2623,14 +2670,15 @@ WipeTower::WipeTowerInfo::ToolChange WipeTower::set_toolchange(int old_tool, int float length_to_extrude = volume_to_length(wipe_volume, m_perimeter_width, layer_height); float toolchange_gap_width = get_block_gap_width(new_tool,false); float nozzlechange_gap_width = get_block_gap_width(old_tool,true); + float filament_change_length = !is_same_extruder(old_tool, new_tool) ? m_filaments_change_length.first[old_tool] : m_filaments_change_length.second[old_tool]; depth += std::ceil(length_to_extrude / width) * toolchange_gap_width; // depth *= m_extra_spacing; float nozzle_change_depth = 0; float nozzle_change_length = 0; - if (!m_filament_map.empty() && m_filament_map[old_tool] != m_filament_map[new_tool]) { + if (is_need_ramming(old_tool,new_tool)) { double e_flow = nozzle_change_extrusion_flow(layer_height); - double length = m_filaments_change_length[old_tool] / e_flow; + double length = filament_change_length / e_flow; int nozzle_change_line_count = std::ceil(length / nozzle_change_width); nozzle_change_depth = nozzle_change_line_count * nozzlechange_gap_width; depth += nozzle_change_depth; @@ -2644,10 +2692,10 @@ WipeTower::WipeTowerInfo::ToolChange WipeTower::set_toolchange(int old_tool, int // Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, - unsigned int new_tool, float wipe_volume, float purge_volume) + unsigned int new_tool, float wipe_volume_ec,float wipe_volume_nc,float purge_volume) { assert(m_plan.empty() || m_plan.back().z <= z_par + WT_EPSILON); // refuses to add a layer below the last one - + float wipe_volume = is_same_extruder(old_tool,new_tool)&&!is_same_nozzle(old_tool, new_tool) ? wipe_volume_nc : wipe_volume_ec; if (m_plan.empty() || m_plan.back().z + WT_EPSILON < z_par) // if we moved to a new layer, we'll add it to m_plan first m_plan.push_back(WipeTowerInfo(z_par, layer_height_par)); @@ -2686,12 +2734,12 @@ 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; - + float filament_change_length = !is_same_extruder(old_tool, new_tool) ? m_filaments_change_length.first[old_tool] : m_filaments_change_length.second[old_tool]; float nozzle_change_depth = 0; float nozzle_change_length = 0; - if (!m_filament_map.empty() && m_filament_map[old_tool] != m_filament_map[new_tool]) { + if (is_need_ramming(old_tool,new_tool)) { double e_flow = nozzle_change_extrusion_flow(layer_height_par); - double length = m_filaments_change_length[old_tool] / e_flow; + double length = filament_change_length / e_flow; int nozzle_change_line_count = std::ceil(length / (m_wipe_tower_width - 2*m_nozzle_change_perimeter_width)); nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width; depth += nozzle_change_depth; @@ -2842,9 +2890,11 @@ 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 +bool WipeTower::is_need_reverse_travel(int filament_id,bool extruder_change) const { - return m_filpar[filament_id].ramming_travel_time > EPSILON && m_filaments_change_length[filament_id]>EPSILON; + if (extruder_change) + return m_filpar[filament_id].ramming_travel_time.first > EPSILON && m_filaments_change_length.first[filament_id]>EPSILON; + return m_filpar[filament_id].ramming_travel_time.second > EPSILON && m_filaments_change_length.second[filament_id] > EPSILON; } // QDS: consider both soluable and support properties @@ -2925,7 +2975,7 @@ void WipeTower::get_wall_skip_points(const WipeTowerInfo &layer) 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 (is_need_ramming(new_filament,old_filament)) { if (m_filament_categories[new_filament] == m_filament_categories[old_filament]) process_depth += nozzle_change_depth; else { @@ -2959,10 +3009,11 @@ void WipeTower::get_wall_skip_points(const WipeTowerInfo &layer) 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]) { + bool hotend_change = false; + if (is_need_ramming(m_current_tool,new_tool)) { + hotend_change = m_multi_nozzle_group_result->are_filaments_same_extruder(m_current_tool, 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); + if (is_valid_last_layer(m_current_tool)) m_nozzle_change_result = ramming(m_current_tool, new_tool, solid_nozzlechange, !hotend_change); } size_t old_tool = m_current_tool; @@ -3078,8 +3129,25 @@ WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool sol 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) +//for extruder change and nozzle change +WipeTower::NozzleChangeResult WipeTower::ramming(int old_filament_id, int new_filament_id, bool solid_infill, bool extruder_change) { + auto format_line_M104 = [this](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(this->m_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_line_M106 = []() { return std::string{"M106 S255\n"};}; + auto format_line_M633 = []() { return std::string{"M633\n"};}; + auto format_line_M632 = [](int filament_id) { + std::string buffer = "M632 S" + std::to_string(filament_id) + " M N" + "\n"; + return buffer; + }; + int nozzle_change_line_count = 0; float x_offset = m_perimeter_width + (m_nozzle_change_perimeter_width - m_perimeter_width) / 2; float nozzle_change_box_width = m_wipe_tower_width - 2 * x_offset; @@ -3101,11 +3169,12 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id, }; 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; + float max_e_ramming_speed = extruder_change ? m_filpar[m_current_tool].max_e_ramming_speed.first : m_filpar[m_current_tool].max_e_ramming_speed.second; + float nozzle_change_speed = 60.0f * 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 + float bridge_speed = std::min(60.0f * 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) @@ -3114,7 +3183,17 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id, .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); - + + //only for nozzle change + if (!extruder_change) + { + writer.append(format_line_M632(new_filament_id)); + if (m_filpar[m_current_tool].precool_target_temp.second != 0) { + writer.append(format_line_M104(m_filpar[m_current_tool].precool_target_temp.second, m_filament_map[m_current_tool] - 1)) + .append(format_line_M106()); + } + writer.append(format_line_M633()); + } WipeTowerBlock* block = get_block_by_category(m_filpar[old_filament_id].category, false); if (!block) { assert(false); @@ -3139,13 +3218,19 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id, 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; + float precool_t = extruder_change ? m_filpar[m_current_tool].precool_t.first[extruder_id] : m_filpar[m_current_tool].precool_t.second[extruder_id]; + float precool_t_first_layer = extruder_change ? m_filpar[m_current_tool].precool_t_first_layer.first[extruder_id] : + m_filpar[m_current_tool].precool_t_first_layer.second[extruder_id]; + float per_cooling_max_speed = nozzle_change_speed; + if (extruder_change) { + if (is_first_layer() && precool_t_first_layer > EPSILON) + per_cooling_max_speed = ramming_length / precool_t_first_layer * 60.f; + else if (precool_t > EPSILON) + per_cooling_max_speed = ramming_length / precool_t * 60.f; + }//QDS:nozzle change does not require forcing a cooldown to a specific temperature. 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; + LimitFlow LimitRamming = extruder_change ? LimitFlow::LimitRammingFlow : LimitFlow::LimitRammingFlowNC; for (int i = 0; true; ++i) { if (need_thick_bridge_flow(writer.pos().y())) { writer.set_extrusion_flow(nozzle_change_extrusion_flow(0.2)); @@ -3153,9 +3238,9 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id, 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); + writer.extrude(xr + wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), need_change_flow ? bridge_speed : nozzle_change_speed, LimitRamming); else - writer.extrude(xl - wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), need_change_flow ? bridge_speed : nozzle_change_speed,LimitFlow::LimitRammingFlow); + writer.extrude(xl - wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), need_change_flow ? bridge_speed : nozzle_change_speed, LimitRamming); 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; @@ -3164,7 +3249,7 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id, 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); + writer.extrude(writer.x(), writer.y() + dy, nozzle_change_speed, LimitRamming); m_left_to_right = !m_left_to_right; } if (need_change_flow) { @@ -3174,17 +3259,19 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id, writer.set_extrusion_flow(nz_extrusion_flow); // Reset the extrusion flow. block->cur_depth += nozzle_change_depth; block->last_nozzle_change_id = old_filament_id; - + if (!extruder_change) + writer.append(format_line_M632(new_filament_id)); NozzleChangeResult result; - if (is_need_reverse_travel(m_current_tool)) { + if (is_need_reverse_travel(m_current_tool,extruder_change)) { 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 ramming_travel_time = extruder_change ? m_filpar[m_current_tool].ramming_travel_time.first : m_filpar[m_current_tool].ramming_travel_time.second; + float need_reverse_travel_dis = 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); + writer.travel(writer.x(), writer.y() + dy/2); for (int i = 0; true; ++i) { need_reverse_travel_dis -= (xr - xl - 2 * m_perimeter_width); @@ -3211,13 +3298,14 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id, result.wipe_path.push_back(Vec2f(m_wipe_tower_width, writer.pos_rotated().y())); } } - + if (!extruder_change) writer.append(format_line_M633()); 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(); + result.is_extruder_change = extruder_change; return result; } @@ -3583,14 +3671,13 @@ WipeTower::ToolChangeResult WipeTower::finish_block_solid(const WipeTowerBlock & 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"); - + writer.set_extrusion_flow(m_extrusion_flow * (is_first_layer() ? m_first_layer_flow_ratio : 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"); + writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Width) + std::to_string(m_first_layer_flow_ratio * 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; @@ -3605,24 +3692,57 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat 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; + const std::vector WipeSpeedMap{0.33f * target_speed, 0.375f * target_speed, 0.458f * target_speed, 0.875f * target_speed, + std::min(target_speed, 0.875f * target_speed + 50.f)}; + float wipe_speed = WipeSpeedMap[0]; 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); + auto estimate_wipe_time = [&cleaning_box, &target_speed, &x_to_wipe, &xr, &xl,&dy, &WipeSpeedMap, &solid_tool_toolchange]() -> float { + int n = std::ceil(x_to_wipe / (xr - xl)); + if (solid_tool_toolchange) n = (cleaning_box.lu[1] - cleaning_box.ld[1]) / dy; + float one_line_len = xr - xl; + float time = std::numeric_limits::max(); + if (n <= 1) + time = one_line_len / WipeSpeedMap[0]; + else if (n <= 2) + time = one_line_len / WipeSpeedMap[0] + one_line_len / WipeSpeedMap[1]; + else if (n <= 3) + time = one_line_len / WipeSpeedMap[0] + one_line_len / WipeSpeedMap[1] + one_line_len / WipeSpeedMap[2]; + else if(n<=4) + time = one_line_len / WipeSpeedMap[0] + one_line_len / WipeSpeedMap[1] + one_line_len / WipeSpeedMap[2] + one_line_len / WipeSpeedMap[3]; + else + { + time = one_line_len / WipeSpeedMap[0] + one_line_len / WipeSpeedMap[1] + one_line_len / WipeSpeedMap[2] + one_line_len / WipeSpeedMap[3]; + time += (n - 4) * one_line_len / WipeSpeedMap[4]; } + return time*60.f; + }; + auto format_line_M104 = [this](int target_temp, int target_extruder = -1, const std::string &comment = "") { + std::string buffer = "M104"; + if (target_extruder != -1) buffer += (" T" + std::to_string(this->m_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 add_M104_by_requirement = [&writer, &format_line_M104, this]() { + if (!m_is_multiple_nozzle||m_filpar[m_current_tool].filament_cooling_before_tower < EPSILON) return; + float target_temp = is_first_layer() ? m_filpar[m_current_tool].nozzle_temperature_initial_layer : m_filpar[m_current_tool].nozzle_temperature; + writer.append(format_line_M104(target_temp, m_filament_map[this->m_current_tool] - 1)); + }; + float speed_factor = 1.f; + if (m_is_multiple_nozzle && m_filpar[m_current_tool].filament_cooling_before_tower > EPSILON) { + float estimate_time = estimate_wipe_time(); + int extruder_id = m_filament_map[m_current_tool] - 1; + float heat_time = m_filpar[m_current_tool].filament_cooling_before_tower / m_hotend_heating_rate[extruder_id]; + speed_factor = estimate_time / (heat_time+estimate_time); + wipe_speed *= speed_factor; + } + for (int i = 0; true; ++i) { + if (i < WipeSpeedMap.size()) wipe_speed = WipeSpeedMap[i] * speed_factor; bool need_change_flow = need_thick_bridge_flow(writer.y()); // QDS: check the bridging area and use the bridge flow @@ -3647,6 +3767,7 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat } else writer.travel(writer.x() + 1.5 * ironing_length, writer.y(), 240.); writer.retract(-retract_length, retract_speed); + add_M104_by_requirement(); 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(); @@ -3662,9 +3783,11 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat }else writer.travel(writer.x() - 1.5 * ironing_length, writer.y(), 240.); writer.retract(-retract_length, retract_speed); + add_M104_by_requirement(); writer.extrude(xl - wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), wipe_speed); } } else { + if (i == 0) add_M104_by_requirement(); if (m_left_to_right) writer.extrude(xr + wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), wipe_speed); else @@ -3786,6 +3909,11 @@ void WipeTower::set_nozzle_last_layer_id() } } +void WipeTower::set_first_layer_flow_ratio(const float flow_ratio) +{ + m_first_layer_flow_ratio = flow_ratio; +} + void WipeTower::update_all_layer_depth(float wipe_tower_depth) { m_wipe_tower_depth = 0.f; @@ -3825,7 +3953,7 @@ void WipeTower::generate_wipe_tower_blocks() 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)) { + if (is_need_ramming(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); } @@ -3923,9 +4051,10 @@ void WipeTower::calc_block_infill_gap() for (auto &toolchange : m_plan[i].tool_changes) { int new_tool =toolchange.new_tool; int old_tool =toolchange.old_tool; - if (!m_filament_map.empty() && m_filament_map[old_tool] != m_filament_map[new_tool]) { + if (is_need_ramming(old_tool,new_tool)) { + bool extruder_change = is_in_same_extruder(new_tool, old_tool); block_info[m_filpar[old_tool].category].has_ramming=true; - if (is_need_reverse_travel(old_tool)) block_info[m_filpar[old_tool].category].has_reverse_travel = true; + if (is_need_reverse_travel(old_tool, extruder_change)) block_info[m_filpar[old_tool].category].has_reverse_travel = true; block_info[m_filpar[old_tool].category].depth += toolchange.nozzle_change_depth; } if (!block_info.count(m_filpar[new_tool].category)) block_info.insert({m_filpar[new_tool].category,BlockInfo{}}); @@ -4141,7 +4270,6 @@ void WipeTower::generate_new(std::vectorare_filaments_same_nozzle(filament_id_1, filament_id_2); + //return !is_in_same_extruder(filament_id_1, filament_id_2); +} + +bool WipeTower::is_same_extruder(int filament_id_1, int filament_id_2) +{ + return m_multi_nozzle_group_result->are_filaments_same_extruder(filament_id_1, filament_id_2); +} + +bool WipeTower::is_same_nozzle(int filament_id_1, int filament_id_2) +{ + return m_multi_nozzle_group_result->are_filaments_same_nozzle(filament_id_1, filament_id_2); +} + } // namespace Slic3r diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 62e6353..d6ec94a 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -12,7 +12,7 @@ #include "libslic3r/Polyline.hpp" #include "libslic3r/TriangleMesh.hpp" #include - +#include "libslic3r/MultiNozzleUtils.hpp" namespace Slic3r { @@ -58,6 +58,7 @@ public: Vec2f origin_start_pos; // not rotated std::vector wipe_path; + bool is_extruder_change; }; struct ToolChangeResult @@ -182,7 +183,7 @@ public: // Appends into internal structure m_plan containing info about the future wipe tower // to be used before building begins. The entries must be added ordered in z. - void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume = 0.f, float prime_volume = 0.f); + void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume_ec = 0.f, float wipe_volume_nc = 0.f, float prime_volume = 0.f); // Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result" @@ -259,7 +260,7 @@ public: // Returns gcode to prime the nozzles at the front edge of the print bed. std::vector prime( // print_z of the first layer. - float initial_layer_print_height, + float initial_layer_print_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. const std::vector &tools, // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. @@ -305,15 +306,7 @@ public: 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_filaments_change_length[filament_id]>EPSILON) { - m_need_reverse_travel = true; - return; - } - } - } + bool has_tpu_filament() const { return m_has_tpu_filament; } struct FilamentParameters { std::string material = "PLA"; @@ -341,17 +334,21 @@ public: 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; + std::pair max_e_ramming_speed;//[0]extruder change [1]nozzle change + std::pair ramming_travel_time; // Travel time after ramming + std::pair,std::vector> precool_t;//Pre-cooling time, set to 0 to ensure the ramming speed is controlled solely by ramming volumetric speed. + std::pair, std::vector> precool_t_first_layer; + std::pair precool_target_temp; + float filament_cooling_before_tower = 0.f; }; - void set_used_filament_ids(const std::vector &used_filament_ids) { m_used_filament_ids = used_filament_ids; }; + 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; + void set_nozzle_group_result(const MultiNozzleUtils::MultiNozzleGroupResult &multi_nozzle_group_result) { m_multi_nozzle_group_result = &multi_nozzle_group_result; }; + std::vector m_used_filament_ids; std::vector m_filament_categories; + const MultiNozzleUtils::MultiNozzleGroupResult *m_multi_nozzle_group_result{nullptr}; struct WipeTowerBlock { @@ -393,14 +390,19 @@ public: void generate_wipe_tower_blocks(); void update_all_layer_depth(float wipe_tower_depth); void set_nozzle_last_layer_id(); + void set_first_layer_flow_ratio(const float flow_ratio); void calc_block_infill_gap(); 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); + NozzleChangeResult ramming(int old_filament_id, int new_filament_id, bool solid_change = false, bool extruder_change = true); // extruder_chang means nozzle_change 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; } + bool is_need_ramming(int filament_id_1, int filament_id_2); + bool is_same_extruder(int filament_id_1, int filament_id_2); + bool is_same_nozzle(int filament_id_1, int filament_id_2); + private: enum wipe_shape // A fill-in direction @@ -438,7 +440,7 @@ private: float m_first_layer_speed = 0.f; size_t m_first_layer_idx = size_t(-1); std::vector m_last_layer_id; - std::vector m_filaments_change_length; + std::pair,std::vector> m_filaments_change_length;//[0]extruder change [1]nozzle change size_t m_cur_layer_id; NozzleChangeResult m_nozzle_change_result; std::vector m_filament_map; @@ -453,7 +455,6 @@ private: 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; @@ -466,7 +467,7 @@ private: //bool m_set_extruder_trimpot = false; bool m_adhesion = true; GCodeFlavor m_gcode_flavor; - + bool m_is_multiple_nozzle = false; std::vector m_normal_accels; std::vector m_first_layer_normal_accels; std::vector m_travel_accels; @@ -474,6 +475,7 @@ private: unsigned int m_max_accels; bool m_accel_to_decel_enable; float m_accel_to_decel_factor; + std::vector m_hotend_heating_rate; // Bed properties enum { @@ -484,6 +486,7 @@ private: float m_bed_width; // width of the bed bounding box Vec2f m_bed_bottom_left; // bottom-left corner coordinates (for rectangular beds) + float m_first_layer_flow_ratio; 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. @@ -515,6 +518,7 @@ private: 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; bool m_flat_ironing=false; + std::vector m_physical_extruder_map; // Calculates length of extrusion line to extrude given volume float volume_to_length(float volume, float line_width, float layer_height) const { return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f)))); @@ -536,7 +540,7 @@ private: void save_on_last_wipe(); bool is_tpu_filament(int filament_id) const; - bool is_need_reverse_travel(int filament) const; + bool is_need_reverse_travel(int filament, bool extruder_change) const; // QDS box_coordinates align_perimeter(const box_coordinates& perimeter_box); diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index e4ef696..931f6a3 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -387,7 +387,7 @@ 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.get_at(get_extruder_index(this->config, filament()->id())) * 60.0); + w.emit_f(this->config.travel_speed.get_at(get_config_idx_for_filament(this->config, filament()->id())) * 60.0); //QDS w.emit_comment(GCodeWriter::full_gcode_comment, comment); return set_travel_acceleration() + w.string(); @@ -522,7 +522,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.get_at(get_extruder_index(this->config, filament()->id())) * 60.0); + w0.emit_f(this->config.travel_speed.get_at(get_config_idx_for_filament(this->config, filament()->id())) * 60.0); //QDS w0.emit_comment(GCodeWriter::full_gcode_comment, "slope lift Z"); slop_move = w0.string(); @@ -537,13 +537,13 @@ 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.get_at(get_extruder_index(this->config, filament()->id())) * 60.0); + w0.emit_f(this->config.travel_speed.get_at(get_config_idx_for_filament(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.get_at(get_extruder_index(this->config, filament()->id())) * 60.0); + w0.emit_f(this->config.travel_speed.get_at(get_config_idx_for_filament(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); } @@ -577,13 +577,13 @@ 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.get_at(get_extruder_index(this->config, filament()->id())) * 60.0); + w.emit_f(this->config.travel_speed.get_at(get_config_idx_for_filament(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.get_at(get_extruder_index(this->config, filament()->id())) * 60.0); + w.emit_f(this->config.travel_speed.get_at(get_config_idx_for_filament(this->config, filament()->id())) * 60.0); w.emit_comment(GCodeWriter::full_gcode_comment, comment); out_string = w.string(); } @@ -616,9 +616,9 @@ std::string GCodeWriter::_travel_to_z(double z, const std::string &comment, bool { m_pos(2) = z; - double speed = this->config.travel_speed_z.get_at(get_extruder_index(this->config, filament()->id())); + double speed = this->config.travel_speed_z.get_at(get_config_idx_for_filament(this->config, filament()->id())); if (speed == 0.) - speed = this->config.travel_speed.get_at(get_extruder_index(this->config, filament()->id())); + speed = this->config.travel_speed.get_at(get_config_idx_for_filament(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 } @@ -634,9 +634,9 @@ std::string GCodeWriter::_spiral_travel_to_z(double z, const Vec2d &ij_offset, c { m_pos(2) = z; - double speed = this->config.travel_speed_z.get_at(get_extruder_index(this->config, filament()->id())); + double speed = this->config.travel_speed_z.get_at(get_config_idx_for_filament(this->config, filament()->id())); if (speed == 0.) - speed = this->config.travel_speed.get_at(get_extruder_index(this->config, filament()->id())); + speed = this->config.travel_speed.get_at(get_config_idx_for_filament(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 } @@ -804,6 +804,19 @@ std::string GCodeWriter::unretract() return gcode; } +double GCodeWriter::get_extruder_retracted_length(const int filament_id) +{ + double res = 0.0; + 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); + + if (filament_extruder_iter->is_share_extruder()) + res = filament_extruder_iter->get_share_retracted_length(); + else + res = filament_extruder_iter->get_single_retracted_length(); + + return res; +} std::string GCodeWriter::unlift() { diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index cc5dc7a..3d4f20b 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -85,6 +85,7 @@ public: std::string retract(bool before_wipe = false); std::string retract_for_toolchange(bool before_wipe = false); std::string unretract(); + double get_extruder_retracted_length(const int filament_id); // do lift instantly std::string eager_lift(const LiftType type,bool tool_change = false); // record a lift request, do realy lift in next travel diff --git a/src/libslic3r/Interlocking/InterlockingGenerator.cpp b/src/libslic3r/Interlocking/InterlockingGenerator.cpp index a52c92d..680c94a 100644 --- a/src/libslic3r/Interlocking/InterlockingGenerator.cpp +++ b/src/libslic3r/Interlocking/InterlockingGenerator.cpp @@ -22,6 +22,48 @@ template<> struct hash namespace Slic3r { +void InterlockingGenerator::generate_embedding_wall(PrintObject* print_object){ + //params + const int interface_depth = 2; + const int boundary_avoidance = 2; + constexpr coord_t DEFAULT_BEAM_WIDTH = scaled(0.2); // 例如默认2mm + const coord_t beam_width = DEFAULT_BEAM_WIDTH; + + 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); + + //generator + tbb::parallel_for( + tbb::blocked_range(0, print_object->layers().size()), + [print_object, beam_width, boundary_avoidance, cell_size, interface_dilation, air_dilation, air_filtering](const tbb::blocked_range& range) { + for (size_t i = range.begin(); i != range.end(); ++i) { + Layer* layer = print_object->layers()[i]; + if (layer->id() % 2 == 0) + continue; + for(size_t region_a_index = 0; region_a_index < layer->region_count(); region_a_index++){ + const PrintRegionConfig& config = layer->get_region(region_a_index)->region().config(); + + for(size_t region_b_index = region_a_index + 1; region_b_index < layer->region_count(); region_b_index++){ + const PrintRegionConfig& config_other = layer->get_region(region_b_index)->region().config(); + // wall to infill + if (!(config_other.embedding_wall_into_infill || config.embedding_wall_into_infill)|| layer->has_compatible_layer_regions(config, config_other) || + (config.wall_loops == 0 && config_other.wall_loops == 0) || + (config.wall_loops > 0 && config_other.wall_loops > 0)) { + continue; + } + //has embedding part + InterlockingGenerator gen(*print_object, region_a_index, region_b_index, beam_width, boundary_avoidance, 0, cell_size, 1, + interface_dilation, air_dilation, air_filtering); + gen.generateInterlockingwall(layer); + } + } + } + }); +} void InterlockingGenerator::generate_interlocking_structure(PrintObject* print_object) { @@ -142,6 +184,32 @@ void InterlockingGenerator::handleThinAreas(const std::unordered_set 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::generateInterlockingwall(Layer* layer) const{ + // get shell shape + std::vector> voxels_per_mesh = getLayerShellVoxels(interface_dilation, layer); + + 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; + } + + ExPolygons layer_regions; + expolygons_append(layer_regions, to_expolygons(layer->get_region(region_a_index)->slices.surfaces)); + expolygons_append(layer_regions, to_expolygons(layer->get_region(region_b_index)->slices.surfaces)); + layer_regions = closing_ex(layer_regions, ignored_gap_); // Morphological close to merge meshes into single volume + + std::unordered_set air_cells; + addLayerBoundaryCells(layer_regions, layer->id(), air_dilation, air_cells); + + for (const GridPoint3& p : air_cells) { + has_all_meshes.erase(p); + } + + applyEmbeddingToOutlines(has_all_meshes, layer_regions, layer, layer->id()); +} void InterlockingGenerator::generateInterlockingStructure() const { @@ -170,6 +238,19 @@ void InterlockingGenerator::generateInterlockingStructure() const applyMicrostructureToOutlines(has_all_meshes, layer_regions); } +std::vector> InterlockingGenerator::getLayerShellVoxels(const DilationKernel& kernel, Layer* layer) 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]; + ExPolygons rotated_polygons_per_layer = to_expolygons(layer->get_region(region)->slices.surfaces); + addLayerBoundaryCells(rotated_polygons_per_layer, layer->id(), kernel, mesh_voxels); + } + + return voxels_per_mesh; +} std::vector> InterlockingGenerator::getShellVoxels(const DilationKernel& kernel) const { @@ -194,6 +275,29 @@ std::vector> InterlockingGenerator::getShellVoxel return voxels_per_mesh; } +void InterlockingGenerator::addLayerBoundaryCells(const ExPolygons & layers, + const int &layer_cnt, + 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; + }; + + const coord_t z = static_cast(layer_cnt); + vu.walkDilatedPolygons(layers, z, kernel, voxel_emplacer); + ExPolygons skin; + // 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); + +} + void InterlockingGenerator::addBoundaryCells(const std::vector& layers, const DilationKernel& kernel, @@ -237,6 +341,26 @@ std::vector InterlockingGenerator::computeUnionedVolumeRegions() con return layer_regions; } + +ExPolygons InterlockingGenerator::generateLayerMicrostructure() const +{ + ExPolygons cell_area_per_mesh_per_layer; + 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.emplace_back(poly); + } + return cell_area_per_mesh_per_layer; +} + std::vector> InterlockingGenerator::generateMicrostructure() const { std::vector> cell_area_per_mesh_per_layer; @@ -268,6 +392,39 @@ std::vector> InterlockingGenerator::generateMicrostructu return cell_area_per_mesh_per_layer; } +void InterlockingGenerator::applyEmbeddingToOutlines(const std::unordered_set& cells, const ExPolygons & layer_regions, Layer *layer, const int &idx) const{ + ExPolygons cell_area_per_mesh_per_layer = generateLayerMicrostructure(); + + ExPolygons structure; // 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 + // 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); + ExPolygons areas_here = cell_area_per_mesh_per_layer; + for (auto & here : areas_here) { + here.translate(bottom_corner.x(), bottom_corner.y()); + } + expolygons_append(structure, areas_here); + } + + ExPolygons layer_outlines = layer_regions; + const ExPolygons areas_here = intersection_ex(structure, layer_regions); + for (size_t region_idx = 0; region_idx < 2; region_idx++) { + const size_t region = (region_idx == 0) ? region_a_index : region_b_index; + auto& slices = layer->get_region(region)->slices; + ExPolygons polys = to_expolygons(slices.surfaces); + if (layer->get_region(region)->region().config().wall_loops > 0) + slices.set(union_ex(polys, // reduce layer areas inward with beams from other mesh + areas_here)// extend layer areas outward with newly added beams + , stInternal); + else + slices.set(diff_ex(polys, areas_here), stInternal); + } +} + void InterlockingGenerator::applyMicrostructureToOutlines(const std::unordered_set& cells, const std::vector& layer_regions) const { diff --git a/src/libslic3r/Interlocking/InterlockingGenerator.hpp b/src/libslic3r/Interlocking/InterlockingGenerator.hpp index 4e82ea3..c1f0d6d 100644 --- a/src/libslic3r/Interlocking/InterlockingGenerator.hpp +++ b/src/libslic3r/Interlocking/InterlockingGenerator.hpp @@ -46,13 +46,13 @@ public: * Generate an interlocking structure between each two adjacent meshes. */ static void generate_interlocking_structure(PrintObject* print_object); - + static void generate_embedding_wall(PrintObject* print_object); private: /*! * Generate an interlocking structure between two meshes */ void generateInterlockingStructure() const; - + void generateInterlockingwall(Layer* layer) 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 @@ -114,7 +114,7 @@ private: * \return The shell voxels for mesh a and those for mesh b */ std::vector> getShellVoxels(const DilationKernel& kernel) const; - + std::vector> getLayerShellVoxels(const DilationKernel& kernel, Layer* layer) const; /*! * Compute the voxels overlapping with the shell of some layers. * This includes the walls, but also top/bottom skin. @@ -124,6 +124,7 @@ private: * \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; + void addLayerBoundaryCells(const ExPolygons& layers,const int &layer_cnt, const DilationKernel& kernel, std::unordered_set& cells) const; /*! * Compute the regions occupied by both models. @@ -138,6 +139,7 @@ private: * \return cell_area_per_mesh_per_layer The output polygons for each beam */ std::vector> generateMicrostructure() const; + ExPolygons generateLayerMicrostructure() const; /*! * Change the outlines of the meshes with the computed interlocking structure. @@ -146,7 +148,7 @@ private: * \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; - + void applyEmbeddingToOutlines(const std::unordered_set& cells, const ExPolygons& layer_regions, Layer *layer, const int& idx) 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; diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 8294aa8..152ccbf 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -151,9 +151,9 @@ bool Layer::has_compatible_layer_regions(const PrintRegionConfig &config, const return config.wall_filament == other_config.wall_filament && config.wall_loops == other_config.wall_loops && config.wall_sequence == other_config.wall_sequence - && 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.inner_wall_speed.get_at(get_config_idx_for_filament(config.wall_filament)) == other_config.inner_wall_speed.get_at(get_config_idx_for_filament(config.wall_filament)) + && config.outer_wall_speed.get_at(get_config_idx_for_filament(config.wall_filament)) == other_config.outer_wall_speed.get_at(get_config_idx_for_filament(config.wall_filament)) + && config.gap_infill_speed.get_at(get_config_idx_for_filament(config.wall_filament)) == other_config.gap_infill_speed.get_at(get_config_idx_for_filament(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") @@ -222,6 +222,7 @@ void Layer::make_perimeters() if (layerms.size() == 1) { // optimization (*layerm)->fill_surfaces.surfaces.clear(); + (*layerm)->fill_no_overlap_expolygons.clear(); (*layerm)->make_perimeters((*layerm)->slices, perimeter_regions, &(*layerm)->fill_surfaces, &(*layerm)->fill_no_overlap_expolygons, this->loop_nodes); (*layerm)->fill_expolygons = to_expolygons((*layerm)->fill_surfaces.surfaces); @@ -235,8 +236,6 @@ void Layer::make_perimeters() for (LayerRegion *layerm : layerms) { for (const Surface &surface : layerm->slices.surfaces) slices[surface.extra_perimeters].emplace_back(surface); - if (layerm->region().config().sparse_infill_density > layerm_config->region().config().sparse_infill_density) - layerm_config = layerm; } // merge the surfaces assigned to each group for (std::pair &surfaces_with_extra_perimeters : slices) @@ -252,7 +251,7 @@ void Layer::make_perimeters() SurfaceCollection fill_surfaces; //QDS ExPolygons fill_no_overlap; - layerm_config->make_perimeters(new_slices, perimeter_regions, &fill_surfaces, &fill_no_overlap, this->loop_nodes); + (*layerm)->make_perimeters(new_slices, perimeter_regions, &fill_surfaces, &fill_no_overlap, this->loop_nodes); // assign fill_surfaces to each layer if (!fill_surfaces.surfaces.empty()) { @@ -602,6 +601,11 @@ size_t Layer::get_extruder_id(unsigned int filament_id) const return m_object->print()->get_extruder_id(filament_id); } +size_t Layer::get_config_idx_for_filament(unsigned int filament_id) const +{ + return m_object->print()->get_config_idx_for_filament(filament_id); +} + BoundingBox get_extents(const LayerRegion &layer_region) { BoundingBox bbox; diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index b67e839..e80c37f 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -219,6 +219,7 @@ public: //QDS: this function calculate the maximum void grid area of sparse infill of this layer. Just estimated value coordf_t get_sparse_infill_max_void_area(); + bool has_compatible_layer_regions(const PrintRegionConfig &config, const PrintRegionConfig &other_config); // FN_HIGHER_EQUAL: the provided object pointer has a Z value >= of an internal threshold. // Find the first item with Z value >= of an internal threshold of fn_higher_equal. // If no vec item with Z value >= of an internal threshold of fn_higher_equal is found, return vec.size() @@ -254,6 +255,7 @@ public: } size_t get_extruder_id(unsigned int filament_id) const; + size_t get_config_idx_for_filament(unsigned int filament_id) const; protected: friend class PrintObject; @@ -272,8 +274,6 @@ protected: void simplify_support_multi_path(ExtrusionMultiPath* multipath); void simplify_support_loop(ExtrusionLoop* loop); - bool has_compatible_layer_regions(const PrintRegionConfig &config, const PrintRegionConfig &other_config); - private: // Sequential index of layer, 0-based, offsetted by number of raft layers. size_t m_id; diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index b7d5c72..6bca1e2 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -620,7 +620,7 @@ MCAPI_ATTR void MCAPI_CALL mcDebugOutput(McDebugSource source, } -bool do_boolean_single(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts) +bool do_boolean_single(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts, const BooleanCancelCB& cancel_cb, const BooleanProgressCB& progress_cb) { // create context McContext context = MC_NULL_HANDLE; @@ -639,7 +639,7 @@ bool do_boolean_single(McutMesh &srcMesh, const McutMesh &cutMesh, const std::st // NOTE#1: you can extend these flags by bitwise ORing with additional flags (see `McDispatchFlags' in mcut.h) // NOTE#2: below order of columns MATTERS const std::map booleanOpts = { - {"SUBSTRACTION", MC_DISPATCH_FILTER_FRAGMENT_SEALING_INSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_ABOVE}, + {"A_NOT_B", MC_DISPATCH_FILTER_FRAGMENT_SEALING_INSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_ABOVE}, {"B_NOT_A", MC_DISPATCH_FILTER_FRAGMENT_SEALING_OUTSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_BELOW}, {"UNION", MC_DISPATCH_FILTER_FRAGMENT_SEALING_OUTSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_ABOVE}, {"INTERSECTION", MC_DISPATCH_FILTER_FRAGMENT_SEALING_INSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_BELOW}, @@ -651,6 +651,9 @@ bool do_boolean_single(McutMesh &srcMesh, const McutMesh &cutMesh, const std::st if (srcMesh.vertexCoordsArray.empty() && (boolean_opts == "UNION" || boolean_opts == "B_NOT_A")) { srcMesh = cutMesh; mcReleaseContext(context); + if (progress_cb) { + progress_cb(100.0f); + } return true; } @@ -664,11 +667,17 @@ bool do_boolean_single(McutMesh &srcMesh, const McutMesh &cutMesh, const std::st // cut mesh reinterpret_cast(cutMesh.vertexCoordsArray.data()), cutMesh.faceIndicesArray.data(), cutMesh.faceSizesArray.data(), static_cast(cutMesh.vertexCoordsArray.size() / 3), static_cast(cutMesh.faceSizesArray.size())); + if (progress_cb) { + progress_cb(10.0f); + } if (err != MC_NO_ERROR) { BOOST_LOG_TRIVIAL(debug) << "MCUT mcDispatch fails! err=" << err; mcReleaseContext(context); if (boolean_opts == "UNION") { merge_mcut_meshes(srcMesh, cutMesh); + if (progress_cb) { + progress_cb(100.0f); + } return true; } return false; @@ -677,11 +686,17 @@ bool do_boolean_single(McutMesh &srcMesh, const McutMesh &cutMesh, const std::st // query the number of available connected component uint32_t numConnComps; err = mcGetConnectedComponents(context, MC_CONNECTED_COMPONENT_TYPE_FRAGMENT, 0, NULL, &numConnComps); + if (progress_cb) { + progress_cb(20.0f); + } if (err != MC_NO_ERROR || numConnComps==0) { BOOST_LOG_TRIVIAL(debug) << "MCUT mcGetConnectedComponents fails! err=" << err << ", numConnComps" << numConnComps; mcReleaseContext(context); if (numConnComps == 0 && boolean_opts == "UNION") { merge_mcut_meshes(srcMesh, cutMesh); + if (progress_cb) { + progress_cb(100.0f); + } return true; } return false; @@ -689,26 +704,52 @@ bool do_boolean_single(McutMesh &srcMesh, const McutMesh &cutMesh, const std::st std::vector connectedComponents(numConnComps, MC_NULL_HANDLE); err = mcGetConnectedComponents(context, MC_CONNECTED_COMPONENT_TYPE_FRAGMENT, (uint32_t) connectedComponents.size(), connectedComponents.data(), NULL); - + if (progress_cb) { + progress_cb(30.0f); + } McutMesh outMesh; int N_vertices = 0; // traversal of all connected components for (int n = 0; n < numConnComps; ++n) { + if (cancel_cb && cancel_cb()) { + return false; + } // query the data of each connected component from MCUT McConnectedComponent connComp = connectedComponents[n]; // query the vertices McSize numBytes = 0; err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_VERTEX_DOUBLE, 0, NULL, &numBytes); + + const float sub_progress = 60.0 * n / numConnComps; + const float temp_progress = 30.0f + sub_progress; + if (progress_cb) { + progress_cb(temp_progress + 60.0 / numConnComps * 0.1f); + } + uint32_t ccVertexCount = (uint32_t) (numBytes / (sizeof(double) * 3)); std::vector ccVertices((uint64_t) ccVertexCount * 3u, 0); err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_VERTEX_DOUBLE, numBytes, (void *) ccVertices.data(), NULL); + if (progress_cb) { + progress_cb(temp_progress + 60.0 / numConnComps * 0.2f); + } + // query the faces numBytes = 0; err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION, 0, NULL, &numBytes); + + if (progress_cb) { + progress_cb(temp_progress + 60.0 / numConnComps * 0.3f); + } + std::vector ccFaceIndices(numBytes / sizeof(uint32_t), 0); err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION, numBytes, ccFaceIndices.data(), NULL); + + if (progress_cb) { + progress_cb(temp_progress + 60.0 / numConnComps * 0.4f); + } + std::vector faceSizes(ccFaceIndices.size() / 3, 3); const uint32_t ccFaceCount = static_cast(faceSizes.size()); @@ -717,9 +758,17 @@ bool do_boolean_single(McutMesh &srcMesh, const McutMesh &cutMesh, const std::st McPatchLocation patchLocation = (McPatchLocation) 0; err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_PATCH_LOCATION, sizeof(McPatchLocation), &patchLocation, NULL); + if (progress_cb) { + progress_cb(temp_progress + 60.0 / numConnComps * 0.5f); + } + McFragmentLocation fragmentLocation = (McFragmentLocation) 0; err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FRAGMENT_LOCATION, sizeof(McFragmentLocation), &fragmentLocation, NULL); + if (progress_cb) { + progress_cb(temp_progress + 60.0 / numConnComps * 0.6f); + } + outMesh.vertexCoordsArray.insert(outMesh.vertexCoordsArray.end(), ccVertices.begin(), ccVertices.end()); // add offset to face index @@ -732,6 +781,9 @@ bool do_boolean_single(McutMesh &srcMesh, const McutMesh &cutMesh, const std::st // for each face in CC std::vector faces(ccFaceCount); for (uint32_t f = 0; f < ccFaceCount; ++f) { + if (cancel_cb && cancel_cb()) { + return false; + } bool reverseWindingOrder = (fragmentLocation == MC_FRAGMENT_LOCATION_BELOW) && (patchLocation == MC_PATCH_LOCATION_OUTSIDE); int faceSize = faceSizes.at(f); if (reverseWindingOrder) { @@ -743,10 +795,18 @@ bool do_boolean_single(McutMesh &srcMesh, const McutMesh &cutMesh, const std::st faceVertexOffsetBase += faceSize; } + if (progress_cb) { + progress_cb(temp_progress + 60.0 / numConnComps * 0.8f); + } + outMesh.faceIndicesArray.insert(outMesh.faceIndicesArray.end(), ccFaceIndices.begin(), ccFaceIndices.end()); outMesh.faceSizesArray.insert(outMesh.faceSizesArray.end(), faceSizes.begin(), faceSizes.end()); N_vertices += ccVertexCount; + + if (progress_cb) { + progress_cb(temp_progress + 60.0 / numConnComps * 1.0f); + } } // free connected component data @@ -755,11 +815,13 @@ bool do_boolean_single(McutMesh &srcMesh, const McutMesh &cutMesh, const std::st err = mcReleaseContext(context); srcMesh = outMesh; - + if (progress_cb) { + progress_cb(100.0f); + } return true; } -void do_boolean(McutMesh& srcMesh, const McutMesh& cutMesh, const std::string& boolean_opts) +bool do_boolean(McutMesh& srcMesh, const McutMesh& cutMesh, const std::string& boolean_opts, const BooleanCancelCB& cancel_cb, const BooleanProgressCB& progress_cb, const BooleanFailedCB& failed_cb) { try { TriangleMesh tri_src = mcut_to_triangle_mesh(srcMesh); @@ -770,20 +832,35 @@ void do_boolean(McutMesh& srcMesh, const McutMesh& cutMesh, const std::string& b if (src_parts.empty() && boolean_opts == "UNION") { srcMesh = cutMesh; - return; + return true; } - if (cut_parts.empty()) return; + if (cut_parts.empty()) return true; // 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 == "SUBSTRACTION") { + const auto total_count = src_parts.size() * cut_parts.size(); + int count_index = 0; + BooleanProgressCB temp_progress_cb = nullptr; + if (progress_cb) { + temp_progress_cb = [&](float progress)->void { + float current_progress = 10.0f + 70.0f * (count_index * 1.0f / total_count) + 70.0f * 1.0f / total_count * progress / 100.0f; + if (progress_cb) { + progress_cb(current_progress); + } + }; + } + 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++) { + if (cancel_cb && cancel_cb()) { + return false; + } auto cut_part = triangle_mesh_to_mcut(cut_parts[j]); - do_boolean_single(*src_part, *cut_part, boolean_opts); + do_boolean_single(*src_part, *cut_part, boolean_opts, cancel_cb, temp_progress_cb); + ++count_index; } TriangleMesh tri_part = mcut_to_triangle_mesh(*src_part); its_merge(all_its, tri_part.its); @@ -791,9 +868,13 @@ void do_boolean(McutMesh& srcMesh, const McutMesh& cutMesh, const std::string& b } 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 (cancel_cb && cancel_cb()) { + return false; + } + ++count_index; 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); + bool success = do_boolean_single(*src_part, *cut_part, boolean_opts, cancel_cb, temp_progress_cb); if (success) { TriangleMesh tri_part = mcut_to_triangle_mesh(*src_part); its_merge(all_its, tri_part.its); @@ -802,21 +883,51 @@ void do_boolean(McutMesh& srcMesh, const McutMesh& cutMesh, const std::string& b } } srcMesh = *triangle_mesh_to_mcut(all_its); + return true; } catch (const std::exception &e) { + if (failed_cb) { + failed_cb(); + } BOOST_LOG_TRIVIAL(error) << "check error:" << e.what(); + return false; } } -void make_boolean(const TriangleMesh &src_mesh, const TriangleMesh &cut_mesh, std::vector &dst_mesh, const std::string &boolean_opts) +void make_boolean(const TriangleMesh &src_mesh, const TriangleMesh &cut_mesh, std::vector &dst_mesh, const std::string &boolean_opts, const BooleanCancelCB& cancel_cb, const BooleanProgressCB& progress_cb, const BooleanFailedCB& failed_cb) { McutMesh srcMesh, cutMesh; triangle_mesh_to_mcut(src_mesh, srcMesh); triangle_mesh_to_mcut(cut_mesh, cutMesh); //dst_mesh = make_boolean(srcMesh, cutMesh, boolean_opts); - do_boolean(srcMesh, cutMesh, boolean_opts); + const bool rt = do_boolean(srcMesh, cutMesh, boolean_opts, cancel_cb, progress_cb, failed_cb); + if (!rt) { + return; + } + if (cancel_cb && cancel_cb()) { + return; + } + if (progress_cb) { + progress_cb(90.0f); + } TriangleMesh tri_src = mcut_to_triangle_mesh(srcMesh); - if (!tri_src.empty()) - dst_mesh.push_back(std::move(tri_src)); + if (!tri_src.empty()) { + // Split into parts, fix negative volume for each part, then merge back + std::vector parts = tri_src.split(); + if (parts.size() > 1) { + TriangleMesh fixed_mesh; + for (auto& part : parts) { + if (part.volume() < 0) part.flip_triangles(); + fixed_mesh.merge(part); + } + dst_mesh.push_back(std::move(fixed_mesh)); + } else { + if (tri_src.volume() < 0) tri_src.flip_triangles(); + dst_mesh.push_back(std::move(tri_src)); + } + } + if (progress_cb) { + progress_cb(100.0f); + } } } // namespace mcut diff --git a/src/libslic3r/MeshBoolean.hpp b/src/libslic3r/MeshBoolean.hpp index fb54279..0c1362c 100644 --- a/src/libslic3r/MeshBoolean.hpp +++ b/src/libslic3r/MeshBoolean.hpp @@ -84,16 +84,19 @@ bool empty(const McutMesh &mesh); McutMeshPtr triangle_mesh_to_mcut(const indexed_triangle_set &M); TriangleMesh mcut_to_triangle_mesh(const McutMesh &mcutmesh); +using BooleanCancelCB = std::function; +using BooleanProgressCB = std::function; +using BooleanFailedCB = std::function; // do boolean and save result to srcMesh // return true if sucessful -bool do_boolean_single(McutMesh& srcMesh, const McutMesh& cutMesh, const std::string& boolean_opts); +bool do_boolean_single(McutMesh& srcMesh, const McutMesh& cutMesh, const std::string& boolean_opts, const BooleanCancelCB& cancel_cb = nullptr, const BooleanProgressCB& progress_cb = nullptr); // do boolean of mesh with multiple volumes and save result to srcMesh // Both srcMesh and cutMesh may have multiple volumes. -void do_boolean(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts); +bool do_boolean(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts, const BooleanCancelCB& cancel_cb = nullptr, const BooleanProgressCB& progress_cb = nullptr, const BooleanFailedCB& failed_cb = nullptr); // do boolean and convert result to TriangleMesh -void make_boolean(const TriangleMesh &src_mesh, const TriangleMesh &cut_mesh, std::vector &dst_mesh, const std::string &boolean_opts); +void make_boolean(const TriangleMesh &src_mesh, const TriangleMesh &cut_mesh, std::vector &dst_mesh, const std::string &boolean_opts, const BooleanCancelCB& calcen_cb = nullptr, const BooleanProgressCB& progress_cb = nullptr, const BooleanFailedCB& failed_cb = nullptr); } // namespace mcut } // namespace MeshBoolean diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 8ae0e06..12fb035 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -299,6 +299,8 @@ Model Model::read_from_file(const std::string& } else if (obj_info.face_colors.size() > 0 && obj_info.has_uv_png == false) { // mtl file if (objFn) { // 1.result is ok and pop up a dialog in_out.input_colors = std::move(obj_info.face_colors); + in_out.mtl_colors = std::move(obj_info.mtl_colors); + in_out.first_time_using_makerlab = obj_info.first_time_using_makerlab; in_out.is_single_color = obj_info.is_single_mtl; in_out.deal_vertex_color = false; objFn(in_out); @@ -986,7 +988,7 @@ std::string Model::get_backup_path() } boost::filesystem::path temp_path(backup_path); std::string temp_path_safe = PathSanitizer::sanitize(temp_path); - try { + try { if (!boost::filesystem::exists(temp_path)) { BOOST_LOG_TRIVIAL(info) << "create /3D/Objects in " << temp_path_safe; @@ -2675,7 +2677,7 @@ ModelObjectPtrs ModelObject::merge_volumes(std::vector& vol_indeces) ModelVolume* vol = upper->add_volume(mesh); for (int i = 0; i < volumes.size();i++) { if (std::find(vol_indeces.begin(), vol_indeces.end(), i) != vol_indeces.end()) { - vol->name = volumes[i]->name + "_merged"; + vol->name = "Merged Parts"; vol->config.assign_config(volumes[i]->config); } else @@ -3110,6 +3112,27 @@ std::vector ModelVolume::get_extruders() const if (volume_extruder_id > 0) volume_extruders.push_back(volume_extruder_id); + // push back filaments for features + if (this->config.option("wall_filament") && this->config.option("wall_filament")->getInt() > 0) volume_extruders.push_back(this->config.option("wall_filament")->getInt()); + // wall_filament of this volume not set, try use object options + else if (this->config.option("wall_filament") == nullptr && this->get_object()->config.option("wall_filament") && + this->get_object()->config.option("wall_filament")->getInt() > 0) + volume_extruders.push_back(this->get_object()->config.option("wall_filament")->getInt()); + //due to we cannot access the global config inside modelvolume, + // we have to limit the global preset of filament for features + + if (this->config.option("solid_infill_filament") && this->config.option("solid_infill_filament")->getInt() > 0) + volume_extruders.push_back(this->config.option("solid_infill_filament")->getInt()); + else if (this->config.option("solid_infill_filament") == nullptr && this->get_object()->config.option("solid_infill_filament") && + this->get_object()->config.option("solid_infill_filament")->getInt() > 0) + volume_extruders.push_back(this->get_object()->config.option("solid_infill_filament")->getInt()); + + if (this->config.option("sparse_infill_filament") && this->config.option("sparse_infill_filament")->getInt() > 0) + volume_extruders.push_back(this->config.option("sparse_infill_filament")->getInt()); + else if (this->config.option("sparse_infill_filament") == nullptr && this->get_object()->config.option("sparse_infill_filament") && + this->get_object()->config.option("sparse_infill_filament")->getInt() > 0) + volume_extruders.push_back(this->get_object()->config.option("sparse_infill_filament")->getInt()); + return volume_extruders; } @@ -3507,6 +3530,24 @@ void ModelVolume::set_text_configuration(const TextConfiguration text_configurat m_text_info.text_configuration = text_configuration; } +void ModelVolume::check_boldness_skew_min_max(float min_boldness, float max_boldness, float min_skew, float max_skew) +{ + float temp_custom_boldness = m_text_info.text_configuration.style.prop.boldness.value_or(0.f); + if (temp_custom_boldness > max_boldness) { + m_text_info.text_configuration.style.prop.boldness = 0.f; + } else if (temp_custom_boldness < min_boldness) { + m_text_info.text_configuration.style.prop.boldness = 0.f; + } + + float temp_custom_skew = m_text_info.text_configuration.style.prop.skew.value_or(0.f); + if (temp_custom_skew > max_skew) { + m_text_info.text_configuration.style.prop.skew = 0.f; + } + else if(temp_custom_skew < min_skew) { + m_text_info.text_configuration.style.prop.skew = 0.f; + } +} + const Transform3d &ModelVolume::get_matrix(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index fa11abd..e4a2618 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -1066,7 +1066,8 @@ public: void set_text_info(const TextInfo& text_info) { m_text_info = text_info; } void clear_text_info() { m_text_info.m_text = ""; } const TextInfo& get_text_info() const { return m_text_info; } - bool is_text() const { return !m_text_info.m_text.empty(); } + void check_boldness_skew_min_max(float min_boldness, float max_boldness, float min_skew, float max_skew); + bool is_text() const{ return !m_text_info.m_text.empty(); } const Transform3d &get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const; void set_new_unique_id() { ObjectBase::set_new_unique_id(); diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 350f786..e16592b 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1495,7 +1495,7 @@ static inline std::vector> mmu_segmentation_top_and_bott out.extrusion_width = std::max(out.extrusion_width, outer_wall_line_width); 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 ? + out.small_region_threshold = config.gap_infill_speed.get_at(print_object.print()->get_config_idx_for_filament(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. diff --git a/src/libslic3r/MultiNozzleUtils.cpp b/src/libslic3r/MultiNozzleUtils.cpp new file mode 100644 index 0000000..9d945cb --- /dev/null +++ b/src/libslic3r/MultiNozzleUtils.cpp @@ -0,0 +1,487 @@ +#include "MultiNozzleUtils.hpp" +#include "ProjectTask.hpp" +#include "Utils.hpp" + +namespace Slic3r { namespace MultiNozzleUtils { + +std::vector build_nozzle_list(std::vector nozzle_groups) +{ + std::vector ret; + std::sort(nozzle_groups.begin(), nozzle_groups.end()); + int nozzle_id = 0; + for (auto& group : nozzle_groups) { + for (int i = 0; i < group.nozzle_count; ++i) { + NozzleInfo tmp; + tmp.diameter = group.diameter; + tmp.extruder_id = group.extruder_id; + tmp.volume_type = group.volume_type; + tmp.group_id = nozzle_id++; + ret.emplace_back(std::move(tmp)); + } + } + return ret; +} + +std::vector build_nozzle_list(double diameter, const std::vector& filament_nozzle_map, const std::vector& filament_volume_map, const std::vector& filament_map) +{ + std::string diameter_str = format_diameter_to_str(diameter); + std::map> nozzle_to_filaments; + for(size_t idx = 0; idx < filament_nozzle_map.size(); ++idx){ + int nozzle_id = filament_nozzle_map[idx]; + nozzle_to_filaments[nozzle_id].emplace_back(static_cast(idx)); + } + std::vector ret; + for(auto& elem : nozzle_to_filaments){ + int nozzle_id = elem.first; + auto& filaments = elem.second; + NozzleInfo info; + info.diameter = diameter_str; + info.group_id = nozzle_id; + info.extruder_id = filament_map[filaments.front()]; + info.volume_type = NozzleVolumeType(filament_volume_map[filaments.front()]); + ret.emplace_back(std::move(info)); + } + return ret; +} + + +MultiNozzleGroupResult::MultiNozzleGroupResult(const std::vector &filament_nozzle_map, const std::vector &nozzle_list, const std::vector& used_filaments_) +{ + filament_map = filament_nozzle_map; + used_filaments = used_filaments_; + filament_to_nozzle.resize(filament_nozzle_map.size()); + for (size_t filament_idx = 0; filament_idx < filament_nozzle_map.size(); ++filament_idx) { + int nozzle_id = filament_nozzle_map[filament_idx]; + const NozzleInfo &nozzle = nozzle_list[nozzle_id]; + int extruder_id = nozzle.extruder_id; + extruder_to_filament_nozzles[extruder_id][filament_idx] = nozzle; + filament_to_nozzle[filament_idx] = nozzle; + filament_map[filament_idx] = extruder_id; + } +} + +std::optional MultiNozzleGroupResult::init_from_slice_filament(const std::vector& filament_map, const std::vector& filament_info) +{ + if (filament_map.empty()) + return std::nullopt; + + std::map nozzle_list_map; + std::vector filament_nozzle_map = filament_map; + std::map> group_ids_in_extruder; + std::vector used_filaments; + + auto volume_type_str_to_enum = ConfigOptionEnum::get_enum_values(); + + // used filaments + for (size_t idx = 0; idx < filament_info.size(); ++idx) { + int nozzle_idx = filament_info[idx].group_id; + int filament_idx = filament_info[idx].id; + int extruder_idx = filament_map[filament_idx] - 1; // 0 based idx + double diameter = filament_info[idx].nozzle_diameter; + std::string volume_type = filament_info[idx].nozzle_volume_type; + if (nozzle_idx == -1) return std::nullopt; // Nozzle group id is not set, return empty optional + + used_filaments.emplace_back(filament_idx); + group_ids_in_extruder[extruder_idx].emplace_back(nozzle_idx); + + NozzleInfo nozzle; + nozzle.diameter = format_diameter_to_str(diameter); + nozzle.group_id = nozzle_idx; + nozzle.extruder_id = extruder_idx; + nozzle.volume_type = NozzleVolumeType(volume_type_str_to_enum[volume_type]); + if (!nozzle_list_map.count(nozzle_idx)) nozzle_list_map[nozzle_idx] = nozzle; + + filament_nozzle_map[filament_idx] = nozzle_idx; + } + + // handle unused filaments to build some unused nozzles + for (size_t idx = 0; idx < filament_map.size(); ++idx) { + auto iter = std::find_if(filament_info.begin(), filament_info.end(), [idx](const FilamentInfo& info) { + return info.id == static_cast(idx); + }); + if (iter != filament_info.end()) + continue; + + int extruder_idx = filament_map[idx] - 1; + if (group_ids_in_extruder.count(extruder_idx)) { + // reuse one nozzle in current extruder + filament_nozzle_map[idx] = group_ids_in_extruder[extruder_idx].front(); + } + else { + // create a new nozzle + int max_nozzle_idx = 0; + for (auto& nozzle_groups : group_ids_in_extruder) { + for (auto group_id : nozzle_groups.second) + max_nozzle_idx = std::max(max_nozzle_idx, group_id); + } + + NozzleInfo nozzle; + nozzle.diameter = nozzle_list_map[max_nozzle_idx].diameter; + nozzle.volume_type = nozzle_list_map[max_nozzle_idx].volume_type; + nozzle.group_id = max_nozzle_idx + 1; + nozzle.extruder_id = extruder_idx; + + nozzle_list_map[max_nozzle_idx + 1] = nozzle; + + filament_nozzle_map[idx] = max_nozzle_idx + 1; + } + + } + + + std::vector new_filament_nozzle_map = filament_nozzle_map; + std::vector nozzle_list_vec; + + // reset group id for nozzles + for (auto &elem : nozzle_list_map) { + int nozzle_id = elem.first; + auto nozzle_info = elem.second; + nozzle_info.group_id = nozzle_list_vec.size(); + nozzle_list_vec.emplace_back(nozzle_info); + for (size_t idx = 0; idx < filament_nozzle_map.size(); ++idx) { + if (filament_nozzle_map[idx] == nozzle_id) new_filament_nozzle_map[idx] = nozzle_list_vec.size() - 1; + } + } + + if(new_filament_nozzle_map.empty() || nozzle_list_vec.empty()) + return std::nullopt; + + return MultiNozzleGroupResult(new_filament_nozzle_map, nozzle_list_vec, used_filaments); +} + +std::optional MultiNozzleGroupResult::init_from_cli_config(const std::vector& used_filaments,const std::vector& filament_map, const std::vector& filament_volume_map, const std::vector& filament_nozzle_map, const std::vector>& nozzle_count, float diameter) +{ + std::vector nozzle_groups; + for(size_t extruder_id = 0; extruder_id < nozzle_count.size(); ++extruder_id){ + for (auto elem : nozzle_count[extruder_id]) { + MultiNozzleUtils::NozzleGroupInfo group_info; + group_info.diameter = format_diameter_to_str(diameter); + group_info.volume_type = elem.first; + group_info.nozzle_count = elem.second; + group_info.extruder_id = extruder_id; + nozzle_groups.emplace_back(group_info); + } + } + + auto nozzle_list = build_nozzle_list(nozzle_groups); + std::vector used_nozzle(nozzle_list.size(),false); + + std::map input_nozzle_id_to_output; + + std::vector output_nozzle_map(filament_nozzle_map.size(),0); + + for(auto filament_idx : used_filaments){ + NozzleVolumeType req_type = NozzleVolumeType(filament_volume_map[filament_idx]); + int req_extruder = filament_map[filament_idx]; + int input_nozzle_idx = filament_nozzle_map[filament_idx]; + + if(input_nozzle_id_to_output.find(input_nozzle_idx) != input_nozzle_id_to_output.end()){ + output_nozzle_map[filament_idx] = input_nozzle_id_to_output[input_nozzle_idx]; + continue; + } + + int output_nozzle_idx = -1; + for(size_t nozzle_idx = 0; nozzle_idx < nozzle_list.size(); ++nozzle_idx){ + if(used_nozzle[nozzle_idx]) + continue; + auto& nozzle_info = nozzle_list[nozzle_idx]; + if(!(nozzle_info.extruder_id == req_extruder&& nozzle_info.volume_type == req_type)) + continue; + + output_nozzle_idx = nozzle_idx; + input_nozzle_id_to_output[input_nozzle_idx] = output_nozzle_idx; + used_nozzle[nozzle_idx] = true; + break; + } + + if(output_nozzle_idx == -1){ + return std::nullopt; + } + output_nozzle_map[filament_idx] = output_nozzle_idx; + } + + return MultiNozzleGroupResult(output_nozzle_map,nozzle_list,used_filaments); +} + +int MultiNozzleGroupResult::get_extruder_id(int filament_id) const +{ + for (auto &elem : extruder_to_filament_nozzles) { + auto &filament_to_nozzle = elem.second; + int extruder_id = elem.first; + if (filament_to_nozzle.find(filament_id) == filament_to_nozzle.end()) continue; + return extruder_id; + } + return -1; +} + +std::optional MultiNozzleGroupResult::get_nozzle_for_filament(int filament_id) const +{ + for (auto &elem : extruder_to_filament_nozzles) { + auto &filament_to_nozzle = elem.second; + int extruder_id = elem.first; + auto iter = filament_to_nozzle.find(filament_id); + if (iter == filament_to_nozzle.end()) continue; + return iter->second; + } + return std::nullopt; +} + +bool MultiNozzleGroupResult::are_filaments_same_extruder(int filament_id1, int filament_id2) const +{ + int extruder_id1 = get_extruder_id(filament_id1); + int extruder_id2 = get_extruder_id(filament_id2); + + if (extruder_id1 == -1 || extruder_id2 == -1) return false; + + return extruder_id1 == extruder_id2; +} + +bool MultiNozzleGroupResult::are_filaments_same_nozzle(int filament_id1, int filament_id2) const +{ + std::optional nozzle_info1 = get_nozzle_for_filament(filament_id1); + std::optional nozzle_info2 = get_nozzle_for_filament(filament_id2); + if (!nozzle_info1 || !nozzle_info2) return false; + + return nozzle_info1->group_id == nozzle_info2->group_id; +} + +int MultiNozzleGroupResult::get_extruder_count() const { return static_cast(extruder_to_filament_nozzles.size()); } + +int MultiNozzleGroupResult::get_nozzle_count(int target_extruder_id) const +{ + std::set nozzles; + for (auto &elem : extruder_to_filament_nozzles) { + auto &filament_to_nozzle = elem.second; + int extruder_id = elem.first; + + if (target_extruder_id == -1 || extruder_id == target_extruder_id) { + for (auto &filament_nozzle : filament_to_nozzle) { nozzles.insert(filament_nozzle.second.group_id); } + } + } + return static_cast(nozzles.size()); +} + +std::vector MultiNozzleGroupResult::get_nozzle_vec(int target_extruder_id) const +{ + std::set nozzles; + std::vector nozzleinfo_vec; + for (auto& elem : extruder_to_filament_nozzles) { + auto& filament_to_nozzle = elem.second; + int extruder_id = elem.first; + + if (target_extruder_id == -1 || extruder_id == target_extruder_id) { + for (auto& filament_nozzle : filament_to_nozzle) { + int filament_id = filament_nozzle.first; + if(std::find(used_filaments.begin(), used_filaments.end(), filament_id) == used_filaments.end()) + continue; + if (nozzles.count(filament_nozzle.second.group_id) == 0) { + nozzles.insert(filament_nozzle.second.group_id); + nozzleinfo_vec.push_back(filament_nozzle.second); + } + } + } + } + return nozzleinfo_vec; +} + +std::vector MultiNozzleGroupResult::get_used_nozzles(const std::vector &filament_list, int target_extruder_id) const +{ + std::vector result; + for (auto filament : filament_list) { + int filament_idx = static_cast(filament); + if (filament_to_nozzle[filament_idx].extruder_id == target_extruder_id) { + result.emplace_back(filament_to_nozzle[filament_idx]); + } + } + return result; +} + +std::pair MultiNozzleGroupResult::get_used_extruders_nozzles_count(const std::vector &filament_list) const +{ + std::pair result; + std::vector mask_extruder(64,0); + std::vector mask_nozzle(64, 0); + int extruder_count = 0, nozzle_count = 0; + for (auto filament : filament_list) { + int filament_idx = static_cast(filament); + auto &nozzle = filament_to_nozzle[filament_idx]; + + extruder_count += (mask_extruder[nozzle.extruder_id] == 0); + nozzle_count += (mask_nozzle[nozzle.group_id] == 0); + mask_extruder[nozzle.extruder_id] = 1; + mask_nozzle[nozzle.group_id] = 1; + } + return {extruder_count, nozzle_count}; +} + +std::vector MultiNozzleGroupResult::get_used_extruders(const std::vector &filament_list) const +{ + std::set used_extruders; + for (auto filament : filament_list) { + int filament_idx = static_cast(filament); + int extruder_id = get_extruder_id(filament_idx); + if (extruder_id == -1) continue; + used_extruders.insert(extruder_id); + if (used_extruders.size() == extruder_to_filament_nozzles.size()) break; + } + return std::vector(used_extruders.begin(), used_extruders.end()); +} + +std::vector MultiNozzleGroupResult::get_extruder_list() const +{ + std::set extruder_list; + for (auto &elem : extruder_to_filament_nozzles) { extruder_list.insert(elem.first); } + return std::vector(extruder_list.begin(), extruder_list.end()); +} + +bool NozzleStatusRecorder::is_nozzle_empty(int nozzle_id) const +{ + auto iter = nozzle_filament_status.find(nozzle_id); + if (iter == nozzle_filament_status.end()) return true; + return false; +} + +int NozzleStatusRecorder::get_filament_in_nozzle(int nozzle_id) const +{ + auto iter = nozzle_filament_status.find(nozzle_id); + if (iter == nozzle_filament_status.end()) return -1; + return iter->second; +} + + +void NozzleStatusRecorder::set_nozzle_status(int nozzle_id, int filament_id) +{ + nozzle_filament_status[nozzle_id] = filament_id; +} + +void NozzleStatusRecorder::clear_nozzle_status(int nozzle_id) +{ + auto iter = nozzle_filament_status.find(nozzle_id); + if (iter == nozzle_filament_status.end()) return; + nozzle_filament_status.erase(iter); +} + +std::string NozzleGroupInfo::serialize() const +{ + std::ostringstream oss; + oss << extruder_id << "-" + << std::setprecision(2) << diameter << "-" + << get_nozzle_volume_type_string(volume_type)<<"-" + << nozzle_count; + return oss.str(); +} + +std::optional NozzleGroupInfo::deserialize(const std::string &str) +{ + std::istringstream iss(str); + std::string token; + std::vector tokens; + + while (std::getline(iss, token, '-')) { tokens.push_back(token); } + + if (tokens.size() != 4) { return std::nullopt; } + + try { + int extruder_id = std::stoi(tokens[0]); + std::string diameter = tokens[1]; + NozzleVolumeType volume_type = NozzleVolumeType(ConfigOptionEnum::get_enum_values().at(tokens[2])); + int nozzle_count = std::stoi(tokens[3]); + + return NozzleGroupInfo(diameter, volume_type, extruder_id, nozzle_count); + } catch (const std::exception &) { + return std::nullopt; + } +} + +std::vector MultiNozzleGroupResult::get_extruder_map(bool zero_based) const +{ + if(zero_based) + return filament_map; + + auto new_filament_map = filament_map; + std::transform(new_filament_map.begin(), new_filament_map.end(), new_filament_map.begin(), [this](int val) { return val + 1; }); + return new_filament_map; +} + +std::vector MultiNozzleGroupResult::get_nozzle_map() const +{ + std::vector nozzle_map(filament_map.size()); + for (size_t idx = 0; idx < filament_to_nozzle.size(); ++idx) + nozzle_map[idx] = filament_to_nozzle[idx].group_id; + return nozzle_map; +} + +std::vector MultiNozzleGroupResult::get_volume_map() const +{ + std::vector volume_map(filament_map.size()); + for (size_t idx = 0; idx < filament_to_nozzle.size(); ++idx) + volume_map[idx] = filament_to_nozzle[idx].volume_type; + return volume_map; +} + + +int MultiNozzleGroupResult::get_config_idx_for_filament(int filament_idx, const PrintConfig& config) +{ + if(auto iter=config_idx_map.find(&config); iter != config_idx_map.end()){ + return iter->second[filament_idx]; + } + + auto print_extruder_varint = config.printer_extruder_variant.values; + auto print_extruder_id = config.printer_extruder_id.values; + std::vector extruder_type_list; + for (size_t idx = 0; idx < config.extruder_type.size(); ++idx) + extruder_type_list.emplace_back(ExtruderType(config.extruder_type.values[idx])); + + std::vector config_index_vec(filament_map.size()); + for(size_t idx = 0; idx < config_index_vec.size(); ++idx){ + int extruder_id = filament_to_nozzle[idx].extruder_id; + NozzleVolumeType volume_type = filament_to_nozzle[idx].volume_type; + ExtruderType extruder_type = extruder_type_list[extruder_id]; + + std::string variant = get_extruder_variant_string(extruder_type, volume_type); + + int target_index = 0; + for (size_t j = 0; j < print_extruder_id.size(); ++j) { + if (print_extruder_id[j] == extruder_id && variant == print_extruder_varint[j]) { + target_index = j; + break; + } + } + + config_index_vec[idx] = target_index; + } + config_idx_map[&config] = config_index_vec; + return config_index_vec[filament_idx]; + +} + +int MultiNozzleGroupResult::estimate_seq_flush_weight(const std::vector>>& flush_matrix, const std::vector& filament_change_seq) const +{ + auto get_weight_from_volume = [](float volume){ + return static_cast(volume * 1.26 * 0.01); + }; + + float total_flush_volume = 0; + MultiNozzleUtils::NozzleStatusRecorder recorder; + for(auto filament: filament_change_seq){ + auto nozzle = get_nozzle_for_filament(filament); + if(!nozzle) + continue; + + int extruder_id = nozzle->extruder_id; + int nozzle_id = nozzle->group_id; + int last_filament = recorder.get_filament_in_nozzle(nozzle_id); + + if(last_filament!= -1 && last_filament != filament){ + float flush_volume = flush_matrix[extruder_id][last_filament][filament]; + total_flush_volume += flush_volume; + } + recorder.set_nozzle_status(nozzle_id, filament); + } + + return get_weight_from_volume(total_flush_volume); +} + + + +}} // namespace Slic3r::MultiNozzleUtils \ No newline at end of file diff --git a/src/libslic3r/MultiNozzleUtils.hpp b/src/libslic3r/MultiNozzleUtils.hpp new file mode 100644 index 0000000..b5fbf21 --- /dev/null +++ b/src/libslic3r/MultiNozzleUtils.hpp @@ -0,0 +1,135 @@ +#ifndef MULTI_NOZZLE_UTILS_HPP +#define MULTI_NOZZLE_UTILS_HPP + +#include +#include +#include +#include +#include "PrintConfig.hpp" + +namespace Slic3r { +struct FilamentInfo; +namespace MultiNozzleUtils { +// 瀛樺偍鍗曚釜鍠峰槾鐨勪俊鎭 +struct NozzleInfo +{ + std::string diameter; + NozzleVolumeType volume_type; + int extruder_id{ -1 }; // 閫昏緫鎸ゅ嚭鏈篿d + int group_id{ -1 }; // 瀵瑰簲閫昏緫鍠峰槾id, 鏃犲疄闄呮剰涔 +}; + +// 鍠峰槾缁勪俊鎭紝鎵ц鍚屾鎿嶄綔鍚庡墠绔紶閫掔粰鍚庣鐨勬暟鎹 +struct NozzleGroupInfo +{ + std::string diameter; + NozzleVolumeType volume_type; + int extruder_id; + int nozzle_count; + + NozzleGroupInfo() = default; + + NozzleGroupInfo(const std::string& nozzle_diameter_, const NozzleVolumeType volume_type_, const int extruder_id_, const int nozzle_count_) + : diameter(nozzle_diameter_), volume_type(volume_type_), extruder_id(extruder_id_), nozzle_count(nozzle_count_) + {} + + inline bool operator<(const NozzleGroupInfo &rhs) const + { + if (extruder_id != rhs.extruder_id) return extruder_id < rhs.extruder_id; + if (diameter != rhs.diameter) return diameter < rhs.diameter; + if (volume_type != rhs.volume_type) return volume_type < rhs.volume_type; + return nozzle_count < rhs.nozzle_count; + } + + bool is_same_type(const NozzleGroupInfo &rhs) const + { + return diameter == rhs.diameter && volume_type == rhs.volume_type && extruder_id == rhs.extruder_id; + } + + inline bool operator==(const NozzleGroupInfo &rhs) const + { + return diameter == rhs.diameter && volume_type == rhs.volume_type && extruder_id == rhs.extruder_id && nozzle_count == rhs.nozzle_count; + } + + std::string serialize() const; + static std::optional deserialize(const std::string& str); +}; + +// 鍒嗙粍鍚庣殑缁撴灉锛孏CodeProcessor锛屽彂璧锋墦鍗伴〉闈㈤渶瑕 +class MultiNozzleGroupResult +{ +private: + std::unordered_map> extruder_to_filament_nozzles; + std::vector filament_to_nozzle; + std::vector used_filaments; + std::vector filament_map; // extruder map +public: + MultiNozzleGroupResult() = default; + MultiNozzleGroupResult(const std::vector &filament_nozzle_map, const std::vector &nozzle_list, const std::vector& used_filament); + static std::optional init_from_slice_filament(const std::vector &filament_map, + const std::vector &filament_info); // 1 based filament_map + + static std::optional init_from_cli_config(const std::vector& used_filaments, + const std::vector& filament_map, + const std::vector& filament_volume_map, + const std::vector& filament_nozzle_map, + const std::vector>& nozzle_count, + float diameter); + + bool are_filaments_same_extruder(int filament_id1, int filament_id2) const; // 鍒ゆ柇涓や釜鏉愭枡鏄惁澶勪簬鍚屼竴涓尋鍑烘満 + bool are_filaments_same_nozzle(int filament_id1, int filament_id2) const; // 鍒ゆ柇涓や釜鏉愭枡鏄惁澶勪簬鍚屼竴涓柗鍢 + int get_extruder_count() const; // 鑾峰彇鎸ゅ嚭鏈烘暟閲 + + std::vector get_used_nozzles(const std::vector &filament_list, int target_extruder_id = -1) const; // 鑾峰彇缁欏畾鏉愭枡鍒楄〃涓嬫寚瀹氭尋鍑烘満浣跨敤鐨勫柗鍢 + std::vector get_used_extruders(const std::vector &filament_list) const; // 鑾峰彇浣跨敤鐨勬尋鍑烘満鍒楄〃 + std::pair get_used_extruders_nozzles_count(const std::vector &filament_list) const; // 鑾峰彇缁欏畾鏉愭枡鍒楄〃涓嬩娇鐢ㄧ殑鎸ゅ嚭鏈猴紝鍙婂搴旂殑鍠峰槾 + std::vector get_extruder_list() const; + + std::vector get_extruder_map(bool zero_based = true) const; + std::vector get_nozzle_map() const; + std::vector get_volume_map() const; + + + int get_config_idx_for_filament(int filament_idx, const PrintConfig& config); + + /** + * @brief 棰勪及缁欏畾搴忓垪鐨勫啿鍒烽噸閲 + * + * @param flush_matrix 鎹㈡尋鍑烘満鐨勭煩闃碉紝鎸ゅ嚭鏈-璧峰鑰楁潗-缁撴潫鑰楁潗 + * @param filament_change_seq 鎹㈡枡搴忓垪 + * @return int 鍐插埛閲嶉噺 + */ + int estimate_seq_flush_weight(const std::vector>>& flush_matrix, const std::vector& filament_change_seq) const; + +public: + int get_extruder_id(int filament_id) const; // 鏍规嵁鏉愭枡id鍙栭昏緫鎸ゅ嚭鏈篿d + int get_nozzle_count(int extruder_id = -1) const; // 鑾峰彇鎸囧畾鎸ゅ嚭鏈轰笅鐨勪娇鐢ㄧ殑鍠峰槾鏁伴噺锛-1琛ㄧず鎵鏈夋尋鍑烘満鐨勫柗鍢存暟閲忔诲拰 + std::vector get_nozzle_vec(int extruder_id = -1) const; // 鑾峰彇鎸囧畾鎸ゅ嚭鏈轰笅鐨勪娇鐢ㄧ殑鍠峰槾鍒楄〃锛-1琛ㄧず鎵鏈夋尋鍑烘満鐨勫柗鍢 + std::optional get_nozzle_for_filament(int filament_id) const; + + std::unordered_map> config_idx_map; +}; + +class NozzleStatusRecorder +{ +private: + std::unordered_map nozzle_filament_status; + +public: + NozzleStatusRecorder() = default; + bool is_nozzle_empty(int nozzle_id) const; + int get_filament_in_nozzle(int nozzle_id) const; + + void clear_nozzle_status(int nozzle_id); + void set_nozzle_status(int nozzle_id, int filament_id); +}; + + +std::vector build_nozzle_list(std::vector info); +std::vector build_nozzle_list(double diameter,const std::vector& filament_nozzle_map, const std::vector& filament_volume_map, const std::vector& filament_map); + + +} // namespace MultiNozzleUtils +} // namespace Slic3r + +#endif \ No newline at end of file diff --git a/src/libslic3r/ObjColorUtils.hpp b/src/libslic3r/ObjColorUtils.hpp index 5bb4563..f53c748 100644 --- a/src/libslic3r/ObjColorUtils.hpp +++ b/src/libslic3r/ObjColorUtils.hpp @@ -1,14 +1,23 @@ #pragma once #include #include +#include #include "opencv2/opencv.hpp" #include "libslic3r/Color.hpp" class QuantKMeans { + struct ClusterInfo + { + int idx; + int count; + cv::Vec3b center; + }; + public: int m_alpha_thres; + bool m_do_convert; cv::Mat m_flatten_labels; cv::Mat m_centers8UC3; QuantKMeans(int alpha_thres = 10) : m_alpha_thres(alpha_thres) {} @@ -28,8 +37,7 @@ public: cv::Mat image8UC3; convert_color_space(flatten_image8UC3, image8UC3, color_space); cv::Mat image32FC3(image8UC3.rows, 1, CV_32FC3); - for (int i = 0; i < image8UC3.rows; i++) - image32FC3.at(i, 0) = image8UC3.at(i, 0); + for (int i = 0; i < image8UC3.rows; i++) image32FC3.at(i, 0) = image8UC3.at(i, 0); apply(image32FC3, num_cluster, color_space); repalce_centers_aplha(ori_image, new_image); @@ -65,58 +73,105 @@ public: int color_space = 2) { cv::Mat image8UC3; + this->m_do_convert = true; convert_color_space(flatten_image8UC3, image8UC3, color_space); cv::Mat image32FC3(image8UC3.rows, 1, CV_32FC3); - for (int i = 0; i < image8UC3.rows; i++) - image32FC3.at(i, 0) = image8UC3.at(i, 0); + for (int i = 0; i < image8UC3.rows; i++) image32FC3.at(i, 0) = image8UC3.at(i, 0); - int best_cluster = 1; - double cur_score = 0, best_score = 100; - num_cluster = fmin(num_cluster, max_cluster); - if (num_cluster < 1) { - if (!this->more_than_request(image8UC3, max_cluster)) max_cluster = compute_num_colors(image8UC3); - num_cluster = fmin(num_cluster, max_cluster); - cur_score = cv::kmeans(image32FC3, 1, this->m_flatten_labels, cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 300, 0.5), 3, cv::KMEANS_PP_CENTERS); - best_score = cur_score; - - for (int cur_cluster = 2; cur_cluster < max_cluster + 1; cur_cluster++) { - cv::Mat centers32FC3; - cur_score = cv::kmeans(image32FC3, cur_cluster, this->m_flatten_labels, cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 300, 0.5), 3, - cv::KMEANS_PP_CENTERS, centers32FC3); - if (this->repeat_center(cur_cluster, centers32FC3, color_space)) - break; - best_cluster = cur_score < best_score ? cur_cluster : best_cluster; - best_score = cur_score < best_score ? cur_score : best_score; - } - } else if (this->more_than_request(image8UC3, num_cluster)) - best_cluster = num_cluster; - else { - best_cluster = compute_num_colors(image8UC3); - std::cout << "num of image color is " << best_cluster << ", less than custom number " << num_cluster << std::endl; - } + int best_cluster = 1; + int real_num_cluster = num_cluster < 1 ? max_cluster : fmin(num_cluster, max_cluster); cv::Mat centers32FC3; - cv::kmeans(image32FC3, best_cluster, this->m_flatten_labels, cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 300, 0.5), 3, cv::KMEANS_PP_CENTERS, - centers32FC3); - this->m_centers8UC3 = cv::Mat(best_cluster, 1, CV_8UC3); - for (int i = 0; i < best_cluster; i++) { + if (!this->more_than_request(flatten_image8UC3, real_num_cluster, -1)) { + real_num_cluster = compute_num_colors(flatten_image8UC3); + this->m_do_convert = false; + apply_color_cluster_image(flatten_image8UC3, centers32FC3); + } else if (!this->more_than_request(image8UC3, real_num_cluster, -1)) { + real_num_cluster = compute_num_colors(image8UC3); + apply_color_cluster_image(image8UC3, centers32FC3); + } else { + apply_color_cluster_kmeans(image32FC3, real_num_cluster, centers32FC3); + } + + this->m_centers8UC3 = cv::Mat(real_num_cluster, 1, CV_8UC3); + for (int i = 0; i < real_num_cluster; i++) { auto center = centers32FC3.row(i); this->m_centers8UC3.at(i) = {uchar(center.at(0)), uchar(center.at(1)), uchar(center.at(2))}; } - convert_color_space(this->m_centers8UC3, this->m_centers8UC3, color_space, true); + + if (this->m_do_convert) convert_color_space(this->m_centers8UC3, this->m_centers8UC3, color_space, true); + sort_center_and_relabel(); cluster_results.clear(); labels.clear(); - for (int i = 0; i < flatten_image8UC3.rows; i++) - labels.emplace_back(this->m_flatten_labels.at(i, 0)); - for (int i = 0; i < best_cluster; i++) { + for (int i = 0; i < flatten_image8UC3.rows; i++) labels.emplace_back(this->m_flatten_labels.at(i, 0)); + for (int i = 0; i < real_num_cluster; i++) { cv::Vec3f center = this->m_centers8UC3.at(i, 0); cluster_results.emplace_back(std::array{center[0] / 255.f, center[1] / 255.f, center[2] / 255.f, 1.f}); } } - bool more_than_request(const cv::Mat &image8UC3, int target_num) + void apply_color_cluster_kmeans(cv::Mat &image32FC3, int num_cluster, cv::Mat ¢er32FC3) + { + uint64 old_state = cv::theRNG().state; + cv::theRNG().state = 12345; + cv::kmeans(image32FC3, num_cluster, this->m_flatten_labels, cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 300, 0.5), 3, cv::KMEANS_PP_CENTERS, + center32FC3); + cv::theRNG().state = old_state; + } + + void apply_color_cluster_image(const cv::Mat &image8UC3, cv::Mat ¢er32FC3) + { + int find_index; + std::vector uniqueImage; + cv::Vec3b cur_color; + this->m_flatten_labels = cv::Mat(image8UC3.rows, 1, CV_32S); + for (int i = 0; i < image8UC3.rows; i++) { + cur_color = image8UC3.at(i, 0); + find_index = find_color_index(cur_color, uniqueImage); + if (find_index == -1) { + find_index = uniqueImage.size(); + uniqueImage.emplace_back(cur_color); + } + this->m_flatten_labels.at(i, 0) = find_index; + } + cv::Mat un8U(uniqueImage.size(), 1, CV_8UC3, uniqueImage.data()); + un8U.convertTo(center32FC3, CV_32FC3); + } + + void sort_center_and_relabel() + { + int K = this->m_centers8UC3.rows; + int nums = this->m_flatten_labels.rows; + + std::vector counts(K, 0); + for (int i = 0; i < K; i++) counts[this->m_flatten_labels.at(i, 0)]++; + + std::vector info(K); + for (int i = 0; i < K; i++) info[i] = {i, counts[i], this->m_centers8UC3.at(i, 0)}; + + std::sort(info.begin(), info.end(), [](const ClusterInfo &a, const ClusterInfo &b) { + if (a.count != b.count) return a.count > b.count; + if (a.center[0] != b.center[0]) return a.center[0] < b.center[0]; + if (a.center[1] != b.center[1]) return a.center[1] < b.center[1]; + return a.center[2] < b.center[2]; + }); + + std::vector remap(K); + for (int new_label = 0; new_label < K; new_label++) remap[info[new_label].idx] = new_label; + + cv::Mat new_centers(K, 1, CV_8UC3); + for (int i = 0; i < K; i++) new_centers.at(i, 0) = info[i].center; + this->m_centers8UC3 = new_centers.clone(); + + for (int i = 0; i < nums; i++) { + int old_label = this->m_flatten_labels.at(i, 0); + this->m_flatten_labels.at(i, 0) = remap[old_label]; + } + } + + bool more_than_request(const cv::Mat &image8UC3, int target_num, int degree = 1) { std::vector uniqueImage; cv::Vec3b cur_color; @@ -124,7 +179,8 @@ public: cur_color = image8UC3.at(i, 0); if (!is_in(cur_color, uniqueImage)) { uniqueImage.emplace_back(cur_color); - if (uniqueImage.size() >= target_num) return true; + if (degree > 0 && uniqueImage.size() >= target_num) return true; + if (degree < 0 && uniqueImage.size() > target_num) return true; } } return false; @@ -142,6 +198,14 @@ public: return uniqueImage.size(); } + int find_color_index(const cv::Vec3b &cur_color, const std::vector &uniqueImage) + { + for (int i = 0; i < uniqueImage.size(); i++) { + if (cur_color[0] == uniqueImage[i][0] && cur_color[1] == uniqueImage[i][1] && cur_color[2] == uniqueImage[i][2]) return i; + } + return -1; + } + bool is_in(const cv::Vec3b &cur_color, const std::vector &uniqueImage) { for (auto &color : uniqueImage) @@ -153,7 +217,7 @@ public: { cv::Mat centers8UC3 = cv::Mat(cur_cluster, 1, CV_8UC3); for (int i = 0; i < cur_cluster; i++) { - auto center = centers32FC3.row(i); + auto center = centers32FC3.row(i); centers8UC3.at(i) = {uchar(center.at(0)), uchar(center.at(1)), uchar(center.at(2))}; } convert_color_space(centers8UC3, centers8UC3, color_space, true); @@ -204,15 +268,15 @@ public: case 0: image = ori_image; break; case 1: if (reverse) - cvtColor(ori_image, image, cv::COLOR_HSV2BGR); + cvtColor(ori_image, image, cv::COLOR_HSV2RGB); else - cvtColor(ori_image, image, cv::COLOR_BGR2HSV); + cvtColor(ori_image, image, cv::COLOR_RGB2HSV); break; case 2: if (reverse) - cvtColor(ori_image, image, cv::COLOR_Lab2BGR); + cvtColor(ori_image, image, cv::COLOR_Lab2RGB); else - cvtColor(ori_image, image, cv::COLOR_BGR2Lab); + cvtColor(ori_image, image, cv::COLOR_RGB2Lab); break; default: break; } @@ -262,9 +326,8 @@ public: } }; -//1.9.5 bool obj_color_deal_algo(std::vector &input_colors, - std::vector& cluster_colors_from_algo, - std::vector& cluster_labels_from_algo, + std::vector &cluster_colors_from_algo, + std::vector & cluster_labels_from_algo, char & cluster_number, int max_cluster); \ No newline at end of file diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index aa52293..ab6ab99 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -873,7 +873,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.get_at(get_extruder_index(*print_config, this->config->wall_filament - 1)) > 0; + bool has_gap_fill = this->config->gap_infill_speed.get_at(get_config_idx_for_filament(*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)); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index e07a9ee..49ce8ac 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -919,8 +919,9 @@ 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", "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", "top_color_penetration_layers", "bottom_color_penetration_layers", + "infill_instead_top_bottom_surfaces", "smooth_speed_discontinuity_area","smooth_coefficient", "seam_position", "seam_placement_away_from_overhangs", - "wall_sequence", "is_infill_first", "sparse_infill_density", "sparse_infill_pattern", "sparse_infill_anchor", "sparse_infill_anchor_max", "top_surface_pattern", + "wall_sequence", "is_infill_first", "sparse_infill_density", "fill_multiline", "sparse_infill_pattern", "sparse_infill_anchor", "sparse_infill_anchor_max", "top_surface_pattern", "locked_skin_infill_pattern", "locked_skeleton_infill_pattern", "bottom_surface_pattern", "internal_solid_infill_pattern", "infill_direction", "bridge_angle", "infill_shift_step", "skeleton_infill_density", "infill_lock_depth", "skin_infill_depth", "skin_infill_density", "infill_rotate_step", @@ -975,14 +976,14 @@ static std::vector s_Preset_print_options { "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", "z_direction_outwall_speed_continuous", - "vertical_shell_speed","detect_floating_vertical_shell", "vertical_shell_speed","detect_floating_vertical_shell", "enable_wrapping_detection", // calib "print_flow_ratio", //Orca - "exclude_object", "override_filament_scarf_seam_setting", "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", "precise_outer_wall", - "interlocking_beam", "interlocking_orientation", "interlocking_beam_layer_count", "interlocking_depth", "interlocking_boundary_avoidance", "interlocking_beam_width" + "exclude_object", "override_filament_scarf_seam_setting", "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", "precise_outer_wall", + "interlocking_beam", "interlocking_orientation", "interlocking_beam_layer_count", "interlocking_depth", "interlocking_boundary_avoidance", "interlocking_beam_width", "embedding_wall_into_infill" //w16 ,"resonance_avoidance", "min_resonance_avoidance_speed", "max_resonance_avoidance_speed" //w13 @@ -992,7 +993,7 @@ static std::vector s_Preset_print_options { static std::vector s_Preset_filament_options{/*"filament_colour", */ "default_filament_colour", "required_nozzle_HRC", "filament_diameter", "volumetric_speed_coefficients", "filament_type", "filament_soluble", "filament_is_support", "filament_printable", "filament_scarf_seam_type", "filament_scarf_height", "filament_scarf_gap", "filament_scarf_length", - "filament_max_volumetric_speed", "impact_strength_z", "filament_ramming_volumetric_speed", "filament_adaptive_volumetric_speed", + "filament_max_volumetric_speed", "impact_strength_z", "filament_ramming_volumetric_speed","filament_ramming_volumetric_speed_nc", "filament_adaptive_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 @@ -1002,13 +1003,13 @@ static std::vector s_Preset_filament_options{/*"filament_colour", * "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", "no_slow_down_for_cooling_on_outwalls", "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", "first_x_layer_fan_speed", "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", "no_slow_down_for_cooling_on_outwalls", "fan_min_speed","filament_ramming_travel_time","filament_pre_cooling_temperature","filament_ramming_travel_time_nc","filament_pre_cooling_temperature_nc", + "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","first_x_layer_fan_speed", "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", // Retract overrides - "filament_retraction_length", "filament_z_hop", "filament_z_hop_types", "filament_retraction_speed", "filament_deretraction_speed", "filament_retract_restart_extra", "filament_retraction_minimum_travel", + "filament_retraction_length", "filament_z_hop", "filament_z_hop_types", "filament_retraction_speed", "filament_deretraction_speed", "filament_retract_length_nc", "filament_retract_restart_extra", "filament_retraction_minimum_travel", "filament_retract_when_changing_layer", "filament_wipe", "filament_retract_before_wipe", // Profile compatibility "filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits", @@ -1020,8 +1021,8 @@ static std::vector s_Preset_filament_options{/*"filament_colour", * "enable_pressure_advance", "pressure_advance", "chamber_temperatures","filament_notes", "filament_long_retractions_when_cut","filament_retraction_distances_when_cut","filament_shrink", "filament_velocity_adaptation_factor", //QDS filament change length while the extruder color - "filament_change_length","filament_prime_volume","filament_flush_volumetric_speed","filament_flush_temp", - "long_retractions_when_ec", "retraction_distances_when_ec", + "filament_change_length","filament_change_length_nc","filament_prime_volume","filament_prime_volume_nc","filament_flush_volumetric_speed","filament_flush_temp", + "long_retractions_when_ec", "retraction_distances_when_ec", "filament_cooling_before_tower", //w13 "additional_cooling_fan_speed_unseal" //y58 @@ -1046,8 +1047,8 @@ static std::vector s_Preset_printer_options { "default_print_profile", "inherits", "silent_mode", // QDS - "scan_first_layer", "wrapping_detection_layers", "wrapping_exclude_area", "machine_load_filament_time", "machine_unload_filament_time", "machine_pause_gcode", "template_custom_gcode", - "nozzle_type","auxiliary_fan", "fan_direction", "nozzle_volume","upward_compatible_machine", "z_hop_types","support_chamber_temp_control","support_air_filtration","printer_structure","thumbnail_size", + "scan_first_layer", "wrapping_detection_layers", "wrapping_exclude_area", "machine_load_filament_time", "machine_unload_filament_time", "machine_pause_gcode", "template_custom_gcode","machine_hotend_change_time", + "nozzle_type","auxiliary_fan", "fan_direction", "nozzle_volume","upward_compatible_machine", "z_hop_types","support_chamber_temp_control","support_air_filtration","support_cooling_filter","cooling_filter_enabled","auto_disable_filter_on_overheat","printer_structure","thumbnail_size", //w12 "thumbnails_formats", "best_object_pos", "head_wrap_detect_zone","printer_notes", @@ -1058,8 +1059,9 @@ static std::vector s_Preset_printer_options { "printhost_cafile","printhost_port","printhost_authorization_type", "printhost_user", "printhost_password", "printhost_ssl_ignore_revoke", "use_relative_e_distances", "extruder_type","use_firmware_retraction", - "grab_length","machine_switch_extruder_time","hotend_cooling_rate","hotend_heating_rate","enable_pre_heating", "support_object_skip_flush", "physical_extruder_map", - "bed_temperature_formula","machine_prepare_compensation_time", "nozzle_flush_dataset","apply_top_surface_compensation" + "grab_length","machine_switch_extruder_time","hotend_cooling_rate","hotend_heating_rate","enable_pre_heating", "support_object_skip_flush","physical_extruder_map", + "bed_temperature_formula","machine_prepare_compensation_time", "nozzle_flush_dataset","apply_top_surface_compensation", + "group_algo_with_time","extruder_max_nozzle_count" //w34 ,"support_multi_bed_types" //y58 @@ -2328,8 +2330,10 @@ std::pair PresetCollection::load_external_preset( //we can not reach here preset.save(nullptr); } - if (&this->get_selected_preset() == &preset) + if (&this->get_selected_preset() == &preset) { this->get_edited_preset().is_external = true; + this->get_edited_preset().is_project_embedded = preset.is_project_embedded; + } //QDS: add config related logs BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << boost::format(", type %1% added a preset, name %2%, is_system %3%, is_default %4%, is_external %5%")%Preset::get_type_string(m_type) %preset.name %preset.is_system %preset.is_default %preset.is_external; @@ -3212,7 +3216,8 @@ void PresetCollection::update_map_system_profile_renamed() void PresetCollection::set_custom_preset_alias(Preset &preset) { - if (m_type == Preset::Type::TYPE_FILAMENT && preset.config.has(QDT_JSON_KEY_INHERITS) && preset.config.option(QDT_JSON_KEY_INHERITS)->value.empty()) { + if (m_type == Preset::Type::TYPE_FILAMENT && preset.is_user() && preset.config.has(QDT_JSON_KEY_INHERITS) && + preset.config.option(QDT_JSON_KEY_INHERITS)->value.empty()) { std::string alias_name; std::string preset_name = preset.name; if (alias_name.empty()) { @@ -3272,6 +3277,26 @@ void PresetCollection::set_printer_hold_alias(const std::string &alias, Preset & } } +std::string PresetCollection::get_preset_alias(Preset &preset, bool force) +{ + if (!preset.alias.empty()) + return preset.alias; + else + set_custom_preset_alias(preset); + + if (!preset.alias.empty() || !force) + return preset.alias; + + std::string alias_name; + std::string preset_name = preset.name; + size_t end_pos = preset_name.find_first_of("@"); + if (end_pos != std::string::npos) { + alias_name = preset_name.substr(0, end_pos); + boost::trim_right(alias_name); + } + return alias_name; +} + std::string PresetCollection::name() const { switch (this->type()) { diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 2150201..4d806dc 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -716,6 +716,9 @@ public: std::string path_from_name(const std::string &new_name, bool detach = false) const; std::string path_for_preset(const Preset & preset) const; + // Get the alias of a preset, setting it if it's empty + std::string get_preset_alias(Preset &preset, bool force = false); + size_t num_default_presets() { return m_num_default_presets; } protected: diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 653fb65..53a633c 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -48,7 +48,11 @@ static std::vector s_project_options { "flush_multiplier", "nozzle_volume_type", "filament_map_mode", - "filament_map" + "filament_map", + "filament_volume_map", + "filament_nozzle_map", + "extruder_nozzle_stats", + "prime_volume_mode" }; //QDS: add QDT as default @@ -66,7 +70,8 @@ DynamicPrintConfig PresetBundle::construct_full_config( const DynamicPrintConfig& project_config, std::vector& in_filament_presets, bool apply_extruder, - std::optional> filament_maps_new) + std::optional> filament_maps_new, + std::optional> filament_volume_maps_new) { DynamicPrintConfig &printer_config = in_printer_preset.config; DynamicPrintConfig &print_config = in_print_preset.config; @@ -81,12 +86,23 @@ DynamicPrintConfig PresetBundle::construct_full_config( size_t num_filaments = in_filament_presets.size(); std::vector filament_maps = out.option("filament_map")->values; + std::vector filament_volume_maps(num_filaments, (int)nvtStandard); + + ConfigOptionInts* filament_volume_map_opt = out.option("filament_volume_map"); if (filament_maps_new.has_value()) filament_maps = *filament_maps_new; + if (filament_volume_maps_new.has_value()) + filament_volume_maps = *filament_volume_maps_new; + else if (filament_volume_map_opt && filament_volume_map_opt->values.size() == num_filaments) + filament_volume_maps = filament_volume_map_opt->values; + // in some middle state, they may be different if (filament_maps.size() != num_filaments) { filament_maps.resize(num_filaments, 1); } + if (filament_volume_maps.size() != num_filaments) { + filament_volume_maps.resize(num_filaments, nvtStandard); + } 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. @@ -106,18 +122,27 @@ DynamicPrintConfig PresetBundle::construct_full_config( std::string print_inherits = in_print_preset.inherits(); inherits.emplace_back(print_inherits); - // QDS: update printer config related with variants + //QDS: update printer config related with variants + std::vector> nozzle_volume_types; + int extruder_count = 1, extruder_volume_type_count = 1; + bool different_extruder = false; 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"); + different_extruder = out.support_different_extruders(extruder_count); + extruder_volume_type_count = out.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); + + if ((extruder_count > 1) || different_extruder) { + out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2); + out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant"); + // update print config related with variants + out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, print_options_with_variant, "print_extruder_id", "print_extruder_variant"); + } } if (num_filaments <= 1) { // QDS: update filament config related with variants DynamicPrintConfig filament_config = in_filament_presets[0].config; - if (apply_extruder) filament_config.update_values_to_printer_extruders(out, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0]); + if (apply_extruder && ((extruder_count > 1) || different_extruder)) + filament_config.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0], (NozzleVolumeType)filament_volume_maps[0]); out.apply(filament_config); compatible_printers_condition.emplace_back(in_filament_presets[0].compatible_printers_condition()); compatible_prints_condition.emplace_back(in_filament_presets[0].compatible_prints_condition()); @@ -140,8 +165,8 @@ DynamicPrintConfig PresetBundle::construct_full_config( 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]); + if (apply_extruder && ((extruder_count > 1) || different_extruder)) + filament_temp_configs[i].update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i], (NozzleVolumeType)filament_volume_maps[i]); } // loop through options and apply them to the resulting config. @@ -216,6 +241,7 @@ DynamicPrintConfig PresetBundle::construct_full_config( out.option("printer_settings_id", true)->value = in_printer_preset.name; out.option("filament_ids", true)->values = filament_ids; out.option("filament_map", true)->values = filament_maps; + out.option("filament_volume_map", true)->values = filament_volume_maps; auto add_if_some_non_empty = [&out](std::vector &&values, const std::string &key) { bool nonempty = false; @@ -237,6 +263,64 @@ DynamicPrintConfig PresetBundle::construct_full_config( return out; } +int ExtruderNozzleStat::get_extruder_nozzle_count(int extruder_id, std::optional volume_type) const +{ + if(extruder_id<0 || extruder_id >= extruder_nozzle_counts.size()) + return 0; + if (!volume_type.has_value() || volume_type == NozzleVolumeType::nvtHybrid) + return std::accumulate(extruder_nozzle_counts[extruder_id].begin(), extruder_nozzle_counts[extruder_id].end(), 0, + [](int sum, const std::pair& p) { return sum + p.second; }); + + auto iter = extruder_nozzle_counts[extruder_id].find(*volume_type); + if(iter == extruder_nozzle_counts[extruder_id].end()) + return 0; + return iter->second; +} + +void ExtruderNozzleStat::on_printer_model_change(PresetBundle* preset_bundle) +{ + if (force_keep_stat) + return; + BOOST_LOG_TRIVIAL(info)<< __FUNCTION__ << boost::format(": reset extruder nozzle stat by printer model change : %1%") % preset_bundle->printers.get_selected_preset().name; + auto nozzle_volume_type = preset_bundle->project_config.option("nozzle_volume_type"); + auto max_nozzle_count = preset_bundle->printers.get_selected_preset().config.option("extruder_max_nozzle_count"); + extruder_nozzle_counts.resize(max_nozzle_count->size()); + for (size_t eid = 0; eid < extruder_nozzle_counts.size(); ++eid) { + NozzleVolumeType type = nvtStandard; + if (eid >= nozzle_volume_type->size()) + BOOST_LOG_TRIVIAL(error)<< __FUNCTION__ << boost::format(": eid out of bounds, use standard flow"); + else + type = NozzleVolumeType(nozzle_volume_type->values[eid]); + set_extruder_nozzle_count(eid, type, max_nozzle_count->values[eid], true); + } +} + +void ExtruderNozzleStat::on_volume_type_switch(int extruder_id, NozzleVolumeType type) +{ + + if (data_flag == NozzleDataFlag::ndfMachine) { + // do nothing here + } + else if (type != nvtHybrid) { + int current_count = get_extruder_nozzle_count(extruder_id, std::nullopt); + if (extruder_id >= extruder_nozzle_counts.size()) { + extruder_nozzle_counts.resize(extruder_id + 1); + } + extruder_nozzle_counts[extruder_id].clear(); + extruder_nozzle_counts[extruder_id][type] = current_count; + } +} + +void ExtruderNozzleStat::set_extruder_nozzle_count(int extruder_id, NozzleVolumeType type, int count, bool clear) +{ + if (extruder_id >= extruder_nozzle_counts.size()) + extruder_nozzle_counts.resize(extruder_id + 1); + if(clear) + extruder_nozzle_counts[extruder_id].clear(); + extruder_nozzle_counts[extruder_id][type] = count; +} + + PresetBundle::PresetBundle() : prints(Preset::TYPE_PRINT, Preset::print_options(), static_cast(FullPrintConfig::defaults())) , filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast(FullPrintConfig::defaults()), "Default Filament") @@ -678,7 +762,7 @@ void PresetBundle::reset_project_embedded_presets() if (!prefered_filament_profile.empty()) filament_presets[i] = prefered_filament_profile; else - filament_presets[i] = this->filaments.first_visible().name; + filament_presets[i] = this->filaments.first_visible().name; } } } @@ -1201,6 +1285,38 @@ void PresetBundle::update_system_preset_setting_ids(std::map>> PresetBundle::get_full_flush_matrix(bool with_multiplier) const +{ + auto full_config = this->full_config(); + int extruder_nums = full_config.option("nozzle_diameter")->values.size(); + std::vector flush_volume_value = full_config.option("flush_volumes_matrix")->values; + int filament_nums = full_config.option("filament_type")->values.size(); + + std::vector>> matrix; + for(size_t extruder_id = 0; extruder_id < extruder_nums; ++ extruder_id){ + std::vector flush_matrix(cast(get_flush_volumes_matrix(flush_volume_value, extruder_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)); + + matrix.emplace_back(wipe_volumes); + } + + if(with_multiplier){ + auto flush_multiplies = project_config.option("flush_multiplier")->values; + flush_multiplies.resize(extruder_nums, 1); + for (size_t extruder_id = 0; extruder_id < extruder_nums; ++extruder_id) { + for (auto& vec : matrix[extruder_id]) { + for (auto& v : vec) + v *= flush_multiplies[extruder_id]; + } + } + } + + return matrix; +} + //QDS: validate printers from previous project static std::set gcodes_key_set = {"filament_end_gcode", "filament_start_gcode", "change_filament_gcode", "layer_change_gcode", "machine_end_gcode", "machine_pause_gcode", "machine_start_gcode", "template_custom_gcode", "printing_by_object_gcode", "before_layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode"}; @@ -1681,22 +1797,22 @@ const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& p } //QDS: get filament required hrc by filament type -const int PresetBundle::get_required_hrc_by_filament_type(const std::string& filament_type) const +const int PresetBundle::get_required_hrc_by_filament_id(const std::string& filament_id) const { - static std::unordered_mapfilament_type_to_hrc; - if (filament_type_to_hrc.empty()) { + static std::unordered_mapfilament_id_to_hrc; + if (filament_id_to_hrc.empty()) { for (auto iter = filaments.m_presets.begin(); iter != filaments.m_presets.end(); iter++) { if (iter->vendor && iter->vendor->id == "QDT") { - if (iter->config.has("filament_type") && iter->config.has("required_nozzle_HRC")) { - auto type = iter->config.opt_string("filament_type", 0); + if (!iter->filament_id.empty() && iter->config.has("required_nozzle_HRC")) { + auto id = iter->filament_id; auto hrc = iter->config.opt_int("required_nozzle_HRC", 0); - filament_type_to_hrc[type] = hrc; + filament_id_to_hrc[id] = hrc; } } } } - auto iter = filament_type_to_hrc.find(filament_type); - if (iter != filament_type_to_hrc.end()) + auto iter = filament_id_to_hrc.find(filament_id); + if (iter != filament_id_to_hrc.end()) return iter->second; else return 0; @@ -1903,6 +2019,12 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p std::vector filament_maps(filament_colors.size(), 1); project_config.option("filament_map")->values = filament_maps; + std::vector filament_nozzle_maps(filament_colors.size(),0); + project_config.option("filament_nozzle_map")->values = filament_nozzle_maps; + + std::vector filament_volume_maps(filament_colors.size(), static_cast(NozzleVolumeType::nvtStandard)); + project_config.option("filament_volume_map")->values = filament_volume_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(",")); @@ -1969,13 +2091,20 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p 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)) { + for (size_t eid = 0; eid < nozzle_volume_type_option->size(); ++eid) { + extruder_nozzle_stat.on_volume_type_switch(eid, NozzleVolumeType(nozzle_volume_type_option->values[eid])); + } 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; + auto nozzle_volume_type_option = project_config.option("nozzle_volume_type"); + nozzle_volume_type_option->values = current_printer.config.option("default_nozzle_volume_type")->values; + for (size_t eid = 0; eid < nozzle_volume_type_option->size(); ++eid) { + extruder_nozzle_stat.on_volume_type_switch(eid, NozzleVolumeType(nozzle_volume_type_option->values[eid])); + } } // Parse the initial physical printer name. @@ -2053,11 +2182,15 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color) ConfigOptionStrings *filament_multi_color = project_config.option("filament_multi_colour"); ConfigOptionStrings* filament_color_type = project_config.option("filament_colour_type"); ConfigOptionInts* filament_map = project_config.option("filament_map"); + ConfigOptionInts* filament_nozzle_map = project_config.option("filament_nozzle_map"); + ConfigOptionInts* filament_volume_map = project_config.option("filament_volume_map"); filament_color->resize(n); filament_multi_color->resize(n); filament_color_type->resize(n); filament_map->values.resize(n, 1); ams_multi_color_filment.resize(n); + filament_nozzle_map->values.resize(n, 0); + filament_volume_map->values.resize(n, static_cast(NozzleVolumeType::nvtStandard)); //QDS set new filament color to new_color if (old_filament_count < n) { @@ -2745,7 +2878,10 @@ bool PresetBundle::is_the_only_edited_filament(unsigned int filament_index) 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; + auto nozzle_volume_type_option = this->project_config.option("nozzle_volume_type"); + nozzle_volume_type_option->values = current_printer.config.option("default_nozzle_volume_type")->values; + for(size_t eid = 0; eid < nozzle_volume_type_option->size(); ++eid) + extruder_nozzle_stat.on_volume_type_switch(eid, NozzleVolumeType(nozzle_volume_type_option->values[eid])); } int PresetBundle::get_printer_extruder_count() const @@ -2766,10 +2902,26 @@ bool PresetBundle::support_different_extruders() return supported; } -DynamicPrintConfig PresetBundle::full_config(bool apply_extruder, std::optional>filament_maps) const +std::vector PresetBundle::get_default_nozzle_volume_types_for_filaments(std::vector& f_maps) +{ + std::vector result; + int filament_count = f_maps.size(); + result.resize(filament_count, static_cast(NozzleVolumeType::nvtStandard)); + + auto opt_nozzle_volume_type = dynamic_cast(this->project_config.option("nozzle_volume_type")); + for (int index = 0; index < filament_count; index++) + { + if (opt_nozzle_volume_type && opt_nozzle_volume_type->values.size() > (f_maps[index] - 1)) + result[index] = opt_nozzle_volume_type->values[f_maps[index] - 1]; + } + + return result; +} + +DynamicPrintConfig PresetBundle::full_config(bool apply_extruder, std::optional>filament_maps, std::optional> filament_volume_maps) const { return (this->printers.get_edited_preset().printer_technology() == ptFFF) ? - this->full_fff_config(apply_extruder, filament_maps) : + this->full_fff_config(apply_extruder, filament_maps, filament_volume_maps) : this->full_sla_config(); } @@ -2791,7 +2943,7 @@ const std::set ignore_settings_list ={ "print_settings_id", "filament_settings_id", "printer_settings_id" }; -DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optional> filament_maps_new) const +DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optional> filament_maps_new, std::optional> filament_volume_maps_new) const { DynamicPrintConfig out; out.apply(FullPrintConfig::defaults()); @@ -2805,12 +2957,24 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio size_t num_filaments = this->filament_presets.size(); std::vector filament_maps = out.option("filament_map")->values; + std::vector filament_volume_maps(num_filaments, (int)nvtStandard); + + ConfigOptionInts* filament_volume_map_opt = out.option("filament_volume_map"); if (filament_maps_new.has_value()) filament_maps = *filament_maps_new; + if (filament_volume_maps_new.has_value()) { + filament_volume_maps = *filament_volume_maps_new; + out.option("filament_volume_map", true)->values = filament_volume_maps; + } + else if (filament_volume_map_opt && filament_volume_map_opt->values.size() == num_filaments) + filament_volume_maps = filament_volume_map_opt->values; //in some middle state, they may be different if (filament_maps.size() != num_filaments) { filament_maps.resize(num_filaments, 1); } + if (filament_volume_maps.size() != num_filaments) { + filament_volume_maps.resize(num_filaments, nvtStandard); + } 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. @@ -2840,18 +3004,26 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio different_settings.emplace_back(different_print_settings); //QDS: update printer config related with variants + std::vector> nozzle_volume_types; + int extruder_count = 1, extruder_volume_type_count = 1; + bool different_extruder = false; 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"); + different_extruder = out.support_different_extruders(extruder_count); + extruder_volume_type_count = out.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); + + if ((extruder_count > 1) || different_extruder) { + out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2); + out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant"); + //update print config related with variants + out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, print_options_with_variant, "print_extruder_id", "print_extruder_variant"); + } } if (num_filaments <= 1) { //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]); + if (apply_extruder && ((extruder_count > 1) || different_extruder)) + filament_config.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0],(NozzleVolumeType)filament_volume_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()); @@ -2944,8 +3116,8 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio 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]); + if (apply_extruder && ((extruder_count > 1) || different_extruder)) + filament_temp_configs[i].update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i],(NozzleVolumeType)filament_volume_maps[i]); } // loop through options and apply them to the resulting config. @@ -3022,6 +3194,7 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio out.erase("inherits"); //QDS: add logic for settings check between different system presets out.erase("different_settings_to_system"); + out.erase("filament_map_2"); static const char *keys[] = { "support_filament", "support_interface_filament" }; for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++ i) { @@ -3056,6 +3229,7 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio 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("extruder_nozzle_stats", true)->values = save_extruder_nozzle_stats_to_string(this->extruder_nozzle_stat.get_raw_stat()); out.option("printer_technology", true)->value = ptFFF; return out; @@ -3183,31 +3357,55 @@ ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, Forw return ConfigSubstitutions{}; } - -//some filament presets split from one to sperate ones -//following map recording these filament presets -//for example: previously ''Bambu PLA Basic @BBL H2D 0.6 nozzle' was saved in ''Bambu PLA Basic @BBL H2D' with 0.4 -static std::map> filament_preset_convert = { -{"Bambu Lab H2D 0.6 nozzle", {{"Bambu PLA Basic @BBL H2D", "Bambu PLA Basic @BBL H2D 0.6 nozzle"}, - {"Bambu PLA Matte @BBL H2D", "Bambu PLA Matte @BBL H2D 0.6 nozzle"}, - {"Bambu ABS @BBL H2D", "Bambu ABS @BBL H2D 0.6 nozzle"}}}, -{"Bambu Lab H2D 0.8 nozzle", {{"Bambu PETG HF @BBL H2D 0.6 nozzle", "Bambu PETG HF @BBL H2D 0.8 nozzle"}, - {"Bambu ASA @BBL H2D 0.6 nozzle", "Bambu ASA @BBL H2D 0.8 nozzle"}}} -}; - -//convert the old filament preset to new one after split -static void convert_filament_preset_name(std::string& machine_name, std::string& filament_name) +// some filament presets split from one to sperate ones +// following map recording these filament presets +// for example: previously ''QIDI PLA Basic @Qidi Q2 0.6 nozzle' was saved in ''QIDI PLA Basic @Qidi Q2' with 0.4 +bool find_filament_preset_map_name(std::string machine_name, std::string filament_old_name, std::string &filament_new_name) { - auto machine_iter = filament_preset_convert.find(machine_name); - if (machine_iter != filament_preset_convert.end()) - { - std::map& filament_maps = machine_iter->second; - auto filament_iter = filament_maps.find(filament_name); - if (filament_iter != filament_maps.end()) - { - filament_name = filament_iter->second; + static std::map> filament_preset_convert; + + if (filament_preset_convert.empty()) { + namespace fs = boost::filesystem; + fs::path path = fs::path(Slic3r::resources_dir()) / "profiles" / "BBL" / "filament" / "filament_name_map.json"; + + std::ifstream file(path.string()); + if (!file.is_open()) { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format("Failed to open filament preset map: %1%") % path.string(); + return false; + } + + json j; + try { + file >> j; + } catch (const std::exception &e) { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format("JSON parse error: %1%") % e.what(); + return false; + } + + for (auto &[printer_name, filament_json] : j.items()) { + std::map inner_map; + for (auto &[old_name, new_name] : filament_json.items()) inner_map[old_name] = new_name; + filament_preset_convert[printer_name] = std::move(inner_map); } } + + auto machine_iter = filament_preset_convert.find(machine_name); + if (machine_iter != filament_preset_convert.end()) { + std::map &filament_maps = machine_iter->second; + auto filament_iter = filament_maps.find(filament_old_name); + if (filament_iter != filament_maps.end()) { + filament_new_name = filament_iter->second; + return true; + } + } + return false; +} + +// convert the old filament preset to new one after split +void convert_filament_preset_name(std::string &machine_name, std::string &filament_name) +{ + std::string filament_new_name; + if (find_filament_preset_map_name(machine_name, filament_name, filament_new_name)) filament_name = filament_new_name; } // 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 @@ -3314,6 +3512,18 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool if (this->extruder_ams_counts.empty()) this->extruder_ams_counts = get_extruder_ams_count(extruder_ams_count); + if (auto nozzle_stats_ptr = config.option("extruder_nozzle_stats"); + nozzle_stats_ptr && !(nozzle_stats_ptr->values.empty() || + std::any_of(nozzle_stats_ptr->values.begin(), nozzle_stats_ptr->values.end(), [](const std::string &elem) { return elem.empty(); }))) { + this->extruder_nozzle_stat = ExtruderNozzleStat(get_extruder_nozzle_stats(nozzle_stats_ptr->values)); + } else { + auto nozzle_volume_opt = config.option("nozzle_volume_type"); + if (this->extruder_nozzle_stat.get_raw_stat().size() != nozzle_volume_opt->size()) + this->extruder_nozzle_stat.on_printer_model_change(this); + for (size_t idx = 0; idx < nozzle_volume_opt->size(); ++idx) { + this->extruder_nozzle_stat.on_volume_type_switch(idx, NozzleVolumeType(nozzle_volume_opt->values[idx])); + } + } // 1) Create a name from the file name. // Keep the suffix (.ini, .gcode, .amf, .3mf etc) to differentiate it from the normal profiles. diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index aff0290..41a5078 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -67,6 +67,33 @@ struct FilamentBaseInfo int filament_printable = 3; }; +class PresetBundle; +struct ExtruderNozzleStat +{ +public: + enum NozzleDataFlag { + ndfMachine = 0, + ndfNone + }; +public: + ExtruderNozzleStat() = default; + ExtruderNozzleStat(const std::vector>& nozzle_counts, const NozzleDataFlag flag = ndfNone) : extruder_nozzle_counts(nozzle_counts), data_flag(flag) {} + void on_volume_type_switch(int extruder_id, NozzleVolumeType type); + void on_printer_model_change(PresetBundle* preset_bundle); + void set_extruder_nozzle_count(int extruder_id, NozzleVolumeType type, int count, bool clear); + int get_extruder_nozzle_count(int extruder_id, std::optional volume_type = std::nullopt) const; + + const std::vector> get_raw_stat() const { return extruder_nozzle_counts; } + void set_raw_stat(const std::vector>& data) { extruder_nozzle_counts = data; } + + void set_nozzle_data_flag(NozzleDataFlag flag){ data_flag = flag; } + void set_force_keep_flag(bool flag) { force_keep_stat = flag; } +private: + bool force_keep_stat{ false }; + std::vector> extruder_nozzle_counts; + NozzleDataFlag data_flag{ ndfNone }; +}; + // Bundle of Print + Filament + Printer presets. class PresetBundle { @@ -76,7 +103,8 @@ public: const DynamicPrintConfig &project_config, std::vector &in_filament_presets, bool apply_extruder, - std::optional> filament_maps_new); + std::optional> filament_maps_new, + std::optional> filament_volume_maps_new); PresetBundle(); PresetBundle(const PresetBundle &rhs); PresetBundle& operator=(const PresetBundle &rhs); @@ -122,6 +150,8 @@ public: void remove_user_presets_directory(const std::string preset_folder); void update_system_preset_setting_ids(std::map>& system_presets); + std::vector>> get_full_flush_matrix(bool with_multiplier = true) const; + //QDS: add API to get previous machine int validate_presets(const std::string &file_name, DynamicPrintConfig& config, std::set& different_gcodes); @@ -196,7 +226,7 @@ public: std::vector> ams_multi_color_filment; std::vector> extruder_ams_counts; - + ExtruderNozzleStat extruder_nozzle_stat; // Calibrate Preset const * calibrate_printer = nullptr; std::set calibrate_filaments; @@ -222,13 +252,14 @@ public: bool has_defauls_only() const { return prints.has_defaults_only() && filaments.has_defaults_only() && printers.has_defaults_only(); } - DynamicPrintConfig full_config(bool apply_extruder = true, std::optional>filament_maps = std::nullopt) const; + DynamicPrintConfig full_config(bool apply_extruder = true, std::optional>filament_maps = std::nullopt, std::optional> filament_volume_maps = std::nullopt) const; // full_config() with the some "useless" config removed. 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(); + std::vector get_default_nozzle_volume_types_for_filaments(std::vector& f_maps); // Load user configuration and store it into the user profiles. // This method is called by the configuration wizard. @@ -307,7 +338,7 @@ public: const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias) const; - const int get_required_hrc_by_filament_type(const std::string& filament_type) const; + const int get_required_hrc_by_filament_id(const std::string& filament_id) const; // Save current preset of a provided type under a new name. If the name is different from the old one, // Unselected option would be reverted to the beginning values //QDS: add project embedded preset logic @@ -348,12 +379,15 @@ private: /*ConfigSubstitutions load_config_file_config_bundle( const std::string &path, const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);*/ - DynamicPrintConfig full_fff_config(bool apply_extruder, std::optional> filament_maps=std::nullopt) const; + DynamicPrintConfig full_fff_config(bool apply_extruder, std::optional> filament_maps=std::nullopt, std::optional> filament_volume_maps=std::nullopt) const; DynamicPrintConfig full_sla_config() const; }; ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute) +//add these functions here to allow other access +extern void convert_filament_preset_name(std::string& machine_name, std::string& filament_name); + } // namespace Slic3r #endif /* slic3r_PresetBundle_hpp_ */ diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 3215fb8..510a171 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -66,6 +66,7 @@ void Print::clear() m_print_regions.clear(); m_model.clear_objects(); m_statistics_by_extruder_count.clear(); + m_nozzle_group_result.reset(); } bool Print::has_tpu_filament() const @@ -130,7 +131,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "filament_colour", "default_filament_colour", "filament_diameter", - "volumetric_speed_coefficients", + "volumetric_speed_coefficients", "filament_density", "filament_cost", "initial_layer_acceleration", @@ -164,6 +165,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "retraction_length", "retract_length_toolchange", "z_hop", + "filament_retract_length_nc", "retract_restart_extra", "retract_restart_extra_toolchange", "retraction_speed", @@ -280,11 +282,14 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "filament_max_volumetric_speed" || opt_key == "filament_adaptive_volumetric_speed" || opt_key == "filament_ramming_volumetric_speed" + || opt_key == "filament_ramming_volumetric_speed_nc" || opt_key == "gcode_flavor" || opt_key == "single_extruder_multi_material" || opt_key == "nozzle_temperature" || opt_key == "filament_pre_cooling_temperature" + || opt_key == "filament_pre_cooling_temperature_nc" || opt_key == "filament_ramming_travel_time" + || opt_key == "filament_ramming_travel_time_nc" // QDS || opt_key == "supertack_plate_temp" || opt_key == "cool_plate_temp" @@ -309,13 +314,19 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "other_layers_print_sequence" || opt_key == "other_layers_print_sequence_nums" || opt_key == "extruder_ams_count" + || opt_key == "extruder_nozzle_stats" + || opt_key == "filament_cooling_before_tower" + || opt_key == "prime_volume_mode" || opt_key == "filament_map_mode" || opt_key == "filament_map" + || opt_key == "filament_nozzle_map" + || opt_key == "filament_volume_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 == "filament_prime_volume" + || opt_key == "filament_prime_volume_nc" || opt_key == "flush_into_infill" || opt_key == "flush_into_support" || opt_key == "initial_layer_infill_speed" @@ -339,8 +350,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "filament_scarf_gap" || opt_key == "filament_scarf_length" || opt_key == "filament_change_length" - || opt_key == "independent_support_layer_height" - || opt_key == "top_z_overrides_xy_distance") { + || opt_key == "independent_support_layer_height" + || opt_key == "top_z_overrides_xy_distance" + || opt_key == "filament_change_length_nc") { steps.emplace_back(psWipeTower); // Soluble support interface / non-soluble base interface produces non-soluble interface layers below soluble interface layers. // Thus switching between soluble / non-soluble interface layer material may require recalculation of supports. @@ -360,7 +372,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "seam_slope_type" || opt_key == "seam_slope_start_height" || opt_key == "seam_slope_gap" - || opt_key == "seam_slope_min_length") { + || opt_key == "seam_slope_min_length" + || opt_key == "embedding_wall_into_infill") { osteps.emplace_back(posPerimeters); osteps.emplace_back(posInfill); osteps.emplace_back(posSupportMaterial); @@ -1243,8 +1256,13 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* // #4043 if (total_copies_count > 1 && m_config.print_sequence != PrintSequence::ByObject) return {L("Please select \"By object\" print sequence to print multiple objects in spiral vase mode."), nullptr, "spiral_mode"}; + bool SFFF_enabled = false; + for (const PrintObject *object : m_objects) { + auto cfg = object->object_extruders(); + if (cfg.size() > 1) SFFF_enabled = true; + } assert(m_objects.size() == 1); - if (m_objects.front()->all_regions().size() > 1) + if (m_objects.front()->all_regions().size() > 1 || SFFF_enabled) return {L("The spiral vase mode does not work when an object contains more than one materials."), nullptr, "spiral_mode"}; } @@ -1830,7 +1848,8 @@ void Print::process(std::unordered_map* slice_time, bool }; int object_count = m_objects.size(); std::set need_slicing_objects; - std::set re_slicing_objects; + //std::set re_slicing_objects; + m_reslicing_objects.clear(); if (!use_cache) { for (int index = 0; index < object_count; index++) { @@ -1842,8 +1861,10 @@ void Print::process(std::unordered_map* slice_time, bool break; } } - if (!obj->get_shared_object()) + if (!obj->get_shared_object()) { need_slicing_objects.insert(obj); + m_reslicing_objects.insert(obj); + } } } else { @@ -1871,7 +1892,7 @@ void Print::process(std::unordered_map* slice_time, bool //throw Slic3r::SlicingError("Can not find the cached data."); //don't report errot, set use_cache to false, and reslice these objects need_slicing_objects.insert(obj); - re_slicing_objects.insert(obj); + m_reslicing_objects.insert(obj); //use_cache = false; } } @@ -1971,7 +1992,7 @@ void Print::process(std::unordered_map* slice_time, bool } else { for (PrintObject *obj : m_objects) { - if (re_slicing_objects.count(obj) == 0) { + if (m_reslicing_objects.count(obj) == 0) { if (obj->set_started(posSlice)) obj->set_done(posSlice); if (obj->set_started(posPerimeters)) @@ -1988,6 +2009,7 @@ void Print::process(std::unordered_map* slice_time, bool obj->set_done(posDetectOverhangsForLift); } else { + obj->set_auto_circle_compenstaion_params(auto_contour_holes_compensation_params); obj->make_perimeters(); obj->infill(); obj->ironing(); @@ -2021,12 +2043,26 @@ void Print::process(std::unordered_map* slice_time, bool this->set_geometric_unprintable_filaments(geometric_unprintables); } + { + std::unordered_map> filament_print_time; + for(PrintObject* obj : m_objects){ + auto obj_filament_print_time = obj->calc_estimated_filament_print_time(); + for(auto [filament_idx,extruder_time] : obj_filament_print_time) { + for (auto [extruder_idx, time] : extruder_time) { + filament_print_time[filament_idx][extruder_idx] += time; + } + } + } + this->set_filament_print_time(filament_print_time); + } + + m_nozzle_group_result.reset(); 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) { + } else if (this->config().print_sequence != PrintSequence::ByObject + || (this->config().print_sequence == PrintSequence::ByObject && m_objects.size() == 1)) { // 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); @@ -2070,7 +2106,7 @@ void Print::process(std::unordered_map* slice_time, bool std::vector::const_iterator print_object_instance_sequential_active; std::vector>> layers_to_print = GCode::collect_layers_to_print(*this); std::vector printExtruders; - if (this->config().print_sequence == PrintSequence::ByObject) { + if (this->config().print_sequence == PrintSequence::ByObject && m_objects.size() > 1) { // Order object instances for sequential print. print_object_instances_ordering = sort_object_instances_by_model_order(*this); std::vector first_layer_used_filaments; @@ -2090,16 +2126,20 @@ void Print::process(std::unordered_map* slice_time, bool 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); + { + if (!get_nozzle_group_result().has_value()) { + auto map_mode = get_filament_map_mode(); + auto group_result = ToolOrdering::get_recommended_filament_maps(this, all_filaments, map_mode, physical_unprintables, geometric_unprintables); + set_nozzle_group_result(group_result); + } + auto group_result = get_nozzle_group_result(); + update_filament_maps_to_config( + FilamentGroupUtils::update_used_filament_values(this->config().filament_map.values, group_result->get_extruder_map(false), used_filaments), + FilamentGroupUtils::update_used_filament_values(this->config().filament_volume_map.values, group_result->get_volume_map(), used_filaments), + group_result->get_nozzle_map() + ); } - // 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; }); // print_object_instances_ordering = sort_object_instances_by_max_z(print); print_object_instance_sequential_active = print_object_instances_ordering.begin(); @@ -2178,7 +2218,7 @@ void Print::process(std::unordered_map* slice_time, bool //QDS for (PrintObject *obj : m_objects) { if (((!use_cache)&&(need_slicing_objects.count(obj) != 0)) - || (use_cache &&(re_slicing_objects.count(obj) != 0))){ + || (use_cache &&(m_reslicing_objects.count(obj) != 0))){ obj->simplify_extrusion_path(); } else { @@ -2249,8 +2289,10 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor gcode.do_export(this, path.c_str(), result, thumbnail_cb); gcode.export_layer_filaments(result); //QDS - if (result != nullptr) + if (result != nullptr){ result->conflict_result = m_conflict_result; + result->nozzle_group_result = this->get_nozzle_group_result(); + } return path.c_str(); } @@ -2614,16 +2656,53 @@ 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) +void Print::update_filament_maps_to_config(std::vector f_maps, std::vector f_volume_maps, std::vector f_nozzle_maps) { - if (m_config.filament_map.values != f_maps) + if ((m_config.filament_map.values != f_maps) || (m_config.filament_volume_map.values != f_volume_maps) || (m_config.filament_nozzle_map.values != f_nozzle_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; + if (!f_volume_maps.empty()) { + m_ori_full_print_config.option("filament_volume_map", true)->values = f_volume_maps; + m_config.filament_volume_map.values = f_volume_maps; + } + else { + m_ori_full_print_config.option("filament_volume_map", true)->values.resize(f_maps.size(), nvtStandard); + m_config.filament_volume_map.values.resize(f_maps.size(), nvtStandard); + } + + if (!f_nozzle_maps.empty()) { + m_ori_full_print_config.option("filament_nozzle_map", true)->values = f_nozzle_maps; + m_config.filament_nozzle_map.values = f_nozzle_maps; + } + + int extruder_count, extruder_volume_type_count; + bool support_multi = m_ori_full_print_config.support_different_extruders(extruder_count); + std::vector> nozzle_volume_types; + extruder_volume_type_count = m_ori_full_print_config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); + + //filament_map_2 + m_config.filament_map_2.values = f_maps; + auto opt_extruder_type = dynamic_cast(m_ori_full_print_config.option("extruder_type")); + auto opt_nozzle_volume_type = dynamic_cast(m_ori_full_print_config.option("nozzle_volume_type")); + for (int index = 0; index < f_maps.size(); index++) + { + ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(f_maps[index] - 1)); + NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(f_maps[index] - 1)); + if (f_volume_maps.empty()) { + m_config.filament_volume_map.values[index] = nozzle_volume_type; + m_ori_full_print_config.option("filament_volume_map")->values[index] = nozzle_volume_type; + } + else if ((extruder_volume_type_count > extruder_count) && (m_config.filament_volume_map.values.size() > index)) + nozzle_volume_type = (NozzleVolumeType)(m_config.filament_volume_map.values[index]); + m_config.filament_map_2.values[index] = m_ori_full_print_config.get_index_for_extruder(f_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant"); + } 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"); + + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: extruder_count %2%, extruder_volume_type_count %3%")%__LINE__ %extruder_count %extruder_volume_type_count; + m_full_print_config.update_values_to_printer_extruders_for_multiple_filaments(m_full_print_config, extruder_count, extruder_volume_type_count, 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_"; @@ -2636,7 +2715,7 @@ void Print::update_filament_maps_to_config(std::vector f_maps) 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); + compute_filament_override_value(opt_key, opt_old_machine, opt_new_machine, opt_new_filament, m_full_print_config, print_diff, filament_overrides, m_config.filament_map_2.values); } t_config_option_keys keys(filament_options_with_variant.begin(), filament_options_with_variant.end()); @@ -2659,6 +2738,16 @@ std::vector Print::get_filament_maps() const return m_config.filament_map.values; } +std::vector Print::get_filament_nozzle_maps() const +{ + return m_config.filament_nozzle_map.values; +} + +std::vector Print::get_filament_volume_maps() const +{ + return m_config.filament_volume_map.values; +} + FilamentMapMode Print::get_filament_map_mode() const { return m_config.filament_map_mode; @@ -2728,6 +2817,18 @@ std::vector Print::get_extruder_unprintable_polygons() const return std::move(extruder_unprintable_polys); } +Polygons Print::get_extruder_shared_printable_polygon() const +{ + if (m_config.nozzle_diameter.size() < 2) return {Polygon::new_scale(m_config.printable_area.values)}; + std::vector> extruder_printable_areas = m_config.extruder_printable_area.values; + Polygons shared_printable_polys = {Polygon::new_scale(extruder_printable_areas.front())}; + for (int i = 1; i < extruder_printable_areas.size();i++) { + Polygons polys = {Polygon::new_scale(extruder_printable_areas[i])}; + shared_printable_polys = intersection(shared_printable_polys, polys); + } + return shared_printable_polys; +} + size_t Print::get_extruder_id(unsigned int filament_id) const { std::vector filament_map = get_filament_maps(); @@ -2737,6 +2838,15 @@ size_t Print::get_extruder_id(unsigned int filament_id) const return 0; } +size_t Print::get_config_idx_for_filament(unsigned int filament_id) const +{ + std::vector filament_map_2 = m_config.filament_map_2.values; + if (filament_id < filament_map_2.size()) { + return filament_map_2[filament_id]; + } + return 0; +} + // Wipe tower support. bool Print::has_wipe_tower() const { @@ -2787,6 +2897,10 @@ const WipeTowerData& Print::wipe_tower_data(size_t filaments_cnt) const if (! is_step_done(psWipeTower) && filaments_cnt !=0) { std::vector filament_wipe_volume = m_config.filament_prime_volume.values; + if (m_config.prime_volume_mode == pvmSaving) { + for (auto& v : filament_wipe_volume) + v = 15.f; + } double wipe_volume = get_max_element(filament_wipe_volume); 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; @@ -2882,12 +2996,13 @@ void Print::_make_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_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, m_wipe_tower_data.tool_ordering.all_extruders()); + wipe_tower.set_first_layer_flow_ratio(m_default_region_config.initial_layer_flow_ratio); wipe_tower.set_has_tpu_filament(this->has_tpu_filament()); wipe_tower.set_filament_map(this->get_filament_maps()); + wipe_tower.set_nozzle_group_result(m_nozzle_group_result.value()); // 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)); @@ -2912,42 +3027,51 @@ void Print::_make_wipe_tower() } std::vectorfilament_maps = get_filament_maps(); + MultiNozzleUtils::NozzleStatusRecorder nozzle_recorder; - 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; + assert(m_nozzle_group_result.has_value()); + unsigned int old_filament_id = m_wipe_tower_data.tool_ordering.first_extruder(); + nozzle_recorder.set_nozzle_status(m_nozzle_group_result->get_nozzle_for_filament(old_filament_id)->group_id, old_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_filament_id, current_filament_id); + wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, old_filament_id, old_filament_id); used_filament_ids.insert(layer_tools.extruders.begin(), layer_tools.extruders.end()); for (const auto filament_id : layer_tools.extruders) { - if (filament_id == current_filament_id) + if (filament_id == old_filament_id) continue; - int nozzle_id = filament_maps[filament_id] - 1; - unsigned int pre_filament_id = nozzle_cur_filament_ids[nozzle_id]; + int extruder_id = filament_maps[filament_id] - 1; + int nozzle_id = m_nozzle_group_result->get_nozzle_for_filament(filament_id)->group_id; + int prev_nozzle_filament = nozzle_recorder.get_filament_in_nozzle(nozzle_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); + + if(!nozzle_recorder.is_nozzle_empty(nozzle_id) && filament_id != prev_nozzle_filament){ + volume_to_purge = multi_extruder_flush[extruder_id][prev_nozzle_filament][filament_id]; + volume_to_purge *= m_config.flush_multiplier.get_at(extruder_id); + volume_to_purge = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, old_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 + float grab_purge_volume = m_config.grab_length.get_at(extruder_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; + float wipe_volume_ec = m_config.filament_prime_volume.values[filament_id]; + float wipe_volume_nc = m_config.filament_prime_volume_nc.values[filament_id]; + // special primte volume settings for H2C + if(m_config.prime_volume_mode == PrimeVolumeMode::pvmSaving){ + wipe_volume_ec = 15.f; + wipe_volume_nc = 15.f; + } + wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, old_filament_id, filament_id, + wipe_volume_ec, wipe_volume_nc, volume_to_purge); + old_filament_id = filament_id; + + nozzle_recorder.set_nozzle_status(nozzle_id, filament_id); } layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this); @@ -2961,7 +3085,6 @@ void Print::_make_wipe_tower() break; } } - wipe_tower.set_used_filament_ids(std::vector(used_filament_ids.begin(), used_filament_ids.end())); std::vector categories; @@ -2969,7 +3092,6 @@ void Print::_make_wipe_tower() 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_new(m_wipe_tower_data.tool_changes); @@ -3042,12 +3164,17 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces { try { GCodeProcessor processor; + if (result && result->nozzle_group_result) + processor.initialize_from_context(*result->nozzle_group_result); const Vec3d origin = this->get_plate_origin(); processor.set_xy_offset(origin(0), origin(1)); //processor.enable_producers(true); processor.process_file(file); + // filament seq is loaded from file, processor result will override the value + auto seq_loaded = result->filament_change_sequence; *result = std::move(processor.extract_result()); + result->filament_change_sequence = seq_loaded; } catch (std::exception & /* ex */) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": found errors when process gcode file %1%") %file.c_str(); throw Slic3r::RuntimeError( @@ -3847,10 +3974,11 @@ static void from_json(const json& j, groupedVolumeSlices& firstlayer_group) } } -int Print::export_cached_data(const std::string& directory, bool with_space) +int Print::export_cached_data(const std::string& directory, int& obj_cnt_exported, bool with_space) { int ret = 0; boost::filesystem::path directory_path(directory); + obj_cnt_exported = 0; auto convert_layer_to_json = [](json& layer_json, const Layer* layer) { json slice_polygons_json = json::array(), slice_bboxs_json = json::array(), overhang_polygons_json = json::array(), layer_regions_json = json::array(); @@ -3897,13 +4025,15 @@ int Print::export_cached_data(const std::string& directory, bool with_space) }; //firstly clear this directory - if (fs::exists(directory_path)) { + /*if (fs::exists(directory_path)) { fs::remove_all(directory_path); - } + }*/ try { - if (!fs::create_directory(directory_path)) { - BOOST_LOG_TRIVIAL(error) << boost::format("create directory %1% failed")%directory; - return CLI_EXPORT_CACHE_DIRECTORY_CREATE_FAILED; + if (!fs::exists(directory_path)) { + if (!fs::create_directory(directory_path)) { + BOOST_LOG_TRIVIAL(error) << boost::format("create directory %1% failed")%directory; + return CLI_EXPORT_CACHE_DIRECTORY_CREATE_FAILED; + } } } catch (...) @@ -3915,19 +4045,30 @@ int Print::export_cached_data(const std::string& directory, bool with_space) int count = 0; std::vector filename_vector; std::vector json_vector; + size_t region_cnt = this->num_print_regions(); + size_t hash_values = 0; + for (size_t region_idx = 0; region_idx < region_cnt; region_idx++) + { + boost::hash_combine(hash_values, this->get_print_region(region_idx).config_hash()); + } for (PrintObject *obj : m_objects) { const ModelObject* model_obj = obj->model_object(); - if (obj->get_shared_object()) { + /*if (obj->get_shared_object()) { BOOST_LOG_TRIVIAL(info) << boost::format("shared object %1%, skip directly")%model_obj->name; continue; + }*/ + if (m_reslicing_objects.count(obj) == 0) { + BOOST_LOG_TRIVIAL(info) << boost::format("shared object or already cached before: %1%, skip directly")%model_obj->name; + continue; } + obj_cnt_exported++; const PrintInstance &print_instance = obj->instances()[0]; const ModelInstance *model_instance = print_instance.model_instance; size_t identify_id = (model_instance->loaded_id > 0)?model_instance->loaded_id: model_instance->id().id; - std::string file_name = directory +"/obj_"+std::to_string(identify_id)+".json"; + std::string file_name = directory + "/obj_" + std::to_string(identify_id) + "_" + std::to_string(region_cnt) + "_" + std::to_string(hash_values) + ".json"; - BOOST_LOG_TRIVIAL(info) << boost::format("begin to dump object %1%, identify_id %2% to %3%")%model_obj->name %identify_id %file_name; + BOOST_LOG_TRIVIAL(warning) << boost::format("begin to dump object %1%, identify_id %2%, hash %3% to %4%, region count %5%")%model_obj->name %identify_id %hash_values %file_name %region_cnt; try { json root_json, layers_json = json::array(), support_layers_json = json::array(), first_layer_groups = json::array(); @@ -4138,6 +4279,12 @@ int Print::load_cached_data(const std::string& directory) int count = 0; std::vector> object_filenames; + size_t region_cnt = this->num_print_regions(); + size_t hash_values = 0; + for (size_t region_idx = 0; region_idx < region_cnt; region_idx++) + { + boost::hash_combine(hash_values, this->get_print_region(region_idx).config_hash()); + } for (PrintObject *obj : m_objects) { const ModelObject* model_obj = obj->model_object(); const PrintInstance &print_instance = obj->instances()[0]; @@ -4153,10 +4300,10 @@ int Print::load_cached_data(const std::string& directory) BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": object %1%'s loaded_id is 0, need to use the instance_id %2%")%model_obj->name %identify_id; //continue; } - std::string file_name = directory +"/obj_"+std::to_string(identify_id)+".json"; + std::string file_name = directory + "/obj_" + std::to_string(identify_id) + "_" + std::to_string(region_cnt) + "_" + std::to_string(hash_values) + ".json"; if (!fs::exists(file_name)) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__< +#include "MultiNozzleUtils.hpp" + #include "libslic3r.h" #include @@ -559,6 +561,8 @@ private: */ std::vector> detect_extruder_geometric_unprintables() const; + std::unordered_map> calc_estimated_filament_print_time() const; + void slice_volumes(); //QDS ExPolygons _shrink_contour_holes(double contour_delta, double hole_delta, const ExPolygons& polys) const; @@ -845,7 +849,7 @@ public: // If preview_data is not null, the preview_data is filled in for the G-code visualization (not used by the command line Slic3r). std::string export_gcode(const std::string& path_template, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); //return 0 means successful - int export_cached_data(const std::string& dir_path, bool with_space=false); + int export_cached_data(const std::string& dir_path, int& obj_cnt_exported, bool with_space=false); int load_cached_data(const std::string& directory); // methods for handling state @@ -881,8 +885,8 @@ public: const PrintObjectConfig& default_object_config() const { return m_default_object_config; } const PrintRegionConfig& default_region_config() const { return m_default_region_config; } ConstPrintObjectPtrsAdaptor objects() const { return ConstPrintObjectPtrsAdaptor(&m_objects); } - PrintObject* get_object(size_t idx) { return const_cast(m_objects[idx]); } const PrintObject* get_object(size_t idx) const { return m_objects[idx]; } + PrintObject* get_object(size_t idx) { return const_cast(m_objects[idx]); } // PrintObject by its ObjectID, to be used to uniquely bind slicing warnings to their source PrintObjects // in the notification center. const PrintObject* get_object(ObjectID object_id) const { @@ -921,14 +925,18 @@ public: 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); + void update_filament_maps_to_config(std::vector f_maps, std::vector f_volume_maps = std::vector{}, std::vector f_nozzle_maps = std::vector{}); void apply_config_for_render(const DynamicConfig &config); // 1 based group ids std::vector get_filament_maps() const; FilamentMapMode get_filament_map_mode() const; + std::vector get_filament_volume_maps() const; + std::vector get_filament_nozzle_maps() const; // get the group label of filament size_t get_extruder_id(unsigned int filament_id) const; + // get the config idx for filament + size_t get_config_idx_for_filament(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; } @@ -936,6 +944,12 @@ public: 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_filament_print_time(const std::unordered_map>& filament_print_time) { m_filament_print_time = filament_print_time; } + std::unordered_map> get_filament_print_time() const { return m_filament_print_time; } + + void set_nozzle_group_result(const std::optional& result) { m_nozzle_group_result = result; } + const std::optional& get_nozzle_group_result() { return m_nozzle_group_result; } + 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; @@ -958,7 +972,7 @@ public: std::vector get_extruder_printable_height() const; std::vector get_extruder_printable_polygons() const; std::vector get_extruder_unprintable_polygons() const; - + Polygons get_extruder_shared_printable_polygon() const; bool enable_timelapse_print() const; std::string output_filename(const std::string &filename_base = std::string()) const override; @@ -1077,6 +1091,8 @@ private: bool m_support_used {false}; StatisticsByExtruderCount m_statistics_by_extruder_count; + std::optional m_nozzle_group_result; + std::vector m_slice_used_filaments; std::vector m_slice_used_filaments_first_layer; @@ -1089,7 +1105,10 @@ private: FakeWipeTower m_fake_wipe_tower; bool m_has_auto_filament_map_result{false}; + std::set m_reslicing_objects; + std::vector> m_geometric_unprintable_filaments; + std::unordered_map> m_filament_print_time; // OrcaSlicer: calibration Calib_Params m_calib_params; diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 8c99192..be5aa2e 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -237,7 +237,10 @@ static t_config_option_keys print_config_diffs( 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) { - compute_filament_override_value(opt_key, opt_old, opt_new, opt_new_filament, new_full_config, print_diff, filament_overrides, filament_maps); + std::vector filament_map_indices(filament_maps.size(), 0); + for (int i = 0; i < filament_maps.size(); i++) + filament_map_indices[i] = filament_maps[i] - 1; + compute_filament_override_value(opt_key, opt_old, opt_new, opt_new_filament, new_full_config, print_diff, filament_overrides, filament_map_indices); } 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")) { @@ -1237,20 +1240,32 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ //apply extruder related values std::vector print_variant_index; + std::vector> nozzle_volume_types; + int extruder_count = 1, extruder_volume_type_count = 1; + bool different_extruder = false; + + different_extruder = new_full_config.support_different_extruders(extruder_count); + extruder_volume_type_count = new_full_config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); 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"); + if ((extruder_count > 1) || different_extruder) { + new_full_config.update_values_to_printer_extruders(new_full_config, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2); + new_full_config.update_values_to_printer_extruders(new_full_config, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant"); + //update print config related with variants + print_variant_index = new_full_config.update_values_to_printer_extruders(new_full_config, extruder_count, extruder_volume_type_count, nozzle_volume_types, print_options_with_variant, "print_extruder_id", "print_extruder_variant"); + } + else + print_variant_index.resize(1, 0); 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"); + if ((extruder_count > 1) || different_extruder) + new_full_config.update_values_to_printer_extruders_for_multiple_filaments(new_full_config, extruder_count, extruder_volume_type_count, 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++) + //should not come here, we can not get the result of print_variant, for the values have been updated + //we just use the default values here + auto variant_opt = dynamic_cast(new_full_config.option("printer_extruder_variant")); + print_variant_index.resize(variant_opt->values.size()); + for (int e_index = 0; e_index < variant_opt->values.size(); e_index++) { print_variant_index[e_index] = e_index; } @@ -1268,7 +1283,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ //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()) + if (!print_diff_set.empty() && 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) { @@ -1280,12 +1295,35 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ old_opt->set(new_opt); m_config.filament_map = *new_opt; } + if (print_diff_set.find("filament_volume_map") != print_diff_set.end()) { + print_diff_set.erase("filament_volume_map"); + //full_config_diff.erase("filament_volume_map"); + ConfigOptionInts* old_opt = m_full_print_config.option("filament_volume_map", true); + ConfigOptionInts* new_opt = new_full_config.option("filament_volume_map", true); + old_opt->set(new_opt); + m_config.filament_volume_map = *new_opt; + } + if (print_diff_set.find("filament_nozzle_map") != print_diff_set.end()) { + print_diff_set.erase("filament_nozzle_map"); + //full_config_diff.erase("filament_nozzle_map"); + ConfigOptionInts* old_opt = m_full_print_config.option("filament_nozzle_map", true); + ConfigOptionInts* new_opt = new_full_config.option("filament_nozzle_map", true); + old_opt->set(new_opt); + m_config.filament_nozzle_map = *new_opt; + } } else { print_diff_set.erase("extruder_ams_count"); + if (map_mode == fmmManual) { + // this param is not used in gui studio + print_diff_set.erase("filament_nozzle_map"); + } std::vector old_filament_map = m_config.filament_map.values; std::vector new_filament_map = new_full_config.option("filament_map", true)->values; + std::vector old_filament_volume_map = m_config.filament_volume_map.values; + std::vector new_filament_volume_map = new_full_config.option("filament_volume_map", true)->values; + if (old_filament_map.size() == new_filament_map.size()) { bool same_map = true; @@ -1307,6 +1345,20 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ print_diff.assign(print_diff_set.begin(), print_diff_set.end()); } + //filament_map_2 + m_config.filament_map_2.values = filament_maps; + auto opt_extruder_type = dynamic_cast(new_full_config.option("extruder_type")); + auto opt_filament_volume_maps = dynamic_cast(new_full_config.option("filament_volume_map")); + auto opt_nozzle_volume_type = dynamic_cast(new_full_config.option("nozzle_volume_type")); + for (int index = 0; index < filament_maps.size(); index++) + { + ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(filament_maps[index] - 1)); + NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(filament_maps[index] - 1)); + if ((extruder_volume_type_count > extruder_count) && opt_filament_volume_maps && (opt_filament_volume_maps->values.size() > index)) + nozzle_volume_type = (NozzleVolumeType)(opt_filament_volume_maps->values[index]); + m_config.filament_map_2.values[index] = new_full_config.get_index_for_extruder(filament_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant"); + } + // 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) diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index c0ec7b0..3f35c39 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -425,10 +425,8 @@ public: // After calling the apply() function, call set_task() to limit the task to be processed by process(). virtual void set_task(const TaskParams ¶ms) {} // Perform the calculation. This is the only method that is to be called at a worker thread. - - //1.9.5 virtual void process(std::unordered_map* slice_time = nullptr, bool use_cache = false) = 0; - virtual int export_cached_data(const std::string& dir_path, bool with_space=false) { return 0;} + virtual int export_cached_data(const std::string& dir_path, int& obj_cnt_exported, bool with_space=false) { return 0;} virtual int load_cached_data(const std::string& directory) { return 0;} // Clean up after process() finished, either with success, error or if canceled. // The adjustments on the Print / PrintObject data due to set_task() are to be reverted here. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 8d94739..e7ae651 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -64,11 +64,11 @@ const std::vector filament_extruder_override_keys = { "filament_retraction_length", "filament_z_hop", "filament_z_hop_types", - "filament_retract_lift_above", - "filament_retract_lift_below", + "filament_retract_lift_above", //not in filament_options_with_variant, not used? + "filament_retract_lift_below", //not in filament_options_with_variant, not used? "filament_retraction_speed", "filament_deretraction_speed", - "filament_retract_restart_extra", + "filament_retract_restart_extra", //not in filament_options_with_variant, added on 20250816 "filament_retraction_minimum_travel", // QDS: floats "filament_wipe_distance", @@ -89,6 +89,14 @@ size_t get_extruder_index(const GCodeConfig& config, unsigned int filament_id) return 0; } +size_t get_config_idx_for_filament(const GCodeConfig& config, unsigned int filament_id) +{ + if (filament_id < config.filament_map_2.size()) { + return config.filament_map_2.get_at(filament_id); + } + 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; @@ -439,17 +447,24 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ExtruderType) static const t_config_enum_values s_keys_map_NozzleVolumeType = { { "Standard", nvtStandard }, - { "High Flow", nvtHighFlow } + { "High Flow", nvtHighFlow }, + { "Hybrid", nvtHybrid} }; 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 } + { "Manual", fmmManual }, + { "Nozzle Manual", fmmNozzleManual} }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FilamentMapMode) +static const t_config_enum_values s_keys_map_PrimeVolumeMode = { + { "Default", pvmDefault}, + { "Saving", pvmSaving} +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrimeVolumeMode) //QDS std::string get_extruder_variant_string(ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type) @@ -461,7 +476,7 @@ std::string get_extruder_variant_string(ExtruderType extruder_type, NozzleVolume //extruder_type = etDirectDrive; return variant_string; } - if (nozzle_volume_type > nvtMaxNozzleVolumeType) { + if (nozzle_volume_type >= nvtMaxNozzleVolumeType) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", unsupported NozzleVolumeType=%1%")%nozzle_volume_type; //extruder_type = etDirectDrive; return variant_string; @@ -503,6 +518,30 @@ std::vector> get_extruder_ams_count(const std::vector> get_extruder_nozzle_stats(const std::vector& strs) +{ + std::vector> extruder_nozzle_counts; + for (const std::string& str : strs) { + std::map nozzle_count_map; + if(str.empty()){ + extruder_nozzle_counts.emplace_back(nozzle_count_map); + continue; + } + std::vector nozzle_infos; + boost::algorithm::split(nozzle_infos, str, boost::is_any_of("|")); + for (auto& nozzle_info : nozzle_infos) { + std::vector attr; + boost::algorithm::split(attr, nozzle_info, boost::is_any_of("#")); + NozzleVolumeType volume_type = NozzleVolumeType(s_keys_map_NozzleVolumeType.at(attr[0])); + int nozzle_count = std::atoi(attr[1].c_str()); + nozzle_count_map[volume_type] = nozzle_count; + } + extruder_nozzle_counts.emplace_back(nozzle_count_map); + } + return extruder_nozzle_counts; +} + + std::vector save_extruder_ams_count_to_string(const std::vector> &extruder_ams_count) { std::vector extruder_ams_count_str; @@ -520,6 +559,22 @@ std::vector save_extruder_ams_count_to_string(const std::vector save_extruder_nozzle_stats_to_string(const std::vector>& extruder_nozzle_stats) +{ + std::vector extruder_nozzle_count_str; + for (size_t idx = 0; idx < extruder_nozzle_stats.size(); ++idx) { + std::ostringstream oss; + const auto& item = extruder_nozzle_stats[idx]; + for (auto it = item.begin(); it != item.end(); ++it) { + oss << get_nozzle_volume_type_string(it->first) << "#" << it->second; + if (std::next(it) != item.end()) + oss << "|"; + } + extruder_nozzle_count_str.emplace_back(oss.str()); + } + return extruder_nozzle_count_str; +} + //w12 static const t_config_enum_values s_keys_map_GCodeThumbnailsFormat = { { "PNG", int(GCodeThumbnailsFormat::PNG) }, @@ -746,7 +801,7 @@ void PrintConfigDef::init_fff_params() 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->tooltip = L("Smoothing outwall speed in z direction to get better surface quality. Print time will increases. This does not work on spiral vase mode."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); @@ -932,7 +987,7 @@ void PrintConfigDef::init_fff_params() def->multiline = true; def->full_width = true; def->height = 5; - def->mode = comAdvanced; + def->mode = comDevelop; def->set_default_value(new ConfigOptionString("")); def = this->add("bottom_shell_layers", coInt); @@ -1139,7 +1194,7 @@ void PrintConfigDef::init_fff_params() def->label = "75%"; def->category = L("Speed"); 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->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; @@ -1150,7 +1205,7 @@ void PrintConfigDef::init_fff_params() 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->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; @@ -1864,6 +1919,17 @@ void PrintConfigDef::init_fff_params() def->mode = comDevelop; def->set_default_value(new ConfigOptionInts{1}); + def = this->add("filament_map_2", coInts); + def->label = "Filament map plus for multi nozzle"; + def->tooltip = "Filament map to the index identified by extruder and nozzle_volume_type"; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInts{1}); + + def = this->add("filament_volume_map", coInts); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInts{(int)(NozzleVolumeType::nvtStandard)}); + + 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"; @@ -1877,14 +1943,20 @@ void PrintConfigDef::init_fff_params() 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("Nozzle 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->enum_labels.push_back(L("Nozzle Manual")); def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum(fmmAutoForFlush)); + def = this->add("filament_nozzle_map",coInts); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInts{1}); + def = this->add("filament_flush_temp", coInts); def->label = L("Flush temperature"); def->tooltip = L("temperature when flushing filament. 0 indicates the upper bound of the recommended nozzle temperature range"); @@ -1918,8 +1990,18 @@ void PrintConfigDef::init_fff_params() 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->label = L("Extruder change"); + def->tooltip = L("The maximum volumetric speed for ramming before extruder change, 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_ramming_volumetric_speed_nc", coFloats); + def->label = L("Hotend change"); + def->tooltip = L("The maximum volumetric speed for ramming before a hotend change, where -1 means using the maximum volumetric speed."); def->sidetext = L("mm鲁/s"); def->min = -1; def->max = 200; @@ -1963,6 +2045,17 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(5)); + def = this->add("machine_hotend_change_time", coFloat); + def->label = L("Hotend change time"); + def->tooltip = L("Time to change hotend."); + def->sidetext = L("s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.0)); + + def = this->add("group_algo_with_time", coBool); + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("machine_prepare_compensation_time", coFloat); def->mode = comDevelop; def->set_default_value(new ConfigOptionFloat(260)); @@ -1980,7 +2073,7 @@ void PrintConfigDef::init_fff_params() def = this->add("support_object_skip_flush", 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."); @@ -2082,7 +2175,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"); @@ -2141,13 +2234,21 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionFloats{10}); def = this->add("filament_change_length", coFloats); - def->label = L("Filament ramming length"); + def->label = L("Extruder change"); 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_change_length_nc", coFloats); + def->label = L("Hotend change"); + def->tooltip = L("When changing the hotend, it is recommended to extrude a certain length of filament from the original nozzle. 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"); @@ -2165,13 +2266,31 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionInts{3}); 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->label = L("Filament change"); + //def->tooltip = L("The volume of material to prime extruder on tower."); + def->tooltip = L("The volume of material required to prime the extruder on the tower, excluding a hotend change."); def->sidetext = L("mm鲁"); def->min = 1.0; def->mode = comSimple; def->set_default_value(new ConfigOptionFloats{45.}); + // QDS + def = this->add("filament_prime_volume_nc", coFloats); + def->label = L("Hotend change"); + def->tooltip = L("The volume of material required to prime the extruder for a hotend change on the tower."); + def->sidetext = L("mm鲁"); + def->min = 1.0; + def->mode = comSimple; + def->set_default_value(new ConfigOptionFloats{60.}); + + def = this->add("filament_cooling_before_tower", coFloats); + def->label = L("Wipe tower cooling"); + def->tooltip = L("Temperature drop before entering filament tower"); + def->sidetext = "掳C"; + def->mode = comDevelop; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{10}); + // QDS def = this->add("temperature_vitrification", coInts); def->label = L("Softening temperature"); @@ -2180,7 +2299,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionInts{ 100 }); def = this->add("filament_ramming_travel_time", coFloats); - def->label = L("Travel time after ramming"); + def->label = L("Extruder change"); 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; @@ -2189,8 +2308,18 @@ void PrintConfigDef::init_fff_params() def->nullable = true; def->set_default_value(new ConfigOptionFloatsNullable{0}); + def = this->add("filament_ramming_travel_time_nc", coFloats); + def->label = L("Hotend change"); + 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->label = L("Extruder change"); 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; @@ -2201,6 +2330,16 @@ void PrintConfigDef::init_fff_params() def->nullable = true; def->set_default_value(new ConfigOptionIntsNullable{0}); + def = this->add("filament_pre_cooling_temperature_nc", coInts); + def->label = L("Hotend change"); + def->tooltip = L( + "To prevent oozing, the nozzle temperature will be cooled during ramming. Note: only a cooldown command and fan activation are triggered, reaching the target temperature is not guaranteed. 0 means disabled."); + def->mode = comAdvanced; + def->sidetext = "掳C"; + def->min = 0; + 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"); @@ -2244,6 +2383,14 @@ void PrintConfigDef::init_fff_params() def->max = 100; def->set_default_value(new ConfigOptionPercent(20)); + def = this->add("fill_multiline", coInt); + def->label = L("Fill multiline"); + def->category = L("Strength"); + def->tooltip = L("Using multiple lines for the infill pattern, if supported by infill pattern."); + def->min = 1; + def->max = 5; + def->set_default_value(new ConfigOptionInt(1)); + def = this->add("sparse_infill_pattern", coEnum); def->label = L("Sparse infill pattern"); def->category = L("Strength"); @@ -2796,6 +2943,7 @@ void PrintConfigDef::init_fff_params() def->readonly=false; def = this->add("apply_top_surface_compensation", coBool); + def->label = L("Apply top surface compensation"); def->mode = comDevelop; def->set_default_value(new ConfigOptionBool(false)); @@ -2813,6 +2961,22 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("support_cooling_filter", coBool); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("cooling_filter_enabled", coBool); + def->label = L("Use cooling filter"); + def->tooltip = L("Enable this if printer support cooling filter"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("auto_disable_filter_on_overheat", coBool); + def->label = L("Auto turn off filter on overheat"); + def->tooltip = L("Enable this if printer support cooling filter"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("gcode_flavor", coEnum); def->label = L("G-code flavor"); def->tooltip = L("What kind of gcode the printer is compatible with"); @@ -3017,12 +3181,13 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionFloatOrPercent(20, false)); def = this->add("sparse_infill_filament", coInt); - def->label = L("Infill"); + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->label = L("Sparse infill filament"); def->category = L("Extruders"); def->tooltip = L("Filament to print internal sparse infill."); - def->min = 1; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(1)); + def->min = 0; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInt(0)); def = this->add("sparse_infill_line_width", coFloat); def->label = L("Sparse infill"); @@ -3277,6 +3442,11 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("")); + def = this->add("extruder_max_nozzle_count", coInts); + def->mode = comDevelop; + def->nullable = true; + def->set_default_value(new ConfigOptionIntsNullable{ 1 }); + def = this->add("has_scarf_joint_seam", coBool); def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); @@ -3607,12 +3777,13 @@ void PrintConfigDef::init_fff_params() //def->label = L("Walls"); //def->category = L("Extruders"); //def->tooltip = L("Filament to print walls"); - def->label = "Walls"; - def->category = "Extruders"; - def->tooltip = "Filament to print walls"; - def->min = 1; + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->label = L("Walls filament"); + def->category = L("Extruders"); + def->tooltip = L("Filament to print walls"); + def->min = 0; def->mode = comDevelop; - def->set_default_value(new ConfigOptionInt(1)); + def->set_default_value(new ConfigOptionInt(0)); def = this->add("inner_wall_line_width", coFloat); def->label = L("Inner wall"); @@ -3642,6 +3813,12 @@ void PrintConfigDef::init_fff_params() def->max = 1000; def->set_default_value(new ConfigOptionInt(2)); + def = this->add("embedding_wall_into_infill", coBool); + def->label = L("Embedding the wall into the infill"); + def->category = L("Strength"); + def->tooltip = L("Embedding the wall into parts where the wall loops are absent ensures that the wall connects seamlessly to the infill."); + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("post_process", coStrings); def->label = L("Post-processing Scripts"); def->tooltip = L("If you want to process the output G-code through custom scripts, " @@ -3898,8 +4075,10 @@ void PrintConfigDef::init_fff_params() 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_values.push_back("Hybrid"); def->enum_labels.push_back(L("Standard")); def->enum_labels.push_back(L("High Flow")); + def->enum_labels.push_back(L("Hybrid")); def->mode = comSimple; def->set_default_value(new ConfigOptionEnumsGeneric{ NozzleVolumeType::nvtStandard }); @@ -3909,8 +4088,10 @@ void PrintConfigDef::init_fff_params() 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_values.push_back(L("Hybrid")); def->enum_labels.push_back(L("Standard")); def->enum_labels.push_back(L("High Flow")); + def->enum_labels.push_back(L("Hybrid")); def->mode = comDevelop; def->set_default_value(new ConfigOptionEnumsGeneric{ NozzleVolumeType::nvtStandard }); @@ -3925,6 +4106,35 @@ void PrintConfigDef::init_fff_params() def->tooltip = "BOX counts of per extruder"; def->set_default_value(new ConfigOptionStrings { }); + def = this->add("extruder_nozzle_stats", coStrings); + def->set_default_value(new ConfigOptionStrings { }); + + def = this->add("prime_volume_mode", coEnum); + def->enum_values.push_back("Default"); + def->enum_values.push_back("Saving"); + def->enum_labels.push_back(L("Default")); + def->enum_labels.push_back(L("Saving")); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->set_default_value(new ConfigOptionEnum{ PrimeVolumeMode::pvmDefault }); + + + def = this->add("extruder_nozzle_count", coInts); + def->label = "extruder nozzle count"; + def->tooltip = "extruder nozzle count"; + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInts{1}); + + def = this->add("extruder_nozzle_volume_type", coEnums); + def->label = "extruder nozzle volume type"; + def->tooltip = "extruder 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 = comDevelop; + def->set_default_value(new ConfigOptionEnumsGeneric{ NozzleVolumeType::nvtStandard }); + def = this->add("printer_extruder_id", coInts); def->label = "Printer extruder id"; def->tooltip = "Printer extruder id"; @@ -3972,6 +4182,17 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionInts { 1 }); def->cli = ConfigOptionDef::nocli; + def = this->add("filament_retract_length_nc", coFloats); + def->label = L("length when change hotend"); + def->tooltip = L("When this retraction value is modified, it will be used as the amount of filament retracted " + "inside the hotend before changing hotends."); + def->sidetext = L("mm"); + def->mode = comDevelop; + def->nullable = true; + def->min = 0; + def->max = 18; + def->set_default_value(new ConfigOptionFloatsNullable { 10. }); + def = this->add("retract_restart_extra", coFloats); def->label = L("Extra length on restart"); //def->label = "Extra length on restart"; @@ -4214,12 +4435,13 @@ void PrintConfigDef::init_fff_params() //def->label = L("Solid infill"); //def->category = L("Extruders"); //def->tooltip = L("Filament to print solid infill"); - def->label = "Solid infill"; - def->category = "Extruders"; - def->tooltip = "Filament to print solid infill"; - def->min = 1; + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->label = L("Solid infill filament"); + def->category = L("Extruders"); + def->tooltip = L("Filament to print solid infill"); + def->min = 0; def->mode = comDevelop; - def->set_default_value(new ConfigOptionInt(1)); + def->set_default_value(new ConfigOptionInt(0)); def = this->add("internal_solid_infill_line_width", coFloat); def->label = L("Internal solid infill"); @@ -4675,7 +4897,7 @@ void PrintConfigDef::init_fff_params() def->category = L("Support"); def->tooltip = L("When top z distance overrides support/object xy distance, give priority to ensuring that supports are generated beneath overhangs, " "and a gap of the same size as top z distance is leaved with the model. Whereas in the opposite case, the gap between supports " - "and the model follows support/object xy distance all the time"); + "and the model follows support/object xy distance all the time. Only recommended to enable when using HybridTree."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); @@ -4897,6 +5119,12 @@ void PrintConfigDef::init_fff_params() def->min = 1; def->set_default_value(new ConfigOptionInt(3)); + def = this->add("infill_instead_top_bottom_surfaces", coBool); + def->label = L("Use infill instead of top and bottom surfaces"); + def->category = L("Strength"); + def->tooltip = L("Using infill instead of top and bottom surfaces."); + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("travel_speed", coFloats); def->label = L("Travel"); def->tooltip = L("Speed of travel which is faster and without extrusion"); @@ -5421,7 +5649,7 @@ void PrintConfigDef::init_filament_option_keys() m_filament_option_keys = { "filament_diameter", "min_layer_height", "max_layer_height","volumetric_speed_coefficients", "retraction_length", "z_hop", "z_hop_types", "retraction_speed", "deretraction_speed", - "retract_before_wipe", "retract_restart_extra", "retraction_minimum_travel", "wipe", "wipe_distance", + "retract_before_wipe", "filament_retract_length_nc","retract_restart_extra", "retraction_minimum_travel", "wipe", "wipe_distance", "retract_when_changing_layer", "retract_length_toolchange", "retract_restart_extra_toolchange", "filament_colour", "default_filament_profile","retraction_distances_when_cut","long_retractions_when_cut" }; @@ -5430,6 +5658,7 @@ void PrintConfigDef::init_filament_option_keys() "deretraction_speed", "long_retractions_when_cut", "retract_before_wipe", + "filament_retract_length_nc", "retract_restart_extra", "retract_when_changing_layer", "retraction_distances_when_cut", @@ -6335,9 +6564,13 @@ std::set filament_options_with_variant = { "filament_ramming_volumetric_speed", "filament_pre_cooling_temperature", "filament_ramming_travel_time", + "filament_ramming_volumetric_speed_nc", + "filament_pre_cooling_temperature_nc", + "filament_ramming_travel_time_nc", //"filament_extruder_id", "filament_extruder_variant", "filament_retraction_length", + "filament_retract_length_nc", "filament_z_hop", "filament_z_hop_types", "filament_retract_restart_extra", @@ -6358,7 +6591,8 @@ std::set filament_options_with_variant = { "filament_flush_volumetric_speed", "filament_flush_temp", "volumetric_speed_coefficients", - "filament_adaptive_volumetric_speed" + "filament_adaptive_volumetric_speed", + "filament_cooling_before_tower" }; // Parameters that are the same as the number of extruders @@ -6369,7 +6603,8 @@ std::set printer_extruder_options = { "extruder_printable_area", "extruder_printable_height", "min_layer_height", - "max_layer_height" + "max_layer_height", + "extruder_max_nozzle_count" }; std::set printer_options_with_variant_1 = { @@ -6463,7 +6698,7 @@ double min_object_distance(const ConfigBase &cfg) return ret; } -void DynamicPrintConfig::normalize_fdm(int used_filaments) +void DynamicPrintConfig::normalize_fdm() { if (this->has("extruder")) { int extruder = this->option("extruder")->getInt(); @@ -6506,32 +6741,6 @@ void DynamicPrintConfig::normalize_fdm(int used_filaments) // Resolution will be above 1um. opt_gcode_resolution->value = std::max(opt_gcode_resolution->value, 0.001); - // QDS - ConfigOptionBool* ept_opt = this->option("enable_prime_tower"); - if (used_filaments > 0 && ept_opt != nullptr) { - ConfigOptionBool* islh_opt = this->option("independent_support_layer_height", true); - //ConfigOptionBool* alh_opt = this->option("adaptive_layer_height"); - ConfigOptionEnum* ps_opt = this->option>("print_sequence"); - - ConfigOptionEnum* timelapse_opt = this->option>("timelapse_type"); - bool is_smooth_timelapse = timelapse_opt != nullptr && timelapse_opt->value == TimelapseType::tlSmooth; - if (!is_smooth_timelapse && (used_filaments == 1 || ps_opt->value == PrintSequence::ByObject)) { - ept_opt->value = false; - } - - if (ept_opt->value) { - if (islh_opt) - islh_opt->value = false; - //if (alh_opt) - // alh_opt->value = false; - } - /* QDS: MusangKing - not sure if this is still valid, just comment it out cause "Independent support layer height" is re-opened. - else { - if (islh_opt) - islh_opt->value = true; - } - */ - } } //QDS:divide normalize_fdm to 2 steps and call them one by one in Print::Apply @@ -6874,6 +7083,9 @@ int DynamicPrintConfig::get_index_for_extruder(int extruder_or_filament_id, std: if (variant_opt != nullptr) { int v_size = variant_opt->values.size(); //int i_size = id_opt->values.size(); + // nvtHybrid not supported in presets, switch to nvtStandard to match the preset values + if (nozzle_volume_type == nvtHybrid) + nozzle_volume_type = nvtStandard; std::string extruder_variant = get_extruder_variant_string(extruder_type, nozzle_volume_type); for (int index = 0; index < v_size; index++) { @@ -7287,7 +7499,7 @@ int DynamicPrintConfig::update_values_from_multi_to_multi_2(const std::vector::max(); for(auto idx : indices){ - if(opt && !opt->is_nil(idx)){ + if(opt && idx < opt->values.size() && !opt->is_nil(idx)){ has_value = true; target_value = std::min(target_value, src_values[idx]); } @@ -7474,27 +7686,84 @@ DynamicPrintConfig::get_filament_type() const 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 DynamicPrintConfig::get_extruder_nozzle_volume_count(int extruder_count, std::vector>& nozzle_volume_types) const { - int extruder_count; - bool different_extruder = printer_config.support_different_extruders(extruder_count); - std::vector variant_index; + int count = extruder_count; + auto opt_extruder_nozzle_count = dynamic_cast(this->option("extruder_nozzle_stats")); + nozzle_volume_types.resize(extruder_count, std::vector{}); + if (opt_extruder_nozzle_count && (opt_extruder_nozzle_count->values.size() == extruder_count)) { + std::vector extruder_nozzle_count_strs = opt_extruder_nozzle_count->values; + std::vector> extruder_nozzle_counts; - if ((extruder_count > 1) || different_extruder) + extruder_nozzle_counts = get_extruder_nozzle_stats(extruder_nozzle_count_strs); + count = 0; + for (int i = 0; i < extruder_count; i++) + { + count += extruder_nozzle_counts[i].size(); + for (auto& iter: extruder_nozzle_counts[i]) + nozzle_volume_types[i].push_back(iter.first); + } + } + /*auto opt_extruder_nozzle_volume_types = dynamic_cast(this->option("extruder_nozzle_volume_type")); + if (opt_extruder_nozzle_count && opt_extruder_nozzle_volume_types + && (opt_extruder_nozzle_count->values.size() == extruder_count)) { + count = 0; + for (int i = 0; i < extruder_count; i++) + { + if (opt_extruder_nozzle_count->values[i] == 1) + count += 1; + else { + std::unordered_set unique(opt_extruder_nozzle_volume_types->values.begin() + count, + opt_extruder_nozzle_volume_types->values.begin() + count + opt_extruder_nozzle_count->values[i]); + count += unique.size(); + } + } + }*/ + return count; +} + + +std::vector DynamicPrintConfig::update_values_to_printer_extruders(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::vector>& nv_types, + std::set& key_set, std::string id_name, std::string variant_name, unsigned int stride, unsigned int extruder_id, NozzleVolumeType filament_nvt) +{ + //int extruder_count; + //bool different_extruder = printer_config.support_different_extruders(extruder_count); + std::vector variant_index; + int variant_count = extruder_count; + + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: extruder_count %2%, extruder_nozzle_volume_count %3%")%__LINE__ %extruder_count %extruder_nozzle_volume_count; + + //if (extruder_nozzle_volume_count > 1) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: different extruders processing")%__LINE__; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: different nozzle volume 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)); + if (nozzle_volume_type == nvtHybrid) { + if (extruder_nozzle_volume_count > extruder_count) { + //use the one passed + nozzle_volume_type = filament_nvt; + } + else { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: nozzle_volume_type is default in unsupported machine.")%__LINE__; + assert(false); + } + } + else if (nozzle_volume_type != filament_nvt) { + if (extruder_nozzle_volume_count > extruder_count) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: nozzle_volume_type is %2%, not equal to filament_nvt %3%")%__LINE__ %nozzle_volume_type %filament_nvt; + //assert(false); + } + } + //variant index variant_index[0] = get_index_for_extruder(extruder_id, id_name, extruder_type, nozzle_volume_type, variant_name); @@ -7504,25 +7773,39 @@ std::vector DynamicPrintConfig::update_values_to_printer_extruders(DynamicP assert(false); } - extruder_count = 1; + variant_count = 1; } else { - variant_index.resize(extruder_count); + if (extruder_nozzle_volume_count > extruder_count){ + variant_count = extruder_nozzle_volume_count; + } + variant_index.resize(variant_count); + int v_index = 0; 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; + int nvt_count = 1; + if (extruder_nozzle_volume_count > extruder_count) { + nvt_count = nv_types[e_index].size(); + } + for (int nvt_index = 0; nvt_index < nvt_count; nvt_index++) + { + if (extruder_nozzle_volume_count > extruder_count) + nozzle_volume_type = nv_types[e_index][nvt_index]; + //variant index + variant_index[v_index] = get_index_for_extruder(e_index+1, id_name, extruder_type, nozzle_volume_type, variant_name); + if (variant_index[v_index] < 0) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: could not found extruder_type %2%, nozzle_volume_type %3%, extruder_index %4%, nvt_index %5%, nvt_count %6%") + %__LINE__ %s_keys_names_ExtruderType[extruder_type] % s_keys_names_NozzleVolumeType[nozzle_volume_type] % (e_index+1) %nvt_index %nvt_count; + assert(false); + //for some updates happens in a invalid state(caused by popup window) + //we need to avoid crash + variant_index[v_index] = 0; + } + v_index++; } } } @@ -7545,8 +7828,8 @@ std::vector DynamicPrintConfig::update_values_to_printer_extruders(DynamicP 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++) + new_values.resize(variant_count * stride); + for (int e_index = 0; e_index < variant_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); @@ -7559,8 +7842,8 @@ std::vector DynamicPrintConfig::update_values_to_printer_extruders(DynamicP 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++) + new_values.resize(variant_count * stride); + for (int e_index = 0; e_index < variant_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); @@ -7573,8 +7856,8 @@ std::vector DynamicPrintConfig::update_values_to_printer_extruders(DynamicP 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++) + new_values.resize(variant_count * stride); + for (int e_index = 0; e_index < variant_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); @@ -7587,8 +7870,8 @@ std::vector DynamicPrintConfig::update_values_to_printer_extruders(DynamicP 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++) + new_values.resize(variant_count * stride); + for (int e_index = 0; e_index < variant_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); @@ -7601,8 +7884,8 @@ std::vector DynamicPrintConfig::update_values_to_printer_extruders(DynamicP 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++) + new_values.resize(variant_count * stride); + for (int e_index = 0; e_index < variant_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); @@ -7615,8 +7898,8 @@ std::vector DynamicPrintConfig::update_values_to_printer_extruders(DynamicP 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++) + new_values.resize(variant_count * stride); + for (int e_index = 0; e_index < variant_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); @@ -7629,8 +7912,8 @@ std::vector DynamicPrintConfig::update_values_to_printer_extruders(DynamicP 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++) + new_values.resize(variant_count * stride); + for (int e_index = 0; e_index < variant_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); @@ -7648,13 +7931,16 @@ std::vector DynamicPrintConfig::update_values_to_printer_extruders(DynamicP 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) +void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filaments(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, 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) + //int extruder_count, extruder_volume_type_count; + //bool different_extruder = printer_config.support_different_extruders(extruder_count); + + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: extruder_count %2%, extruder_nozzle_volume_count %3%")%__LINE__ %extruder_count %extruder_nozzle_volume_count; + + //if (extruder_nozzle_volume_count > 1) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: extruder_count=%2%, different_extruder=%3%")%__LINE__ %extruder_count %different_extruder; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: different nozzle volume processing")%__LINE__; std::vector filament_maps = printer_config.option("filament_map")->values; size_t filament_count = filament_maps.size(); //apply process settings @@ -7662,6 +7948,11 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen //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")); + + auto opt_filament_volume_maps = dynamic_cast(printer_config.option("filament_volume_map")); + std::vector filament_volume_maps; + if (opt_filament_volume_maps) + filament_volume_maps = opt_filament_volume_maps->values; auto opt_ids = id_name.empty()? nullptr: dynamic_cast(this->option(id_name)); std::vector variant_index; @@ -7672,6 +7963,10 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen 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)); + if ((extruder_nozzle_volume_count > extruder_count)&&(!filament_volume_maps.empty())) { + nozzle_volume_type = (NozzleVolumeType)(filament_volume_maps[f_index]); + } + //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) { @@ -7983,7 +8278,7 @@ void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPr } 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) + t_config_option_keys& diff_keys, DynamicPrintConfig& filament_overrides, std::vector& f_map_indices) { bool is_nil = opt_new_filament->is_nil(); @@ -8005,7 +8300,7 @@ void compute_filament_override_value(const std::string& opt_key, const ConfigOpt } auto opt_copy = opt_new_machine->clone(); - opt_copy->apply_override(opt_new_filament, f_maps); + opt_copy->apply_override(opt_new_filament, f_map_indices); bool changed = *opt_old_machine != *opt_copy; if (changed) { diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index ce8e317..1a5e405 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -269,6 +269,11 @@ enum FanDirection { fdBoth }; +enum PrimeVolumeMode { + pvmDefault = 0, + pvmSaving +}; + static std::unordered_mapNozzleTypeEumnToStr = { {NozzleType::ntUndefine, "undefine"}, {NozzleType::ntHardenedSteel, "hardened_steel"}, @@ -315,20 +320,22 @@ enum ExtruderType { enum NozzleVolumeType { nvtStandard = 0, nvtHighFlow, - nvtMaxNozzleVolumeType = nvtHighFlow + nvtHybrid, + nvtMaxNozzleVolumeType = nvtHybrid }; enum FilamentMapMode { fmmAutoForFlush, fmmAutoForMatch, fmmManual, + fmmNozzleManual, 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); +std::string get_nozzle_volume_type_string(NozzleVolumeType nozzle_volume_type); //w12 enum class GCodeThumbnailsFormat { PNG, Qidi @@ -407,6 +414,8 @@ extern const std::vector filament_extruder_override_keys; // 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); +extern std::vector> get_extruder_nozzle_stats(const std::vector & strs); +extern std::vector save_extruder_nozzle_stats_to_string(const std::vector> &extruder_nozzle_stats); #define CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NAME) \ template<> const t_config_enum_names& ConfigOptionEnum::get_enum_names(); \ @@ -512,7 +521,7 @@ public: // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. const ConfigDef* def() const override { return &print_config_def; } - void normalize_fdm(int used_filaments = 0); + void normalize_fdm(); void normalize_fdm_1(); //return the changed param set t_config_option_keys normalize_fdm_2(int num_objects, int used_filaments = 0); @@ -543,9 +552,11 @@ public: //QDS bool is_using_different_extruders(); bool support_different_extruders(int& extruder_count); + int get_extruder_nozzle_volume_count(int extruder_count, std::vector>& nozzle_volume_types) const; 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); + std::vector update_values_to_printer_extruders(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::vector>& nv_types, + std::set& key_set, std::string id_name, std::string variant_name, unsigned int stride = 1, unsigned int extruder_id = 0, NozzleVolumeType filament_nvt = nvtStandard); + void update_values_to_printer_extruders_for_multiple_filaments(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, 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); @@ -573,7 +584,7 @@ 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); + t_config_option_keys& diff_keys, DynamicPrintConfig& filament_overrides, std::vector& f_map_indices); void handle_legacy_sla(DynamicPrintConfig &config); @@ -931,6 +942,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionPercent, skeleton_infill_density)) ((ConfigOptionPercent, skin_infill_density)) ((ConfigOptionPercent, sparse_infill_density)) + ((ConfigOptionInt, fill_multiline)) ((ConfigOptionFloat, infill_lock_depth)) ((ConfigOptionFloat, skin_infill_depth)) ((ConfigOptionEnum, sparse_infill_pattern)) @@ -980,6 +992,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatsOrPercentsNullable, vertical_shell_speed)) ((ConfigOptionInt, top_color_penetration_layers)) ((ConfigOptionInt, bottom_color_penetration_layers)) + ((ConfigOptionBool, infill_instead_top_bottom_surfaces)) ((ConfigOptionEnum, wall_sequence)) //QDS ((ConfigOptionBoolsNullable, enable_overhang_speed)) @@ -1013,6 +1026,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, seam_slope_min_length)) ((ConfigOptionInt, seam_slope_steps)) ((ConfigOptionBool, seam_slope_inner_walls)) + ((ConfigOptionBool, embedding_wall_into_infill)) //w16 //y58 ((ConfigOptionBools, resonance_avoidance)) ((ConfigOptionFloatsNullable, min_resonance_avoidance_speed)) @@ -1080,6 +1094,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatsOrPercents, filament_scarf_gap)) ((ConfigOptionFloats, filament_scarf_length)) ((ConfigOptionFloats, filament_change_length)) + ((ConfigOptionFloats, filament_change_length_nc)) ((ConfigOptionFloats, filament_cost)) ((ConfigOptionFloats, impact_strength_z)) ((ConfigOptionString, filament_notes)) @@ -1088,17 +1103,26 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatsNullable, filament_ramming_travel_time)) //QDS ((ConfigOptionIntsNullable, filament_pre_cooling_temperature))// QDS ((ConfigOptionFloatsNullable, filament_max_volumetric_speed)) - ((ConfigOptionFloatsNullable, filament_ramming_volumetric_speed)) + ((ConfigOptionFloatsNullable, filament_ramming_volumetric_speed))//extruder change + ((ConfigOptionFloatsNullable, filament_ramming_travel_time_nc))//nc:nozzle change + ((ConfigOptionIntsNullable, filament_pre_cooling_temperature_nc)) + ((ConfigOptionFloatsNullable, filament_max_volumetric_speed_nc)) + ((ConfigOptionFloatsNullable, filament_ramming_volumetric_speed_nc)) ((ConfigOptionFloat, prime_tower_lift_speed)) ((ConfigOptionFloat, prime_tower_lift_height)) ((ConfigOptionInts, required_nozzle_HRC)) ((ConfigOptionEnum, filament_map_mode)) ((ConfigOptionInts, filament_map)) + ((ConfigOptionInts, filament_volume_map)) + ((ConfigOptionInts, filament_nozzle_map)) + ((ConfigOptionInts, filament_map_2)) //used for multi nozzle, map filament to the index identified by extruder+nozzle_volume_type //((ConfigOptionInts, filament_extruder_id)) ((ConfigOptionStrings, filament_extruder_variant)) ((ConfigOptionFloat, machine_load_filament_time)) ((ConfigOptionFloat, machine_unload_filament_time)) ((ConfigOptionFloat, machine_switch_extruder_time)) + ((ConfigOptionFloat, machine_hotend_change_time)) + ((ConfigOptionBool, group_algo_with_time)) ((ConfigOptionFloat, machine_prepare_compensation_time)) ((ConfigOptionBool, enable_pre_heating)) ((ConfigOptionBool, support_object_skip_flush)) @@ -1139,6 +1163,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatsNullable, z_hop)) // QDS ((ConfigOptionEnumsGenericNullable,z_hop_types)) + ((ConfigOptionFloatsNullable, filament_retract_length_nc)) ((ConfigOptionFloatsNullable, retract_restart_extra)) ((ConfigOptionFloatsNullable, retract_restart_extra_toolchange)) ((ConfigOptionFloatsNullable, retraction_speed)) @@ -1166,11 +1191,17 @@ PRINT_CONFIG_CLASS_DEFINE( //y58 ((ConfigOptionBool, support_box_temp_control)) ((ConfigOptionBool, support_air_filtration)) + ((ConfigOptionBool, support_cooling_filter)) + ((ConfigOptionBool, cooling_filter_enabled)) + ((ConfigOptionBool, auto_disable_filter_on_overheat)) + ((ConfigOptionIntsNullable, extruder_max_nozzle_count)) ((ConfigOptionBool, accel_to_decel_enable)) ((ConfigOptionPercent, accel_to_decel_factor)) ((ConfigOptionEnumsGeneric, extruder_type)) ((ConfigOptionEnumsGeneric, nozzle_volume_type)) ((ConfigOptionStrings, extruder_ams_count)) + ((ConfigOptionStrings, extruder_nozzle_stats)) + ((ConfigOptionEnum,prime_volume_mode)) ((ConfigOptionInts, printer_extruder_id)) ((ConfigOptionInt, master_extruder_id)) ((ConfigOptionStrings, printer_extruder_variant)) @@ -1354,6 +1385,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloats, hole_limit_min)) ((ConfigOptionFloats, hole_limit_max)) ((ConfigOptionFloats, filament_prime_volume)) + ((ConfigOptionFloats, filament_prime_volume_nc)) + ((ConfigOptionFloatsNullable, filament_cooling_before_tower)) //y61 ((ConfigOptionString, box_id)) ((ConfigOptionBool, is_support_timelapse)) @@ -1779,6 +1812,7 @@ static void set_flush_volumes_matrix(std::vector &out_matrix, const std::vect } size_t get_extruder_index(const GCodeConfig& config, unsigned int filament_id); +size_t get_config_idx_for_filament(const GCodeConfig& config, unsigned int filament_id); } // namespace Slic3r diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index db1a20a..45ec286 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -315,6 +315,61 @@ std::vector> PrintObject::detect_extruder_geometric_unprintables() return geometric_unprintables; } + + +std::unordered_map> PrintObject::calc_estimated_filament_print_time() const +{ + auto get_limit_from_volumetric_speed = [&](int filament_idx, int extruder_idx, double width, double height) { + double max_volumetric_speed = print()->config().filament_max_volumetric_speed.values[filament_idx]; + double flow_ratio = print()->config().filament_flow_ratio.values[filament_idx]; + + double mm3_per_mm = height * (width - height * (1 - PI / 4)) * flow_ratio; + return max_volumetric_speed / mm3_per_mm; + }; + + std::unordered_map> filament_print_time; + // Iterate through all layers and regions to calculate the print area for each filament. + int extruder_num = this->print()->config().nozzle_diameter.size(); + for (size_t idx = 0; idx < m_layers.size(); ++idx) { + auto layer = m_layers[idx]; + + for (auto layerm : layer->regions()) { + const auto& region = layerm->region(); + double sparse_width = region.config().sparse_infill_line_width; + double solid_width = region.config().internal_solid_infill_line_width; + double wall_width = (region.config().outer_wall_line_width + region.config().inner_wall_line_width) / 2.f; + + int wall_filament = region.config().wall_filament - 1; + int solid_infill_filament = region.config().solid_infill_filament - 1; + int sparse_infill_filament = region.config().sparse_infill_filament - 1; + + auto sparse_infill_surfaces = layerm->fill_surfaces.filter_by_type(stInternal); + double sparse_area = unscale_(unscale_(std::accumulate(sparse_infill_surfaces.begin(), sparse_infill_surfaces.end(), 0.0, + [](double val, const Surface* surface) { return val + surface->area(); }))); + double full_area = unscale_(unscale_(get_expolygons_area(layerm->raw_slices))); + double infill_area = unscale_(unscale_(get_expolygons_area(layerm->fill_expolygons))); + double solid_area = infill_area - sparse_area; + double wall_area = full_area - infill_area; + sparse_area *= region.config().sparse_infill_density.value / 100.0; + + for (int eidx = 0; eidx < extruder_num; ++eidx) { + double sparse_speed = std::min(region.config().sparse_infill_speed.values[eidx], get_limit_from_volumetric_speed(sparse_infill_filament, eidx, sparse_width, layer->height)); + double solid_speed = std::min(region.config().internal_solid_infill_speed.values[eidx], get_limit_from_volumetric_speed(solid_infill_filament, eidx, solid_width, layer->height)); + double wall_speed = std::min((region.config().inner_wall_speed.values[eidx] + region.config().outer_wall_speed.values[eidx]) / 2.0, get_limit_from_volumetric_speed(wall_filament, eidx, wall_width, layer->height)); + + double sparse_time = sparse_area / (sparse_speed * sparse_width); + double solid_time = solid_area / (solid_speed * solid_width); + double wall_time = wall_area / (wall_speed * wall_width); + + filament_print_time[solid_infill_filament][eidx] += solid_time; + filament_print_time[sparse_infill_filament][eidx] += sparse_time; + filament_print_time[wall_filament][eidx] += wall_time; + } + } + } + return filament_print_time; +} + // 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). @@ -1076,7 +1131,8 @@ bool PrintObject::invalidate_state_by_config_options( opt_key == "bottom_shell_layers" || opt_key == "top_shell_layers" || opt_key == "top_color_penetration_layers" - || opt_key == "bottom_color_penetration_layers") { + || opt_key == "bottom_color_penetration_layers" + || opt_key == "infill_instead_top_bottom_surfaces") { steps.emplace_back(posSlice); #if (0) @@ -1098,6 +1154,7 @@ bool PrintObject::invalidate_state_by_config_options( #endif } else if ( opt_key == "interface_shells" + || opt_key == "fill_multiline" || opt_key == "infill_combination" || opt_key == "bottom_shell_thickness" || opt_key == "top_shell_thickness" @@ -1284,7 +1341,7 @@ void PrintObject::reset_slice_surfaces(const std::vectorm_regions[region_id]; - if(layerm->region().config().sparse_infill_pattern == ipLockedZag){ + if (layerm->region().config().infill_instead_top_bottom_surfaces && layerm->region().config().sparse_infill_pattern == ipLockedZag) { layerm->slices = slice_surfaces_cpy[idx_layer][region_id]; ExPolygons exps; layerm->fill_surfaces.keep_type(SurfaceType::stInternal, exps); @@ -1348,7 +1405,7 @@ void PrintObject::detect_surfaces_type(std::vectorm_regions[region_id]; slice_surfaces_cpy[idx_layer].resize(layer->m_regions.size()); //record surface data - if(layerm->region().config().sparse_infill_pattern == ipLockedZag) { + if (layerm->region().config().infill_instead_top_bottom_surfaces && layerm->region().config().sparse_infill_pattern == ipLockedZag) { // layerm->fill_surfaces_copy = layerm->fill_expolygons; slice_surfaces_cpy[idx_layer][region_id] = layerm->slices; } @@ -2877,7 +2934,16 @@ void PrintObject::bridge_over_infill() static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders) { - if (opt.value > (int)num_extruders) + if (opt.value > (int) num_extruders) + // assign the default extruder + opt.value = 1; +} +static void clamp_exturder_to_default_protect0(ConfigOptionInt &opt, size_t num_extruders) +{ + if (opt.value > (int) num_extruders) + // assign the default extruder + opt.value = 1; + else if (opt.value < 1) // assign the default extruder opt.value = 1; } @@ -2916,9 +2982,10 @@ static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPr if (ConfigOption* my_opt = out.option(it->first, false); my_opt != nullptr) { if (one_of(it->first, keys_extruders)) { // Ignore "default" extruders. - int extruder = static_cast(it->second.get())->value; - if (extruder > 0) - my_opt->setInt(extruder); + // QDS: 2025-10-20 skip these filament settings, they will be processed out of this func + //int extruder = static_cast(it->second.get())->value; + //if (extruder > 0) + // my_opt->setInt(extruder); } 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())) @@ -2952,10 +3019,38 @@ PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &defau assert(volume.is_model_part()); apply_to_print_region_config(config, *layer_range_config, variant_index); } + + {//over write the seprate filament for features config + auto resolve_filament_value = [&](const std::string &key, int default_or_parent, int previous_value) -> int { + int filament_value_temp = 0; + auto *opt_vol = volume.config.get().opt(key); + auto *opt_obj = volume.get_object()->config.get().opt(key); + if (opt_vol) // deside use which value + filament_value_temp = opt_vol->value; + else if (opt_obj) + filament_value_temp = opt_obj->value; + + if (layer_range_config != nullptr && volume.is_model_part()) { + auto *opt = layer_range_config->opt(key); + if (opt) filament_value_temp = opt->value; + } + + if (filament_value_temp > 0) + return filament_value_temp; + else + return previous_value; + }; + config.wall_filament.value = resolve_filament_value("wall_filament", default_or_parent_region_config.wall_filament.value, config.wall_filament.value); + config.sparse_infill_filament.value = resolve_filament_value("sparse_infill_filament", default_or_parent_region_config.sparse_infill_filament.value, + config.sparse_infill_filament.value); + config.solid_infill_filament.value = resolve_filament_value("solid_infill_filament", default_or_parent_region_config.solid_infill_filament.value, + config.solid_infill_filament.value); + } + // Clamp invalid extruders to the default extruder (with index 1). - clamp_exturder_to_default(config.sparse_infill_filament, num_extruders); - clamp_exturder_to_default(config.wall_filament, num_extruders); - clamp_exturder_to_default(config.solid_infill_filament, num_extruders); + clamp_exturder_to_default_protect0(config.sparse_infill_filament, num_extruders); + clamp_exturder_to_default_protect0(config.wall_filament, num_extruders); + clamp_exturder_to_default_protect0(config.solid_infill_filament, num_extruders); if (config.sparse_infill_density.value < 0.00011f) // Switch of infill for very low infill rates, also avoid division by zero in infill generator for these very low rates. // See GH issue #5910. @@ -3073,7 +3168,9 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_parameters.object_print_z_max + slicing_parameters.object_print_z_min) > 1e-3)) layer_height_profile.clear(); - if (layer_height_profile.empty() || layer_height_profile[1] != slicing_parameters.first_object_layer_height) { + bool not_match_flag = !slicing_parameters.has_raft(); // if there is raft layer_height_profile[1] could also be adaptive + not_match_flag &= !layer_height_profile.empty() && (layer_height_profile[1] != slicing_parameters.first_object_layer_height); + if (layer_height_profile.empty() || not_match_flag) { //layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_config_ranges, model_object.volumes); layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_config_ranges); updated = true; diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 2414d03..7cfbea4 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -1180,6 +1180,9 @@ void PrintObject::slice_volumes() InterlockingGenerator::generate_interlocking_structure(this); m_print->throw_if_canceled(); + InterlockingGenerator::generate_embedding_wall(this); + m_print->throw_if_canceled(); + // SuperSlicer: filament shrink for (Layer *layer : m_layers) { for (size_t i = 0; i < layer->region_count(); ++i) { diff --git a/src/libslic3r/ProjectTask.hpp b/src/libslic3r/ProjectTask.hpp index 73b2164..41d8c42 100644 --- a/src/libslic3r/ProjectTask.hpp +++ b/src/libslic3r/ProjectTask.hpp @@ -51,6 +51,11 @@ struct FilamentInfo std::vector colors = std::vector(); int mapping_result = 0; + /*for multi nozzle*/ + int group_id {-1}; + double nozzle_diameter{0}; + std::string nozzle_volume_type; + /*for new ams mapping*/ std::string ams_id; std::string slot_id; diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index 27b9df0..ea103b2 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -89,8 +89,9 @@ struct SupportParameters { external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(object, frExternalPerimeter, slicing_params.layer_height).width())); bridge_flow_ratio += region.config().bridge_flow; } - this->gap_xy = !print_config.top_z_overrides_xy_distance ? object_config.support_object_xy_distance.value : - std::min(object_config.support_object_xy_distance.value, object_config.support_top_z_distance.value); + this->gap_xy = !print_config.top_z_overrides_xy_distance ? + object_config.support_object_xy_distance.value : + std::min(object_config.support_object_xy_distance.value, std::max(0.2, object_config.support_top_z_distance.value)); this->gap_xy_first_layer = object_config.support_object_first_layer_gap.value; bridge_flow_ratio /= object.num_printing_regions(); diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index 8450346..22e0aa6 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -2233,9 +2233,7 @@ void TreeSupport::draw_circles() bool on_buildplate_only = m_object_config->support_on_build_plate_only.value; Polygon branch_circle; //Pre-generate a circle with correct diameter so that we don't have to recompute those (co)sines every time. - double orig_xy_distance = m_ts_data->m_xy_distance; - if (print->config().top_z_overrides_xy_distance) - m_ts_data->m_xy_distance = std::min(m_ts_data->m_xy_distance, double(top_z_distance)); + const bool z_overrides = print->config().top_z_overrides_xy_distance; // Use square support if there are too many nodes per layer because circle support needs much longer time to compute // Hower circle support can be printed faster, so we prefer circle for fewer nodes case. @@ -2369,7 +2367,7 @@ void TreeSupport::draw_circles() if (node.overhang.contour.size() > 100 || node.overhang.holes.size() > 1) area.emplace_back(node.overhang); else { - area = offset_ex({node.overhang}, scale_(orig_xy_distance)); + area = offset_ex({node.overhang}, scale_(m_ts_data->m_xy_distance)); } } else { Polygon circle(branch_circle); @@ -2439,9 +2437,10 @@ void TreeSupport::draw_circles() //m_object->print()->set_status(65, (boost::format( _u8L("Support: generate polygons at layer %d")) % layer_nr).str()); // join roof segments - roof_areas = diff_clipped(offset2_ex(roof_areas, line_width_scaled, -line_width_scaled), get_collision(false)); + roof_areas = diff_clipped(offset2_ex(roof_areas, line_width_scaled, -line_width_scaled), get_collision(z_overrides)); roof_areas = intersection_ex(roof_areas, m_machine_border); - roof_1st_layer = diff_clipped(offset2_ex(roof_1st_layer, line_width_scaled, -line_width_scaled), get_collision(false)); + roof_1st_layer = diff_clipped(offset2_ex(roof_1st_layer, line_width_scaled, -line_width_scaled), + z_overrides ? offset_ex(get_collision(z_overrides), line_width_scaled / 2) : get_collision(false)); // roof_1st_layer and roof_areas may intersect, so need to subtract roof_areas from roof_1st_layer roof_1st_layer = diff_ex(roof_1st_layer, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roof_areas,get_extents(roof_1st_layer))); @@ -3899,6 +3898,7 @@ void TreeSupport::generate_contact_points() return contact_node; }; + auto extrudable_collision = offset_ex(layer->lower_layer->lslices_extrudable, m_ts_data->m_xy_distance); 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; @@ -3946,7 +3946,8 @@ void TreeSupport::generate_contact_points() overhangs_regular = overhangs; } - overhangs_regular = diff_ex(overhangs_regular, relevant_forbidden); + if (!is_sharp_tail && layer->lower_layer) + overhangs_regular = diff_ex(overhangs_regular, extrudable_collision); for (auto &overhang : overhangs_regular) { if (is_sharp_tail && !m_support_params.soluble_interface && overhang.area() < SQ(scale_(2.))) add_interface = false; BoundingBox overhang_bounds = get_extents(overhang); diff --git a/src/libslic3r/Support/TreeSupportCommon.hpp b/src/libslic3r/Support/TreeSupportCommon.hpp index c61ddb7..2d2c1ba 100644 --- a/src/libslic3r/Support/TreeSupportCommon.hpp +++ b/src/libslic3r/Support/TreeSupportCommon.hpp @@ -65,7 +65,7 @@ struct TreeSupportMeshGroupSettings { this->support_bottom_distance = scaled(slicing_params.gap_object_support); this->support_xy_distance = scaled(std::max(0.01, config.support_object_xy_distance.value)); if (print_config.top_z_overrides_xy_distance) - this->support_xy_distance = std::min(this->support_xy_distance, std::max(this->support_top_distance, coord_t(scale_(0.01)))); + this->support_xy_distance = std::min(this->support_xy_distance, std::max(this->support_top_distance, coord_t(scale_(0.2)))); 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)); @@ -79,7 +79,7 @@ struct TreeSupportMeshGroupSettings { this->support_roof_line_distance = scaled(config.support_interface_spacing.value) + this->support_roof_line_width; double support_tree_angle_slow = 25;// TODO add a setting? double tree_support_tip_diameter = 0.8; - this->support_tree_branch_distance = scaled(config.tree_support_branch_distance.value); + this->support_tree_branch_distance = scaled(config.tree_support_branch_distance.value); this->support_tree_angle = std::clamp(config.tree_support_branch_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON); this->support_tree_angle_slow = std::clamp(support_tree_angle_slow * M_PI / 180., 0., this->support_tree_angle - EPSILON); this->support_tree_branch_diameter = scaled(config.tree_support_branch_diameter.value); @@ -736,5 +736,6 @@ enum class LineStatus TO_BP_SAFE }; + } // namespace TreeSupport3D } // namespace slic3r \ No newline at end of file diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 1cf1838..ff1055a 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -912,6 +912,20 @@ Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const Transfor return its_convex_hull_2d_above(its, [t](const Vec3f &p){ return t * p; }, z); } +indexed_triangle_set its_make_xoy_center_rect(float width, float height, float depth) +{ + float x = width / 2.f, y = height /2.f, z = 0.f; + if (depth > 0.01f) { + z = depth / 2.f; + return {{{0, 3, 2}, {0, 2, 1}, {4, 5, 6}, {4, 6, 7}, {0, 4, 7}, {0, 7, 3}, {7, 6,2}, {7, 2,3}, {2, 6, 5}, {2, 5, 1}, {1, 5, 4}, {1, 4, 0}}, + {{-x, -y, -z}, {x, -y, -z}, {x, y, -z}, {-x, y, -z}, + {-x, -y, z}, {x, -y, z}, {x, y, z}, {-x, y, z}} + }; + } else { + return {{{0, 1, 2}, {0, 2, 3}}, {{-x, -y, z}, {x, -y, z}, {x, y, z}, {-x, y, z}}}; + } +} + // Generate the vertex list for a cube solid of arbitrary size in X/Y/Z. indexed_triangle_set its_make_cube(double xd, double yd, double zd) { diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index cdb4cf9..79ea23f 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -334,6 +334,7 @@ inline Vec3f its_face_normal(const indexed_triangle_set &its, const stl_triangle inline Vec3f its_face_normal(const indexed_triangle_set &its, const int face_idx) { return its_face_normal(its, its.indices[face_idx]); } +indexed_triangle_set its_make_xoy_center_rect(float width,float height,float depth =0.f); indexed_triangle_set its_make_cube(double x, double y, double z); indexed_triangle_set its_make_prism(float width, float length, float height); indexed_triangle_set its_make_cylinder(double r, double h, double fa=(2*PI/360)); diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 812bbd6..3e7d0b2 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -100,6 +100,8 @@ const std::string& var_dir(); // Return a full resource path for a file_name. std::string var(const std::string &file_name); +std::string format_diameter_to_str(double diameter, int precision = 1); + // Set a path with various static definition data (for example the initial config bundles). void set_resources_dir(const std::string &path); // Return a full path to the resources directory. @@ -766,15 +768,15 @@ inline std::string get_qdt_monitor_time_dhm(float time_in_secs) time_in_secs -= (float)days * 86400.0f; int hours = (int)(time_in_secs / 3600.0f); time_in_secs -= (float)hours * 3600.0f; - int minutes = (int)(time_in_secs / 60.0f); + int minutes = (int) std::ceil(time_in_secs / 60.0f); char buffer[64]; if (days > 0) - ::sprintf(buffer, "%dd%dh%dm", days, hours, minutes); + ::sprintf(buffer, "%dd%dh%dmin", days, hours, minutes); else if (hours > 0) - ::sprintf(buffer, "%dh%dm", hours, minutes); + ::sprintf(buffer, "%dh%dmin", hours, minutes); else if (minutes >= 0) - ::sprintf(buffer, "%dm", minutes); + ::sprintf(buffer, "%dmin", minutes); else { return ""; } @@ -827,7 +829,7 @@ inline std::string get_qdt_remain_time_dhms(float time_in_secs) time_in_secs -= (float) days * 86400.0f; int hours = (int) (time_in_secs / 3600.0f); time_in_secs -= (float) hours * 3600.0f; - int minutes = (int) (time_in_secs / 60.0f); + int minutes = (int) std::ceil(time_in_secs / 60.0f); time_in_secs -= (float) minutes * 60.0f; char buffer[64]; diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index e63b4ff..5656440 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -1340,6 +1340,15 @@ std::string format_memsize_MB(size_t n) return out + "MB"; } +std::string format_diameter_to_str(double diameter, int precision) +{ + double candidates[] = {0.2, 0.4, 0.6, 0.8}; + double best = *std::min_element(std::begin(candidates), std::end(candidates), [diameter](double a, double b) { return std::abs(a - diameter) < std::abs(b - diameter); }); + std::ostringstream oss; + oss << std::fixed << std::setprecision(precision) << best; + return oss.str(); +} + // Returns platform-specific string to be used as log output or parsed in SysInfoDialog. // The latter parses the string with (semi)colons as separators, it should look about as // "desc1: value1; desc2: value2" or similar (spaces should not matter).