diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index c1d86e9c47..f75d05bb0a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -4528,9 +4528,9 @@ size_t GCode::get_extruder_id(unsigned int filament_id) const size_t GCode::get_filament_config_index(int filament_id) const { - if (m_print) { - return m_print->get_filament_config_indx(filament_id, m_cur_layer_idx); - } + if (m_print) + return m_filament_index_cache.get(filament_id, m_cur_layer_idx, m_print->config_index_generation(), + [&] { return m_print->get_filament_config_indx(filament_id, m_cur_layer_idx); }); // Orca: without a Print the filament-indexed arrays are unexpanded, so the // filament id itself is the only meaningful column. return filament_id; @@ -4544,9 +4544,9 @@ size_t GCode::get_filament_config_index(int filament_id, size_t layer_id) const size_t GCode::get_nozzle_config_index(int filament_id) const { - if (m_print) { - return m_print->get_nozzle_config_index(filament_id, m_cur_layer_idx); - } + if (m_print) + return m_nozzle_index_cache.get(filament_id, m_cur_layer_idx, m_print->config_index_generation(), + [&] { return m_print->get_nozzle_config_index(filament_id, m_cur_layer_idx); }); // Orca: same reasoning; degenerate to the filament's extruder column. return get_extruder_id(filament_id); } @@ -8319,28 +8319,36 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, unsigned int acceleration_i = 0; double jerk = 0; // adjust acceleration - if (NOZZLE_CONFIG(default_acceleration) > 0) { + const size_t nozzle = get_nozzle_config_index(m_writer.filament()->id()); + if (m_config.default_acceleration.get_at(nozzle) > 0) { + const ExtrusionRole role = path.role(); + const double bridge_acceleration = is_bridge(role) ? + m_config.bridge_acceleration.get_at(nozzle).get_abs_value(m_config.outer_wall_acceleration.get_at(nozzle)) : 0.; + const double sparse_infill_acceleration = role == erInternalInfill ? + m_config.sparse_infill_acceleration.get_at(nozzle).get_abs_value(m_config.default_acceleration.get_at(nozzle)) : 0.; + const double internal_solid_infill_acceleration = role == erSolidInfill ? + m_config.internal_solid_infill_acceleration.get_at(nozzle).get_abs_value(m_config.default_acceleration.get_at(nozzle)) : 0.; double acceleration; - if (this->on_first_layer() && NOZZLE_CONFIG(initial_layer_acceleration) > 0) { - acceleration = NOZZLE_CONFIG(initial_layer_acceleration); + if (this->on_first_layer() && m_config.initial_layer_acceleration.get_at(nozzle) > 0) { + acceleration = m_config.initial_layer_acceleration.get_at(nozzle); #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; #endif - } else if (m_config.get_abs_value_at("bridge_acceleration", get_nozzle_config_index(m_writer.filament()->id())) > 0 && is_bridge(path.role())) { - acceleration = m_config.get_abs_value_at("bridge_acceleration", get_nozzle_config_index(m_writer.filament()->id())); - } else if (m_config.get_abs_value_at("sparse_infill_acceleration", get_nozzle_config_index(m_writer.filament()->id())) > 0 && (path.role() == erInternalInfill)) { - acceleration = m_config.get_abs_value_at("sparse_infill_acceleration", get_nozzle_config_index(m_writer.filament()->id())); - } else if (m_config.get_abs_value_at("internal_solid_infill_acceleration", get_nozzle_config_index(m_writer.filament()->id())) > 0 && (path.role() == erSolidInfill)) { - acceleration = m_config.get_abs_value_at("internal_solid_infill_acceleration", get_nozzle_config_index(m_writer.filament()->id())); - } else if (NOZZLE_CONFIG(outer_wall_acceleration) > 0 && is_external_perimeter(path.role())) { - acceleration = NOZZLE_CONFIG(outer_wall_acceleration); - } else if (NOZZLE_CONFIG(inner_wall_acceleration) > 0 && is_internal_perimeter(path.role())) { - acceleration = NOZZLE_CONFIG(inner_wall_acceleration); - } else if (NOZZLE_CONFIG(top_surface_acceleration) > 0 && is_top_surface(path.role())) { - acceleration = NOZZLE_CONFIG(top_surface_acceleration); + } else if (bridge_acceleration > 0) { + acceleration = bridge_acceleration; + } else if (sparse_infill_acceleration > 0) { + acceleration = sparse_infill_acceleration; + } else if (internal_solid_infill_acceleration > 0) { + acceleration = internal_solid_infill_acceleration; + } else if (m_config.outer_wall_acceleration.get_at(nozzle) > 0 && is_external_perimeter(role)) { + acceleration = m_config.outer_wall_acceleration.get_at(nozzle); + } else if (m_config.inner_wall_acceleration.get_at(nozzle) > 0 && is_internal_perimeter(role)) { + acceleration = m_config.inner_wall_acceleration.get_at(nozzle); + } else if (m_config.top_surface_acceleration.get_at(nozzle) > 0 && is_top_surface(role)) { + acceleration = m_config.top_surface_acceleration.get_at(nozzle); } else { - acceleration = NOZZLE_CONFIG(default_acceleration); + acceleration = m_config.default_acceleration.get_at(nozzle); } acceleration_i = (unsigned int)floor(acceleration + 0.5); } @@ -8441,7 +8449,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, speed = std::min(speed, m_config.scarf_joint_speed.get_abs_value(speed)); } } else if(path.role() == erInternalBridgeInfill) { - speed = m_config.get_abs_value_at("internal_bridge_speed", get_nozzle_config_index(m_writer.filament()->id())); + speed = m_config.internal_bridge_speed.get_at(nozzle).get_abs_value(m_config.bridge_speed.get_at(nozzle)); } else if (path.role() == erOverhangPerimeter || path.role() == erSupportTransition || path.role() == erBridgeInfill) { speed = NOZZLE_CONFIG(bridge_speed); } else if (path.role() == erInternalInfill) { @@ -8453,7 +8461,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } else if (path.role() == erIroning) { const size_t filament_idx = get_filament_config_index(m_writer.filament()->id()); speed = m_config.filament_ironing_speed.is_nil(filament_idx) - ? m_config.get_abs_value("ironing_speed") + ? m_config.ironing_speed.value : m_config.filament_ironing_speed.get_at(filament_idx); } else if (path.role() == erBottomSurface) { speed = NOZZLE_CONFIG(initial_layer_infill_speed); @@ -8514,7 +8522,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } // Override skirt speed if set if (path.role() == erSkirt) { - const double skirt_speed = m_config.get_abs_value("skirt_speed"); + const double skirt_speed = m_config.skirt_speed.value; if (skirt_speed > 0.0) speed = skirt_speed; } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 11c084d22f..92716ac2f7 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -314,6 +314,24 @@ public: size_t get_filament_config_index(int filament_id, size_t layer_id) const; size_t get_nozzle_config_index(int filament_id) const; + // Holds the last slot a resolver returned without locking, so only the G-code generator may + // call the resolvers. + struct ConfigIndexCache + { + bool valid{false}; + int filament_id{0}; + size_t layer_idx{0}; + size_t generation{0}; + size_t index{0}; + + template size_t get(int filament, size_t layer, size_t gen, Lookup &&lookup) + { + if (!valid || filament_id != filament || layer_idx != layer || generation != gen) + *this = {true, filament, layer, gen, size_t(lookup())}; + return index; + } + }; + // Object and support extrusions of the same PrintObject at the same print_z. // public, so that it could be accessed by free helper functions from GCode.cpp struct LayerToPrint @@ -805,6 +823,8 @@ private: // Object layer id of the layer being generated; keys the per-filament config-slot // resolvers. Distinct from m_layer_index (an export progress counter starting at -1). size_t m_cur_layer_idx{0}; + mutable ConfigIndexCache m_filament_index_cache; + mutable ConfigIndexCache m_nozzle_index_cache; std::set m_initial_layer_extruders; std::vector> m_sorted_layer_filaments; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 34e858521b..ea95e4fbbc 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -4488,10 +4488,11 @@ Polygons Print::get_extruder_shared_printable_polygon() const return shared_printable_polys; } -// Narrow the stored grouping result to the layer-aware type the slicing pipeline uses. -std::shared_ptr Print::get_layered_nozzle_group_result() const +void Print::set_nozzle_group_result(std::shared_ptr result) { - return std::dynamic_pointer_cast(m_nozzle_group_result); + m_nozzle_group_result = std::move(result); + m_layered_nozzle_group_result = std::dynamic_pointer_cast(m_nozzle_group_result); + ++m_config_index_generation; } // Dynamic (per-layer selector) regroup predicate. @@ -4527,6 +4528,7 @@ int Print::get_filament_config_indx(int filament_id, int layer_id, bool use_cach void Print::update_filament_self_index_cache() { m_missing_nozzle_group_logged.clear(); // reset the per-slice get_config_index log dedupe + ++m_config_index_generation; std::vector values; if (m_full_print_config.has("filament_self_index")) { @@ -4566,7 +4568,7 @@ int Print::get_nozzle_config_index(int filament_id, int layer_id) int Print::get_config_index(int filament_id, int layer_id, const std::vector &variant_list, const std::vector& self_index_list, FilamentIndexMap *index_map) { - auto group_result = get_layered_nozzle_group_result(); + const MultiNozzleUtils::LayeredNozzleGroupResult *group_result = m_layered_nozzle_group_result.get(); // Orca: defensive — when no grouping producer has published a result yet, fall back to the // static identity: one filament-variant column per filament. if (!group_result) @@ -4601,7 +4603,7 @@ int Print::get_config_index(int filament_id, int layer_id, const std::vector &variant_list, const std::vector& self_index_list, PrintIndexMap &index_map) { - auto group_result = get_layered_nozzle_group_result(); + const MultiNozzleUtils::LayeredNozzleGroupResult *group_result = m_layered_nozzle_group_result.get(); // Orca: same static fallback as the filament overload; the slot degenerates to the filament's // extruder column (filament_map is 1 based, get_extruder_id guards the filament id range). if (!group_result) diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index bcfa6d9349..99aa6d385f 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -1073,9 +1073,9 @@ public: // Logical (extruder, nozzle) grouping result produced by ToolOrdering during reorder. // Consumed by GCode via get_layered_nozzle_group_result()->get_nozzle_id(filament, layer) etc. - void set_nozzle_group_result(std::shared_ptr result) { m_nozzle_group_result = result; } + void set_nozzle_group_result(std::shared_ptr result); std::shared_ptr get_nozzle_group_result() const { return m_nozzle_group_result; } - std::shared_ptr get_layered_nozzle_group_result() const; + std::shared_ptr get_layered_nozzle_group_result() const { return m_layered_nozzle_group_result; } // True only when the project opts into the per-layer filament selector // (enable_filament_dynamic_map) in auto-for-flush mode on a multi-extruder machine. Gates the @@ -1238,6 +1238,9 @@ public: // pipeline's cooling stage, which runs concurrently with the generator stage filling it. int get_filament_config_indx(int filament_id, int layer_id, bool use_cache = true); int get_nozzle_config_index(int filament_id, int layer_id); + // Changes with the grouping result and the filament maps, so a caller may reuse a resolved slot + // until it changes. + size_t config_index_generation() const { return m_config_index_generation; } // Orca: Implement prusa's filament shrink compensation approach // Returns if all used filaments have same shrinkage compensations. @@ -1364,6 +1367,9 @@ private: // Logical (extruder, nozzle) grouping result, set by ToolOrdering during reorder. std::shared_ptr m_nozzle_group_result; + // m_nozzle_group_result narrowed to the layer-aware type; only set_nozzle_group_result() assigns + // either. + std::shared_ptr m_layered_nozzle_group_result; // Sequential (by-object) selector plans, keyed by object; see sequential_dynamic_orderings(). // Rebuilt (or cleared) on every process(). @@ -1373,6 +1379,7 @@ private: FilamentIndexMap m_filament_index_map; // Used to cache printer and process parameter information PrintIndexMap m_nozzle_index_map; + size_t m_config_index_generation{0}; // Orca: filament ids already reported as missing a nozzle-group entry this slice. get_config_index() // falls back per-filament/per-layer in the g-code hot path, so this dedupes its log to once per // filament instead of flooding thousands of identical error lines. Cleared with the caches each slice. diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index dd7d83511f..eba04d7d35 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1310,7 +1310,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // Reproduce that exact expansion here so an unchanged config diffs empty — the expanded // keys invalidate the wipe tower / g-code export, and the placeholder parser aliases // the full config — instead of trimming back to one slot per filament. - auto group_result = std::dynamic_pointer_cast(this->get_nozzle_group_result()); + auto group_result = this->get_layered_nozzle_group_result(); std::unordered_map> filament_variant_uses; if (group_result && group_result->is_support_dynamic_nozzle_map() && collect_filament_variant_uses(*group_result, m_ori_full_print_config, filament_variant_uses)) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 6bd0797ac9..94ef20e9fd 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -6735,8 +6735,10 @@ void GUI_App::reload_settings() tab->reload_config(); tab->update_changed_ui(); } - if (plater_) + if (plater_) { plater_->sidebar().update_all_preset_comboboxes(); + plater_->normalize_bed_types(false); + } }; if (is_main_thread_active()) refresh_synced_ui(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp b/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp index 4aed58d884..fe1368b69e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp @@ -5151,7 +5151,7 @@ void GLGizmoTextureDisplacement::render_debug_stage_panel(ModelVolume *mv) m_imgui->scaled(20.f)); ImGui::SameLine(); - ImGui::Checkbox(_u8L("Check topology").c_str(), &m_debug_check_topology); + m_imgui->bbl_checkbox(_L("Check topology"), m_debug_check_topology); if (ImGui::IsItemHovered()) m_imgui->tooltip(_u8L("Count open and non-manifold edges after each stage, which is how a stage " "that tore the mesh is spotted. It scans every edge, so it adds noticeably " @@ -5341,6 +5341,8 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float GizmoImguiBegin(get_name(), flags); ensure_panel_icons(); process_uv_commands(); // clicks from the UV editor pane, run here where the GL context is current + const float previous_body_h = m_panel_body_h; + const float previous_footer_h = m_panel_footer_h; // Pinned every frame while Standard is active, so what Preview shows is always what Bake will do. if (!pro_mode() && apply_standard_mode_presets(mv)) @@ -5362,20 +5364,16 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float const float card_pad = std::round(m_imgui->scaled(0.55f)); const float wrap_w = m_imgui->scaled(20.f); const ImVec4 orca = ImGuiWrapper::COL_ORCA; - const ImVec4 col_link = dark ? ImVec4(0.30f, 0.71f, 0.67f, 1.f) : ImVec4(0.f, 0.47f, 0.42f, 1.f); - const ImVec4 col_frame = dark ? ImVec4(0.212f, 0.212f, 0.235f, 1.f) : ImVec4(0.808f, 0.808f, 0.808f, 1.f); - const ImU32 col_card = dark ? IM_COL32(255, 255, 255, 10) : IM_COL32(0, 0, 0, 12); - const ImU32 col_line = dark ? IM_COL32(255, 255, 255, 18) : IM_COL32(0, 0, 0, 23); + const ImVec4 col_link = ImGuiWrapper::COL_ORCA; + const ImVec4 col_frame = ImGui::GetStyleColorVec4(ImGuiCol_Separator); + const ImU32 col_card = ImGui::GetColorU32(ImGuiCol_Text, dark ? 0.045f : 0.047f); + const ImU32 col_line = ImGui::GetColorU32(ImGuiCol_Text, dark ? 0.08f : 0.09f); const ImU32 col_sep = ImGui::GetColorU32(ImGuiCol_Separator); - // Combo drop-downs otherwise inherit ImGui's near-black default popup background; under the light - // theme that leaves the dark item text unreadable ("the dropbox is black"). Pushed only around each - // Combo below (never around a tooltip, whose own near-black default is what makes it readable). - const ImVec4 combo_popup_bg = dark ? ImVec4(0.18f, 0.18f, 0.19f, 1.f) : ImVec4(0.93f, 0.93f, 0.93f, 1.f); const auto scoped_combo = [&](const char *id, int *v, const char *const items[], int n) { - ImGui::PushStyleColor(ImGuiCol_PopupBg, combo_popup_bg); + ImGuiWrapper::push_combo_style(m_parent.get_scale()); const bool changed = ImGui::Combo(id, v, items, n); - ImGui::PopStyleColor(); + ImGuiWrapper::pop_combo_style(); return changed; }; const auto hover_tip = [&](const auto &text) { @@ -5782,10 +5780,10 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float wf_toggle = true; const std::string auto_label = _u8L("Auto"); - const float auto_w = frame_h + style.ItemInnerSpacing.x + ImGui::CalcTextSize(auto_label.c_str()).x; + const float auto_w = frame_h * 0.78f /*ratio from BBLCheckbox*/ + style.ItemInnerSpacing.x + ImGui::CalcTextSize(auto_label.c_str()).x; ImGui::SameLine(); ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), ImGui::GetWindowContentRegionMax().x - auto_w)); - if (ImGui::Checkbox((auto_label + "##auto_update").c_str(), &m_auto_update) && m_auto_update) + if (m_imgui->bbl_checkbox(wxString::FromUTF8(auto_label) + "##auto_update", m_auto_update) && m_auto_update) rebuild_preview(); // catch up anything that changed while it was off hover_tip(_u8L("Rebuilds the preview as soon as anything changes. Turn it off on a heavy model if painting " "or dragging a slider starts to stutter - the preview then waits until you let go.")); @@ -6020,7 +6018,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float "costs fine detail.")); // Edge fade: the relief flattens toward the boundary of the painted area. - m_preview_params_dirty |= ImGui::Checkbox((_u8L("Edge fade") + "##edge_smoothing").c_str(), &layer.edge_smoothing); + m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Edge fade") + "##edge_smoothing", layer.edge_smoothing); hover_tip(_u8L("Flattens the relief as it approaches the edge of the painted area, so it " "blends into the bare surface instead of stopping at a step.")); ImGui::SameLine(); @@ -6035,7 +6033,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float // Invert and Colours share a row. { const float x0 = ImGui::GetCursorPosX(); - m_preview_params_dirty |= ImGui::Checkbox(_u8L("Invert").c_str(), &layer.invert); + m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Invert"), layer.invert); hover_tip(_u8L("Turns the relief inside out: what stood out is cut in, and the other way " "round. The same as using a negative of the image.")); ImGui::SameLine(); @@ -6047,7 +6045,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float const bool has_color = decode_height_texture(layer).has_color(); bool color_enabled = layer.color_enabled && has_color; m_imgui->disabled_begin(!has_color); - if (ImGui::Checkbox(_u8L("Colours").c_str(), &color_enabled)) { + if (m_imgui->bbl_checkbox(_L("Colours"), color_enabled)) { layer.color_enabled = color_enabled; m_preview_params_dirty = true; } @@ -6065,7 +6063,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float // under whichever layer turned colour on. if (color_enabled) { TextureDisplacementOptions &opts = mv->texture_displacement_options; - if (ImGui::Checkbox(_u8L("Mix filaments").c_str(), &opts.color_mix_enabled)) + if (m_imgui->bbl_checkbox(_L("Mix filaments"), opts.color_mix_enabled)) m_preview_params_dirty = true; hover_tip(_u8L("Interleaves two filaments to fake the colours in between, so a handful " "of filaments can cover a photo or a gradient. An image of flat colours " @@ -6184,7 +6182,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float "a slide projector. Faces turned away from you stretch, so line the view up with " "the surface you care about first.")); - if (ImGui::Checkbox(_u8L("Project only on visible").c_str(), &m_project_only_visible)) { + if (m_imgui->bbl_checkbox(_L("Project only on visible"), m_project_only_visible)) { if (m_project_only_visible && select_visible_faces() == 0) show_error(nullptr, _u8L("Nothing is visible from this angle - turn the model to face the " "part you want to project onto.")); @@ -6195,7 +6193,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float "behind anything - and projects onto those. Replaces what the layer had painted.")); bool projector_open = m_projector_frame != nullptr && m_projector_frame->IsShown(); - if (ImGui::Checkbox(_u8L("Projection frame").c_str(), &projector_open)) + if (m_imgui->bbl_checkbox(_L("Projection frame"), projector_open)) show_projector(projector_open); hover_tip(_u8L("Opens a window you drag over the model. Whatever you can see through it is what " "gets the texture, and its border becomes the edge of the projection.")); @@ -6236,7 +6234,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float // Tile, and how it repeats. { const float x0 = ImGui::GetCursorPosX(); - m_preview_params_dirty |= ImGui::Checkbox((_u8L("Tile") + "##tile_enabled").c_str(), &layer.tile_enabled); + m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Tile") + "##tile_enabled", layer.tile_enabled); hover_tip(_u8L("Repeats the texture across the painted area. Off places one copy, like a decal, " "at the size set by Tile size.")); ImGui::SameLine(); @@ -6424,7 +6422,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float const ImVec2 mn(mouse.x - inner - 0.5f * grip_w, mouse.y - 0.5f * row_h); const ImVec2 mx(mn.x + (content_rx - list_x), mn.y + row_h); fg->AddRectFilled(ImVec2(mn.x + 2.f, mn.y + 4.f), ImVec2(mx.x + 2.f, mx.y + 4.f), IM_COL32(0, 0, 0, 70), rounding); - fg->AddRectFilled(mn, mx, dark ? IM_COL32(0x3a, 0x3a, 0x40, 245) : IM_COL32(255, 255, 255, 245), rounding); + fg->AddRectFilled(mn, mx, ImGui::GetColorU32(ImGuiCol_WindowBg, 245.f / 255.f), rounding); fg->AddRect(mn, mx, ImGui::GetColorU32(orca), rounding, 0, 1.5f); float x = mn.x + inner; @@ -6463,7 +6461,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float ImGui::Separator(); heading(_L("Subdivision")); - if (ImGui::Checkbox(_u8L("Only painted area (adaptive)").c_str(), &m_subdivide_adaptive)) { + if (m_imgui->bbl_checkbox(_L("Only painted area (adaptive)"), m_subdivide_adaptive)) { if (m_subdivide_editing) rebuild_subdivide_preview(); // switch the wireframe between the uniform and adaptive result m_parent.set_as_dirty(); @@ -6494,7 +6492,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float m_parent.set_as_dirty(); }; - if (ImGui::Checkbox(_u8L("Follow texture detail").c_str(), &m_subdivide_feature)) + if (m_imgui->bbl_checkbox(_L("Follow texture detail"), m_subdivide_feature)) preview_live(); hover_tip(_u8L("Spends the triangles where the texture actually bends - packed along ridges and edges, " "sparse over flat ground - instead of spreading them evenly. The same detail for fewer " @@ -6621,7 +6619,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float hover_tip(_u8L("Triangle size the whole model is rebuilt with, in millimetres. Displacement works best on an " "even mesh; this is what makes one out of an uneven import.")); - ImGui::Checkbox((_u8L("Keep sharp edges") + "##remesh_sharp").c_str(), &m_remesh_keep_sharp_edges); + m_imgui->bbl_checkbox(_L("Keep sharp edges") + "##remesh_sharp", m_remesh_keep_sharp_edges); hover_tip(_u8L("Holds hard edges and open borders in place while the rest is remeshed. Without it " "the remesher slides vertices along the surface and rounds every crisp edge off - " "a cube comes back with wobbly edges.")); @@ -6648,12 +6646,12 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float ImGui::Separator(); heading(_L("Result")); - m_preview_params_dirty |= ImGui::Checkbox(_u8L("Displace up to the border").c_str(), &opts.displace_border); + m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Displace up to the border"), opts.displace_border); hover_tip(_u8L("Lets the relief run right to the edge of the painted area. Turn it off to hold that " "outer ring flat, which keeps the displacement strictly inside your paint but flattens " "the pattern at the border.")); - m_preview_params_dirty |= ImGui::Checkbox(_u8L("Smooth result").c_str(), &opts.smooth_enabled); + m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Smooth result"), opts.smooth_enabled); hover_tip(_u8L("Smooths the geometry after the texture has been applied, to take the hard steps out of a " "low-resolution image. Only what the displacement moved is touched. The Smoothing slider " "on a layer is a different thing: it blurs the image before it is used.")); @@ -6670,7 +6668,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float hover_tip(_u8L("How many smoothing passes to run. More passes spread the smoothing further across " "the surface; Strength decides how much each one moves.")); - m_preview_params_dirty |= ImGui::Checkbox(_u8L("Ignore outer ring").c_str(), &opts.smooth_skip_border); + m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Ignore outer ring"), opts.smooth_skip_border); hover_tip(_u8L("Keeps the outer ring of the painted area out of the smoothing. Its neighbours " "outside the paint never move, so smoothing it drags the relief down and leaves the " "pattern half-melted at the border.")); @@ -6695,7 +6693,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float // The classic path is the opt-in: the one-run pipeline is the default, and its resolution // control lives in the footer next to Bake (see below). bool classic = !opts.pipeline_v2; - if (ImGui::Checkbox(_u8L("Experimental: classic bake pipeline").c_str(), &classic)) { + if (m_imgui->bbl_checkbox(_L("Experimental: classic bake pipeline"), classic)) { opts.pipeline_v2 = !classic; m_preview_params_dirty = true; } @@ -6724,13 +6722,13 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float if (SHOW_PIPELINE_DEV_CONTROLS && opts.pipeline_v2) { // Keeping the relief above the plate is not a checkbox: it is unconditional, in both pipelines // (see build_texture_displacement()). - m_preview_params_dirty |= ImGui::Checkbox(_u8L("Align mesh to texture edges").c_str(), &opts.v2_relocate); + m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Align mesh to texture edges"), opts.v2_relocate); hover_tip(_u8L("Slide vertices sideways onto the edges in the texture before displacing them. " "Displacement can only move vertices up and down, so without this a sharp step " "in the image lands wherever the triangles happen to be and comes out as a " "staircase. Moving the vertices onto the step first gives a straight wall at the " "same triangle count.")); - m_preview_params_dirty |= ImGui::Checkbox(_u8L("Clean up slivers").c_str(), &opts.v2_regularize); + m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Clean up slivers"), opts.v2_regularize); hover_tip(_u8L("Collapse the thin triangles refinement inherits from the model's own " "tessellation, before displacement samples them. A sliver's three corners " "land on three unrelated parts of the texture, which is what makes the " @@ -6810,7 +6808,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float // a fixed value, seeded with the recommendation so it starts from something sensible. bool auto_res = opts.v2_refine_mm <= 0.f; m_imgui->disabled_begin(busy); - if (ImGui::Checkbox("##v2auto", &auto_res)) { + if (m_imgui->bbl_checkbox(wxString::FromUTF8("##v2auto"), auto_res)) { if (auto_res) { opts.v2_refine_mm = 0.f; opts.v2_max_triangles_k = -1; @@ -6908,11 +6906,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float const std::string bake_label = m_prepare_in_progress ? _u8L("Preparing...") : m_bake_in_progress ? _u8L("Baking...") : into_u8(m_desc.at("bake")); - ImGui::PushStyleColor(ImGuiCol_Button, orca); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGuiWrapper::COL_ORCA_HOVER); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, orca); - ImGui::PushStyleColor(ImGuiCol_Border, orca); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f)); + GLGizmoUtils::push_orca_button_style(); m_imgui->push_bold_font(); m_imgui->disabled_begin(!can_bake); if (ImGui::Button((bake_label + "##bake").c_str(), ImVec2(x0 + panel_w - ImGui::GetCursorPosX(), button_h))) { @@ -6925,7 +6919,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float } m_imgui->disabled_end(); m_imgui->pop_bold_font(); - ImGui::PopStyleColor(5); + GLGizmoUtils::pop_orca_button_style(); if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) m_imgui->tooltip(mv != nullptr && !mv->is_texture_displacement_painted() ? (m_seam_edit_mode ? _u8L("Nothing is painted yet. The UV editor's seam tool is on, so " @@ -6964,7 +6958,17 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float } } m_panel_footer_h = ImGui::GetCursorScreenPos().y - footer_top; - + constexpr float resize_epsilon = 0.5f; + const float content_bottom = ImGui::GetCursorPosY() - style.ItemSpacing.y; + const float window_bottom = ImGui::GetWindowContentRegionMax().y; + const bool outer_window_needs_fit = content_bottom < window_bottom - resize_epsilon || + (content_bottom > window_bottom + resize_epsilon && + ImGui::GetWindowHeight() < ImGui::GetMainViewport()->Size.y - + 2.f * style.DisplaySafeAreaPadding.y - resize_epsilon); + if (std::abs(m_panel_body_h - previous_body_h) > resize_epsilon || + std::abs(m_panel_footer_h - previous_footer_h) > resize_epsilon || outer_window_needs_fit) { + m_parent.request_extra_frame(); + } GizmoImguiEnd(); ImGuiWrapper::pop_toolbar_style(); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 64bb5e11ca..41663d147e 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -2656,11 +2656,7 @@ void ImGuiWrapper::push_toolbar_style(const float scale) ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(238 / 255.0f, 238 / 255.0f, 238 / 255.0f, 1.00f)); // 10 ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(238 / 255.0f, 238 / 255.0f, 238 / 255.0f, 0.00f)); // 11 ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, COL_GREEN_LIGHT); // 12 - // The checkbox/radio frame behind this is drawn fully transparent (see FrameBg above, - // alpha 0), showing the light window background through it - a white check mark there is - // invisible. Dark mode doesn't have this problem (its window background is dark), so only - // this branch needs a check mark color with real contrast against a light background. - ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.f, 156 / 255.f, 136 / 255.f, 1.00f));//13 + ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(1.00f, 1.00f, 1.00f, 1.00f));//13 ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, ImVec4(0.42f, 0.42f, 0.42f, 1.00f)); ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, ImVec4(0.93f, 0.93f, 0.93f, 1.00f)); ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, ImVec4(0.93f, 0.93f, 0.93f, 1.00f)); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index ae0324b186..ecb35d16d5 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -3894,6 +3894,8 @@ bool MainFrame::load_config_file(const std::string &path) return false; } wxGetApp().load_current_presets(); + if (Plater *plater = wxGetApp().plater()) + plater->normalize_bed_types(false); return true; } diff --git a/src/slic3r/GUI/ParamsDialog.cpp b/src/slic3r/GUI/ParamsDialog.cpp index 87888848a6..008f71d6d1 100644 --- a/src/slic3r/GUI/ParamsDialog.cpp +++ b/src/slic3r/GUI/ParamsDialog.cpp @@ -3,6 +3,7 @@ #include "ParamsPanel.hpp" #include "GUI_App.hpp" #include "MainFrame.hpp" +#include "Plater.hpp" #include "Tab.hpp" #include "libslic3r/Utils.hpp" @@ -70,6 +71,18 @@ ParamsDialog::ParamsDialog(wxWindow * parent) } Hide(); + if (tab && tab->type() == Preset::TYPE_PRINTER) { + // Normalize only after the dialog closes, when the final capability is known. + auto &preset_bundle = *wxGetApp().preset_bundle; + const bool supports_multiple_bed_types = preset_bundle.is_bbl_vendor() || + preset_bundle.printers.get_edited_preset().config.opt_bool("support_multi_bed_types"); + if (m_initial_multi_bed_types != supports_multiple_bed_types) { + wxGetApp().plater()->normalize_bed_types(true); + if (auto *plate_tab = dynamic_cast(wxGetApp().get_plate_tab())) + plate_tab->update_model_config(); + } + } + if (!m_editing_filament_id.empty()) { Filamentinformation *filament_info = new Filamentinformation(); filament_info->filament_id = m_editing_filament_id; @@ -93,7 +106,15 @@ void ParamsDialog::Popup() if (m_panel && m_panel->get_current_tab()) { bool just_edit = false; if (!m_editing_filament_id.empty()) just_edit = true; - dynamic_cast(m_panel->get_current_tab())->set_just_edit(just_edit); + auto *tab = dynamic_cast(m_panel->get_current_tab()); + tab->set_just_edit(just_edit); + if (tab->type() == Preset::TYPE_PRINTER) { + // Remember the initial capability and compare it when the dialog closes. + // Bambu profiles support multiple bed types even when this option is unset. + auto &preset_bundle = *wxGetApp().preset_bundle; + m_initial_multi_bed_types = preset_bundle.is_bbl_vendor() || + preset_bundle.printers.get_edited_preset().config.opt_bool("support_multi_bed_types"); + } } Show(); } diff --git a/src/slic3r/GUI/ParamsDialog.hpp b/src/slic3r/GUI/ParamsDialog.hpp index 9ad34e165e..80c9c3d2fe 100644 --- a/src/slic3r/GUI/ParamsDialog.hpp +++ b/src/slic3r/GUI/ParamsDialog.hpp @@ -43,6 +43,7 @@ protected: private: std::string m_editing_filament_id; + bool m_initial_multi_bed_types = false; ParamsPanel * m_panel; wxWindowDisabler *m_winDisabler = nullptr; }; diff --git a/src/slic3r/GUI/PlateSettingsDialog.cpp b/src/slic3r/GUI/PlateSettingsDialog.cpp index b640b2cb84..31b8367774 100644 --- a/src/slic3r/GUI/PlateSettingsDialog.cpp +++ b/src/slic3r/GUI/PlateSettingsDialog.cpp @@ -431,8 +431,9 @@ PlateSettingsDialog::PlateSettingsDialog(wxWindow* parent, const wxString& title } } - if (!wxGetApp().preset_bundle->is_bbl_vendor()) - m_bed_type_choice->Disable(); + auto &preset_bundle = *wxGetApp().preset_bundle; + const auto &printer_config = preset_bundle.printers.get_edited_preset().config; + m_bed_type_choice->Enable(preset_bundle.is_bbl_vendor() || printer_config.opt_bool("support_multi_bed_types")); wxStaticText* m_bed_type_txt = new wxStaticText(this, wxID_ANY, _L("Bed type")); m_bed_type_txt->SetFont(Label::Body_14); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 702f4b2c7c..71086d1915 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -7403,7 +7403,8 @@ struct Plater::priv std::vector load_files(const std::vector& input_files, LoadStrategy strategy, bool ask_multi = false, - bool* published_out = nullptr); + bool* published_out = nullptr, + bool* config_loaded_out = nullptr); std::vector load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false, bool split_object = false, bool auto_drop = true); // Texture-to-color import: a mesh loaded with UVs + a texture map gets its faces clustered @@ -8875,8 +8876,12 @@ void read_binary_stl(const std::string& filename, std::string& model_id, std::st std::vector Plater::priv::load_files(const std::vector& input_files, LoadStrategy strategy, bool ask_multi, - bool* published_out) + bool* published_out, + bool* config_loaded_out) { + if (config_loaded_out != nullptr) + *config_loaded_out = false; + std::vector empty_result; bool dlg_cont = true; bool is_user_cancel = false; @@ -9561,6 +9566,8 @@ std::vector Plater::priv::load_files(const std::vector& input_ id = agent->to_orca_filament_id(id); } preset_bundle->load_config_model(filename.string(), std::move(config), file_version, &published_config); + if (config_loaded_out != nullptr) + *config_loaded_out = true; // Mixed-filament definitions that collided with one of the // receiver's real slots were relocated during the preset load. @@ -12820,6 +12827,8 @@ void Plater::priv::on_select_bed_type(wxCommandEvent &evt) // update plater with new config q->on_config_change(wxGetApp().preset_bundle->full_config()); + if (auto *plate_tab = dynamic_cast(wxGetApp().get_plate_tab())) + plate_tab->update_model_config(); // update app_config AppConfig* app_config = wxGetApp().app_config; @@ -15723,6 +15732,38 @@ void Plater::reset_project_dirty_initial_presets() { p->reset_project_dirty_init void Plater::render_project_state_debug_window() const { p->render_project_state_debug_window(); } #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW +void Plater::normalize_bed_types(bool printer_setting_changed) +{ + if (only_gcode_mode() || is_gcode_3mf()) + return; + + auto &preset_bundle = *wxGetApp().preset_bundle; + // Keep FFF plate settings intact while an SLA printer is selected. + if (preset_bundle.printers.get_edited_preset().printer_technology() != ptFFF) + return; + + const auto &printer_config = preset_bundle.printers.get_edited_preset().config; + + const bool supports_multiple_bed_types = + preset_bundle.is_bbl_vendor() || printer_config.opt_bool("support_multi_bed_types"); + // Clear local overrides for single-bed printers. + const bool overrides_reset = !supports_multiple_bed_types && + !p->partplate_list.check_all_plate_local_bed_type({}); + + if (overrides_reset) { + set_plater_dirty(true); + show_info(this, + _L("The selected printer does not support multiple bed types.\nBed type overrides were reset to the global bed type."), + _L("Plate bed types reset")); + } + + // Refresh the controls after a capability change, even when no override was reset. + if (printer_setting_changed || overrides_reset) { + sidebar().update_all_preset_comboboxes(); + wxGetApp().obj_list()->update_and_show_object_settings_item(); + } +} + std::vector Plater::mixed_filament_config_indices() const { std::vector indices; @@ -16303,6 +16344,9 @@ void Plater::load_project(wxString const& filename2, p->dirty_state.update_from_undo_redo_stack(true); up_to_date(true, true); + // Clear plate overrides that are incompatible with the selected printer. + normalize_bed_types(false); + wxGetApp().params_panel()->switch_to_object_if_has_object_configs(); auto has_modify = is_flush_config_modified(); @@ -17935,7 +17979,11 @@ std::vector Plater::load_files(const std::vector& input_files, p->m_slice_all_only_has_gcode = false; //BBS: wish to reset all plates stats item selected state when load a new file p->preview->get_canvas3d()->reset_select_plate_toolbar_selection(); - return p->load_files(input_files, strategy, ask_multi, published_out); + bool config_loaded = false; + std::vector result = p->load_files(input_files, strategy, ask_multi, published_out, &config_loaded); + if (config_loaded && !is_loading_project()) + normalize_bed_types(false); + return result; } bool Plater::preview_zip_archive(const boost::filesystem::path& archive_path) diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index fd23c7e2b7..a51dad4255 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -343,6 +343,7 @@ public: bool is_presets_dirty() const; void set_plater_dirty(bool is_dirty); void update_project_dirty_from_presets(); + void normalize_bed_types(bool printer_setting_changed); int save_project_if_dirty(const wxString& reason); void reset_project_dirty_after_save(); void reset_project_dirty_initial_presets(); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 868e81ffa7..21f8e4354c 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -88,6 +88,7 @@ #include "slic3r/plugin/PluginConfig.hpp" #include "slic3r/plugin/PluginManager.hpp" #include "Plater.hpp" +#include "ParamsDialog.hpp" #include "MainFrame.hpp" #include "format.hpp" #include "UnsavedChangesDialog.hpp" @@ -3305,6 +3306,11 @@ void TabPrint::toggle_options() m_config_manipulation.toggle_print_fff_options(m_config, int(intptr_t(m_extruder_switch->GetClientData())), m_type < Preset::TYPE_COUNT); + if (m_type == Preset::TYPE_PLATE) { + const auto &printer_config = m_preset_bundle->printers.get_edited_preset().config; + toggle_option("curr_bed_type", m_preset_bundle->is_bbl_vendor() || printer_config.opt_bool("support_multi_bed_types")); + } + Field *field = m_active_page->get_field("support_style"); auto support_type = m_config->opt_enum("support_type"); if (auto choice = dynamic_cast(field)) { @@ -7248,6 +7254,15 @@ bool Tab::select_preset( } load_current_preset(); + // Wait for the settings dialog to close; its edits are still provisional. + if (printer_tab && is_selected) { + Plater *plater = wxGetApp().plater(); + ParamsDialog *dialog = wxGetApp().params_dialog(); + if (plater && !plater->is_loading_project() && + (!dialog || !dialog->IsShown())) + plater->normalize_bed_types(false); + } + { Slic3r::LifecycleEventContext ctx; ctx.name = preset_name; diff --git a/src/slic3r/GUI/WebGuideDialog.cpp b/src/slic3r/GUI/WebGuideDialog.cpp index 4f1998ae94..350dd9d4be 100644 --- a/src/slic3r/GUI/WebGuideDialog.cpp +++ b/src/slic3r/GUI/WebGuideDialog.cpp @@ -71,6 +71,7 @@ #include #include "MainFrame.hpp" +#include "Plater.hpp" #include #include #include @@ -1161,6 +1162,8 @@ bool GuideFrame::run() app.app_config->set_legacy_datadir(false); app.update_mode(); + if (Plater *plater = app.plater()) + plater->normalize_bed_types(false); // BBS //app.obj_manipul()->update_ui_from_settings(); BOOST_LOG_TRIVIAL(info) << "GuideFrame applied"; diff --git a/tests/fff_print/test_gcodewriter.cpp b/tests/fff_print/test_gcodewriter.cpp index 6c565c80e0..bee7a3909a 100644 --- a/tests/fff_print/test_gcodewriter.cpp +++ b/tests/fff_print/test_gcodewriter.cpp @@ -4,18 +4,21 @@ #include #include #include +#include #include #include #include #include "nlohmann/json.hpp" +#include "libslic3r/GCodeReader.hpp" #include "libslic3r/GCodeWriter.hpp" #include "libslic3r/GCode.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/ModelArrange.hpp" +#include #include #include "test_helpers.hpp" @@ -97,6 +100,22 @@ SCENARIO("Origin manipulation", "[GCodeWriter]") { } } +TEST_CASE("A cached config slot is looked up again whenever its key changes", "[GCodeWriter]") +{ + GCode::ConfigIndexCache cache; + int lookups = 0; + auto slot = [&](int filament, size_t layer, size_t generation) { + return cache.get(filament, layer, generation, [&] { ++lookups; return filament * 100 + int(layer) * 10 + int(generation); }); + }; + REQUIRE(slot(1, 2, 3) == 123); + REQUIRE(slot(1, 2, 3) == 123); + REQUIRE(lookups == 1); + REQUIRE(slot(4, 2, 3) == 423); + REQUIRE(slot(4, 5, 3) == 453); + REQUIRE(slot(4, 5, 6) == 456); + REQUIRE(lookups == 4); +} + // Verify that emit_machine_limits_to_gcode emits the correct max value across // used extruders (regression for commit b4ee665: "Emit max value of machine // limit among used extruders"). @@ -1136,3 +1155,46 @@ TEST_CASE("Custom G-code motion limits are restored before generated moves", "[G REQUIRE(gcode.find("M204 S6000 ; adjust acceleration", custom_gcode_pos) != std::string::npos); REQUIRE(gcode.find("M205 X8 Y8 ; adjust jerk", custom_gcode_pos) != std::string::npos); } + +TEST_CASE("Percent accelerations resolve against the option they are a percentage of", "[GCodeWriter]") +{ + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "gcode_flavor", "marlin" }, + { "machine_max_acceleration_extruding", "20000,20000" }, + { "default_acceleration", "4000" }, + { "initial_layer_acceleration", "0" }, + { "outer_wall_acceleration", "3000" }, + { "bridge_acceleration", "50%" }, + { "sparse_infill_acceleration", "25%" }, + { "internal_solid_infill_acceleration", "60%" }, + { "sparse_infill_density", "20%" }, + }); + // get_abs_value_at() resolves each percentage through the ratio_over in the config definitions. + const std::map expected = { + { "Bridge", int(config.get_abs_value_at("bridge_acceleration", 0)) }, + { "Sparse infill", int(config.get_abs_value_at("sparse_infill_acceleration", 0)) }, + { "Internal solid infill", int(config.get_abs_value_at("internal_solid_infill_acceleration", 0)) }, + }; + REQUIRE(expected.at("Bridge") == 1500); + REQUIRE(expected.at("Sparse infill") == 1000); + REQUIRE(expected.at("Internal solid infill") == 2400); + + std::map> accelerations_by_role; + std::string role; + int acceleration = 0; + GCodeReader reader; + reader.parse_buffer(Slic3r::Test::slice({ TestMesh::bridge }, config), [&](GCodeReader &self, const GCodeReader::GCodeLine &line) { + float value; + if (boost::starts_with(line.raw(), ";TYPE:")) + role = line.raw().substr(6); + else if (line.cmd_is("M204") && line.has_value('S', value)) + acceleration = int(value); + else if (line.extruding(self) && line.dist_XY(self) > 0) + accelerations_by_role[role].insert(acceleration); + }); + for (const auto &[role_name, value] : expected) { + INFO(role_name); + REQUIRE(accelerations_by_role[role_name] == std::set{ value }); + } +} diff --git a/tests/libslic3r/test_toolordering_nozzle_group.cpp b/tests/libslic3r/test_toolordering_nozzle_group.cpp index 4074a80995..837b66287a 100644 --- a/tests/libslic3r/test_toolordering_nozzle_group.cpp +++ b/tests/libslic3r/test_toolordering_nozzle_group.cpp @@ -510,6 +510,22 @@ TEST_CASE("Print config-index resolvers pick per-filament Hybrid slots", "[Print } } +TEST_CASE("Regrouping or rewriting the filament maps changes the config-index generation", "[Print][H2C]") +{ + Model model; + model.add_object("cube", "", make_cube(20, 20, 20))->add_instance(); + Print print; + print.apply(model, DynamicPrintConfig::full_print_config()); + + size_t generation = print.config_index_generation(); + print.set_nozzle_group_result(nullptr); + REQUIRE(print.config_index_generation() != generation); + + generation = print.config_index_generation(); + print.update_filament_maps_to_config({1}, {(int) nvtStandard}, {0}); + REQUIRE(print.config_index_generation() != generation); +} + TEST_CASE("Re-applying an unchanged config after slicing keeps the result valid", "[Print][H2C]") { // apply() rebuilds m_config.filament_map_2 to the real per-filament slot map, while the