diff --git a/localization/i18n/OrcaSlicer.pot b/localization/i18n/OrcaSlicer.pot
index d71ad5aef1..c3ca1cbf96 100644
--- a/localization/i18n/OrcaSlicer.pot
+++ b/localization/i18n/OrcaSlicer.pot
@@ -18656,3 +18656,69 @@ msgstr ""
#, possible-c-format, possible-boost-format
msgid "The current nozzle mapping may produce an extra %0.2f g of waste."
msgstr ""
+
+msgid ""
+"Filament switcher detected. All AMS filaments are now available for both "
+"extruders. The slicer will auto-assign for optimal printing. "
+msgstr ""
+
+msgid ""
+"A filament switcher is detected but not calibrated and thus currently "
+"unavailable. Please calibrate it on the printer and synchronize before use. "
+msgstr ""
+
+msgid ""
+"The Filament Track Switch installed on the printer does not match the slicing "
+"file. Please re-slice to avoid print quality issues."
+msgstr ""
+
+msgid "This print requires a Filament Track Switch. Please install it first."
+msgstr ""
+
+msgid "The Filament Track Switch has not been setup. Please setup it first."
+msgstr ""
+
+msgid "\"Load\" or \"Unload\" is not supported for external spool while using Filament Track Switch."
+msgstr ""
+
+msgid "The Filament Track Switch has not been setup. Please setup on printer."
+msgstr ""
+
+msgid "Load %s to "
+msgstr ""
+
+msgid "AMS filaments"
+msgstr ""
+
+msgid "Fila Saving"
+msgstr ""
+
+msgid "Enable smart filament assign: Assign one filament to multiple nozzles to maximize savings"
+msgstr ""
+
+msgid "Filament Track Switch"
+msgstr ""
+
+msgid "AMS has not been initialized. Please initialize it before use."
+msgstr ""
+
+msgid "External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it."
+msgstr ""
+
+msgid "Switch"
+msgstr ""
+
+msgid "hotend"
+msgstr ""
+
+msgid "Wait for AMS cooling"
+msgstr ""
+
+msgid "Switch current filament at Filament Track Switch"
+msgstr ""
+
+msgid "Pull back current filament at Filament Track Switch"
+msgstr ""
+
+msgid "Switch track at Filament Track Switch"
+msgstr ""
diff --git a/resources/images/fila_switch.svg b/resources/images/fila_switch.svg
new file mode 100644
index 0000000000..f9a1ed93d4
--- /dev/null
+++ b/resources/images/fila_switch.svg
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/images/fila_switch_error.svg b/resources/images/fila_switch_error.svg
new file mode 100644
index 0000000000..e2be18ce39
--- /dev/null
+++ b/resources/images/fila_switch_error.svg
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/images/filament_track_switch.png b/resources/images/filament_track_switch.png
new file mode 100644
index 0000000000..fe64bc442e
Binary files /dev/null and b/resources/images/filament_track_switch.png differ
diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp
index 311451f774..5d151ca43f 100644
--- a/src/OrcaSlicer.cpp
+++ b/src/OrcaSlicer.cpp
@@ -1326,6 +1326,14 @@ int CLI::run(int argc, char **argv)
}
set_temporary_dir(temp_path);
+ // The Filament Track Switch flags are live-device state with no meaning in headless slicing;
+ // default both off unless explicitly provided on the command line, so an old 3MF that had
+ // them enabled still slices without the switch behavior.
+ if (!m_extra_config.has("has_filament_switcher"))
+ m_extra_config.set_key_value("has_filament_switcher", new ConfigOptionBool(false));
+ if (!m_extra_config.has("enable_filament_dynamic_map"))
+ m_extra_config.set_key_value("enable_filament_dynamic_map", new ConfigOptionBool(false));
+
m_extra_config.apply(m_config, true);
m_extra_config.normalize_fdm();
diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp
index 8ff9dcea32..df5b3f5380 100644
--- a/src/libslic3r/AppConfig.cpp
+++ b/src/libslic3r/AppConfig.cpp
@@ -304,6 +304,10 @@ void AppConfig::set_defaults()
if (get("show_3d_navigator").empty())
set_bool("show_3d_navigator", true);
+ // Show the one-time "Filament Track Switch is ready" tip until it has been seen once.
+ if (get("show_fila_switch_tips").empty())
+ set_bool("show_fila_switch_tips", true);
+
if (get("show_plate_gridlines").empty())
set_bool("show_plate_gridlines", true);
diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp
index 610ca41619..ea4ad5bc52 100644
--- a/src/libslic3r/PresetBundle.cpp
+++ b/src/libslic3r/PresetBundle.cpp
@@ -57,7 +57,12 @@ static std::vector s_project_options {
"prime_volume_mode",
"nozzle_volume_type",
"filament_map_mode",
- "filament_map"
+ "filament_map",
+ // Filament Track Switch device state: whether the switch is installed and ready, and
+ // whether dynamic per-nozzle filament mapping is active. Persisted with the project and
+ // restored from a saved 3mf; reset to false on load and set true only by live device sync.
+ "has_filament_switcher",
+ "enable_filament_dynamic_map"
};
//Orca: add custom as default
@@ -4323,6 +4328,10 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
};
clear_compatible_printers(config);
+ // Dynamic per-nozzle filament mapping reflects live device state, not a stored setting;
+ // drop it from any imported config so it only comes from the connected printer.
+ config.erase("enable_filament_dynamic_map");
+
#if 0
size_t num_extruders = (printer_technology == ptFFF) ?
std::min(config.option("nozzle_diameter" )->values.size(),
diff --git a/src/slic3r/GUI/AmsMappingPopup.cpp b/src/slic3r/GUI/AmsMappingPopup.cpp
index 241c2799a7..9a3b97b061 100644
--- a/src/slic3r/GUI/AmsMappingPopup.cpp
+++ b/src/slic3r/GUI/AmsMappingPopup.cpp
@@ -22,6 +22,7 @@
#include "Plater.hpp"
#include "BitmapCache.hpp"
#include "BindDialog.hpp"
+#include "slic3r/GUI/DeviceCore/DevFilaSwitch.h"
#include "DeviceCore/DevFilaSystem.h"
#include "DeviceCore/DevNozzleRack.h"
@@ -974,6 +975,8 @@ void AmsMapingPopup::on_left_down(wxMouseEvent &evt)
auto left = item->GetSize();
if (pos.x > p_rect.x && pos.y > p_rect.y && pos.x < (p_rect.x + item->GetSize().x) && pos.y < (p_rect.y + item->GetSize().y)) {
+ // Orca: the external spool is un-pickable while a Filament Track Switch is installed (Apple hit-tests here).
+ if (m_fila_switch_installed && (item->m_ams_id == VIRTUAL_TRAY_MAIN_ID || item->m_ams_id == VIRTUAL_TRAY_DEPUTY_ID)) { return; }
if (item->m_tray_data.type == TrayType::NORMAL) {
if (!m_ext_mapping_filatype_check && (item->m_ams_id == VIRTUAL_TRAY_MAIN_ID || item->m_ams_id == VIRTUAL_TRAY_DEPUTY_ID)) {
// Do nothing
@@ -1169,6 +1172,11 @@ void AmsMapingPopup::update(MachineObject* obj, const std::vector&
if (!obj) {return;}
m_ams_remain_detect_flag = obj->GetFilaSystem()->IsDetectRemainEnabled();
+ // Orca: with a Filament Track Switch installed, filament routes through the switch and the external
+ // spool cannot be used, so its mapping slots are rendered greyed-out and un-pickable. Inert (false)
+ // on any printer without a switch — GetFilaSwitch()->IsInstalled() defaults to false.
+ m_fila_switch_installed = obj->GetFilaSwitch()->IsInstalled();
+
for (auto& ams_container : m_amsmapping_container_list) {
ams_container->Destroy();
}
@@ -1546,6 +1554,20 @@ void AmsMapingPopup::add_ext_ams_mapping(TrayData tray_data, MappingItem* item)
#ifdef __APPLE__
m_mapping_item_list.push_back(item);
#endif
+
+ // Orca: a Filament Track Switch cannot route the external spool, so grey the ext slot out and make it
+ // un-pickable, with a tooltip explaining why (the click below is disabled; on_left_down also skips it).
+ if (m_fila_switch_installed) {
+ const wxString disp_name = (tray_data.type == THIRD) ? wxString("?")
+ : (tray_data.type == EMPTY) ? wxString("-")
+ : wxString(tray_data.name);
+ item->set_data(m_tag_material, wxColour(0xEE, 0xEE, 0xEE), disp_name, false, tray_data, true);
+ item->SetToolTip(_L("External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it."));
+ item->Bind(wxEVT_LEFT_DOWN, [](wxMouseEvent&) { /* un-pickable while the switch is installed */ });
+ item->set_tray_index(_L("Ext"));
+ return;
+ }
+
// set button
if (tray_data.type == NORMAL) {
if (is_match_material(tray_data.filament_type)) {
diff --git a/src/slic3r/GUI/AmsMappingPopup.hpp b/src/slic3r/GUI/AmsMappingPopup.hpp
index c264dadcbf..0850c0e728 100644
--- a/src/slic3r/GUI/AmsMappingPopup.hpp
+++ b/src/slic3r/GUI/AmsMappingPopup.hpp
@@ -244,6 +244,9 @@ public:
bool m_has_unmatch_filament {false};
int m_current_filament_id;
ShowType m_show_type{ShowType::RIGHT};
+ // Orca: set from update() — true when a Filament Track Switch is installed, which makes the
+ // external-spool slots un-pickable (false on any printer without a switch, so behavior is unchanged).
+ bool m_fila_switch_installed{false};
std::string m_tag_material;
wxBoxSizer *m_sizer_main{nullptr};
wxBoxSizer *m_sizer_ams{nullptr};
diff --git a/src/slic3r/GUI/DeviceCore/DevDefs.h b/src/slic3r/GUI/DeviceCore/DevDefs.h
index 204064645c..76a1d6843a 100644
--- a/src/slic3r/GUI/DeviceCore/DevDefs.h
+++ b/src/slic3r/GUI/DeviceCore/DevDefs.h
@@ -41,6 +41,31 @@ enum AmsStatusMain
AMS_STATUS_MAIN_UNKNOWN = 0xFF,
};
+// Device-numbered filament-change step codes. Newer firmware reports the exact change-step
+// sequence through the AMS (ams.cfs), keyed by these codes, instead of the client hardcoding
+// steps per model. Distinct from the legacy GUI-side Slic3r::GUI::FilamentStep enum.
+// STEP_CHECK_POSITION and STEP_CONFIRM_EXTRUDED share code 0x08 by device design.
+enum DevFilamentStep
+{
+ STEP_IDLE = 0x00,
+ STEP_PAUSE = 0x01,
+ STEP_HEAT_NOZZLE = 0x02,
+ STEP_CUT_FILAMENT = 0x03,
+ STEP_PULL_CURR_FILAMENT = 0x04,
+ STEP_PUSH_NEW_FILAMENT = 0x05,
+ STEP_GRAB_NEW_FILAMENT = 0x06,
+ STEP_PURGE_OLD_FILAMENT = 0x07,
+ STEP_CHECK_POSITION = 0x08,
+ STEP_SWITCH_EXTRUDER = 0x09,
+ STEP_SWITCH_HOTEND = 0x0A,
+ STEP_AMS_FILA_COOLING = 0x0B,
+ STEP_PUSH_SWITCHER_FILA = 0x0C,
+ STEP_PULL_SWITCHER_FILA = 0x0D,
+ STEP_SWITCHER_SWITCH = 0x0E,
+ STEP_CONFIRM_EXTRUDED = 0x08,
+ STEP_COUNT,
+};
+
// Slots and Tray
#define VIRTUAL_TRAY_MAIN_ID 255
#define VIRTUAL_TRAY_DEPUTY_ID 254
diff --git a/src/slic3r/GUI/DeviceCore/DevFilaSwitch.cpp b/src/slic3r/GUI/DeviceCore/DevFilaSwitch.cpp
index 7768030271..1f7a9a7623 100644
--- a/src/slic3r/GUI/DeviceCore/DevFilaSwitch.cpp
+++ b/src/slic3r/GUI/DeviceCore/DevFilaSwitch.cpp
@@ -9,6 +9,19 @@ namespace Slic3r {
DevFilaSwitch::DevFilaSwitch(MachineObject *owner) { m_owner = owner; }
+bool DevFilaSwitch::IsReady() const
+{
+ if (!m_is_installed) { return false; }
+
+ const auto& ams_list = m_owner->GetFilaSystem()->GetAmsList();
+ for (const auto& ams_item : ams_list) {
+ if (ams_item.second->GetBindedExtruderSet().empty()) { return false; }
+ if (!ams_item.second->GetSwitcherPos().has_value()) { return false; }
+ }
+
+ return true;
+}
+
void DevFilaSwitch::Reset()
{
m_is_installed = false;
diff --git a/src/slic3r/GUI/DeviceCore/DevFilaSwitch.h b/src/slic3r/GUI/DeviceCore/DevFilaSwitch.h
index 52c3a271b3..e18dea338c 100644
--- a/src/slic3r/GUI/DeviceCore/DevFilaSwitch.h
+++ b/src/slic3r/GUI/DeviceCore/DevFilaSwitch.h
@@ -49,6 +49,11 @@ public:
public:
bool IsInstalled() const { return m_is_installed; };
+ // Ready once installed and every AMS has resolved both its extruder binding and its
+ // switcher track position from the device. The send dialog and sidebar sync UX only
+ // treat the switch as usable when this is true.
+ bool IsReady() const;
+
std::optional IsInA_HasFilament() const { return m_in_a_has_filament; };
std::optional IsInB_HasFilament() const { return m_in_b_has_filament; };
@@ -67,12 +72,6 @@ public:
void Reset();
void ParseFilaSwitchInfo(const nlohmann::json& print_jj);
- // NOTE: an IsReady() accessor is intentionally omitted. It would walk
- // GetFilaSystem()->GetAmsList() calling GetBindedExtruderSet()/GetSwitcherPos(),
- // AMS filament-switch binding accessors Orca's DevFilaSystem does not yet carry.
- // It is unused by the filament blacklist; port it alongside the filament-switch
- // device-sync UI that needs those accessors.
-
private:
MachineObject* m_owner;
diff --git a/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp b/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp
index 2202a7d689..fccc31c47b 100644
--- a/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp
+++ b/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp
@@ -346,6 +346,15 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
if (!key_field_only)
{
+ // Newer firmware sends the exact filament-change step sequence in ams.cfs, so the
+ // client no longer hardcodes the steps per model. Absent on current firmware, which
+ // keeps the list empty and the legacy hardcoded/ams_status_sub step path in effect.
+ if (jj["ams"].contains("cfs")) {
+ system->m_filament_change_steps = DevJsonValParser::GetVal>(jj["ams"], "cfs");
+ } else {
+ system->m_filament_change_steps.clear();
+ }
+
if (jj["ams"].contains("tray_read_done_bits"))
{
obj->tray_read_done_bits = stol(jj["ams"]["tray_read_done_bits"].get(), nullptr, 16);
@@ -403,17 +412,43 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
int type_id = 1; // 0:dummy 1:ams 2:ams-lite 3:n3f 4:n3s
/*ams info*/
+ std::set binded_extruder_set;
+ std::optional binded_switcher_pos;
if (it->contains("info")) {
const std::string& info = (*it)["info"].get();
type_id = DevUtil::get_flag_bits(info, 0, 4);
extuder_id = DevUtil::get_flag_bits(info, 8, 4);
+ if (extuder_id == 0xE && obj->GetFilaSwitch()->IsInstalled()) {
+ int bind_switch_in = DevUtil::get_flag_bits(info, 24, 4);
+ if (bind_switch_in == 0 || bind_switch_in == 1) {
+ binded_extruder_set = { MAIN_EXTRUDER_ID, DEPUTY_EXTRUDER_ID };
+ }
+
+ if (bind_switch_in == 0) {
+ binded_switcher_pos = DevFilaSwitch::SwitchPos::POS_IN_B;
+ } else if (bind_switch_in == 1) {
+ binded_switcher_pos = DevFilaSwitch::SwitchPos::POS_IN_A;
+ }
+
+ // Orca: the switch feeds every AMS to both extruders; pin a deterministic
+ // single-extruder id so legacy GetExtruderId() consumers keep a valid value
+ // while switch-aware code reads the binding set instead.
+ extuder_id = MAIN_EXTRUDER_ID;
+ } else if (extuder_id != 0xE) {
+ binded_extruder_set = { extuder_id };
+ }
} else {
if (!obj->is_enable_ams_np && obj->get_printer_ams_type() == "f1") {
type_id = DevAms::AMS_LITE;
}
+ binded_extruder_set = { MAIN_EXTRUDER_ID };
}
/*AMS without initialization*/
+ // Orca: an AMS reporting extruder 0xE without a Filament Track Switch installed has
+ // no usable extruder binding; drop it, preserving the existing display for
+ // half-initialized printers. With the switch installed the 0xE case is remapped
+ // above to both extruders and falls through to normal handling.
if (extuder_id == 0xE)
{
ams_id_set.erase(ams_id);
@@ -445,6 +480,13 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
/*set ams type flag*/
curr_ams->SetAmsType(type_id);
+ // Refresh the switch-aware extruder binding on every push (both create and
+ // update paths) so it can't go stale after an AMS is re-homed to another
+ // extruder. Without the switch the set holds the single bound extruder and
+ // the track position stays empty.
+ curr_ams->m_binded_extruder_set = binded_extruder_set;
+ curr_ams->m_binded_switcher_pos = binded_switcher_pos;
+
/*set ams exist flag*/
try
diff --git a/src/slic3r/GUI/DeviceCore/DevFilaSystem.h b/src/slic3r/GUI/DeviceCore/DevFilaSystem.h
index 8401498a37..604f228c13 100644
--- a/src/slic3r/GUI/DeviceCore/DevFilaSystem.h
+++ b/src/slic3r/GUI/DeviceCore/DevFilaSystem.h
@@ -4,9 +4,12 @@
#include "DevDefs.h"
#include "DevFilaAmsSetting.h"
+#include "DevFilaSwitch.h"
#include "DevUtil.h"
#include
+#include
+#include
#include
#include
#include
@@ -169,6 +172,20 @@ public:
// installed on the extruder
int GetExtruderId() const { return m_ext_id; }
+ // Extruders this AMS can currently feed. Without a Filament Track Switch installed this
+ // is the single {m_ext_id}; with the switch installed the device binds it to both extruders.
+ const std::set& GetBindedExtruderSet() const { return m_binded_extruder_set; }
+
+ // The bound extruder when exactly one is bound, empty otherwise. Used to attribute an AMS
+ // to a single extruder on printers without the switch.
+ std::optional GetUniqueBindedExtruderId() const
+ {
+ return m_binded_extruder_set.size() == 1 ? std::optional(*m_binded_extruder_set.begin()) : std::nullopt;
+ }
+
+ // Which switch input track (A/B) feeds this AMS, set only when a Filament Track Switch is installed.
+ std::optional GetSwitcherPos() const { return m_binded_switcher_pos; }
+
// temperature and humidity
float GetCurrentTemperature() const { return m_current_temperature; }
@@ -185,6 +202,12 @@ private:
AmsType m_ams_type = AmsType::AMS;
std::string m_ams_id;
int m_ext_id;//extruder id
+ // Orca: keeps the legacy single m_ext_id for existing consumers and carries the
+ // switch-aware binding alongside it (BambuStudio stores only the set). Without a Filament
+ // Track Switch the set is {m_ext_id}; with one installed the device binds both extruders and
+ // records which input track (A/B) feeds this AMS.
+ std::set m_binded_extruder_set;
+ std::optional m_binded_switcher_pos;
bool m_exist = false;
// slots and trays
@@ -268,6 +291,11 @@ public:
std::weak_ptr GetAmsFirmwareSwitch() const { return m_ams_firmware_switch;}
+ // filament change steps
+ // The exact change-step sequence reported by the AMS (ams.cfs). Empty on current firmware,
+ // which leaves the legacy hardcoded/ams_status_sub step handling in effect.
+ const std::vector& GetFilamentChangeSteps() const { return m_filament_change_steps; }
+
public:
// ctrls
int CtrlAmsReset() const;
@@ -281,6 +309,9 @@ private:
/* ams properties */
int m_ams_cali_stat = 0;
+ // Change-step sequence reported by the AMS (ams.cfs); empty when firmware doesn't send it.
+ std::vector m_filament_change_steps;
+
std::map amsList;// key: ams[id], start with 0
DevAmsSystemSetting m_ams_system_setting{ this };
diff --git a/src/slic3r/GUI/DeviceCore/DevFirmware.h b/src/slic3r/GUI/DeviceCore/DevFirmware.h
index 2339ae9e3a..eff34db0ea 100644
--- a/src/slic3r/GUI/DeviceCore/DevFirmware.h
+++ b/src/slic3r/GUI/DeviceCore/DevFirmware.h
@@ -45,6 +45,7 @@ public:
bool isCuttingModule() const { return product_name.Contains("Cutting Module"); }
bool isExtinguishSystem() const { return product_name.Contains("Extinguishing System"); }
bool isWTM() const { return name.find("wtm") != std::string::npos; } // nozzle (rack hotend) module firmware
+ bool isFilaTrackSwitch() const { return product_name.Contains("Filament Track"); }
};
diff --git a/src/slic3r/GUI/DeviceManager.cpp b/src/slic3r/GUI/DeviceManager.cpp
index ddf6166d11..527fe4f372 100644
--- a/src/slic3r/GUI/DeviceManager.cpp
+++ b/src/slic3r/GUI/DeviceManager.cpp
@@ -869,6 +869,7 @@ void MachineObject::clear_version_info()
laser_version_info = DevFirmwareVersionInfo();
cutting_module_version_info = DevFirmwareVersionInfo();
extinguish_version_info = DevFirmwareVersionInfo();
+ filatrack_version_info = DevFirmwareVersionInfo();
module_vers.clear();
// Drop cached rack-hotend (WTM) firmware alongside the module list.
// Inert for non-rack printers (rack firmware map is empty).
@@ -890,6 +891,8 @@ void MachineObject::store_version_info(const DevFirmwareVersionInfo& info)
// the rack upgrade UI can read per-nozzle versions. isWTM() is false for every non-rack
// printer's modules, so this branch never fires outside H2C.
m_nozzle_system->AddFirmwareInfoWTM(info);
+ } else if (info.isFilaTrackSwitch()) {
+ filatrack_version_info = info;
}
module_vers.emplace(info.name, info);
@@ -1572,7 +1575,7 @@ int MachineObject::check_resume_condition()
}
return 0;
}
-int MachineObject::command_ams_change_filament(bool load, std::string ams_id, std::string slot_id, int old_temp, int new_temp)
+int MachineObject::command_ams_change_filament(bool load, std::string ams_id, std::string slot_id, int old_temp, int new_temp, std::optional extruder_id)
{
json j;
try {
@@ -1604,6 +1607,13 @@ int MachineObject::command_ams_change_filament(bool load, std::string ams_id, st
j["print"]["slot_id"] = atoi(slot_id.c_str());
}
+ // Filament Track Switch: route the load to the chosen extruder. Only present when the
+ // caller supplied a value (FTS-installed+ready path), so every other caller is unchanged.
+ if (extruder_id.has_value())
+ {
+ j["print"]["extruder_id"] = *extruder_id;
+ }
+
} catch (const std::exception &) {}
return this->publish_json(j);
}
@@ -5085,6 +5095,7 @@ void MachineObject::parse_new_info(json print)
// fun2 may have infinite length, use get_flag_bits_no_border
if (!fun2.empty()) {
is_support_print_with_emmc = get_flag_bits_no_border(fun2, 0) == 1;
+ is_support_check_track_switch_match_slice_printer = get_flag_bits_no_border(fun2, 19) == 1;
}
/*aux*/
diff --git a/src/slic3r/GUI/DeviceManager.hpp b/src/slic3r/GUI/DeviceManager.hpp
index 9bb00dfcab..773bdc200b 100644
--- a/src/slic3r/GUI/DeviceManager.hpp
+++ b/src/slic3r/GUI/DeviceManager.hpp
@@ -8,6 +8,7 @@
#include
#include
#include
+#include
#include
#include
#include "nlohmann/json.hpp"
@@ -378,6 +379,7 @@ public:
DevFirmwareVersionInfo laser_version_info;
DevFirmwareVersionInfo cutting_module_version_info;
DevFirmwareVersionInfo extinguish_version_info;
+ DevFirmwareVersionInfo filatrack_version_info;
std::map module_vers;
std::map new_ver_list;
bool m_new_ver_list_exist = false;
@@ -633,6 +635,7 @@ public:
// fun2
bool is_support_print_with_emmc{false};
+ bool is_support_check_track_switch_match_slice_printer{false};
bool installed_upgrade_kit{false};
int bed_temperature_limit = -1;
@@ -746,7 +749,7 @@ public:
int check_resume_condition();
// ams controls
//int command_ams_switch(int tray_index, int old_temp = 210, int new_temp = 210);
- int command_ams_change_filament(bool load, std::string ams_id, std::string slot_id, int old_temp = 210, int new_temp = 210);
+ int command_ams_change_filament(bool load, std::string ams_id, std::string slot_id, int old_temp = 210, int new_temp = 210, std::optional extruder_id = std::nullopt);
int command_ams_user_settings(bool start_read_opt, bool tray_read_opt, bool remain_flag = false);
int command_ams_switch_filament(bool switch_filament);
int command_ams_air_print_detect(bool air_print_detect);
diff --git a/src/slic3r/GUI/FilamentGroupPopup.cpp b/src/slic3r/GUI/FilamentGroupPopup.cpp
index a29e4537fb..e5e49928dc 100644
--- a/src/slic3r/GUI/FilamentGroupPopup.cpp
+++ b/src/slic3r/GUI/FilamentGroupPopup.cpp
@@ -174,6 +174,9 @@ FilamentGroupPopup::FilamentGroupPopup(wxWindow *parent) : PopupWindow(parent, w
button_labels[idx]->Bind(wxEVT_LEAVE_WINDOW, [this](auto &) { UpdateButtonStatus(); });
}
+ // Smart filament assign section
+ MakeSmartFilamentSection(top_sizer, horizontal_margin, vertical_padding);
+
{
wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL);
// ORCA Unified hyperlinks
@@ -257,6 +260,7 @@ void FilamentGroupPopup::Init()
SetFilamentMapMode(m_mode);
}
+ UpdateSmartFilamentSection();
UpdateButtonStatus();
GUI::wxGetApp().UpdateDarkUIWin(this);
}
@@ -408,4 +412,65 @@ void FilamentGroupPopup::UpdateButtonStatus(int hover_idx)
Fit();
}
+void FilamentGroupPopup::MakeSmartFilamentSection(wxSizer *top_sizer, int horizontal_margin, int vertical_padding)
+{
+ m_smart_filament_panel = new StaticBox(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0);
+ m_smart_filament_panel->SetCornerRadius(FromDIP(4));
+ m_smart_filament_panel->SetBorderWidth(FromDIP(1));
+ m_smart_filament_panel->SetBorderColor(wxColour("#CECECE"));
+ m_smart_filament_panel->SetBackgroundColor(StateColor(std::pair(wxColour("#F8F8F8"), StateColor::Normal)));
+
+ auto *label = new Label(m_smart_filament_panel, _L("Enable smart filament assign: Assign one filament to multiple nozzles to maximize savings"));
+ label->SetFont(Label::Body_12);
+ label->SetForegroundColour(GreyColor);
+ label->SetBackgroundColour(wxColour("#F8F8F8"));
+ label->Wrap(FromDIP(240));
+
+ m_smart_filament_switch = new SwitchButton(m_smart_filament_panel);
+ m_smart_filament_switch->Bind(wxEVT_TOGGLEBUTTON, &FilamentGroupPopup::OnSmartFilamentToggle, this);
+#ifdef __WXOSX__
+ // wxEVT_TOGGLEBUTTON event not handled well by PopupWindow on MacOS
+ // we bind a wxEVT_LEFT_DOWN event as a workaround
+ m_smart_filament_switch->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &) {
+ wxCommandEvent evt(wxEVT_TOGGLEBUTTON);
+ evt.SetInt(!m_smart_filament_switch->GetValue());
+ m_smart_filament_switch->Command(evt);
+ });
+#endif
+
+ auto *panel_sizer = new wxBoxSizer(wxHORIZONTAL);
+ panel_sizer->Add(label, 1, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(10));
+ panel_sizer->Add(m_smart_filament_switch, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(10));
+ m_smart_filament_panel->SetSizer(panel_sizer);
+
+ top_sizer->Add(m_smart_filament_panel, 0, wxEXPAND | wxLEFT | wxRIGHT, horizontal_margin);
+ m_smart_filament_spacer = top_sizer->AddSpacer(vertical_padding);
+
+ // Hidden by default; shown in Init() when the filament track switch is ready
+ m_smart_filament_panel->Show(false);
+ m_smart_filament_spacer->Show(false);
+}
+
+void FilamentGroupPopup::OnSmartFilamentToggle(wxCommandEvent &event)
+{
+ auto &config = wxGetApp().preset_bundle->project_config;
+ auto *dynamic_filament = dynamic_cast(config.option("enable_filament_dynamic_map"));
+ if (dynamic_filament) { dynamic_filament->value = m_smart_filament_switch->GetValue(); }
+ plater_ref->update();
+ event.Skip();
+}
+
+void FilamentGroupPopup::UpdateSmartFilamentSection()
+{
+ bool show = wxGetApp().sidebar().is_fila_switch_ready();
+ m_smart_filament_panel->Show(show);
+ m_smart_filament_spacer->Show(show);
+
+ if (show) {
+ auto &config = wxGetApp().preset_bundle->project_config;
+ auto *dynamic_filament = dynamic_cast(config.option("enable_filament_dynamic_map"));
+ if (dynamic_filament) { m_smart_filament_switch->SetValue(dynamic_filament->value); }
+ }
+}
+
}} // namespace Slic3r::GUI
\ No newline at end of file
diff --git a/src/slic3r/GUI/FilamentGroupPopup.hpp b/src/slic3r/GUI/FilamentGroupPopup.hpp
index 2355b13e9e..6d0bd43da7 100644
--- a/src/slic3r/GUI/FilamentGroupPopup.hpp
+++ b/src/slic3r/GUI/FilamentGroupPopup.hpp
@@ -3,10 +3,13 @@
#include
#include
+#include
#include
#include "libslic3r/PrintConfig.hpp"
#include "Widgets/PopupWindow.hpp"
#include "Widgets/Label.hpp"
+#include "Widgets/SwitchButton.hpp"
+#include "Widgets/StaticBox.hpp"
namespace Slic3r { namespace GUI {
@@ -42,6 +45,10 @@ private:
void Init();
void UpdateButtonStatus(int hover_idx = -1);
void DrawRoundedCorner(int radius);
+
+ void MakeSmartFilamentSection(wxSizer *top_sizer, int horizontal_margin, int vertical_padding);
+ void UpdateSmartFilamentSection();
+ void OnSmartFilamentToggle(wxCommandEvent &event);
private:
FilamentMapMode GetFilamentMapMode() const;
void SetFilamentMapMode(const FilamentMapMode mode);
@@ -75,6 +82,10 @@ private:
wxStaticText *wiki_link;
wxStaticText *video_link;
+ StaticBox *m_smart_filament_panel{nullptr};
+ wxSizerItem *m_smart_filament_spacer{nullptr};
+ SwitchButton *m_smart_filament_switch{nullptr};
+
PartPlate* partplate_ref{ nullptr };
Plater* plater_ref{ nullptr };
};
diff --git a/src/slic3r/GUI/FilamentMapDialog.cpp b/src/slic3r/GUI/FilamentMapDialog.cpp
index 72428dae02..19b48ce5f1 100644
--- a/src/slic3r/GUI/FilamentMapDialog.cpp
+++ b/src/slic3r/GUI/FilamentMapDialog.cpp
@@ -39,6 +39,68 @@ static std::vector get_applied_map(DynamicConfig& proj_config, const Plater
extern std::string& get_left_extruder_unprintable_text();
extern std::string& get_right_extruder_unprintable_text();
+// Orca: minimal smart-filament toggle. When a filament track switch is ready every AMS filament is
+// reachable from both nozzles, so one filament can be assigned to multiple nozzles to maximize
+// savings. The checkbox drives the enable_filament_dynamic_map project flag.
+class SmartFilamentPanel : public wxPanel
+{
+ static constexpr int spacing = 20;
+
+public:
+ SmartFilamentPanel(wxWindow *parent) : wxPanel(parent)
+ {
+ SetBackgroundColour(*wxWHITE);
+ wxBoxSizer *main_sizer = new wxBoxSizer(wxVERTICAL);
+
+ main_sizer->AddSpacer(FromDIP(spacing));
+
+ auto *separator = new wxPanel(this);
+ separator->SetBackgroundColour(wxColour("#EEEEEE"));
+ main_sizer->Add(separator, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(15));
+
+ main_sizer->AddSpacer(FromDIP(spacing));
+
+ m_smart_filament_checkbox = new CheckBox(this);
+ if (auto *opt = enable_filament_dynamic_map())
+ m_smart_filament_checkbox->SetValue(opt->value);
+ m_smart_filament_checkbox->Bind(wxEVT_TOGGLEBUTTON, &SmartFilamentPanel::on_smart_filament_checkbox, this);
+
+ auto *label = new Label(this, _L("Enable smart filament assign: Assign one filament to multiple nozzles to maximize savings"));
+ label->SetFont(Label::Body_12);
+
+ // Orca: dropped the vendor "Learn more" tracking link (no Orca help page for this feature).
+
+ auto *smart_sizer = new wxBoxSizer(wxHORIZONTAL);
+ smart_sizer->Add(m_smart_filament_checkbox, 0, wxALIGN_CENTER_VERTICAL);
+ smart_sizer->Add(label, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(3));
+
+ main_sizer->Add(smart_sizer, 0, wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(15));
+
+ SetSizer(main_sizer);
+ Layout();
+ Fit();
+ wxGetApp().UpdateDarkUIWin(this);
+ }
+
+private:
+ static ConfigOptionBool *enable_filament_dynamic_map()
+ {
+ auto &config = wxGetApp().preset_bundle->project_config;
+ return dynamic_cast(config.option("enable_filament_dynamic_map"));
+ }
+
+ void on_smart_filament_checkbox(wxCommandEvent &event)
+ {
+ if (auto *opt = enable_filament_dynamic_map())
+ opt->value = m_smart_filament_checkbox->GetValue();
+ wxGetApp().plater()->update();
+ event.Skip();
+ }
+
+private:
+ CheckBox *m_smart_filament_checkbox{nullptr};
+};
+
bool try_pop_up_before_slice(bool is_slice_all, Plater* plater_ref, PartPlate* partplate_ref, bool force_pop_up)
{
@@ -136,6 +198,14 @@ FilamentMapDialog::FilamentMapDialog(wxWindow *parent,
SetMinSize(wxSize(FromDIP(580), -1));
SetMaxSize(wxSize(FromDIP(580), -1));
+ // Orca: when a filament track switch is ready, every AMS filament reaches both nozzles, so the
+ // Match/Convenience sub-mode is dropped and Auto is presented purely as a filament-saving mode.
+ // Orca has no fmmAutoForQuality, so "only saving" collapses to "switch ready" and the remaining
+ // auto mode set is { fmmAutoForFlush }.
+ m_fila_switch_ready = wxGetApp().sidebar().is_fila_switch_ready();
+ const bool only_saving_mode = m_fila_switch_ready;
+ const bool auto_match_available = machine_synced && !m_fila_switch_ready;
+
if (mode < fmmManual)
m_page_type = PageType::ptAuto;
else if (mode == fmmManual || mode == fmmNozzleManual)
@@ -149,7 +219,7 @@ FilamentMapDialog::FilamentMapDialog(wxWindow *parent,
wxBoxSizer *mode_sizer = new wxBoxSizer(wxHORIZONTAL);
- m_auto_btn = new CapsuleButton(this, PageType::ptAuto, _L("Auto"), false);
+ m_auto_btn = new CapsuleButton(this, PageType::ptAuto, only_saving_mode ? _L("Fila Saving") : _L("Auto"), false);
m_manual_btn = new CapsuleButton(this, PageType::ptManual, _L("Custom"), false);
if (show_default)
m_default_btn = new CapsuleButton(this, PageType::ptDefault, _L("Same as Global"), true);
@@ -168,12 +238,14 @@ FilamentMapDialog::FilamentMapDialog(wxWindow *parent,
auto panel_sizer = new wxBoxSizer(wxHORIZONTAL);
+ // Orca: fall back to the saving mode whenever Match is unavailable, which now also covers the
+ // filament-track-switch-ready case (auto_match_available folds in !m_fila_switch_ready).
FilamentMapMode default_auto_mode = mode >= fmmManual ? fmmAutoForFlush :
- mode == fmmAutoForMatch && !machine_synced ? fmmAutoForFlush :
+ mode == fmmAutoForMatch && !auto_match_available ? fmmAutoForFlush :
mode;
m_manual_map_panel = new FilamentMapManualPanel(this, m_filament_color, m_filament_type, filaments, filament_map);
- m_auto_map_panel = new FilamentMapAutoPanel(this, default_auto_mode, machine_synced);
+ m_auto_map_panel = new FilamentMapAutoPanel(this, default_auto_mode, auto_match_available);
if (show_default)
m_default_map_panel = new FilamentMapDefaultPanel(this);
else
@@ -184,6 +256,13 @@ FilamentMapDialog::FilamentMapDialog(wxWindow *parent,
if (show_default) panel_sizer->Add(m_default_map_panel, 0, wxEXPAND);
main_sizer->Add(panel_sizer, 0, wxEXPAND);
+ // Smart filament section, shown only in filament-saving (flush) mode when the switch is ready.
+ if (m_fila_switch_ready) {
+ m_smart_filament = new SmartFilamentPanel(this);
+ m_smart_filament->Show(get_mode() == fmmAutoForFlush);
+ main_sizer->Add(m_smart_filament, 0, wxEXPAND);
+ }
+
wxPanel* bottom_panel = new wxPanel(this);
bottom_panel->SetBackgroundColour(*wxWHITE);
wxBoxSizer *bottom_sizer = new wxBoxSizer(wxHORIZONTAL);
@@ -319,6 +398,9 @@ void FilamentMapDialog::update_panel_status(PageType page)
m_auto_map_panel->Show();
}
+ if (m_smart_filament)
+ m_smart_filament->Show(get_mode() == fmmAutoForFlush);
+
Layout();
Fit();
}
diff --git a/src/slic3r/GUI/FilamentMapDialog.hpp b/src/slic3r/GUI/FilamentMapDialog.hpp
index 3272f203b3..c0692ec081 100644
--- a/src/slic3r/GUI/FilamentMapDialog.hpp
+++ b/src/slic3r/GUI/FilamentMapDialog.hpp
@@ -15,6 +15,7 @@ namespace GUI {
class DragDropPanel;
class Plater;
class PartPlate;
+class SmartFilamentPanel;
/**
* @brief Try to pop up the filament map dialog before slicing.
@@ -78,6 +79,9 @@ private:
Button* m_ok_btn;
Button* m_cancel_btn;
CheckBox* m_checkbox;
+ SmartFilamentPanel* m_smart_filament{nullptr};
+
+ bool m_fila_switch_ready{false};
PageType m_page_type;
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index e0545a83e9..c5524ab7f0 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -564,6 +564,9 @@ struct Sidebar::priv
ScalableButton* m_printer_setting = nullptr;
wxStaticText * m_text_printer_settings = nullptr;
wxPanel* m_panel_printer_content = nullptr;
+ // Filament Track Switch status overlay: an icon floated over the left/single extruder AMS area,
+ // shown only when the switch is installed (green when ready, red when not calibrated).
+ wxStaticBitmap* extruder_separator_icon = nullptr;
ObjectList *m_object_list{ nullptr };
ObjectSettings *object_settings{ nullptr };
@@ -594,6 +597,19 @@ struct Sidebar::priv
bool switch_diameter(bool single);
void update_sync_status(const MachineObject* obj);
+ // Filament Track Switch (H2-family accessory): true only when the connected printer is the
+ // selected one, online, and reports the switch installed and calibrated.
+ bool is_fila_switch_ready();
+ // One-time info tip when the switch is ready, and a not-calibrated warning otherwise.
+ void show_filament_switcher_dialog(bool is_ready, bool is_manual);
+ void show_fila_switch_msg(bool ready);
+ bool fila_switch_warning_shown = false;
+ // Show/hide and reposition the switcher status icon; caches the last state to avoid churn.
+ void update_extruder_separator_icon(bool show, bool ready);
+ // Orca: BBS resets the switcher UI from DeviceManager::OnSelectedMachineChanged, which Orca lacks.
+ // Track the last-synced device id here so update_sync_status can detect a machine change.
+ std::string last_sync_dev_id;
+
#ifdef _WIN32
wxString btn_reslice_tip;
void show_rich_tip(const wxString& tooltip, wxButton* btn);
@@ -642,6 +658,18 @@ void Sidebar::priv::layout_printer(bool isBBL, bool isDual)
extruder_dual_sizer->AddSpacer(FromDIP(4));
extruder_dual_sizer->Add(right_extruder->sizer, 1, wxEXPAND, 0);
+ // Filament Track Switch status icon, floated over the extruder AMS area (positioned in
+ // update_extruder_separator_icon). Created hidden; a click re-shows the ready/not-ready tip.
+ if (!extruder_separator_icon) {
+ auto bitmap = ScalableBitmap(m_panel_printer_content, "fila_switch", 10);
+ extruder_separator_icon = new wxStaticBitmap(m_panel_printer_content, wxID_ANY, bitmap.bmp(), wxDefaultPosition, bitmap.GetBmpSize());
+ extruder_separator_icon->Hide();
+ extruder_separator_icon->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& evt) {
+ show_fila_switch_msg(is_fila_switch_ready());
+ evt.Skip();
+ });
+ }
+
// single
extruder_single_sizer = single_extruder->sizer;
wxBoxSizer * extruder_sizer = new wxBoxSizer(wxVERTICAL);
@@ -1610,6 +1638,105 @@ std::optional Sidebar::priv::get_nozzle_options(MachineObject* obj
return nozzle_option;
}
+bool Sidebar::priv::is_fila_switch_ready()
+{
+ if (!wxGetApp().plater()->is_same_printer_for_connected_and_selected(false))
+ return false;
+ auto device_manager = wxGetApp().getDeviceManager();
+ if (device_manager == nullptr)
+ return false;
+ auto obj = device_manager->get_selected_machine();
+ if (obj == nullptr || !obj->is_online())
+ return false;
+ auto fila_switch = obj->GetFilaSwitch();
+ if (fila_switch == nullptr)
+ return false;
+ return fila_switch->IsInstalled() && fila_switch->IsReady();
+}
+
+void Sidebar::priv::show_fila_switch_msg(bool ready)
+{
+ wxString msg = ready ? _L("Filament switcher detected. All AMS filaments are now available for both extruders. "
+ "The slicer will auto-assign for optimal printing. ") :
+ _L("A filament switcher is detected but not calibrated and thus currently unavailable. "
+ "Please calibrate it on the printer and synchronize before use. ");
+
+ long style = ready ? (wxICON_INFORMATION | wxOK) : (wxICON_WARNING | wxOK);
+ // Orca: drop the vendor "Learn more" tracking link; there is no Orca help page for the switch yet.
+ MessageDialog dlg(static_cast(wxGetApp().mainframe), msg, _L("Tips"), style);
+ dlg.CenterOnParent();
+ dlg.ShowModal();
+}
+
+void Sidebar::priv::show_filament_switcher_dialog(bool is_ready, bool is_manual)
+{
+ if (is_ready) {
+ // Show the "switch is ready" tip only once, ever.
+ if (wxGetApp().app_config->get("show_fila_switch_tips") == "true") {
+ wxGetApp().app_config->set("show_fila_switch_tips", "false");
+ show_fila_switch_msg(true);
+ }
+ } else {
+ // A manual sync always warns; an automatic update warns once per session.
+ if (is_manual || !fila_switch_warning_shown) {
+ fila_switch_warning_shown = true;
+ show_fila_switch_msg(false);
+ }
+ }
+}
+
+void Sidebar::priv::update_extruder_separator_icon(bool show, bool ready)
+{
+ if (!extruder_separator_icon)
+ return;
+ static bool last_show = false;
+ static bool last_ready = false;
+ static bool first_call = true;
+
+ if (!first_call && last_show == show && last_ready == ready)
+ return;
+ first_call = false;
+ last_show = show;
+ last_ready = ready;
+
+ // Never overlay the icon when the connected printer is not the selected preset.
+ if (!wxGetApp().plater()->is_same_printer_for_connected_and_selected(false)) {
+ extruder_separator_icon->Hide();
+ if (m_panel_printer_content)
+ m_panel_printer_content->Refresh();
+ return;
+ }
+
+ if (show && left_extruder && left_extruder->hsizer_ams && left_extruder->sizer) {
+ // Orca: center the icon over the seam between the two extruder cards, vertically aligned with
+ // the AMS row. left_extruder and the icon share m_panel_printer_content as parent, so
+ // left_extruder->GetPosition() and the icon position live in the same coordinate space.
+ wxPoint left_box_pos = left_extruder->GetPosition();
+ wxPoint ams_local_pos = left_extruder->hsizer_ams->GetPosition();
+ wxSize left_size = left_extruder->sizer->GetSize();
+ wxSize ams_size = left_extruder->hsizer_ams->GetSize();
+ wxSize icon_size = extruder_separator_icon->GetSize();
+ int ams_abs_y = left_box_pos.y + ams_local_pos.y + FromDIP(4);
+ int center_x = left_size.GetWidth() + FromDIP(6);
+ int center_y = ams_abs_y + (ams_size.GetHeight() - icon_size.GetHeight()) / 2;
+ center_x -= icon_size.GetWidth() / 2;
+ center_y -= icon_size.GetHeight() / 2;
+ extruder_separator_icon->SetPosition(wxPoint(center_x, center_y));
+
+ auto normal_bitmap = ScalableBitmap(m_panel_printer_content, "fila_switch", 10);
+ auto error_bitmap = ScalableBitmap(m_panel_printer_content, "fila_switch_error", 10);
+ extruder_separator_icon->SetBitmap(ready ? normal_bitmap.bmp() : error_bitmap.bmp());
+
+ extruder_separator_icon->Show();
+ extruder_separator_icon->Raise();
+ } else {
+ extruder_separator_icon->Hide();
+ }
+
+ if (m_panel_printer_content)
+ m_panel_printer_content->Refresh();
+}
+
bool Sidebar::priv::sync_extruder_list(bool &only_external_material, bool is_manual)
{
MachineObject *obj = wxGetApp().getDeviceManager()->get_selected_machine();
@@ -1728,18 +1855,29 @@ bool Sidebar::priv::sync_extruder_list(bool &only_external_material, bool is_man
}
int deputy_4 = 0, main_4 = 0, deputy_1 = 0, main_1 = 0;
+ const bool switch_ready = is_fila_switch_ready();
for (auto ams : obj->GetFilaSystem()->GetAmsList()) {
- // Main (first) extruder at right
- if (ams.second->GetExtruderId() == 0) {
- if (ams.second->GetAmsType() == DevAms::N3S) // N3S
- ++main_1;
- else
- ++main_4;
- } else if (ams.second->GetExtruderId() == 1) {
- if (ams.second->GetAmsType() == DevAms::N3S) // N3S
- ++deputy_1;
- else
- ++deputy_4;
+ for (int extruder_id : ams.second->GetBindedExtruderSet()) {
+ // With the switch installed every AMS binds to both extruders; once it is ready, attribute
+ // each to the extruder its input track feeds so the per-extruder counts stay correct. Without
+ // a switch the binding set is the single extruder and the filter is skipped, matching the
+ // pre-switch counts.
+ if (switch_ready) {
+ auto switcher_pos = ams.second->GetSwitcherPos();
+ if (!switcher_pos)
+ continue;
+ int switcher_id = obj->is_main_extruder_on_left() ? (1 - static_cast(switcher_pos.value()))
+ : static_cast(switcher_pos.value());
+ if (extruder_id != switcher_id)
+ continue;
+ }
+ const bool is_n3s = ams.second->GetAmsType() == DevAms::N3S;
+ // Main (first) extruder is id 0, deputy is id 1.
+ if (extruder_id == 0) {
+ if (is_n3s) ++main_1; else ++main_4;
+ } else if (extruder_id == 1) {
+ if (is_n3s) ++deputy_1; else ++deputy_4;
+ }
}
}
only_external_material = !obj->GetFilaSystem()->HasAms();
@@ -1774,6 +1912,22 @@ bool Sidebar::priv::sync_extruder_list(bool &only_external_material, bool is_man
printer_tab->set_extruder_volume_type(idx, target_types[idx]);
}
+ if (extruder_nums > 1) {
+ auto fila_switch = obj->GetFilaSwitch();
+ if (fila_switch->IsInstalled())
+ show_filament_switcher_dialog(fila_switch->IsReady(), is_manual);
+ else
+ fila_switch_warning_shown = false;
+ }
+
+ // Copy the live switch state into the project so slicing and the send dialog honor it. Both
+ // resolve to false unless the switch is installed and calibrated, so nothing changes without one.
+ auto &project_config = wxGetApp().preset_bundle->project_config;
+ if (auto *dynamic_filament = project_config.opt("enable_filament_dynamic_map"))
+ dynamic_filament->value = is_fila_switch_ready();
+ if (auto *has_switcher = project_config.opt("has_filament_switcher"))
+ has_switcher->value = is_fila_switch_ready();
+
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " finish sync_extruder_list";
return true;
}
@@ -1791,6 +1945,7 @@ void Sidebar::priv::update_sync_status(const MachineObject *obj)
right_extruder->sync_ams(nullptr, {}, {});
single_extruder->ShowBadge(false);
single_extruder->sync_ams(nullptr, {}, {});
+ update_extruder_separator_icon(false, false);
//btn_sync_printer->SetBorderColor(not_synced_colour);
//btn_sync_printer->SetIcon("printer_sync");
m_printer_bbl_sync->SetBitmap_("printer_sync_not");
@@ -1801,6 +1956,16 @@ void Sidebar::priv::update_sync_status(const MachineObject *obj)
return;
}
+ // Orca: replaces BBS's reset_fila_switch hook on DeviceManager::OnSelectedMachineChanged (absent
+ // in Orca). Selection/MQTT updates all funnel through here, so a change of the selected device id
+ // resets the switcher icon and the once-per-session not-ready warning for the new printer.
+ const std::string cur_dev_id = obj->get_dev_id();
+ if (cur_dev_id != last_sync_dev_id) {
+ last_sync_dev_id = cur_dev_id;
+ update_extruder_separator_icon(false, false);
+ fila_switch_warning_shown = false;
+ }
+
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
if (!preset_bundle) {
clear_all_sync_status();
@@ -1852,6 +2017,21 @@ void Sidebar::priv::update_sync_status(const MachineObject *obj)
if (extruder_nums != obj->GetExtderSystem()->GetTotalExtderCount())
return;
+ // Filament Track Switch: only ever surfaced on multi-extruder machines. When installed, tip/warn
+ // and show the status icon (green ready / red not-calibrated); when absent the icon stays hidden.
+ // Inert on any printer without a switch: GetFilaSwitch()->IsInstalled() is false.
+ const bool fila_switch_flag = is_fila_switch_ready();
+ if (extruder_nums > 1) {
+ auto fila_switch = obj->GetFilaSwitch();
+ if (fila_switch->IsInstalled()) {
+ show_filament_switcher_dialog(fila_switch->IsReady(), false);
+ update_extruder_separator_icon(true, fila_switch->IsReady());
+ } else {
+ update_extruder_separator_icon(false, false);
+ fila_switch_warning_shown = false;
+ }
+ }
+
std::vector extruder_infos(extruder_nums);
std::vector nozzle_volume_types = wxGetApp().preset_bundle->project_config.option("nozzle_volume_type")->values;
//for (size_t i = 0; i < nozzle_volume_types.size(); ++i) {
@@ -1893,16 +2073,34 @@ void Sidebar::priv::update_sync_status(const MachineObject *obj)
machine_extruder_infos[extruder.GetExtId()].diameter = extruder.GetNozzleDiameter();
}
for (auto &item : obj->GetFilaSystem()->GetAmsList()) {
- if (item.second->GetExtruderId() >= machine_extruder_infos.size())
+ int extruder_id;
+ // With the switch ready every AMS feeds both extruders, so attribute each to the extruder its
+ // input track feeds. Without a switch the binding is a single extruder
+ // (GetUniqueBindedExtruderId() == GetExtruderId()) and the switcher position is empty, so this
+ // yields the same attribution as before.
+ if (fila_switch_flag) {
+ auto switcher_pos = item.second->GetSwitcherPos();
+ if (!switcher_pos)
+ continue;
+ extruder_id = obj->is_main_extruder_on_left() ? (1 - static_cast(switcher_pos.value()))
+ : static_cast(switcher_pos.value());
+ } else {
+ const auto &uniq_extruder_id = item.second->GetUniqueBindedExtruderId();
+ if (!uniq_extruder_id)
+ continue;
+ extruder_id = uniq_extruder_id.value();
+ }
+
+ if (extruder_id >= machine_extruder_infos.size())
continue;
if (item.second->GetAmsType() == DevAms::N3S)
{ // N3S
- machine_extruder_infos[item.second->GetExtruderId()].ams_1++;
- machine_extruder_infos[item.second->GetExtruderId()].ams_v1.push_back(item.second);
+ machine_extruder_infos[extruder_id].ams_1++;
+ machine_extruder_infos[extruder_id].ams_v1.push_back(item.second);
} else {
- machine_extruder_infos[item.second->GetExtruderId()].ams_4++;
- machine_extruder_infos[item.second->GetExtruderId()].ams_v4.push_back(item.second);
+ machine_extruder_infos[extruder_id].ams_4++;
+ machine_extruder_infos[extruder_id].ams_v4.push_back(item.second);
}
}
@@ -1956,6 +2154,20 @@ void Sidebar::priv::update_sync_status(const MachineObject *obj)
// btn_sync_printer->SetIcon("printer_sync");
m_printer_bbl_sync->SetBitmap_("printer_sync_not");
}
+
+ // When the switch is ready every AMS filament is available to both extruders; refresh the
+ // filament combos whenever the AMS list changes so both extruders pick up the full set.
+ if (fila_switch_flag) {
+ static std::map last_filament_ams_list;
+ bool is_same_ams_list = last_filament_ams_list == wxGetApp().preset_bundle->filament_ams_list;
+ if (!is_same_ams_list)
+ last_filament_ams_list = wxGetApp().preset_bundle->filament_ams_list;
+ const auto print_tech = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
+ if (print_tech == ptFFF && !is_same_ams_list) {
+ for (PlaterPresetComboBox *cb : combos_filament)
+ cb->update();
+ }
+ }
}
void Sidebar::update_sync_ams_btn_enable(wxUpdateUIEvent &e)
@@ -2408,6 +2620,24 @@ Sidebar::Sidebar(Plater *parent)
p->left_extruder = new ExtruderGroup(p->m_panel_printer_content, 0, _L("Left Nozzle"));
p->right_extruder = new ExtruderGroup(p->m_panel_printer_content, 1, _L("Right Nozzle"));
p->single_extruder = new ExtruderGroup(p->m_panel_printer_content, -1, _L("Nozzle"));
+ // Orca: keep the floating switcher icon aligned with the left extruder's AMS row when the
+ // extruder card is resized (the overlay is absolutely positioned, not managed by a sizer).
+ p->left_extruder->Bind(wxEVT_SIZE, [this](wxSizeEvent &evt) {
+ if (p->extruder_separator_icon && p->extruder_separator_icon->IsShown()) {
+ wxPoint left_box_pos = p->left_extruder->GetPosition();
+ wxPoint ams_local_pos = p->left_extruder->hsizer_ams->GetPosition();
+ wxSize left_size = p->left_extruder->sizer->GetSize();
+ wxSize ams_size = p->left_extruder->hsizer_ams->GetSize();
+ wxSize icon_size = p->extruder_separator_icon->GetSize();
+ int ams_abs_y = left_box_pos.y + ams_local_pos.y + FromDIP(4);
+ int center_x = left_size.GetWidth() + FromDIP(6) - icon_size.GetWidth() / 2;
+ int center_y = ams_abs_y + (ams_size.GetHeight() - icon_size.GetHeight()) / 2 - icon_size.GetHeight() / 2;
+ p->extruder_separator_icon->SetPosition(wxPoint(center_x, center_y));
+ if (p->m_panel_printer_content)
+ p->m_panel_printer_content->Refresh();
+ }
+ evt.Skip();
+ });
auto switch_diameter = [this](wxCommandEvent & evt) {
auto extruder = dynamic_cast(dynamic_cast(evt.GetEventObject())->GetParent());
p->is_switching_diameter = true;
@@ -3821,6 +4051,16 @@ bool Sidebar::sync_extruder_list()
return p->sync_extruder_list(only_external_material);
}
+bool Sidebar::is_fila_switch_ready()
+{
+ return p->is_fila_switch_ready();
+}
+
+void Sidebar::reset_fila_switch()
+{
+ p->update_extruder_separator_icon(false, false);
+}
+
bool Sidebar::need_auto_sync_extruder_list_after_connect_priner(const MachineObject *obj)
{
if(!obj)
@@ -6947,6 +7187,14 @@ std::vector Plater::priv::load_files(const std::vector& input_
}
}
}
+
+ // Filament Track Switch state is derived live from the connected
+ // printer; a loaded project must not carry a stale installed/active
+ // flag, so clear both and let device sync re-derive them.
+ if (auto* has_switcher = proj_cfg.opt("has_filament_switcher"))
+ has_switcher->value = false;
+ if (auto* dynamic_map = proj_cfg.opt("enable_filament_dynamic_map"))
+ dynamic_map->value = false;
}
// Update filament combobox after loading config
wxGetApp().plater()->sidebar().update_presets(Preset::TYPE_FILAMENT);
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index 626d288f46..1234cac325 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -196,6 +196,8 @@ public:
std::map build_filament_ams_list(MachineObject* obj);
void sync_ams_list(bool is_from_big_sync_btn = false);
bool sync_extruder_list();
+ bool is_fila_switch_ready();
+ void reset_fila_switch();
bool need_auto_sync_extruder_list_after_connect_priner(const MachineObject* obj);
void update_sync_status(const MachineObject* obj);
int get_sidebar_pos_right_x();
diff --git a/src/slic3r/GUI/PrePrintChecker.cpp b/src/slic3r/GUI/PrePrintChecker.cpp
index 870d3f835d..4344e7170c 100644
--- a/src/slic3r/GUI/PrePrintChecker.cpp
+++ b/src/slic3r/GUI/PrePrintChecker.cpp
@@ -38,6 +38,7 @@ std::string PrePrintChecker::get_print_status_info(PrintDialogStatus status)
case PrintStatusUnsupportedPrinter: return "PrintStatusUnsupportedPrinter";
case PrintStatusRackNozzleMappingWaiting: return "PrintStatusRackNozzleMappingWaiting";
case PrintStatusRackNozzleMappingError: return "PrintStatusRackNozzleMappingError";
+ case PrintStatusFilaSwitcherError: return "PrintStatusFilaSwitcherError";
case PrintStatusColorQuantityExceed: return "PrintStatusColorQuantityExceed";
// Handle filament errors
case PrintStatusAmsOnSettingup: return "PrintStatusAmsOnSettingup";
@@ -50,6 +51,7 @@ std::string PrePrintChecker::get_print_status_info(PrintDialogStatus status)
case PrintStatusTimelapseWarning: return "PrintStatusTimelapseWarning";
case PrintStatusMixAmsAndVtSlotWarning: return "PrintStatusMixAmsAndVtSlotWarning";
case PrintStatusRackNozzleMappingWarning: return "PrintStatusRackNozzleMappingWarning";
+ case PrintStatusFilaSwitcherSlicingNotMatch: return "PrintStatusFilaSwitcherSlicingNotMatch";
case PrintStatusWarningKvalueNotUsed: return "PrintStatusWarningKvalueNotUsed";
case PrintStatusHasFilamentInBlackListWarning: return "PrintStatusHasFilamentInBlackListWarning";
case PrintStatusFilamentWarningHighChamberTemp: return "PrintStatusFilamentWarningHighChamberTemp";
diff --git a/src/slic3r/GUI/PrePrintChecker.hpp b/src/slic3r/GUI/PrePrintChecker.hpp
index 53ab441df4..5996782b1d 100644
--- a/src/slic3r/GUI/PrePrintChecker.hpp
+++ b/src/slic3r/GUI/PrePrintChecker.hpp
@@ -67,6 +67,7 @@ enum PrintDialogStatus : unsigned int {
PrintStatusUnsupportedPrinter,
PrintStatusRackNozzleMappingWaiting,
PrintStatusRackNozzleMappingError,
+ PrintStatusFilaSwitcherError,
PrintStatusPrinterErrorEnd,
// Errors for filament, Block Print
@@ -92,6 +93,7 @@ enum PrintDialogStatus : unsigned int {
PrintStatusMixAmsAndVtSlotWarning,
PrintStatusToolHeadCoolingFanWarning,
PrintStatusRackNozzleMappingWarning,
+ PrintStatusFilaSwitcherSlicingNotMatch,
PrintStatusPrinterWarningEnd,
// Warnings for filament
diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp
index c52d0eed3a..e24604fd68 100644
--- a/src/slic3r/GUI/PresetComboBoxes.cpp
+++ b/src/slic3r/GUI/PresetComboBoxes.cpp
@@ -3,6 +3,8 @@
#include
#include
#include
+#include
+#include
#include
#include
@@ -510,8 +512,14 @@ bool PresetComboBox::add_ams_filaments(std::string selected, bool alias_name)
bool selected_in_ams = false;
bool is_bbl_vendor_preset = m_preset_bundle->is_bbl_vendor();
if (is_bbl_vendor_preset && !m_preset_bundle->filament_ams_list.empty()) {
+ // When a filament track switch is installed and calibrated, every AMS filament is reachable
+ // from both extruders, so present one deduplicated group instead of the Left/Right split.
+ bool fila_switch_ready = wxGetApp().sidebar().is_fila_switch_ready();
bool dual_extruder = (m_preset_bundle->filament_ams_list.begin()->first & 0x10000) == 0;
- set_label_marker(Append(dual_extruder ? _L("Left filaments") : _L("AMS filament"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM));
+ if (fila_switch_ready)
+ set_label_marker(Append(_L("AMS filaments"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM));
+ else
+ set_label_marker(Append(dual_extruder ? _L("Left filaments") : _L("AMS filament"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM));
m_first_ams_filament = GetCount();
auto &filaments = m_collection->get_presets();
@@ -523,8 +531,12 @@ bool PresetComboBox::add_ams_filaments(std::string selected, bool alias_name)
icon_width = 32;
}
+ // Deduplicate by (tray_name, filament_id) so a filament shared by both extruders is
+ // listed once when the switch is ready. Uses Orca's tray naming/lookup, not BBS's.
+ std::set> added_filaments;
+
for (auto &entry : m_preset_bundle->filament_ams_list) {
- if (dual_extruder && (entry.first & 0x10000)) {
+ if (!fila_switch_ready && dual_extruder && (entry.first & 0x10000)) {
dual_extruder = false;
set_label_marker(Append(_L("Right filaments"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM));
}
@@ -535,6 +547,13 @@ bool PresetComboBox::add_ams_filaments(std::string selected, bool alias_name)
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1% 's filament_id is empty.") % name;
continue;
}
+ if (fila_switch_ready) {
+ // skip the external spool and collapse duplicates shared across both extruders
+ if (name == "Ext")
+ continue;
+ if (!added_filaments.insert(std::make_pair(name, filament_id)).second)
+ continue;
+ }
auto iter = std::find_if(filaments.begin(), filaments.end(),
[&filament_id, this](auto &f) { return f.is_compatible && m_collection->get_preset_base(f) == &f && f.filament_id == filament_id; });
if (iter == filaments.end()) {
diff --git a/src/slic3r/GUI/SelectMachine.cpp b/src/slic3r/GUI/SelectMachine.cpp
index e66d07d2e9..ec4f3a7cab 100644
--- a/src/slic3r/GUI/SelectMachine.cpp
+++ b/src/slic3r/GUI/SelectMachine.cpp
@@ -1593,6 +1593,58 @@ bool SelectMachineDialog::use_dynamic_nozzle_map() const
return false;
}
+bool SelectMachineDialog::slicing_with_fila_switch() const
+{
+ if (use_dynamic_nozzle_map())
+ return true;
+
+ if (m_print_type == FROM_NORMAL) {
+ auto has_filament_switcher = wxGetApp().preset_bundle->project_config.option("has_filament_switcher");
+ if (has_filament_switcher)
+ return has_filament_switcher->value;
+ } else if (m_print_type == FROM_SDCARD_VIEW) {
+ if (m_required_data_plate_data_list.size() > (size_t) m_print_plate_idx) {
+ auto has_filament_switcher = m_required_data_plate_data_list[m_print_plate_idx]->config.option("has_filament_switcher");
+ if (has_filament_switcher)
+ return has_filament_switcher->value;
+ }
+ auto has_filament_switcher = m_required_data_config.option("has_filament_switcher");
+ if (has_filament_switcher)
+ return has_filament_switcher->value;
+ }
+ return false;
+}
+
+bool SelectMachineDialog::CheckErrorDynamicSwitchNozzle(MachineObject* obj_)
+{
+ if (!obj_)
+ return false;
+
+ // Advisory only (does not block Send): the file was sliced for a switch state that doesn't
+ // match the installed hardware, so grouping/flush may be suboptimal. Only on firmware that
+ // reports it can check this.
+ if (obj_->is_support_check_track_switch_match_slice_printer && slicing_with_fila_switch() != obj_->GetFilaSwitch()->IsInstalled()) {
+ show_status(PrintDialogStatus::PrintStatusFilaSwitcherSlicingNotMatch,
+ {_L("The Filament Track Switch installed on the printer does not match the slicing file. Please re-slice to avoid print quality issues.")});
+ }
+
+ // The blocking checks below only matter for dynamic nozzle mapping, which requires a switch.
+ if (!use_dynamic_nozzle_map())
+ return true;
+
+ if (!obj_->GetFilaSwitch()->IsInstalled()) {
+ show_status(PrintDialogStatus::PrintStatusFilaSwitcherError, {_L("This print requires a Filament Track Switch. Please install it first.")});
+ return false;
+ }
+
+ if (!obj_->GetFilaSwitch()->IsReady()) {
+ show_status(PrintDialogStatus::PrintStatusFilaSwitcherError, {_L("The Filament Track Switch has not been setup. Please setup it first.")});
+ return false;
+ }
+
+ return true;
+}
+
void SelectMachineDialog::clear_nozzle_mapping()
{
m_nozzle_mapping_result.clear();
@@ -2010,6 +2062,14 @@ void SelectMachineDialog::show_status(PrintDialogStatus status, std::vectorGetExtderSystem()->GetCurrentExtder();
- m_filament_step->SetupSteps(cur_ext ? cur_ext->HasFilamentInExt() : false);
+ m_filament_step->SetupSteps(obj, cur_ext ? cur_ext->HasFilamentInExt() : false);
Layout();
Fit();
@@ -3526,6 +3526,14 @@ void StatusPanel::update_ams_control_state(std::string ams_id, std::string slot_
if (ams_id.empty() || slot_id.empty()) {
load_error_info = _L("Choose an AMS slot then press \"Load\" or \"Unload\" button to automatically load or unload filament.");
unload_error_info = _L("Choose an AMS slot then press \"Load\" or \"Unload\" button to automatically load or unload filament.");
+ } else if (obj->GetFilaSwitch()->IsInstalled() && devPrinterUtil::IsVirtualSlot(ams_id)) {
+ // Orca: with a Filament Track Switch installed the external spool cannot be routed, so both actions are blocked.
+ load_error_info = _L("\"Load\" or \"Unload\" is not supported for external spool while using Filament Track Switch.");
+ unload_error_info = load_error_info;
+ } else if (obj->GetFilaSwitch()->IsInstalled() && !obj->GetFilaSwitch()->IsReady()) {
+ // Orca: an un-calibrated Filament Track Switch cannot route filament to either extruder, so block both actions.
+ load_error_info = _L("The Filament Track Switch has not been setup. Please setup on printer.");
+ unload_error_info = load_error_info;
} else if (ams_id == std::to_string(VIRTUAL_TRAY_MAIN_ID) || ams_id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID)) {
for (auto ext : obj->GetExtderSystem()->GetExtruders()) {
if (ext.GetSlotNow().ams_id == ams_id && ext.GetSlotNow().slot_id == slot_id)
@@ -3535,6 +3543,8 @@ void StatusPanel::update_ams_control_state(std::string ams_id, std::string slot_
}
} else {
for (auto ext : obj->GetExtderSystem()->GetExtruders()) {
+ // Orca: with a Filament Track Switch installed a slot is not bound to a single extruder, so skip the "already loaded" identity check.
+ if (obj->GetFilaSwitch()->IsInstalled()) { continue; }
if (ext.GetSlotNow().ams_id == ams_id && ext.GetSlotNow().slot_id == slot_id)
{
load_error_info = _L("Current slot has already been loaded.");
@@ -4231,6 +4241,43 @@ void StatusPanel::on_ams_load_curr()
std::string curr_ams_id = m_ams_control->GetCurentAms();
std::string curr_can_id = m_ams_control->GetCurrentCan(curr_ams_id);
+ std::optional extruder_id = std::nullopt;
+ if (obj->GetFilaSwitch() && obj->GetFilaSwitch()->IsInstalled()) {
+ if (!obj->GetFilaSwitch()->IsReady()) {
+ MessageDialog msg_dlg(nullptr, _L("The Filament Track Switch has not been setup. Please setup on printer."), wxEmptyString, wxICON_WARNING | wxOK);
+ msg_dlg.ShowModal();
+ return;
+ }
+
+ // Filament Track Switch is calibrated: the load can go to either extruder, so ask the
+ // user which. Record each extruder's currently-loaded slot so the dialog can grey out a
+ // side that already holds this filament.
+ std::vector> extruderSlots(2, {"", ""});
+ if (auto ext = obj->GetExtderSystem()->GetExtderById(MAIN_EXTRUDER_ID); ext.has_value())
+ {
+ if (ext->HasFilamentInExt())
+ {
+ extruderSlots[MAIN_EXTRUDER_ID] = {ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id};
+ }
+ }
+ if (auto ext = obj->GetExtderSystem()->GetExtderById(DEPUTY_EXTRUDER_ID); ext.has_value())
+ {
+ if (ext->HasFilamentInExt())
+ {
+ extruderSlots[DEPUTY_EXTRUDER_ID] = {ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id};
+ }
+ }
+
+ FeedDirectionDialog dialog(nullptr, 2, obj->printer_type);
+ dialog.SetExtruderMapping(obj, curr_ams_id, curr_can_id, extruderSlots);
+ auto rtn = dialog.ShowModal();
+
+ if (rtn != wxID_OK)
+ {
+ return;
+ }
+ extruder_id = dialog.GetExtruderID();
+ }
update_load_with_temp();
//virtual tray
@@ -4262,11 +4309,11 @@ void StatusPanel::on_ams_load_curr()
if (obj->is_enable_np || obj->is_enable_ams_np) {
try {
if (!curr_ams_id.empty() && !curr_can_id.empty()) {
- obj->command_ams_change_filament(true, curr_ams_id, "0", old_temp, new_temp);
+ obj->command_ams_change_filament(true, curr_ams_id, "0", old_temp, new_temp, extruder_id);
}
} catch (...) {}
} else {
- obj->command_ams_change_filament(true, "254", "0", old_temp, new_temp);
+ obj->command_ams_change_filament(true, "254", "0", old_temp, new_temp, extruder_id);
}
}
@@ -4302,12 +4349,12 @@ void StatusPanel::on_ams_load_curr()
if (obj->is_enable_np) {
try {
if (!curr_ams_id.empty() && !curr_can_id.empty()) {
- obj->command_ams_change_filament(true, curr_ams_id, curr_can_id, old_temp, new_temp);
+ obj->command_ams_change_filament(true, curr_ams_id, curr_can_id, old_temp, new_temp, extruder_id);
}
}
catch (...){}
} else {
- obj->command_ams_change_filament(true, curr_ams_id, curr_can_id, old_temp, new_temp);
+ obj->command_ams_change_filament(true, curr_ams_id, curr_can_id, old_temp, new_temp, extruder_id);
}
}
}
diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp
index 658d658823..ca4bfeddbb 100644
--- a/src/slic3r/GUI/Tab.cpp
+++ b/src/slic3r/GUI/Tab.cpp
@@ -5636,6 +5636,18 @@ void TabPrinter::on_preset_loaded()
if (use_default_nozzle_volume_type) {
m_preset_bundle->project_config.option("nozzle_volume_type")->values = current_printer.config.option("default_nozzle_volume_type")->values;
}
+
+ // Changing printer model drops any Filament Track Switch context from the previous
+ // printer; clear both device-derived flags so a switch-less printer never inherits a
+ // stale installed/active state (re-derived by device sync when a printer is connected).
+ if (auto* has_switcher = m_preset_bundle->project_config.opt("has_filament_switcher"))
+ has_switcher->value = false;
+ if (auto* dynamic_map = m_preset_bundle->project_config.opt("enable_filament_dynamic_map"))
+ dynamic_map->value = false;
+ // Orca: also clear the sidebar switcher status icon on printer-model change (mirrors BBS's
+ // reset_fila_switch on machine change); it re-derives from device sync once a printer connects.
+ if (wxGetApp().plater())
+ wxGetApp().plater()->sidebar().reset_fila_switch();
}
}
diff --git a/src/slic3r/GUI/UpgradePanel.cpp b/src/slic3r/GUI/UpgradePanel.cpp
index 9ff1d12709..16fe0420d8 100644
--- a/src/slic3r/GUI/UpgradePanel.cpp
+++ b/src/slic3r/GUI/UpgradePanel.cpp
@@ -28,6 +28,7 @@ static const std::unordered_map ACCESSORY_DISPLAY_STR = {
{"O2L_ACM", "Active Cutting Module"},
{"O2L_UCM", "Ultrasonic Cutting Module"},
{"O2L-AFP", L("Auto Fire Extinguishing System")},
+ {"O2L-FTS", L("Filament Track Switch")},
};
enum FIRMWARE_STASUS
@@ -217,6 +218,7 @@ MachineInfoPanel::MachineInfoPanel(wxWindow* parent, wxWindowID id, const wxPoin
createLaserWidgets(m_main_left_sizer);
createAirPumpWidgets(m_main_left_sizer);
createExtinguishWidgets(m_main_left_sizer);
+ createFilaTrackSwitchWidgets(m_main_left_sizer);
// nozzle rack widgets (H2C induction hotend rack; hidden unless GetNozzleRack()->IsSupported())
createNozzleRackWidgets(m_main_left_sizer);
@@ -406,6 +408,27 @@ void MachineInfoPanel::createExtinguishWidgets(wxBoxSizer* main_left_sizer)
main_left_sizer->Add(m_extinguish_sizer, 0, wxEXPAND, 0);
}
+void MachineInfoPanel::createFilaTrackSwitchWidgets(wxBoxSizer* main_left_sizer)
+{
+ m_filatrack_line_above = new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL);
+ m_filatrack_line_above->SetBackgroundColour(wxColour(206, 206, 206));
+ main_left_sizer->Add(m_filatrack_line_above, 0, wxEXPAND | wxLEFT, FromDIP(40));
+
+ m_filatrack_img = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize(FromDIP(200), FromDIP(200)));
+ m_filatrack_img->SetBitmap(m_img_filatrack.bmp());
+
+ wxBoxSizer* content_sizer = new wxBoxSizer(wxVERTICAL);
+ content_sizer->Add(0, 40, 0, wxEXPAND, FromDIP(5));
+ m_filatrack_version = new uiDeviceUpdateVersion(this, wxID_ANY);
+ content_sizer->Add(m_filatrack_version, 0, wxEXPAND, 0);
+
+ m_filatrack_sizer = new wxBoxSizer(wxHORIZONTAL);
+ m_filatrack_sizer->Add(m_filatrack_img, 0, wxALIGN_TOP | wxALL, FromDIP(5));
+ m_filatrack_sizer->Add(content_sizer, 1, wxEXPAND, 0);
+
+ main_left_sizer->Add(m_filatrack_sizer, 0, wxEXPAND, 0);
+}
+
void MachineInfoPanel::msw_rescale()
{
rescale_bitmaps();
@@ -435,6 +458,7 @@ void MachineInfoPanel::init_bitmaps()
m_img_laser = ScalableBitmap(this, "laser", 160);
m_img_cutting = ScalableBitmap(this, "cut", 160);
m_img_extinguish = ScalableBitmap(this, "extinguish", 160);
+ m_img_filatrack = ScalableBitmap(this, "filament_track_switch", 160);
m_img_nozzle_rack = ScalableBitmap(this, "nozzle_rack", 160);
upgrade_green_icon = ScalableBitmap(this, "monitor_upgrade_online", 5);
@@ -544,6 +568,7 @@ void MachineInfoPanel::update(MachineObject* obj)
update_cut(obj);
update_laszer(obj);
update_extinguish(obj);
+ update_filatrack(obj);
update_nozzle_rack(obj);
//update progress
@@ -1129,6 +1154,19 @@ void MachineInfoPanel::update_extinguish(MachineObject* obj)
}
}
+void MachineInfoPanel::update_filatrack(MachineObject* obj)
+{
+ if (obj && obj->filatrack_version_info.isValid())
+ {
+ m_filatrack_version->UpdateInfo(obj->filatrack_version_info);
+ show_filatrack(true);
+ }
+ else
+ {
+ show_filatrack(false);
+ }
+}
+
void MachineInfoPanel::show_status(int status, std::string upgrade_status_str)
{
if (last_status == status && last_status_str == upgrade_status_str) return;
@@ -1263,6 +1301,16 @@ void MachineInfoPanel::show_extinguish(bool show)
}
}
+void MachineInfoPanel::show_filatrack(bool show)
+{
+ if (m_filatrack_version->IsShown() != show)
+ {
+ m_filatrack_img->Show(show);
+ m_filatrack_line_above->Show(show);
+ m_filatrack_version->Show(show);
+ }
+}
+
void MachineInfoPanel::createNozzleRackWidgets(wxBoxSizer *main_left_sizer)
{
// horizontal line above
diff --git a/src/slic3r/GUI/UpgradePanel.hpp b/src/slic3r/GUI/UpgradePanel.hpp
index ef56bde03d..0cf88ae080 100644
--- a/src/slic3r/GUI/UpgradePanel.hpp
+++ b/src/slic3r/GUI/UpgradePanel.hpp
@@ -134,6 +134,12 @@ protected:
wxStaticLine* m_extinguish_line_above = nullptr;;
uiDeviceUpdateVersion* m_extinguish_version = nullptr;
+ /* filament track switch info*/
+ wxBoxSizer* m_filatrack_sizer = nullptr;
+ wxStaticBitmap* m_filatrack_img = nullptr;
+ wxStaticLine* m_filatrack_line_above = nullptr;
+ uiDeviceUpdateVersion* m_filatrack_version = nullptr;
+
/* nozzle rack (H2C induction hotend rack) — opens wgtDeviceNozzleRackUpgradeDlg */
wxBoxSizer* m_nozzle_rack_sizer = nullptr;
wxStaticBitmap* m_nozzle_rack_img = nullptr;
@@ -162,6 +168,7 @@ protected:
ScalableBitmap m_img_cutting;
ScalableBitmap m_img_laser;
ScalableBitmap m_img_extinguish;
+ ScalableBitmap m_img_filatrack;
ScalableBitmap m_img_nozzle_rack;
ScalableBitmap upgrade_gray_icon;
ScalableBitmap upgrade_green_icon;
@@ -220,18 +227,21 @@ private:
void createCuttingWidgets(wxBoxSizer* main_left_sizer);
void createLaserWidgets(wxBoxSizer* main_left_sizer);
void createExtinguishWidgets(wxBoxSizer* main_left_sizer);
+ void createFilaTrackSwitchWidgets(wxBoxSizer* main_left_sizer);
void createNozzleRackWidgets(wxBoxSizer* main_left_sizer);
void update_air_pump(MachineObject* obj);
void update_cut(MachineObject* obj);
void update_laszer(MachineObject* obj);
void update_extinguish(MachineObject* obj);
+ void update_filatrack(MachineObject* obj);
void update_nozzle_rack(MachineObject* obj);
void show_air_pump(bool show = true);
void show_cut(bool show = true);
void show_laszer(bool show = true);
void show_extinguish(bool show = true);
+ void show_filatrack(bool show = true);
void show_nozzle_rack(bool show = true);
void on_nozzle_rack_update(wxCommandEvent& event);
diff --git a/src/slic3r/GUI/Widgets/AMSControl.cpp b/src/slic3r/GUI/Widgets/AMSControl.cpp
index a3913dc4e4..ea693fbd4c 100644
--- a/src/slic3r/GUI/Widgets/AMSControl.cpp
+++ b/src/slic3r/GUI/Widgets/AMSControl.cpp
@@ -9,9 +9,11 @@
#include "slic3r/GUI/DeviceCore/DevManager.h"
#include "slic3r/GUI/DeviceCore/DevFilaSystem.h"
+#include "slic3r/GUI/DeviceCore/DevFilaSwitch.h"
#include
#include
+#include
#include
@@ -39,7 +41,7 @@ AMSControl::AMSControl(wxWindow *parent, wxWindowID id, const wxPoint &pos, cons
SetBackgroundColour(*wxWHITE);
// normal mode
//Freeze();
- wxBoxSizer *m_sizer_body = new wxBoxSizer(wxVERTICAL);
+ m_sizer_body = new wxBoxSizer(wxVERTICAL);
m_amswin = new wxWindow(this, wxID_ANY);
m_amswin->SetBackgroundColour(*wxWHITE);
m_amswin->SetSize(wxSize(FromDIP(578), -1));
@@ -151,6 +153,12 @@ AMSControl::AMSControl(wxWindow *parent, wxWindowID id, const wxPoint &pos, cons
m_extruder = new AMSextruder(m_amswin, wxID_ANY, m_total_ext_count, wxDefaultPosition, AMS_EXTRUDER_SIZE);
m_sizer_option_mid->Add( m_extruder, 0, wxALIGN_CENTER, 0 );
+ // Orca: filament-switch routing glyph; hidden by default so it stays inert (zero layout impact)
+ // on any printer without a Filament Track Switch. Shown from UpdateAms only when installed.
+ m_switcher = new SwitcherImage(m_amswin, wxID_ANY, "fila_switch", wxSize(FromDIP(29), FromDIP(16)), wxDefaultPosition);
+ m_switcher->Hide();
+ m_sizer_option_mid->Add(m_switcher, 0, wxALIGN_CENTER | wxLEFT, FromDIP(6));
+
/*option right*/
m_button_extruder_feed = new Button(m_panel_option_right, _L("Load"));
@@ -975,6 +983,32 @@ void AMSControl::UpdateAms(const std::string &series_name,
{
m_amswin->Layout();
}
+
+ /*update switch status*/
+ // Orca: inert on any printer without a Filament Track Switch — install is false, so the banner is
+ // never materialized, the glyph stays hidden, and every ShowRoad(true) below is a no-op.
+ const auto [install, ready] = isFilaSwitchReady();
+ show_switcher_status(install && (!ready));
+ bool isShow = install && m_total_ext_count >= 2;
+ if (m_switcher->IsShown() != isShow)
+ {
+ m_switcher->Show(isShow);
+ m_sizer_body->Layout();
+ m_sizer_body->Fit(this);
+ this->Layout();
+ this->Refresh(true);
+ this->Update();
+ }
+
+ /*update ext road visibility when fila switch installed*/
+ bool road_visibility_changed = false;
+ for (auto& [ams_id, ams_item] : m_ams_item_list) {
+ if (!ams_item) continue;
+ // Orca: drop the external-spool road (AMSModel::EXT_AMS) while the switch routes all filament.
+ const bool should_show_road = !(install && ams_item->get_ams_model() == AMSModel::EXT_AMS);
+ if (ams_item->ShowRoad(should_show_road)) { road_visibility_changed = true; }
+ }
+ if (road_visibility_changed) { m_amswin->Layout(); }
}
void AMSControl::AddAmsPreview(AMSinfo info, AMSModel type)
@@ -1596,6 +1630,54 @@ void AMSControl::on_ams_setting_click(wxMouseEvent &event)
post_event(SimpleEvent(EVT_AMS_SETTINGS));
}
+std::tuple AMSControl::isFilaSwitchReady()
+{
+ DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
+ if (!dev) return {false, false};
+ MachineObject* obj = dev->get_selected_machine();
+ if (!obj) return {false, false};
+ // Orca: GetFilaSwitch() is a raw non-null accessor (BBS returns a shared_ptr).
+ DevFilaSwitch* fila_switch = obj->GetFilaSwitch();
+ if (fila_switch)
+ {
+ return {fila_switch->IsInstalled(), fila_switch->IsReady()};
+ }
+ return {false, false};
+}
+
+void AMSControl::show_switcher_status(bool show)
+{
+ // Orca: never materialize the banner on printers that never request it, so the AMS control is
+ // byte-identical without a Filament Track Switch (UpdateAms calls this with show=false every refresh).
+ if (!show && tipPanel == nullptr) { return; }
+
+ if (tipPanel == nullptr)
+ {
+ m_sizer_body->Add(0, 0, 1, wxEXPAND | wxTOP, FromDIP(5));
+ tipPanel = new wxPanel(m_amswin);
+ tipPanel->SetBackgroundColour(wxColour(255, 153, 0));
+ tipSizer = new wxBoxSizer(wxHORIZONTAL);
+ tipPanel->SetSizer(tipSizer);
+ icon = new wxStaticBitmap(tipPanel, wxID_ANY,
+ wxArtProvider::GetBitmap(wxART_INFORMATION, wxART_MESSAGE_BOX, wxSize(FromDIP(16), FromDIP(16))));
+ tipSizer->Add(icon, 0, wxALL, FromDIP(8));
+ tipText = new wxStaticText(tipPanel, wxID_ANY, _L("AMS has not been initialized. Please initialize it before use."));
+ tipText->SetForegroundColour(wxColour(255, 255, 255));
+ tipText->SetFont(wxFont(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
+ tipText->Wrap(-1);
+ tipText->SetMinSize(wxSize(-1, -1));
+ tipSizer->Add(tipText, 0, wxALL | wxALIGN_CENTER_VERTICAL | wxEXPAND, FromDIP(8));
+ m_sizer_body->Add(tipPanel, 1, wxEXPAND, 0);
+ }
+ if (tipPanel->IsShown() == show)
+ {
+ return;
+ }
+ tipPanel->Show(show);
+ m_amswin->Layout();
+ m_amswin->Fit();
+}
+
void AMSControl::parse_object(MachineObject* obj) {
if (!obj || obj->GetFilaSystem()->GetAmsList().size() == 0)
{
diff --git a/src/slic3r/GUI/Widgets/AMSControl.hpp b/src/slic3r/GUI/Widgets/AMSControl.hpp
index 31c6b86feb..7616d269f2 100644
--- a/src/slic3r/GUI/Widgets/AMSControl.hpp
+++ b/src/slic3r/GUI/Widgets/AMSControl.hpp
@@ -13,6 +13,7 @@
#include
#include
#include
+#include
#include "slic3r/GUI/DeviceCore/DevExtruderSystem.h"
@@ -51,8 +52,16 @@ protected:
int m_total_ext_count = 1;
AMSextruder *m_extruder{nullptr};
+ SwitcherImage *m_switcher{nullptr}; // Orca: filament-switch routing glyph (hidden unless a switch is installed)
AMSRoadDownPart* m_down_road{ nullptr };
+ // Orca: "AMS not initialized" warning banner, materialized lazily only when a switch needs it (see show_switcher_status)
+ wxBoxSizer* m_sizer_body{nullptr};
+ wxPanel* tipPanel{nullptr};
+ wxBoxSizer* tipSizer{nullptr};
+ wxStaticBitmap* icon{nullptr};
+ wxStaticText* tipText{nullptr};
+
/*items*/
wxBoxSizer* m_sizer_ams_items{nullptr};
wxScrolledWindow* m_panel_prv_left {nullptr};
@@ -151,6 +160,11 @@ public:
void StopRridLoading(wxString amsid, wxString canid);
void ShowFilamentTip(bool hasams = true);
+ // Orca: Filament Track Switch awareness. isFilaSwitchReady() resolves the selected machine and
+ // returns {installed, ready}; show_switcher_status() toggles the "AMS not initialized" banner.
+ std::tuple isFilaSwitchReady();
+ void show_switcher_status(bool show);
+
void UpdatePassRoad(string ams_id, AMSPassRoadType type, AMSPassRoadSTEP step);
void CreateAms();
void CreateAmsDoubleNozzle(const std::string &series_name, const std::string& printer_type);
diff --git a/src/slic3r/GUI/Widgets/AMSItem.cpp b/src/slic3r/GUI/Widgets/AMSItem.cpp
index 129eec9c61..2e47f01d6c 100644
--- a/src/slic3r/GUI/Widgets/AMSItem.cpp
+++ b/src/slic3r/GUI/Widgets/AMSItem.cpp
@@ -742,6 +742,63 @@ void AMSExtImage::doRender(wxDC& dc)
}
+// Orca: SwitcherImage — routing glyph drawn when a Filament Track Switch is installed.
+void SwitcherImage::paintEvent(wxPaintEvent &evt)
+{
+ wxPaintDC dc(this);
+ render(dc);
+}
+
+void SwitcherImage::render(wxDC &dc)
+{
+#ifdef __WXMSW__
+ wxSize size = GetSize();
+ wxMemoryDC memdc;
+ wxBitmap bmp(size.x, size.y);
+ memdc.SelectObject(bmp);
+ memdc.Blit({0, 0}, size, &dc, {0, 0});
+
+ {
+ wxGCDC dc2(memdc);
+ doRender(dc2);
+ }
+
+ memdc.SelectObject(wxNullBitmap);
+ dc.DrawBitmap(bmp, 0, 0);
+#else
+ doRender(dc);
+#endif
+}
+
+void SwitcherImage::doRender(wxDC &dc)
+{
+ auto size = GetSize();
+ if (m_show_state){
+ dc.SetPen(*wxTRANSPARENT_PEN);
+ dc.SetBrush(*wxWHITE);
+ dc.DrawBitmap(m_switcher.bmp(), wxPoint((size.x - m_switcher.GetBmpSize().x) / 2, 0));
+ }
+ Layout();
+}
+
+SwitcherImage::SwitcherImage(wxWindow *parent, wxWindowID id, string file_name, const wxSize& size, const wxPoint &pos)
+{
+ wxWindow::Create(parent, id, pos, size);
+ SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
+ m_show_state = true;
+ m_switcher = ScalableBitmap(this, file_name, 16);
+ m_file_name = file_name;
+ SetSize(size);
+ SetMinSize(size);
+ SetMaxSize(size);
+
+
+ Bind(wxEVT_PAINT, &SwitcherImage::paintEvent, this);
+}
+
+SwitcherImage::~SwitcherImage() {}
+
+
//DevAms Extruder
AMSextruder::AMSextruder(wxWindow *parent, wxWindowID id, int nozzle_num, const wxPoint &pos, const wxSize &size)
{
@@ -3577,6 +3634,18 @@ void AmsItem::doRender(wxDC& dc)
}
}
+// Orca: toggle the road segment below the item (m_panel_road). Used to drop the external-spool
+// road when a Filament Track Switch is installed. No-op (returns false) when already in state.
+bool AmsItem::ShowRoad(bool show)
+{
+ if (!m_panel_road) return false;
+ if (m_panel_road->IsShown() == show) return false;
+ m_panel_road->Show(show);
+ Layout();
+ Refresh();
+ return true;
+}
+
void AmsItem::RenderLiteRoad(wxDC& dc, wxSize size) {
auto end_top = size.x - FromDIP(3);
if (m_panel_pos == AMSPanelPos::RIGHT_PANEL){
@@ -3706,4 +3775,342 @@ void AmsItem::show_sn_value(bool show)
}
}
+DevExtruderImage::DevExtruderImage(wxWindow *parent, wxWindowID id, int extruder_num, const wxPoint &pos, const wxSize &size) : wxWindow(parent, id, pos, wxDefaultSize), m_extruder_num(extruder_num)
+{
+ // wxWindow::Create(parent, id, pos, wxSize(FromDIP(45), FromDIP(112)));
+ SetBackgroundColour(*wxWHITE);
+ SetSize(wxSize(FromDIP(48), FromDIP(112)));
+ SetMinSize(wxSize(FromDIP(48), FromDIP(112)));
+ SetMaxSize(wxSize(FromDIP(48), FromDIP(112)));
+
+
+ m_left_extruder_active_filled = new ScalableBitmap(this, "left_extruder_active_filled", 62);
+ m_left_extruder_active_empty = new ScalableBitmap(this, "left_extruder_active_empty", 62);
+ m_left_extruder_unactive_filled = new ScalableBitmap(this, "left_extruder_unactive_filled", 62);
+ m_left_extruder_unactive_empty = new ScalableBitmap(this, "left_extruder_unactive_empty", 62);
+ m_right_extruder_active_filled = new ScalableBitmap(this, "right_extruder_active_filled", 62);
+ m_right_extruder_active_empty = new ScalableBitmap(this, "right_extruder_active_empty", 62);
+ m_right_extruder_unactive_filled = new ScalableBitmap(this, "right_extruder_unactive_filled", 62);
+ m_right_extruder_unactive_empty = new ScalableBitmap(this, "right_extruder_unactive_empty", 62);
+
+ m_extruder_single_nozzle_empty_load = new ScalableBitmap(this, "monitor_extruder_empty_load", 106);
+ m_extruder_single_nozzle_empty_unload = new ScalableBitmap(this, "monitor_extruder_empty_unload", 106);
+ m_extruder_single_nozzle_filled_load = new ScalableBitmap(this, "monitor_extruder_filled_load", 106);
+ m_extruder_single_nozzle_filled_unload = new ScalableBitmap(this, "monitor_extruder_filled_unload", 106);
+
+ Bind(wxEVT_PAINT, &DevExtruderImage::paintEvent, this);
+}
+
+void DevExtruderImage::msw_rescale()
+{
+ m_left_extruder_active_filled->msw_rescale();
+ m_left_extruder_active_empty->msw_rescale();
+ m_left_extruder_unactive_filled->msw_rescale();
+ m_left_extruder_unactive_empty->msw_rescale();
+ m_right_extruder_active_filled->msw_rescale();
+ m_right_extruder_active_empty->msw_rescale();
+ m_right_extruder_unactive_filled->msw_rescale();
+ m_right_extruder_unactive_empty->msw_rescale();
+
+ m_extruder_single_nozzle_empty_load->msw_rescale();
+ m_extruder_single_nozzle_empty_unload->msw_rescale();
+ m_extruder_single_nozzle_filled_load->msw_rescale();
+ m_extruder_single_nozzle_filled_unload->msw_rescale();
+ Layout();
+ Refresh();
+}
+
+void DevExtruderImage::render(wxDC &dc)
+{
+#ifdef __WXMSW__
+ wxSize size = GetSize();
+ wxMemoryDC memdc;
+ wxBitmap bmp(size.x, size.y);
+ memdc.SelectObject(bmp);
+ memdc.Blit({0, 0}, size, &dc, {0, 0});
+
+ {
+ wxGCDC dc2(memdc);
+ doRender(dc2);
+ }
+
+ memdc.SelectObject(wxNullBitmap);
+ dc.DrawBitmap(bmp, 0, 0);
+#else
+ doRender(dc);
+#endif
+}
+
+void DevExtruderImage::doRender(wxDC &dc)
+{
+ auto size = GetSize();
+ auto pot = wxPoint(size.x / 2, (size.y - m_left_extruder_active_filled->GetBmpSize().y) / 2);
+
+ if (m_extruder_num >= 2)
+ {
+ ScalableBitmap *left_extruder_bmp{nullptr};
+ ScalableBitmap *right_extruder_bmp{nullptr};
+
+ switch (m_right_ext_state)
+ {
+ case DevExtruderState::FILLED_LOAD:
+ right_extruder_bmp = current_extruder_loc == "right" ? m_right_extruder_active_filled : m_right_extruder_unactive_filled;
+ break;
+ case DevExtruderState::FILLED_UNLOAD:
+ right_extruder_bmp = current_extruder_loc == "right" ? m_right_extruder_active_filled : m_right_extruder_unactive_filled;
+ break;
+ case DevExtruderState::EMPTY_LOAD:
+ if (current_extruder_loc.empty())
+ {
+ right_extruder_bmp = m_right_extruder_active_empty;
+ }
+ else
+ {
+ right_extruder_bmp = current_extruder_loc == "right" ? m_right_extruder_active_empty : m_right_extruder_unactive_empty;
+ }
+ break;
+ case DevExtruderState::EMPTY_UNLOAD:
+ right_extruder_bmp = current_extruder_loc == "right" ? m_right_extruder_active_empty : m_right_extruder_unactive_empty;
+ break;
+ default: break;
+ }
+
+ switch (m_left_ext_state)
+ {
+ case DevExtruderState::FILLED_LOAD:
+ left_extruder_bmp = current_extruder_loc == "left" ? m_left_extruder_active_filled : m_left_extruder_unactive_filled;
+ break;
+ case DevExtruderState::FILLED_UNLOAD:
+ left_extruder_bmp = current_extruder_loc == "left" ? m_left_extruder_active_filled : m_left_extruder_unactive_filled;
+ break;
+ case DevExtruderState::EMPTY_LOAD:
+ if (current_extruder_loc.empty())
+ {
+ left_extruder_bmp = m_left_extruder_active_empty;
+ }
+ else
+ {
+ left_extruder_bmp = current_extruder_loc == "left" ? m_left_extruder_active_empty : m_left_extruder_unactive_empty;
+ }
+ break;
+ case DevExtruderState::EMPTY_UNLOAD:
+ left_extruder_bmp = current_extruder_loc == "left" ? m_left_extruder_active_empty : m_left_extruder_unactive_empty;
+ break;
+ default: break;
+ }
+
+ if (left_extruder_bmp) { dc.DrawBitmap(left_extruder_bmp->bmp(), pot.x - left_extruder_bmp->GetBmpWidth(), pot.y); }
+ if (right_extruder_bmp) { dc.DrawBitmap(right_extruder_bmp->bmp(), pot.x, pot.y); }
+ }
+ else
+ {
+ ScalableBitmap *extruder_bmp = nullptr;
+ switch (m_single_ext_state)
+ {
+ case DevExtruderState::FILLED_LOAD:
+ extruder_bmp = m_extruder_single_nozzle_filled_load;
+ break;
+ case DevExtruderState::FILLED_UNLOAD:
+ extruder_bmp = m_extruder_single_nozzle_filled_unload;
+ break;
+ case DevExtruderState::EMPTY_LOAD:
+ extruder_bmp = m_extruder_single_nozzle_empty_load;
+ break;
+ case DevExtruderState::EMPTY_UNLOAD:
+ extruder_bmp = m_extruder_single_nozzle_empty_unload;
+ break;
+ default: break;
+ }
+
+ if (extruder_bmp) { dc.DrawBitmap(extruder_bmp->bmp(), pot.x - extruder_bmp->GetBmpWidth() / 2, (size.y - extruder_bmp->GetBmpHeight()) / 2); }
+ }
+}
+
+
+FeedDirectionDialog::FeedDirectionDialog(wxWindow* parent,
+ const int extruderNum,
+ const std::string& printer_type)
+ : wxDialog(parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize),
+ m_extruder_num(extruderNum),
+ m_printer_type(printer_type)
+{
+ SetBackgroundColour(wxColour("#FFFFFF"));
+ SetMaxSize(wxSize(FromDIP(360), FromDIP(207)));
+ SetMinSize(wxSize(FromDIP(360), FromDIP(207)));
+ SetSize(wxSize(FromDIP(360), FromDIP(207)));
+
+ wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
+
+ wxGridSizer* topSizer = new wxGridSizer (1, 3, FromDIP(5), 0);
+
+ m_radioHelper = new wxRadioButton(this, wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
+ m_leftRadio = new wxRadioButton(this, wxID_ANY,
+ _L(DevPrinterConfigUtil::get_toolhead_display_name(m_printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::TitleCase, true)));
+ m_rightRadio = new wxRadioButton(this, wxID_ANY,
+ _L(DevPrinterConfigUtil::get_toolhead_display_name(m_printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::TitleCase, true)));
+ m_radioHelper->Show(false);
+ m_radioHelper->SetCanFocus(false);
+
+ m_leftRadio->SetForegroundColour(*wxBLACK);
+ m_rightRadio->SetForegroundColour(*wxBLACK);
+
+ topSizer->Add(m_leftRadio, 0, wxALIGN_CENTER | wxALL, FromDIP(20));
+ m_extruderImage = new DevExtruderImage(this, wxID_ANY, m_extruder_num);
+ topSizer->Add(m_extruderImage, 0, wxALIGN_CENTER | wxALL, FromDIP(5));
+ topSizer->Add(m_rightRadio, 0, wxALIGN_CENTER | wxALL, FromDIP(20));
+
+ mainSizer->AddStretchSpacer(1);
+ mainSizer->Add(topSizer, 1, wxEXPAND);
+ mainSizer->AddStretchSpacer(1);
+
+ wxBoxSizer* bottomSizer = new wxBoxSizer(wxHORIZONTAL);
+ m_confirmBtn = new Button(this, _L("Confirm"));
+ m_confirmBtn->SetSize(wxSize(FromDIP(80), FromDIP(32)));
+ m_confirmBtn->Enable(false);
+ m_confirmBtn->SetFont(::Label::Body_14);
+ bottomSizer->Add(m_confirmBtn, 0, wxALIGN_RIGHT);
+
+ mainSizer->Add(bottomSizer, 0, wxALIGN_RIGHT | wxBOTTOM | wxRIGHT, FromDIP(10));
+
+ SetSizer(mainSizer);
+ Layout();
+ Centre(wxBOTH);
+
+ m_lastChecked = m_radioHelper;
+ m_confirmBtn->Bind(wxEVT_BUTTON, &FeedDirectionDialog::OnConfirm, this);
+ m_leftRadio->Bind(wxEVT_RADIOBUTTON, &FeedDirectionDialog::OnRadioClicked, this);
+ m_rightRadio->Bind(wxEVT_RADIOBUTTON, &FeedDirectionDialog::OnRadioClicked, this);
+}
+
+void FeedDirectionDialog::OnConfirm(wxCommandEvent& event)
+{
+ EndModal(wxID_OK);
+}
+
+void FeedDirectionDialog::OnRadioClicked(wxCommandEvent& evt)
+{
+ auto clicked = static_cast(evt.GetEventObject());
+ m_load_extruder_id = std::nullopt;
+ if (clicked == m_lastChecked)
+ {
+ m_radioHelper->SetValue(true);
+ m_lastChecked = m_radioHelper;
+ m_confirmBtn->Enable(false);
+ m_extruderImage->update(DevExtruderState::EMPTY_LOAD, DevExtruderState::EMPTY_LOAD);
+ m_extruderImage->setExtruderUsed("");
+
+ }
+ else
+ {
+ clicked->SetValue(true);
+ m_lastChecked = clicked;
+ m_confirmBtn->Enable(true);
+
+ if (clicked == m_leftRadio)
+ {
+ m_extruderImage->update(DevExtruderState::FILLED_LOAD, DevExtruderState::EMPTY_LOAD);
+ m_extruderImage->setExtruderUsed("left");
+ m_load_extruder_id = 1;
+ {
+ SetTitle(wxString::Format(_L("Load %s to ") + _L(DevPrinterConfigUtil::get_toolhead_display_name(m_printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::LowerCase)), m_filament_id));
+ }
+ }
+ else if (clicked == m_rightRadio)
+ {
+ m_extruderImage->update(DevExtruderState::EMPTY_LOAD, DevExtruderState::FILLED_LOAD);
+ m_extruderImage->setExtruderUsed("right");
+ m_load_extruder_id = 0;
+ {
+ SetTitle(wxString::Format(_L("Load %s to ") + _L(DevPrinterConfigUtil::get_toolhead_display_name(m_printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::LowerCase)), m_filament_id));
+ }
+ }
+ }
+ m_leftRadio->Refresh();
+ m_rightRadio->Refresh();
+ Fit();
+ Update();
+}
+
+std::optional FeedDirectionDialog::GetExtruderID()
+{
+ if (m_extruderImage)
+ {
+ return m_load_extruder_id;
+ }
+ return std::nullopt;
+}
+
+wxString FeedDirectionDialog::calcTrayName(MachineObject* obj, const std::string& amsID, const std::string& slotID)
+{
+ if (amsID.empty() || slotID.empty() || !obj)
+ return wxString();
+
+ auto filaSys = obj->GetFilaSystem();
+ if (!filaSys)
+ return wxString();
+
+ DevAms* ams = filaSys->GetAmsById(amsID);
+ if (!ams)
+ return wxString();
+
+ int ams_id_int = std::stoi(amsID);
+ int slot_id_int = std::stoi(slotID);
+ int tray_id = 0;
+
+ // Orca: DevAms::AmsType is the local enum (AMS / AMS_LITE / N3F / N3S); it has no EXT_SPOOL
+ // member, and external/virtual spools are not real DevAms objects (GetAmsById returns nullptr
+ // above), so the EXT_SPOOL branch from upstream is unnecessary here.
+ if (ams->GetAmsType() == DevAms::AMS || ams->GetAmsType() == DevAms::AMS_LITE || ams->GetAmsType() == DevAms::N3F) {
+ tray_id = ams_id_int * 4 + slot_id_int;
+ } else if (ams->GetAmsType() == DevAms::N3S) {
+ tray_id = ams_id_int + slot_id_int;
+ } else {
+ return wxString();
+ }
+
+ return wxGetApp().transition_tridid(tray_id);
+}
+
+void FeedDirectionDialog::SetExtruderMapping(MachineObject* obj,
+ const std::string& currAmsId,
+ const std::string& currSlotId,
+ const std::vector>& extruderSlots)
+{
+ wxString filamentID = calcTrayName(obj, currAmsId, currSlotId);
+ if (filamentID.empty())
+ return;
+
+ m_filament_id = filamentID;
+ SetTitle(wxString::Format(_L("Load %s to "), filamentID));
+
+ std::vector extruderMapping(extruderSlots.size());
+ for (size_t i = 0; i < extruderSlots.size(); ++i) {
+ if (!extruderSlots[i].first.empty())
+ extruderMapping[i] = calcTrayName(obj, extruderSlots[i].first, extruderSlots[i].second);
+ }
+
+ if (extruderMapping.size() > 1 && extruderMapping[1] == filamentID) //left extruder
+ {
+ m_leftRadio->Enable(false);
+ m_confirmBtn->Enable(false);
+ m_extruderImage->update(DevExtruderState::EMPTY_LOAD, DevExtruderState::EMPTY_LOAD);
+ m_extruderImage->setExtruderUsed("right");
+ }
+ else if (!extruderMapping.empty() && extruderMapping[0] == filamentID) //right extruder
+ {
+ m_rightRadio->Enable(false);
+ m_confirmBtn->Enable(false);
+ m_extruderImage->update(DevExtruderState::EMPTY_LOAD, DevExtruderState::EMPTY_LOAD);
+ m_extruderImage->setExtruderUsed("left");
+ }
+ else
+ {
+ m_radioHelper->SetValue(true);
+ m_lastChecked = m_radioHelper;
+ m_confirmBtn->Enable(false);
+ m_extruderImage->update(DevExtruderState::EMPTY_LOAD, DevExtruderState::EMPTY_LOAD);
+ m_extruderImage->setExtruderUsed("");
+ }
+}
+
}} // namespace Slic3r::GUI
diff --git a/src/slic3r/GUI/Widgets/AMSItem.hpp b/src/slic3r/GUI/Widgets/AMSItem.hpp
index 676fec6ec6..526fa82f36 100644
--- a/src/slic3r/GUI/Widgets/AMSItem.hpp
+++ b/src/slic3r/GUI/Widgets/AMSItem.hpp
@@ -12,6 +12,7 @@
#include
#include
#include
+#include
#define AMS_CONTROL_BRAND_COLOUR wxColour(0, 150, 136)
@@ -400,6 +401,26 @@ private:
};
+// Orca: routing glyph shown on the AMS control when a Filament Track Switch is installed.
+class SwitcherImage: public wxWindow
+{
+public:
+ void setShowState(bool show_state) { m_show_state = show_state; };
+ // void msw_rescale();
+ void paintEvent(wxPaintEvent &evt);
+
+ void render(wxDC &dc);
+ bool m_show_state = {false};
+ wxColour m_colour;
+ ScalableBitmap m_switcher;
+ string m_file_name;
+ // bool m_ams_loading{ false };
+ void doRender(wxDC &dc);
+ SwitcherImage(wxWindow *parent, wxWindowID id, string file_name, const wxSize& size, const wxPoint &pos = wxDefaultPosition);
+ ~SwitcherImage();
+};
+
+
class AMSextruder : public wxWindow
{
private:
@@ -771,6 +792,9 @@ public:
void PlayRridLoading(wxString canid);
void StopRridLoading(wxString canid);
void msw_rescale();
+ // Orca: hide/show the road segment below the item; used to drop the external-spool road
+ // when a Filament Track Switch is installed. Returns true if the visibility actually changed.
+ bool ShowRoad(bool show);
void show_sn_value(bool show);
void SetAmsStepExtra(wxString canid, AMSPassRoadType type, AMSPassRoadSTEP step);
void SetAmsStep(std::string amsid, std::string canid, AMSPassRoadType type, AMSPassRoadSTEP step);
@@ -849,6 +873,111 @@ wxDECLARE_EVENT(EVT_AMS_UNSELETED_AMS, wxCommandEvent);
wxDECLARE_EVENT(EVT_VAMS_ON_FILAMENT_EDIT, wxCommandEvent);
wxDECLARE_EVENT(EVT_AMS_SWITCH, SimpleEvent);
+enum class DevExtruderState {
+ FILLED_LOAD,
+ FILLED_UNLOAD,
+ EMPTY_LOAD,
+ EMPTY_UNLOAD
+};
+
+class DevExtruderImage : public wxWindow
+{
+ ScalableBitmap *m_left_extruder_active_filled;
+ ScalableBitmap *m_left_extruder_active_empty;
+ ScalableBitmap *m_left_extruder_unactive_filled;
+ ScalableBitmap *m_left_extruder_unactive_empty;
+ ScalableBitmap *m_right_extruder_active_filled;
+ ScalableBitmap *m_right_extruder_active_empty;
+ ScalableBitmap *m_right_extruder_unactive_filled;
+ ScalableBitmap *m_right_extruder_unactive_empty;
+
+ ScalableBitmap *m_extruder_single_nozzle_empty_load;
+ ScalableBitmap *m_extruder_single_nozzle_empty_unload;
+ ScalableBitmap *m_extruder_single_nozzle_filled_load;
+ ScalableBitmap *m_extruder_single_nozzle_filled_unload;
+
+ DevExtruderState m_left_ext_state = {DevExtruderState::EMPTY_LOAD};
+ DevExtruderState m_right_ext_state = {DevExtruderState::EMPTY_LOAD};
+ DevExtruderState m_single_ext_state = {DevExtruderState::EMPTY_LOAD};
+
+public:
+ DevExtruderImage(wxWindow *parent, wxWindowID id,
+ int extruder_num,
+ const wxPoint &pos = wxDefaultPosition,
+ const wxSize &size = wxDefaultSize);
+ ~DevExtruderImage()
+ {
+
+ }
+ void update(DevExtruderState single_state)
+ {
+ m_single_ext_state = single_state;
+ }
+ void update(DevExtruderState left_state, DevExtruderState right_state)
+ {
+ m_left_ext_state = left_state;
+ m_right_ext_state = right_state;
+ }
+
+ void msw_rescale();
+ void setExtruderCount(int extruder_num)
+ {
+ m_extruder_num = extruder_num;
+ }
+ void setExtruderUsed(const std::string& loc)
+ {
+ if (current_extruder_loc == loc) { return; }
+ current_extruder_loc = loc;
+ Refresh();
+ }
+private:
+ void paintEvent(wxPaintEvent &evt)
+ {
+ wxPaintDC dc(this);
+ render(dc);
+ }
+ void render(wxDC &dc);
+ void doRender(wxDC &dc);
+ int m_extruder_num = 1;
+ std::string current_extruder_loc = "";
+
+};
+
+// Filament Track Switch: when the switch is installed and calibrated a filament can be routed to
+// either extruder, so this dialog lets the user pick. GetExtruderID() returns the chosen extruder
+// (1 = deputy/left, 0 = main/right) or nullopt if none was picked; that value is passed to
+// MachineObject::command_ams_change_filament. Only constructed on the FTS-ready path.
+class FeedDirectionDialog : public wxDialog
+{
+public:
+ FeedDirectionDialog(wxWindow* parent, const int extruderNum, const std::string& printer_type = "");
+
+ std::optional GetExtruderID();
+
+ void SetExtruderMapping(MachineObject* obj,
+ const std::string& currAmsId,
+ const std::string& currSlotId,
+ const std::vector>& extruderSlots);
+
+private:
+ static wxString calcTrayName(MachineObject* obj, const std::string& amsID, const std::string& slotID);
+
+ int m_extruder_num{};
+ std::string m_printer_type;
+ wxString m_filament_id{};
+ wxRadioButton* m_radioHelper{nullptr};
+ wxRadioButton* m_leftRadio{nullptr};
+ wxRadioButton* m_rightRadio{nullptr};
+ wxRadioButton* m_lastChecked{nullptr};
+ DevExtruderImage* m_extruderImage{nullptr};
+ Button* m_confirmBtn{nullptr};
+ std::optional m_load_extruder_id = std::nullopt;
+
+ void OnConfirm(wxCommandEvent& event);
+ void OnRadioClicked(wxCommandEvent& evt);
+
+};
+
}} // namespace Slic3r::GUI
#endif // !slic3r_GUI_amscontrol_hpp_
diff --git a/src/slic3r/GUI/Widgets/FilamentLoad.cpp b/src/slic3r/GUI/Widgets/FilamentLoad.cpp
index 502b153fef..2f2ef4dd92 100644
--- a/src/slic3r/GUI/Widgets/FilamentLoad.cpp
+++ b/src/slic3r/GUI/Widgets/FilamentLoad.cpp
@@ -3,6 +3,7 @@
#include "../BitmapCache.hpp"
#include "../I18N.hpp"
#include "../GUI_App.hpp"
+#include "../DeviceCore/DevFilaSystem.h"
#include
#include
@@ -37,6 +38,28 @@ FilamentLoad::FilamentLoad(wxWindow* parent, wxWindowID id, const wxPoint& pos,
//FILAMENT_CHANGE_STEP_STRING[FilamentStep::STEP_FEED_FILAMENT] = _L("Feed Filament");
FILAMENT_CHANGE_STEP_STRING[FilamentStep::STEP_CONFIRM_EXTRUDED] = _L("Confirm extruded");
FILAMENT_CHANGE_STEP_STRING[FilamentStep::STEP_CHECK_POSITION] = _L("Check filament location");
+
+ // Orca: labels for the device-numbered steps carried by the AMS (ams.cfs). Overlapping steps
+ // reuse the wording above so the current-step highlight (driven by the legacy ams_status_sub
+ // path) still text-matches; the switch/hotend/cooling and Filament Track Switch steps are new.
+ // STEP_CHECK_POSITION and STEP_CONFIRM_EXTRUDED share code 0x08, so CONFIRM is assigned first
+ // and CHECK_POSITION last, leaving code 0x08 mapped to "Check filament location" as on device.
+ DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_IDLE] = _L("Idling...");
+ DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_PAUSE] = _L("Pause");
+ DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_HEAT_NOZZLE] = _L("Heat the nozzle");
+ DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_CUT_FILAMENT] = _L("Cut filament");
+ DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_PULL_CURR_FILAMENT] = _L("Pull back the current filament");
+ DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_PUSH_NEW_FILAMENT] = _L("Push new filament into extruder");
+ DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_GRAB_NEW_FILAMENT] = _L("Grab new filament");
+ DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_PURGE_OLD_FILAMENT] = _L("Purge old filament");
+ DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_SWITCH_EXTRUDER] = _L("Switch") + " " + _L("extruder");
+ DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_SWITCH_HOTEND] = _L("Switch") + " " + _L("hotend");
+ DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_AMS_FILA_COOLING] = _L("Wait for AMS cooling");
+ DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_PUSH_SWITCHER_FILA] = _L("Switch current filament at Filament Track Switch");
+ DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_PULL_SWITCHER_FILA] = _L("Pull back current filament at Filament Track Switch");
+ DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_SWITCHER_SWITCH] = _L("Switch track at Filament Track Switch");
+ DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_CONFIRM_EXTRUDED] = _L("Confirm extruded");
+ DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_CHECK_POSITION] = _L("Check filament location");
}
void FilamentLoad::SetFilamentStep(FilamentStep item_idx, FilamentStepType f_type)
@@ -124,11 +147,34 @@ void FilamentLoad::SetFilamentStep(FilamentStep item_idx, FilamentStepType f_typ
step_control->SetSlotInformation(slot_info);
}
-void FilamentLoad::SetupSteps(bool has_fila_to_switch) {
+void FilamentLoad::SetupSteps(MachineObject* obj_, bool has_fila_to_switch) {
m_filament_load_steps->DeleteAllItems();
m_filament_unload_steps->DeleteAllItems();
m_filament_vt_load_steps->DeleteAllItems();
+ // Orca: newer firmware sends the exact change-step sequence through the AMS (ams.cfs). When
+ // present, build the visible steps straight from that list and skip the hardcoded per-model
+ // sequences below. Inert on current firmware: the list is empty, so this branch is skipped and
+ // the legacy logic runs unchanged.
+ if (obj_ && obj_->GetFilaSystem() && !obj_->GetFilaSystem()->GetFilamentChangeSteps().empty()) {
+ const auto& steps = obj_->GetFilaSystem()->GetFilamentChangeSteps();
+ for (auto step : steps) {
+ auto iter = DEV_FILAMENT_CHANGE_STEP_STRING.find(step);
+ if (iter == DEV_FILAMENT_CHANGE_STEP_STRING.end()) {
+ BOOST_LOG_TRIVIAL(error) << "Unknown filament change step: " << static_cast(step);
+ continue;
+ }
+
+ m_filament_load_steps->AppendItem(iter->second);
+ m_filament_unload_steps->AppendItem(iter->second);
+ m_filament_vt_load_steps->AppendItem(iter->second);
+ }
+
+ Layout();
+ Fit();
+ return;
+ }
+
if (m_ams_model == AMSModel::GENERIC_AMS || m_ext_model == AMSModel::N3F_AMS || m_ext_model == AMSModel::N3S_AMS) {
if (has_fila_to_switch) {
m_filament_load_steps->AppendItem(FILAMENT_CHANGE_STEP_STRING[FilamentStep::STEP_HEAT_NOZZLE]);
diff --git a/src/slic3r/GUI/Widgets/FilamentLoad.hpp b/src/slic3r/GUI/Widgets/FilamentLoad.hpp
index c358816b77..e3cc6daf67 100644
--- a/src/slic3r/GUI/Widgets/FilamentLoad.hpp
+++ b/src/slic3r/GUI/Widgets/FilamentLoad.hpp
@@ -35,6 +35,10 @@ protected:
public:
std::map FILAMENT_CHANGE_STEP_STRING;
+ // Labels for the device-numbered steps reported by the AMS (ams.cfs). Keyed by the device
+ // enum, separate from the legacy FilamentStep map above. Only used when the AMS provides a
+ // step list; empty AMS list falls back to the legacy sequences below.
+ std::map DEV_FILAMENT_CHANGE_STEP_STRING;
AMSModel m_ams_model{ AMSModel::GENERIC_AMS };
AMSModel m_ext_model{ AMSModel::AMS_LITE };
AMSModel m_is_none_ams_mode{ AMSModel::AMS_LITE };
@@ -44,7 +48,7 @@ public:
void SetFilamentStep(FilamentStep item_idx, FilamentStepType f_type);
void ShowFilamentTip(bool hasams = true);
- void SetupSteps(bool is_extrusion_exist);
+ void SetupSteps(MachineObject* obj_, bool is_extrusion_exist);
void show_nofilament_mode(bool show);
void updateID(int ams_id, int slot_id) { m_ams_id = ams_id; m_slot_id = slot_id; };