From c948d871023a58d7da0edffba7b52711f5d14def Mon Sep 17 00:00:00 2001 From: SoftFever Date: Wed, 1 Apr 2026 14:43:12 +0800 Subject: [PATCH 01/14] Fix flush_multiplier type mismatch: use ConfigOptionFloats instead of ConfigOptionFloat (#13062) --- src/libslic3r/PrintConfig.cpp | 7 ++----- src/slic3r/GUI/Plater.cpp | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 50be57e0f8..c4b3c28ad9 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -9029,11 +9029,8 @@ void DynamicPrintConfig::update_values_to_printer_extruders(DynamicPrintConfig& //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 + // Orca: This is expected during transient UI states (e.g. popup windows), + // fall back to 0 silently. variant_index[e_index] = 0; } } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ef46801722..64274adf19 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4050,8 +4050,8 @@ void Sidebar::auto_calc_flushing_volumes_internal(const int modify_id, const int const std::vector& min_flush_volumes = get_min_flush_volumes(full_config, extruder_id); - ConfigOptionFloat* flush_multi_opt = project_config.option("flush_multiplier"); - float flush_multiplier = flush_multi_opt ? flush_multi_opt->getFloat() : 1.f; + const auto* flush_multi_opt = project_config.option("flush_multiplier"); + float flush_multiplier = flush_multi_opt ? (float)flush_multi_opt->get_at(extruder_id) : 1.f; std::vector matrix = init_matrix; int m_max_flush_volume = Slic3r::g_max_flush_volume; unsigned int m_number_of_extruders = (int)(sqrt(init_matrix.size()) + 0.001); From e0e714d22acf2efc95d93679e731f2a58acd38f6 Mon Sep 17 00:00:00 2001 From: TheLegendTubaGuy Date: Wed, 1 Apr 2026 03:07:24 -0500 Subject: [PATCH 02/14] Add fallback for Max 4 series ID matching The Qidi Max 4 doesn't report that it's a Qidi Max 4 via /server/info like the other Qidi printers apparently do. This means that filament matching is a fool's erand because you'll never actually match anything. this adds a fallback where we just use the configured machine type. I'm not sure why we wouldn't just do that all the time, but I wanted to change as little as possible. --- src/slic3r/Utils/QidiPrinterAgent.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/slic3r/Utils/QidiPrinterAgent.cpp b/src/slic3r/Utils/QidiPrinterAgent.cpp index dfd2838513..6b05480194 100644 --- a/src/slic3r/Utils/QidiPrinterAgent.cpp +++ b/src/slic3r/Utils/QidiPrinterAgent.cpp @@ -50,6 +50,10 @@ bool QidiPrinterAgent::fetch_filament_info(std::string dev_id) series_id = infer_series_id(info.model_id, info.dev_name); } } + if (series_id.empty()) { + // Fall back to the configured Orca model if Moonraker doesn't expose a usable identifier. + series_id = infer_series_id(device_info.model_id, device_info.model_name); + } // 2. Fetch filament dictionary QidiFilamentDict dict; From d1a78026378296d0f2fc8525582ef105c4fc5b5d Mon Sep 17 00:00:00 2001 From: Olof Larsson Date: Fri, 3 Apr 2026 14:32:00 +0200 Subject: [PATCH 03/14] fix: update stl and texture for Creality K2 (#13098) * fix: update stl and texture for k2 * fix: bump profile version --- resources/profiles/Creality.json | 2 +- .../Creality/creality_k2_buildplate_model.stl | Bin 0 -> 104284 bytes .../creality_k2_buildplate_texture.png | Bin 0 -> 13629 bytes .../profiles/Creality/machine/Creality K2.json | 4 ++-- 4 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 resources/profiles/Creality/creality_k2_buildplate_model.stl create mode 100644 resources/profiles/Creality/creality_k2_buildplate_texture.png diff --git a/resources/profiles/Creality.json b/resources/profiles/Creality.json index 5775c1e479..79d40f2880 100644 --- a/resources/profiles/Creality.json +++ b/resources/profiles/Creality.json @@ -1,6 +1,6 @@ { "name": "Creality", - "version": "02.03.02.60", + "version": "02.03.02.61", "force_update": "0", "description": "Creality configurations", "machine_model_list": [ diff --git a/resources/profiles/Creality/creality_k2_buildplate_model.stl b/resources/profiles/Creality/creality_k2_buildplate_model.stl new file mode 100644 index 0000000000000000000000000000000000000000..c442446270b1e5a0b0690d35d3bbd86462404c63 GIT binary patch literal 104284 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 gR>{D4T+BZ Date: Sat, 4 Apr 2026 01:45:12 +0200 Subject: [PATCH 04/14] Fix typo in German translation: change "SETP Dateien" to "STEP Dateien" (#13100) --- localization/i18n/de/OrcaSlicer_de.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/localization/i18n/de/OrcaSlicer_de.po b/localization/i18n/de/OrcaSlicer_de.po index 19017ff54d..2a338e7afe 100644 --- a/localization/i18n/de/OrcaSlicer_de.po +++ b/localization/i18n/de/OrcaSlicer_de.po @@ -1654,7 +1654,7 @@ msgid "Based on PrusaSlicer and BambuStudio" msgstr "Basierend auf PrusaSlicer und BambuStudio" msgid "STEP files" -msgstr "SETP Dateien" +msgstr "STEP Dateien" msgid "STL files" msgstr "STL Dateien" From d7dbe6a0eda6ab3c18628070149178bcfa4971a0 Mon Sep 17 00:00:00 2001 From: Eldenroot Date: Sat, 4 Apr 2026 03:31:26 +0200 Subject: [PATCH 05/14] UX: Convert 'Change type' to submenu with checkmarks (#12205) * UX: Convert 'Change type' to submenu with checkmarks * Update GUI_ObjectList.cpp * Update GUI_ObjectList.hpp --------- Co-authored-by: yw4z --- src/slic3r/GUI/GUI_Factories.cpp | 57 ++++++++++--- src/slic3r/GUI/GUI_ObjectList.cpp | 130 ++++++++++++++++++++++++++++++ src/slic3r/GUI/GUI_ObjectList.hpp | 2 + 3 files changed, 177 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 9486bed67a..5c3c0267ee 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -750,18 +750,51 @@ wxMenuItem* MenuFactory::append_menu_item_settings(wxMenu* menu_) wxMenuItem* MenuFactory::append_menu_item_change_type(wxMenu* menu) { - return append_menu_item(menu, wxID_ANY, _L("Change type"), "", - [](wxCommandEvent&) { obj_list()->change_part_type(); }, "", menu, - []() { - wxDataViewItemArray selections; - obj_list()->GetSelections(selections); - if (selections.empty()) return false; - for (const auto& it : selections) { - if (!(obj_list()->GetModel()->GetItemType(it) & itVolume)) - return false; // non-volume present -> disable - } - return true; - }, m_parent); + const wxString menu_name = _L("Change type"); + + // Delete old menu item if exists + const int item_id = menu->FindItem(menu_name); + if (item_id != wxNOT_FOUND) + menu->Destroy(item_id); + + // Create submenu + wxMenu* type_menu = new wxMenu(); + + struct TypeInfo { + ModelVolumeType type; + wxString label; + }; + + std::vector types = { + { ModelVolumeType::MODEL_PART, _L("Part") }, + { ModelVolumeType::NEGATIVE_VOLUME, _L("Negative Part") }, + { ModelVolumeType::PARAMETER_MODIFIER, _L("Modifier") }, + { ModelVolumeType::SUPPORT_BLOCKER, _L("Support Blocker") }, + { ModelVolumeType::SUPPORT_ENFORCER, _L("Support Enforcer") } + }; + + for (const auto& info : types) { + wxMenuItem* item = append_menu_check_item(type_menu, wxID_ANY, info.label, "", + [type = info.type](wxCommandEvent&) { obj_list()->set_volume_type(type); }, type_menu); + + // Update checkmark dynamically when menu is shown - check all selected volumes + m_parent->Bind(wxEVT_UPDATE_UI, [type = info.type](wxUpdateUIEvent& evt) { + bool has_type = false; + wxDataViewItemArray sels; + obj_list()->GetSelections(sels); + for (auto item : sels) { + ModelVolumeType vol_type = obj_list()->GetModel()->GetVolumeType(item); + if (vol_type == type) { + has_type = true; + break; + } + } + evt.Check(has_type); + }, item->GetId()); + } + + menu->Append(wxID_ANY, menu_name, type_menu, _L("Change part type")); + return nullptr; } wxMenuItem* MenuFactory::append_menu_item_instance_to_object(wxMenu* menu) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index b20e26653a..fe2479386b 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -5538,6 +5538,136 @@ void ObjectList::change_part_type() return; } +ModelVolumeType ObjectList::get_selected_volume_type() +{ + ModelVolume* volume = get_selected_model_volume(); + if (volume) + return volume->type(); + return ModelVolumeType::INVALID; +} + +void ObjectList::set_volume_type(ModelVolumeType new_type) +{ + struct VolumeSelection { + int object_idx; + ModelVolume* volume; + }; + + std::vector volumes; + auto add_volume = [&volumes](int obj_idx, ModelVolume* volume) { + if (volume == nullptr) + return; + auto it = std::find_if(volumes.begin(), volumes.end(), [volume](const VolumeSelection& other) { return other.volume == volume; }); + if (it == volumes.end()) + volumes.push_back({ obj_idx, volume }); + }; + + wxDataViewItemArray sels; + GetSelections(sels); + for (auto item : sels) { + wxDataViewItem volume_item = item; + ItemType type = m_objects_model->GetItemType(item); + if (!(type & itVolume)) { + if ((type & itSettings) && (m_objects_model->GetItemType(m_objects_model->GetParent(item)) & itVolume)) + volume_item = m_objects_model->GetParent(item); + else + continue; + } + + const int obj_idx = m_objects_model->GetObjectIdByItem(volume_item); + const int vol_idx = m_objects_model->GetVolumeIdByItem(volume_item); + if (obj_idx < 0 || vol_idx < 0 || obj_idx >= m_objects->size()) + continue; + + const int real_idx = m_objects_model->get_real_volume_index_in_3d(obj_idx, vol_idx); + if (real_idx < 0 || real_idx >= (*m_objects)[obj_idx]->volumes.size()) + continue; + + add_volume(obj_idx, (*m_objects)[obj_idx]->volumes[real_idx]); + } + + auto collect_from_canvas = [&add_volume](GLCanvas3D* canvas) { + if (canvas == nullptr) + return; + const Selection& selection = canvas->get_selection(); + for (auto idx : selection.get_volume_idxs()) { + const GLVolume* gl_volume = selection.get_volume(idx); + if (gl_volume == nullptr || gl_volume->object_idx() < 0) + continue; + ModelVolume* volume = get_model_volume(*gl_volume, selection.get_model()->objects); + add_volume(gl_volume->object_idx(), volume); + } + }; + + if (volumes.empty()) { + collect_from_canvas(wxGetApp().plater()->canvas3D()); + if (volumes.empty()) { + auto canvas_type = wxGetApp().plater()->get_current_canvas3D()->get_canvas_type(); + if (canvas_type == GLCanvas3D::ECanvasType::CanvasView3D && is_connectors_item_selected()) + collect_from_canvas(wxGetApp().plater()->get_view3D_canvas3D()); + } + if (volumes.empty()) + return; + } + + const bool any_diff = std::any_of(volumes.begin(), volumes.end(), + [new_type](const VolumeSelection& sel) { return sel.volume->type() != new_type; }); + + if (!any_diff) + return; + + if (new_type != ModelVolumeType::MODEL_PART) { + std::map total_part_cnt; + std::map selected_part_cnt; + + for (const auto& sel : volumes) { + if (total_part_cnt.find(sel.object_idx) == total_part_cnt.end()) { + int count = 0; + for (auto vol : (*m_objects)[sel.object_idx]->volumes) + if (vol->type() == ModelVolumeType::MODEL_PART) + ++count; + total_part_cnt.emplace(sel.object_idx, count); + } + if (sel.volume->type() == ModelVolumeType::MODEL_PART) + ++selected_part_cnt[sel.object_idx]; + } + + for (const auto& sel : selected_part_cnt) { + auto it = total_part_cnt.find(sel.first); + if (it != total_part_cnt.end() && it->second > 0 && sel.second == it->second) { + Slic3r::GUI::show_error(nullptr, _(L("The type of the last solid object part is not to be changed."))); + return; + } + } + } + + take_snapshot("Change part type"); + + std::set changed_volumes; + std::set touched_objects; + for (const auto& sel : volumes) { + sel.volume->set_type(new_type); + changed_volumes.insert(sel.volume); + touched_objects.insert(sel.object_idx); + } + + wxDataViewItemArray new_selection; + for (int obj_idx : touched_objects) { + wxDataViewItemArray sel_items = reorder_volumes_and_get_selection(obj_idx, [&changed_volumes](const ModelVolume* volume) { + return changed_volumes.find(volume) != changed_volumes.end(); + }); + for (const auto& item : sel_items) + new_selection.push_back(item); + } + + if (!new_selection.IsEmpty()) { + m_prevent_list_events = true; + UnselectAll(); + SetSelections(new_selection); + m_prevent_list_events = false; + } +} + void ObjectList::last_volume_is_deleted(const int obj_idx) { // BBS: object (obj_idx calc in obj list) is already removed from m_objects in Plater::priv::remove(). diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 1f84fff5d3..fef8230a28 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -414,6 +414,8 @@ public: ModelVolume* get_selected_model_volume(); void change_part_type(); + void set_volume_type(ModelVolumeType new_type); + ModelVolumeType get_selected_volume_type(); void last_volume_is_deleted(const int obj_idx); void update_and_show_object_settings_item(); From 362ddec743e1f9b29f742fcfed028f4f28a92021 Mon Sep 17 00:00:00 2001 From: Kiss Lorand <50251547+kisslorand@users.noreply.github.com> Date: Sat, 4 Apr 2026 22:56:42 +0300 Subject: [PATCH 06/14] Fix overhang reverse threshold being ignored (#13061) --- src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp index d73513622b..fb9ea23822 100644 --- a/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp +++ b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp @@ -196,7 +196,7 @@ void group_region_by_fuzzify(PerimeterGenerator& g) surfaces.push_back(&surface); } - if (cfg.type != FuzzySkinType::None) { + if (cfg.type != FuzzySkinType::None && cfg.type != FuzzySkinType::Disabled_fuzzy) { g.has_fuzzy_skin = true; if (cfg.type != FuzzySkinType::External) { g.has_fuzzy_hole = true; From aa51d1965f4613a5193ff22864ac42971cf880bd Mon Sep 17 00:00:00 2001 From: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> Date: Sun, 5 Apr 2026 07:53:41 -0300 Subject: [PATCH 07/14] Don't steal focus from text inputs on mouse (#12834) In GLCanvas3D::on_mouse, avoid calling SetFocus when the currently focused window is a text input control (wxTextCtrl, wxComboBox or wxSpinCtrl). This prevents deselection bugs when the mouse leaves the window while a text control is focused. Also commented out explicit wx text-control includes near the top of the file. Only TextCtrl needed cleaning Removed unused wxWidgets header includes. Update GLCanvas3D.cpp Co-authored-by: yw4z Co-authored-by: yw4z --- src/slic3r/GUI/GLCanvas3D.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a19bed05a8..334643a201 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4277,7 +4277,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) p = p->GetParent(); auto *top_level_wnd = dynamic_cast(p); //Orca: Set focus so hotkeys like 'tab' work when a notification is shown. - if (top_level_wnd != nullptr && top_level_wnd->IsActive()) + //But don't steal focus from text input controls. + wxWindow* focused = wxWindow::FindFocus(); + bool focus_in_text_ctrl = dynamic_cast(focused) != nullptr; + if (top_level_wnd != nullptr && top_level_wnd->IsActive() && !focus_in_text_ctrl) m_canvas->SetFocus(); } m_mouse.position = pos.cast(); From cfc2e9b62ad4754dbc60fba1d4693f2f6cfc66c9 Mon Sep 17 00:00:00 2001 From: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> Date: Sun, 5 Apr 2026 15:10:09 -0300 Subject: [PATCH 08/14] Fix "Access Violation" (#12704) (#12856) Co-authored-by: Kiss Lorand <50251547+kisslorand@users.noreply.github.com> --- src/libslic3r/GCode/GCodeProcessor.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 906c2070e4..f7c9d51cb5 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -5354,8 +5354,10 @@ void GCodeProcessor::process_M1020(const GCodeReader::GCodeLine &line) BOOST_LOG_TRIVIAL(error) << "Invalid M1020 command (" << line.raw() << ")."; } else { - if (eid >= m_result.filaments_count) + if (eid >= m_result.filaments_count) { BOOST_LOG_TRIVIAL(error) << "Invalid M1020 command (" << line.raw() << ")."; + return; + } process_filament_change(eid); } } @@ -5383,8 +5385,10 @@ void GCodeProcessor::process_T(const std::string_view command) BOOST_LOG_TRIVIAL(error) << "Invalid T command (" << command << ")."; } else { - if (eid >= m_result.filaments_count) + if (eid >= m_result.filaments_count) { BOOST_LOG_TRIVIAL(error) << "Invalid T command (" << command << ")."; + return; + } process_filament_change(eid); } } From 05e397f98d2be109b1cb084df165eda00d7dd74a Mon Sep 17 00:00:00 2001 From: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> Date: Sun, 5 Apr 2026 23:01:58 -0300 Subject: [PATCH 09/14] Fix save custom machine profiles with different extruder count (#13035) * fix save profiles * clean 1 * copilot suggestions --- src/libslic3r/Config.hpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index df2c6039a1..7c449f8107 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -587,26 +587,30 @@ public: auto rhs_opt = static_cast*>(rhs); auto inherits_opt = static_cast*>(inherits); - if (inherits->size() != rhs->size()) - throw ConfigurationError("ConfigOptionVector::set_with_nil(): rhs size different with inherits size"); + if (stride <= 0) + throw ConfigurationError("ConfigOptionVector::set_with_nil(): invalid stride"); - this->values.resize(inherits->size(), this->values.front()); + // Tolerate legacy/transitional presets where vector sizes may diverge + // (for example after reducing extruder/variant count). + // Keep rhs as source of truth and nil-mark only on overlapping range. + this->values = rhs_opt->values; - for (size_t i = 0; i < inherits_opt->size(); i= i+stride) { + const size_t overlap_size = std::min(rhs_opt->size(), inherits_opt->size()); + + for (size_t i = 0; i < overlap_size; i += size_t(stride)) { + const size_t group_size = std::min(size_t(stride), overlap_size - i); bool set_nil = true; - for (size_t j = 0; j < stride; j++) { - if (inherits_opt->values[i +j] != rhs_opt->values[i +j]) { + for (size_t j = 0; j < group_size; ++j) { + if (inherits_opt->values[i + j] != rhs_opt->values[i + j]) { set_nil = false; break; } } - for (size_t j = 0; j < stride; j++) { + for (size_t j = 0; j < group_size; ++j) { if (set_nil) { - this->set_at_to_nil(i +j); + this->set_at_to_nil(i + j); } - else - this->values[i +j] = rhs_opt->values[i +j]; } } } From 5ba71e1198ef7352f6de1f7da21f70db182dae45 Mon Sep 17 00:00:00 2001 From: Kevin Lynagh Date: Mon, 6 Apr 2026 06:40:36 +0200 Subject: [PATCH 10/14] Allow printing even if nozzle info isn't reported by firmware. (#12814) * Don't assume 0.4mm nozzle; format as "unknown" if not defined. * Skip nozzle diameter and hardness checks if nozzle info unknown. --------- Co-authored-by: SoftFever --- src/slic3r/GUI/DeviceCore/DevNozzleSystem.h | 2 +- src/slic3r/GUI/GUI_Utils.cpp | 10 +++++++ src/slic3r/GUI/GUI_Utils.hpp | 1 + src/slic3r/GUI/PrintOptionsDialog.cpp | 14 +++++----- src/slic3r/GUI/PrintOptionsDialog.hpp | 3 +-- src/slic3r/GUI/SelectMachine.cpp | 29 ++++++++++++++++----- 6 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/slic3r/GUI/DeviceCore/DevNozzleSystem.h b/src/slic3r/GUI/DeviceCore/DevNozzleSystem.h index 1dbff547b2..db786cf966 100644 --- a/src/slic3r/GUI/DeviceCore/DevNozzleSystem.h +++ b/src/slic3r/GUI/DeviceCore/DevNozzleSystem.h @@ -15,7 +15,7 @@ namespace Slic3r int m_nozzle_id = -1; NozzleFlowType m_nozzle_flow = NozzleFlowType::S_FLOW;// 0-common 1-high flow NozzleType m_nozzle_type = NozzleType::ntUndefine;// 0-stainless_steel 1-hardened_steel 5-tungsten_carbide - float m_diameter = 0.4f;// 0.2mm 0.4mm 0.6mm 0.8mm + float m_diameter = 0.0f;// unknown until reported by the printer }; class DevNozzleSystem diff --git a/src/slic3r/GUI/GUI_Utils.cpp b/src/slic3r/GUI/GUI_Utils.cpp index 5d7dde4d93..3f60f4a409 100644 --- a/src/slic3r/GUI/GUI_Utils.cpp +++ b/src/slic3r/GUI/GUI_Utils.cpp @@ -1,6 +1,7 @@ #include "GUI.hpp" #include "GUI_Utils.hpp" #include "GUI_App.hpp" +#include "I18N.hpp" #include #include @@ -32,6 +33,15 @@ wxDEFINE_EVENT(EVT_VOLUME_ATTACHED, VolumeAttachedEvent); wxDEFINE_EVENT(EVT_VOLUME_DETACHED, VolumeDetachedEvent); #endif // _WIN32 +wxString format_nozzle_diameter(float diameter) +{ + if (diameter <= 0.0f) { + return _L("Unknown"); + } + + return wxString::Format("%smm", wxString::FromDouble(diameter)); +} + CopyFileResult copy_file_gui(const std::string &from, const std::string &to, std::string& error_message, const bool with_check) { #ifdef WIN32 diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index a3c6d1ce64..7e62d00c23 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -66,6 +66,7 @@ wxDECLARE_EVENT(EVT_VOLUME_DETACHED, VolumeDetachedEvent); #endif /* _WIN32 */ wxTopLevelWindow* find_toplevel_parent(wxWindow *window); +wxString format_nozzle_diameter(float diameter); void on_window_geometry(wxTopLevelWindow *tlw, std::function callback); diff --git a/src/slic3r/GUI/PrintOptionsDialog.cpp b/src/slic3r/GUI/PrintOptionsDialog.cpp index f6681e9d7a..3268a6ee9c 100644 --- a/src/slic3r/GUI/PrintOptionsDialog.cpp +++ b/src/slic3r/GUI/PrintOptionsDialog.cpp @@ -1345,7 +1345,7 @@ bool PrinterPartsDialog::Show(bool show) auto type = obj->GetExtderSystem()->GetNozzleType(MAIN_EXTRUDER_ID); auto diameter = obj->GetExtderSystem()->GetNozzleDiameter(MAIN_EXTRUDER_ID); nozzle_type_checkbox->SetValue(GetString(type)); - nozzle_diameter_checkbox->SetValue(GetString(diameter)); + nozzle_diameter_checkbox->SetValue(format_nozzle_diameter(diameter)); // nozzle flow type nozzle_flow_type_label->Show(obj->is_nozzle_flow_type_supported()); @@ -1369,7 +1369,7 @@ bool PrinterPartsDialog::Show(bool show) auto diameter = obj->GetExtderSystem()->GetNozzleDiameter(DEPUTY_EXTRUDER_ID); auto flow_type = obj->GetExtderSystem()->GetNozzleFlowType(DEPUTY_EXTRUDER_ID); multiple_left_nozzle_type_checkbox->SetValue(GetString(type)); - multiple_left_nozzle_diameter_checkbox->SetValue(GetString(diameter)); + multiple_left_nozzle_diameter_checkbox->SetValue(format_nozzle_diameter(diameter)); multiple_left_nozzle_flow_checkbox->SetValue(GetString(flow_type)); //right @@ -1377,7 +1377,7 @@ bool PrinterPartsDialog::Show(bool show) diameter = obj->GetExtderSystem()->GetNozzleDiameter(MAIN_EXTRUDER_ID); flow_type = obj->GetExtderSystem()->GetNozzleFlowType(MAIN_EXTRUDER_ID); multiple_right_nozzle_type_checkbox->SetValue(GetString(type)); - multiple_right_nozzle_diameter_checkbox->SetValue(GetString(diameter)); + multiple_right_nozzle_diameter_checkbox->SetValue(format_nozzle_diameter(diameter)); multiple_right_nozzle_flow_checkbox->SetValue(GetString(flow_type)); if (obj->is_support_refresh_nozzle) { @@ -1421,7 +1421,7 @@ wxString PrinterPartsDialog::GetString(NozzleType nozzle_type) const { default: break; } - return wxEmptyString; + return _L("Unknown"); } wxString PrinterPartsDialog::GetString(NozzleFlowType nozzle_flow_type) const { @@ -1483,7 +1483,7 @@ void PrinterPartsDialog::UpdateNozzleInfo(){ auto type = obj->GetExtderSystem()->GetNozzleType(MAIN_EXTRUDER_ID); auto diameter = obj->GetExtderSystem()->GetNozzleDiameter(MAIN_EXTRUDER_ID); nozzle_type_checkbox->SetValue(GetString(type)); - nozzle_diameter_checkbox->SetValue(GetString(diameter)); + nozzle_diameter_checkbox->SetValue(format_nozzle_diameter(diameter)); // nozzle flow type nozzle_flow_type_label->Show(obj->is_nozzle_flow_type_supported()); @@ -1499,7 +1499,7 @@ void PrinterPartsDialog::UpdateNozzleInfo(){ auto diameter = obj->GetExtderSystem()->GetNozzleDiameter(DEPUTY_EXTRUDER_ID); auto flow_type = obj->GetExtderSystem()->GetNozzleFlowType(DEPUTY_EXTRUDER_ID); multiple_left_nozzle_type_checkbox->SetValue(GetString(type)); - multiple_left_nozzle_diameter_checkbox->SetValue(GetString(diameter)); + multiple_left_nozzle_diameter_checkbox->SetValue(format_nozzle_diameter(diameter)); multiple_left_nozzle_flow_checkbox->SetValue(GetString(flow_type)); //right @@ -1507,7 +1507,7 @@ void PrinterPartsDialog::UpdateNozzleInfo(){ diameter = obj->GetExtderSystem()->GetNozzleDiameter(MAIN_EXTRUDER_ID); flow_type = obj->GetExtderSystem()->GetNozzleFlowType(MAIN_EXTRUDER_ID); multiple_right_nozzle_type_checkbox->SetValue(GetString(type)); - multiple_right_nozzle_diameter_checkbox->SetValue(GetString(diameter)); + multiple_right_nozzle_diameter_checkbox->SetValue(format_nozzle_diameter(diameter)); multiple_right_nozzle_flow_checkbox->SetValue(GetString(flow_type)); } diff --git a/src/slic3r/GUI/PrintOptionsDialog.hpp b/src/slic3r/GUI/PrintOptionsDialog.hpp index 9918cfb80e..9fd6a5c83d 100644 --- a/src/slic3r/GUI/PrintOptionsDialog.hpp +++ b/src/slic3r/GUI/PrintOptionsDialog.hpp @@ -68,8 +68,7 @@ private: wxString GetString(NozzleType nozzle_type) const; wxString GetString(NozzleFlowType nozzle_flow_type) const; - wxString GetString(float diameter) const { return wxString::FromDouble(diameter); }; -}; + }; class PrintOptionsDialog : public DPIDialog diff --git a/src/slic3r/GUI/SelectMachine.cpp b/src/slic3r/GUI/SelectMachine.cpp index 464a1332c2..3495fca1a6 100644 --- a/src/slic3r/GUI/SelectMachine.cpp +++ b/src/slic3r/GUI/SelectMachine.cpp @@ -1823,7 +1823,15 @@ static bool _is_same_nozzle_diameters(MachineObject* obj, float &tag_nozzle_diam } tag_nozzle_diameter = float(opt_nozzle_diameters->get_at(used_nozzle_idx)); - if (tag_nozzle_diameter != obj->GetExtderSystem()->GetNozzleDiameter(used_nozzle_idx)) + auto machine_nozzle_diameter = obj->GetExtderSystem()->GetNozzleDiameter(used_nozzle_idx); + + // Assume matching if diameter is unknown + if (machine_nozzle_diameter == 0.0f) + { + continue; + } + + if (tag_nozzle_diameter != machine_nozzle_diameter) { mismatch_nozzle_id = used_nozzle_idx; return false; @@ -1840,7 +1848,16 @@ static bool _is_same_nozzle_diameters(MachineObject* obj, float &tag_nozzle_diam bool SelectMachineDialog::is_nozzle_hrc_matched(const DevExtder* extruder, std::string& filament_type) const { - auto printer_nozzle_hrc = Print::get_hrc_by_nozzle_type(extruder->GetNozzleType()); + if (extruder == nullptr) return false; + + auto printer_nozzle_type = extruder->GetNozzleType(); + + // Assume matching if nozzle type unknown + if (printer_nozzle_type == NozzleType::ntUndefine) { + return true; + } + + auto printer_nozzle_hrc = Print::get_hrc_by_nozzle_type(printer_nozzle_type); auto preset_bundle = wxGetApp().preset_bundle; MaterialHash::const_iterator iter = m_materialList.begin(); @@ -5154,13 +5171,11 @@ static wxString _get_tips(MachineObject* obj_) wxString ext_diameter; if (obj_->GetExtderSystem()->GetTotalExtderCount() == 1) { - ext_diameter += wxString::FromDouble(obj_->GetExtderSystem()->GetNozzleDiameter(0)); - ext_diameter += "mm"; + ext_diameter += format_nozzle_diameter(obj_->GetExtderSystem()->GetNozzleDiameter(0)); } else if (obj_->GetExtderSystem()->GetTotalExtderCount() == 2) { - ext_diameter += wxString::FromDouble(obj_->GetExtderSystem()->GetNozzleDiameter(1));//Left + ext_diameter += format_nozzle_diameter(obj_->GetExtderSystem()->GetNozzleDiameter(1));//Left ext_diameter += "/"; - ext_diameter += wxString::FromDouble(obj_->GetExtderSystem()->GetNozzleDiameter(0)); - ext_diameter += "mm"; + ext_diameter += format_nozzle_diameter(obj_->GetExtderSystem()->GetNozzleDiameter(0)); } else { assert(0); } From 7d21cf78c79010f892e2e3c71921f31b2d1353b1 Mon Sep 17 00:00:00 2001 From: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> Date: Mon, 6 Apr 2026 03:06:48 -0300 Subject: [PATCH 11/14] =?UTF-8?q?=F0=9F=92=A5Fix=20crash=20when=20return?= =?UTF-8?q?=20from=20assembly=20mode=20linux=20(#13091)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix return from assembly linux * Tie deferred callback to canvas lifetime and add shutdown guards Use m_canvas->CallAfter instead of wxGetApp().CallAfter to ensure the callback is only executed if the canvas is still alive. Add early returns if wxGetApp().is_closing() is true before accessing plater or the 3D canvas, reducing the risk of use-after-free during application shutdown. Addresses feedback regarding potential UI teardown race conditions. * copilot suggestion * Use local reference to wxGetApp() in Return button callback Replace multiple calls to wxGetApp() with a single local reference to improve readability and avoid redundant function calls. --- src/slic3r/GUI/GLCanvas3D.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 334643a201..5e23a9d4ae 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -8480,11 +8480,27 @@ void GLCanvas3D::_render_return_toolbar() const ImVec2 margin = ImVec2(10.0f, 5.0f); if (ImGui::ImageTextButton(real_size,_utf8(L("Return")).c_str(), m_return_toolbar.get_return_texture_id(), button_icon_size, uv0, uv1, -1, bg_col, tint_col, margin)) { - if (m_canvas != nullptr) - wxPostEvent(m_canvas, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); const_cast(&m_gizmos)->reset_all_states(); - wxGetApp().plater()->get_view3D_canvas3D()->get_gizmos_manager().reset_all_states(); - wxGetApp().plater()->get_view3D_canvas3D()->reload_scene(true); + if (m_canvas != nullptr && !wxGetApp().is_closing()) { + m_canvas->CallAfter([]() { + auto& app = wxGetApp(); + if (app.is_closing()) + return; + + auto* plater = app.plater(); + if (plater == nullptr) + return; + + plater->select_view_3D("3D"); + + auto* view3d_canvas = plater->get_view3D_canvas3D(); + if (view3d_canvas == nullptr) + return; + + view3d_canvas->get_gizmos_manager().reset_all_states(); + view3d_canvas->reload_scene(true); + }); + } } ImGui::PopStyleColor(5); ImGui::PopStyleVar(1); From ff77db77dbce4222fa59a0fd49e4a3d206663a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Herrmann?= Date: Mon, 6 Apr 2026 08:08:10 +0200 Subject: [PATCH 12/14] Add build definitions for cachyos (#12898) Derived from Arch linux.d file but changed a bit to build on CachyOS --- scripts/linux.d/cachyos | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 scripts/linux.d/cachyos diff --git a/scripts/linux.d/cachyos b/scripts/linux.d/cachyos new file mode 100644 index 0000000000..9557c55e62 --- /dev/null +++ b/scripts/linux.d/cachyos @@ -0,0 +1,45 @@ +#!/bin/bash +# these are the CachyOS Linux specific build functions + +# Additional Dev packages for OrcaSlicer +export REQUIRED_DEV_PACKAGES=( + cmake + curl + dbus + eglexternalplatform + extra-cmake-modules + file + gettext + git + glew + gstreamer + gtk3 + libmspack + libsecret + libspnav + mesa + ninja + openssl + texinfo + wayland-protocols + webkit2gtk + wget +) + +if [[ -n "$UPDATE_LIB" ]] +then + echo -n -e "Updating linux ...\n" + NEEDED_PKGS=() + for PKG in "${REQUIRED_DEV_PACKAGES[@]}"; do + pacman -Q "${PKG}" > /dev/null || NEEDED_PKGS+=("${PKG}") + done + + if [[ "${#NEEDED_PKGS[*]}" -gt 0 ]]; then + sudo pacman -Syy --noconfirm "${NEEDED_PKGS[@]}" + fi + echo -e "done\n" + exit 0 +fi + +export FOUND_GTK3_DEV +FOUND_GTK3_DEV=$(pacman -Q gtk3) From b79d3e17011aedfbb0a5bcc2f1349a954984b91c Mon Sep 17 00:00:00 2001 From: Sabriel-Koh <52443698+Sabriel-Koh@users.noreply.github.com> Date: Mon, 6 Apr 2026 14:27:49 +0800 Subject: [PATCH 13/14] Fix: generic filaments not showing on list (#13037) --- src/libslic3r/Preset.hpp | 2 +- src/libslic3r/PresetBundle.cpp | 27 +++++-- src/slic3r/GUI/WebGuideDialog.cpp | 116 +++++++++++++++------------- src/slic3r/GUI/Widgets/DropDown.cpp | 8 +- 4 files changed, 90 insertions(+), 63 deletions(-) diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index d76f2b93c2..09e2c82890 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -653,7 +653,7 @@ public: int match_quality = -1; for (; i < n; ++i) // Since we use the filament selection from Wizard, it's needed to control the preset visibility too - if (m_presets[i].is_compatible) { + if (m_presets[i].is_compatible && m_presets[i].is_visible) { int this_match_quality = prefered_condition(m_presets[i]); if (this_match_quality > match_quality) { if (match_quality == std::numeric_limits::max()) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index d5244b6bc0..7c6522310c 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -734,10 +734,17 @@ void PresetBundle::reset_project_embedded_presets() Preset& current_printer = this->printers.get_selected_preset(); const std::vector &prefered_filament_profiles = current_printer.config.option("default_filament_profile")->values; const std::string prefered_filament_profile = prefered_filament_profiles.empty() ? std::string() : prefered_filament_profiles.front(); - if (!prefered_filament_profile.empty()) - filament_presets[i] = prefered_filament_profile; - else - filament_presets[i] = this->filaments.first_visible().name; + if (!prefered_filament_profile.empty()) { + // Check if preferred filament exists and is visible + const Preset* preferred_preset = this->filaments.find_preset(prefered_filament_profile, false); + if (preferred_preset && preferred_preset->is_visible) { + filament_presets[i] = prefered_filament_profile; + } else { + // Fall back to first visible filament + filament_presets[i] = this->filaments.first_visible().name; + } + } else + filament_presets[i] = this->filaments.first_visible().name; } } } @@ -1970,8 +1977,14 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p initial_print_profile_name = prefered_print_profile; const std::vector& prefered_filament_profiles = preferred_printer->config.option("default_filament_profile")->values; - if ((!initial_filament_profile_name.compare("Default Filament")) && (prefered_filament_profiles.size() > 0)) - initial_filament_profile_name = prefered_filament_profiles[0]; + if ((!initial_filament_profile_name.compare("Default Filament")) && (prefered_filament_profiles.size() > 0)) { + // Check if preferred filament is visible + const Preset* preferred_preset = this->filaments.find_preset(prefered_filament_profiles[0], false); + if (preferred_preset && preferred_preset->is_visible) { + initial_filament_profile_name = prefered_filament_profiles[0]; + } + // If not visible, keep the default "Default Filament" which will be resolved later + } } // Selects the profile, leaves it to -1 if the initial profile name is empty or if it was not found. @@ -4452,7 +4465,7 @@ void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_pri int operator()(const Preset &preset) const { // Don't match any properties of the "-- default --" profile or the external profiles when switching printer profile. - if (preset.is_default || preset.is_external) + if (preset.is_default || preset.is_external || !preset.is_visible) return 0; if (! m_prefered_alias.empty() && m_prefered_alias == preset.alias) // Matching an alias, always take this preset with priority. diff --git a/src/slic3r/GUI/WebGuideDialog.cpp b/src/slic3r/GUI/WebGuideDialog.cpp index f0624056d8..2933dd7bc9 100644 --- a/src/slic3r/GUI/WebGuideDialog.cpp +++ b/src/slic3r/GUI/WebGuideDialog.cpp @@ -7,6 +7,7 @@ #include #include "I18N.hpp" #include "libslic3r/AppConfig.hpp" +#include "libslic3r/Config.hpp" #include "libslic3r/PresetBundle.hpp" #include "slic3r/GUI/wxExtensions.hpp" #include "slic3r/GUI/GUI_App.hpp" @@ -438,6 +439,50 @@ void GuideFrame::OnScriptMessage(wxWebViewEvent &evt) wxString s2 = OneSelect["model"]; if (s1.compare(s2) == 0) { m_ProfileJson["model"][m]["nozzle_selected"] = m_ProfileJson["model"][m]["nozzle_diameter"]; + + // Automatically select default materials for this printer model + // This mirrors the behavior of the old ConfigWizard::select_default_materials_for_printer_model() + if (TmpModel.contains("materials") && !TmpModel["materials"].is_null()) { + std::string materials_str; + + // Handle both string and JSON array formats for materials + if (TmpModel["materials"].is_string()) { + materials_str = TmpModel["materials"].get(); + } else if (TmpModel["materials"].is_array()) { + // Convert JSON array to semicolon-separated string for unescape_strings_cstyle + for (const auto& material : TmpModel["materials"]) { + if (!materials_str.empty()) materials_str += ";"; + materials_str += material.get(); + } + } else { + materials_str = ""; + } + + boost::trim(materials_str); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Processing default_materials for printer: " << s1.ToStdString() << " - materials: " << materials_str; + + // Use the same parsing logic as ConfigWizard::select_default_materials_for_printer_model() + // This calls unescape_strings_cstyle() just like Preset.cpp:298 does + std::vector materials; + if (Slic3r::unescape_strings_cstyle(materials_str, materials)) { + for (const std::string& material : materials) { + if (!material.empty()) { + // Mark this filament as selected if it exists in our filament list + // This mirrors appconfig_new.set(section, material, "true") from ConfigWizard.cpp:2150 + if (m_ProfileJson["filament"].contains(material)) { + m_ProfileJson["filament"][material]["selected"] = 1; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Automatically selected default filament: " << material; + } else { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " Default filament '" << material << "' not found in available filaments for printer: " << s1.ToStdString(); + } + } + } + } else { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Malformed default_materials field: " << materials_str << " for printer: " << s1.ToStdString(); + } + } else { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " No default_materials defined for printer: " << s1.ToStdString(); + } break; } } @@ -838,45 +883,8 @@ bool GuideFrame::apply_config(AppConfig *app_config, PresetBundle *preset_bundle // Not switch filament //get_first_added_material_preset(AppConfig::SECTION_FILAMENTS, first_added_filament); - // For each @System filament, check if a vendor-specific override exists - // in the loaded profiles. If so, replace the @System variant with the - // override (e.g. replace "Generic ABS @System" with BBL "Generic ABS"). - // When printers from the default bundle are also selected, keep @System - // too since those printers need it. - static const std::string system_suffix = " @System"; - auto it_default = enabled_vendors.find(PresetBundle::ORCA_DEFAULT_BUNDLE); - bool has_default_bundle_printer = it_default != enabled_vendors.end() && !it_default->second.empty(); - bool has_filament_profiles = m_ProfileJson.contains("filament"); - - // Check if any non-default vendor has selected printers - bool has_vendor_printer = false; - for (const auto& [vendor, models] : enabled_vendors) { - if (vendor != PresetBundle::ORCA_DEFAULT_BUNDLE && !models.empty()) { - has_vendor_printer = true; - break; - } - } - - std::map supplemented_filaments; - for (const auto& [name, value] : enabled_filaments) { - if (name.size() > system_suffix.size() && - name.compare(name.size() - system_suffix.size(), system_suffix.size(), system_suffix) == 0) { - std::string short_name = name.substr(0, name.size() - system_suffix.size()); - if (has_vendor_printer && has_filament_profiles && m_ProfileJson["filament"].contains(short_name)) { - supplemented_filaments[short_name] = value; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Replacing @System filament: '" << name << "' -> '" << short_name << "'"; - if (has_default_bundle_printer) { - supplemented_filaments[name] = value; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Also keeping '" << name << "' for default bundle printers"; - } - continue; - } - } - supplemented_filaments[name] = value; - } - //update the app_config - app_config->set_section(AppConfig::SECTION_FILAMENTS, supplemented_filaments); + app_config->set_section(AppConfig::SECTION_FILAMENTS, enabled_filaments); app_config->set_vendors(m_appconfig_new); if (check_unsaved_preset_changes) @@ -886,10 +894,10 @@ bool GuideFrame::apply_config(AppConfig *app_config, PresetBundle *preset_bundle // If the active filament is not in the wizard-selected filaments, switch to the first // compatible wizard-selected filament. This handles the first-run case where load_presets // falls back to "Generic PLA" even though the user selected a different filament. - bool active_filament_selected = supplemented_filaments.empty() - || supplemented_filaments.count(preset_bundle->filament_presets.front()) > 0; + bool active_filament_selected = enabled_filaments.empty() + || enabled_filaments.count(preset_bundle->filament_presets.front()) > 0; if (!active_filament_selected) { - for (const auto& [filament_name, _] : supplemented_filaments) { + for (const auto& [filament_name, _] : enabled_filaments) { const Preset* preset = preset_bundle->filaments.find_preset(filament_name); if (preset && preset->is_visible && preset->is_compatible) { preset_bundle->filaments.select_preset_by_name(filament_name, true); @@ -1124,21 +1132,23 @@ int GuideFrame::LoadProfileData() return 0; } - //sync to web - std::string strAll = m_ProfileJson.dump(-1, ' ', false, json::error_handler_t::ignore); + wxGetApp().CallAfter([this] { + if (!m_destroy) { + //sync to appconfig first to populate current selections + SaveProfileData(); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished, json contents: " << std::endl << strAll; - json m_Res = json::object(); - m_Res["command"] = "userguide_profile_load_finish"; - m_Res["sequence_id"] = "10001"; - wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', true)); - if (!m_destroy) - wxGetApp().CallAfter([this, strJS] { RunScript(strJS); }); + //sync to web after selections are populated + std::string strAll = m_ProfileJson.dump(-1, ' ', false, json::error_handler_t::ignore); - //sync to appconfig - if (!m_destroy) - wxGetApp().CallAfter([this] { SaveProfileData(); }); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished, json contents: " << std::endl << strAll; + json m_Res = json::object(); + m_Res["command"] = "userguide_profile_load_finish"; + m_Res["sequence_id"] = "10001"; + wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', true)); + RunScript(strJS); + } + }); } catch (std::exception& e) { // wxLogMessage("GUIDE: load_profile_error %s ", e.what()); // wxMessageBox(e.what(), "", MB_OK); diff --git a/src/slic3r/GUI/Widgets/DropDown.cpp b/src/slic3r/GUI/Widgets/DropDown.cpp index 04807f374d..1aee9377e1 100644 --- a/src/slic3r/GUI/Widgets/DropDown.cpp +++ b/src/slic3r/GUI/Widgets/DropDown.cpp @@ -420,7 +420,9 @@ int DropDown::hoverIndex() { if (hover_item < 0) return -1; - if (count == items.size()) + // BUG FIX: Can't take the shortcut if subDropDown exists (which means there are groups) + // because we need to detect group headers which return negative indices + if (count == items.size() && subDropDown == nullptr) return hover_item; int index = -1; std::set groups; @@ -448,7 +450,9 @@ int DropDown::selectedItem() { if (selection < 0) return -1; - if (count == items.size()) + // BUG FIX: Can't take the shortcut if subDropDown exists (which means there are groups) + // because the visual position differs from the actual item index when groups are shown + if (count == items.size() && subDropDown == nullptr) return selection; auto & sel = items[selection]; if (group.IsEmpty() ? !sel.group.IsEmpty() : sel.group != group) From 7bec6c2d6bc86c23b879e11b0f406e5aeb9602aa Mon Sep 17 00:00:00 2001 From: Eldenroot Date: Mon, 6 Apr 2026 14:51:52 +0200 Subject: [PATCH 14/14] unix: Add model/step to the list of supported mime-types (#13000) Cherry picked: https://github.com/bambulab/BambuStudio/commit/64d360523d8007b3a4c988d2dbef2402ad42110f#diff-cc4863f0a9f2b1f46d3b0e04611772075d67ba61b6bdcfdc3a746b265668f8f4 --- src/dev-utils/platform/unix/OrcaSlicer.desktop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dev-utils/platform/unix/OrcaSlicer.desktop b/src/dev-utils/platform/unix/OrcaSlicer.desktop index c28ce56d44..188d2ead34 100644 --- a/src/dev-utils/platform/unix/OrcaSlicer.desktop +++ b/src/dev-utils/platform/unix/OrcaSlicer.desktop @@ -5,7 +5,7 @@ Icon=OrcaSlicer Exec=orca-slicer %U Terminal=false Type=Application -MimeType=model/stl;model/3mf;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;x-scheme-handler/orcaslicer; +MimeType=model/stl;model/3mf;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;x-scheme-handler/orcaslicer;model/step; Categories=Graphics;3DGraphics;Engineering; Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA StartupNotify=false