diff --git a/src/libslic3r/GCode/AdaptivePAProcessor.cpp b/src/libslic3r/GCode/AdaptivePAProcessor.cpp index 051d33321a..80d6b034ad 100644 --- a/src/libslic3r/GCode/AdaptivePAProcessor.cpp +++ b/src/libslic3r/GCode/AdaptivePAProcessor.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace Slic3r { @@ -282,4 +283,81 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) { return output.str(); } +std::string AdaptivePAProcessor::validate_adaptive_pa_model(const std::string& model_str) +{ + if (model_str.empty()) + return {}; // Empty model is valid + + std::istringstream model_stream(model_str); + std::string line; + int line_number = 0; + + while (std::getline(model_stream, line)) { + ++line_number; + + // Trim whitespace + const auto first = line.find_first_not_of(" \t\r\n"); + if (first == std::string::npos) + continue; // Skip empty lines + + const auto last = line.find_last_not_of(" \t\r\n"); + line = line.substr(first, last - first + 1); + + // Only numbers, commas and dots are allowed (no letters or other characters) + for (char c : line) { + if (!std::isdigit(static_cast(c)) && c != ',' && c != '.') { + return "Line " + std::to_string(line_number) + + ": only numbers, commas and dots are allowed"; + } + } + + // Count commas to validate format (should be exactly 2 for 3 values) + int comma_count = 0; + for (char c : line) { + if (c == ',') comma_count++; + } + + if (comma_count != 2) { + return "Line " + std::to_string(line_number) + + ": must contain exactly 3 comma-separated values (PA, flow, acceleration)"; + } + + // Parse and validate the values + try { + std::istringstream line_stream(line); + std::string value; + + // Parse PA + if (!std::getline(line_stream, value, ',')) + return "Line " + std::to_string(line_number) + ": missing PA value"; + double pa = std::stod(value); + + // Parse flow + if (!std::getline(line_stream, value, ',')) + return "Line " + std::to_string(line_number) + ": missing flow value"; + double flow = std::stod(value); + + // Parse acceleration + if (!std::getline(line_stream, value, ',')) + return "Line " + std::to_string(line_number) + ": missing acceleration value"; + double accel = std::stod(value); + + // Validate constraints + if (pa >= 2.0) { + return "Line " + std::to_string(line_number) + ": PA value must be less than 2"; + } + if (flow <= pa) { + return "Line " + std::to_string(line_number) + ": flow value must be greater than PA value"; + } + if (accel <= flow) { + return "Line " + std::to_string(line_number) + ": acceleration value must be greater than flow value"; + } + } catch (const std::exception&) { + return "Line " + std::to_string(line_number) + ": invalid numeric value"; + } + } + + return {}; // All validations passed +} + } // namespace Slic3r diff --git a/src/libslic3r/GCode/AdaptivePAProcessor.hpp b/src/libslic3r/GCode/AdaptivePAProcessor.hpp index b9d2ef15fb..33d4c26d24 100644 --- a/src/libslic3r/GCode/AdaptivePAProcessor.hpp +++ b/src/libslic3r/GCode/AdaptivePAProcessor.hpp @@ -54,6 +54,20 @@ public: */ void resetPreviousPA(double PA){ m_last_predicted_pa = PA; }; + /** + * @brief Validates an adaptive pressure advance model string. + * + * Checks that: + * - Each non-empty line has exactly 3 comma-separated values (PA, flow, accel) + * - PA value is less than 2 + * - Flow value is greater than PA value + * - Accel value is greater than flow value + * + * @param model_str The model string to validate (typically from config) + * @return Empty string if valid, or an error message describing the first issue found + */ + static std::string validate_adaptive_pa_model(const std::string& model_str); + private: GCode &m_gcodegen; ///< Reference to the GCode object. std::unordered_map> m_AdaptivePAInterpolators; ///< Map between Interpolator objects and tool ID's diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 8be08f22eb..6be171750d 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -1925,6 +1926,23 @@ StringObjectException Print::validate(std::vector *warnin if (m_default_region_config.precise_outer_wall && m_default_region_config.wall_sequence != WallSequence::InnerOuter) warn(L("The precise wall option will be ignored for outer-inner or inner-outer-inner wall sequences."), "precise_outer_wall"); + // check adaptive pressure advance model + for (unsigned int extruder_id : extruders) { + if (m_config.adaptive_pressure_advance.get_at(extruder_id) && + m_config.enable_pressure_advance.get_at(extruder_id)) { + + const std::string pa_model = m_config.adaptive_pressure_advance_model.get_at(extruder_id); + if (!pa_model.empty()) { + std::string validation_error = AdaptivePAProcessor::validate_adaptive_pa_model(pa_model); + if (!validation_error.empty()) { + warn(L("The Adaptive Pressure Advance model for one or more extruders may contain invalid values."), + "adaptive_pressure_advance_model"); + break; + } + } + } + } + } catch (std::exception& e) { BOOST_LOG_TRIVIAL(warning) << "Orca: validate motion ability failed: " << e.what() << std::endl; } diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 3527d18503..d39a3fdfcb 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -9,13 +9,29 @@ #include "libslic3r/MaterialType.hpp" #include "MsgDialog.hpp" #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/GCode/AdaptivePAProcessor.hpp" #include "Plater.hpp" +#include #include namespace Slic3r { namespace GUI { +namespace { + +std::string trim_copy(const std::string& text) +{ + const auto first = text.find_first_not_of(" \t\r\n"); + if (first == std::string::npos) + return {}; + + const auto last = text.find_last_not_of(" \t\r\n"); + return text.substr(first, last - first + 1); +} + +} // namespace + void ConfigManipulation::apply(DynamicPrintConfig* config, DynamicPrintConfig* new_config) { bool modified = false; @@ -141,6 +157,30 @@ void ConfigManipulation::check_nozzle_temperature_initial_layer_range(DynamicPri } } +void ConfigManipulation::check_adaptive_pressure_advance_model(DynamicPrintConfig* config) +{ + if (is_msg_dlg_already_exist || !config->has("adaptive_pressure_advance_model")) + return; + + const auto* model = config->option("adaptive_pressure_advance_model"); + if (model == nullptr || model->values.empty()) + return; + + std::string raw_model; + for (const std::string& chunk : model->values) + raw_model += chunk; + + std::string error = AdaptivePAProcessor::validate_adaptive_pa_model(raw_model); + if (!error.empty()) { + wxString msg_text = _L("Adaptive Pressure Advance model validation failed:\n"); + msg_text += from_u8(error); + MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK); + is_msg_dlg_already_exist = true; + dialog.ShowModal(); + is_msg_dlg_already_exist = false; + } +} + void ConfigManipulation::check_filament_max_volumetric_speed(DynamicPrintConfig *config) { diff --git a/src/slic3r/GUI/ConfigManipulation.hpp b/src/slic3r/GUI/ConfigManipulation.hpp index 59d7779d33..1224f1a6de 100644 --- a/src/slic3r/GUI/ConfigManipulation.hpp +++ b/src/slic3r/GUI/ConfigManipulation.hpp @@ -77,6 +77,7 @@ public: void check_nozzle_recommended_temperature_range(DynamicPrintConfig *config); void check_nozzle_temperature_range(DynamicPrintConfig* config); void check_nozzle_temperature_initial_layer_range(DynamicPrintConfig* config); + void check_adaptive_pressure_advance_model(DynamicPrintConfig* config); void check_filament_max_volumetric_speed(DynamicPrintConfig *config); void check_chamber_temperature(DynamicPrintConfig* config); void set_is_BBL_Printer(bool is_bbl_printer) { is_BBL_Printer = is_bbl_printer; }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index f7017cb74b..8391bb5d61 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3946,6 +3946,15 @@ void TabFilament::build() option.opt.is_code = true; option.opt.height = 15; optgroup->append_single_option_line(option); + optgroup->m_on_change = [this](const t_config_option_key& opt_key, const boost::any& value) { + DynamicPrintConfig& filament_config = m_preset_bundle->filaments.get_edited_preset().config; + + update_dirty(); + if (opt_key == "adaptive_pressure_advance_model") + m_config_manipulation.check_adaptive_pressure_advance_model(&filament_config); + + on_value_change(opt_key, value); + }; // optgroup = page->new_optgroup(L("Print chamber temperature"), L"param_chamber_temp");