mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 19:01:02 +00:00
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:
@@ -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)) {
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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"); }
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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 };
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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; };
|
||||
|
||||
Reference in New Issue
Block a user