From 2434f3a181125a56e995de4c2614da2f45b42c46 Mon Sep 17 00:00:00 2001 From: tome9111991 <57866234+tome9111991@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:46:31 +0200 Subject: [PATCH 01/26] Fix AMS filament preset switch crash --- src/slic3r/GUI/Plater.cpp | 6 +++++- src/slic3r/GUI/PresetComboBoxes.cpp | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a015c8624e..e67e664bf9 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3335,7 +3335,8 @@ std::map Sidebar::build_filament_ams_list(MachineObject tray_config.set_key_value("slot_id", new ConfigOptionStrings{slot_id}); tray_config.set_key_value("filament_type", new ConfigOptionStrings{tray.m_fila_type}); tray_config.set_key_value("tray_name", new ConfigOptionStrings{ name }); - tray_config.set_key_value("filament_colour", new ConfigOptionStrings{into_u8(wxColour("#" + tray.color).GetAsString(wxC2S_HTML_SYNTAX))}); + const std::string filament_color = into_u8(wxColour("#" + tray.color).GetAsString(wxC2S_HTML_SYNTAX)); + tray_config.set_key_value("filament_colour", new ConfigOptionStrings{filament_color}); tray_config.set_key_value("filament_multi_colour", new ConfigOptionStrings{}); tray_config.set_key_value("filament_colour_type", new ConfigOptionStrings{std::to_string(tray.ctype)}); tray_config.set_key_value("filament_exist", new ConfigOptionBools{tray.is_exists}); @@ -3348,6 +3349,9 @@ std::map Sidebar::build_filament_ams_list(MachineObject for (int i = 0; i < tray.cols.size(); ++i) { tray_config.opt("filament_multi_colour")->values.push_back(into_u8(wxColour("#" + tray.cols[i]).GetAsString(wxC2S_HTML_SYNTAX))); } + if (tray_config.opt("filament_multi_colour")->values.empty() && !filament_color.empty()) { + tray_config.opt("filament_multi_colour")->values.push_back(filament_color); + } return tray_config; }; diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 70cbd5688a..0f35e7ab93 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -255,6 +255,11 @@ int PresetComboBox::update_ams_color() auto color_pack = static_cast(cfg->option("filament_multi_colour")->clone()); // multi color (all colors in all kinds of filament) auto color_type = static_cast(cfg->option("filament_colour_type")->clone()); // color type + if (m_filament_idx >= color_head->values.size()) color_head->values.resize(m_filament_idx + 1); + if (m_filament_idx >= color_pack->values.size()) color_pack->values.resize(m_filament_idx + 1); + if (m_filament_idx >= color_type->values.size()) color_type->values.resize(m_filament_idx + 1); + if (ctype.empty()) ctype = "1"; + color_head->values[m_filament_idx] = color; color_type->values[m_filament_idx] = ctype; std::string color_str = ""; // Translate multi color info to config storage format From 99f2bffada42c8c9878d67cadd615374ff10e9bc Mon Sep 17 00:00:00 2001 From: tome9111991 <57866234+tome9111991@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:31:01 +0200 Subject: [PATCH 02/26] Fix Moonraker Happy Hare AMS filament sync --- src/slic3r/GUI/Plater.cpp | 56 ++++++++++++++-------- src/slic3r/Utils/MoonrakerPrinterAgent.cpp | 6 +++ 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e67e664bf9..6226514208 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3358,6 +3358,10 @@ std::map Sidebar::build_filament_ams_list(MachineObject if (obj->ams_support_virtual_tray) { int extruder = 0x10000; // Main (first) extruder at right for (auto & vt_tray : obj->vt_slot) { + if (!vt_tray.is_exists && vt_tray.setting_id.empty() && vt_tray.m_fila_type.empty() && vt_tray.color.empty()) { + extruder = 0; + continue; + } filament_ams_list.emplace(extruder + stoi(vt_tray.id), build_tray_config(vt_tray, "Ext",vt_tray.id, "0"));//254 or 255 extruder = 0; } @@ -3514,7 +3518,9 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn) p->plater->pop_warning_and_go_to_device_page("", Plater::PrinterWarningType::EMPTY_FILAMENT, _L("Sync printer information")); return; } - if (!wxGetApp().plater()->is_same_printer_for_connected_and_selected()) { + auto* agent = wxGetApp().getDeviceManager()->get_agent(); + const bool direct_pull_sync = agent && agent->get_filament_sync_mode() == FilamentSyncMode::pull; + if (!direct_pull_sync && !wxGetApp().plater()->is_same_printer_for_connected_and_selected()) { return; } std::string ams_filament_ids = wxGetApp().app_config->get("ams_filament_ids", p->ams_list_device); @@ -3522,31 +3528,39 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn) if (!ams_filament_ids.empty()) { boost::algorithm::split(list2, ams_filament_ids, boost::algorithm::is_any_of(",")); } - wxGetApp().plater()->update_all_plate_thumbnails(true);//preview thumbnail for sync_dlg - SyncAmsInfoDialog::SyncInfo temp_info; - temp_info.use_dialog_pos = false; - temp_info.cancel_text_to_later = is_from_big_sync_btn; - if (m_sync_dlg == nullptr) { - m_sync_dlg = new SyncAmsInfoDialog(this, temp_info); + SyncAmsInfoDialog::SyncResult sync_result; + int dlg_res{(int) wxID_YES}; + if (direct_pull_sync) { + sync_result.direct_sync = true; + sync_result.is_same_printer = true; } else { - m_sync_dlg->set_info(temp_info); - } - int dlg_res{(int) wxID_CANCEL}; - if (m_sync_dlg->is_need_show()) { - m_sync_dlg->deal_only_exist_ext_spool(obj); - if (m_sync_dlg->is_dirty_filament()) { - wxGetApp().get_tab(Preset::TYPE_FILAMENT)->select_preset(wxGetApp().preset_bundle->filament_presets[0], false, "", false, true); - wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config); - dynamic_filament_list.update(); + wxGetApp().plater()->update_all_plate_thumbnails(true);//preview thumbnail for sync_dlg + SyncAmsInfoDialog::SyncInfo temp_info; + temp_info.use_dialog_pos = false; + temp_info.cancel_text_to_later = is_from_big_sync_btn; + if (m_sync_dlg == nullptr) { + m_sync_dlg = new SyncAmsInfoDialog(this, temp_info); + } else { + m_sync_dlg->set_info(temp_info); + } + dlg_res = (int) wxID_CANCEL; + if (m_sync_dlg->is_need_show()) { + m_sync_dlg->deal_only_exist_ext_spool(obj); + if (m_sync_dlg->is_dirty_filament()) { + wxGetApp().get_tab(Preset::TYPE_FILAMENT)->select_preset(wxGetApp().preset_bundle->filament_presets[0], false, "", false, true); + wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config); + dynamic_filament_list.update(); + } + m_sync_dlg->set_check_dirty_fialment(false); + dlg_res = m_sync_dlg->ShowModal(); + } else { + dlg_res =(int) wxID_YES; } - m_sync_dlg->set_check_dirty_fialment(false); - dlg_res = m_sync_dlg->ShowModal(); - } else { - dlg_res =(int) wxID_YES; } if (dlg_res == wxID_CANCEL) return; - auto sync_result = m_sync_dlg->get_result(); + if (!direct_pull_sync) + sync_result = m_sync_dlg->get_result(); if (!sync_result.is_same_printer) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "check error: sync_result.is_same_printer value is false"; return; diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index f1d892134b..85097ff468 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -776,6 +776,7 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vectorfilaments.filament_id_by_type(tray.tray_type) @@ -818,6 +820,10 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vector Date: Fri, 29 May 2026 20:42:10 +0200 Subject: [PATCH 03/26] feat(moonraker): Bridge-Filament-Hint (tray_info_idx + vendor) respektieren MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit KX-Bridge sendet bereits konkrete Filament-IDs und Vendor-Hints im AMS-JSON, aber MoonrakerPrinterAgent überschreibt sie unbedingt mit filament_id_by_type() → erstes sichtbares Preset alphabetisch gewinnt. Patch: - AmsTrayData um filament_vendor erweitert - Liest tray_info_idx + filament_vendor aus Bridge-JSON - Wenn tray_info_idx leer + vendor vorhanden: Vendor+Type+Color-Match (gleiche Logik wie SnapmakerPrinterAgent) - Sonst: alter filament_id_by_type-Fallback (unverändertes Verhalten) Co-Authored-By: Claude Sonnet 4.6 --- src/slic3r/Utils/MoonrakerPrinterAgent.cpp | 60 ++++++++++++++++++++-- src/slic3r/Utils/MoonrakerPrinterAgent.hpp | 1 + 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index 85097ff468..7cc53d8b0a 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -37,6 +37,48 @@ struct WsEndpoint bool secure = false; }; +// Find best-matching visible filament preset by vendor + type + color. +// Same algorithm as SnapmakerPrinterAgent's helper. Allows the KX-Bridge +// (which knows the user's slot brand selection) to direct OrcaSlicer to a +// specific preset instead of falling back to "first sichtbares PETG" lookup. +std::string find_closest_color_preset_by_vendor_and_type(const Slic3r::PresetCollection& filaments, + const std::string& vendor_name, + const std::string& filament_type, + const std::string& color_rgba) +{ + std::string best_match_id = ""; + int best_color_distance = 0xffffffff; + if (vendor_name.empty() || filament_type.empty()) return best_match_id; + + auto parse_color = [](const std::string& s) -> unsigned int { + if (s.empty()) return 0; + std::string h = s; + if (!h.empty() && h.front() == '#') h.erase(0, 1); + if (h.size() >= 8) h = h.substr(0, 6); // strip alpha + try { return std::stoul(h, nullptr, 16); } catch (...) { return 0; } + }; + + unsigned int target = parse_color(color_rgba); + + for (const auto& p : filaments.get_presets()) { + if (!(p.is_visible && p.is_compatible)) continue; + if (filaments.get_preset_base(p) != &p) continue; + if (p.config.opt_string("filament_vendor", 0u) != vendor_name) continue; + if (p.config.opt_string("filament_type", 0u) != filament_type) continue; + + unsigned int pc = parse_color(p.config.opt_string("default_filament_colour", 0u)); + int dr = ((target ) & 0xff) - ((pc ) & 0xff); + int dg = ((target >> 8) & 0xff) - ((pc >> 8) & 0xff); + int db = ((target >> 16) & 0xff) - ((pc >> 16) & 0xff); + unsigned int distance = dr * dr + dg * dg + db * db; + if (distance < (unsigned int)best_color_distance) { + best_color_distance = (int)distance; + best_match_id = p.filament_id; + } + } + return best_match_id; +} + bool parse_ws_endpoint(const std::string& base_url, WsEndpoint& endpoint) { if (base_url.empty()) { @@ -806,10 +848,22 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vectorfilaments.filament_id_by_type(tray.tray_type) - : map_filament_type_to_generic_id(tray.tray_type); + if (tray.tray_info_idx.empty() && bundle && !tray.filament_vendor.empty()) { + std::string mid = find_closest_color_preset_by_vendor_and_type( + bundle->filaments, tray.filament_vendor, tray.tray_type, tray.tray_color); + if (!mid.empty()) tray.tray_info_idx = mid; + } + if (tray.tray_info_idx.empty()) { + tray.tray_info_idx = bundle + ? bundle->filaments.filament_id_by_type(tray.tray_type) + : map_filament_type_to_generic_id(tray.tray_type); + } max_lane_index = std::max(max_lane_index, lane_index); trays.push_back(tray); diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp index a37a6c3fd6..47f5846245 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp @@ -94,6 +94,7 @@ protected: std::string tray_type; // Material type (e.g., "PLA", "ASA") std::string tray_color; // Raw color (#RRGGBB, 0xRRGGBB, or RRGGBBAA) std::string tray_info_idx; // Setting ID (optional) + std::string filament_vendor; // Vendor hint from bridge (optional, KX-Bridge sendet das) int bed_temp = 0; // Optional int nozzle_temp = 0; // Optional }; From 6fe27304c835ca94066f6014b16016a5e44577e7 Mon Sep 17 00:00:00 2001 From: viewit Date: Sun, 31 May 2026 20:03:11 +0200 Subject: [PATCH 04/26] =?UTF-8?q?fix(moonraker):=20Vendor-Match=20auch=20f?= =?UTF-8?q?=C3=BCr=20inkompatible/nicht-instantiierbare=20Base-Presets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pass 1: kompatible + sichtbare Presets (wie bisher) Pass 2 (neu): wenn Pass 1 leer → alle Base-Presets des Vendors + Typs durchsuchen, unabhängig von is_compatible/is_visible. Behebt: Elegoo PLA (@base, instantiation=false) und ähnliche Hersteller die ein globales Base-Preset haben aber kein druckerspezifisches Profil für den Kobra X → bisher immer "Generic PLA", jetzt "Elegoo PLA". Co-Authored-By: Claude Sonnet 4.6 --- src/slic3r/Utils/MoonrakerPrinterAgent.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index 7cc53d8b0a..2624bc038e 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -60,6 +60,7 @@ std::string find_closest_color_preset_by_vendor_and_type(const Slic3r::PresetCol unsigned int target = parse_color(color_rgba); + // Pass 1: compatible + visible presets (ideal match) for (const auto& p : filaments.get_presets()) { if (!(p.is_visible && p.is_compatible)) continue; if (filaments.get_preset_base(p) != &p) continue; @@ -76,6 +77,25 @@ std::string find_closest_color_preset_by_vendor_and_type(const Slic3r::PresetCol best_match_id = p.filament_id; } } + if (!best_match_id.empty()) return best_match_id; + + // Pass 2: fallback — any base preset matching vendor+type, even if not compatible/instantiatable. + // Covers vendors like Elegoo that have a @base preset but no printer-specific variant for this printer. + for (const auto& p : filaments.get_presets()) { + if (p.config.opt_string("filament_vendor", 0u) != vendor_name) continue; + if (p.config.opt_string("filament_type", 0u) != filament_type) continue; + if (p.filament_id.empty()) continue; + + unsigned int pc = parse_color(p.config.opt_string("default_filament_colour", 0u)); + int dr = ((target ) & 0xff) - ((pc ) & 0xff); + int dg = ((target >> 8) & 0xff) - ((pc >> 8) & 0xff); + int db = ((target >> 16) & 0xff) - ((pc >> 16) & 0xff); + unsigned int distance = dr * dr + dg * dg + db * db; + if (distance < (unsigned int)best_color_distance) { + best_color_distance = (int)distance; + best_match_id = p.filament_id; + } + } return best_match_id; } From bdc9cc815bb9cc66b1e12d0c1a94789e9dc8f3ba Mon Sep 17 00:00:00 2001 From: viewit Date: Sun, 31 May 2026 21:18:48 +0200 Subject: [PATCH 05/26] fix(moonraker): Vendor-Match auch wenn tray_info_idx gesetzt aber inkompatibel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wenn die Bridge eine tray_info_idx sendet (z.B. GFL99 für Bambu Lab PLA) die kein kompatibler Preset für den aktiven Drucker hat, wird jetzt trotzdem der Vendor+Type+Color-Match versucht statt direkt auf Generic zurückzufallen. Behebt: Bridge sendet GFL99 (Bambu-ID) für Slot mit vendor='Bambu Lab' → kein Kobra-X-kompatibler Preset für GFL99 → bisher Generic PLA → jetzt Vendor-Match → findet besten Bambu-Lab-PLA-Preset Co-Authored-By: Claude Sonnet 4.6 --- src/slic3r/Utils/MoonrakerPrinterAgent.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index 2624bc038e..ff18d98a81 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -874,7 +874,22 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vectorfilaments.get_presets()) { + if (p.is_compatible && p.filament_id == tray.tray_info_idx) { + idx_has_compatible_preset = true; + break; + } + } + } + + if (bundle && !tray.filament_vendor.empty() && (tray.tray_info_idx.empty() || !idx_has_compatible_preset)) { std::string mid = find_closest_color_preset_by_vendor_and_type( bundle->filaments, tray.filament_vendor, tray.tray_type, tray.tray_color); if (!mid.empty()) tray.tray_info_idx = mid; From a862161302b804d0b434372b29a205eaa310ba32 Mon Sep 17 00:00:00 2001 From: Florian Stamer Date: Mon, 18 May 2026 14:50:27 +0200 Subject: [PATCH 06/26] Matching for Filaments by Vendor Name, Material and Special Name --- src/slic3r/Utils/MoonrakerPrinterAgent.cpp | 261 +++++++++++++-------- src/slic3r/Utils/MoonrakerPrinterAgent.hpp | 5 +- 2 files changed, 161 insertions(+), 105 deletions(-) diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index ff18d98a81..c29549bbd3 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -4,6 +4,7 @@ #include "libslic3r/PresetBundle.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/DeviceCore/DevFilaSystem.h" +#include "slic3r/GUI/DeviceCore/DevExtruderSystem.h" #include "slic3r/GUI/DeviceCore/DevManager.h" #include "../GUI/DeviceCore/DevStorage.h" #include "../GUI/DeviceCore/DevFirmware.h" @@ -37,68 +38,6 @@ struct WsEndpoint bool secure = false; }; -// Find best-matching visible filament preset by vendor + type + color. -// Same algorithm as SnapmakerPrinterAgent's helper. Allows the KX-Bridge -// (which knows the user's slot brand selection) to direct OrcaSlicer to a -// specific preset instead of falling back to "first sichtbares PETG" lookup. -std::string find_closest_color_preset_by_vendor_and_type(const Slic3r::PresetCollection& filaments, - const std::string& vendor_name, - const std::string& filament_type, - const std::string& color_rgba) -{ - std::string best_match_id = ""; - int best_color_distance = 0xffffffff; - if (vendor_name.empty() || filament_type.empty()) return best_match_id; - - auto parse_color = [](const std::string& s) -> unsigned int { - if (s.empty()) return 0; - std::string h = s; - if (!h.empty() && h.front() == '#') h.erase(0, 1); - if (h.size() >= 8) h = h.substr(0, 6); // strip alpha - try { return std::stoul(h, nullptr, 16); } catch (...) { return 0; } - }; - - unsigned int target = parse_color(color_rgba); - - // Pass 1: compatible + visible presets (ideal match) - for (const auto& p : filaments.get_presets()) { - if (!(p.is_visible && p.is_compatible)) continue; - if (filaments.get_preset_base(p) != &p) continue; - if (p.config.opt_string("filament_vendor", 0u) != vendor_name) continue; - if (p.config.opt_string("filament_type", 0u) != filament_type) continue; - - unsigned int pc = parse_color(p.config.opt_string("default_filament_colour", 0u)); - int dr = ((target ) & 0xff) - ((pc ) & 0xff); - int dg = ((target >> 8) & 0xff) - ((pc >> 8) & 0xff); - int db = ((target >> 16) & 0xff) - ((pc >> 16) & 0xff); - unsigned int distance = dr * dr + dg * dg + db * db; - if (distance < (unsigned int)best_color_distance) { - best_color_distance = (int)distance; - best_match_id = p.filament_id; - } - } - if (!best_match_id.empty()) return best_match_id; - - // Pass 2: fallback — any base preset matching vendor+type, even if not compatible/instantiatable. - // Covers vendors like Elegoo that have a @base preset but no printer-specific variant for this printer. - for (const auto& p : filaments.get_presets()) { - if (p.config.opt_string("filament_vendor", 0u) != vendor_name) continue; - if (p.config.opt_string("filament_type", 0u) != filament_type) continue; - if (p.filament_id.empty()) continue; - - unsigned int pc = parse_color(p.config.opt_string("default_filament_colour", 0u)); - int dr = ((target ) & 0xff) - ((pc ) & 0xff); - int dg = ((target >> 8) & 0xff) - ((pc >> 8) & 0xff); - int db = ((target >> 16) & 0xff) - ((pc >> 16) & 0xff); - unsigned int distance = dr * dr + dg * dg + db * db; - if (distance < (unsigned int)best_color_distance) { - best_color_distance = (int)distance; - best_match_id = p.filament_id; - } - } - return best_match_id; -} - bool parse_ws_endpoint(const std::string& base_url, WsEndpoint& endpoint) { if (base_url.empty()) { @@ -150,6 +89,88 @@ std::string map_moonraker_state(std::string state) return "IDLE"; } +std::string normalize_filament_name_for_match(const std::string& input) +{ + std::string normalized = input; + boost::trim(normalized); + // Ignore profile suffixes like " @0.4 nozzle" for name matching. + if (const auto suffix_pos = normalized.find(" @"); suffix_pos != std::string::npos) { + normalized = normalized.substr(0, suffix_pos); + } + std::transform(normalized.begin(), normalized.end(), normalized.begin(), + [](unsigned char c) { return static_cast(std::toupper(c)); }); + return normalized; +} + +bool filament_name_match_relaxed(const std::string& wanted, const std::string& candidate) +{ + if (wanted == candidate) { + return true; + } + + // Allow lane names with trailing color descriptors, e.g.: + // "ELEGOO RAPID PETG GREY" -> "ELEGOO RAPID PETG". + if (!candidate.empty() && boost::starts_with(wanted, candidate + " ")) { + return true; + } + return false; +} + +std::vector vendor_match_candidates(std::string vendor) +{ + std::vector candidates; + boost::trim(vendor); + if (vendor.empty()) { + return candidates; + } + + candidates.push_back(vendor); + + // Also try first token (e.g. "Bambu Lab" -> "Bambu") without hardcoded aliases. + const auto first_space = vendor.find_first_of(" \t"); + if (first_space != std::string::npos) { + std::string first = vendor.substr(0, first_space); + boost::trim(first); + if (!first.empty() && !boost::iequals(first, vendor)) { + candidates.push_back(first); + } + } + return candidates; +} + +std::string filament_id_by_name(const Slic3r::PresetCollection& filaments, const std::string& filament_name) +{ + if (filament_name.empty()) { + BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher received empty filament name"; + return ""; + } + + const std::string wanted = normalize_filament_name_for_match(filament_name); + BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher lookup requested='" << filament_name + << "' normalized='" << wanted << "'"; + for (size_t i = 0; i < filaments.size(); ++i) { + const auto& preset = filaments.preset(i); + if (!preset.is_visible || !preset.is_compatible || preset.filament_id.empty()) { + BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher skip preset='" << preset.name + << "' visible=" << preset.is_visible << " compatible=" << preset.is_compatible + << " filament_id_empty=" << preset.filament_id.empty(); + continue; + } + const std::string candidate = normalize_filament_name_for_match(preset.name); + BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher compare preset='" << preset.name + << "' normalized='" << candidate << "' filament_id='" << preset.filament_id << "'"; + if (filament_name_match_relaxed(wanted, candidate)) { + BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher matched requested='" << filament_name + << "' normalized='" << wanted << "' to preset='" << preset.name + << "' filament_id='" << preset.filament_id << "'"; + return preset.filament_id; + } + } + BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher found no match for requested='" << filament_name + << "' normalized='" << wanted << "'"; + return ""; +} + } // namespace namespace Slic3r { @@ -510,7 +531,7 @@ int MoonrakerPrinterAgent::set_queue_on_main_fn(QueueOnMainFn fn) return BAMBU_NETWORK_SUCCESS; } -void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index, const std::vector& trays) +void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index, const std::vector& trays, int active_lane_index) { // Look up MachineObject via DeviceManager @@ -561,6 +582,7 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index, tray_json["tray_info_idx"] = tray->tray_info_idx; tray_json["tray_type"] = tray->tray_type; + tray_json["tray_sub_brands"] = tray->tray_sub_brands.empty() ? tray->tray_type : tray->tray_sub_brands; tray_json["tray_color"] = normalize_color_value(tray->tray_color); // Add temperature data if provided @@ -592,6 +614,11 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index, ams_json["ams"] = ams_array; ams_json["ams_exist_bits"] = ams_exist_ss.str(); ams_json["tray_exist_bits"] = tray_exist_ss.str(); + if (active_lane_index >= 0) { + const std::string active_lane = std::to_string(active_lane_index); + ams_json["tray_now"] = active_lane; + ams_json["tray_tar"] = active_lane; + } // Wrap in the expected structure for ParseV1_0 nlohmann::json print_json = nlohmann::json::object(); @@ -599,6 +626,7 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index, // Call the parser to populate DevFilaSystem DevFilaSystemParser::ParseV1_0(print_json, obj, obj->GetFilaSystem(), false); + ExtderSystemParser::ParseV1_0(print_json, obj->GetExtderSystem()); BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::build_ams_payload: Parsed " << trays.size() << " trays"; // Set printer_type so update_sync_status() can match it against the preset's printer type. @@ -632,6 +660,7 @@ bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id) { std::vector trays; int max_lane_index = 0; + int active_lane_index = -1; // Try Moonraker filament data (more generic, supports any filament changer // software that reports lane data to Moonraker like AFC and recent Happy @@ -640,16 +669,16 @@ bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id) BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: Detected Moonraker filament system with " << (max_lane_index + 1) << " lanes"; int ams_count = (max_lane_index + 4) / 4; - build_ams_payload(ams_count, max_lane_index, trays); + build_ams_payload(ams_count, max_lane_index, trays, active_lane_index); return true; } // Attempt Happy Hare first (more widely adopted, supports more filament changers) - if (fetch_hh_filament_info(trays, max_lane_index)) { + if (fetch_hh_filament_info(trays, max_lane_index, active_lane_index)) { BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: Detected Happy Hare MMU with " << (max_lane_index + 1) << " gates"; int ams_count = (max_lane_index + 4) / 4; - build_ams_payload(ams_count, max_lane_index, trays); + build_ams_payload(ams_count, max_lane_index, trays, active_lane_index); return true; } @@ -838,7 +867,6 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vectorfilaments.get_presets()) { - if (p.is_compatible && p.filament_id == tray.tray_info_idx) { - idx_has_compatible_preset = true; - break; + if (bundle) { + const auto vendor_candidates = vendor_match_candidates(lane_vendor); + auto match_with_vendor_prefix = [&](const std::string& suffix) -> std::string { + if (suffix.empty()) { + return ""; } - } - } + for (const auto& vendor_candidate : vendor_candidates) { + const std::string requested = vendor_candidate + " " + suffix; + std::string match_id = filament_id_by_name(bundle->filaments, requested); + if (!match_id.empty()) { + return match_id; + } + } + return ""; + }; - if (bundle && !tray.filament_vendor.empty() && (tray.tray_info_idx.empty() || !idx_has_compatible_preset)) { - std::string mid = find_closest_color_preset_by_vendor_and_type( - bundle->filaments, tray.filament_vendor, tray.tray_type, tray.tray_color); - if (!mid.empty()) tray.tray_info_idx = mid; - } - if (tray.tray_info_idx.empty()) { - tray.tray_info_idx = bundle - ? bundle->filaments.filament_id_by_type(tray.tray_type) - : map_filament_type_to_generic_id(tray.tray_type); + // Prefer the most specific lane identity first, then broader vendor/material mapping. + tray.tray_info_idx = match_with_vendor_prefix(lane_name); + if (tray.tray_info_idx.empty()) { + tray.tray_info_idx = match_with_vendor_prefix(tray.tray_type); + } + if (tray.tray_info_idx.empty() && !lane_name.empty()) { + tray.tray_info_idx = filament_id_by_name(bundle->filaments, lane_name); + } + if (tray.tray_info_idx.empty()) { + tray.tray_info_idx = bundle->filaments.filament_id_by_type(tray.tray_type); + } + BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_moonraker_filament_data: lane='" << lane_key + << "' index=" << lane_index << " material='" << tray.tray_type + << "' vendor='" << lane_vendor << "' vendor_candidates=" << vendor_candidates.size() + << "' name='" << lane_name + << "' mapped_by='preset_bundle' tray_info_idx='" << tray.tray_info_idx << "'"; + } else { + tray.tray_info_idx = map_filament_type_to_generic_id(tray.tray_type); + BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_moonraker_filament_data: lane='" << lane_key + << "' index=" << lane_index << " material='" << tray.tray_type + << "' mapped_by='generic_fallback' tray_info_idx='" << tray.tray_info_idx << "'"; } max_lane_index = std::max(max_lane_index, lane_index); @@ -909,15 +947,11 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vector& trays, int& max_lane_index) +bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector& trays, int& max_lane_index, int& active_lane_index) { // Query Happy Hare MMU status std::string url = join_url(device_info.base_url, "/printer/objects/query?mmu"); @@ -989,8 +1023,18 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector& tra // Get arrays const auto& gate_status = mmu.contains("gate_status") ? mmu["gate_status"] : nlohmann::json::array(); const auto& gate_material = mmu.contains("gate_material") ? mmu["gate_material"] : nlohmann::json::array(); + const auto& gate_filament_name = mmu.contains("gate_filament_name") ? mmu["gate_filament_name"] : nlohmann::json::array(); const auto& gate_color = mmu.contains("gate_color") ? mmu["gate_color"] : nlohmann::json::array(); const auto& gate_temperature = mmu.contains("gate_temperature") ? mmu["gate_temperature"] : nlohmann::json::array(); + const int active_gate = safe_json_int(mmu, "gate"); + active_lane_index = active_gate; + std::string active_filament_name; + if (mmu.contains("active_filament") && mmu["active_filament"].is_object()) { + active_filament_name = safe_json_string(mmu["active_filament"], "filament_name"); + if (active_filament_name.empty()) { + active_filament_name = safe_json_string(mmu["active_filament"], "material"); + } + } if (!gate_status.is_array() || !gate_material.is_array() || !gate_color.is_array() || !gate_temperature.is_array()) { @@ -1011,8 +1055,13 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector& tra // Extract gate data std::string material = safe_array_string(gate_material, gate_idx); + std::string filament_name = safe_array_string(gate_filament_name, gate_idx); std::string color = safe_array_string(gate_color, gate_idx); int nozzle_temp = safe_array_int(gate_temperature, gate_idx); + // For the active gate, prefer active_filament from MMU state. + if (gate_idx == active_gate && !active_filament_name.empty()) { + filament_name = active_filament_name; + } // Skip if no material type (empty gate) if (material.empty()) { @@ -1022,15 +1071,21 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector& tra AmsTrayData tray; tray.slot_index = gate_idx; tray.tray_type = material; + tray.tray_sub_brands = filament_name; tray.tray_color = color; tray.nozzle_temp = nozzle_temp; tray.bed_temp = 0; // HH doesn't provide bed temp in gate arrays tray.has_filament = true; auto* bundle = GUI::wxGetApp().preset_bundle; - tray.tray_info_idx = bundle - ? bundle->filaments.filament_id_by_type(tray.tray_type) - : map_filament_type_to_generic_id(tray.tray_type); + if (bundle) { + tray.tray_info_idx = filament_id_by_name(bundle->filaments, filament_name); + if (tray.tray_info_idx.empty()) { + tray.tray_info_idx = bundle->filaments.filament_id_by_type(tray.tray_type); + } + } else { + tray.tray_info_idx = map_filament_type_to_generic_id(tray.tray_type); + } max_lane_index = std::max(max_lane_index, gate_idx); trays.push_back(tray); diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp index 47f5846245..2eeb1099df 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp @@ -92,6 +92,7 @@ protected: int slot_index = 0; // 0-based slot index bool has_filament = false; std::string tray_type; // Material type (e.g., "PLA", "ASA") + std::string tray_sub_brands; // Human-readable filament name std::string tray_color; // Raw color (#RRGGBB, 0xRRGGBB, or RRGGBBAA) std::string tray_info_idx; // Setting ID (optional) std::string filament_vendor; // Vendor hint from bridge (optional, KX-Bridge sendet das) @@ -100,7 +101,7 @@ protected: }; // Build ams JSON and call parser - void build_ams_payload(int ams_count, int max_lane_index, const std::vector& trays); + void build_ams_payload(int ams_count, int max_lane_index, const std::vector& trays, int active_lane_index = -1); // Methods that derived classes may need to override or access virtual bool init_device_info(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl); @@ -162,7 +163,7 @@ private: uint64_t generation); // System-specific filament fetch methods - bool fetch_hh_filament_info(std::vector& trays, int& max_lane_index); + bool fetch_hh_filament_info(std::vector& trays, int& max_lane_index, int& active_lane_index); bool fetch_moonraker_filament_data(std::vector& trays, int& max_lane_index); // JSON helper methods From fbc76888b667afa5c494eae8b377394b2801bddc Mon Sep 17 00:00:00 2001 From: Florian Stamer Date: Mon, 18 May 2026 14:55:45 +0200 Subject: [PATCH 07/26] Improve Moonraker filament preset matching Prefer vendor-scoped name matching from lane_data, add robust filament-name normalization, and keep material fallback for unmatched entries. --- src/slic3r/Utils/MoonrakerPrinterAgent.cpp | 66 +++++++++++++++++++--- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index c29549bbd3..c5eaccaf32 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -97,9 +97,35 @@ std::string normalize_filament_name_for_match(const std::string& input) if (const auto suffix_pos = normalized.find(" @"); suffix_pos != std::string::npos) { normalized = normalized.substr(0, suffix_pos); } - std::transform(normalized.begin(), normalized.end(), normalized.begin(), - [](unsigned char c) { return static_cast(std::toupper(c)); }); - return normalized; + // Remove non-name symbols (e.g. trademark signs) while preserving separators + // commonly used in filament names. + std::string cleaned; + cleaned.reserve(normalized.size()); + for (unsigned char c : normalized) { + if (std::isalnum(c) || c == '-' || c == '+' || c == '/' || std::isspace(c)) { + cleaned.push_back(static_cast(std::toupper(c))); + } else { + cleaned.push_back(' '); + } + } + + // Collapse repeated whitespace. + std::string collapsed; + collapsed.reserve(cleaned.size()); + bool prev_space = true; + for (unsigned char c : cleaned) { + if (std::isspace(c)) { + if (!prev_space) { + collapsed.push_back(' '); + } + prev_space = true; + } else { + collapsed.push_back(static_cast(c)); + prev_space = false; + } + } + boost::trim(collapsed); + return collapsed; } bool filament_name_match_relaxed(const std::string& wanted, const std::string& candidate) @@ -138,7 +164,9 @@ std::vector vendor_match_candidates(std::string vendor) return candidates; } -std::string filament_id_by_name(const Slic3r::PresetCollection& filaments, const std::string& filament_name) +std::string filament_id_by_name(const Slic3r::PresetCollection& filaments, + const std::string& filament_name, + const std::vector& vendor_filters = {}) { if (filament_name.empty()) { BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher received empty filament name"; @@ -146,8 +174,17 @@ std::string filament_id_by_name(const Slic3r::PresetCollection& filaments, const } const std::string wanted = normalize_filament_name_for_match(filament_name); + std::vector normalized_vendor_filters; + normalized_vendor_filters.reserve(vendor_filters.size()); + for (const auto& vendor_filter : vendor_filters) { + const std::string normalized_vendor = normalize_filament_name_for_match(vendor_filter); + if (!normalized_vendor.empty()) { + normalized_vendor_filters.push_back(normalized_vendor); + } + } + BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher lookup requested='" << filament_name - << "' normalized='" << wanted << "'"; + << "' normalized='" << wanted << "' vendor_filters=" << normalized_vendor_filters.size(); for (size_t i = 0; i < filaments.size(); ++i) { const auto& preset = filaments.preset(i); if (!preset.is_visible || !preset.is_compatible || preset.filament_id.empty()) { @@ -156,6 +193,21 @@ std::string filament_id_by_name(const Slic3r::PresetCollection& filaments, const << " filament_id_empty=" << preset.filament_id.empty(); continue; } + if (!normalized_vendor_filters.empty()) { + const std::string preset_vendor = normalize_filament_name_for_match(preset.config.opt_string("filament_vendor", 0u)); + bool vendor_match = false; + for (const auto& vendor_filter : normalized_vendor_filters) { + if (preset_vendor == vendor_filter) { + vendor_match = true; + break; + } + } + if (!vendor_match) { + BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher skip preset='" << preset.name + << "' reason=vendor_filter_miss preset_vendor='" << preset_vendor << "'"; + continue; + } + } const std::string candidate = normalize_filament_name_for_match(preset.name); BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher compare preset='" << preset.name << "' normalized='" << candidate << "' filament_id='" << preset.filament_id << "'"; @@ -907,7 +959,7 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vectorfilaments, requested); + std::string match_id = filament_id_by_name(bundle->filaments, requested, vendor_candidates); if (!match_id.empty()) { return match_id; } @@ -921,7 +973,7 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vectorfilaments, lane_name); + tray.tray_info_idx = filament_id_by_name(bundle->filaments, lane_name, vendor_candidates); } if (tray.tray_info_idx.empty()) { tray.tray_info_idx = bundle->filaments.filament_id_by_type(tray.tray_type); From b5fb069c548d17d109193f6eb98c12e3f822cf22 Mon Sep 17 00:00:00 2001 From: viewit Date: Tue, 2 Jun 2026 12:43:57 +0200 Subject: [PATCH 08/26] =?UTF-8?q?fix(moonraker):=20Zwei-Pass-Suche=20in=20?= =?UTF-8?q?filament=5Fid=5Fby=5Fname=20=E2=80=94=20kompatibel=20first,=20d?= =?UTF-8?q?ann=20alle=20sichtbaren?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pass 1: nur is_compatible Presets (wie bisher) Pass 2: alle sichtbaren Presets unabhängig von is_compatible Behebt: Hersteller wie eSUN, Eryone, Elegoo haben globale Base-Presets aber keine druckerspezifischen Profile für den Kobra X → is_compatible=false → bisher immer Generic PLA. Mit Pass 2 werden diese Base-Presets als Fallback gefunden. --- src/slic3r/Utils/MoonrakerPrinterAgent.cpp | 70 +++++++++++++--------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index c5eaccaf32..ae0b1e95ca 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -185,37 +185,53 @@ std::string filament_id_by_name(const Slic3r::PresetCollection& filaments, BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher lookup requested='" << filament_name << "' normalized='" << wanted << "' vendor_filters=" << normalized_vendor_filters.size(); - for (size_t i = 0; i < filaments.size(); ++i) { - const auto& preset = filaments.preset(i); - if (!preset.is_visible || !preset.is_compatible || preset.filament_id.empty()) { - BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher skip preset='" << preset.name - << "' visible=" << preset.is_visible << " compatible=" << preset.is_compatible - << " filament_id_empty=" << preset.filament_id.empty(); - continue; - } - if (!normalized_vendor_filters.empty()) { - const std::string preset_vendor = normalize_filament_name_for_match(preset.config.opt_string("filament_vendor", 0u)); - bool vendor_match = false; - for (const auto& vendor_filter : normalized_vendor_filters) { - if (preset_vendor == vendor_filter) { - vendor_match = true; - break; + + // Two-pass search: Pass 1 = compatible presets only (ideal), Pass 2 = all visible presets + // (fallback for vendors like eSUN/Eryone that have no printer-specific Kobra X profile). + for (int pass = 1; pass <= 2; ++pass) { + for (size_t i = 0; i < filaments.size(); ++i) { + const auto& preset = filaments.preset(i); + if (!preset.is_visible || preset.filament_id.empty()) { + if (pass == 1) { + BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher skip preset='" << preset.name + << "' visible=" << preset.is_visible + << " filament_id_empty=" << preset.filament_id.empty(); } - } - if (!vendor_match) { - BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher skip preset='" << preset.name - << "' reason=vendor_filter_miss preset_vendor='" << preset_vendor << "'"; continue; } + if (pass == 1 && !preset.is_compatible) { + continue; // Pass 1: only compatible presets + } + if (!normalized_vendor_filters.empty()) { + const std::string preset_vendor = normalize_filament_name_for_match(preset.config.opt_string("filament_vendor", 0u)); + bool vendor_match = false; + for (const auto& vendor_filter : normalized_vendor_filters) { + if (preset_vendor == vendor_filter) { + vendor_match = true; + break; + } + } + if (!vendor_match) { + if (pass == 1) { + BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher skip preset='" << preset.name + << "' reason=vendor_filter_miss preset_vendor='" << preset_vendor << "'"; + } + continue; + } + } + const std::string candidate = normalize_filament_name_for_match(preset.name); + BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher compare (pass=" << pass << ") preset='" << preset.name + << "' normalized='" << candidate << "' filament_id='" << preset.filament_id << "'"; + if (filament_name_match_relaxed(wanted, candidate)) { + BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher matched (pass=" << pass << ") requested='" << filament_name + << "' normalized='" << wanted << "' to preset='" << preset.name + << "' filament_id='" << preset.filament_id << "'"; + return preset.filament_id; + } } - const std::string candidate = normalize_filament_name_for_match(preset.name); - BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher compare preset='" << preset.name - << "' normalized='" << candidate << "' filament_id='" << preset.filament_id << "'"; - if (filament_name_match_relaxed(wanted, candidate)) { - BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher matched requested='" << filament_name - << "' normalized='" << wanted << "' to preset='" << preset.name - << "' filament_id='" << preset.filament_id << "'"; - return preset.filament_id; + if (pass == 1) { + BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher pass 1 (compatible) found no match for '" + << filament_name << "', trying pass 2 (all visible)"; } } BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher found no match for requested='" << filament_name From 202603448524467c7acb959faa051e9fe0011263 Mon Sep 17 00:00:00 2001 From: Felix Jen Date: Tue, 21 Apr 2026 23:23:00 -0500 Subject: [PATCH 09/26] Add unique filament_id for inherited user presets When a user creates a new filament preset by inheriting from an existing one (e.g., "Brand ABS @BBL H2D" inheriting from "Generic ABS @BBL H2D"), the resulting preset previously had no filament_id of its own. This caused two problems: 1. The AMS sync pipeline could not distinguish the user preset from its parent, so syncing filaments from the printer always resolved to the generic base preset instead of the user's custom one. 2. External tools that rely on filament_id for preset identification (e.g., Bambuddy, see maziggy/bambuddy#1053) could not assign or recognize user-created inherited presets in AMS slots. The root cause was twofold: - No filament_id was generated during save_current_preset() for inherited filament presets. Only base (non-inheriting) presets received one via the CreatePresetsDialog flow. - The AMS matching logic in sync_ams_list(), get_ams_cobox_infos(), and get_filament_presets() filtered candidates with `get_preset_base(f) == &f`, which by definition excludes any preset with a non-empty inherits() field. This commit: - Generates a unique filament_id (MD5 hash of preset name, prefixed with "P", truncated to 8 chars) when creating a new filament preset in PresetCollection::save_current_preset(). This matches the existing ID generation scheme used for base filaments in CreatePresetsDialog::get_filament_id(). - Persists filament_id into the JSON when saving inherited presets via Preset::save() and get_differed_values_to_update(). - Broadens the AMS filament matching predicates to also consider user presets that carry their own filament_id, rather than requiring them to be base presets. Files changed: src/libslic3r/Preset.cpp - ID generation, save, lookup src/libslic3r/PresetBundle.cpp - AMS sync matching predicates --- src/libslic3r/Preset.cpp | 753 ++++++++------------------------- src/libslic3r/PresetBundle.cpp | 4 +- 2 files changed, 190 insertions(+), 567 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index cd58e90e97..2f3c37e3c1 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -41,6 +41,8 @@ #include #include #include +#include +#include #include "libslic3r.h" #include "Utils.hpp" @@ -52,78 +54,6 @@ using boost::property_tree::ptree; namespace Slic3r { -namespace { - -struct ParsedName { - PresetOrigin::Kind kind { PresetOrigin::Kind::User }; - std::string bundle_id; - std::string bare; -}; - -// Canonical names are built in-memory with '/' separators, so a straight prefix+split match is enough. -static ParsedName parse_preset_name(const std::string &raw_name) -{ - ParsedName out; - - auto try_prefix = [&](const char *dir, PresetOrigin::Kind kind) { - const std::string prefix = std::string(dir) + "/"; - if (! boost::starts_with(raw_name, prefix)) - return false; - const size_t id_start = prefix.size(); - const size_t id_end = raw_name.find('/', id_start); - if (id_end == std::string::npos || id_end == id_start) - return false; - out.kind = kind; - out.bundle_id = raw_name.substr(id_start, id_end - id_start); - out.bare = raw_name.substr(id_end + 1); - return true; - }; - - if (! try_prefix(PRESET_LOCAL_DIR, PresetOrigin::Kind::LocalBundle) && - ! try_prefix(PRESET_SUBSCRIBED_DIR, PresetOrigin::Kind::SubscribedBundle)) - out.bare = raw_name; - - return out; -} - -} // namespace - -std::string get_preset_canonical_name(const std::string &preset_bare_name, const PresetOrigin &origin) -{ - switch (origin.kind) { - case PresetOrigin::Kind::LocalBundle: - return origin.bundle_id.empty() ? preset_bare_name : std::string(PRESET_LOCAL_DIR) + "/" + origin.bundle_id + "/" + preset_bare_name; - case PresetOrigin::Kind::SubscribedBundle: - return origin.bundle_id.empty() ? preset_bare_name : std::string(PRESET_SUBSCRIBED_DIR) + "/" + origin.bundle_id + "/" + preset_bare_name; - default: - return preset_bare_name; - } -} - -std::string get_preset_bare_name(const std::string &canonical_name) -{ - const auto pos = canonical_name.find_last_of('/'); - return pos == std::string::npos ? canonical_name : canonical_name.substr(pos + 1); -} - -PresetOrigin detect_origin_from_path(const boost::filesystem::path &path, const PresetOrigin &explicit_origin) -{ - if (explicit_origin.kind != PresetOrigin::Kind::Auto) - return explicit_origin; - - for (auto it = path.begin(); it != path.end(); ++ it) { - const auto next = std::next(it); - if (next == path.end()) - break; - const std::string segment = it->string(); - if (segment == PRESET_LOCAL_DIR) - return PresetOrigin(PresetOrigin::Kind::LocalBundle, next->string()); - if (segment == PRESET_SUBSCRIBED_DIR) - return PresetOrigin(PresetOrigin::Kind::SubscribedBundle, next->string()); - } - return PresetOrigin(PresetOrigin::Kind::User); -} - //BBS: add a function to load the version from xxx.json Semver get_version_from_json(std::string file_path) { @@ -581,12 +511,20 @@ void Preset::load_info(const std::string& file) catch (...) { return; } + + //TODO: workaround for current info file convert, will remove it later + if (this->updated_time == 0) { + this->updated_time = (long long)Slic3r::Utils::get_current_time_utc(); + //this->sync_info = "update"; + BOOST_LOG_TRIVIAL(info) << boost::format("old info file, updated time to %1%") % this->updated_time; + save_info(); + } } void Preset::save_info(std::string file) { //BBS: add project embedded preset logic - if (this->is_project_embedded || this->is_from_bundle()) + if (this->is_project_embedded) return; if (file.empty()) { fs::path idx_file(this->file); @@ -608,26 +546,17 @@ void Preset::save_info(std::string file) c.close(); } -void Preset::remove_files(bool cloud_already_deleted) +void Preset::remove_files() { //BBS: add project embedded preset logic - if (this->is_project_embedded) { + if (this->is_project_embedded) return; - } // Erase the preset file. boost::nowide::remove(this->file.c_str()); fs::path idx_path(this->file); idx_path.replace_extension(".info"); - if (fs::exists(idx_path)) { - if (!this->setting_id.empty() && !cloud_already_deleted) { - // Cloud-synced preset - mark for deletion and keep .info file until sync confirms - this->sync_info = "delete"; - this->save_info(idx_path.string()); - } else { - // Local-only preset or cloud already confirmed deletion - safe to delete .info immediately - boost::nowide::remove(idx_path.string().c_str()); - } - } + if (fs::exists(idx_path)) + boost::nowide::remove(idx_path.string().c_str()); } //BBS: add logic for only difference save @@ -643,15 +572,12 @@ void Preset::save(DynamicPrintConfig* parent_config) from_str = std::string("User"); else if (this->is_project_embedded) from_str = std::string("Project"); - else if (this->is_from_bundle()) - from_str = std::string("Bundle"); else if (this->is_system) from_str = std::string("System"); else from_str = std::string("Default"); boost::filesystem::create_directories(fs::path(this->file).parent_path()); - const std::string bare_name = get_preset_bare_name(this->name); //BBS: only save difference if it has parent if (parent_config) { @@ -691,22 +617,24 @@ void Preset::save(DynamicPrintConfig* parent_config) opt_dst->set(opt_src); } } - temp_config.save_to_json(this->file, bare_name, from_str, this->version.to_string()); + + if (!filament_id.empty()) { + temp_config.set_key_value(BBL_JSON_KEY_FILAMENT_ID, new ConfigOptionString(filament_id)); + } + + temp_config.save_to_json(this->file, this->name, from_str, this->version.to_string()); } else if (!filament_id.empty() && inherits().empty()) { DynamicPrintConfig temp_config = config; temp_config.set_key_value(BBL_JSON_KEY_FILAMENT_ID, new ConfigOptionString(filament_id)); - temp_config.save_to_json(this->file, bare_name, from_str, this->version.to_string()); + temp_config.save_to_json(this->file, this->name, from_str, this->version.to_string()); } else { - this->config.save_to_json(this->file, bare_name, from_str, this->version.to_string()); + this->config.save_to_json(this->file, this->name, from_str, this->version.to_string()); } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " save config for: " << this->name << " and filament_id: " << filament_id << " and base_id: " << this->base_id; - // Bundle presets are synced via bundle_id and don't need individual .info files. - if (! this->is_from_bundle()) { - fs::path idx_file(this->file); - idx_file.replace_extension(".info"); - this->save_info(idx_file.string()); - } + fs::path idx_file(this->file); + idx_file.replace_extension(".info"); + this->save_info(idx_file.string()); } void Preset::reload(Preset const &parent) @@ -782,8 +710,7 @@ bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const Pre if (preset.vendor != nullptr && preset.preset.type == Preset::TYPE_FILAMENT) { const auto& excluded_printers = preset.preset.m_excluded_from; const auto excluded = preset.vendor->name == PresetBundle::ORCA_FILAMENT_LIBRARY && - (excluded_printers.find(active_printer.preset.name) != excluded_printers.end() || - excluded_printers.find(active_printer.preset.inherits()) != excluded_printers.end()); + excluded_printers.find(active_printer.preset.name) != excluded_printers.end(); if (excluded) return false; } @@ -967,300 +894,74 @@ bool Preset::has_cali_lines(PresetBundle* preset_bundle) return false; } -static std::vector s_Preset_print_options{ - "layer_height", - "initial_layer_print_height", - "wall_loops", - "alternate_extra_wall", - "slice_closing_radius", - "spiral_mode", - "spiral_mode_smooth", - "spiral_mode_max_xy_smoothing", - "spiral_starting_flow_ratio", - "spiral_finishing_flow_ratio", - "slicing_mode", - "top_shell_layers", - "top_shell_thickness", - "top_surface_density", - "bottom_surface_density", - "bottom_shell_layers", - "bottom_shell_thickness", - "extra_perimeters_on_overhangs", - "ensure_vertical_shell_thickness", - "reduce_crossing_wall", - "detect_thin_wall", - "detect_overhang_wall", - "overhang_reverse", - "overhang_reverse_threshold", - "overhang_reverse_internal_only", - "wall_direction", - "seam_position", - "staggered_inner_seams", - "wall_sequence", - "is_infill_first", - "sparse_infill_density", - "fill_multiline", - "gyroid_optimized", - "sparse_infill_pattern", - "lateral_lattice_angle_1", - "lateral_lattice_angle_2", - "infill_overhang_angle", - "top_surface_pattern", - "bottom_surface_pattern", - "infill_direction", - "solid_infill_direction", - "counterbore_hole_bridging", - "infill_shift_step", - "sparse_infill_rotate_template", - "solid_infill_rotate_template", - "symmetric_infill_y_axis", - "skeleton_infill_density", - "infill_lock_depth", - "skin_infill_depth", - "skin_infill_density", - "align_infill_direction_to_model", - "extra_solid_infills", - "minimum_sparse_infill_area", - "reduce_infill_retraction", - "internal_solid_infill_pattern", - "gap_fill_target", - "ironing_type", - "ironing_pattern", - "ironing_flow", - "ironing_speed", - "ironing_spacing", - "ironing_angle", - "ironing_angle_fixed", - "ironing_inset", - "support_ironing", - "support_ironing_pattern", - "support_ironing_flow", - "support_ironing_spacing", +static std::vector s_Preset_print_options { + "layer_height", "initial_layer_print_height", "wall_loops", "alternate_extra_wall", "slice_closing_radius", "spiral_mode", "spiral_mode_smooth", "spiral_mode_max_xy_smoothing", "spiral_starting_flow_ratio", "spiral_finishing_flow_ratio", "slicing_mode", + "top_shell_layers", "top_shell_thickness", "top_surface_density", "bottom_surface_density", "bottom_shell_layers", "bottom_shell_thickness", + "extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall", "overhang_reverse", "overhang_reverse_threshold","overhang_reverse_internal_only", "wall_direction", + "seam_position", "staggered_inner_seams", "wall_sequence", "is_infill_first", "sparse_infill_density","fill_multiline", "sparse_infill_pattern", "lateral_lattice_angle_1", "lateral_lattice_angle_2", "infill_overhang_angle", "top_surface_pattern", "bottom_surface_pattern", + "infill_direction", "solid_infill_direction", "counterbore_hole_bridging","infill_shift_step", "sparse_infill_rotate_template", "solid_infill_rotate_template", "symmetric_infill_y_axis","skeleton_infill_density", "infill_lock_depth", "skin_infill_depth", "skin_infill_density", + "align_infill_direction_to_model", "extra_solid_infills", + "minimum_sparse_infill_area", "reduce_infill_retraction","internal_solid_infill_pattern","gap_fill_target", + "ironing_type", "ironing_pattern", "ironing_flow", "ironing_speed", "ironing_spacing", "ironing_angle", "ironing_angle_fixed", "ironing_inset", + "support_ironing", "support_ironing_pattern", "support_ironing_flow", "support_ironing_spacing", "max_travel_detour_distance", - "fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_distance", "fuzzy_skin_first_layer", "fuzzy_skin_noise_type", "fuzzy_skin_mode", "fuzzy_skin_scale", "fuzzy_skin_octaves", "fuzzy_skin_persistence", "fuzzy_skin_ripples_per_layer", "fuzzy_skin_ripple_offset", "fuzzy_skin_layers_between_ripple_offset", + "fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_distance", "fuzzy_skin_first_layer", "fuzzy_skin_noise_type", "fuzzy_skin_mode", "fuzzy_skin_scale", "fuzzy_skin_octaves", "fuzzy_skin_persistence", "max_volumetric_extrusion_rate_slope", "max_volumetric_extrusion_rate_slope_segment_length","extrusion_rate_smoothing_external_perimeter_only", "inner_wall_speed", "outer_wall_speed", "sparse_infill_speed", "internal_solid_infill_speed", "top_surface_speed", "support_speed", "support_object_xy_distance", "support_object_first_layer_gap", "support_interface_speed", "bridge_speed", "internal_bridge_speed", "gap_infill_speed", "travel_speed", "travel_speed_z", "initial_layer_speed", "outer_wall_acceleration", "initial_layer_acceleration", "top_surface_acceleration", "default_acceleration", "skirt_type", "skirt_loops", "skirt_speed","min_skirt_length", "skirt_distance", "skirt_start_angle", "skirt_height","single_loop_draft_shield", "draft_shield", - "brim_width", "brim_object_gap", "brim_flow_ratio", "brim_use_efc_outline", "combine_brims", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers", + "brim_width", "brim_object_gap", "brim_use_efc_outline", "combine_brims", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers", "raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion", "support_base_pattern", "support_base_pattern_spacing", "support_expansion", "support_style", // BBS - "print_extruder_id", - "print_extruder_variant", + "print_extruder_id", "print_extruder_variant", "independent_support_layer_height", - "support_angle", - "support_interface_top_layers", - "support_interface_bottom_layers", - "support_interface_pattern", - "support_interface_spacing", - "support_interface_loop_pattern", - "support_top_z_distance", - "support_on_build_plate_only", - "support_critical_regions_only", - "bridge_no_support", - "thick_bridges", - "thick_internal_bridges", - "dont_filter_internal_bridges", - "enable_extra_bridge_layer", - "max_bridge_length", - "print_sequence", - "print_order", - "support_remove_small_overhang", - "filename_format", - "wall_filament", - "support_bottom_z_distance", - "sparse_infill_filament", - "solid_infill_filament", - "support_filament", - "support_interface_filament", - "support_interface_not_for_body", - "ooze_prevention", - "standby_temperature_delta", - "preheat_time", - "preheat_steps", - "interface_shells", - "line_width", - "initial_layer_line_width", - "inner_wall_line_width", - "outer_wall_line_width", - "sparse_infill_line_width", - "internal_solid_infill_line_width", - "skin_infill_line_width", - "skeleton_infill_line_width", - "top_surface_line_width", - "support_line_width", - "infill_wall_overlap", - "top_bottom_infill_wall_overlap", - "bridge_flow", - "internal_bridge_flow", - "elefant_foot_compensation", - "elefant_foot_compensation_layers", - "elefant_foot_layers_density", - "xy_contour_compensation", - "xy_hole_compensation", - "resolution", - "enable_prime_tower", - "prime_tower_enable_framework", - "prime_tower_width", - "prime_tower_brim_width", - "prime_tower_skip_points", - "prime_volume", + "support_angle", "support_interface_top_layers", "support_interface_bottom_layers", + "support_interface_pattern", "support_interface_spacing", "support_interface_loop_pattern", + "support_top_z_distance", "support_on_build_plate_only","support_critical_regions_only", "bridge_no_support", "thick_bridges", "thick_internal_bridges","dont_filter_internal_bridges","enable_extra_bridge_layer", "max_bridge_length", "print_sequence", "print_order", "support_remove_small_overhang", + "filename_format", "wall_filament", "support_bottom_z_distance", + "sparse_infill_filament", "solid_infill_filament", "support_filament", "support_interface_filament","support_interface_not_for_body", + "ooze_prevention", "standby_temperature_delta", "preheat_time","preheat_steps", "interface_shells", "line_width", "initial_layer_line_width", "inner_wall_line_width", + "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", + "skin_infill_line_width","skeleton_infill_line_width", + "top_surface_line_width", "support_line_width", "infill_wall_overlap","top_bottom_infill_wall_overlap", "bridge_flow", "internal_bridge_flow", + "elefant_foot_compensation", "elefant_foot_compensation_layers", "xy_contour_compensation", "xy_hole_compensation", "resolution", "enable_prime_tower", "prime_tower_enable_framework", + "prime_tower_width", "prime_tower_brim_width", "prime_tower_skip_points", "prime_volume", "prime_tower_infill_gap", "prime_tower_flat_ironing", "enable_tower_interface_features", "enable_tower_interface_cooldown_during_tower", - "wipe_tower_no_sparse_layers", - "compatible_printers", - "compatible_printers_condition", - "inherits", - "flush_into_infill", - "flush_into_objects", - "flush_into_support", - "tree_support_branch_angle", - "tree_support_angle_slow", - "tree_support_wall_count", - "tree_support_top_rate", - "tree_support_branch_distance", - "tree_support_tip_diameter", - "tree_support_branch_diameter", - "tree_support_branch_diameter_angle", - "detect_narrow_internal_solid_infill", - "gcode_add_line_number", - "enable_arc_fitting", - "precise_z_height", - "infill_combination", - "infill_combination_max_layer_height", /*"adaptive_layer_height",*/ - "support_bottom_interface_spacing", - "enable_overhang_speed", - "slowdown_for_curled_perimeters", - "overhang_1_4_speed", - "overhang_2_4_speed", - "overhang_3_4_speed", - "overhang_4_4_speed", - "initial_layer_infill_speed", - "only_one_wall_top", - "timelapse_type", - "wall_generator", - "wall_transition_length", - "wall_transition_filter_deviation", - "wall_transition_angle", - "wall_distribution_count", - "min_feature_size", - "min_bead_width", - "post_process", - "process_change_extrusion_role_gcode", - "min_length_factor", - "wall_maximum_resolution", - "wall_maximum_deviation", - "small_perimeter_speed", - "small_perimeter_threshold", - "bridge_angle", - "internal_bridge_angle", - "filter_out_gap_fill", - "travel_acceleration", - "inner_wall_acceleration", - "min_width_top_surface", - "default_jerk", - "outer_wall_jerk", - "inner_wall_jerk", - "infill_jerk", - "top_surface_jerk", - "initial_layer_jerk", - "travel_jerk", - "default_junction_deviation", - "top_solid_infill_flow_ratio", - "bottom_solid_infill_flow_ratio", - "only_one_wall_first_layer", - "print_flow_ratio", - "seam_gap", - "set_other_flow_ratios", - "first_layer_flow_ratio", - "outer_wall_flow_ratio", - "inner_wall_flow_ratio", - "overhang_flow_ratio", - "sparse_infill_flow_ratio", - "internal_solid_infill_flow_ratio", - "gap_fill_flow_ratio", - "support_flow_ratio", - "support_interface_flow_ratio", - "role_based_wipe_speed", - "wipe_speed", - "accel_to_decel_enable", - "accel_to_decel_factor", - "wipe_on_loops", - "wipe_before_external_loop", - "bridge_density", - "internal_bridge_density", - "precise_outer_wall", - "bridge_acceleration", - "sparse_infill_acceleration", - "internal_solid_infill_acceleration", - "tree_support_auto_brim", - "tree_support_brim_width", - "gcode_comments", - "gcode_label_objects", - "initial_layer_travel_speed", - "initial_layer_travel_acceleration", - "initial_layer_travel_jerk", - "exclude_object", - "slow_down_layers", - "infill_anchor", - "infill_anchor_max", - "initial_layer_min_bead_width", - "make_overhang_printable", - "make_overhang_printable_angle", - "make_overhang_printable_hole_size", - "notes", - "wipe_tower_cone_angle", - "wipe_tower_extra_spacing", - "wipe_tower_max_purge_speed", - "wipe_tower_wall_type", - "wipe_tower_extra_rib_length", - "wipe_tower_rib_width", - "wipe_tower_fillet_wall", - "wipe_tower_filament", - "wiping_volumes_extruders", - "wipe_tower_bridging", - "wipe_tower_extra_flow", - "single_extruder_multi_material_priming", - "wipe_tower_rotation_angle", - "tree_support_branch_distance_organic", - "tree_support_branch_diameter_organic", - "tree_support_branch_angle_organic", - "hole_to_polyhole", - "hole_to_polyhole_threshold", - "hole_to_polyhole_twisted", - "mmu_segmented_region_max_width", - "mmu_segmented_region_interlocking_depth", - "small_area_infill_flow_compensation", - "small_area_infill_flow_compensation_model", - "enable_wrapping_detection", - "seam_slope_type", - "seam_slope_conditional", - "scarf_angle_threshold", - "scarf_joint_speed", - "scarf_joint_flow_ratio", - "seam_slope_start_height", - "seam_slope_entire_loop", - "seam_slope_min_length", - "seam_slope_steps", - "seam_slope_inner_walls", - "scarf_overhang_threshold", - "interlocking_beam", - "interlocking_orientation", - "interlocking_beam_layer_count", - "interlocking_depth", - "interlocking_boundary_avoidance", - "interlocking_beam_width", - "calib_flowrate_topinfill_special_order", - // Z Anti-Aliasing (ZAA) - "zaa_enabled", - "zaa_minimize_perimeter_height", - "zaa_dont_alternate_fill_direction", - "zaa_min_z", - "ironing_expansion", + "wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits", + "flush_into_infill", "flush_into_objects", "flush_into_support", + "tree_support_branch_angle", "tree_support_angle_slow", "tree_support_wall_count", "tree_support_top_rate", "tree_support_branch_distance", "tree_support_tip_diameter", + "tree_support_branch_diameter", "tree_support_branch_diameter_angle", + "detect_narrow_internal_solid_infill", + "gcode_add_line_number", "enable_arc_fitting", "precise_z_height", "infill_combination","infill_combination_max_layer_height", /*"adaptive_layer_height",*/ + "support_bottom_interface_spacing", "enable_overhang_speed", "slowdown_for_curled_perimeters", "overhang_1_4_speed", "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed", + "initial_layer_infill_speed", "only_one_wall_top", + "timelapse_type", + "wall_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", + "wall_distribution_count", "min_feature_size", "min_bead_width", "post_process", "min_length_factor", + "small_perimeter_speed", "small_perimeter_threshold","bridge_angle","internal_bridge_angle", "filter_out_gap_fill", "travel_acceleration","inner_wall_acceleration", "min_width_top_surface", + "default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk","travel_jerk","default_junction_deviation", + "top_solid_infill_flow_ratio","bottom_solid_infill_flow_ratio","only_one_wall_first_layer", "print_flow_ratio", "seam_gap", + "set_other_flow_ratios", "first_layer_flow_ratio", "outer_wall_flow_ratio", "inner_wall_flow_ratio", "overhang_flow_ratio", "sparse_infill_flow_ratio", "internal_solid_infill_flow_ratio", "gap_fill_flow_ratio", "support_flow_ratio", "support_interface_flow_ratio", + "role_based_wipe_speed", "wipe_speed", "accel_to_decel_enable", "accel_to_decel_factor", "wipe_on_loops", "wipe_before_external_loop", + "bridge_density","internal_bridge_density", "precise_outer_wall", "bridge_acceleration", + "sparse_infill_acceleration", "internal_solid_infill_acceleration", "tree_support_auto_brim", + "tree_support_brim_width", "gcode_comments", "gcode_label_objects", + "initial_layer_travel_speed", "exclude_object", "slow_down_layers", "infill_anchor", "infill_anchor_max","initial_layer_min_bead_width", + "make_overhang_printable", "make_overhang_printable_angle", "make_overhang_printable_hole_size" ,"notes", + "wipe_tower_cone_angle", "wipe_tower_extra_spacing","wipe_tower_max_purge_speed", + "wipe_tower_wall_type", "wipe_tower_extra_rib_length", "wipe_tower_rib_width", "wipe_tower_fillet_wall", + "wipe_tower_filament", "wiping_volumes_extruders","wipe_tower_bridging", "wipe_tower_extra_flow","single_extruder_multi_material_priming", + "wipe_tower_rotation_angle", "tree_support_branch_distance_organic", "tree_support_branch_diameter_organic", "tree_support_branch_angle_organic", + "hole_to_polyhole", "hole_to_polyhole_threshold", "hole_to_polyhole_twisted", "mmu_segmented_region_max_width", "mmu_segmented_region_interlocking_depth", + "small_area_infill_flow_compensation", "small_area_infill_flow_compensation_model", + "enable_wrapping_detection", + "seam_slope_type", "seam_slope_conditional", "scarf_angle_threshold", "scarf_joint_speed", "scarf_joint_flow_ratio", "seam_slope_start_height", "seam_slope_entire_loop", "seam_slope_min_length", "seam_slope_steps", "seam_slope_inner_walls", "scarf_overhang_threshold", + "interlocking_beam", "interlocking_orientation", "interlocking_beam_layer_count", "interlocking_depth", "interlocking_boundary_avoidance", "interlocking_beam_width","calib_flowrate_topinfill_special_order", }; static std::vector s_Preset_filament_options {/*"filament_colour", */ "default_filament_colour", "required_nozzle_HRC", "filament_diameter", "pellet_flow_coefficient", "volumetric_speed_coefficients", "filament_type", @@ -1275,10 +976,10 @@ static std::vector s_Preset_filament_options {/*"filament_colour", // "bed_type", //BBS:temperature_vitrification "temperature_vitrification", "reduce_fan_stop_start_freq","dont_slow_down_outer_wall", "slow_down_for_layer_cooling", "fan_min_speed", - "fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "overhang_fan_threshold", "close_fan_the_first_x_layers", "close_additional_fan_first_x_layers", "first_x_layer_fan_speed", "full_fan_speed_layer", "additional_fan_full_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed", - "filament_start_gcode", "filament_end_gcode", "filament_change_extrusion_role_gcode", + "fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "overhang_fan_threshold", "close_fan_the_first_x_layers", "full_fan_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed", + "filament_start_gcode", "filament_end_gcode", //exhaust fan control - "activate_air_filtration","activate_air_filtration_during_print","activate_air_filtration_on_completion","during_print_exhaust_fan_speed","complete_print_exhaust_fan_speed", + "activate_air_filtration","during_print_exhaust_fan_speed","complete_print_exhaust_fan_speed", // Retract overrides "filament_retraction_length", "filament_z_hop", "filament_z_hop_types", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_lift_enforce", "filament_retraction_speed", "filament_deretraction_speed", "filament_retract_restart_extra", "filament_retraction_minimum_travel", "filament_retract_when_changing_layer", "filament_wipe", "filament_retract_before_wipe", @@ -1299,7 +1000,7 @@ static std::vector s_Preset_filament_options {/*"filament_colour", "filament_multitool_ramming", "filament_multitool_ramming_volume", "filament_multitool_ramming_flow", "activate_chamber_temp_control", "filament_long_retractions_when_cut","filament_retraction_distances_when_cut", "idle_temperature", //BBS filament change length while the extruder color - "filament_change_length","filament_flush_volumetric_speed","filament_flush_temp", "filament_cooling_before_tower", + "filament_change_length","filament_flush_volumetric_speed","filament_flush_temp", "long_retractions_when_ec", "retraction_distances_when_ec" }; @@ -1312,14 +1013,12 @@ static std::vector s_Preset_machine_limits_options { "machine_max_junction_deviation", //resonance avoidance ported from qidi slicer "resonance_avoidance", "min_resonance_avoidance_speed", "max_resonance_avoidance_speed", - // Orca: input shaping - "input_shaping_emit", "input_shaping_type", "input_shaping_freq_x", "input_shaping_freq_y", "input_shaping_damp_x", "input_shaping_damp_y", }; static std::vector s_Preset_printer_options { "printer_technology", "printable_area", "extruder_printable_area", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor", - "fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs", + "fan_kickstart", "fan_speedup_time", "fan_speedup_overhangs", "single_extruder_multi_material", "manual_filament_change", "file_start_gcode", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "printing_by_object_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", "change_extrusion_role_gcode", "printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type", "printable_height", "extruder_printable_height", "extruder_clearance_radius", "extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod", @@ -1336,7 +1035,7 @@ static std::vector s_Preset_printer_options { "use_relative_e_distances", "extruder_type", "use_firmware_retraction", "printer_notes", "grab_length", "support_object_skip_flush", "physical_extruder_map", "cooling_tube_retraction", - "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "wipe_tower_type", "purge_in_prime_tower", "enable_filament_ramming", "tool_change_on_wipe_tower", + "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "wipe_tower_type", "purge_in_prime_tower", "enable_filament_ramming", "z_offset", "disable_m73", "preferred_orientation", "emit_machine_limits_to_gcode", "pellet_modded_printer", "support_multi_bed_types", "default_bed_type", "bed_mesh_min","bed_mesh_max","bed_mesh_probe_distance", "adaptive_bed_mesh_margin", "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut", "bed_temperature_formula", "nozzle_flush_dataset" @@ -1512,34 +1211,19 @@ void PresetCollection::add_default_preset(const std::vector &keys, ++ m_num_default_presets; } -std::string PresetCollection::canonical_preset_name(const std::string &name, const PresetOrigin &load_origin) const -{ - const ParsedName parsed = parse_preset_name(name); - PresetOrigin origin = load_origin; - if (origin.kind == PresetOrigin::Kind::Auto) { - origin.kind = parsed.kind; - origin.bundle_id = parsed.bundle_id; - } else if (origin.is_bundle() && origin.bundle_id.empty()) { - origin.bundle_id = parsed.bundle_id; - } - return get_preset_canonical_name(parsed.bare, origin); -} - // Load all presets found in dir_path. // Throws an exception on error. void PresetCollection::load_presets( const std::string &dir_path, const std::string &subdir, - PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule, - std::function preset_loaded_fn, const PresetOrigin &load_origin) + PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule) { // Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points, // see https://github.com/prusa3d/PrusaSlicer/issues/732 boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / subdir).make_preferred(); - const PresetOrigin resolved_origin = detect_origin_from_path(dir, load_origin); // Load custom roots first if (fs::exists(dir / "base")) { - load_presets(dir.string(), "base", substitutions, substitution_rule, nullptr, resolved_origin); + load_presets(dir.string(), "base", substitutions, substitution_rule); } //BBS: add config related logs @@ -1569,16 +1253,14 @@ void PresetCollection::load_presets( if (Slic3r::is_json_file(file_name)) { // Remove the .ini suffix. std::string name = file_name.erase(file_name.size() - 5); - std::string canonical_name = this->canonical_preset_name(name, resolved_origin); - if (this->find_preset(canonical_name, false)) { + if (this->find_preset(name, false)) { // This happens when there's is a preset (most likely legacy one) with the same name as a system preset // that's already been loaded from a bundle. - BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << canonical_name; + BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name; continue; } try { - Preset preset(m_type, canonical_name, false); - preset.bundle_id = resolved_origin.bundle_id; + Preset preset(m_type, name, false); preset.file = dir_entry.path().string(); // Load the preset file, apply preset values on top of defaults. try { @@ -1629,6 +1311,7 @@ void PresetCollection::load_presets( std::string inherits_value = option_str->value; // Orca: try to find if the parent preset has been renamed inherit_preset = this->find_preset2(inherits_value); + } else { ; } @@ -1706,9 +1389,6 @@ void PresetCollection::load_presets( fs::remove(file_path); } - if (preset_loaded_fn != nullptr) - preset_loaded_fn(preset); - presets_loaded.emplace_back(preset); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " load config successful and preset name is:" << preset.name; } catch (const std::runtime_error &err) { @@ -1827,9 +1507,9 @@ int PresetCollection::get_differed_values_to_update(Preset& preset, std::map& proje inherits_value.replace(pos, 1, 1, '~'); option_str->value = inherits_value; }*/ - inherit_preset = this->find_preset2(inherits_value, true); + inherit_preset = this->find_preset(inherits_value, false, true); } const Preset& default_preset = this->default_preset_for(config); if (inherit_preset) { @@ -1981,10 +1661,9 @@ bool PresetCollection::reset_project_embedded_presets() void PresetCollection::set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time) { lock(); - const std::string canonical_name = this->canonical_preset_name(name); for (auto it = m_presets.begin(); it != m_presets.end(); it++) { Preset* preset = &m_presets[it - m_presets.begin()]; - if (preset->name == canonical_name) { + if (preset->name == name) { if (syncinfo.empty()) preset->sync_info.clear(); else @@ -2049,7 +1728,7 @@ void PresetCollection::update_user_presets_directory(const std::string& dir_path } //BBS: save user presets to local -void PresetCollection::save_user_presets(const std::string& dir_path, const std::string& type, std::map& need_to_delete_list) +void PresetCollection::save_user_presets(const std::string& dir_path, const std::string& type, std::vector& need_to_delete_list) { boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / type).make_preferred(); @@ -2103,37 +1782,31 @@ void PresetCollection::save_user_presets(const std::string& dir_path, const std: } //BBS: load one user preset from key-values -bool PresetCollection::load_user_preset(std::string name, std::map preset_values, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule, const PresetOrigin &load_origin) +bool PresetCollection::load_user_preset(std::string name, std::map preset_values, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule) { std::string errors_cummulative; // Store the loaded presets into a new vector, otherwise the binary search for already existing presets would be broken. // (see the "Preset already present, not loading" message). //std::deque presets_loaded; int count = 0; - const std::string canonical_name = this->canonical_preset_name(name, load_origin); - auto update_alias = [this](Preset &preset) { - if (! preset.alias.empty()) - return; - set_custom_preset_alias(preset); - }; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, name %1% , total value counts %2%")%canonical_name %preset_values.size(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, name %1% , total value counts %2%")%name %preset_values.size(); //if the version is not matching, skip it if (preset_values.find(BBL_JSON_KEY_VERSION) == preset_values.end()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find version, not loading for user preset %1%")%canonical_name; + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find version, not loading for user preset %1%")%name; return false; } std::string version_str = preset_values[BBL_JSON_KEY_VERSION]; boost::optional cloud_version = Semver::parse(version_str); if (!cloud_version) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("invalid version %1%, not loading for user preset %2%")%version_str %canonical_name; + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("invalid version %1%, not loading for user preset %2%")%version_str %name; return false; } //setting_id if (preset_values.find(BBL_JSON_KEY_SETTING_ID) == preset_values.end()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find setting_id, not loading for user preset %1%")%canonical_name; + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find setting_id, not loading for user preset %1%")%name; return false; } std::string cloud_setting_id = preset_values[BBL_JSON_KEY_SETTING_ID]; @@ -2146,17 +1819,17 @@ bool PresetCollection::load_user_preset(std::string name, std::mapname; - auto iter = this->find_preset_internal(canonical_name); + auto iter = this->find_preset_internal(name); bool need_update = false; - if ((iter != m_presets.end()) && (iter->name == canonical_name)) { - BOOST_LOG_TRIVIAL(info) << "Found the Preset locally: " << canonical_name; + if ((iter != m_presets.end()) && (iter->name == name)) { + BOOST_LOG_TRIVIAL(info) << "Found the Preset locally: " << name; //BBS: we should compare the time between cloud and local if ((cloud_update_time == 0) || (cloud_update_time <= iter->updated_time)) { if (cloud_update_time < iter->updated_time) @@ -2168,7 +1841,7 @@ bool PresetCollection::load_user_preset(std::string name, std::mapfile); idx_file.replace_extension(".info"); iter->save_info(idx_file.string()); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("preset %1%'s update_time is eqaul or newer, cloud update_time %2%, local update_time %3%")%canonical_name %cloud_update_time %iter->updated_time; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("preset %1%'s update_time is eqaul or newer, cloud update_time %2%, local update_time %3%")%name %cloud_update_time %iter->updated_time; unlock(); return false; } @@ -2178,36 +1851,26 @@ bool PresetCollection::load_user_preset(std::string name, std::mapsecond; - } else { - const auto inherits_iter = preset_values.find(BBL_JSON_KEY_INHERITS); - const bool preset_inherits_from_parent = inherits_iter != preset_values.end() && !inherits_iter->second.empty(); - if (preset_inherits_from_parent) { - // This indicates that there is inherits exists but there is no base_id - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ - << boost::format("can not find base_id, not loading for user preset %1%") % canonical_name; - unlock(); - return false; - } + // base_id + if (preset_values.find(BBL_JSON_KEY_BASE_ID) == preset_values.end()) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find base_id, not loading for user preset %1%") % name; + unlock(); + return false; } + std::string cloud_base_id = preset_values[BBL_JSON_KEY_BASE_ID]; //filament_id std::string cloud_filament_id; if ((m_type == Preset::TYPE_FILAMENT) && preset_values.find(BBL_JSON_KEY_FILAMENT_ID) != preset_values.end()) { cloud_filament_id = preset_values[BBL_JSON_KEY_FILAMENT_ID]; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << canonical_name << " filament_id: " << cloud_filament_id << " base_id: " << based_id; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << name << " filament_id: " << cloud_filament_id << " base_id: " << cloud_base_id; } DynamicPrintConfig new_config, cloud_config; try { ConfigSubstitutions config_substitutions = cloud_config.load_string_map(preset_values, rule); if (! config_substitutions.empty()) - substitutions.push_back({ canonical_name, m_type, PresetConfigSubstitutions::Source::UserCloud, canonical_name, std::move(config_substitutions) }); + substitutions.push_back({ name, m_type, PresetConfigSubstitutions::Source::UserCloud, name, std::move(config_substitutions) }); //BBS: use inherit config as the base Preset* inherit_preset = nullptr; @@ -2215,7 +1878,12 @@ bool PresetCollection::load_user_preset(std::string name, std::map (inherits_config); std::string inherits_value = option_str->value; - inherit_preset = this->find_preset2(inherits_value, true); + /*size_t pos = inherits_value.find_first_of('*'); + if (pos != std::string::npos) { + inherits_value.replace(pos, 1, 1, '~'); + option_str->value = inherits_value; + }*/ + inherit_preset = this->find_preset(inherits_value, false, true); } const Preset& default_preset = this->default_preset_for(cloud_config); if (inherit_preset) { @@ -2229,7 +1897,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map(inherits_config); if (inherits_config2 && !inherits_config2->value.empty()) { //we should skip this preset here - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", can not find inherit preset for user preset %1%, just skip")%canonical_name; + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", can not find inherit preset for user preset %1%, just skip")%name; unlock(); return false; } @@ -2255,17 +1923,13 @@ bool PresetCollection::load_user_preset(std::string name, std::mapname == m_edited_preset.name && iter->is_dirty) { // Keep modifies when update from remote new_config.apply_only(m_edited_preset.config, m_edited_preset.config.diff(iter->config)); - } else if (iter->name == m_edited_preset.name) { - // Preset is not dirty (no local unsaved changes) — also update the edited preset - // to prevent a false "dirty" indication (orange highlight) after a silent cloud sync - m_edited_preset.config = new_config; } iter->config = new_config; iter->updated_time = cloud_update_time; @@ -2273,28 +1937,25 @@ bool PresetCollection::load_user_preset(std::string name, std::mapversion = cloud_version.value(); iter->user_id = cloud_user_id; iter->setting_id = cloud_setting_id; - iter->base_id = based_id; + iter->base_id = cloud_base_id; iter->filament_id = cloud_filament_id; - update_alias(*iter); //presets_loaded.emplace_back(*it->second); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", update the user preset %1% from cloud, type %2%, setting_id %3%, base_id %4%, sync_info %5% inherits %6%, filament_id %7%") % iter->name %Preset::get_type_string(m_type) %iter->setting_id %iter->base_id %iter->sync_info %iter->inherits() % iter->filament_id; } else { //create a new one - Preset preset(m_type, canonical_name, false); + Preset preset(m_type, name, false); preset.is_system = false; preset.loaded = true; - preset.bundle_id = load_origin.bundle_id; preset.config = new_config; preset.updated_time = cloud_update_time; preset.sync_info = "save"; preset.version = cloud_version.value(); preset.user_id = cloud_user_id; preset.setting_id = cloud_setting_id; - preset.base_id = based_id; + preset.base_id = cloud_base_id; preset.filament_id = cloud_filament_id; - update_alias(preset); size_t cur_index = iter - m_presets.begin(); m_presets.insert(iter, preset); @@ -2316,7 +1977,7 @@ bool PresetCollection::load_user_preset(std::string name, std::mapcanonical_preset_name(preset_name); - std::deque::iterator it = this->find_preset_internal(canonical_name); - bool found = (it != m_presets.end()) && (it->name == canonical_name) && is_trusted(*it); + std::deque::iterator it = this->find_preset_internal(preset_name); + bool found = (it != m_presets.end()) && (it->name == preset_name) && (it->is_system || it->is_default); if (!found) { - it = this->find_preset_renamed(canonical_name); - found = it != m_presets.end() && is_trusted(*it); + it = this->find_preset_renamed(preset_name); + found = it != m_presets.end() && (it->is_system || it->is_default); } if (!found) { if (!inherit_name.empty()) { - const std::string canonical_inherit_name = this->canonical_preset_name(inherit_name); - it = this->find_preset_internal(canonical_inherit_name); - found = it != m_presets.end() && it->name == canonical_inherit_name && is_trusted(*it); + it = this->find_preset_internal(inherit_name); + found = it != m_presets.end() && it->name == inherit_name && (it->is_system || it->is_default); if (found) BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": preset_name %1%, inherit_name %2%, found inherit in list")%preset_name %inherit_name; else @@ -2430,7 +2084,7 @@ std::pair PresetCollection::load_external_preset( cfg.apply_only(combined_config, keys, true); std::string &inherits = Preset::inherits(cfg); - //add different settings check logic, replace the old system preset's default value with new system preset's default values + //BBS: add different settings check logic, replace the old system preset's default value with new system preset's default values std::deque::iterator it = this->find_preset_internal(original_name); bool found = it != m_presets.end() && it->name == original_name; if (! found) { @@ -2770,7 +2424,7 @@ std::map> PresetCollection::get_filamen std::map> filament_presets; for (auto &preset : m_presets) { if (preset.is_user()) { - if (preset.inherits() == "") { filament_presets[preset.filament_id].push_back(&preset); } + if (preset.inherits() == "" || !preset.filament_id.empty()) { filament_presets[preset.filament_id].push_back(&preset); } continue; } if (get_preset_base(preset) == &preset) { filament_presets[preset.filament_id].push_back(&preset); } @@ -2792,7 +2446,7 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det // Preset with the same name found. Preset &preset = *it; //BBS: add project embedded preset logic - if (!preset.can_overwrite()) { + if (preset.is_default || preset.is_system) { //if (preset.is_default || preset.is_external || preset.is_system) // Cannot overwrite the default preset. //BBS: add lock logic for sync preset in background @@ -2851,9 +2505,6 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det preset.is_default = false; preset.is_system = false; preset.is_external = false; - - preset.bundle_id.clear(); - preset.file = this->path_for_preset(preset); // The newly saved preset will be activated -> make it visible. preset.is_visible = true; @@ -2867,8 +2518,18 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det preset.is_project_embedded = false; if (m_type == Preset::TYPE_PRINT) preset.config.option("print_settings_id", true)->value = new_name; - else if (m_type == Preset::TYPE_FILAMENT) + else if (m_type == Preset::TYPE_FILAMENT) { preset.config.option("filament_settings_id", true)->values[0] = new_name; + + boost::uuids::detail::md5 hash; + boost::uuids::detail::md5::digest_type digest; + hash.process_bytes(new_name.data(), new_name.size()); + hash.get_digest(digest); + const auto char_digest = reinterpret_cast(&digest); + std::string result; + boost::algorithm::hex(char_digest, char_digest + sizeof(boost::uuids::detail::md5::digest_type), std::back_inserter(result)); + preset.filament_id = "P" + result.substr(0, 7); + } else if (m_type == Preset::TYPE_PRINTER) preset.config.option("printer_settings_id", true)->value = new_name; //BBS: add lock logic for sync preset in background @@ -2896,7 +2557,7 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det bool PresetCollection::delete_current_preset() { Preset &selected = this->get_selected_preset(); - if (!selected.can_overwrite()) + if (selected.is_default) return false; if (get_preset_base(selected) == &selected) { @@ -2927,24 +2588,18 @@ bool PresetCollection::delete_current_preset() return true; } -bool PresetCollection::delete_preset(const std::string& name, bool force) +bool PresetCollection::delete_preset(const std::string& name) { - Preset *preset_ptr = this->find_preset(name, false, true); - if (preset_ptr == nullptr) - return false; - - auto it = this->find_preset_internal(preset_ptr->name); - if (it == m_presets.end() || it->name != preset_ptr->name) - return false; + auto it = this->find_preset_internal(name); Preset& preset = *it; - // ORCA: if the preset can't be overridden then don't allow deletion - // force=true bypasses this for bundle preset cleanup from cloud sync - if (!force && !preset.can_overwrite()) + if (preset.is_default) return false; - - preset.remove_files(); - + //BBS: add project embedded preset logic and refine is_external + //if (!preset.is_external && !preset.is_system) { + if (! preset.is_system) { + preset.remove_files(); + } //BBS: add lock logic for sync preset in background lock(); set_printer_hold_alias(it->alias, *it, true); @@ -2954,30 +2609,6 @@ bool PresetCollection::delete_preset(const std::string& name, bool force) return true; } -void PresetCollection::check_and_fix_syncinfo(Preset& preset, const std::string& user_id) -{ - // user id can't be empty - if (user_id.empty()) - return; - // correct the sync info if preset.user_id is empty(the profile json file is copied to the user folder with missing .info file) or preset.user_id - // is not equal to the current user id or preset.setting_id is not in expected format(the .info is copied from the older format) - if (preset.user_id.empty() || preset.user_id != user_id || preset.setting_id.find('-') == std::string::npos) { - preset.user_id = user_id; - preset.setting_id = ""; - - if (preset.base_id.empty()) { - const std::string inherits = Preset::inherits(preset.config); - Preset* parent_preset = find_preset2(inherits, true); - if (parent_preset) - preset.base_id = parent_preset->setting_id; - } - // tell the sync logic to sync it as a new preset - preset.updated_time = 0; - preset.sync_info = "create"; - preset.save_info(); - } -} - const Preset* PresetCollection::get_selected_preset_parent() const { if (this->get_selected_idx() == size_t(-1)) @@ -3040,7 +2671,7 @@ const Preset *PresetCollection::get_preset_base(const Preset &child) const // Handle user preset if (child.inherits().empty()) return &child; // this is user root - auto inherits = find_preset2(child.inherits(), true); + auto inherits = find_preset2(child.inherits(),true); return inherits ? get_preset_base(*inherits) : nullptr; } @@ -3114,21 +2745,20 @@ const std::string& PresetCollection::get_suffix_modified() { // If a preset was not found by its name, null is returned. Preset* PresetCollection::find_preset(const std::string &name, bool first_visible_if_not_found, bool real, bool only_from_library) { - const ParsedName parsed = parse_preset_name(name); - const std::string canonical = get_preset_canonical_name(parsed.bare, PresetOrigin(parsed.kind, parsed.bundle_id)); - auto it = this->find_preset_internal(canonical, only_from_library); - if (it != m_presets.end() && it->name == canonical) - return &this->preset(it - m_presets.begin(), real); - return first_visible_if_not_found ? &this->first_visible() : nullptr; + Preset key(m_type, name, false); + auto it = this->find_preset_internal(name, only_from_library); + // Ensure that a temporary copy is returned if the preset found is currently selected. + return (it != m_presets.end() && it->name == key.name) ? &this->preset(it - m_presets.begin(), real) : + first_visible_if_not_found ? &this->first_visible() : nullptr; } Preset* PresetCollection::find_preset2(const std::string& name, bool auto_match/* = true */) { - auto preset = find_preset(name, false, true); + auto preset = find_preset(name,false,true); if (preset == nullptr) { auto _name = get_preset_name_renamed(name); if (_name != nullptr) - preset = find_preset(*_name, false, true); + preset = find_preset(*_name,false,true); if (auto_match && preset == nullptr) { //Orca: one more try, find the most likely preset in OrcaFilamentLibrary if (name.find("Generic") != std::string::npos) { @@ -3144,6 +2774,7 @@ Preset* PresetCollection::find_preset2(const std::string& name, bool auto_match/ } } } + return preset; } @@ -3495,11 +3126,10 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b //BBS: add config related logs BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, try to select by name %2%, force %3%")%Preset::get_type_string(m_type) %name_w_suffix %force; std::string name = Preset::remove_suffix_modified(name_w_suffix); - const std::string normalized_name = this->canonical_preset_name(name); // 1) Try to find the preset by its name. - auto it = this->find_preset_internal(normalized_name); + auto it = this->find_preset_internal(name); size_t idx = 0; - if (it != m_presets.end() && it->name == normalized_name && it->is_visible) + if (it != m_presets.end() && it->name == name && it->is_visible) // Preset found by its name and it is visible. idx = it - m_presets.begin(); else { @@ -3532,12 +3162,11 @@ bool PresetCollection::select_preset_by_name_strict(const std::string &name) { //BBS: add config related logs BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, try to select by name %2%")%Preset::get_type_string(m_type) %name; - const std::string canonical_name = this->canonical_preset_name(name); // 1) Try to find the preset by its name. - auto it = this->find_preset_internal(canonical_name); + auto it = this->find_preset_internal(name); size_t idx = (size_t)-1; - if (it != m_presets.end() && it->name == canonical_name && it->is_visible) + if (it != m_presets.end() && it->name == name && it->is_visible) // Preset found by its name. idx = it - m_presets.begin(); // 2) Select the new preset. @@ -3649,30 +3278,24 @@ void PresetCollection::update_map_system_profile_renamed() void PresetCollection::set_custom_preset_alias(Preset &preset) { - // For filaments, remove the postfix - // For printers, there is nothing to remove - // For prints AKA processes, the postfix should be kept - // Alias should be set here, as the preset name may be augmented further later (i.e., prefixing relative path for bundles) - std::string bare_preset_name = get_preset_bare_name(preset.name); - std::string alias_name = bare_preset_name; - - const bool is_root_filament_preset = - m_type == Preset::Type::TYPE_FILAMENT && - preset.config.has(BBL_JSON_KEY_INHERITS) && - preset.config.option(BBL_JSON_KEY_INHERITS)->value.empty(); - if (is_root_filament_preset) { - const size_t suffix_separator_pos = bare_preset_name.find_first_of("@"); - if (suffix_separator_pos != std::string::npos) { - alias_name = bare_preset_name.substr(0, suffix_separator_pos); - boost::trim_right(alias_name); - if (alias_name.empty()) - alias_name = bare_preset_name; + if (m_type == Preset::Type::TYPE_FILAMENT && preset.config.has(BBL_JSON_KEY_INHERITS) && preset.config.option(BBL_JSON_KEY_INHERITS)->value.empty()) { + std::string alias_name; + std::string preset_name = preset.name; + if (alias_name.empty()) { + size_t end_pos = preset_name.find_first_of("@"); + if (end_pos != std::string::npos) { + alias_name = preset_name.substr(0, end_pos); + boost::trim_right(alias_name); + } + } + if (alias_name.empty() || is_alias_exist(alias_name, &preset)) + preset.alias = ""; + else { + preset.alias = std::move(alias_name); + m_map_alias_to_profile_name[preset.alias].push_back(preset.name); + set_printer_hold_alias(preset.alias, preset); } } - - preset.alias = std::move(alias_name); - m_map_alias_to_profile_name[preset.alias].push_back(preset.name); - set_printer_hold_alias(preset.alias, preset); } void PresetCollection::set_printer_hold_alias(const std::string &alias, Preset &preset, bool remove) @@ -3773,7 +3396,7 @@ std::string PresetCollection::path_from_name(const std::string &new_name, bool d std::string PresetCollection::path_for_preset(const Preset &preset) const { - return path_from_name(get_preset_bare_name(preset.name), is_base_preset(preset)); + return path_from_name(preset.name, is_base_preset(preset)); } const Preset& PrinterPresetCollection::default_preset_for(const DynamicPrintConfig &config) const diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 5e0ab15849..f85b0103c7 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -3101,7 +3101,7 @@ void PresetBundle::get_ams_cobox_infos(AMSComboInfo& combox_info) continue; } auto iter = std::find_if(filaments.begin(), filaments.end(), - [this, &filament_id](auto &f) { return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; }); + [this, &filament_id](auto &f) { return f.is_compatible && (filaments.get_preset_base(f) == &f || (f.is_user() && !f.filament_id.empty())) && f.filament_id == filament_id; }); if (iter == filaments.end()) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": filament_id %1% not found or system or compatible") % filament_id; auto filament_type = ams.opt_string("filament_type", 0u); @@ -3203,7 +3203,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector Date: Tue, 2 Jun 2026 13:25:34 +0200 Subject: [PATCH 10/26] Revert "Add unique filament_id for inherited user presets" This reverts commit fe4c79cd27119ecbdd115b718cddf91e5fd9d411. --- src/libslic3r/Preset.cpp | 753 +++++++++++++++++++++++++-------- src/libslic3r/PresetBundle.cpp | 4 +- 2 files changed, 567 insertions(+), 190 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 2f3c37e3c1..cd58e90e97 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -41,8 +41,6 @@ #include #include #include -#include -#include #include "libslic3r.h" #include "Utils.hpp" @@ -54,6 +52,78 @@ using boost::property_tree::ptree; namespace Slic3r { +namespace { + +struct ParsedName { + PresetOrigin::Kind kind { PresetOrigin::Kind::User }; + std::string bundle_id; + std::string bare; +}; + +// Canonical names are built in-memory with '/' separators, so a straight prefix+split match is enough. +static ParsedName parse_preset_name(const std::string &raw_name) +{ + ParsedName out; + + auto try_prefix = [&](const char *dir, PresetOrigin::Kind kind) { + const std::string prefix = std::string(dir) + "/"; + if (! boost::starts_with(raw_name, prefix)) + return false; + const size_t id_start = prefix.size(); + const size_t id_end = raw_name.find('/', id_start); + if (id_end == std::string::npos || id_end == id_start) + return false; + out.kind = kind; + out.bundle_id = raw_name.substr(id_start, id_end - id_start); + out.bare = raw_name.substr(id_end + 1); + return true; + }; + + if (! try_prefix(PRESET_LOCAL_DIR, PresetOrigin::Kind::LocalBundle) && + ! try_prefix(PRESET_SUBSCRIBED_DIR, PresetOrigin::Kind::SubscribedBundle)) + out.bare = raw_name; + + return out; +} + +} // namespace + +std::string get_preset_canonical_name(const std::string &preset_bare_name, const PresetOrigin &origin) +{ + switch (origin.kind) { + case PresetOrigin::Kind::LocalBundle: + return origin.bundle_id.empty() ? preset_bare_name : std::string(PRESET_LOCAL_DIR) + "/" + origin.bundle_id + "/" + preset_bare_name; + case PresetOrigin::Kind::SubscribedBundle: + return origin.bundle_id.empty() ? preset_bare_name : std::string(PRESET_SUBSCRIBED_DIR) + "/" + origin.bundle_id + "/" + preset_bare_name; + default: + return preset_bare_name; + } +} + +std::string get_preset_bare_name(const std::string &canonical_name) +{ + const auto pos = canonical_name.find_last_of('/'); + return pos == std::string::npos ? canonical_name : canonical_name.substr(pos + 1); +} + +PresetOrigin detect_origin_from_path(const boost::filesystem::path &path, const PresetOrigin &explicit_origin) +{ + if (explicit_origin.kind != PresetOrigin::Kind::Auto) + return explicit_origin; + + for (auto it = path.begin(); it != path.end(); ++ it) { + const auto next = std::next(it); + if (next == path.end()) + break; + const std::string segment = it->string(); + if (segment == PRESET_LOCAL_DIR) + return PresetOrigin(PresetOrigin::Kind::LocalBundle, next->string()); + if (segment == PRESET_SUBSCRIBED_DIR) + return PresetOrigin(PresetOrigin::Kind::SubscribedBundle, next->string()); + } + return PresetOrigin(PresetOrigin::Kind::User); +} + //BBS: add a function to load the version from xxx.json Semver get_version_from_json(std::string file_path) { @@ -511,20 +581,12 @@ void Preset::load_info(const std::string& file) catch (...) { return; } - - //TODO: workaround for current info file convert, will remove it later - if (this->updated_time == 0) { - this->updated_time = (long long)Slic3r::Utils::get_current_time_utc(); - //this->sync_info = "update"; - BOOST_LOG_TRIVIAL(info) << boost::format("old info file, updated time to %1%") % this->updated_time; - save_info(); - } } void Preset::save_info(std::string file) { //BBS: add project embedded preset logic - if (this->is_project_embedded) + if (this->is_project_embedded || this->is_from_bundle()) return; if (file.empty()) { fs::path idx_file(this->file); @@ -546,17 +608,26 @@ void Preset::save_info(std::string file) c.close(); } -void Preset::remove_files() +void Preset::remove_files(bool cloud_already_deleted) { //BBS: add project embedded preset logic - if (this->is_project_embedded) + if (this->is_project_embedded) { return; + } // Erase the preset file. boost::nowide::remove(this->file.c_str()); fs::path idx_path(this->file); idx_path.replace_extension(".info"); - if (fs::exists(idx_path)) - boost::nowide::remove(idx_path.string().c_str()); + if (fs::exists(idx_path)) { + if (!this->setting_id.empty() && !cloud_already_deleted) { + // Cloud-synced preset - mark for deletion and keep .info file until sync confirms + this->sync_info = "delete"; + this->save_info(idx_path.string()); + } else { + // Local-only preset or cloud already confirmed deletion - safe to delete .info immediately + boost::nowide::remove(idx_path.string().c_str()); + } + } } //BBS: add logic for only difference save @@ -572,12 +643,15 @@ void Preset::save(DynamicPrintConfig* parent_config) from_str = std::string("User"); else if (this->is_project_embedded) from_str = std::string("Project"); + else if (this->is_from_bundle()) + from_str = std::string("Bundle"); else if (this->is_system) from_str = std::string("System"); else from_str = std::string("Default"); boost::filesystem::create_directories(fs::path(this->file).parent_path()); + const std::string bare_name = get_preset_bare_name(this->name); //BBS: only save difference if it has parent if (parent_config) { @@ -617,24 +691,22 @@ void Preset::save(DynamicPrintConfig* parent_config) opt_dst->set(opt_src); } } - - if (!filament_id.empty()) { - temp_config.set_key_value(BBL_JSON_KEY_FILAMENT_ID, new ConfigOptionString(filament_id)); - } - - temp_config.save_to_json(this->file, this->name, from_str, this->version.to_string()); + temp_config.save_to_json(this->file, bare_name, from_str, this->version.to_string()); } else if (!filament_id.empty() && inherits().empty()) { DynamicPrintConfig temp_config = config; temp_config.set_key_value(BBL_JSON_KEY_FILAMENT_ID, new ConfigOptionString(filament_id)); - temp_config.save_to_json(this->file, this->name, from_str, this->version.to_string()); + temp_config.save_to_json(this->file, bare_name, from_str, this->version.to_string()); } else { - this->config.save_to_json(this->file, this->name, from_str, this->version.to_string()); + this->config.save_to_json(this->file, bare_name, from_str, this->version.to_string()); } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " save config for: " << this->name << " and filament_id: " << filament_id << " and base_id: " << this->base_id; - fs::path idx_file(this->file); - idx_file.replace_extension(".info"); - this->save_info(idx_file.string()); + // Bundle presets are synced via bundle_id and don't need individual .info files. + if (! this->is_from_bundle()) { + fs::path idx_file(this->file); + idx_file.replace_extension(".info"); + this->save_info(idx_file.string()); + } } void Preset::reload(Preset const &parent) @@ -710,7 +782,8 @@ bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const Pre if (preset.vendor != nullptr && preset.preset.type == Preset::TYPE_FILAMENT) { const auto& excluded_printers = preset.preset.m_excluded_from; const auto excluded = preset.vendor->name == PresetBundle::ORCA_FILAMENT_LIBRARY && - excluded_printers.find(active_printer.preset.name) != excluded_printers.end(); + (excluded_printers.find(active_printer.preset.name) != excluded_printers.end() || + excluded_printers.find(active_printer.preset.inherits()) != excluded_printers.end()); if (excluded) return false; } @@ -894,74 +967,300 @@ bool Preset::has_cali_lines(PresetBundle* preset_bundle) return false; } -static std::vector s_Preset_print_options { - "layer_height", "initial_layer_print_height", "wall_loops", "alternate_extra_wall", "slice_closing_radius", "spiral_mode", "spiral_mode_smooth", "spiral_mode_max_xy_smoothing", "spiral_starting_flow_ratio", "spiral_finishing_flow_ratio", "slicing_mode", - "top_shell_layers", "top_shell_thickness", "top_surface_density", "bottom_surface_density", "bottom_shell_layers", "bottom_shell_thickness", - "extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall", "overhang_reverse", "overhang_reverse_threshold","overhang_reverse_internal_only", "wall_direction", - "seam_position", "staggered_inner_seams", "wall_sequence", "is_infill_first", "sparse_infill_density","fill_multiline", "sparse_infill_pattern", "lateral_lattice_angle_1", "lateral_lattice_angle_2", "infill_overhang_angle", "top_surface_pattern", "bottom_surface_pattern", - "infill_direction", "solid_infill_direction", "counterbore_hole_bridging","infill_shift_step", "sparse_infill_rotate_template", "solid_infill_rotate_template", "symmetric_infill_y_axis","skeleton_infill_density", "infill_lock_depth", "skin_infill_depth", "skin_infill_density", - "align_infill_direction_to_model", "extra_solid_infills", - "minimum_sparse_infill_area", "reduce_infill_retraction","internal_solid_infill_pattern","gap_fill_target", - "ironing_type", "ironing_pattern", "ironing_flow", "ironing_speed", "ironing_spacing", "ironing_angle", "ironing_angle_fixed", "ironing_inset", - "support_ironing", "support_ironing_pattern", "support_ironing_flow", "support_ironing_spacing", +static std::vector s_Preset_print_options{ + "layer_height", + "initial_layer_print_height", + "wall_loops", + "alternate_extra_wall", + "slice_closing_radius", + "spiral_mode", + "spiral_mode_smooth", + "spiral_mode_max_xy_smoothing", + "spiral_starting_flow_ratio", + "spiral_finishing_flow_ratio", + "slicing_mode", + "top_shell_layers", + "top_shell_thickness", + "top_surface_density", + "bottom_surface_density", + "bottom_shell_layers", + "bottom_shell_thickness", + "extra_perimeters_on_overhangs", + "ensure_vertical_shell_thickness", + "reduce_crossing_wall", + "detect_thin_wall", + "detect_overhang_wall", + "overhang_reverse", + "overhang_reverse_threshold", + "overhang_reverse_internal_only", + "wall_direction", + "seam_position", + "staggered_inner_seams", + "wall_sequence", + "is_infill_first", + "sparse_infill_density", + "fill_multiline", + "gyroid_optimized", + "sparse_infill_pattern", + "lateral_lattice_angle_1", + "lateral_lattice_angle_2", + "infill_overhang_angle", + "top_surface_pattern", + "bottom_surface_pattern", + "infill_direction", + "solid_infill_direction", + "counterbore_hole_bridging", + "infill_shift_step", + "sparse_infill_rotate_template", + "solid_infill_rotate_template", + "symmetric_infill_y_axis", + "skeleton_infill_density", + "infill_lock_depth", + "skin_infill_depth", + "skin_infill_density", + "align_infill_direction_to_model", + "extra_solid_infills", + "minimum_sparse_infill_area", + "reduce_infill_retraction", + "internal_solid_infill_pattern", + "gap_fill_target", + "ironing_type", + "ironing_pattern", + "ironing_flow", + "ironing_speed", + "ironing_spacing", + "ironing_angle", + "ironing_angle_fixed", + "ironing_inset", + "support_ironing", + "support_ironing_pattern", + "support_ironing_flow", + "support_ironing_spacing", "max_travel_detour_distance", - "fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_distance", "fuzzy_skin_first_layer", "fuzzy_skin_noise_type", "fuzzy_skin_mode", "fuzzy_skin_scale", "fuzzy_skin_octaves", "fuzzy_skin_persistence", + "fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_distance", "fuzzy_skin_first_layer", "fuzzy_skin_noise_type", "fuzzy_skin_mode", "fuzzy_skin_scale", "fuzzy_skin_octaves", "fuzzy_skin_persistence", "fuzzy_skin_ripples_per_layer", "fuzzy_skin_ripple_offset", "fuzzy_skin_layers_between_ripple_offset", "max_volumetric_extrusion_rate_slope", "max_volumetric_extrusion_rate_slope_segment_length","extrusion_rate_smoothing_external_perimeter_only", "inner_wall_speed", "outer_wall_speed", "sparse_infill_speed", "internal_solid_infill_speed", "top_surface_speed", "support_speed", "support_object_xy_distance", "support_object_first_layer_gap", "support_interface_speed", "bridge_speed", "internal_bridge_speed", "gap_infill_speed", "travel_speed", "travel_speed_z", "initial_layer_speed", "outer_wall_acceleration", "initial_layer_acceleration", "top_surface_acceleration", "default_acceleration", "skirt_type", "skirt_loops", "skirt_speed","min_skirt_length", "skirt_distance", "skirt_start_angle", "skirt_height","single_loop_draft_shield", "draft_shield", - "brim_width", "brim_object_gap", "brim_use_efc_outline", "combine_brims", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers", + "brim_width", "brim_object_gap", "brim_flow_ratio", "brim_use_efc_outline", "combine_brims", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers", "raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion", "support_base_pattern", "support_base_pattern_spacing", "support_expansion", "support_style", // BBS - "print_extruder_id", "print_extruder_variant", + "print_extruder_id", + "print_extruder_variant", "independent_support_layer_height", - "support_angle", "support_interface_top_layers", "support_interface_bottom_layers", - "support_interface_pattern", "support_interface_spacing", "support_interface_loop_pattern", - "support_top_z_distance", "support_on_build_plate_only","support_critical_regions_only", "bridge_no_support", "thick_bridges", "thick_internal_bridges","dont_filter_internal_bridges","enable_extra_bridge_layer", "max_bridge_length", "print_sequence", "print_order", "support_remove_small_overhang", - "filename_format", "wall_filament", "support_bottom_z_distance", - "sparse_infill_filament", "solid_infill_filament", "support_filament", "support_interface_filament","support_interface_not_for_body", - "ooze_prevention", "standby_temperature_delta", "preheat_time","preheat_steps", "interface_shells", "line_width", "initial_layer_line_width", "inner_wall_line_width", - "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", - "skin_infill_line_width","skeleton_infill_line_width", - "top_surface_line_width", "support_line_width", "infill_wall_overlap","top_bottom_infill_wall_overlap", "bridge_flow", "internal_bridge_flow", - "elefant_foot_compensation", "elefant_foot_compensation_layers", "xy_contour_compensation", "xy_hole_compensation", "resolution", "enable_prime_tower", "prime_tower_enable_framework", - "prime_tower_width", "prime_tower_brim_width", "prime_tower_skip_points", "prime_volume", + "support_angle", + "support_interface_top_layers", + "support_interface_bottom_layers", + "support_interface_pattern", + "support_interface_spacing", + "support_interface_loop_pattern", + "support_top_z_distance", + "support_on_build_plate_only", + "support_critical_regions_only", + "bridge_no_support", + "thick_bridges", + "thick_internal_bridges", + "dont_filter_internal_bridges", + "enable_extra_bridge_layer", + "max_bridge_length", + "print_sequence", + "print_order", + "support_remove_small_overhang", + "filename_format", + "wall_filament", + "support_bottom_z_distance", + "sparse_infill_filament", + "solid_infill_filament", + "support_filament", + "support_interface_filament", + "support_interface_not_for_body", + "ooze_prevention", + "standby_temperature_delta", + "preheat_time", + "preheat_steps", + "interface_shells", + "line_width", + "initial_layer_line_width", + "inner_wall_line_width", + "outer_wall_line_width", + "sparse_infill_line_width", + "internal_solid_infill_line_width", + "skin_infill_line_width", + "skeleton_infill_line_width", + "top_surface_line_width", + "support_line_width", + "infill_wall_overlap", + "top_bottom_infill_wall_overlap", + "bridge_flow", + "internal_bridge_flow", + "elefant_foot_compensation", + "elefant_foot_compensation_layers", + "elefant_foot_layers_density", + "xy_contour_compensation", + "xy_hole_compensation", + "resolution", + "enable_prime_tower", + "prime_tower_enable_framework", + "prime_tower_width", + "prime_tower_brim_width", + "prime_tower_skip_points", + "prime_volume", "prime_tower_infill_gap", "prime_tower_flat_ironing", "enable_tower_interface_features", "enable_tower_interface_cooldown_during_tower", - "wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits", - "flush_into_infill", "flush_into_objects", "flush_into_support", - "tree_support_branch_angle", "tree_support_angle_slow", "tree_support_wall_count", "tree_support_top_rate", "tree_support_branch_distance", "tree_support_tip_diameter", - "tree_support_branch_diameter", "tree_support_branch_diameter_angle", - "detect_narrow_internal_solid_infill", - "gcode_add_line_number", "enable_arc_fitting", "precise_z_height", "infill_combination","infill_combination_max_layer_height", /*"adaptive_layer_height",*/ - "support_bottom_interface_spacing", "enable_overhang_speed", "slowdown_for_curled_perimeters", "overhang_1_4_speed", "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed", - "initial_layer_infill_speed", "only_one_wall_top", - "timelapse_type", - "wall_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", - "wall_distribution_count", "min_feature_size", "min_bead_width", "post_process", "min_length_factor", - "small_perimeter_speed", "small_perimeter_threshold","bridge_angle","internal_bridge_angle", "filter_out_gap_fill", "travel_acceleration","inner_wall_acceleration", "min_width_top_surface", - "default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk","travel_jerk","default_junction_deviation", - "top_solid_infill_flow_ratio","bottom_solid_infill_flow_ratio","only_one_wall_first_layer", "print_flow_ratio", "seam_gap", - "set_other_flow_ratios", "first_layer_flow_ratio", "outer_wall_flow_ratio", "inner_wall_flow_ratio", "overhang_flow_ratio", "sparse_infill_flow_ratio", "internal_solid_infill_flow_ratio", "gap_fill_flow_ratio", "support_flow_ratio", "support_interface_flow_ratio", - "role_based_wipe_speed", "wipe_speed", "accel_to_decel_enable", "accel_to_decel_factor", "wipe_on_loops", "wipe_before_external_loop", - "bridge_density","internal_bridge_density", "precise_outer_wall", "bridge_acceleration", - "sparse_infill_acceleration", "internal_solid_infill_acceleration", "tree_support_auto_brim", - "tree_support_brim_width", "gcode_comments", "gcode_label_objects", - "initial_layer_travel_speed", "exclude_object", "slow_down_layers", "infill_anchor", "infill_anchor_max","initial_layer_min_bead_width", - "make_overhang_printable", "make_overhang_printable_angle", "make_overhang_printable_hole_size" ,"notes", - "wipe_tower_cone_angle", "wipe_tower_extra_spacing","wipe_tower_max_purge_speed", - "wipe_tower_wall_type", "wipe_tower_extra_rib_length", "wipe_tower_rib_width", "wipe_tower_fillet_wall", - "wipe_tower_filament", "wiping_volumes_extruders","wipe_tower_bridging", "wipe_tower_extra_flow","single_extruder_multi_material_priming", - "wipe_tower_rotation_angle", "tree_support_branch_distance_organic", "tree_support_branch_diameter_organic", "tree_support_branch_angle_organic", - "hole_to_polyhole", "hole_to_polyhole_threshold", "hole_to_polyhole_twisted", "mmu_segmented_region_max_width", "mmu_segmented_region_interlocking_depth", - "small_area_infill_flow_compensation", "small_area_infill_flow_compensation_model", - "enable_wrapping_detection", - "seam_slope_type", "seam_slope_conditional", "scarf_angle_threshold", "scarf_joint_speed", "scarf_joint_flow_ratio", "seam_slope_start_height", "seam_slope_entire_loop", "seam_slope_min_length", "seam_slope_steps", "seam_slope_inner_walls", "scarf_overhang_threshold", - "interlocking_beam", "interlocking_orientation", "interlocking_beam_layer_count", "interlocking_depth", "interlocking_boundary_avoidance", "interlocking_beam_width","calib_flowrate_topinfill_special_order", + "wipe_tower_no_sparse_layers", + "compatible_printers", + "compatible_printers_condition", + "inherits", + "flush_into_infill", + "flush_into_objects", + "flush_into_support", + "tree_support_branch_angle", + "tree_support_angle_slow", + "tree_support_wall_count", + "tree_support_top_rate", + "tree_support_branch_distance", + "tree_support_tip_diameter", + "tree_support_branch_diameter", + "tree_support_branch_diameter_angle", + "detect_narrow_internal_solid_infill", + "gcode_add_line_number", + "enable_arc_fitting", + "precise_z_height", + "infill_combination", + "infill_combination_max_layer_height", /*"adaptive_layer_height",*/ + "support_bottom_interface_spacing", + "enable_overhang_speed", + "slowdown_for_curled_perimeters", + "overhang_1_4_speed", + "overhang_2_4_speed", + "overhang_3_4_speed", + "overhang_4_4_speed", + "initial_layer_infill_speed", + "only_one_wall_top", + "timelapse_type", + "wall_generator", + "wall_transition_length", + "wall_transition_filter_deviation", + "wall_transition_angle", + "wall_distribution_count", + "min_feature_size", + "min_bead_width", + "post_process", + "process_change_extrusion_role_gcode", + "min_length_factor", + "wall_maximum_resolution", + "wall_maximum_deviation", + "small_perimeter_speed", + "small_perimeter_threshold", + "bridge_angle", + "internal_bridge_angle", + "filter_out_gap_fill", + "travel_acceleration", + "inner_wall_acceleration", + "min_width_top_surface", + "default_jerk", + "outer_wall_jerk", + "inner_wall_jerk", + "infill_jerk", + "top_surface_jerk", + "initial_layer_jerk", + "travel_jerk", + "default_junction_deviation", + "top_solid_infill_flow_ratio", + "bottom_solid_infill_flow_ratio", + "only_one_wall_first_layer", + "print_flow_ratio", + "seam_gap", + "set_other_flow_ratios", + "first_layer_flow_ratio", + "outer_wall_flow_ratio", + "inner_wall_flow_ratio", + "overhang_flow_ratio", + "sparse_infill_flow_ratio", + "internal_solid_infill_flow_ratio", + "gap_fill_flow_ratio", + "support_flow_ratio", + "support_interface_flow_ratio", + "role_based_wipe_speed", + "wipe_speed", + "accel_to_decel_enable", + "accel_to_decel_factor", + "wipe_on_loops", + "wipe_before_external_loop", + "bridge_density", + "internal_bridge_density", + "precise_outer_wall", + "bridge_acceleration", + "sparse_infill_acceleration", + "internal_solid_infill_acceleration", + "tree_support_auto_brim", + "tree_support_brim_width", + "gcode_comments", + "gcode_label_objects", + "initial_layer_travel_speed", + "initial_layer_travel_acceleration", + "initial_layer_travel_jerk", + "exclude_object", + "slow_down_layers", + "infill_anchor", + "infill_anchor_max", + "initial_layer_min_bead_width", + "make_overhang_printable", + "make_overhang_printable_angle", + "make_overhang_printable_hole_size", + "notes", + "wipe_tower_cone_angle", + "wipe_tower_extra_spacing", + "wipe_tower_max_purge_speed", + "wipe_tower_wall_type", + "wipe_tower_extra_rib_length", + "wipe_tower_rib_width", + "wipe_tower_fillet_wall", + "wipe_tower_filament", + "wiping_volumes_extruders", + "wipe_tower_bridging", + "wipe_tower_extra_flow", + "single_extruder_multi_material_priming", + "wipe_tower_rotation_angle", + "tree_support_branch_distance_organic", + "tree_support_branch_diameter_organic", + "tree_support_branch_angle_organic", + "hole_to_polyhole", + "hole_to_polyhole_threshold", + "hole_to_polyhole_twisted", + "mmu_segmented_region_max_width", + "mmu_segmented_region_interlocking_depth", + "small_area_infill_flow_compensation", + "small_area_infill_flow_compensation_model", + "enable_wrapping_detection", + "seam_slope_type", + "seam_slope_conditional", + "scarf_angle_threshold", + "scarf_joint_speed", + "scarf_joint_flow_ratio", + "seam_slope_start_height", + "seam_slope_entire_loop", + "seam_slope_min_length", + "seam_slope_steps", + "seam_slope_inner_walls", + "scarf_overhang_threshold", + "interlocking_beam", + "interlocking_orientation", + "interlocking_beam_layer_count", + "interlocking_depth", + "interlocking_boundary_avoidance", + "interlocking_beam_width", + "calib_flowrate_topinfill_special_order", + // Z Anti-Aliasing (ZAA) + "zaa_enabled", + "zaa_minimize_perimeter_height", + "zaa_dont_alternate_fill_direction", + "zaa_min_z", + "ironing_expansion", }; static std::vector s_Preset_filament_options {/*"filament_colour", */ "default_filament_colour", "required_nozzle_HRC", "filament_diameter", "pellet_flow_coefficient", "volumetric_speed_coefficients", "filament_type", @@ -976,10 +1275,10 @@ static std::vector s_Preset_filament_options {/*"filament_colour", // "bed_type", //BBS:temperature_vitrification "temperature_vitrification", "reduce_fan_stop_start_freq","dont_slow_down_outer_wall", "slow_down_for_layer_cooling", "fan_min_speed", - "fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "overhang_fan_threshold", "close_fan_the_first_x_layers", "full_fan_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed", - "filament_start_gcode", "filament_end_gcode", + "fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "overhang_fan_threshold", "close_fan_the_first_x_layers", "close_additional_fan_first_x_layers", "first_x_layer_fan_speed", "full_fan_speed_layer", "additional_fan_full_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed", + "filament_start_gcode", "filament_end_gcode", "filament_change_extrusion_role_gcode", //exhaust fan control - "activate_air_filtration","during_print_exhaust_fan_speed","complete_print_exhaust_fan_speed", + "activate_air_filtration","activate_air_filtration_during_print","activate_air_filtration_on_completion","during_print_exhaust_fan_speed","complete_print_exhaust_fan_speed", // Retract overrides "filament_retraction_length", "filament_z_hop", "filament_z_hop_types", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_lift_enforce", "filament_retraction_speed", "filament_deretraction_speed", "filament_retract_restart_extra", "filament_retraction_minimum_travel", "filament_retract_when_changing_layer", "filament_wipe", "filament_retract_before_wipe", @@ -1000,7 +1299,7 @@ static std::vector s_Preset_filament_options {/*"filament_colour", "filament_multitool_ramming", "filament_multitool_ramming_volume", "filament_multitool_ramming_flow", "activate_chamber_temp_control", "filament_long_retractions_when_cut","filament_retraction_distances_when_cut", "idle_temperature", //BBS filament change length while the extruder color - "filament_change_length","filament_flush_volumetric_speed","filament_flush_temp", + "filament_change_length","filament_flush_volumetric_speed","filament_flush_temp", "filament_cooling_before_tower", "long_retractions_when_ec", "retraction_distances_when_ec" }; @@ -1013,12 +1312,14 @@ static std::vector s_Preset_machine_limits_options { "machine_max_junction_deviation", //resonance avoidance ported from qidi slicer "resonance_avoidance", "min_resonance_avoidance_speed", "max_resonance_avoidance_speed", + // Orca: input shaping + "input_shaping_emit", "input_shaping_type", "input_shaping_freq_x", "input_shaping_freq_y", "input_shaping_damp_x", "input_shaping_damp_y", }; static std::vector s_Preset_printer_options { "printer_technology", "printable_area", "extruder_printable_area", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor", - "fan_kickstart", "fan_speedup_time", "fan_speedup_overhangs", + "fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs", "single_extruder_multi_material", "manual_filament_change", "file_start_gcode", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "printing_by_object_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", "change_extrusion_role_gcode", "printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type", "printable_height", "extruder_printable_height", "extruder_clearance_radius", "extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod", @@ -1035,7 +1336,7 @@ static std::vector s_Preset_printer_options { "use_relative_e_distances", "extruder_type", "use_firmware_retraction", "printer_notes", "grab_length", "support_object_skip_flush", "physical_extruder_map", "cooling_tube_retraction", - "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "wipe_tower_type", "purge_in_prime_tower", "enable_filament_ramming", + "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "wipe_tower_type", "purge_in_prime_tower", "enable_filament_ramming", "tool_change_on_wipe_tower", "z_offset", "disable_m73", "preferred_orientation", "emit_machine_limits_to_gcode", "pellet_modded_printer", "support_multi_bed_types", "default_bed_type", "bed_mesh_min","bed_mesh_max","bed_mesh_probe_distance", "adaptive_bed_mesh_margin", "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut", "bed_temperature_formula", "nozzle_flush_dataset" @@ -1211,19 +1512,34 @@ void PresetCollection::add_default_preset(const std::vector &keys, ++ m_num_default_presets; } +std::string PresetCollection::canonical_preset_name(const std::string &name, const PresetOrigin &load_origin) const +{ + const ParsedName parsed = parse_preset_name(name); + PresetOrigin origin = load_origin; + if (origin.kind == PresetOrigin::Kind::Auto) { + origin.kind = parsed.kind; + origin.bundle_id = parsed.bundle_id; + } else if (origin.is_bundle() && origin.bundle_id.empty()) { + origin.bundle_id = parsed.bundle_id; + } + return get_preset_canonical_name(parsed.bare, origin); +} + // Load all presets found in dir_path. // Throws an exception on error. void PresetCollection::load_presets( const std::string &dir_path, const std::string &subdir, - PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule) + PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule, + std::function preset_loaded_fn, const PresetOrigin &load_origin) { // Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points, // see https://github.com/prusa3d/PrusaSlicer/issues/732 boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / subdir).make_preferred(); + const PresetOrigin resolved_origin = detect_origin_from_path(dir, load_origin); // Load custom roots first if (fs::exists(dir / "base")) { - load_presets(dir.string(), "base", substitutions, substitution_rule); + load_presets(dir.string(), "base", substitutions, substitution_rule, nullptr, resolved_origin); } //BBS: add config related logs @@ -1253,14 +1569,16 @@ void PresetCollection::load_presets( if (Slic3r::is_json_file(file_name)) { // Remove the .ini suffix. std::string name = file_name.erase(file_name.size() - 5); - if (this->find_preset(name, false)) { + std::string canonical_name = this->canonical_preset_name(name, resolved_origin); + if (this->find_preset(canonical_name, false)) { // This happens when there's is a preset (most likely legacy one) with the same name as a system preset // that's already been loaded from a bundle. - BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name; + BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << canonical_name; continue; } try { - Preset preset(m_type, name, false); + Preset preset(m_type, canonical_name, false); + preset.bundle_id = resolved_origin.bundle_id; preset.file = dir_entry.path().string(); // Load the preset file, apply preset values on top of defaults. try { @@ -1311,7 +1629,6 @@ void PresetCollection::load_presets( std::string inherits_value = option_str->value; // Orca: try to find if the parent preset has been renamed inherit_preset = this->find_preset2(inherits_value); - } else { ; } @@ -1389,6 +1706,9 @@ void PresetCollection::load_presets( fs::remove(file_path); } + if (preset_loaded_fn != nullptr) + preset_loaded_fn(preset); + presets_loaded.emplace_back(preset); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " load config successful and preset name is:" << preset.name; } catch (const std::runtime_error &err) { @@ -1507,9 +1827,9 @@ int PresetCollection::get_differed_values_to_update(Preset& preset, std::map& proje inherits_value.replace(pos, 1, 1, '~'); option_str->value = inherits_value; }*/ - inherit_preset = this->find_preset(inherits_value, false, true); + inherit_preset = this->find_preset2(inherits_value, true); } const Preset& default_preset = this->default_preset_for(config); if (inherit_preset) { @@ -1661,9 +1981,10 @@ bool PresetCollection::reset_project_embedded_presets() void PresetCollection::set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time) { lock(); + const std::string canonical_name = this->canonical_preset_name(name); for (auto it = m_presets.begin(); it != m_presets.end(); it++) { Preset* preset = &m_presets[it - m_presets.begin()]; - if (preset->name == name) { + if (preset->name == canonical_name) { if (syncinfo.empty()) preset->sync_info.clear(); else @@ -1728,7 +2049,7 @@ void PresetCollection::update_user_presets_directory(const std::string& dir_path } //BBS: save user presets to local -void PresetCollection::save_user_presets(const std::string& dir_path, const std::string& type, std::vector& need_to_delete_list) +void PresetCollection::save_user_presets(const std::string& dir_path, const std::string& type, std::map& need_to_delete_list) { boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / type).make_preferred(); @@ -1782,31 +2103,37 @@ void PresetCollection::save_user_presets(const std::string& dir_path, const std: } //BBS: load one user preset from key-values -bool PresetCollection::load_user_preset(std::string name, std::map preset_values, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule) +bool PresetCollection::load_user_preset(std::string name, std::map preset_values, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule, const PresetOrigin &load_origin) { std::string errors_cummulative; // Store the loaded presets into a new vector, otherwise the binary search for already existing presets would be broken. // (see the "Preset already present, not loading" message). //std::deque presets_loaded; int count = 0; + const std::string canonical_name = this->canonical_preset_name(name, load_origin); + auto update_alias = [this](Preset &preset) { + if (! preset.alias.empty()) + return; + set_custom_preset_alias(preset); + }; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, name %1% , total value counts %2%")%name %preset_values.size(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, name %1% , total value counts %2%")%canonical_name %preset_values.size(); //if the version is not matching, skip it if (preset_values.find(BBL_JSON_KEY_VERSION) == preset_values.end()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find version, not loading for user preset %1%")%name; + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find version, not loading for user preset %1%")%canonical_name; return false; } std::string version_str = preset_values[BBL_JSON_KEY_VERSION]; boost::optional cloud_version = Semver::parse(version_str); if (!cloud_version) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("invalid version %1%, not loading for user preset %2%")%version_str %name; + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("invalid version %1%, not loading for user preset %2%")%version_str %canonical_name; return false; } //setting_id if (preset_values.find(BBL_JSON_KEY_SETTING_ID) == preset_values.end()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find setting_id, not loading for user preset %1%")%name; + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find setting_id, not loading for user preset %1%")%canonical_name; return false; } std::string cloud_setting_id = preset_values[BBL_JSON_KEY_SETTING_ID]; @@ -1819,17 +2146,17 @@ bool PresetCollection::load_user_preset(std::string name, std::mapname; - auto iter = this->find_preset_internal(name); + auto iter = this->find_preset_internal(canonical_name); bool need_update = false; - if ((iter != m_presets.end()) && (iter->name == name)) { - BOOST_LOG_TRIVIAL(info) << "Found the Preset locally: " << name; + if ((iter != m_presets.end()) && (iter->name == canonical_name)) { + BOOST_LOG_TRIVIAL(info) << "Found the Preset locally: " << canonical_name; //BBS: we should compare the time between cloud and local if ((cloud_update_time == 0) || (cloud_update_time <= iter->updated_time)) { if (cloud_update_time < iter->updated_time) @@ -1841,7 +2168,7 @@ bool PresetCollection::load_user_preset(std::string name, std::mapfile); idx_file.replace_extension(".info"); iter->save_info(idx_file.string()); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("preset %1%'s update_time is eqaul or newer, cloud update_time %2%, local update_time %3%")%name %cloud_update_time %iter->updated_time; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("preset %1%'s update_time is eqaul or newer, cloud update_time %2%, local update_time %3%")%canonical_name %cloud_update_time %iter->updated_time; unlock(); return false; } @@ -1851,26 +2178,36 @@ bool PresetCollection::load_user_preset(std::string name, std::mapsecond; + } else { + const auto inherits_iter = preset_values.find(BBL_JSON_KEY_INHERITS); + const bool preset_inherits_from_parent = inherits_iter != preset_values.end() && !inherits_iter->second.empty(); + if (preset_inherits_from_parent) { + // This indicates that there is inherits exists but there is no base_id + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ + << boost::format("can not find base_id, not loading for user preset %1%") % canonical_name; + unlock(); + return false; + } } - std::string cloud_base_id = preset_values[BBL_JSON_KEY_BASE_ID]; //filament_id std::string cloud_filament_id; if ((m_type == Preset::TYPE_FILAMENT) && preset_values.find(BBL_JSON_KEY_FILAMENT_ID) != preset_values.end()) { cloud_filament_id = preset_values[BBL_JSON_KEY_FILAMENT_ID]; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << name << " filament_id: " << cloud_filament_id << " base_id: " << cloud_base_id; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << canonical_name << " filament_id: " << cloud_filament_id << " base_id: " << based_id; } DynamicPrintConfig new_config, cloud_config; try { ConfigSubstitutions config_substitutions = cloud_config.load_string_map(preset_values, rule); if (! config_substitutions.empty()) - substitutions.push_back({ name, m_type, PresetConfigSubstitutions::Source::UserCloud, name, std::move(config_substitutions) }); + substitutions.push_back({ canonical_name, m_type, PresetConfigSubstitutions::Source::UserCloud, canonical_name, std::move(config_substitutions) }); //BBS: use inherit config as the base Preset* inherit_preset = nullptr; @@ -1878,12 +2215,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map (inherits_config); std::string inherits_value = option_str->value; - /*size_t pos = inherits_value.find_first_of('*'); - if (pos != std::string::npos) { - inherits_value.replace(pos, 1, 1, '~'); - option_str->value = inherits_value; - }*/ - inherit_preset = this->find_preset(inherits_value, false, true); + inherit_preset = this->find_preset2(inherits_value, true); } const Preset& default_preset = this->default_preset_for(cloud_config); if (inherit_preset) { @@ -1897,7 +2229,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map(inherits_config); if (inherits_config2 && !inherits_config2->value.empty()) { //we should skip this preset here - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", can not find inherit preset for user preset %1%, just skip")%name; + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", can not find inherit preset for user preset %1%, just skip")%canonical_name; unlock(); return false; } @@ -1923,13 +2255,17 @@ bool PresetCollection::load_user_preset(std::string name, std::mapname == m_edited_preset.name && iter->is_dirty) { // Keep modifies when update from remote new_config.apply_only(m_edited_preset.config, m_edited_preset.config.diff(iter->config)); + } else if (iter->name == m_edited_preset.name) { + // Preset is not dirty (no local unsaved changes) — also update the edited preset + // to prevent a false "dirty" indication (orange highlight) after a silent cloud sync + m_edited_preset.config = new_config; } iter->config = new_config; iter->updated_time = cloud_update_time; @@ -1937,25 +2273,28 @@ bool PresetCollection::load_user_preset(std::string name, std::mapversion = cloud_version.value(); iter->user_id = cloud_user_id; iter->setting_id = cloud_setting_id; - iter->base_id = cloud_base_id; + iter->base_id = based_id; iter->filament_id = cloud_filament_id; + update_alias(*iter); //presets_loaded.emplace_back(*it->second); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", update the user preset %1% from cloud, type %2%, setting_id %3%, base_id %4%, sync_info %5% inherits %6%, filament_id %7%") % iter->name %Preset::get_type_string(m_type) %iter->setting_id %iter->base_id %iter->sync_info %iter->inherits() % iter->filament_id; } else { //create a new one - Preset preset(m_type, name, false); + Preset preset(m_type, canonical_name, false); preset.is_system = false; preset.loaded = true; + preset.bundle_id = load_origin.bundle_id; preset.config = new_config; preset.updated_time = cloud_update_time; preset.sync_info = "save"; preset.version = cloud_version.value(); preset.user_id = cloud_user_id; preset.setting_id = cloud_setting_id; - preset.base_id = cloud_base_id; + preset.base_id = based_id; preset.filament_id = cloud_filament_id; + update_alias(preset); size_t cur_index = iter - m_presets.begin(); m_presets.insert(iter, preset); @@ -1977,7 +2316,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map::iterator it = this->find_preset_internal(preset_name); - bool found = (it != m_presets.end()) && (it->name == preset_name) && (it->is_system || it->is_default); + // Presets that came from system vendors, the built-in defaults, or any loaded bundle (local or + // subscribed) are trusted — their g-code isn't user-authored, so the 3MF importer should not + // warn about them. + auto is_trusted = [](const Preset &p) { return p.is_system || p.is_default || p.is_from_bundle(); }; + + const std::string canonical_name = this->canonical_preset_name(preset_name); + std::deque::iterator it = this->find_preset_internal(canonical_name); + bool found = (it != m_presets.end()) && (it->name == canonical_name) && is_trusted(*it); if (!found) { - it = this->find_preset_renamed(preset_name); - found = it != m_presets.end() && (it->is_system || it->is_default); + it = this->find_preset_renamed(canonical_name); + found = it != m_presets.end() && is_trusted(*it); } if (!found) { if (!inherit_name.empty()) { - it = this->find_preset_internal(inherit_name); - found = it != m_presets.end() && it->name == inherit_name && (it->is_system || it->is_default); + const std::string canonical_inherit_name = this->canonical_preset_name(inherit_name); + it = this->find_preset_internal(canonical_inherit_name); + found = it != m_presets.end() && it->name == canonical_inherit_name && is_trusted(*it); if (found) BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": preset_name %1%, inherit_name %2%, found inherit in list")%preset_name %inherit_name; else @@ -2084,7 +2430,7 @@ std::pair PresetCollection::load_external_preset( cfg.apply_only(combined_config, keys, true); std::string &inherits = Preset::inherits(cfg); - //BBS: add different settings check logic, replace the old system preset's default value with new system preset's default values + //add different settings check logic, replace the old system preset's default value with new system preset's default values std::deque::iterator it = this->find_preset_internal(original_name); bool found = it != m_presets.end() && it->name == original_name; if (! found) { @@ -2424,7 +2770,7 @@ std::map> PresetCollection::get_filamen std::map> filament_presets; for (auto &preset : m_presets) { if (preset.is_user()) { - if (preset.inherits() == "" || !preset.filament_id.empty()) { filament_presets[preset.filament_id].push_back(&preset); } + if (preset.inherits() == "") { filament_presets[preset.filament_id].push_back(&preset); } continue; } if (get_preset_base(preset) == &preset) { filament_presets[preset.filament_id].push_back(&preset); } @@ -2446,7 +2792,7 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det // Preset with the same name found. Preset &preset = *it; //BBS: add project embedded preset logic - if (preset.is_default || preset.is_system) { + if (!preset.can_overwrite()) { //if (preset.is_default || preset.is_external || preset.is_system) // Cannot overwrite the default preset. //BBS: add lock logic for sync preset in background @@ -2505,6 +2851,9 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det preset.is_default = false; preset.is_system = false; preset.is_external = false; + + preset.bundle_id.clear(); + preset.file = this->path_for_preset(preset); // The newly saved preset will be activated -> make it visible. preset.is_visible = true; @@ -2518,18 +2867,8 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det preset.is_project_embedded = false; if (m_type == Preset::TYPE_PRINT) preset.config.option("print_settings_id", true)->value = new_name; - else if (m_type == Preset::TYPE_FILAMENT) { + else if (m_type == Preset::TYPE_FILAMENT) preset.config.option("filament_settings_id", true)->values[0] = new_name; - - boost::uuids::detail::md5 hash; - boost::uuids::detail::md5::digest_type digest; - hash.process_bytes(new_name.data(), new_name.size()); - hash.get_digest(digest); - const auto char_digest = reinterpret_cast(&digest); - std::string result; - boost::algorithm::hex(char_digest, char_digest + sizeof(boost::uuids::detail::md5::digest_type), std::back_inserter(result)); - preset.filament_id = "P" + result.substr(0, 7); - } else if (m_type == Preset::TYPE_PRINTER) preset.config.option("printer_settings_id", true)->value = new_name; //BBS: add lock logic for sync preset in background @@ -2557,7 +2896,7 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det bool PresetCollection::delete_current_preset() { Preset &selected = this->get_selected_preset(); - if (selected.is_default) + if (!selected.can_overwrite()) return false; if (get_preset_base(selected) == &selected) { @@ -2588,18 +2927,24 @@ bool PresetCollection::delete_current_preset() return true; } -bool PresetCollection::delete_preset(const std::string& name) +bool PresetCollection::delete_preset(const std::string& name, bool force) { - auto it = this->find_preset_internal(name); + Preset *preset_ptr = this->find_preset(name, false, true); + if (preset_ptr == nullptr) + return false; + + auto it = this->find_preset_internal(preset_ptr->name); + if (it == m_presets.end() || it->name != preset_ptr->name) + return false; Preset& preset = *it; - if (preset.is_default) + // ORCA: if the preset can't be overridden then don't allow deletion + // force=true bypasses this for bundle preset cleanup from cloud sync + if (!force && !preset.can_overwrite()) return false; - //BBS: add project embedded preset logic and refine is_external - //if (!preset.is_external && !preset.is_system) { - if (! preset.is_system) { - preset.remove_files(); - } + + preset.remove_files(); + //BBS: add lock logic for sync preset in background lock(); set_printer_hold_alias(it->alias, *it, true); @@ -2609,6 +2954,30 @@ bool PresetCollection::delete_preset(const std::string& name) return true; } +void PresetCollection::check_and_fix_syncinfo(Preset& preset, const std::string& user_id) +{ + // user id can't be empty + if (user_id.empty()) + return; + // correct the sync info if preset.user_id is empty(the profile json file is copied to the user folder with missing .info file) or preset.user_id + // is not equal to the current user id or preset.setting_id is not in expected format(the .info is copied from the older format) + if (preset.user_id.empty() || preset.user_id != user_id || preset.setting_id.find('-') == std::string::npos) { + preset.user_id = user_id; + preset.setting_id = ""; + + if (preset.base_id.empty()) { + const std::string inherits = Preset::inherits(preset.config); + Preset* parent_preset = find_preset2(inherits, true); + if (parent_preset) + preset.base_id = parent_preset->setting_id; + } + // tell the sync logic to sync it as a new preset + preset.updated_time = 0; + preset.sync_info = "create"; + preset.save_info(); + } +} + const Preset* PresetCollection::get_selected_preset_parent() const { if (this->get_selected_idx() == size_t(-1)) @@ -2671,7 +3040,7 @@ const Preset *PresetCollection::get_preset_base(const Preset &child) const // Handle user preset if (child.inherits().empty()) return &child; // this is user root - auto inherits = find_preset2(child.inherits(),true); + auto inherits = find_preset2(child.inherits(), true); return inherits ? get_preset_base(*inherits) : nullptr; } @@ -2745,20 +3114,21 @@ const std::string& PresetCollection::get_suffix_modified() { // If a preset was not found by its name, null is returned. Preset* PresetCollection::find_preset(const std::string &name, bool first_visible_if_not_found, bool real, bool only_from_library) { - Preset key(m_type, name, false); - auto it = this->find_preset_internal(name, only_from_library); - // Ensure that a temporary copy is returned if the preset found is currently selected. - return (it != m_presets.end() && it->name == key.name) ? &this->preset(it - m_presets.begin(), real) : - first_visible_if_not_found ? &this->first_visible() : nullptr; + const ParsedName parsed = parse_preset_name(name); + const std::string canonical = get_preset_canonical_name(parsed.bare, PresetOrigin(parsed.kind, parsed.bundle_id)); + auto it = this->find_preset_internal(canonical, only_from_library); + if (it != m_presets.end() && it->name == canonical) + return &this->preset(it - m_presets.begin(), real); + return first_visible_if_not_found ? &this->first_visible() : nullptr; } Preset* PresetCollection::find_preset2(const std::string& name, bool auto_match/* = true */) { - auto preset = find_preset(name,false,true); + auto preset = find_preset(name, false, true); if (preset == nullptr) { auto _name = get_preset_name_renamed(name); if (_name != nullptr) - preset = find_preset(*_name,false,true); + preset = find_preset(*_name, false, true); if (auto_match && preset == nullptr) { //Orca: one more try, find the most likely preset in OrcaFilamentLibrary if (name.find("Generic") != std::string::npos) { @@ -2774,7 +3144,6 @@ Preset* PresetCollection::find_preset2(const std::string& name, bool auto_match/ } } } - return preset; } @@ -3126,10 +3495,11 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b //BBS: add config related logs BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, try to select by name %2%, force %3%")%Preset::get_type_string(m_type) %name_w_suffix %force; std::string name = Preset::remove_suffix_modified(name_w_suffix); + const std::string normalized_name = this->canonical_preset_name(name); // 1) Try to find the preset by its name. - auto it = this->find_preset_internal(name); + auto it = this->find_preset_internal(normalized_name); size_t idx = 0; - if (it != m_presets.end() && it->name == name && it->is_visible) + if (it != m_presets.end() && it->name == normalized_name && it->is_visible) // Preset found by its name and it is visible. idx = it - m_presets.begin(); else { @@ -3162,11 +3532,12 @@ bool PresetCollection::select_preset_by_name_strict(const std::string &name) { //BBS: add config related logs BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, try to select by name %2%")%Preset::get_type_string(m_type) %name; + const std::string canonical_name = this->canonical_preset_name(name); // 1) Try to find the preset by its name. - auto it = this->find_preset_internal(name); + auto it = this->find_preset_internal(canonical_name); size_t idx = (size_t)-1; - if (it != m_presets.end() && it->name == name && it->is_visible) + if (it != m_presets.end() && it->name == canonical_name && it->is_visible) // Preset found by its name. idx = it - m_presets.begin(); // 2) Select the new preset. @@ -3278,24 +3649,30 @@ void PresetCollection::update_map_system_profile_renamed() void PresetCollection::set_custom_preset_alias(Preset &preset) { - if (m_type == Preset::Type::TYPE_FILAMENT && preset.config.has(BBL_JSON_KEY_INHERITS) && preset.config.option(BBL_JSON_KEY_INHERITS)->value.empty()) { - std::string alias_name; - std::string preset_name = preset.name; - if (alias_name.empty()) { - size_t end_pos = preset_name.find_first_of("@"); - if (end_pos != std::string::npos) { - alias_name = preset_name.substr(0, end_pos); - boost::trim_right(alias_name); - } - } - if (alias_name.empty() || is_alias_exist(alias_name, &preset)) - preset.alias = ""; - else { - preset.alias = std::move(alias_name); - m_map_alias_to_profile_name[preset.alias].push_back(preset.name); - set_printer_hold_alias(preset.alias, preset); + // For filaments, remove the postfix + // For printers, there is nothing to remove + // For prints AKA processes, the postfix should be kept + // Alias should be set here, as the preset name may be augmented further later (i.e., prefixing relative path for bundles) + std::string bare_preset_name = get_preset_bare_name(preset.name); + std::string alias_name = bare_preset_name; + + const bool is_root_filament_preset = + m_type == Preset::Type::TYPE_FILAMENT && + preset.config.has(BBL_JSON_KEY_INHERITS) && + preset.config.option(BBL_JSON_KEY_INHERITS)->value.empty(); + if (is_root_filament_preset) { + const size_t suffix_separator_pos = bare_preset_name.find_first_of("@"); + if (suffix_separator_pos != std::string::npos) { + alias_name = bare_preset_name.substr(0, suffix_separator_pos); + boost::trim_right(alias_name); + if (alias_name.empty()) + alias_name = bare_preset_name; } } + + preset.alias = std::move(alias_name); + m_map_alias_to_profile_name[preset.alias].push_back(preset.name); + set_printer_hold_alias(preset.alias, preset); } void PresetCollection::set_printer_hold_alias(const std::string &alias, Preset &preset, bool remove) @@ -3396,7 +3773,7 @@ std::string PresetCollection::path_from_name(const std::string &new_name, bool d std::string PresetCollection::path_for_preset(const Preset &preset) const { - return path_from_name(preset.name, is_base_preset(preset)); + return path_from_name(get_preset_bare_name(preset.name), is_base_preset(preset)); } const Preset& PrinterPresetCollection::default_preset_for(const DynamicPrintConfig &config) const diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index f85b0103c7..5e0ab15849 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -3101,7 +3101,7 @@ void PresetBundle::get_ams_cobox_infos(AMSComboInfo& combox_info) continue; } auto iter = std::find_if(filaments.begin(), filaments.end(), - [this, &filament_id](auto &f) { return f.is_compatible && (filaments.get_preset_base(f) == &f || (f.is_user() && !f.filament_id.empty())) && f.filament_id == filament_id; }); + [this, &filament_id](auto &f) { return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; }); if (iter == filaments.end()) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": filament_id %1% not found or system or compatible") % filament_id; auto filament_type = ams.opt_string("filament_type", 0u); @@ -3203,7 +3203,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector Date: Tue, 2 Jun 2026 13:26:10 +0200 Subject: [PATCH 11/26] fix(moonraker): User-Presets ohne filament_id per Namen matchen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Presets die via 'Save As' angelegt werden haben keine filament_id. Statt sie zu überspringen werden sie per Namen gematcht und ihr Preset-Name als Identifier zurückgegeben. --- src/slic3r/Utils/MoonrakerPrinterAgent.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index ae0b1e95ca..4386f4517d 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -191,14 +191,12 @@ std::string filament_id_by_name(const Slic3r::PresetCollection& filaments, for (int pass = 1; pass <= 2; ++pass) { for (size_t i = 0; i < filaments.size(); ++i) { const auto& preset = filaments.preset(i); - if (!preset.is_visible || preset.filament_id.empty()) { - if (pass == 1) { - BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher skip preset='" << preset.name - << "' visible=" << preset.is_visible - << " filament_id_empty=" << preset.filament_id.empty(); - } + if (!preset.is_visible) { continue; } + // User presets (created via "Save As") may have no filament_id yet. + // We still match them by name and use their name as the identifier. + const std::string preset_id = preset.filament_id.empty() ? preset.name : preset.filament_id; if (pass == 1 && !preset.is_compatible) { continue; // Pass 1: only compatible presets } @@ -221,12 +219,12 @@ std::string filament_id_by_name(const Slic3r::PresetCollection& filaments, } const std::string candidate = normalize_filament_name_for_match(preset.name); BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher compare (pass=" << pass << ") preset='" << preset.name - << "' normalized='" << candidate << "' filament_id='" << preset.filament_id << "'"; + << "' normalized='" << candidate << "' preset_id='" << preset_id << "'"; if (filament_name_match_relaxed(wanted, candidate)) { BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher matched (pass=" << pass << ") requested='" << filament_name << "' normalized='" << wanted << "' to preset='" << preset.name - << "' filament_id='" << preset.filament_id << "'"; - return preset.filament_id; + << "' preset_id='" << preset_id << "'"; + return preset_id; } } if (pass == 1) { From c4a18fc0ac542b9cd1793566c59ddbde32d2b8f1 Mon Sep 17 00:00:00 2001 From: viewit Date: Tue, 2 Jun 2026 14:37:37 +0200 Subject: [PATCH 12/26] =?UTF-8?q?fix(preset):=20filament=5Fid=20f=C3=BCr?= =?UTF-8?q?=20User-Presets=20beim=20Speichern=20generieren=20(PR=20#13315?= =?UTF-8?q?=20portiert)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wenn ein Filament-User-Preset noch keine filament_id hat, wird beim Speichern/Umbenennen automatisch eine MD5-basierte ID (P + 7 Zeichen) generiert. Damit kann der Moonraker-Filament-Matcher das Preset korrekt einem Slot zuordnen statt auf Generic PLA zurückzufallen. --- src/libslic3r/Preset.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index cd58e90e97..61463dd81f 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -41,6 +41,8 @@ #include #include #include +#include +#include #include "libslic3r.h" #include "Utils.hpp" @@ -2817,8 +2819,20 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det if (m_type == Preset::TYPE_PRINT) preset.config.option("print_settings_id", true)->value = new_name; - else if (m_type == Preset::TYPE_FILAMENT) + else if (m_type == Preset::TYPE_FILAMENT) { preset.config.option("filament_settings_id", true)->values[0] = new_name; + // Generate a unique filament_id for user presets that don't have one yet (PR #13315). + if (preset.filament_id.empty()) { + boost::uuids::detail::md5 hash; + boost::uuids::detail::md5::digest_type digest; + hash.process_bytes(new_name.data(), new_name.size()); + hash.get_digest(digest); + const auto char_digest = reinterpret_cast(&digest); + std::string result; + boost::algorithm::hex(char_digest, char_digest + sizeof(boost::uuids::detail::md5::digest_type), std::back_inserter(result)); + preset.filament_id = "P" + result.substr(0, 7); + } + } else if (m_type == Preset::TYPE_PRINTER) preset.config.option("printer_settings_id", true)->value = new_name; final_inherits = preset.inherits(); From 7249588d7c5498f5781f3778f912093c6aaf59ea Mon Sep 17 00:00:00 2001 From: viewit Date: Tue, 2 Jun 2026 18:33:17 +0200 Subject: [PATCH 13/26] fix(preset): filament_id in Preset::save() generieren statt nur in save_current_preset() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Der vorherige Fix griff nur beim Umbenennen. Beim normalen Speichern läuft Preset::save() — dort wird jetzt ebenfalls eine MD5-basierte ID generiert wenn das User-Filament-Preset noch keine hat. --- src/libslic3r/Preset.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 61463dd81f..60ead0f814 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -638,6 +638,23 @@ void Preset::save(DynamicPrintConfig* parent_config) //BBS: add project embedded preset logic if (this->is_project_embedded) return; + + // Generate a unique filament_id for user filament presets that don't have one yet. + // Inherited presets (e.g. "My PLA" inheriting "Generic PLA @System") previously had + // no filament_id which caused AMS sync to fall back to the parent's Generic ID. + if (this->is_user() && this->filament_id.empty() && !this->name.empty() && + this->type() == Preset::TYPE_FILAMENT) { + boost::uuids::detail::md5 hash; + boost::uuids::detail::md5::digest_type digest; + hash.process_bytes(this->name.data(), this->name.size()); + hash.get_digest(digest); + const auto char_digest = reinterpret_cast(&digest); + std::string result; + boost::algorithm::hex(char_digest, char_digest + sizeof(boost::uuids::detail::md5::digest_type), std::back_inserter(result)); + this->filament_id = "P" + result.substr(0, 7); + BOOST_LOG_TRIVIAL(info) << "Preset::save: generated filament_id='" << this->filament_id << "' for user preset '" << this->name << "'"; + } + //BBS: change to json format //this->config.save(this->file); std::string from_str; From aed7206f1337335a9042ec5221a0c71fbd45eec6 Mon Sep 17 00:00:00 2001 From: viewit Date: Tue, 2 Jun 2026 19:21:59 +0200 Subject: [PATCH 14/26] fix(preset): type Feld statt type() Methode in Preset::save() --- src/libslic3r/Preset.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 60ead0f814..74b3c65f02 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -643,7 +643,7 @@ void Preset::save(DynamicPrintConfig* parent_config) // Inherited presets (e.g. "My PLA" inheriting "Generic PLA @System") previously had // no filament_id which caused AMS sync to fall back to the parent's Generic ID. if (this->is_user() && this->filament_id.empty() && !this->name.empty() && - this->type() == Preset::TYPE_FILAMENT) { + this->type == Preset::TYPE_FILAMENT) { boost::uuids::detail::md5 hash; boost::uuids::detail::md5::digest_type digest; hash.process_bytes(this->name.data(), this->name.size()); From be7d3512bf43d0b9304cc4f536cbb539fdbd3b8f Mon Sep 17 00:00:00 2001 From: viewit Date: Tue, 2 Jun 2026 20:06:55 +0200 Subject: [PATCH 15/26] fix(preset): filament_id auch im parent_config-Branch von Preset::save() schreiben --- src/libslic3r/Preset.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 74b3c65f02..6e600096db 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -710,6 +710,8 @@ void Preset::save(DynamicPrintConfig* parent_config) opt_dst->set(opt_src); } } + if (!filament_id.empty()) + temp_config.set_key_value(BBL_JSON_KEY_FILAMENT_ID, new ConfigOptionString(filament_id)); temp_config.save_to_json(this->file, bare_name, from_str, this->version.to_string()); } else if (!filament_id.empty() && inherits().empty()) { DynamicPrintConfig temp_config = config; From 4fb6bb475cda17cdba4149215f4aa5fc44cc899d Mon Sep 17 00:00:00 2001 From: viewit Date: Tue, 2 Jun 2026 20:09:37 +0200 Subject: [PATCH 16/26] =?UTF-8?q?fix(moonraker):=20Pass=203=20=E2=80=94=20?= =?UTF-8?q?auch=20unsichtbare=20Presets=20durchsuchen=20f=C3=BCr=20uninsta?= =?UTF-8?q?llierte=20Drucker-Hersteller?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/slic3r/Utils/MoonrakerPrinterAgent.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index 4386f4517d..6ff593a16e 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -188,10 +188,13 @@ std::string filament_id_by_name(const Slic3r::PresetCollection& filaments, // Two-pass search: Pass 1 = compatible presets only (ideal), Pass 2 = all visible presets // (fallback for vendors like eSUN/Eryone that have no printer-specific Kobra X profile). - for (int pass = 1; pass <= 2; ++pass) { + for (int pass = 1; pass <= 3; ++pass) { for (size_t i = 0; i < filaments.size(); ++i) { const auto& preset = filaments.preset(i); - if (!preset.is_visible) { + // Pass 1: compatible + visible only + // Pass 2: visible but not necessarily compatible (vendors without printer-specific profile) + // Pass 3: any preset including invisible (vendors not installed as printer) + if (pass <= 2 && !preset.is_visible) { continue; } // User presets (created via "Save As") may have no filament_id yet. @@ -230,6 +233,9 @@ std::string filament_id_by_name(const Slic3r::PresetCollection& filaments, if (pass == 1) { BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher pass 1 (compatible) found no match for '" << filament_name << "', trying pass 2 (all visible)"; + } else if (pass == 2) { + BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher pass 2 (all visible) found no match for '" + << filament_name << "', trying pass 3 (all presets incl. invisible)"; } } BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher found no match for requested='" << filament_name From f832fb2d4dbae756fed888b9577e9dd7c80fbca0 Mon Sep 17 00:00:00 2001 From: viewit Date: Tue, 2 Jun 2026 22:07:50 +0200 Subject: [PATCH 17/26] fix(preset): filament_id auch generieren wenn sie vom Parent geerbt wurde (== base_id) --- src/libslic3r/Preset.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 6e600096db..89b12e625f 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -642,8 +642,12 @@ void Preset::save(DynamicPrintConfig* parent_config) // Generate a unique filament_id for user filament presets that don't have one yet. // Inherited presets (e.g. "My PLA" inheriting "Generic PLA @System") previously had // no filament_id which caused AMS sync to fall back to the parent's Generic ID. - if (this->is_user() && this->filament_id.empty() && !this->name.empty() && - this->type == Preset::TYPE_FILAMENT) { + // Also generate a new ID if filament_id is inherited from the parent (== base_id). + // This happens when a user preset is saved for the first time without having its own ID. + bool needs_unique_filament_id = this->is_user() && !this->name.empty() && + this->type == Preset::TYPE_FILAMENT && + (this->filament_id.empty() || (!this->base_id.empty() && this->filament_id == this->base_id)); + if (needs_unique_filament_id) { boost::uuids::detail::md5 hash; boost::uuids::detail::md5::digest_type digest; hash.process_bytes(this->name.data(), this->name.size()); From 3c291f8883ca5b999c46e606a587b0900112e5f5 Mon Sep 17 00:00:00 2001 From: viewit Date: Wed, 3 Jun 2026 11:10:04 +0200 Subject: [PATCH 18/26] =?UTF-8?q?fix(preset):=20filament=5Fid=20f=C3=BCr?= =?UTF-8?q?=20User-Presets=20generieren=20wenn=20keine=20P-Prefix-ID=20vor?= =?UTF-8?q?handen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/libslic3r/Preset.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 89b12e625f..5b83c306f9 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -644,9 +644,13 @@ void Preset::save(DynamicPrintConfig* parent_config) // no filament_id which caused AMS sync to fall back to the parent's Generic ID. // Also generate a new ID if filament_id is inherited from the parent (== base_id). // This happens when a user preset is saved for the first time without having its own ID. + // A user preset needs its own filament_id if: + // - it has no filament_id at all, OR + // - its filament_id does not start with "P" (user preset IDs start with "P", + // system IDs start with GFL/OGFL/GFA/etc.) bool needs_unique_filament_id = this->is_user() && !this->name.empty() && this->type == Preset::TYPE_FILAMENT && - (this->filament_id.empty() || (!this->base_id.empty() && this->filament_id == this->base_id)); + (this->filament_id.empty() || this->filament_id.front() != 'P'); if (needs_unique_filament_id) { boost::uuids::detail::md5 hash; boost::uuids::detail::md5::digest_type digest; From 9de06ea7f14f690916f5dbfa16265fc16bf2ff6d Mon Sep 17 00:00:00 2001 From: viewit Date: Wed, 3 Jun 2026 11:15:59 +0200 Subject: [PATCH 19/26] fix(preset): filament_id auch im parent_config-Branch von Preset::save() schreiben MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Außerdem: sub_brands in AMS-Config übergeben damit sync_ams_list() bei ID-Kollisionen (z.B. OGFL04 = Overture + eSUN) den korrekten Hersteller per Namens-Prefix bevorzugt. --- src/libslic3r/PresetBundle.cpp | 18 +++++++++++++++--- src/slic3r/GUI/Plater.cpp | 1 + 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 5e0ab15849..a9ca8df393 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -3201,9 +3201,21 @@ unsigned int PresetBundle::sync_ams_list(std::vector Sidebar::build_filament_ams_list(MachineObject tray_config.set_key_value("filament_colour_type", new ConfigOptionStrings{std::to_string(tray.ctype)}); tray_config.set_key_value("filament_exist", new ConfigOptionBools{tray.is_exists}); tray_config.set_key_value("filament_slot_placeholder", new ConfigOptionBools{tray.is_slot_placeholder}); + tray_config.set_key_value("filament_sub_brands", new ConfigOptionStrings{tray.sub_brands}); std::optional info; if (wxGetApp().preset_bundle) { info = wxGetApp().preset_bundle->get_filament_by_filament_id(tray.setting_id); From b2a133822c5c88523ad4674cfeca008311d4eda1 Mon Sep 17 00:00:00 2001 From: viewit Date: Wed, 3 Jun 2026 14:04:13 +0200 Subject: [PATCH 20/26] =?UTF-8?q?fix(preset):=20eigene=20filament=5Fid=20b?= =?UTF-8?q?eim=20Laden=20nicht=20durch=20Parent-ID=20=C3=BCberschreiben?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/libslic3r/Preset.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 5b83c306f9..af3bfc05e1 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1664,7 +1664,10 @@ void PresetCollection::load_presets( const Preset& default_preset = this->default_preset_for(config); if (inherit_preset) { preset.config = inherit_preset->config; - preset.filament_id = inherit_preset->filament_id; + // Only inherit filament_id from parent if this preset has no own ID in JSON. + // User presets with a P-prefix ID (generated by Preset::save) must keep their own ID. + if (preset.filament_id.empty()) + preset.filament_id = inherit_preset->filament_id; extend_default_config_length(config, false, {}); preset.config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); } From ba5abddfab235c4df16bc2ba85ccccb30cf0b402 Mon Sep 17 00:00:00 2001 From: viewit Date: Wed, 3 Jun 2026 14:47:19 +0200 Subject: [PATCH 21/26] =?UTF-8?q?fix(preset):=20sync=5Fams=5Flist=20auch?= =?UTF-8?q?=20User-Presets=20(P-Prefix=20IDs)=20ber=C3=BCcksichtigen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/libslic3r/PresetBundle.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index a9ca8df393..684f9256bb 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -3216,6 +3216,13 @@ unsigned int PresetBundle::sync_ams_list(std::vector Date: Wed, 3 Jun 2026 21:35:50 +0200 Subject: [PATCH 22/26] feat: Version auf 2.4.0-alpha-kx2 und KX-Bridge Hinweis im About-Dialog --- src/slic3r/GUI/AboutDialog.cpp | 1 + version.inc | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index 51373890af..8ed19a13d4 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -269,6 +269,7 @@ AboutDialog::AboutDialog() text_list.push_back(_L("Open-source slicing stands on a tradition of collaboration and attribution. Slic3r, created by Alessandro Ranellucci and the RepRap community, laid the foundation. PrusaSlicer by Prusa Research built on that work, Bambu Studio forked from PrusaSlicer, and SuperSlicer extended it with community-driven enhancements. Each project carried the work of its predecessors forward, crediting those who came before.")); text_list.push_back(_L("OrcaSlicer began in that same spirit, drawing from PrusaSlicer, BambuStudio, SuperSlicer, and CuraSlicer. But it has since grown far beyond its origins — introducing advanced calibration tools, precise wall and seam control and hundreds of other features.")); text_list.push_back(_L("Today, OrcaSlicer is the most widely used and actively developed open-source slicer in the 3D printing community. Many of its innovations have been adopted by other slicers, making it a driving force for the entire industry.")); + text_list.push_back(_L("This build includes additional patches for KX-Bridge (Anycubic Kobra X Moonraker bridge). These modifications are distributed under the GNU AGPL-3.0 license. Source code available at: https://gitea.it-drui.de/viewit/OrcaSlicer-KX-source")); text_sizer->Add( 0, 0, 0, wxTOP, FromDIP(33)); bool is_zh = wxGetApp().app_config->get("language") == "zh_CN"; diff --git a/version.inc b/version.inc index b1e924b6f0..d2f0fbe217 100644 --- a/version.inc +++ b/version.inc @@ -7,7 +7,7 @@ set(SLIC3R_APP_KEY "OrcaSlicer") if(NOT DEFINED BBL_INTERNAL_TESTING) set(BBL_INTERNAL_TESTING "0") endif() -set(SoftFever_VERSION "2.4.0-alpha") +set(SoftFever_VERSION "2.4.0-alpha-kx2") string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" SoftFever_VERSION_MATCH ${SoftFever_VERSION}) set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1}) From 0f4c6dffff2c5ade3587570d1130c26a07a8c54a Mon Sep 17 00:00:00 2001 From: thysson2701 Date: Thu, 4 Jun 2026 10:09:17 +0200 Subject: [PATCH 23/26] feat: About-Dialog URL auf OrcaSlicer-KX Gitea-Branch aktualisiert --- src/slic3r/GUI/AboutDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index 8ed19a13d4..f1736dec9c 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -269,7 +269,7 @@ AboutDialog::AboutDialog() text_list.push_back(_L("Open-source slicing stands on a tradition of collaboration and attribution. Slic3r, created by Alessandro Ranellucci and the RepRap community, laid the foundation. PrusaSlicer by Prusa Research built on that work, Bambu Studio forked from PrusaSlicer, and SuperSlicer extended it with community-driven enhancements. Each project carried the work of its predecessors forward, crediting those who came before.")); text_list.push_back(_L("OrcaSlicer began in that same spirit, drawing from PrusaSlicer, BambuStudio, SuperSlicer, and CuraSlicer. But it has since grown far beyond its origins — introducing advanced calibration tools, precise wall and seam control and hundreds of other features.")); text_list.push_back(_L("Today, OrcaSlicer is the most widely used and actively developed open-source slicer in the 3D printing community. Many of its innovations have been adopted by other slicers, making it a driving force for the entire industry.")); - text_list.push_back(_L("This build includes additional patches for KX-Bridge (Anycubic Kobra X Moonraker bridge). These modifications are distributed under the GNU AGPL-3.0 license. Source code available at: https://gitea.it-drui.de/viewit/OrcaSlicer-KX-source")); + text_list.push_back(_L("This build includes additional patches for KX-Bridge (Anycubic Kobra X Moonraker bridge). GNU AGPL-3.0: upstream https://github.com/SoftFever/OrcaSlicer + KX patches https://gitea.it-drui.de/viewit/OrcaSlicer-KX/src/branch/build-alpha-pr-kx-filament-hint")); text_sizer->Add( 0, 0, 0, wxTOP, FromDIP(33)); bool is_zh = wxGetApp().app_config->get("language") == "zh_CN"; From 5dbc124b0f208d62bc774e246fe0735976b69d53 Mon Sep 17 00:00:00 2001 From: thysson2701 Date: Sat, 6 Jun 2026 22:26:36 +0200 Subject: [PATCH 24/26] =?UTF-8?q?feat(tools):=20verify=5Fbuild.sh=20pr?= =?UTF-8?q?=C3=BCft=20KX-spezifische=20Strings=20vor=20Release-Upload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/verify_build.sh | 97 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100755 tools/verify_build.sh diff --git a/tools/verify_build.sh b/tools/verify_build.sh new file mode 100755 index 0000000000..6c653679c6 --- /dev/null +++ b/tools/verify_build.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +# Prüft ob ein OrcaSlicer-KX Build-Artefakt die erwarteten Strings enthält. +# Verwendung: +# ./tools/verify_build.sh linux +# ./tools/verify_build.sh windows +# +# Gibt Exit-Code 0 bei Erfolg, 1 bei Fehler zurück. + +set -euo pipefail + +PLATFORM="${1:-}" +ARTIFACT="${2:-}" + +if [[ -z "$PLATFORM" || -z "$ARTIFACT" ]]; then + echo "Verwendung: $0 " + exit 1 +fi + +if [[ ! -f "$ARTIFACT" ]]; then + echo "FEHLER: Artefakt nicht gefunden: $ARTIFACT" + exit 1 +fi + +# Versionstring aus version.inc lesen +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(dirname "$SCRIPT_DIR")" +VERSION=$(grep 'set(SoftFever_VERSION' "$REPO_ROOT/version.inc" | grep -oP '"\K[^"]+') + +if [[ -z "$VERSION" ]]; then + echo "FEHLER: Konnte VERSION nicht aus version.inc lesen" + exit 1 +fi + +echo "=== OrcaSlicer-KX Build-Verifikation ===" +echo "Plattform : $PLATFORM" +echo "Artefakt : $ARTIFACT" +echo "Erwartet : $VERSION" +echo "========================================" + +TMPDIR=$(mktemp -d) +trap "rm -rf $TMPDIR" EXIT + +CHECKS=( + "$VERSION" + "KX-Bridge" + "filament_sub_brands" +) + +BINARY="" + +if [[ "$PLATFORM" == "linux" ]]; then + echo "Extrahiere AppImage..." + chmod +x "$ARTIFACT" + cd "$TMPDIR" + "$ARTIFACT" --appimage-extract bin/orca-slicer 2>/dev/null || \ + "$ARTIFACT" --appimage-extract 2>/dev/null + BINARY=$(find "$TMPDIR/squashfs-root" -name "orca-slicer" -type f | head -1) + if [[ -z "$BINARY" ]]; then + echo "FEHLER: orca-slicer Binary nicht im AppImage gefunden" + exit 1 + fi + +elif [[ "$PLATFORM" == "windows" ]]; then + echo "Extrahiere Windows ZIP..." + unzip -q "$ARTIFACT" "OrcaSlicer.dll" -d "$TMPDIR" 2>/dev/null || true + BINARY="$TMPDIR/OrcaSlicer.dll" + if [[ ! -f "$BINARY" ]]; then + echo "FEHLER: OrcaSlicer.dll nicht im ZIP gefunden" + exit 1 + fi + +else + echo "FEHLER: Unbekannte Plattform '$PLATFORM' (erwartet: linux oder windows)" + exit 1 +fi + +echo "Binary : $BINARY ($(du -sh "$BINARY" | cut -f1))" +echo "" + +FAILED=0 +for needle in "${CHECKS[@]}"; do + if grep -qaF "$needle" "$BINARY" 2>/dev/null; then + echo " [OK] $needle" + else + echo " [FAIL] $needle — NICHT GEFUNDEN" + FAILED=1 + fi +done + +echo "" +if [[ $FAILED -eq 0 ]]; then + echo "=== VERIFIKATION ERFOLGREICH ===" + exit 0 +else + echo "=== VERIFIKATION FEHLGESCHLAGEN — Upload abgebrochen ===" + exit 1 +fi From 99a5e1e555149e204632535f67f6fe64c47e3f7e Mon Sep 17 00:00:00 2001 From: thysson2701 Date: Sat, 6 Jun 2026 23:35:51 +0200 Subject: [PATCH 25/26] =?UTF-8?q?docs:=20Anleitung=20erweitert=20=E2=80=94?= =?UTF-8?q?=20filament=5Fid=20pr=C3=BCfen,=20Preset=20importieren/=C3=BCbe?= =?UTF-8?q?rtragen=20(DE/EN/ES)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/filament-preset-bridge-guide.md | 344 +++++++++++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 docs/filament-preset-bridge-guide.md diff --git a/docs/filament-preset-bridge-guide.md b/docs/filament-preset-bridge-guide.md new file mode 100644 index 0000000000..30862e2aab --- /dev/null +++ b/docs/filament-preset-bridge-guide.md @@ -0,0 +1,344 @@ +# Eigene Filament-Presets anlegen, prüfen und mit KX-Bridge verknüpfen + +> **Gilt für:** OrcaSlicer-KX v2.4.0-alpha-kx2 oder neuer + +--- + +## Was ist die `filament_id` und warum ist sie wichtig? + +Jedes Filament-Preset in OrcaSlicer hat eine interne `filament_id`. Diese ID wird von der KX-Bridge genutzt, um beim AMS-Sync das richtige Preset zuzuordnen. + +- System-Presets (z.B. "Polymaker PolyTerra PLA") haben eine feste ID wie `GFL99` oder `OGFL04`. +- **Eigene (User-)Presets** bekommen in OrcaSlicer-KX automatisch eine eindeutige ID, die mit `P` beginnt (z.B. `P3a7f2c1`). + +Ohne eindeutige ID zeigt OrcaSlicer beim Sync immer "Generic PLA" — auch wenn das Preset existiert. + +--- + +## 1. Eigenes Filament-Preset anlegen + +1. OrcaSlicer-KX starten +2. Rechts oben im **Filament-Dropdown** ein passendes Basis-Preset wählen (z.B. "Generic PLA" oder ein Hersteller-Preset) +3. Einstellungen nach Wunsch anpassen (Temperaturen, Kühlung, etc.) +4. Auf das **Speichern-Symbol** (Diskette) klicken → **"Save as new preset"** +5. Namen eingeben — z.B. `SUNLU PLA+ 2.0` + > Der Name muss später exakt so in der Bridge eingetragen werden. +6. Drucker auswählen: **Anycubic Kobra X 0.4 nozzle** — wichtig für die Kompatibilität! +7. **Speichern** klicken +8. OrcaSlicer **einmal neu starten** — erst dann wird die `filament_id` dauerhaft gespeichert. + +--- + +## 2. Eindeutige ID prüfen + +Nach dem Neustart prüfen, ob die ID korrekt gesetzt wurde: + +**Windows:** +``` +%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json +``` + +**Linux:** +``` +~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json +``` + +Die Datei öffnen und nach `filament_id` suchen: + +```json +{ + "filament_id": "P3a7f2c1", + ... +} +``` + +✅ Korrekt: ID beginnt mit `P` gefolgt von 7 Hex-Zeichen +❌ Fehlt oder leer: OrcaSlicer-KX zu alt — Update auf v2.4.0-alpha-kx2 oder neuer + +--- + +## 3. Preset auf einen anderen PC übertragen (Import) + +### Exportieren (Quell-PC) + +Die Preset-Datei einfach kopieren: + +**Windows:** +``` +%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json +``` + +**Linux:** +``` +~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json +``` + +### Importieren (Ziel-PC) + +**Methode A — Datei direkt kopieren:** +1. Die `.json`-Datei in das gleiche Verzeichnis auf dem Ziel-PC kopieren +2. OrcaSlicer neu starten → Preset erscheint im Dropdown + +**Methode B — OrcaSlicer Import-Funktion:** +1. In OrcaSlicer: **File → Import → Import Configs...** +2. Die `.json`-Datei auswählen +3. OrcaSlicer neu starten + +> **Wichtig:** Die `filament_id` in der Datei bleibt erhalten — das Preset wird auf dem Ziel-PC genauso erkannt wie auf dem Quell-PC. + +--- + +## 4. Preset in KX-Bridge verknüpfen + +1. KX-Bridge UI öffnen +2. **Filament-Verwaltung** → AMS-Slot auswählen +3. Im Feld **Filament-Name** exakt den OrcaSlicer-Preset-Namen eintragen: + ``` + SUNLU PLA+ 2.0 + ``` +4. Speichern + +Die Bridge sendet beim Sync `filament_name: "SUNLU PLA+ 2.0"` → OrcaSlicer findet das Preset anhand von Name und `filament_id` → zeigt es korrekt an. + +--- + +## Wichtige Hinweise + +| Was | Warum | +|-----|-------| +| Name in OrcaSlicer und Bridge müssen **exakt** übereinstimmen | Groß-/Kleinschreibung und Sonderzeichen werden verglichen | +| Preset muss für **Anycubic Kobra X 0.4 nozzle** kompatibel sein | Beim Speichern den richtigen Drucker auswählen | +| Nach dem ersten Speichern OrcaSlicer **neu starten** | Erst dann wird die `filament_id` persistent geschrieben | +| **OrcaSlicer-KX v2.4.0-alpha-kx2** oder neuer verwenden | Ältere Versionen generieren keine eindeutige `filament_id` für User-Presets | + +--- +--- + +# How to Create, Verify and Import Custom Filament Presets for KX-Bridge + +> **Requires:** OrcaSlicer-KX v2.4.0-alpha-kx2 or newer + +--- + +## What is the `filament_id` and why does it matter? + +Every filament preset in OrcaSlicer has an internal `filament_id`. The KX-Bridge uses this ID to match the correct preset during AMS sync. + +- System presets (e.g. "Polymaker PolyTerra PLA") have a fixed ID like `GFL99` or `OGFL04`. +- **Custom (user) presets** automatically receive a unique ID starting with `P` (e.g. `P3a7f2c1`) in OrcaSlicer-KX. + +Without a unique ID, OrcaSlicer will always show "Generic PLA" during sync — even if the preset exists. + +--- + +## 1. Create a Custom Filament Preset + +1. Launch OrcaSlicer-KX +2. Select a suitable base preset from the **filament dropdown** (e.g. "Generic PLA" or a vendor preset) +3. Adjust settings as needed (temperatures, cooling, etc.) +4. Click the **save icon** (floppy disk) → **"Save as new preset"** +5. Enter a name — e.g. `SUNLU PLA+ 2.0` + > This name must be entered in the bridge exactly as typed here. +6. Select printer: **Anycubic Kobra X 0.4 nozzle** — required for compatibility! +7. Click **Save** +8. **Restart OrcaSlicer once** — the `filament_id` is only written permanently after a restart. + +--- + +## 2. Verify the Unique ID + +After restarting, check that the ID was set correctly: + +**Windows:** +``` +%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json +``` + +**Linux:** +``` +~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json +``` + +Open the file and look for `filament_id`: + +```json +{ + "filament_id": "P3a7f2c1", + ... +} +``` + +✅ Correct: ID starts with `P` followed by 7 hex characters +❌ Missing or empty: Your OrcaSlicer-KX version is too old — update to v2.4.0-alpha-kx2 or newer + +--- + +## 3. Transfer a Preset to Another PC (Import) + +### Export (source PC) + +Simply copy the preset file: + +**Windows:** +``` +%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json +``` + +**Linux:** +``` +~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json +``` + +### Import (target PC) + +**Method A — Copy file directly:** +1. Copy the `.json` file to the same directory on the target PC +2. Restart OrcaSlicer → preset appears in the dropdown + +**Method B — OrcaSlicer import function:** +1. In OrcaSlicer: **File → Import → Import Configs...** +2. Select the `.json` file +3. Restart OrcaSlicer + +> **Note:** The `filament_id` inside the file is preserved — the preset will be recognized on the target PC exactly as on the source PC. + +--- + +## 4. Link the Preset in KX-Bridge + +1. Open the KX-Bridge UI +2. Go to **Filament Management** → select the AMS slot +3. In the **Filament Name** field, enter the OrcaSlicer preset name exactly: + ``` + SUNLU PLA+ 2.0 + ``` +4. Save + +The bridge sends `filament_name: "SUNLU PLA+ 2.0"` during sync → OrcaSlicer matches by name and `filament_id` → displays the preset correctly. + +--- + +## Quick Reference + +| What | Why | +|------|-----| +| Name in OrcaSlicer and Bridge must match **exactly** | Case and special characters are compared | +| Preset must be compatible with **Anycubic Kobra X 0.4 nozzle** | Select the correct printer when saving | +| **Restart OrcaSlicer** after saving for the first time | The `filament_id` is only written persistently after a restart | +| Use **OrcaSlicer-KX v2.4.0-alpha-kx2** or newer | Older versions do not generate a unique `filament_id` for user presets | + +--- +--- + +# Cómo crear, verificar e importar perfiles de filamento personalizados para KX-Bridge + +> **Requiere:** OrcaSlicer-KX v2.4.0-alpha-kx2 o superior + +--- + +## ¿Qué es el `filament_id` y por qué es importante? + +Cada perfil de filamento en OrcaSlicer tiene un `filament_id` interno. KX-Bridge usa este ID para asignar el perfil correcto durante la sincronización AMS. + +- Los perfiles del sistema (p. ej. "Polymaker PolyTerra PLA") tienen un ID fijo como `GFL99` o `OGFL04`. +- Los **perfiles personalizados (usuario)** reciben automáticamente un ID único que empieza por `P` (p. ej. `P3a7f2c1`) en OrcaSlicer-KX. + +Sin un ID único, OrcaSlicer mostrará siempre "Generic PLA" durante la sincronización, aunque el perfil exista. + +--- + +## 1. Crear un perfil de filamento personalizado + +1. Iniciar OrcaSlicer-KX +2. Seleccionar un perfil base adecuado en el **menú desplegable de filamento** (p. ej. "Generic PLA" o un perfil de fabricante) +3. Ajustar la configuración según sea necesario (temperaturas, refrigeración, etc.) +4. Hacer clic en el **icono de guardar** (disquete) → **"Save as new preset"** +5. Introducir un nombre — p. ej. `SUNLU PLA+ 2.0` + > Este nombre debe introducirse en la bridge exactamente igual. +6. Seleccionar impresora: **Anycubic Kobra X 0.4 nozzle** — ¡necesario para la compatibilidad! +7. Hacer clic en **Guardar** +8. **Reiniciar OrcaSlicer una vez** — el `filament_id` solo se escribe de forma permanente tras un reinicio. + +--- + +## 2. Verificar el ID único + +Tras reiniciar, comprobar que el ID se ha establecido correctamente: + +**Windows:** +``` +%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json +``` + +**Linux:** +``` +~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json +``` + +Abrir el archivo y buscar `filament_id`: + +```json +{ + "filament_id": "P3a7f2c1", + ... +} +``` + +✅ Correcto: el ID empieza por `P` seguido de 7 caracteres hexadecimales +❌ Falta o está vacío: la versión de OrcaSlicer-KX es demasiado antigua — actualizar a v2.4.0-alpha-kx2 o superior + +--- + +## 3. Transferir un perfil a otro PC (importar) + +### Exportar (PC de origen) + +Simplemente copiar el archivo del perfil: + +**Windows:** +``` +%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json +``` + +**Linux:** +``` +~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json +``` + +### Importar (PC de destino) + +**Método A — Copiar el archivo directamente:** +1. Copiar el archivo `.json` al mismo directorio en el PC de destino +2. Reiniciar OrcaSlicer → el perfil aparece en el menú desplegable + +**Método B — Función de importación de OrcaSlicer:** +1. En OrcaSlicer: **File → Import → Import Configs...** +2. Seleccionar el archivo `.json` +3. Reiniciar OrcaSlicer + +> **Nota:** El `filament_id` dentro del archivo se conserva — el perfil se reconocerá en el PC de destino exactamente igual que en el de origen. + +--- + +## 4. Vincular el perfil en KX-Bridge + +1. Abrir la interfaz de KX-Bridge +2. Ir a **Gestión de filamentos** → seleccionar la ranura AMS +3. En el campo **Nombre de filamento**, introducir el nombre exacto del perfil de OrcaSlicer: + ``` + SUNLU PLA+ 2.0 + ``` +4. Guardar + +La bridge envía `filament_name: "SUNLU PLA+ 2.0"` durante la sincronización → OrcaSlicer busca por nombre y `filament_id` → muestra el perfil correctamente. + +--- + +## Referencia rápida + +| Qué | Por qué | +|-----|---------| +| El nombre en OrcaSlicer y en Bridge debe coincidir **exactamente** | Se comparan mayúsculas, minúsculas y caracteres especiales | +| El perfil debe ser compatible con **Anycubic Kobra X 0.4 nozzle** | Seleccionar la impresora correcta al guardar | +| **Reiniciar OrcaSlicer** tras guardar por primera vez | El `filament_id` solo se escribe de forma permanente tras un reinicio | +| Usar **OrcaSlicer-KX v2.4.0-alpha-kx2** o superior | Las versiones anteriores no generan un `filament_id` único para perfiles de usuario | From 049109bc574b527565916201660381ed137ee423 Mon Sep 17 00:00:00 2001 From: thysson2701 Date: Tue, 9 Jun 2026 13:00:45 +0200 Subject: [PATCH 26/26] fix(preset): abgeleitete User-Presets bekommen eigene P-filament_id (Issue #52) save_current_preset() generierte eine P-ID nur bei leerem filament_id. Von Hersteller-Presets abgeleitete Presets erben aber dessen Vendor-ID (z.B. GFA001), wodurch sync_ams_list() auf das Vendor-Preset zurueckfiel statt das User-Preset zu waehlen. Bedingung an Preset::save() angeglichen: auch non-P IDs werden jetzt durch eine eigene P-ID ersetzt. Doku (DE/EN/ES) und Version auf 2.4.0-alpha-kx3 aktualisiert. Co-Authored-By: Claude Opus 4.8 --- docs/filament-preset-bridge-guide.md | 12 ++++++++++++ src/libslic3r/Preset.cpp | 2 +- version.inc | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/filament-preset-bridge-guide.md b/docs/filament-preset-bridge-guide.md index 30862e2aab..c2933f7b33 100644 --- a/docs/filament-preset-bridge-guide.md +++ b/docs/filament-preset-bridge-guide.md @@ -13,6 +13,9 @@ Jedes Filament-Preset in OrcaSlicer hat eine interne `filament_id`. Diese ID wir Ohne eindeutige ID zeigt OrcaSlicer beim Sync immer "Generic PLA" — auch wenn das Preset existiert. +> **Achtung — abgeleitete Presets (Issue #52):** Wenn du dein Preset von einem **Hersteller-Preset** ableitest (z.B. "Anycubic PLA Matte"), übernimmt es zunächst die feste Hersteller-ID (z.B. `GFA001`). Beim Sync wird dann fälschlicherweise das Hersteller-Preset statt deines eigenen ausgewählt. +> Ab OrcaSlicer-KX **v2.4.0-alpha-kx3** wird beim Speichern automatisch eine eigene `P...`-ID vergeben — auch für abgeleitete Presets. Hast du das Preset mit einer älteren Version angelegt, **öffne es einmal und speichere es erneut** (Save), damit die `P`-ID generiert wird. + --- ## 1. Eigenes Filament-Preset anlegen @@ -110,6 +113,7 @@ Die Bridge sendet beim Sync `filament_name: "SUNLU PLA+ 2.0"` → OrcaSlicer fin | Preset muss für **Anycubic Kobra X 0.4 nozzle** kompatibel sein | Beim Speichern den richtigen Drucker auswählen | | Nach dem ersten Speichern OrcaSlicer **neu starten** | Erst dann wird die `filament_id` persistent geschrieben | | **OrcaSlicer-KX v2.4.0-alpha-kx2** oder neuer verwenden | Ältere Versionen generieren keine eindeutige `filament_id` für User-Presets | +| Bei von Hersteller-Presets abgeleiteten Presets: **v2.4.0-alpha-kx3** oder neuer | Erst ab dieser Version wird die geerbte Hersteller-ID beim Speichern durch eine eigene `P`-ID ersetzt (Issue #52) | --- --- @@ -129,6 +133,9 @@ Every filament preset in OrcaSlicer has an internal `filament_id`. The KX-Bridge Without a unique ID, OrcaSlicer will always show "Generic PLA" during sync — even if the preset exists. +> **Caution — derived presets (Issue #52):** If you derive your preset from a **vendor preset** (e.g. "Anycubic PLA Matte"), it initially inherits the fixed vendor ID (e.g. `GFA001`). During sync the vendor preset is then incorrectly selected instead of your own. +> As of OrcaSlicer-KX **v2.4.0-alpha-kx3**, a unique `P...` ID is generated automatically on save — including for derived presets. If you created the preset with an older version, **open it once and save it again** so the `P` ID gets generated. + --- ## 1. Create a Custom Filament Preset @@ -226,6 +233,7 @@ The bridge sends `filament_name: "SUNLU PLA+ 2.0"` during sync → OrcaSlicer ma | Preset must be compatible with **Anycubic Kobra X 0.4 nozzle** | Select the correct printer when saving | | **Restart OrcaSlicer** after saving for the first time | The `filament_id` is only written persistently after a restart | | Use **OrcaSlicer-KX v2.4.0-alpha-kx2** or newer | Older versions do not generate a unique `filament_id` for user presets | +| For presets derived from vendor presets: **v2.4.0-alpha-kx3** or newer | Only from this version is the inherited vendor ID replaced with a unique `P` ID on save (Issue #52) | --- --- @@ -245,6 +253,9 @@ Cada perfil de filamento en OrcaSlicer tiene un `filament_id` interno. KX-Bridge Sin un ID único, OrcaSlicer mostrará siempre "Generic PLA" durante la sincronización, aunque el perfil exista. +> **Atención — perfiles derivados (Issue #52):** Si derivas tu perfil de un **perfil de fabricante** (p. ej. "Anycubic PLA Matte"), inicialmente hereda el ID fijo del fabricante (p. ej. `GFA001`). Durante la sincronización se selecciona entonces por error el perfil del fabricante en lugar del tuyo. +> A partir de OrcaSlicer-KX **v2.4.0-alpha-kx3**, se genera automáticamente un ID `P...` único al guardar — también para perfiles derivados. Si creaste el perfil con una versión anterior, **ábrelo una vez y vuelve a guardarlo** (Save) para que se genere el ID `P`. + --- ## 1. Crear un perfil de filamento personalizado @@ -342,3 +353,4 @@ La bridge envía `filament_name: "SUNLU PLA+ 2.0"` durante la sincronización | El perfil debe ser compatible con **Anycubic Kobra X 0.4 nozzle** | Seleccionar la impresora correcta al guardar | | **Reiniciar OrcaSlicer** tras guardar por primera vez | El `filament_id` solo se escribe de forma permanente tras un reinicio | | Usar **OrcaSlicer-KX v2.4.0-alpha-kx2** o superior | Las versiones anteriores no generan un `filament_id` único para perfiles de usuario | +| Para perfiles derivados de perfiles de fabricante: **v2.4.0-alpha-kx3** o superior | Solo a partir de esta versión se reemplaza el ID heredado del fabricante por un ID `P` único al guardar (Issue #52) | diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index af3bfc05e1..5f4c6f9ba2 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -2852,7 +2852,7 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det else if (m_type == Preset::TYPE_FILAMENT) { preset.config.option("filament_settings_id", true)->values[0] = new_name; // Generate a unique filament_id for user presets that don't have one yet (PR #13315). - if (preset.filament_id.empty()) { + if (preset.filament_id.empty() || preset.filament_id.front() != 'P') { boost::uuids::detail::md5 hash; boost::uuids::detail::md5::digest_type digest; hash.process_bytes(new_name.data(), new_name.size()); diff --git a/version.inc b/version.inc index d2f0fbe217..970274dc6f 100644 --- a/version.inc +++ b/version.inc @@ -7,7 +7,7 @@ set(SLIC3R_APP_KEY "OrcaSlicer") if(NOT DEFINED BBL_INTERNAL_TESTING) set(BBL_INTERNAL_TESTING "0") endif() -set(SoftFever_VERSION "2.4.0-alpha-kx2") +set(SoftFever_VERSION "2.4.0-alpha-kx3") string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" SoftFever_VERSION_MATCH ${SoftFever_VERSION}) set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1})