From 6bcb809dd07d221695068a8cb292402b5042634a Mon Sep 17 00:00:00 2001 From: Ian Bassi Date: Mon, 27 Jul 2026 20:11:44 -0300 Subject: [PATCH] Calibrations improvements (#14759) --- src/libslic3r/GCode.cpp | 36 +++---- src/libslic3r/calib.hpp | 12 +++ src/slic3r/GUI/Plater.cpp | 62 +++++++++--- src/slic3r/GUI/calib_dlg.cpp | 169 ++++++++++++++++++++++++++++++++ src/slic3r/GUI/calib_dlg.hpp | 3 + src/slic3r/Utils/CalibUtils.cpp | 34 ++++++- 6 files changed, 284 insertions(+), 32 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 089e56fe87..5b0ce2f716 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -5530,7 +5530,9 @@ LayerResult GCode::process_layer( //Calibration Layer-specific GCode switch (print.calib_mode()) { case CalibMode::Calib_PA_Tower: { - gcode += writer().set_pressure_advance(print.calib_params().start + static_cast(print_z) * print.calib_params().step); + gcode += writer().set_pressure_advance(this->interpolate_value_across_layers(static_cast(print.calib_params().start), + static_cast(print.calib_params().end), + static_cast(print.calib_params().step))); break; } case CalibMode::Calib_Temp_Tower: { @@ -5538,7 +5540,12 @@ LayerResult GCode::process_layer( break; } case CalibMode::Calib_VFA_Tower: { - auto _speed = print.calib_params().start + std::floor(print_z / 5.0) * print.calib_params().step; + // Step the outer wall speed from start to end across the tower's layers. Plater::calib_VFA sizes the + // geometry so each speed step spans one visual block (a fixed number of layers), so the layer-based + // stepping stays aligned with the blocks regardless of nozzle size / layer height. + float _speed = this->interpolate_value_across_layers(static_cast(print.calib_params().start), + static_cast(print.calib_params().end), + static_cast(print.calib_params().step)); m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloatsNullable({std::round(_speed)})); break; } @@ -8343,29 +8350,22 @@ std::string GCode::extrusion_role_to_string_for_parser(const ExtrusionRole & rol } // Calculate the interpolated value for the current layer between start_value and end_value. -// Step will create equal layers steps from first to last value. +// Step > 0 splits the range into equal-width bands from first to last value (both inclusive). // Step = 0 means gradual interpolation finishing at last value. float GCode::interpolate_value_across_layers(float start_value, float end_value, float step) const { if (m_layer_index <= 1) { return start_value; } - else { - bool use_steps = step > 0.f; - if (use_steps) { - if (start_value > end_value) { - start_value += step; - } else { - end_value += step; - } - } - float ratio = m_layer_index / (m_layer_count - 1.f); - float value = start_value + ratio * (end_value - start_value); - if (use_steps) { - value = trunc(value / step) * step; - } - return value; + const float ratio = m_layer_index / (m_layer_count - 1.f); + if (step > 0.f) { + // Discrete equal-width bands. band is clamped to the last band so the result can't overshoot the range: + // at the top layer ratio * n_bands == n_bands, which would otherwise index one band past the end. + const int n_bands = std::lround(std::abs(end_value - start_value) / step) + 1; + const int band = std::min(n_bands - 1, static_cast(ratio * n_bands)); + return start_value + (end_value >= start_value ? 1.f : -1.f) * band * step; } + return start_value + ratio * (end_value - start_value); } std::string encodeBase64(uint64_t value) diff --git a/src/libslic3r/calib.hpp b/src/libslic3r/calib.hpp index 15ac7303c9..ed0f76ee86 100644 --- a/src/libslic3r/calib.hpp +++ b/src/libslic3r/calib.hpp @@ -42,10 +42,22 @@ struct Calib_Params std::string shaper_type; std::vector accelerations; std::vector speeds; + // Resolved layer height for the VFA tower (0 = auto: nozzle_diameter / 2). Each speed block is a + // fixed number of layers tall, so this also determines the physical block height / tower height. + double vfa_layer_height = 0.0; + // Scale the calibration model to the nozzle diameter and set the layer height accordingly (temp tower / VFA). + // When false the 0.4 mm / 0.2 mm reference model is printed as-is. + bool nozzle_based_resize = true; CalibMode mode; }; +// Number of printed layers per speed block in the VFA tower. The base model has 5 mm blocks designed +// for a 0.2 mm layer height (0.4 mm nozzle), i.e. 25 layers per block. +static constexpr int vfa_layers_per_block = 25; +static constexpr double vfa_base_block_height = 5.0; +static constexpr double vfa_base_nozzle_diameter = 0.4; + enum FlowRatioCalibrationType { COMPLETE_CALIBRATION = 0, FINE_CALIBRATION, diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index feecdfa163..d0a28fdf5e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -14183,7 +14183,7 @@ void Plater::calib_temp(const Calib_Params& params) { } } - if (std::abs(nozzle_scale - 1.0) > EPSILON) + if (params.nozzle_based_resize && std::abs(nozzle_scale - 1.0) > EPSILON) model().objects[0]->scale(nozzle_scale, nozzle_scale, nozzle_scale); model().objects[0]->ensure_on_bed(); @@ -14191,7 +14191,9 @@ void Plater::calib_temp(const Calib_Params& params) { printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false}); set_config_values(filament_config, "nozzle_temperature_initial_layer", (int) start_temp); set_config_values(filament_config, "nozzle_temperature", (int) start_temp); - model().objects[0]->config.set_key_value("layer_height", new ConfigOptionFloat(nozzle_diameter/2)); + // When resizing is disabled the 0.4 mm / 0.2 mm reference model is printed as-is (preset layer height kept). + if (params.nozzle_based_resize) + model().objects[0]->config.set_key_value("layer_height", new ConfigOptionFloat(nozzle_diameter/2)); model().objects[0]->config.set_key_value("brim_type", new ConfigOptionEnum(btOuterOnly)); model().objects[0]->config.set_key_value("brim_width", new ConfigOptionFloat(5.0)); model().objects[0]->config.set_key_value("brim_object_gap", new ConfigOptionFloat(0.0)); @@ -14202,7 +14204,8 @@ void Plater::calib_temp(const Calib_Params& params) { auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config; print_config->set_key_value("enable_wrapping_detection", new ConfigOptionBool(false)); - print_config->set_key_value("initial_layer_print_height", new ConfigOptionFloat(nozzle_diameter/2)); + if (params.nozzle_based_resize) + print_config->set_key_value("initial_layer_print_height", new ConfigOptionFloat(nozzle_diameter/2)); changed_objects({ 0 }); @@ -14366,6 +14369,42 @@ void Plater::calib_VFA(const Calib_Params& params) auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config; auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config; auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config; + + const ConfigOptionFloats* nozzle_diameter_config = printer_config->option("nozzle_diameter"); + size_t nozzle_id = static_cast(std::max(params.extruder_id, 0)); + double nozzle_diameter = vfa_base_nozzle_diameter; + if (nozzle_diameter_config && !nozzle_diameter_config->values.empty()) { + nozzle_id = std::min(nozzle_id, nozzle_diameter_config->values.size() - 1); + nozzle_diameter = nozzle_diameter_config->values[nozzle_id]; + } + if (nozzle_diameter <= 0.0) + nozzle_diameter = vfa_base_nozzle_diameter; + + // Resolved layer height: use the (possibly auto-adjusted) value from the dialog, else default to nozzle/2. + double layer_height = params.vfa_layer_height > 0.0 ? params.vfa_layer_height : nozzle_diameter / 2.0; + + // cut upper (on the unscaled model, using the base block height); the scaling below keeps the physical + // block height (vfa_layers_per_block * layer_height) in sync with the speed stepping in GCode::process_layer. + // Subtract EPSILON (as the temperature tower does) so the cut lands just below the flat block surface instead + // of exactly on it, which would otherwise add a degenerate extra layer. + auto obj_bb = model().objects[0]->bounding_box_exact(); + auto height = vfa_base_block_height * ((params.end - params.start) / params.step + 1) - EPSILON; + if (height < obj_bb.size().z()) { + cut_horizontal(0, 0, height, ModelObjectCutAttribute::KeepLower); + } + + // When resizing is enabled, XY scales with the nozzle (footprint / line width) and Z scales so each base + // block becomes vfa_layers_per_block layers of the resolved layer height. When disabled the 0.4 mm / 0.2 mm + // reference model is printed as-is (preset layer height kept). + if (params.nozzle_based_resize) { + const double xy_scale = nozzle_diameter / vfa_base_nozzle_diameter; + const double z_scale = (vfa_layers_per_block * layer_height) / vfa_base_block_height; + if (std::abs(xy_scale - 1.0) > EPSILON || std::abs(z_scale - 1.0) > EPSILON) + model().objects[0]->scale(xy_scale, xy_scale, z_scale); + } + + model().objects[0]->ensure_on_bed(); + printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false}); filament_config->set_key_value("slow_down_layer_time", new ConfigOptionFloats { 0.0 }); set_config_values(print_config, "enable_overhang_speed", false); @@ -14379,6 +14418,10 @@ void Plater::calib_VFA(const Calib_Params& params) print_config->set_key_value("spiral_mode", new ConfigOptionBool(true)); print_config->set_key_value("enable_wrapping_detection", new ConfigOptionBool(false)); print_config->set_key_value("precise_z_height", new ConfigOptionBool(false)); + if (params.nozzle_based_resize) { + print_config->set_key_value("initial_layer_print_height", new ConfigOptionFloat(layer_height)); + model().objects[0]->config.set_key_value("layer_height", new ConfigOptionFloat(layer_height)); + } model().objects[0]->config.set_key_value("brim_type", new ConfigOptionEnum(btOuterOnly)); model().objects[0]->config.set_key_value("brim_width", new ConfigOptionFloat(3.0)); model().objects[0]->config.set_key_value("brim_object_gap", new ConfigOptionFloat(0.0)); @@ -14389,14 +14432,11 @@ void Plater::calib_VFA(const Calib_Params& params) wxGetApp().get_tab(Preset::TYPE_PRINT)->update_ui_from_settings(); wxGetApp().get_tab(Preset::TYPE_FILAMENT)->update_ui_from_settings(); - // cut upper - auto obj_bb = model().objects[0]->bounding_box_exact(); - auto height = 5 * ((params.end - params.start) / params.step + 1); - if (height < obj_bb.size().z()) { - cut_horizontal(0, 0, height, ModelObjectCutAttribute::KeepLower); - } - - p->background_process.fff_print()->set_calib_params(params); + // Pass the resolved layer height on (only meaningful when resized). GCode's VFA stepping is layer-based, so + // it does not require it, but keep it consistent with the geometry. + Calib_Params calib_params = params; + calib_params.vfa_layer_height = params.nozzle_based_resize ? layer_height : 0.0; + p->background_process.fff_print()->set_calib_params(calib_params); } void Plater::calib_input_shaping_freq(const Calib_Params& params) diff --git a/src/slic3r/GUI/calib_dlg.cpp b/src/slic3r/GUI/calib_dlg.cpp index 0a18e2d448..4d8a835f73 100644 --- a/src/slic3r/GUI/calib_dlg.cpp +++ b/src/slic3r/GUI/calib_dlg.cpp @@ -8,7 +8,9 @@ #include "Widgets/HyperLink.hpp" #include #include +#include #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Flow.hpp" #include "libslic3r/Utils.hpp" namespace Slic3r { namespace GUI { @@ -34,6 +36,23 @@ int GetTextMax(wxWindow* parent, const std::vector& labels) return text_size.x + parent->FromDIP(10); } +CheckBox* add_scale_checkbox(wxWindow* parent, wxSizer* settings_sizer) +{ + auto row = new wxBoxSizer(wxHORIZONTAL); + auto cb = new CheckBox(parent); + cb->SetValue(true); + auto text = new wxStaticText(parent, wxID_ANY, _L("Auto-scale for nozzle"), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + cb->SetToolTip(_L("This model is designed around a 0.4 mm nozzle with a 0.2 mm layer height. \n" + "When the scaling option is enabled (recommended), it dynamically resizes to match your current nozzle diameter" + " and an appropriate layer height, making the test both accurate and easy to read.\n" + "Turn scaling off only if you wish to print the reference model exactly as-is.")); + text->SetToolTip(cb->GetToolTipText()); + row->Add(cb , 0, wxALL | wxALIGN_CENTER_VERTICAL, parent->FromDIP(2)); + row->Add(text, 0, wxALL | wxALIGN_CENTER_VERTICAL, parent->FromDIP(2)); + settings_sizer->Add(row, 0, wxLEFT | wxTOP, parent->FromDIP(3)); + return cb; +} + std::vector get_shaper_type_values() { if (auto* preset_bundle = wxGetApp().preset_bundle) { @@ -402,6 +421,9 @@ Temp_Calibration_Dlg::Temp_Calibration_Dlg(wxWindow* parent, wxWindowID id, Plat temp_step_sizer->Add(m_tiStep , 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(2)); settings_sizer->Add(temp_step_sizer, 0, wxLEFT, FromDIP(3)); + // Resize the model to the nozzle diameter (recommended) + m_cbResize = add_scale_checkbox(this, settings_sizer); + settings_sizer->AddSpacer(FromDIP(5)); v_sizer->Add(settings_sizer, 0, wxTOP | wxRIGHT | wxLEFT | wxEXPAND, FromDIP(10)); @@ -475,6 +497,7 @@ void Temp_Calibration_Dlg::on_start(wxCommandEvent& event) { } m_params.start = start; m_params.end = end; + m_params.nozzle_based_resize = m_cbResize->GetValue(); m_params.mode = CalibMode::Calib_Temp_Tower; m_plater->calib_temp(m_params); EndModal(wxID_OK); @@ -691,6 +714,22 @@ VFA_Test_Dlg::VFA_Test_Dlg(wxWindow* parent, wxWindowID id, Plater* plater) vol_step_sizer->Add(m_tiStep , 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(2)); settings_sizer->Add(vol_step_sizer, 0, wxLEFT, FromDIP(3)); + // Resize the model to the nozzle diameter (recommended) + m_cbResize = add_scale_checkbox(this, settings_sizer); + + // Auto-adjust parameters to the filament's max volumetric speed + auto auto_adjust_sizer = new wxBoxSizer(wxHORIZONTAL); + m_cbAutoAdjust = new CheckBox(this); + m_cbAutoAdjust->SetValue(true); + auto auto_adjust_text = new wxStaticText(this, wxID_ANY, _L("Auto-adjust to max volumetric speed"), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + m_cbAutoAdjust->SetToolTip(_L("If the end speed would exceed the filament's maximum volumetric speed, automatically lower the layer " + "height (keeping standard values and staying within the machine's limits) to reach it. If even the " + "minimum layer height is not enough, lower the end speed instead.")); + auto_adjust_text->SetToolTip(m_cbAutoAdjust->GetToolTipText()); + auto_adjust_sizer->Add(m_cbAutoAdjust , 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(2)); + auto_adjust_sizer->Add(auto_adjust_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(2)); + settings_sizer->Add(auto_adjust_sizer, 0, wxLEFT | wxTOP, FromDIP(3)); + settings_sizer->AddSpacer(FromDIP(5)); v_sizer->Add(settings_sizer, 0, wxTOP | wxRIGHT | wxLEFT | wxEXPAND, FromDIP(10)); @@ -732,6 +771,136 @@ void VFA_Test_Dlg::on_start(wxCommandEvent& event) return; } + // If the requested end speed would exceed the filament's maximum volumetric speed, the slicer clamps the + // outer wall speed, so the upper blocks of the tower would all print at the same (clamped) speed instead of + // the requested one. Depending on the "Auto-adjust" option, either fix it automatically or just warn. + m_params.vfa_layer_height = 0.0; // 0 = auto (nozzle/2); overridden below when auto-adjusting + m_params.nozzle_based_resize = m_cbResize->GetValue(); + if (const auto* preset_bundle = wxGetApp().preset_bundle) { + const auto& printer_config = preset_bundle->printers.get_edited_preset().config; + const auto& print_config = preset_bundle->prints.get_edited_preset().config; + const auto& filament_config = preset_bundle->filaments.get_edited_preset().config; + + const int extruder_id = std::max(m_params.extruder_id, 0); + auto get_at = [extruder_id](const ConfigOptionFloats* opt, double fallback) { + if (opt == nullptr || opt->values.empty()) + return fallback; + return opt->values[std::min(static_cast(extruder_id), opt->values.size() - 1)]; + }; + + const double nozzle_diameter = get_at(printer_config.option("nozzle_diameter"), vfa_base_nozzle_diameter); + double preset_lh = nozzle_diameter / 2.0; + if (const auto* lh_opt = print_config.option("layer_height")) + if (lh_opt->value > 0.0) + preset_lh = lh_opt->value; + // Layer height the tower will actually print at: nozzle/2 when resizing, else the preset value. + const double default_lh = m_params.nozzle_based_resize ? nozzle_diameter / 2.0 : preset_lh; + const double max_vol_speed = get_at(filament_config.option("filament_max_volumetric_speed"), 0.0); + const double machine_min_lh = get_at(printer_config.option("min_layer_height"), 0.0); + const double machine_max_lh = get_at(printer_config.option("max_layer_height"), 0.0); + + double line_width = print_config.get_abs_value("outer_wall_line_width", nozzle_diameter); + if (line_width <= 0.0) + line_width = print_config.get_abs_value("line_width", nozzle_diameter); + if (line_width <= 0.0) + line_width = nozzle_diameter; + + // Max outer-wall speed printable at a given layer height without exceeding the volumetric limit. + auto speed_limit_for_lh = [&](double lh) -> double { + const double mm3_per_mm = Flow(line_width, lh, nozzle_diameter).mm3_per_mm(); + return mm3_per_mm > 0.0 ? max_vol_speed / mm3_per_mm : 1e9; + }; + + auto confirm_clamp = [&](const wxString& question) -> bool { + MessageDialog msg_dlg(nullptr, + wxString::Format(_L("The end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed " + "(%.1f mm³/s), which limits the outer wall to about %.0f mm/s at this line width and " + "layer height.\n Speeds above this will be clamped, so the upper blocks of the tower " + "will not print at the requested speed.\n\n%s"), + m_params.end, max_vol_speed, speed_limit_for_lh(default_lh), question), + _L("VFA test"), wxICON_WARNING | wxYES_NO | wxNO_DEFAULT); + return msg_dlg.ShowModal() == wxID_YES; + }; + + if (max_vol_speed > 0.0 && nozzle_diameter > 0.0 && m_params.end > speed_limit_for_lh(default_lh)) { + // The layer-height auto-adjust only applies when resizing is enabled (it changes the layer height). + if (m_cbAutoAdjust->GetValue() && m_params.nozzle_based_resize) { + // Candidate layer heights are the ones actually used by the process profiles compatible with the + // current printer (clamped to the machine's layer-height limits, when set). A smaller layer height + // means a smaller cross-section, hence a higher printable speed under the volumetric limit; pick the + // largest candidate that still reaches the end speed to keep the change from the default minimal. + std::vector candidates; + for (const auto& preset : preset_bundle->prints.get_presets()) { + if (!preset.is_compatible || preset.is_default) + continue; + const auto* lh_opt = preset.config.option("layer_height"); + if (lh_opt == nullptr || lh_opt->value <= 0.0) + continue; + const double lh = lh_opt->value; + if ((machine_min_lh > 0.0 && lh < machine_min_lh - 1e-6) || + (machine_max_lh > 0.0 && lh > machine_max_lh + 1e-6)) + continue; + candidates.push_back(lh); + } + std::sort(candidates.begin(), candidates.end()); + candidates.erase(std::unique(candidates.begin(), candidates.end(), + [](double a, double b) { return std::abs(a - b) < 1e-6; }), + candidates.end()); + + // Largest candidate <= the default layer height that still reaches the end speed (smallest change). + double chosen_lh = 0.0; + for (auto it = candidates.rbegin(); it != candidates.rend(); ++it) { + if (*it > default_lh + 1e-6) + continue; // never increase the layer height above the default + if (speed_limit_for_lh(*it) >= m_params.end) { chosen_lh = *it; break; } + } + + if (chosen_lh > 0.0) { + // Reducing the layer height is enough to reach the requested end speed. + m_params.vfa_layer_height = chosen_lh; + MessageDialog msg_dlg(nullptr, + wxString::Format(_L("The end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed " + "(%.1f mm³/s) at the default layer height (%.2f mm).\n\n" + "The layer height has been reduced to %.2f mm (a value used by this printer's " + "profiles) so the tower can reach the requested speed."), + m_params.end, max_vol_speed, default_lh, chosen_lh), + _L("VFA test"), wxICON_INFORMATION | wxOK); + msg_dlg.ShowModal(); + } else if (!candidates.empty()) { + // Even the smallest available layer height cannot reach the end speed; propose a lower end speed + // based on that layer height, the line width and the maximum volumetric speed. + const double min_lh = candidates.front(); + const double reachable = speed_limit_for_lh(min_lh); + double new_end = std::floor(reachable / m_params.step) * m_params.step; // snap down to a step multiple + if (new_end < m_params.start + m_params.step) + new_end = m_params.start + m_params.step; + MessageDialog msg_dlg(nullptr, + wxString::Format(_L("Even at the smallest layer height used by this printer's profiles (%.2f mm) the " + "end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed " + "(%.1f mm³/s).\n\n" + "The layer height will be set to %.2f mm and the end speed lowered to %.0f mm/s.\n\n" + "Continue?"), + min_lh, m_params.end, max_vol_speed, min_lh, new_end), + _L("VFA test"), wxICON_WARNING | wxYES_NO | wxNO_DEFAULT); + if (msg_dlg.ShowModal() != wxID_YES) + return; + m_params.end = new_end; + m_params.vfa_layer_height = min_lh; + } else { + // No compatible process profiles to draw layer heights from: warn and let the user decide. + if (!confirm_clamp(_L("Continue anyway?"))) + return; + } + } else { + // Auto-adjust off, or resizing disabled (which forbids changing the layer height): just warn. + if (!confirm_clamp(m_params.nozzle_based_resize + ? _L("Enable \"Auto-adjust\" to fix this automatically, or continue anyway?") + : _L("Enable \"Auto-scale for nozzle\" and \"Auto-adjust\" to fix this automatically, or continue anyway?"))) + return; + } + } + } + m_params.mode = CalibMode::Calib_VFA_Tower; m_plater->calib_VFA(m_params); EndModal(wxID_OK); diff --git a/src/slic3r/GUI/calib_dlg.hpp b/src/slic3r/GUI/calib_dlg.hpp index 5ff0fbf3e3..3927c5e1fd 100644 --- a/src/slic3r/GUI/calib_dlg.hpp +++ b/src/slic3r/GUI/calib_dlg.hpp @@ -65,6 +65,7 @@ protected: TextInput* m_tiStart; TextInput* m_tiEnd; TextInput* m_tiStep; + CheckBox* m_cbResize; Plater* m_plater; }; @@ -99,6 +100,8 @@ protected: TextInput* m_tiStart; TextInput* m_tiEnd; TextInput* m_tiStep; + CheckBox* m_cbAutoAdjust; + CheckBox* m_cbResize; Plater* m_plater; }; diff --git a/src/slic3r/Utils/CalibUtils.cpp b/src/slic3r/Utils/CalibUtils.cpp index e7d16ffef9..473f62337f 100644 --- a/src/slic3r/Utils/CalibUtils.cpp +++ b/src/slic3r/Utils/CalibUtils.cpp @@ -1265,6 +1265,19 @@ void CalibUtils::calib_VFA(const CalibInfo &calib_info, wxString &error_message) DynamicPrintConfig filament_config = calib_info.filament_prest->config; DynamicPrintConfig printer_config = calib_info.printer_prest->config; + const ConfigOptionFloats* nozzle_diameter_config = printer_config.option("nozzle_diameter"); + size_t nozzle_id = static_cast(std::max(params.extruder_id, 0)); + double nozzle_diameter = vfa_base_nozzle_diameter; + if (nozzle_diameter_config && !nozzle_diameter_config->values.empty()) { + nozzle_id = std::min(nozzle_id, nozzle_diameter_config->values.size() - 1); + nozzle_diameter = nozzle_diameter_config->values[nozzle_id]; + } + if (nozzle_diameter <= 0.0) + nozzle_diameter = vfa_base_nozzle_diameter; + + // Resolved layer height: use the (possibly auto-adjusted) value if provided, else default to nozzle/2. + double layer_height = params.vfa_layer_height > 0.0 ? params.vfa_layer_height : nozzle_diameter / 2.0; + filament_config.set_key_value("slow_down_layer_time", new ConfigOptionInts{0}); filament_config.set_key_value("filament_max_volumetric_speed", new ConfigOptionFloats{200}); filament_config.set_key_value("curr_bed_type", new ConfigOptionEnum(calib_info.bed_type)); @@ -1280,13 +1293,18 @@ void CalibUtils::calib_VFA(const CalibInfo &calib_info, wxString &error_message) print_config.set_key_value("sparse_infill_density", new ConfigOptionPercent(0)); print_config.set_key_value("overhang_reverse", new ConfigOptionBool(false)); print_config.set_key_value("spiral_mode", new ConfigOptionBool(true)); + print_config.set_key_value("initial_layer_print_height", new ConfigOptionFloat(layer_height)); + model.objects[0]->config.set_key_value("layer_height", new ConfigOptionFloat(layer_height)); model.objects[0]->config.set_key_value("brim_type", new ConfigOptionEnum(btOuterOnly)); model.objects[0]->config.set_key_value("brim_width", new ConfigOptionFloat(3.0)); model.objects[0]->config.set_key_value("brim_object_gap", new ConfigOptionFloat(0.0)); - // cut upper + // cut upper (on the unscaled model, using the base block height); the scaling below keeps the physical + // block height (vfa_layers_per_block * layer_height) in sync with the speed stepping in GCode::process_layer. + // Subtract EPSILON (as the temperature tower does) so the cut lands just below the flat block surface instead + // of exactly on it, which would otherwise add a degenerate extra layer. auto obj_bb = model.objects[0]->bounding_box_exact(); - auto height = 5 * ((params.end - params.start) / params.step + 1); + auto height = vfa_base_block_height * ((params.end - params.start) / params.step + 1) - EPSILON; if (height < obj_bb.size().z()) { cut_model(model, height, ModelObjectCutAttribute::KeepLower); } @@ -1295,6 +1313,13 @@ void CalibUtils::calib_VFA(const CalibInfo &calib_info, wxString &error_message) return; } + // XY scales with the nozzle; Z scales so each base block becomes vfa_layers_per_block layers of layer_height. + const double xy_scale = nozzle_diameter / vfa_base_nozzle_diameter; + const double z_scale = (vfa_layers_per_block * layer_height) / vfa_base_block_height; + if (std::abs(xy_scale - 1.0) > EPSILON || std::abs(z_scale - 1.0) > EPSILON) + model.objects[0]->scale(xy_scale, xy_scale, z_scale); + model.objects[0]->ensure_on_bed(); + DynamicPrintConfig full_config; full_config.apply(FullPrintConfig::defaults()); full_config.apply(print_config); @@ -1303,7 +1328,10 @@ void CalibUtils::calib_VFA(const CalibInfo &calib_info, wxString &error_message) init_multi_extruder_params_for_cali(full_config, calib_info); - process_and_store_3mf(&model, full_config, params, error_message); + // Pass the resolved layer height on so the GCode speed stepping matches the geometry. + Calib_Params store_params = params; + store_params.vfa_layer_height = layer_height; + process_and_store_3mf(&model, full_config, store_params, error_message); if (!error_message.empty()) return;