feat: Filament Track Switch (H2-series O2L-FTS) support

The Filament Track Switch (H2-series accessory, product code O2L-FTS) feeds
every AMS to both extruders through a two-track switch. Port full support
across the device layer, project config, and GUI.

Device / config:
- Model the switch-aware AMS binding (the set of extruders an AMS can feed
  and which input track A/B feeds it), switch readiness, the O2L-FTS firmware
  module, and the fun2 capability bit for checking a slice against installed
  hardware.
- Register has_filament_switcher and enable_filament_dynamic_map as project
  config that persists with the project and restores from a saved 3mf, and
  force both back to false on every project/printer/CLI load path. Live
  device sync is the only thing that sets them true.

GUI:
- Sidebar sync activates the switch from live device state, attributes each
  AMS to the extruder its input track feeds, shows a floating status icon
  (ready / not-calibrated), and surfaces a one-time tip / not-calibrated
  warning.
- Send dialog gains a non-blocking slice-vs-hardware mismatch warning and a
  blocking error when a slice needs dynamic nozzle mapping but the switch is
  missing or not set up.
- AMS load/unload guards, AMS-view routing glyph + un-calibrated banner +
  hidden external-spool road, and mapping-popup external-spool lockout.
- Filament pickers collapse the per-extruder split into a single deduplicated
  "AMS filaments" group with a smart-assign toggle when the switch is ready.
- Firmware-upgrade panel lists the O2L-FTS accessory and its version.
- Device-provided filament-change steps (ams.cfs) drive the change-step
  display when firmware sends them, including the three switch steps.
- "Load current filament" asks which extruder to feed via a
  FeedDirectionDialog when the switch is calibrated.

