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)