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" 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 0000000000..c442446270 Binary files /dev/null and b/resources/profiles/Creality/creality_k2_buildplate_model.stl differ diff --git a/resources/profiles/Creality/creality_k2_buildplate_texture.png b/resources/profiles/Creality/creality_k2_buildplate_texture.png new file mode 100644 index 0000000000..4dcf16e604 Binary files /dev/null and b/resources/profiles/Creality/creality_k2_buildplate_texture.png differ diff --git a/resources/profiles/Creality/machine/Creality K2.json b/resources/profiles/Creality/machine/Creality K2.json index ea6661f391..aacbcf7de4 100644 --- a/resources/profiles/Creality/machine/Creality K2.json +++ b/resources/profiles/Creality/machine/Creality K2.json @@ -5,9 +5,9 @@ "nozzle_diameter": "0.2;0.4;0.6;0.8", "machine_tech": "FFF", "family": "Creality", - "bed_model": "creality_k1_buildplate_model.stl", + "bed_model": "creality_k2_buildplate_model.stl", "default_bed_type": "Textured PEI Plate", - "bed_texture": "creality_k1_buildplate_texture.png", + "bed_texture": "creality_k2_buildplate_texture.png", "hotend_model": "", "default_materials": "Creality Generic ABS @K2-all;Creality Generic ASA @K2-all;Creality Generic PETG @K2-all;Creality Generic PLA @K2-all;Creality Generic PLA High Speed @K2-all;Creality Generic PLA Matte @K2-all;Creality Generic PLA Silk @K2-all" } \ No newline at end of file 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) 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 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]; } } } 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; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 5e1bad0b69..30e569f1cf 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -5461,8 +5461,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); } } @@ -5490,8 +5492,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); } } 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/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 0ae4173911..01fb601148 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -9338,11 +9338,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/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/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 3d964c7dab..cc89ff42d0 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4285,7 +4285,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(); @@ -8485,11 +8488,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); diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 47e6246509..3cc39fe1ce 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -752,18 +752,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 ea7b40893f..a3c68d34d4 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(); 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/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ee235d4192..a325a48d30 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); 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); } 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) 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;