Inert without the accessory: every path is gated on the switch being
installed (MQTT aux bit 29, default off) or ready, both project flags default
false, and the per-extruder AMS attribution is byte-identical, so AMS state,
the send/load UI, and sliced g-code are unchanged for every printer that does
not report a Filament Track Switch.
This commit is contained in:
SoftFever
2026-07-09 18:57:05 +08:00
parent 26a0caff96
commit 0d31325df0
38 changed files with 1655 additions and 38 deletions
+22
View File
@@ -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<FilamentInfo>&
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)) {
+3
View File
@@ -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};
+25
View File
@@ -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
@@ -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;
+5 -6
View File
@@ -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<bool> IsInA_HasFilament() const { return m_in_a_has_filament; };
std::optional<bool> 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;
@@ -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<std::vector<DevFilamentStep>>(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<std::string>(), 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<int> binded_extruder_set;
std::optional<DevFilaSwitch::SwitchPos> binded_switcher_pos;
if (it->contains("info")) {
const std::string& info = (*it)["info"].get<std::string>();
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
+31
View File
@@ -4,9 +4,12 @@
#include "DevDefs.h"
#include "DevFilaAmsSetting.h"
#include "DevFilaSwitch.h"
#include "DevUtil.h"
#include <map>
#include <set>
#include <vector>
#include <optional>
#include <memory>
#include <wx/string.h>
@@ -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<int>& 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<int> GetUniqueBindedExtruderId() const
{
return m_binded_extruder_set.size() == 1 ? std::optional<int>(*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<DevFilaSwitch::SwitchPos> 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<int> m_binded_extruder_set;
std::optional<DevFilaSwitch::SwitchPos> m_binded_switcher_pos;
bool m_exist = false;
// slots and trays
@@ -268,6 +291,11 @@ public:
std::weak_ptr<DevAmsSystemFirmwareSwitch> 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<DevFilamentStep>& 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<DevFilamentStep> m_filament_change_steps;
std::map<std::string, DevAms*, NumericStrCompare> amsList;// key: ams[id], start with 0
DevAmsSystemSetting m_ams_system_setting{ this };
+1
View File
@@ -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"); }
};
+12 -1
View File
@@ -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<int> 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*/
+4 -1
View File
@@ -8,6 +8,7 @@
#include <memory>
#include <chrono>
#include <unordered_set>
#include <optional>
#include <boost/thread.hpp>
#include <boost/nowide/fstream.hpp>
#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<std::string, DevFirmwareVersionInfo> module_vers;
std::map<std::string, DevFirmwareVersionInfo> 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<int> 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);
+65
View File
@@ -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, int>(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<ConfigOptionBool *>(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<ConfigOptionBool *>(config.option("enable_filament_dynamic_map"));
if (dynamic_filament) { m_smart_filament_switch->SetValue(dynamic_filament->value); }
}
}
}} // namespace Slic3r::GUI
+11
View File
@@ -3,10 +3,13 @@
#include <wx/bitmap.h>
#include <wx/bmpbuttn.h>
#include <wx/sizer.h>
#include <wx/timer.h>
#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 };
};
+85 -3
View File
@@ -39,6 +39,68 @@ static std::vector<int> 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<ConfigOptionBool *>(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();
}
+4
View File
@@ -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;
+264 -16
View File
@@ -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<NozzleOption> 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<wxWindow *>(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<int>(switcher_pos.value()))
: static_cast<int>(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<ConfigOptionBool>("enable_filament_dynamic_map"))
dynamic_filament->value = is_fila_switch_ready();
if (auto *has_switcher = project_config.opt<ConfigOptionBool>("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<ExtruderInfo> extruder_infos(extruder_nums);
std::vector<int> nozzle_volume_types = wxGetApp().preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("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<int>(switcher_pos.value()))
: static_cast<int>(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<int, DynamicPrintConfig> 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<ExtruderGroup *>(dynamic_cast<ComboBox *>(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<size_t> Plater::priv::load_files(const std::vector<fs::path>& 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<ConfigOptionBool>("has_filament_switcher"))
has_switcher->value = false;
if (auto* dynamic_map = proj_cfg.opt<ConfigOptionBool>("enable_filament_dynamic_map"))
dynamic_map->value = false;
}
// Update filament combobox after loading config
wxGetApp().plater()->sidebar().update_presets(Preset::TYPE_FILAMENT);
+2
View File
@@ -196,6 +196,8 @@ public:
std::map<int, DynamicPrintConfig> 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();
+2
View File
@@ -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";
+2
View File
@@ -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
+21 -2
View File
@@ -3,6 +3,8 @@
#include <cstddef>
#include <vector>
#include <string>
#include <set>
#include <utility>
#include <algorithm>
#include <boost/algorithm/string.hpp>
@@ -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<std::pair<std::string, std::string>> 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()) {
+64
View File
@@ -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<ConfigOptionBool>("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<ConfigOptionBool>("has_filament_switcher");
if (has_filament_switcher)
return has_filament_switcher->value;
}
auto has_filament_switcher = m_required_data_config.option<ConfigOptionBool>("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::vector<wxSt
// Extra-waste warning: allow Send.
Enable_Refresh_Button(true);
Enable_Send_Button(true);
} else if (status == PrintDialogStatus::PrintStatusFilaSwitcherError) {
// Missing or un-set-up switch required by the slice: block Send.
Enable_Refresh_Button(true);
Enable_Send_Button(false);
} else if (status == PrintDialogStatus::PrintStatusFilaSwitcherSlicingNotMatch) {
// Advisory slicing/hardware mismatch: allow Send.
Enable_Refresh_Button(true);
Enable_Send_Button(true);
}
/*enter perpare mode*/
@@ -3795,6 +3855,10 @@ void SelectMachineDialog::update_show_status(MachineObject* obj_)
}
}
// Filament Track Switch: warn on a slicing/hardware mismatch, and block Send when dynamic
// nozzle mapping needs a switch that isn't installed or set up. No-op for printers without one.
if (!CheckErrorDynamicSwitchNozzle(obj_)) return;
// Rack print-dispatch nozzle mapping (dynamic V1): request/await the printer's mapping and block
// Send while it computes. No-op for non-rack printers.
if (!CheckErrorSyncNozzleMappingResultV1(obj_)) return;
+6
View File
@@ -526,6 +526,12 @@ public:
bool CheckErrorSyncNozzleMappingResultV0(MachineObject* obj_);
void clear_nozzle_mapping();
bool use_dynamic_nozzle_map() const;
// Filament Track Switch: warn when the sliced switch state doesn't match the installed
// hardware, and (for dynamic nozzle mapping) block Send when the switch is missing or not
// set up. slicing_with_fila_switch() reports whether the file was sliced assuming a switch.
bool slicing_with_fila_switch() const;
bool CheckErrorDynamicSwitchNozzle(MachineObject* obj_);
void on_flow_cali_option_changed();
PrintFromType get_print_type() {return m_print_type;};
+52 -5
View File
@@ -2258,7 +2258,7 @@ void StatusBasePanel::show_filament_load_group(bool show)
}
auto cur_ext = obj->GetExtderSystem()->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<int> 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<std::pair<std::string, std::string>> 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);
}
}
}
+12
View File
@@ -5636,6 +5636,18 @@ void TabPrinter::on_preset_loaded()
if (use_default_nozzle_volume_type) {
m_preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values = current_printer.config.option<ConfigOptionEnumsGeneric>("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<ConfigOptionBool>("has_filament_switcher"))
has_switcher->value = false;
if (auto* dynamic_map = m_preset_bundle->project_config.opt<ConfigOptionBool>("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();
}
}
+48
View File
@@ -28,6 +28,7 @@ static const std::unordered_map<wxString, wxString> 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
+10
View File
@@ -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);
+83 -1
View File
@@ -9,9 +9,11 @@
#include "slic3r/GUI/DeviceCore/DevManager.h"
#include "slic3r/GUI/DeviceCore/DevFilaSystem.h"
#include "slic3r/GUI/DeviceCore/DevFilaSwitch.h"
#include <wx/simplebook.h>
#include <wx/dcgraph.h>
#include <wx/artprov.h>
#include <boost/log/trivial.hpp>
@@ -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<bool, bool> 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)
{
+14
View File
@@ -13,6 +13,7 @@
#include <wx/hyperlink.h>
#include <wx/animate.h>
#include <wx/dynarray.h>
#include <tuple>
#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<bool, bool> 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);
+407
View File
@@ -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<wxRadioButton*>(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<int> 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<std::pair<std::string, std::string>>& 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<wxString> 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
+129
View File
@@ -12,6 +12,7 @@
#include <wx/hyperlink.h>
#include <wx/animate.h>
#include <wx/dynarray.h>
#include <optional>
#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<int> GetExtruderID();
void SetExtruderMapping(MachineObject* obj,
const std::string& currAmsId,
const std::string& currSlotId,
const std::vector<std::pair<std::string, std::string>>& 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<int> m_load_extruder_id = std::nullopt;
void OnConfirm(wxCommandEvent& event);
void OnRadioClicked(wxCommandEvent& evt);
};
}} // namespace Slic3r::GUI
#endif // !slic3r_GUI_amscontrol_hpp_
+47 -1
View File
@@ -3,6 +3,7 @@
#include "../BitmapCache.hpp"
#include "../I18N.hpp"
#include "../GUI_App.hpp"
#include "../DeviceCore/DevFilaSystem.h"
#include <wx/simplebook.h>
#include <wx/dcgraph.h>
@@ -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<int>(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]);
+5 -1
View File
@@ -35,6 +35,10 @@ protected:
public:
std::map<FilamentStep, wxString> 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<DevFilamentStep, wxString> 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; };