Merge branch 'main' into dev/ams-heat

# Conflicts:
#	src/libslic3r/Preset.cpp
#	src/libslic3r/PrintConfig.hpp
#	src/slic3r/GUI/DeviceCore/CMakeLists.txt
#	src/slic3r/GUI/DeviceCore/DevDefs.h
#	src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp
#	src/slic3r/GUI/DeviceCore/DevFilaSystem.h
#	src/slic3r/GUI/DeviceCore/DevUtilBackend.cpp
#	src/slic3r/GUI/DeviceCore/DevUtilBackend.h
#	src/slic3r/GUI/DeviceManager.cpp
#	src/slic3r/GUI/DeviceManager.hpp
#	src/slic3r/GUI/SelectMachine.cpp
#	src/slic3r/GUI/SelectMachine.hpp
This commit is contained in:
Noisyfox
2026-07-14 09:48:35 +08:00
702 changed files with 85605 additions and 2732 deletions

View File

@@ -28,8 +28,8 @@ list(APPEND SLIC3R_GUI_SOURCES
GUI/DeviceCore/DevFilaSystem.h
GUI/DeviceCore/DevFilaSystem.cpp
GUI/DeviceCore/DevFilaSystemCtrl.cpp
GUI/DeviceCore/DevUtilBackend.h
GUI/DeviceCore/DevUtilBackend.cpp
GUI/DeviceCore/DevFilaSwitch.h
GUI/DeviceCore/DevFilaSwitch.cpp
GUI/DeviceCore/DevFirmware.h
GUI/DeviceCore/DevFirmware.cpp
GUI/DeviceCore/DevPrintOptions.h
@@ -49,10 +49,17 @@ list(APPEND SLIC3R_GUI_SOURCES
GUI/DeviceCore/DevManager.cpp
GUI/DeviceCore/DevMapping.h
GUI/DeviceCore/DevMapping.cpp
GUI/DeviceCore/DevMappingNozzle.h
GUI/DeviceCore/DevMappingNozzle.cpp
GUI/DeviceCore/DevNozzleSystem.h
GUI/DeviceCore/DevNozzleSystem.cpp
GUI/DeviceCore/DevNozzleRack.h
GUI/DeviceCore/DevNozzleRack.cpp
GUI/DeviceCore/DevNozzleRackCtrl.cpp
GUI/DeviceCore/DevUtil.h
GUI/DeviceCore/DevUtil.cpp
GUI/DeviceCore/DevUtilBackend.h
GUI/DeviceCore/DevUtilBackend.cpp
)
set(SLIC3R_GUI_SOURCES ${SLIC3R_GUI_SOURCES} PARENT_SCOPE)

View File

@@ -87,6 +87,28 @@ std::string DevPrinterConfigUtil::get_printer_ext_img(const std::string& type_st
return (vec.size() > pos) ? vec[pos] : std::string();
};
std::string DevPrinterConfigUtil::get_filament_load_img(const std::string &type_str, int ext_id, bool has_nozzle_rack)
{
if (has_nozzle_rack)
{
const auto &rack_vec = get_value_from_config<std::vector<std::string>>(type_str, "filament_load_image_nozzle_rack");
if (!rack_vec.empty())
{
if (ext_id >= 0 && static_cast<size_t>(ext_id) < rack_vec.size())
{
return rack_vec[ext_id];
}
return rack_vec[0];
}
}
const auto &vec = get_value_from_config<std::vector<std::string>>(type_str, "filament_load_image");
if (ext_id >= 0 && static_cast<size_t>(ext_id) < vec.size())
{
return vec[ext_id];
}
return vec.empty() ? std::string() : vec[0];
}
std::string DevPrinterConfigUtil::get_fan_text(const std::string& type_str, const std::string& key)
{
std::vector<std::string> filaments;

View File

@@ -74,6 +74,7 @@ public:
static std::string get_printer_use_ams_type(std::string type_str) { return get_value_from_config<std::string>(type_str, "use_ams_type"); }
static std::string get_printer_ams_img(const std::string& type_str) { return get_value_from_config<std::string>(type_str, "printer_use_ams_image"); }
static std::string get_printer_ext_img(const std::string& type_str, int pos);//printer_ext_image
static std::string get_filament_load_img(const std::string &type_str, int ext_id, bool has_nozzle_rack = false);
/*fan*/
static std::string get_fan_text(const std::string& type_str, const std::string& key);

View File

@@ -10,6 +10,7 @@
#pragma once
#include <string>
#include <utility>
enum PrinterArch
{
@@ -47,6 +48,32 @@ enum DevAmsType : int
AMS_LITE = 2, // AMS-Lite
N3F = 3, // N3F, AMS 2PRO
N3S = 4, // N3S, AMS HT
AMS_LITE_MIXED = 5, // AMS-Lite for N9
};
// 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
@@ -56,21 +83,44 @@ enum DevAmsType : int
#define VIRTUAL_AMS_MAIN_ID_STR "255"
#define VIRTUAL_AMS_DEPUTY_ID_STR "254"
// Tray index offset for the AMS-Lite-mixed unit (A2L / N9). Its 4 trays occupy
// global tray indices 24..27.
#define AMS_LITE_MIXED_TRAY_INDEX_OFFSET 24
#define INVALID_AMS_TEMPERATURE std::numeric_limits<float>::min()
// (ams_id, slot_id) pair.
using DevAmsSlotId = std::pair<int, int>;
/* Extruder*/
#define MAIN_EXTRUDER_ID 0
#define DEPUTY_EXTRUDER_ID 1
#define UNIQUE_EXTRUDER_ID MAIN_EXTRUDER_ID
#define INVALID_EXTRUDER_ID -1
/* Logical extruder ids (multi-nozzle). */
#define LOGIC_UNIQUE_EXTRUDER_ID 0
#define LOGIC_L_EXTRUDER_ID 0
#define LOGIC_R_EXTRUDER_ID 1
/* Nozzle*/
enum NozzleFlowType
{
NONE_FLOWTYPE,
S_FLOW,
H_FLOW
H_FLOW,
U_FLOW, // TPU 1.75 High Flow (device-reported; maps to nvtTPUHighFlow)
};
// Discrete nozzle diameters reported by the device.
enum NozzleDiameterType : int
{
NONE_DIAMETER_TYPE,
NOZZLE_DIAMETER_0_2,
NOZZLE_DIAMETER_0_4,
NOZZLE_DIAMETER_0_6,
NOZZLE_DIAMETER_0_8
};
/*Print speed*/
@@ -105,4 +155,39 @@ public:
static bool IsVirtualSlot(const std::string& ams_id) { return (ams_id == VIRTUAL_AMS_MAIN_ID_STR || ams_id == VIRTUAL_AMS_DEPUTY_ID_STR); }
};
};// namespace Slic3r
namespace GUI
{
// Print source. Defined here (rather than in SelectMachine.hpp) so the shared device-mapping
// GUI headers — AmsMappingPopup, wgtDeviceNozzleSelect — can name it without pulling in
// SelectMachine.hpp, which would create an include cycle.
enum PrintFromType
{
FROM_NORMAL,
FROM_SDCARD_VIEW,
};
}
};// namespace Slic3r
// A nozzle requirement of the sliced plate (or an installed nozzle's identity), compared in the
// pre-print slicing-vs-installed nozzle checks.
struct NozzleDef
{
float nozzle_diameter;
Slic3r::NozzleFlowType nozzle_flow_type;
bool operator==(const NozzleDef& other) const
{
return nozzle_diameter == other.nozzle_diameter && nozzle_flow_type == other.nozzle_flow_type;
}
};
template<> struct std::hash<NozzleDef>
{
std::size_t operator()(const NozzleDef& v) const noexcept
{
size_t h1 = std::hash<int>{}(v.nozzle_diameter * 1000);
size_t h2 = std::hash<int>{}(v.nozzle_flow_type);
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));
};
};

View File

@@ -1,6 +1,7 @@
#include <nlohmann/json.hpp>
#include "DevExtruderSystem.h"
#include "DevNozzleSystem.h"
#include "DevFilaSystem.h" // complete type for GetFilaSystem()->GetTrayIndexMap() in GetBackupAmsSlotInGroup
// TODO: remove this include
#include "slic3r/GUI/DeviceManager.hpp"
@@ -48,17 +49,43 @@ namespace Slic3r
NozzleType DevExtder::GetNozzleType() const
{
return system->Owner()->GetNozzleSystem()->GetNozzle(m_current_nozzle_id).m_nozzle_type;
return system->Owner()->GetNozzleSystem()->GetExtNozzle(m_current_nozzle_id).m_nozzle_type;
}
NozzleFlowType DevExtder::GetNozzleFlowType() const
{
return system->Owner()->GetNozzleSystem()->GetNozzle(m_current_nozzle_id).m_nozzle_flow;
return system->Owner()->GetNozzleSystem()->GetExtNozzle(m_current_nozzle_id).m_nozzle_flow;
}
float DevExtder::GetNozzleDiameter() const
{
return system->Owner()->GetNozzleSystem()->GetNozzle(m_current_nozzle_id).m_diameter;
return system->Owner()->GetNozzleSystem()->GetExtNozzle(m_current_nozzle_id).m_diameter;
}
std::unordered_map<int, bool> DevExtder::GetBackupStatus(unsigned int fila_back_group)
{
std::unordered_map<int, bool> trayid_group;
for (int i = 0; i < 16; i++)
{
if (fila_back_group & (1 << i))
{
trayid_group[i] = true;
}
}
for (int j = 16; j <= 23; j++)/* single ams is from 128*/
{
if (fila_back_group & (1 << j)) {
trayid_group[128 + j - 16] = true;
}
}
for (int i = 24; i <= 27; i++) /* ams lite for N9*/
{
if (fila_back_group & (1 << i)) { trayid_group[i] = true; }
}
return trayid_group;
}
DevExtderSystem::DevExtderSystem(MachineObject* obj)
@@ -143,6 +170,24 @@ namespace Slic3r
return false;
}
std::vector<DevAmsSlotId> DevExtderSystem::GetBackupAmsSlotInGroup(const DevAmsSlotId& ams_slot_id)
{
std::map<int, DevAmsSlotId> tray_map = Owner()->GetFilaSystem()->GetTrayIndexMap();
std::vector<DevAmsSlotId> backup_group;
for (const auto& extruder : m_extders) {
for (int fila_backup : extruder.GetFilamBackup()) {
for (auto [tray_id, is_valid] : DevExtder::GetBackupStatus(fila_backup)) {
if (is_valid && tray_map.find(tray_id) != tray_map.end() && tray_map[tray_id] != ams_slot_id) {
backup_group.emplace_back(tray_map[tray_id]);
}
}
}
}
return backup_group;
}
void ExtderSystemParser::ParseV1_0(const nlohmann::json& print_json, DevExtderSystem* system)
{
if (system->GetTotalExtderCount() != 1)

View File

@@ -1,5 +1,6 @@
#pragma once
#include <optional>
#include <unordered_map>
#include "libslic3r/CommonDefs.hpp"
#include "slic3r/Utils/json_diff.hpp"
@@ -66,6 +67,10 @@ public:
bool HasFilamBackup() const { return !m_filam_bak.empty(); }
std::vector<int> GetFilamBackup() const { return m_filam_bak; }
// Decode a filament-backup bit group into { tray_id -> valid }. Bits 0-15 map directly to tray ids,
// bits 16-23 to single-AMS tray ids 128+, bits 24-27 to AMS-Lite-mixed (N9) tray ids 24-27.
static std::unordered_map<int, bool> GetBackupStatus(unsigned int fila_back_group);
// ams binding on current extruder
const DevAmsSlotInfo& GetSlotPre() const { return m_spre; }
const DevAmsSlotInfo& GetSlotNow() const { return m_snow; }
@@ -168,6 +173,11 @@ public:
bool HasFilamentBackup() const;
bool HasFilamentInExt(int exter_id) { return GetExtderById(exter_id) ? GetExtderById(exter_id)->HasFilamentInExt() : false; }
// List the other AMS slots that back up the given slot within the same filament-backup group,
// across all extruders. Uses DevFilaSystem::GetTrayIndexMap() to translate backup bits (incl. the
// A2L/N9 AMS-Lite-mixed trays 24-27) into ams/slot ids.
std::vector<DevAmsSlotId> GetBackupAmsSlotInGroup(const DevAmsSlotId& ams_slot_id);
protected:
void AddExtder(const DevExtder& ext) { m_extders[ext.GetExtId()] = ext; };

View File

@@ -1,8 +1,12 @@
#include <nlohmann/json.hpp>
#include <algorithm>
#include <set>
#include "DevFilaBlackList.h"
#include "DevFilaSystem.h"
#include "DevManager.h"
#include "DevConfigUtil.h"
#include "libslic3r/Utils.hpp"
@@ -73,64 +77,97 @@ static std::string _get_filament_name_from_ams(int ams_id, int slot_id)
}
// moved from tao.wang and zhimin.zeng
void check_filaments(std::string model_id,
std::string tag_vendor,
std::string tag_type,
int ams_id,
int slot_id,
std::string tag_name,
bool& in_blacklist,
std::string& ac,
wxString& info,
wxString& wiki_url)
// Struct-in / struct-out, accumulate-all parser: every matching rule is pushed into
// result.action_items[action] and scanning continues (rather than stopping at the first match).
void check_filaments(const DevFilaBlacklist::CheckFilamentInfo& check_info, DevFilaBlacklist::CheckResult& result)
{
std::string tag_type = check_info.fila_type;
std::string tag_name = check_info.fila_name;
std::string tag_vendor = check_info.fila_vendor;
std::string tag_calib_mode = check_info.calib_mode;
// Orca: the print-send and calibration consumers do not populate fila_name; recover it from the
// selected AMS slot so name / name_suffix rules keep matching.
if (tag_name.empty())
{
tag_name = _get_filament_name_from_ams(ams_id, slot_id);
tag_name = _get_filament_name_from_ams(check_info.ams_id, check_info.slot_id);
}
in_blacklist = false;
std::transform(tag_vendor.begin(), tag_vendor.end(), tag_vendor.begin(), ::tolower);
std::transform(tag_type.begin(), tag_type.end(), tag_type.begin(), ::tolower);
std::transform(tag_name.begin(), tag_name.end(), tag_name.begin(), ::tolower);
std::transform(tag_calib_mode.begin(), tag_calib_mode.end(), tag_calib_mode.begin(), ::tolower);
DevFilaBlacklist::load_filaments_blacklist_config();
if (DevFilaBlacklist::filaments_blacklist.contains("blacklist"))
{
for (auto filament_item : DevFilaBlacklist::filaments_blacklist["blacklist"])
{
std::string vendor = filament_item.contains("vendor") ? filament_item["vendor"].get<std::string>() : "";
std::string type = filament_item.contains("type") ? filament_item["type"].get<std::string>() : "";
std::string type_suffix = filament_item.contains("type_suffix") ? filament_item["type_suffix"].get<std::string>() : "";
std::string name = filament_item.contains("name") ? filament_item["name"].get<std::string>() : "";
std::string slot = filament_item.contains("slot") ? filament_item["slot"].get<std::string>() : "";
std::vector<std::string> model_ids = filament_item.contains("model_id") ? filament_item["model_id"].get<std::vector<std::string>>() : std::vector<std::string>();
std::string action = filament_item.contains("action") ? filament_item["action"].get<std::string>() : "";
std::string description = filament_item.contains("description") ? filament_item["description"].get<std::string>() : "";
// blacklist items
std::string vendor = filament_item.contains("vendor") ? filament_item["vendor"].get<std::string>() : "";
std::string type = filament_item.contains("type") ? filament_item["type"].get<std::string>() : "";
std::vector<std::string> types = filament_item.contains("types") ? filament_item["types"].get<std::vector<std::string>>() : std::vector<std::string>();
std::string type_suffix = filament_item.contains("type_suffix") ? filament_item["type_suffix"].get<std::string>() : "";
std::string name_suffix = filament_item.contains("name_suffix") ? filament_item["name_suffix"].get<std::string>() : "";
std::string name = filament_item.contains("name") ? filament_item["name"].get<std::string>() : "";
std::string slot = filament_item.contains("slot") ? filament_item["slot"].get<std::string>() : "";
std::optional<bool> used_for_print_support = filament_item.contains("used_for_print_support") ? filament_item["used_for_print_support"].get<bool>() : std::optional<bool>();
std::optional<bool> used_for_print_object = filament_item.contains("used_for_print_object") ? filament_item["used_for_print_object"].get<bool>() : std::optional<bool>();
std::vector<std::string> model_ids = filament_item.contains("model_id") ? filament_item["model_id"].get<std::vector<std::string>>() : std::vector<std::string>();
std::vector<int> extruder_ids = filament_item.contains("extruder_id") ? filament_item["extruder_id"].get<std::vector<int>>() : std::vector<int>();
std::vector<std::string> nozzle_flows = filament_item.contains("nozzle_flows") ? filament_item["nozzle_flows"].get<std::vector<std::string>>() : std::vector<std::string>();
std::string calib_mode = filament_item.contains("calib_mode") ? filament_item["calib_mode"].get<std::string>() : "";
// check model id
if (!model_ids.empty() && std::find(model_ids.begin(), model_ids.end(), model_id) == model_ids.end()) { continue; }
if (!model_ids.empty() && std::find(model_ids.begin(), model_ids.end(), check_info.model_id) == model_ids.end()) { continue; }
// A rule keyed on nozzle_flows/nozzle_diameters only matches when check_info carries real
// per-nozzle context (threaded by the print-send consumer). Consumers that leave
// nozzle_flow/nozzle_diameter unset (AMS edit, calibration, and the no-nozzle-context
// fallback) never satisfy the non-empty match below, so those rules stay dormant there.
// check nozzle flows
if (!nozzle_flows.empty() && std::find(nozzle_flows.begin(), nozzle_flows.end(), check_info.nozzle_flow.value_or("")) == nozzle_flows.end()) { continue; }
// check nozzle diameter
const std::vector<float> nozzle_diameters = filament_item.contains("nozzle_diameters") ? filament_item["nozzle_diameters"].get<std::vector<float>>() : std::vector<float>();
if (!nozzle_diameters.empty() && check_info.nozzle_diameter.has_value() &&
std::find(nozzle_diameters.begin(), nozzle_diameters.end(), check_info.nozzle_diameter.value()) == nozzle_diameters.end()) {
continue;
}
// check vendor
std::transform(vendor.begin(), vendor.end(), vendor.begin(), ::tolower);
if (!vendor.empty())
{
if ((vendor == "bambu lab" && (tag_vendor == vendor)) ||
(vendor == "third party" && (tag_vendor != "bambu lab")))
{
// Do nothing
}
else
if (!((vendor == "bambu lab" && tag_vendor == "bambu lab") ||
(vendor == "third party" && tag_vendor != "bambu lab")))
{
continue;
}
}
// check calib mode
std::transform(calib_mode.begin(), calib_mode.end(), calib_mode.begin(), ::tolower);
if (!calib_mode.empty() && calib_mode != tag_calib_mode) { continue; }
// check type
std::transform(type.begin(), type.end(), type.begin(), ::tolower);
if (!type.empty() && (type != tag_type)) { continue; }
// check types (plural): item matches if tag_type is any of the listed types.
if (!types.empty())
{
auto it = std::find_if(types.begin(), types.end(), [&tag_type](std::string ttype) {
std::transform(ttype.begin(), ttype.end(), ttype.begin(), ::tolower);
return ttype == tag_type;
});
if (it == types.end()) { continue; }
}
// check type suffix
std::transform(type_suffix.begin(), type_suffix.end(), type_suffix.begin(), ::tolower);
if (!type_suffix.empty())
@@ -143,10 +180,31 @@ void check_filaments(std::string model_id,
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
if (!name.empty() && (name != tag_name)) { continue; }
// check name suffix
std::transform(name_suffix.begin(), name_suffix.end(), name_suffix.begin(), ::tolower);
if (!name_suffix.empty())
{
if (tag_name.length() < name_suffix.length()) { continue; }
if ((tag_name.substr(tag_name.length() - name_suffix.length()) != name_suffix)) { continue; }
}
// check filament used for print support
if (used_for_print_support.has_value() && used_for_print_support != check_info.used_for_print_support) { continue; }
// check filament used for print object
if (used_for_print_object.has_value() && used_for_print_object != check_info.used_for_print_object) { continue; }
// check filament switch
if (filament_item.contains("has_filament_switch"))
{
bool has_filament_switch = filament_item["has_filament_switch"].get<bool>();
if (check_info.has_filament_switch != has_filament_switch) { continue; }
}
// check loc
if (!slot.empty())
{
bool is_virtual_slot = devPrinterUtil::IsVirtualSlot(ams_id);
bool is_virtual_slot = devPrinterUtil::IsVirtualSlot(check_info.ams_id);
bool check_virtual_slot = (slot == "ext");
bool check_ams_slot = (slot == "ams");
if (is_virtual_slot && !check_virtual_slot)
@@ -159,71 +217,115 @@ void check_filaments(std::string model_id,
}
}
if (GUI::wxGetApp().app_config->get("skip_ams_blacklist_check") == "true") {
action = "warning";
// check extruder id
if (!extruder_ids.empty() && check_info.extruder_id.has_value() &&
std::find(extruder_ids.begin(), extruder_ids.end(), check_info.extruder_id.value()) == extruder_ids.end()) {
continue;
}
in_blacklist = true;
ac = action;
info = _L(description);
wiki_url = filament_item.contains("wiki") ? filament_item["wiki"].get<std::string>() : "";
return;
// white items: a matched rule is suppressed when the filament is whitelisted
{
// contains match
std::set<std::string> white_names = filament_item.contains("white_names") ? filament_item["white_names"].get<std::set<std::string>>() : std::set<std::string>();
if (!white_names.empty() && !tag_name.empty())
{
auto it = std::find_if(white_names.begin(), white_names.end(), [&tag_name](std::string white_name) {
std::transform(white_name.begin(), white_name.end(), white_name.begin(), ::tolower);
return tag_name.find(white_name) != std::string::npos;
});
if (it != white_names.end()) { continue; }
}
}
{
// equal match
std::set<std::string> white_fila_ids = filament_item.contains("white_fila_ids") ? filament_item["white_fila_ids"].get<std::set<std::string>>() : std::set<std::string>();
if (!white_fila_ids.empty() && !check_info.fila_id.empty())
{
auto it = std::find_if(white_fila_ids.begin(), white_fila_ids.end(), [&check_info](const std::string& white_fila_id) {
return white_fila_id == check_info.fila_id;
});
if (it != white_fila_ids.end()) { continue; }
}
}
// the item is matched: accumulate it (do not stop scanning)
DevFilaBlacklist::CheckResultItem result_item;
result_item.action = action;
result_item.wiki_url = filament_item.contains("wiki") ? filament_item["wiki"].get<std::string>() : "";
if (description == "%s has a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution.") {
result_item.info_msg = wxString::Format(_L(description), check_info.fila_name);
} else if (description == "%s filaments are hard and brittle and could break in AMS, and there is also a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution.") {
result_item.info_msg = wxString::Format(_L(description), check_info.fila_type);
} else if (description == "%s has a risk of nozzle clogging when using 0.4, 0.6, 0.8mm high-flow nozzles. Use with caution.") {
result_item.info_msg = wxString::Format(_L(description), check_info.fila_name);
} else if (description == "%s may fail to load or unload due to the Filament Track Switch. If you wish to continue.") {
result_item.info_msg = wxString::Format(_L(description), check_info.fila_name);
} else {
result_item.info_msg = _L(description);
}
result.action_items[result_item.action].push_back(result_item);
continue;
// Error in description
L("TPU is not supported by AMS.");
L("AMS does not support 'Bambu Lab PET-CF'.");
L("The current filament doesn't support the E3D high-flow nozzle and can't be used.");
L("The current filament doesn't support the TPU high-flow nozzle and can't be used.");
L("Auto dynamic flow calibration is not supported for TPU filament.");
L("Bambu TPU 85A is not supported for printing with 0.4 mm Standard or High Flow nozzles.");
// Warning in description
L("How to feed TPU filament.");
L("How to feed TPU filament on X2D.");
L("Please cold pull before printing TPU to avoid clogging. You may use cold pull maintenance on the printer.");
L("Damp PVA will become flexible and get stuck inside AMS, please take care to dry it before use.");
L("Damp PVA is flexible and may get stuck in extruder. Dry it before use.");
L("The rough surface of PLA Glow can accelerate wear on the AMS system, particularly on the internal components of the AMS Lite.");
L("PLA Glow may wear the AMS first stage feeder. Use an external spool instead.");
L("CF/GF filaments are hard and brittle, it's easy to break or get stuck in AMS, please use with caution.");
L("PPS-CF is brittle and could break in bended PTFE tube above Toolhead.");
L("PPA-CF is brittle and could break in bended PTFE tube above Toolhead.");
L("Default settings may affect print quality. Adjust as needed for best results.");
L("%s has a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution.");
L("%s filaments are hard and brittle and could break in AMS, and there is also a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution.");
L("%s has a risk of nozzle clogging when using 0.4, 0.6, 0.8mm high-flow nozzles. Use with caution.");
L("%s may fail to load or unload due to the Filament Track Switch. If you wish to continue.");
}
}
}
void DevFilaBlacklist::check_filaments_in_blacklist(std::string model_id,
std::string tag_vendor,
std::string tag_type,
const std::string& filament_id,
int ams_id,
int slot_id,
std::string tag_name,
bool& in_blacklist,
std::string& ac,
wxString& info)
// Extends the prohibition-only bit check with a 4-level filament_extruder_compatibility model
// (0 printable / 1 error / 2 critical warning / 3 warning), bowden-extruder message variants and
// toolhead display names for the "%s extruder" name.
//
// Orca: several deliberate divergences preserve H2D dual-extruder behaviour when the compatibility
// key is absent (default 0 = printable):
// - The extruder is resolved from the ams_id rather than a threaded check_info.extruder_id with an
// early-return: extruder_id is only threaded on the print-send consumer, so falling back on
// ams_id keeps the bit-check firing on the AMS-edit / calibration paths.
// - The prohibition bit-check keeps check_info.fila_type for its "%s", preserving the exact H2D
// AMS-edit message that ships today. The new 4-level messages use the fila_name fallback since
// they stay inert until a filament declares the compatibility key (only X2D ships it).
// - is_bowden_extruder is assigned correctly here so the Bowden message variants actually fire.
void check_filaments_printable(const DevFilaBlacklist::CheckFilamentInfo& check_info, DevFilaBlacklist::CheckResult& result)
{
wxString wiki_url;
check_filaments_in_blacklist_url(model_id, tag_vendor, tag_type, filament_id, ams_id, slot_id, tag_name, in_blacklist, ac, info, wiki_url);
}
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev) { return; }
MachineObject* obj = dev->get_selected_machine();
if (obj == nullptr || !obj->is_multi_extruders()) { return; }
bool check_filaments_printable(const std::string &tag_vendor, const std::string &tag_type, const std::string& filament_id, int ams_id, bool &in_blacklist, std::string &ac, wxString &info)
{
DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev) {
return true;
}
Preset* printer_preset = GUI::get_printer_preset(obj);
if (!printer_preset) { return; }
MachineObject *obj = dev->get_selected_machine();
if (obj == nullptr || !obj->is_multi_extruders()) {
return true;
}
ConfigOptionInts* physical_extruder_map_op = dynamic_cast<ConfigOptionInts*>(printer_preset->config.option("physical_extruder_map"));
if (!physical_extruder_map_op) { return; }
Preset *printer_preset = GUI::get_printer_preset(obj);
if (!printer_preset)
return true;
ConfigOptionInts *physical_extruder_map_op = dynamic_cast<ConfigOptionInts *>(printer_preset->config.option("physical_extruder_map"));
if (!physical_extruder_map_op)
return true;
std::vector<int> physical_extruder_maps = physical_extruder_map_op->values;
int obj_extruder_id = obj->get_extruder_id_by_ams_id(std::to_string(ams_id));
int obj_extruder_id = obj->get_extruder_id_by_ams_id(std::to_string(check_info.ams_id));
int extruder_idx = obj_extruder_id;
for (int index = 0; index < physical_extruder_maps.size(); ++index) {
if (physical_extruder_maps[index] == obj_extruder_id) {
@@ -232,32 +334,85 @@ bool check_filaments_printable(const std::string &tag_vendor, const std::string
}
}
PresetBundle *preset_bundle = GUI::wxGetApp().preset_bundle;
std::optional<FilamentBaseInfo> filament_info = preset_bundle->get_filament_by_filament_id(filament_id, printer_preset->name);
if (filament_info.has_value() && !(filament_info->filament_printable >> extruder_idx & 1)) {
wxString extruder_name = extruder_idx == 0 ? _L("left") : _L("right");
ac = "prohibition";
info = wxString::Format(_L("%s is not supported by %s extruder."), tag_type, extruder_name);
in_blacklist = true;
return false;
PresetBundle* preset_bundle = GUI::wxGetApp().preset_bundle;
std::optional<FilamentBaseInfo> filament_info = preset_bundle->get_filament_by_filament_id(check_info.fila_id, printer_preset->name);
if (!filament_info.has_value()) { return; }
// Physical extruder 0 -> deputy (left), 1 -> main (right); the toolhead display name replaces the
// hard-coded left/right labels (falls back to "left"/"right" for printers without an override).
int bl_ext_id = (extruder_idx == 0) ? DEPUTY_EXTRUDER_ID : MAIN_EXTRUDER_ID;
wxString extruder_name = _L(DevPrinterConfigUtil::get_toolhead_display_name(
obj->printer_type, bl_ext_id, ToolHeadComponent::Extruder, ToolHeadNameCase::LowerCase, true));
// Prohibition-only bit check; keeps fila_type in the "%s" to preserve current behaviour.
if (!(filament_info->filament_printable >> extruder_idx & 1)) {
DevFilaBlacklist::CheckResultItem item;
item.action = "prohibition";
item.info_msg = wxString::Format(_L("%s is not supported by %s extruder."), check_info.fila_type, extruder_name);
result.action_items[item.action].push_back(item);
return;
}
return true;
// Is this a bowden extruder? (drives the message variant for compatibility warnings.)
bool is_bowden_extruder = false;
auto extruder_type_opt = dynamic_cast<const ConfigOptionEnumsGeneric*>(printer_preset->config.option("extruder_type"));
if (extruder_type_opt && (int) extruder_type_opt->values.size() > extruder_idx) {
ExtruderType extruder_type = (ExtruderType) extruder_type_opt->values[extruder_idx];
is_bowden_extruder = (extruder_type == ExtruderType::etBowden);
}
// Compatibility levels: 0 = printable, 1 = error, 2 = critical warning, 3 = warning (4-7 reserved).
std::string fila_name = check_info.fila_name.empty() ? check_info.fila_type : check_info.fila_name;
int compatible_val = filament_info->get_extruder_compatibility(extruder_idx);
if (compatible_val == 0) {
// printable, nothing to report
} else if (compatible_val == 1) {
DevFilaBlacklist::CheckResultItem item;
item.action = "prohibition";
item.info_msg = wxString::Format(_L("%s is not supported by %s extruder."), fila_name, extruder_name);
result.action_items[item.action].push_back(item);
} else if (compatible_val == 2) {
DevFilaBlacklist::CheckResultItem item;
item.action = "warning";
item.info_msg = is_bowden_extruder
? wxString::Format(_L("There may be critical print quality issues when printing '%s' with %s Bowden extruder. Use with caution!"), fila_name, extruder_name)
: wxString::Format(_L("There may be critical print quality issues when printing '%s' with %s extruder. Use with caution!"), fila_name, extruder_name);
result.action_items[item.action].push_back(item);
} else if (compatible_val == 3) {
DevFilaBlacklist::CheckResultItem item;
item.action = "warning";
item.info_msg = is_bowden_extruder
? wxString::Format(_L("There may be print quality issues when printing '%s' with %s Bowden extruder. Use with caution."), fila_name, extruder_name)
: wxString::Format(_L("There may be print quality issues when printing '%s' with %s extruder. Use with caution."), fila_name, extruder_name);
result.action_items[item.action].push_back(item);
}
}
void DevFilaBlacklist::check_filaments_in_blacklist_url(std::string model_id, std::string tag_vendor, std::string tag_type, const std::string& filament_id, int ams_id, int slot_id, std::string tag_name, bool& in_blacklist, std::string& ac, wxString& info, wxString& wiki_url)
DevFilaBlacklist::CheckResult DevFilaBlacklist::check_filaments_in_blacklist(const CheckFilamentInfo& info)
{
if (ams_id < 0 || slot_id < 0)
CheckResult result;
if (info.ams_id < 0 || info.slot_id < 0)
{
return;
return result;
}
if (!check_filaments_printable(tag_vendor, tag_type, filament_id, ams_id, in_blacklist, ac, info))
{
return;
// check the filaments in preset
check_filaments_printable(info, result);
// check the filaments in blacklist file
check_filaments(info, result);
// If skip_ams_blacklist_check is true, demote every prohibition to a warning (aggregate: demote
// all at once rather than per-item).
if (GUI::wxGetApp().app_config->get("skip_ams_blacklist_check") == "true") {
const auto& prohibit_items = result.get_items_by_action("prohibition");
if (!prohibit_items.empty()) {
for (const auto& prohibit_item : prohibit_items) { result.action_items["warning"].push_back(prohibit_item); }
result.action_items.erase("prohibition");
}
}
check_filaments(model_id, tag_vendor, tag_type, ams_id, slot_id, tag_name, in_blacklist, ac, info, wiki_url);
return result;
}
}
}

View File

@@ -1,18 +1,70 @@
#pragma once
#include <optional>
#include <wx/string.h>
#include "slic3r/Utils/json_diff.hpp"
namespace Slic3r
{
class MachineObject;
class DevFilaBlacklist
{
public:
// Struct-in / struct-out check API that accumulates all matching rules,
// replacing the earlier out-param, first-match overloads.
// The per-nozzle dimensions (extruder_id/nozzle_flow/nozzle_diameter), calib_mode and
// has_filament_switch are currently left unset, so the engine behaves exactly as the
// old first-match parser for the shipping rule set.
struct CheckFilamentInfo
{
std::string dev_id;
std::string model_id;
std::string fila_id;
std::string fila_type;
std::string fila_name;
std::string fila_vendor;
std::string calib_mode;
bool has_filament_switch = false;
std::optional<bool> used_for_print_support;// optional
std::optional<bool> used_for_print_object;// optional
int ams_id;
int slot_id;
std::optional<int> extruder_id;// optional
std::optional<std::string> nozzle_flow;// optional
std::optional<float> nozzle_diameter;// optional
};
struct CheckResultItem
{
std::string action;// warning/prohibition
wxString info_msg;
wxString wiki_url;
};
struct CheckResult
{
std::map<std::string, std::vector<CheckResultItem>> action_items;
std::vector<CheckResultItem> get_items_by_action(const std::string& action) const
{
auto it = action_items.find(action);
if (it != action_items.end()) {
return it->second;
}
return std::vector<CheckResultItem>();
}
};
public:
static bool load_filaments_blacklist_config();
static void check_filaments_in_blacklist(std::string model_id, std::string tag_vendor, std::string tag_type, const std::string& filament_id, int ams_id, int slot_id, std::string tag_name, bool& in_blacklist, std::string& ac, wxString& info);
static void check_filaments_in_blacklist_url(std::string model_id, std::string tag_vendor, std::string tag_type, const std::string& filament_id, int ams_id, int slot_id, std::string tag_name, bool& in_blacklist, std::string& ac, wxString& info, wxString& wiki_url);
static CheckResult check_filaments_in_blacklist(const CheckFilamentInfo& info);
public:
static json filaments_blacklist;
};// class DevFilaBlacklist
}// namespace Slic3r
}// namespace Slic3r

View File

@@ -0,0 +1,131 @@
#include "DevExtruderSystem.h"
#include "DevFilaSystem.h"
#include "DevFilaSwitch.h"
#include "DevUtil.h"
#include "slic3r/GUI/DeviceManager.hpp"
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;
m_in_a_has_filament.reset();
m_in_b_has_filament.reset();
m_in_a_slot.reset();
m_in_b_slot.reset();
m_out_a_extruder_id.reset();
m_out_b_extruder_id.reset();
m_cali_status = CaliStatus::CALI_IDLE;
}
std::optional<DevAmsTray> DevFilaSwitch::GetInA_Slot() const
{
auto ams_id_opt = GetInA_SlotId();
if (m_owner && ams_id_opt.has_value()) { return m_owner->get_tray(std::to_string(ams_id_opt->first), std::to_string(ams_id_opt->first)); }
return std::nullopt;
}
std::optional<DevAmsTray> DevFilaSwitch::GetInB_Slot() const
{
auto ams_id_opt = GetInB_SlotId();
if (m_owner && ams_id_opt.has_value()) { return m_owner->get_tray(std::to_string(ams_id_opt->first), std::to_string(ams_id_opt->first)); }
return std::nullopt;
}
std::optional<DevExtder> DevFilaSwitch::GetOutA_Extruder() const
{
auto ext_id_opt = GetOutA_ExtruderId();
if (m_owner && ext_id_opt.has_value()) { return m_owner->GetExtderSystem()->GetExtderById(ext_id_opt.value()); }
return std::nullopt;
}
std::optional<DevExtder> DevFilaSwitch::GetOutB_Extruder() const
{
auto ext_id_opt = GetOutB_ExtruderId();
if (m_owner && ext_id_opt.has_value()) { return m_owner->GetExtderSystem()->GetExtderById(ext_id_opt.value()); }
return std::nullopt;
}
void DevFilaSwitch::ParseFilaSwitchInfo(const nlohmann::json &print_jj)
{
if (print_jj.contains("aux")) {
const auto &info_bits = DevJsonValParser::GetVal<std::string>(print_jj, "aux");
if (m_is_installed != (DevUtil::get_flag_bits(info_bits, 29, 1) == 1)) {
m_is_installed = (DevUtil::get_flag_bits(info_bits, 29, 1) == 1);
if (!m_is_installed) { Reset(); }
};
}
if (print_jj.contains("device") && print_jj["device"].contains("fila_switch")) {
const auto &fila_switch_jj = print_jj["device"]["fila_switch"];
if (fila_switch_jj.contains("in")) {
const auto &in_vec = DevJsonValParser::GetVal<std::vector<int>>(fila_switch_jj, "in");
if (in_vec.size() == 2) {
if (in_vec[0] != -1) {
DevAmsSlotId slot_id;
slot_id.first = DevUtil::get_flag_bits(in_vec[0], 8, 8);
slot_id.second = DevUtil::get_flag_bits(in_vec[0], 0, 8);
m_in_b_slot = slot_id;
} else {
m_in_b_slot = std::nullopt;
};
if (in_vec[1] != -1) {
DevAmsSlotId slot_id;
slot_id.first = DevUtil::get_flag_bits(in_vec[1], 8, 8);
slot_id.second = DevUtil::get_flag_bits(in_vec[1], 0, 8);
m_in_a_slot = slot_id;
} else {
m_in_a_slot.reset();
};
}
}
if (fila_switch_jj.contains("out")) {
const auto &out_vec = DevJsonValParser::GetVal<std::vector<int>>(fila_switch_jj, "out");
if (out_vec.size() == 2) {
if (out_vec[0] != 0xE) {
m_out_b_extruder_id = out_vec[0];
} else {
m_out_b_extruder_id.reset();
}
if (out_vec[1] != 0xE) {
m_out_a_extruder_id = out_vec[1];
} else {
m_out_a_extruder_id.reset();
}
}
}
if (fila_switch_jj.contains("stat")) { m_cali_status = DevJsonValParser::GetVal<CaliStatus>(fila_switch_jj, "stat"); }
if (fila_switch_jj.contains("info")) {
const auto &info_bits = DevJsonValParser::GetVal<int>(fila_switch_jj, "info");
m_in_b_has_filament = DevUtil::get_flag_bits(info_bits, 0, 1);
m_in_a_has_filament = DevUtil::get_flag_bits(info_bits, 0, 1);
}
}
}
}; // namespace Slic3r

View File

@@ -0,0 +1,91 @@
#pragma once
#include "DevDefs.h"
#include <optional>
#include <nlohmann/json.hpp>
namespace Slic3r
{
// Previous definitions
class MachineObject;
class DevExtder;
class DevAmsTray;
// Filament Track Switch (H2-family hardware).
// The filament blacklist consumes IsInstalled() for the `has_filament_switch`
// rule (e.g. PLA Silk). m_is_installed defaults to false and is only raised when
// the device reports the switch over MQTT (aux bit 29), so the rule stays inert on
// every printer that does not report an installed switch.
class DevFilaSwitch
{
public:
enum class CaliStatus : int
{
CALI_IDLE = 0,
CALI_STEPING = 1,
};
enum class CaliStep : int
{
CALI_IDLE = 0,
CALI_SWITCHING = 1,
CALI_SWITCH_CHECK = 2,
CALI_FILA_CHECK = 3,
CALI_FILA_TO_AMS = 4,
CALI_FILA_TO_SWITCH = 5,
CALI_FILA_BACK = 9,
CALI_FINISHED = 14,
};
enum SwitchPos : int
{
POS_IN_B = 0,
POS_IN_A = 1,
};
public:
DevFilaSwitch(MachineObject* owner);
virtual ~DevFilaSwitch() = default;
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; };
std::optional<DevAmsTray> GetInA_Slot() const;
std::optional<DevAmsTray> GetInB_Slot() const;
std::optional<DevAmsSlotId> GetInA_SlotId() const { return m_in_a_slot; };
std::optional<DevAmsSlotId> GetInB_SlotId() const { return m_in_b_slot; };
std::optional<DevExtder> GetOutA_Extruder() const;
std::optional<DevExtder> GetOutB_Extruder() const;
std::optional<int> GetOutA_ExtruderId() const { return m_out_a_extruder_id; };
std::optional<int> GetOutB_ExtruderId() const { return m_out_b_extruder_id; };
CaliStatus GetCaliStatus() const { return m_cali_status; };
void Reset();
void ParseFilaSwitchInfo(const nlohmann::json& print_jj);
private:
MachineObject* m_owner;
bool m_is_installed = false;
std::optional<bool> m_in_a_has_filament;
std::optional<bool> m_in_b_has_filament;
std::optional<DevAmsSlotId> m_in_a_slot;
std::optional<DevAmsSlotId> m_in_b_slot;
std::optional<int> m_out_a_extruder_id;
std::optional<int> m_out_b_extruder_id;
CaliStatus m_cali_status = CaliStatus::CALI_IDLE;
};
};

View File

@@ -1,5 +1,6 @@
#include <nlohmann/json.hpp>
#include "DevFilaSystem.h"
#include "DevNozzleSystem.h" // DevNozzle / DevNozzleSystem for GetNozzleFlowStringByAmsId
// TODO: remove this include
#include "slic3r/GUI/DeviceManager.hpp"
@@ -117,7 +118,7 @@ DevAms::DevAms(const std::string& ams_id, int nozzle_id, int type)
m_ams_id = ams_id;
m_ext_id = nozzle_id;
m_ams_type = (AmsType)type;
assert(EXT_SPOOL < type && m_ams_type <= N3S);
assert(EXT_SPOOL < type && m_ams_type <= AMS_LITE_MIXED);
}
DevAms::~DevAms()
@@ -143,7 +144,8 @@ static unordered_map<int, wxString> s_ams_display_formats = {
wxString DevAms::GetDisplayName() const
{
wxString ams_display_format;
auto iter = s_ams_display_formats.find(m_ams_type);
// GetAmsType() maps AMS_LITE_MIXED -> AMS_LITE so N9 shows the AMS-Lite name.
auto iter = s_ams_display_formats.find(GetAmsType());
if (iter != s_ams_display_formats.end())
{
ams_display_format = iter->second;
@@ -172,11 +174,13 @@ wxString DevAms::GetDisplayName() const
int DevAms::GetSlotCount() const
{
if (m_ams_type == AMS || m_ams_type == AMS_LITE || m_ams_type == N3F)
// GetAmsType() maps AMS_LITE_MIXED -> AMS_LITE, so N9 reports 4 slots like AMS-Lite.
auto ams_type = GetAmsType();
if (ams_type == AMS || ams_type == AMS_LITE || ams_type == N3F)
{
return 4;
}
else if (m_ams_type == N3S)
else if (ams_type == N3S)
{
return 1;
}
@@ -285,6 +289,44 @@ int DevFilaSystem::GetExtruderIdByAmsId(const std::string& ams_id) const
return 0; // not found
}
std::string DevFilaSystem::GetNozzleFlowStringByAmsId(const std::string& ams_id) const
{
auto extruder_id = GetExtruderIdByAmsId(ams_id);
auto nozzle = GetOwner()->GetNozzleSystem()->GetExtNozzle(extruder_id);
return DevNozzle::GetNozzleFlowTypeString(nozzle.GetNozzleFlowType());
}
std::map<int, DevAmsSlotId> DevFilaSystem::GetTrayIndexMap()
{
std::map<int, DevAmsSlotId> tray_id_map;
tray_id_map[VIRTUAL_TRAY_MAIN_ID] = DevAmsSlotId{VIRTUAL_TRAY_MAIN_ID, 0};
tray_id_map[VIRTUAL_TRAY_DEPUTY_ID] = DevAmsSlotId{VIRTUAL_TRAY_DEPUTY_ID, 0};
for (auto& [ams_id, ams_item] : GetAmsList()) {
for (auto &[slot_id, slot_item] : ams_item->GetTrays()) {
if (ams_item && slot_item) {
try {
int ams_id_int = stoi(ams_id);
int slot_id_int = stoi(slot_id);
int tray_index = -1;
if (ams_item->GetAmsType() == DevAms::N3S) {
tray_index = ams_id_int;
} else if(ams_item->GetAmsType() == DevAms::AMS_LITE && ams_item->IsAmsLiteMixed()) {
tray_index = 24 + slot_id_int;
} else {
tray_index = (ams_id_int * 4 + slot_id_int);
}
tray_id_map[tray_index] = {ams_id_int, slot_id_int};
} catch(...) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "invalid ams id: " << ams_id << " or slot id: " << slot_id;
}
}
}
}
return tray_id_map;
}
bool DevFilaSystem::IsAmsSettingUp() const
{
int setting_up_stat = DevUtil::get_flag_bits(m_ams_cali_stat, 0, 8);
@@ -331,6 +373,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);
@@ -388,17 +439,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);
@@ -430,6 +507,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
@@ -442,6 +526,11 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
{
curr_ams->m_exist = (obj->ams_exist_bits & (1 << ams_id_int)) != 0 ? true : false;
}
else if (type_id == DevAms::AMS_LITE_MIXED)
{
// Mixed AMS-Lite (A2L / N9) exist flag lives at bit 12.
curr_ams->m_exist = DevUtil::get_flag_bits(obj->ams_exist_bits, 12);
}
else
{
curr_ams->m_exist = DevUtil::get_flag_bits(obj->ams_exist_bits, 4 + (ams_id_int - 128));
@@ -679,6 +768,11 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
{
curr_tray->is_exists = (obj->tray_exist_bits & (1 << (ams_id_int * 4 + tray_id_int))) != 0 ? true : false;
}
else if (type_id == DevAms::AMS_LITE_MIXED)
{
// Mixed AMS-Lite (A2L / N9) trays occupy tray-exist bits 24..27.
curr_tray->is_exists = DevUtil::get_flag_bits(obj->tray_exist_bits, AMS_LITE_MIXED_TRAY_INDEX_OFFSET + tray_id_int);
}
else
{
curr_tray->is_exists = DevUtil::get_flag_bits(obj->tray_exist_bits, 16 + (ams_id_int - 128));

View File

@@ -4,9 +4,12 @@
#include "DevDefs.h"
#include "DevFilaAmsSetting.h"
#include "DevFilaSwitch.h"
#include "DevUtil.h"
#include <map>
#include <set>
#include <vector>
#include <optional>
#include <memory>
#include <unordered_map>
@@ -142,6 +145,7 @@ public:
static constexpr AmsType AMS_LITE = DevAmsType::AMS_LITE;
static constexpr AmsType N3F = DevAmsType::N3F;
static constexpr AmsType N3S = DevAmsType::N3S;
static constexpr AmsType AMS_LITE_MIXED = DevAmsType::AMS_LITE_MIXED;
public:
@@ -209,11 +213,16 @@ public:
void SetAmsType(int type) { m_ams_type = (AmsType)type; }
void SetAmsType(AmsType type) { m_ams_type = type; }
AmsType GetAmsType() const { return m_ams_type; }
// AMS-Lite-mixed (N9) is a variant of AMS-Lite: report AMS_LITE to generic callers so all
// existing AMS_LITE handling applies. Mixed-specific code uses IsAmsLiteMixed().
AmsType GetAmsType() const { return m_ams_type == AMS_LITE_MIXED ? AMS_LITE : m_ams_type; }
// exist or not
bool IsExist() const { return m_exist; }
// AMS-Lite-mixed for N9 (A2L)
bool IsAmsLiteMixed() const { return m_ams_type == AMS_LITE_MIXED; }
// slots
int GetSlotCount() const;
DevAmsTray* GetTray(const std::string& tray_id) const;
@@ -222,6 +231,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; }
@@ -247,6 +270,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
@@ -319,9 +348,16 @@ public:
DevAmsTray* GetAmsTray(const std::string& ams_id, const std::string& tray_id) const;
void CollectAmsColors(std::vector<wxColour>& ams_colors) const;
// Map a linear tray index -> {ams_id, slot_id}. Includes the two virtual (external-spool) trays,
// N3S single-slot units, and the A2L/N9 AMS-Lite-mixed layout (trays 24-27).
std::map<int, DevAmsSlotId> GetTrayIndexMap();
// extruder
int GetExtruderIdByAmsId(const std::string& ams_id) const;
// nozzle: untranslated flow-type string of the extruder bound to this ams (for blacklist matching)
std::string GetNozzleFlowStringByAmsId(const std::string& ams_id) const;
/* AMS settings*/
DevAmsSystemSetting& GetAmsSystemSetting() { return m_ams_system_setting; }
std::optional<bool> IsDetectOnInsertEnabled() const { return m_ams_system_setting.IsDetectOnInsertEnabled(); };
@@ -331,6 +367,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;
@@ -348,6 +389,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 };

View File

@@ -44,6 +44,8 @@ public:
bool isLaszer() const { return product_name.Contains("Laser"); }
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"); }
};

View File

@@ -95,6 +95,10 @@ namespace Slic3r
{
result.id = ams_id + slot_id;
}
else if (type == DevAms::AMS_LITE_MIXED)
{
result.id = AMS_LITE_MIXED_TRAY_INDEX_OFFSET + slot_id;
}
else
{
result.id = ams_id * 4 + slot_id;
@@ -117,6 +121,11 @@ namespace Slic3r
{
std::string ams_id = ams->second->GetAmsId();
auto ams_type = ams->second->GetAmsType();
// GetAmsType() maps mixed -> AMS_LITE; recover the mixed type so N9 trays index at 24+slot.
if (ams_type == DevAms::AMS_LITE && ams->second->IsAmsLiteMixed())
{
ams_type = DevAms::AMS_LITE_MIXED;
}
for (auto tray = ams->second->GetTrays().begin(); tray != ams->second->GetTrays().end(); tray++)
{
int ams_id = atoi(ams->first.c_str());
@@ -126,6 +135,10 @@ namespace Slic3r
{
tray_index = ams_id * 4 + tray_id;
}
else if (ams_type == DevAms::AMS_LITE_MIXED)
{
tray_index = AMS_LITE_MIXED_TRAY_INDEX_OFFSET + tray_id;
}
else if (ams_type == DevAms::N3S)
{
tray_index = ams_id + tray_id;

View File

@@ -0,0 +1,485 @@
#include "DevMapping.h"
#include "DevMappingNozzle.h"
#include "DevNozzleRack.h"
#include "DevNozzleSystem.h"
#include "DevUtil.h"
#include "DevUtilBackend.h"
#include "libslic3r/MultiNozzleUtils.hpp"
#include "libslic3r/Print.hpp"
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/BackgroundSlicingProcess.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include <algorithm>
#include <unordered_set>
#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>
#include <nlohmann/json.hpp>
using namespace nlohmann;
namespace Slic3r {
void MachineObject::clear_auto_nozzle_mapping()
{
if (m_nozzle_mapping_ptr) {
m_nozzle_mapping_ptr->Clear();
}
}
static std::string s_get_diameter_str(float diameter)
{
return (boost::format("%.2f") % diameter).str();
}
static std::string s_get_diameter_str(const std::string& diameter)
{
try {
float dia = boost::lexical_cast<float>(diameter);
return s_get_diameter_str(dia);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed to boost::lexical_cast: " << diameter;
return diameter;
}
try {
float dia = std::stof(diameter);
return s_get_diameter_str(dia);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " std::stof: " << diameter;
return diameter;
}
}
// get auto nozzle mapping through AP
// warnings:
// (1) the fila_id here is 1 based index
// (2) the AP wants the diameter string with 2 decimal places
int DevNozzleMappingCtrl::CtrlGetAutoNozzleMappingV0(Slic3r::GUI::Plater* plater,
const std::vector<FilamentInfo>& ams_mapping,
int flow_cali_opt, int pa_value)
{
Clear();
m_plater = plater;
if (!plater) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": plater is nullptr";
return -1;
}
GCodeProcessorResult* gcode_result = plater->background_process().get_current_gcode_result();
if (!gcode_result) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": gcode_result is nullptr";
return -1;
}
const auto& result = gcode_result->nozzle_group_result;
if (!result) {
assert(false && "gcode_result->nozzle_group_result");
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": gcode_result->nozzle_group_result is NULL";
return -1;
}
if (ams_mapping.empty()) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": the ams mapping is empty";
return -1;// the ams mapping is empty
}
json command_jj;
command_jj["print"]["command"] = "get_auto_nozzle_mapping";
m_sequence_id = std::to_string(MachineObject::m_sequence_id++);
command_jj["print"]["sequence_id"] = m_sequence_id;
command_jj["print"]["calibration"] = flow_cali_opt;
command_jj["print"]["extrude_cali_manual_mode"] = pa_value;
// filament seq
json filament_seq_jj;
int max_fila_id = 0;
std::unordered_set<int> filaid_set;
for (int idx = 0; idx < gcode_result->filament_change_sequence.size(); idx++) {
int fila_id = gcode_result->filament_change_sequence[idx];
if (filaid_set.count(fila_id) == 0) {
filaid_set.insert(fila_id);
filament_seq_jj[fila_id + 1] = idx;
max_fila_id = std::max(max_fila_id, fila_id + 1);
}
}
for (int fila_id = 0; fila_id <= max_fila_id; fila_id++) {
if (filament_seq_jj[fila_id].is_null()) {
filament_seq_jj[fila_id] = -1;// fill the used fila_id with -1
}
}
command_jj["print"]["filament_seq"] = filament_seq_jj;
// ams mapping
std::vector<int> ams_mapping_vec(33, 0xFFFF);/* AP ask to fill them*/
for (auto item : ams_mapping) {
try {
int ams_id = stoi(item.ams_id);
int slot_id = !item.slot_id.empty() ? stoi(item.slot_id) : 0;
ams_mapping_vec[item.id + 1] = (ams_id << 8) | slot_id;/*using ams_id << 8 | slot_id*/
} catch (std::exception& e) {
assert(false && "invalid ams_id or slot_id");
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " exception: " << e.what();
ams_mapping_vec[item.id + 1] = item.tray_id;
}
}
command_jj["print"]["ams_mapping"] = ams_mapping_vec;
// filament info
json filament_info_jj;
for (int fila_id = 0; fila_id < ams_mapping.size(); fila_id++) {
const auto& fila = ams_mapping[fila_id];
const auto& nozzle_list = result->get_nozzles_for_filament(fila.id);
if (nozzle_list.empty()) {
assert(false && "nozzle_list should not be empty");
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "nozzle_list should not be nullptr";
continue;
}
for (const auto& nozzle_info : nozzle_list) {
json fila_item_jj;
fila_item_jj["id"] = (fila.id + 1);
fila_item_jj["direction"] = (nozzle_info.extruder_id == LOGIC_L_EXTRUDER_ID ? 1 : 2);
fila_item_jj["group"] = nozzle_info.group_id;
fila_item_jj["nozzle_d"] = s_get_diameter_str(nozzle_info.diameter);
fila_item_jj["nozzle_v"] = (nozzle_info.volume_type == NozzleVolumeType::nvtHighFlow) ? "High Flow" : "Standard";
fila_item_jj["cate"] = fila.filament_id;
fila_item_jj["color"] = fila.color;
filament_info_jj.push_back(fila_item_jj);
}
}
command_jj["print"]["fila_info"] = filament_info_jj;
// nozzle info
json nozzle_info_jj;
const auto& nozzle_system = m_obj->GetNozzleSystem();
const auto& extruder_nozzles = nozzle_system->GetExtNozzles();
for (const auto& nozzle : extruder_nozzles) {
if (nozzle.second.IsNormal()) {
json nozzle_item_jj;
nozzle_item_jj["pos"] = nozzle.second.GetNozzleId();
if (nozzle_system->GetReplaceNozzleTar().has_value() && nozzle_item_jj["pos"] == MAIN_EXTRUDER_ID) {
nozzle_item_jj["pos"] = nozzle_system->GetReplaceNozzleTar().value();// special case of tar_id. see protocol definition
}
nozzle_item_jj["nozzle_d"] = s_get_diameter_str(nozzle.second.GetNozzleDiameter());
nozzle_item_jj["nozzle_v"] = DevNozzle::ToNozzleFlowString(nozzle.second.GetNozzleFlowType());
nozzle_item_jj["wear"] = nozzle.second.GetNozzleWear();
nozzle_item_jj["cate"] = nozzle.second.GetFilamentId();
nozzle_item_jj["color"] = nozzle.second.GetFilamentColor();
nozzle_info_jj.push_back(nozzle_item_jj);
}
}
const auto& rack_nozzles = nozzle_system->GetNozzleRack()->GetRackNozzles();
for (const auto& nozzle : rack_nozzles) {
if (nozzle.second.IsNormal()) {
json nozzle_item_jj;
nozzle_item_jj["pos"] = (nozzle.second.GetNozzleId() + 0x10);
if (nozzle_system->GetReplaceNozzleTar().has_value() && nozzle_item_jj["pos"] == MAIN_EXTRUDER_ID) {
nozzle_item_jj["pos"] = nozzle_system->GetReplaceNozzleTar().value();// special case of tar_id. see protocol definition
}
nozzle_item_jj["nozzle_d"] = s_get_diameter_str(nozzle.second.GetNozzleDiameter());
nozzle_item_jj["nozzle_v"] = DevNozzle::ToNozzleFlowString(nozzle.second.GetNozzleFlowType());
nozzle_item_jj["wear"] = nozzle.second.GetNozzleWear();
nozzle_item_jj["cate"] = nozzle.second.GetFilamentId();
nozzle_item_jj["color"] = nozzle.second.GetFilamentColor();
nozzle_info_jj.push_back(nozzle_item_jj);
}
}
command_jj["print"]["nozzle_info"] = nozzle_info_jj;
m_req_version = NozzleMappingVersion::Version_V0;
return m_obj->publish_json(command_jj);
}
int DevNozzleMappingCtrl::CtrlGetAutoNozzleMappingV1(Slic3r::GUI::Plater* plater)
{
Clear();
m_plater = plater;
if (!m_plater) {
return -1;
}
auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(m_plater);
if (!nozzle_group_res) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": gcode_result->nozzle_group_result is NULL";
return -1;
}
json command_jj;
command_jj["print"]["command"] = "get_auto_nozzle_mapping";
m_sequence_id = std::to_string(MachineObject::m_sequence_id++);
command_jj["print"]["sequence_id"] = m_sequence_id;
json nozzle_group_info_jj;
std::unordered_set<int> used_logic_groups;
const auto& used_logic_nozzles = nozzle_group_res->get_used_nozzles_in_extruder();
for (const auto& used_logic_nozzle : used_logic_nozzles) {
if (used_logic_groups.count(used_logic_nozzle.group_id) != 0) {
continue;
}
used_logic_groups.insert(used_logic_nozzle.group_id);
json nozzle_info;
nozzle_info["id"] = used_logic_nozzle.group_id;
nozzle_info["ext"] = (used_logic_nozzle.extruder_id + 1);
try {
nozzle_info["dia"] = std::stof(used_logic_nozzle.diameter);
} catch(const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": e=" << e.what();
continue;
}
if (used_logic_nozzle.volume_type == NozzleVolumeType::nvtHighFlow) {
nozzle_info["vol"] = "High Flow";
} else if(used_logic_nozzle.volume_type == NozzleVolumeType::nvtStandard){
nozzle_info["vol"] = "Standard";
} else if(used_logic_nozzle.volume_type == NozzleVolumeType::nvtTPUHighFlow){
nozzle_info["vol"] = "TPU Flow";
} else {
assert(0);
continue;
}
nozzle_group_info_jj.push_back(nozzle_info);
}
command_jj["print"]["group_info"] = nozzle_group_info_jj;
m_req_version = NozzleMappingVersion::Version_V1;
command_jj["print"]["version"] = (int)m_req_version;
return m_obj->publish_json(command_jj);
}
void DevNozzleMappingCtrl::ParseAutoNozzleMapping(const json& print_jj)
{
if (print_jj.contains("command") && print_jj["command"].get<std::string>() == "get_auto_nozzle_mapping") {
if (print_jj.contains("sequence_id") && print_jj["sequence_id"] == m_sequence_id) {
Clear();
DevJsonValParser::ParseVal(print_jj, "result", m_result);
DevJsonValParser::ParseVal(print_jj, "reason", m_mqtt_reason);
DevJsonValParser::ParseVal(print_jj, "errno", m_errno);
DevJsonValParser::ParseVal(print_jj, "detail", m_detail_json);
DevJsonValParser::ParseVal(print_jj, "type", m_type);
DevJsonValParser::ParseVal(print_jj, "version", m_ack_version);
if (print_jj.contains("mapping")) {
m_nozzle_mapping_json = print_jj["mapping"];
const auto& mapping = print_jj["mapping"].get<std::vector<int>>();
for (int fila_id = 0; fila_id < mapping.size(); ++fila_id) {
m_nozzle_mapping[fila_id] = mapping[fila_id];
}
}
m_flush_weight_base = GetFlushWeight(m_obj);
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": get_auto_nozzle_mapping: " << m_result;
}
}
}
void DevNozzleMappingCtrl::Clear()
{
m_sequence_id.clear();
m_result.clear();
m_mqtt_reason.clear();
m_type.clear();
m_errno = 0;
m_detail_msg.clear();
m_detail_json.clear();
m_nozzle_mapping.clear();
m_nozzle_mapping_json.clear();
m_flush_weight_base = -1;
m_flush_weight_current = -1;
m_ack_version = NozzleMappingVersion::Version_V0;
}
void DevNozzleMappingCtrl::SetManualNozzleMappingByFila(int fila_id, int nozzle_pos_id)
{
if (nozzle_pos_id == MAIN_EXTRUDER_ID && m_obj->GetNozzleSystem()->GetReplaceNozzleTar().has_value()){
nozzle_pos_id = m_obj->GetNozzleSystem()->GetReplaceNozzleTar().value();// special case of tar_id. see protocol definition
}
if (m_nozzle_mapping[fila_id] != nozzle_pos_id) {
m_nozzle_mapping[fila_id] = nozzle_pos_id;
m_flush_weight_current = GetFlushWeight(m_obj);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": fila_id=" << fila_id << ", nozzle_pos_id=" << nozzle_pos_id;
}
try {
auto mapping = m_nozzle_mapping_json.get<std::vector<int>>();
if (mapping.at(fila_id) != nozzle_pos_id) {
mapping[fila_id] = nozzle_pos_id;
m_nozzle_mapping_json = mapping;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": updated to " << m_nozzle_mapping_json.dump();
}
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": exception: " << e.what();
};
}
static std::unordered_set<int> sGetLogicExtruders(const std::vector<MultiNozzleUtils::NozzleInfo>& nozzle_infos)
{
std::unordered_set<int> extruder_ids;
for (const auto& nozzle : nozzle_infos) {
if (nozzle.extruder_id >= 0) {
extruder_ids.insert(nozzle.extruder_id);
}
}
return extruder_ids;
};
std::vector<int> DevNozzleMappingCtrl::GetMappedNozzlePosVecByFilaId(int fila_id) const
{
std::vector<int> physic_nozzle_pos_vec;
auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(m_plater);
if (!nozzle_group_res) {
return physic_nozzle_pos_vec;
}
const auto& used_logic_nozzles = nozzle_group_res->get_nozzles_for_filament(fila_id);
if (m_ack_version == NozzleMappingVersion::Version_V0) {
if (auto iter = m_nozzle_mapping.find(fila_id); iter != m_nozzle_mapping.end()) {
if (m_obj->GetNozzleSystem()->GetReplaceNozzleTar().has_value() &&
iter->second == m_obj->GetNozzleSystem()->GetReplaceNozzleTar().value()) {
physic_nozzle_pos_vec.push_back(MAIN_EXTRUDER_ID);
} else {
physic_nozzle_pos_vec.push_back(iter->second);
}
} else {
const auto& used_logic_extruders = sGetLogicExtruders(used_logic_nozzles);
if (used_logic_extruders.size() == 1 && *used_logic_extruders.begin() == LOGIC_L_EXTRUDER_ID) {
physic_nozzle_pos_vec.push_back(1);
return physic_nozzle_pos_vec; // only left extruder is used
}
}
} else if (m_ack_version == NozzleMappingVersion::Version_V1) {
for (const auto& used_logic_nozzle : used_logic_nozzles) {
if (auto iter = m_nozzle_mapping.find(used_logic_nozzle.group_id); iter != m_nozzle_mapping.end()) {
if (m_obj->GetNozzleSystem()->GetReplaceNozzleTar().has_value() &&
iter->second == m_obj->GetNozzleSystem()->GetReplaceNozzleTar().value()) {
physic_nozzle_pos_vec.push_back(MAIN_EXTRUDER_ID);
} else {
physic_nozzle_pos_vec.push_back(iter->second);
}
}
}
}
return physic_nozzle_pos_vec;
}
wxString DevNozzleMappingCtrl::GetMappedNozzlePosStrByFilaId(int fila_id, const wxString& default_str) const
{
wxString display_str;
const auto& physic_nozzle_pos_vec = GetMappedNozzlePosVecByFilaId(fila_id);
for (int idx = 0; idx < physic_nozzle_pos_vec.size(); idx++) {
int pos_id = physic_nozzle_pos_vec[idx];
if (pos_id == MAIN_EXTRUDER_ID) {
display_str += "R";
} else if (pos_id == DEPUTY_EXTRUDER_ID) {
display_str += "L";
} else if (pos_id >= 0x10) {
display_str += wxString::Format("R%d", pos_id - 0x10 + 1);// display 1~n for rack hotends
} else {
continue;
}
if (idx < physic_nozzle_pos_vec.size() - 1) {
display_str += " ";
}
}
return !display_str.empty() ? display_str : default_str;
}
// flush weight for multi-nozzle printers: total flush volume across all filament changes
float DevNozzleMappingCtrl::GetFlushWeight(Slic3r::MachineObject* obj) const
{
auto plater = Slic3r::GUI::wxGetApp().plater();
if (!plater) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": plater is nullptr";
return -1;
}
GCodeProcessorResult* gcode_result = plater->background_process().get_current_gcode_result();
if (!gcode_result) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": gcode_result is nullptr";
return -1;
}
if (gcode_result->filament_change_sequence.empty()) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": filament_change_sequence is empty";
return -1;
}
if (!Slic3r::GUI::wxGetApp().preset_bundle) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": preset_bundle is nullptr";
return -1;
};
const std::vector<std::vector<std::vector<float>>>& flush_matrix = Slic3r::GUI::wxGetApp().preset_bundle->get_full_flush_matrix();
float total_flush_volume = 0;
MultiNozzleUtils::NozzleStatusRecorder recorder;
for (auto filament : gcode_result->filament_change_sequence) {
const auto& nozzle_pos_vec = GetMappedNozzlePosVecByFilaId(filament);
if (nozzle_pos_vec.empty()) {
continue;
}
auto nozzle_pos = nozzle_pos_vec.at(0);
if (nozzle_pos == -1){
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": nozzle_pos is -1 for fila_id=" << filament;
continue;
}
auto nozzle_info = obj->GetNozzleSystem()->GetNozzleByPosId(nozzle_pos);
if (nozzle_info.IsEmpty()) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": nozzle_info IsEmpty for fila_id=" << filament << ", nozzle_pos=" << nozzle_pos;
continue;
}
int extruder_id = nozzle_info.GetLogicExtruderId();
int nozzle_id = nozzle_info.GetNozzlePosId();
int last_filament = recorder.get_filament_in_nozzle(nozzle_id);
if (last_filament != -1 && last_filament != filament) {
if (flush_matrix.size() > extruder_id &&
flush_matrix[extruder_id].size() > last_filament &&
flush_matrix[extruder_id][last_filament].size() > filament){
total_flush_volume += flush_matrix[extruder_id][last_filament][filament];
}
else{
assert(false && "missing flush volume");
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": missing flush volume for extruder_id=" << extruder_id
<< ", last_filament=" << last_filament << ", filament=" << filament;
}
}
recorder.set_nozzle_status(nozzle_id, filament);
}
// estimate the flush weight: total_flush_volume * 1.26 * 0.001
return total_flush_volume * 1.26 * 0.001;
}
} // namespace Slic3r

View File

@@ -0,0 +1,108 @@
/**
* @file DevMappingNozzle.h
* @brief Support nozzle mapping for hotend rack
*/
#pragma once
#include <string>
#include <unordered_map>
#include <nlohmann/json.hpp>
#include <wx/string.h>
namespace Slic3r
{
namespace GUI
{
class Plater;
}
struct FilamentInfo; // libslic3r/ProjectTask.hpp
class MachineObject;
class DevNozzleMappingCtrl
{
friend class MachineObject;
public:
enum class NozzleMappingVersion : int {
Version_V0 = 0, // V0 means support logical fila_id -> physical nozzle_id mapping
Version_V1 = 1, // V1 means support logical nozzle_id -> physical nozzle_id mapping
};
enum class ErrNoV1 : int {
Success = 0,
ErrorBadRequest3mf = 1,
ErrorBadRequestVersion = 2,
ErrorBadRequestGroupId = 3,
ErrorNoValidNozzles = 4,
ErrorBadRequestReadFail = 5,
ErrorRackCaliFail = 6,
ErrorRackMoving = 7,
ErrorRackFull = 8,
WarningNotEnoughNozzles = 9
};
public:
DevNozzleMappingCtrl(MachineObject* obj) : m_obj(obj) {};
public:
void SetPlater(Slic3r::GUI::Plater* plater) { m_plater = plater; }
void Clear();
bool HasResult() const { return !m_result.empty(); }
std::string GetResultStr() const { return m_result; }
// mqtt error info
std::string GetMqttReason() const { return m_mqtt_reason; }
// command error info
int GetErrno() const { return m_errno; }
ErrNoV1 GetErrnoV1() const { return static_cast<ErrNoV1>(m_errno); }
std::string GetDetailMsg() const { return m_detail_msg; }
// nozzle mapping
std::unordered_map<int, int> GetNozzleMappingMap() const { return m_nozzle_mapping; }
nlohmann::json GetNozzleMappingJson() const { return m_nozzle_mapping_json; }
void SetManualNozzleMappingByFila(int fila_id, int nozzle_pos_id);
std::vector<int> GetMappedNozzlePosVecByFilaId(int fila_id) const;// return empy if not mapped
wxString GetMappedNozzlePosStrByFilaId(int fila_id, const wxString& default_str = "?") const;
// flush weight
float GetFlushWeightBase() const { return m_flush_weight_base; }
float GetFlushWeightCurrent() const { return m_flush_weight_current; }
public:
void ParseAutoNozzleMapping(const nlohmann::json& print_jj);
int CtrlGetAutoNozzleMappingV0(Slic3r::GUI::Plater* plater, const std::vector<FilamentInfo>& ams_mapping, int flow_cali_opt, int pa_value);
int CtrlGetAutoNozzleMappingV1(Slic3r::GUI::Plater* plater);
private:
float GetFlushWeight(Slic3r::MachineObject* obj) const;
private:
MachineObject* m_obj;
Slic3r::GUI::Plater* m_plater = nullptr;
std::string m_sequence_id;
std::string m_result;
std::string m_mqtt_reason;
std::string m_type; // auto or manual
int m_errno;
std::string m_detail_msg;
nlohmann::json m_detail_json;
nlohmann::json m_nozzle_mapping_json;
std::unordered_map<int, int> m_nozzle_mapping; // v0: fila_id -> physical_nozzle_id, v1: logical_nozzle_id -> physical_nozzle_id
float m_flush_weight_base = -1;// the base weight for flush
float m_flush_weight_current = -1;// the weight current
NozzleMappingVersion m_req_version = NozzleMappingVersion::Version_V0;
NozzleMappingVersion m_ack_version = NozzleMappingVersion::Version_V0;
};
}

View File

@@ -0,0 +1,112 @@
#include "DevNozzleRack.h"
#include "DevUtil.h"
#include "slic3r/GUI/DeviceManager.hpp"
wxDEFINE_EVENT(DEV_RACK_EVENT_READING_FINISHED, wxCommandEvent);
namespace Slic3r
{
DevNozzleRack::DevNozzleRack(DevNozzleSystem* nozzle_system)
: wxEvtHandler(), m_nozzle_system(nozzle_system)
{
}
void DevNozzleRack::Reset()
{
m_position = RACK_POS_UNKNOWN;
m_status = RACK_STATUS_UNKNOWN;
m_rack_nozzles.clear();
}
void DevNozzleRack::SendReadingFinished()
{
wxCommandEvent evt(DEV_RACK_EVENT_READING_FINISHED);
evt.SetEventObject(this);
wxPostEvent(this, evt);
}
DevNozzle DevNozzleRack::GetNozzle(int idx) const
{
auto iter = m_rack_nozzles.find(idx);
if (iter == m_rack_nozzles.end()) {
DevNozzle nozzle;
nozzle.SetOnRack(true);
return nozzle;
}
return iter->second;
}
DevFirmwareVersionInfo DevNozzleRack::GetNozzleFirmwareInfo(int nozzle_id) const
{
auto iter = m_rack_nozzles_firmware.find(nozzle_id);
return iter != m_rack_nozzles_firmware.end() ? iter->second : DevFirmwareVersionInfo();
}
bool DevNozzleRack::HasUnreliableNozzles() const
{
for (const auto& nozzle : m_rack_nozzles)
{
if (!nozzle.second.IsInfoReliable())
{
return true;
}
}
return false;
}
bool DevNozzleRack::HasUnknownNozzles() const
{
for (const auto& nozzle : m_rack_nozzles)
{
if (nozzle.second.IsUnknown())
{
return true;
}
}
return false;
}
int DevNozzleRack::GetKnownNozzleCount() const
{
int count = 0;
for (const auto& nozzle : m_rack_nozzles)
{
if (!nozzle.second.IsEmpty() && !nozzle.second.IsUnknown())
{
count++;
}
}
return count;
}
void DevNozzleRack::ParseRackInfo(const nlohmann::json& rack_info)
{
ParseRackInfoV1_0(rack_info);
}
void DevNozzleRack::ParseRackInfoV1_0(const nlohmann::json& rack_info)
{
DevJsonValParser::ParseVal(rack_info, "stat", m_status, RACK_STATUS_UNKNOWN);
if (m_status < RACK_STATUS_UNKNOWN || m_status >= RACK_STATUS_END)
{
m_status = RACK_STATUS_UNKNOWN; // Reset to default if out of range
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Invalid rack status: " << m_status << ", reset";
}
DevJsonValParser::ParseVal(rack_info, "pos", m_position, RACK_POS_UNKNOWN);
if (m_position < RACK_POS_UNKNOWN || m_position >= RACK_POS_END)
{
m_position = RACK_POS_UNKNOWN; // Reset to default if out of range
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Invalid rack position: " << m_position << ", reset";
}
DevJsonValParser::ParseVal(rack_info, "info", m_cali_status);
}
};

View File

@@ -0,0 +1,134 @@
#pragma once
#include "libslic3r/CommonDefs.hpp"
#include "libslic3r/MultiNozzleUtils.hpp"
#include "slic3r/Utils/json_diff.hpp"
#include "DevNozzleSystem.h"
#include "DevFirmware.h"
#include <map>
// wx
#include <wx/string.h>
#include <wx/event.h>
wxDECLARE_EVENT(DEV_RACK_EVENT_READING_FINISHED, wxCommandEvent);
namespace Slic3r
{
// Previous definitions
class DevNozzleSystem;
class DevNozzleRack: public wxEvtHandler
{
public:
enum RackStatus : int
{
RACK_STATUS_UNKNOWN = -1,
RACK_STATUS_IDLE = 0,
RACK_STATUS_HOTEND_CENTRE = 1,
RACK_STATUS_TOOLHEAD_CENTRE = 2,
RACK_STATUS_CALIBRATE_HOTEND_RACK = 3,
RACK_STATUS_CUT_MATERIAL = 4,
RACK_STATUS_UNLOCK_HOTEND = 5,
RACK_STATUS_LIFT_HOTEND_RACK = 6,
RACK_STATUS_PLACE_HOTEND = 7,
RACK_STATUS_PICK_HOTEND = 8,
RACK_STATUS_LOCK_HOTEND = 9,
RACK_STATUS_END,
};
enum RackPos : int
{
RACK_POS_UNKNOWN = 0,
RACK_POS_A_TOP = 1,
RACK_POS_B_TOP = 2,
RACK_POS_CENTRE = 3,
RACK_POS_END,
};
enum RackCaliStatus
{
Rack_CALI_UNKNOWN = -1,
Rack_CALI_NOT = 0,
Rack_CALI_OK = 1,
};
public:
DevNozzleRack(DevNozzleSystem* nozzle_system);
~DevNozzleRack() = default;
public:
// Is supported by the printer
bool IsSupported() const { return m_is_supported; };
void SetSupported(bool supported) { m_is_supported = supported; }
// getters
DevNozzleSystem* GetNozzleSystem() const { return m_nozzle_system; }
RackPos GetPosition() const { return m_position; }
RackStatus GetStatus() const { return m_status; }
RackCaliStatus GetCaliStatus() const { return m_cali_status;}
DevNozzle GetNozzle(int idx) const;
const std::map<int, DevNozzle>& GetRackNozzles() const { return m_rack_nozzles; }
// status
bool HasUnreliableNozzles() const;
bool HasUnknownNozzles() const;
int GetKnownNozzleCount() const;
// refreshing
int GetReadingIdx() const { return m_nozzle_system->GetReadingIdx(); }
int GetReadingCount() const { return m_nozzle_system->GetReadingCount(); }
void SendReadingFinished();
// firmware
void AddNozzleFirmwareInfo(int nozzle_id, const DevFirmwareVersionInfo& info) { m_rack_nozzles_firmware[nozzle_id] = info; }
void ClearNozzleFirmwareInfo() { m_rack_nozzles_firmware.clear(); }
DevFirmwareVersionInfo GetNozzleFirmwareInfo(int nozzle_id) const;
// setters
void Reset();
void AddRackNozzle(DevNozzle& nozzle) { nozzle.SetOnRack(true); m_rack_nozzles[nozzle.m_nozzle_id] = nozzle; };
void ClearRackNozzles() { m_rack_nozzles.clear(); }
public:
void ParseRackInfo(const nlohmann::json& rack_info);
void CtrlRackPosMove(RackPos new_pos) const;
void CtrlRackPosGoHome() const;
void CtrlRackConfirmNozzle(int rack_nozzle_id) const;
void CtrlRackConfirmAll() const;
void CrtlRackReadNozzle(int rack_nozzle_id) const;
void CtrlRackReadAll(bool gui_check = false) const;
bool CtrlCanReadAll() const;
// the upgrade is not supported
// the GUI interface is removed
int CtrlRackUpgradeExtruderNozzle() const;
int CtrlRackUpgradeRackNozzle(int rack_nozzle_id) const;;
int CtrlRackUpgradeAll() const;;
bool CtrlCanUpdateAll() const;
private:
void ParseRackInfoV1_0(const nlohmann::json& rack_info);
int CtrlRackUpgrade(const std::string& module_str) const;
bool CheckRackMoveWarningDlg() const;
private:
DevNozzleSystem* m_nozzle_system = nullptr;
bool m_is_supported = false; // Indicates if the nozzle rack is supported by the printer
RackPos m_position = RACK_POS_UNKNOWN;
RackStatus m_status = RACK_STATUS_UNKNOWN;
RackCaliStatus m_cali_status = Rack_CALI_UNKNOWN;
std::map<int, DevNozzle> m_rack_nozzles; // Map of nozzle ID to DevNozzle objects
std::map<int, DevFirmwareVersionInfo> m_rack_nozzles_firmware;
};
};

View File

@@ -0,0 +1,283 @@
#include "DevNozzleRack.h"
#include "DevUtil.h"
#include "DevExtruderSystem.h"
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/I18N.hpp"
#include <wx/thread.h>
namespace Slic3r
{
void DevNozzleRack::CtrlRackPosMove(RackPos new_pos) const
{
if (!CheckRackMoveWarningDlg())
{
return;
}
int action_id = -1;
if (new_pos == RACK_POS_A_TOP)
{
action_id = 1;
}
else if(new_pos == RACK_POS_B_TOP)
{
action_id = 2;
}
if (action_id != -1)
{
json j;
j["print"]["command"] = "nozzle_holder_ctrl";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["print"]["action"] = action_id;
m_nozzle_system->GetOwner()->publish_json(j);
}
}
void DevNozzleRack::CtrlRackPosGoHome() const
{
if (!CheckRackMoveWarningDlg())
{
return;
}
json j;
j["print"]["command"] = "nozzle_holder_ctrl";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["print"]["action"] = 0;
m_nozzle_system->GetOwner()->publish_json(j);
}
bool DevNozzleRack::CheckRackMoveWarningDlg() const
{
if (wxThread::IsMain())
{
static bool s_show_move_warning = true;
if (s_show_move_warning)
{
Slic3r::GUI::MessageDialog dlg(nullptr, _L("The toolhead and hotend rack may move. Please keep your hands away from the chamber."),
_L("Warning"), wxICON_WARNING | wxOK);
dlg.show_dsa_button();
if (dlg.ShowModal() != wxID_OK)
{
s_show_move_warning = !dlg.get_checkbox_state();
return false;
}
s_show_move_warning = !dlg.get_checkbox_state();
}
}
return true;
}
void DevNozzleRack::CtrlRackConfirmNozzle(int rack_nozzle_id) const
{
json j;
j["print"]["command"] = "nozzle_info_confirm";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["print"]["id"] = (rack_nozzle_id + 16); // from 0x16
m_nozzle_system->GetOwner()->publish_json(j);
}
void DevNozzleRack::CtrlRackConfirmAll() const
{
json j;
j["print"]["command"] = "nozzle_info_confirm";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["print"]["id"] = 0xff;
m_nozzle_system->GetOwner()->publish_json(j);
}
void DevNozzleRack::CrtlRackReadNozzle(int rack_nozzle_id) const
{
json j;
j["print"]["command"] = "holder_nozzle_refresh";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["print"]["id"] = (rack_nozzle_id + 16); // from 0x16
m_nozzle_system->GetOwner()->publish_json(j);
}
bool DevNozzleRack::CtrlCanReadAll() const
{
//is in print
if (m_nozzle_system->GetOwner()->is_in_printing())
{
return false;
}
if (m_nozzle_system->GetOwner()->is_in_upgrading()) {
return false;
}
//if have nozzle
bool has_nozzle_on_rack = false;
for (const auto &nozzle_pair : m_rack_nozzles)
{
if (!nozzle_pair.second.IsEmpty())
{
has_nozzle_on_rack = true;
break;
}
}
bool has_nozzle_on_ext = false;
if (m_nozzle_system->ContainsExtNozzle(MAIN_EXTRUDER_ID)) {
has_nozzle_on_ext = true;
}
if (!has_nozzle_on_rack && !has_nozzle_on_ext) {
return false;
}
//if is in loading
if (m_nozzle_system->GetOwner()->ams_status_main == AMS_STATUS_MAIN_FILAMENT_CHANGE)
{
return false;
}
auto ext = m_nozzle_system->GetOwner()->GetExtderSystem();
if (ext && ext->IsBusyLoading()) return false;
if (GetReadingCount() > 0)
{
return false;
}
return m_status == RACK_STATUS_IDLE;
}
void DevNozzleRack::CtrlRackReadAll(bool gui_check) const
{
if (gui_check && wxThread::IsMain())
{
#if 0
if (!HasUnknownNozzles()) {
Slic3r::GUI::MessageDialog dlg(nullptr, _L("Hotend information may be inaccurate. "
"Would you like to re-read the hotend? (Hotend information may change during power-off)."),
_L("Warning"), wxICON_WARNING | wxOK | wxYES);
dlg.SetButtonLabel(wxID_OK, _L("I confirm all"));
dlg.SetButtonLabel(wxID_YES, _L("Re-read all"));
int rtn = dlg.ShowModal();
if (rtn == wxID_OK) {
CtrlRackConfirmAll();
} else if (rtn == wxID_YES) {
if (CheckRackMoveWarningDlg()) {
CtrlRackReadAll(false);
}
}
return;
}
#endif
if (CheckRackMoveWarningDlg()) {
CtrlRackReadAll(false);
}
return;
}
json j;
j["print"]["command"] = "holder_nozzle_refresh";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["print"]["id"] = 0xff;
m_nozzle_system->GetOwner()->publish_json(j);
}
bool DevNozzleRack::CtrlCanUpdateAll() const
{
if (m_nozzle_system->GetOwner()->is_in_printing())
{
return false;
}
auto ext_nozzle = m_nozzle_system->GetExtNozzle(MAIN_EXTRUDER_ID);
if (ext_nozzle.IsAbnormal())
{
return true;
}
auto ext_nozzle_firmware = m_nozzle_system->GetExtruderNozzleFirmware();
if (!ext_nozzle_firmware.sw_new_ver.empty() &&
ext_nozzle_firmware.sw_new_ver != ext_nozzle_firmware.sw_ver)
{
return true;
}
for (auto val : m_rack_nozzles_firmware)
{
const auto& firmware_info = val.second;
if (!firmware_info.sw_new_ver.empty() && firmware_info.sw_new_ver != firmware_info.sw_ver)
{
return true;
}
else if (GetNozzle(val.first).IsAbnormal())
{
return true;
}
}
return false;
}
int DevNozzleRack::CtrlRackUpgradeExtruderNozzle() const
{
return CtrlRackUpgrade("wtm");
}
int DevNozzleRack::CtrlRackUpgradeRackNozzle(int rack_nozzle_id) const
{
return CtrlRackUpgrade("wtm/" + std::to_string(0x10 + rack_nozzle_id));
}
int DevNozzleRack::CtrlRackUpgradeAll() const
{
return CtrlRackUpgrade("wtm_all");
}
int DevNozzleRack::CtrlRackUpgrade(const std::string& module_str) const
{
if (wxThread::IsMain())
{
if (GetReadingCount() > 0)
{
Slic3r::GUI::MessageDialog dlg(nullptr, _L("Reading the hotends, please wait."), _L("Warning"), wxICON_WARNING | wxOK);
dlg.ShowModal();
return -1;
}
static bool s_show_upgrade_warning = true;
if (s_show_upgrade_warning)
{
Slic3r::GUI::MessageDialog dlg(nullptr, _L("During the hotend upgrade, the toolhead will move. Don't reach into the chamber."),
_L("Warning"), wxICON_WARNING | wxOK | wxCANCEL);
dlg.show_dsa_button();
dlg.SetButtonLabel(wxID_OK, _L("Update"));
int rtn = dlg.ShowModal();
s_show_upgrade_warning = !dlg.get_checkbox_state();
if (rtn != wxID_OK)
{
return -1;
}
}
}
json j;
j["upgrade"]["command"] = "wtm_upgrade";
j["upgrade"]["module"] = module_str;
j["upgrade"]["src_id"] = 1;
j["upgrade"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return m_nozzle_system->GetOwner()->publish_json(j);
}
};

View File

@@ -1,35 +1,413 @@
#include "DevExtruderSystem.h"
#include "DevNozzleRack.h"
#include "DevNozzleSystem.h"
#include "DevUtil.h"
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
namespace Slic3r
{
DevNozzle DevNozzleSystem::GetNozzle(int id) const
// ---- DevNozzle: flow/volume conversions (Standard / High Flow / TPU High Flow) ----------------------
// Device-reported U_FLOW nozzles map to nvtTPUHighFlow so a synced TPU-HF rack activates the H2C
// change_filament_gcode TPU-kit branch. nvtHybrid is a slicer-only sentinel with no device
// representation, so ToNozzleFlowType(nvtHybrid) falls through to NONE_FLOWTYPE.
NozzleFlowType DevNozzle::ToNozzleFlowType(const NozzleVolumeType& type)
{
if (m_nozzles.find(id) != m_nozzles.end())
switch (type) {
case NozzleVolumeType::nvtStandard: return NozzleFlowType::S_FLOW;
case NozzleVolumeType::nvtHighFlow: return NozzleFlowType::H_FLOW;
case NozzleVolumeType::nvtTPUHighFlow: return NozzleFlowType::U_FLOW;
default: return NozzleFlowType::NONE_FLOWTYPE;
}
}
NozzleVolumeType DevNozzle::ToNozzleVolumeType(const NozzleFlowType& type)
{
switch (type) {
case NozzleFlowType::S_FLOW: return NozzleVolumeType::nvtStandard;
case NozzleFlowType::H_FLOW: return NozzleVolumeType::nvtHighFlow;
case NozzleFlowType::U_FLOW: return NozzleVolumeType::nvtTPUHighFlow;
default: {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "nozzle flow type None convert to nozzle volume type Standard";
return NozzleVolumeType::nvtStandard;
}
}
}
wxString DevNozzle::GetNozzleFlowTypeStr(NozzleFlowType type)
{
switch (type) {
case NozzleFlowType::H_FLOW: return _L("High Flow");
case NozzleFlowType::S_FLOW: return _L("Standard");
case NozzleFlowType::U_FLOW: return _L("TPU High Flow");
default: break;
}
return _L("Unknown");
}
// Untranslated flow-type literal used for filaments_blacklist.json nozzle_flows matching.
// Keep the raw English strings; the translated GetNozzleFlowTypeStr must not be used for JSON matching.
std::string DevNozzle::GetNozzleFlowTypeString(NozzleFlowType type)
{
switch (type) {
case NozzleFlowType::H_FLOW: return "High Flow";
case NozzleFlowType::S_FLOW: return "Standard";
case NozzleFlowType::U_FLOW: return "TPU High Flow";
default: return "Unknown";
}
}
// Untranslated flow-type literal serialized into the get_auto_nozzle_mapping payload.
// Differs from GetNozzleFlowTypeString only in the fallback: "" (empty) rather than "Unknown".
std::string DevNozzle::ToNozzleFlowString(const NozzleFlowType& type)
{
switch (type) {
case NozzleFlowType::S_FLOW: return "Standard";
case NozzleFlowType::H_FLOW: return "High Flow";
case NozzleFlowType::U_FLOW: return "TPU High Flow";
default: return std::string();
}
}
wxString DevNozzle::GetNozzleTypeStr(NozzleType type)
{
switch (type) {
case Slic3r::ntHardenedSteel: return _L("Hardened Steel");
case Slic3r::ntStainlessSteel: return _L("Stainless Steel");
case Slic3r::ntTungstenCarbide: return _L("Tungsten Carbide");
default: break;
}
return _L("Unknown");
}
// ---- DevNozzle: status ------------------------------------------------------------------------------
bool DevNozzle::IsInfoReliable() const
{
if (IsEmpty()) { return false; }
return DevUtil::get_flag_bits(m_stat, 0, 1) == 0;
}
bool DevNozzle::IsNormal() const
{
if (IsEmpty()) { return false; }
return DevUtil::get_flag_bits(m_stat, 1, 2) == 0;
}
bool DevNozzle::IsAbnormal() const
{
return DevUtil::get_flag_bits(m_stat, 1, 2) == (1 << 0);
}
bool DevNozzle::IsUnknown() const
{
return DevUtil::get_flag_bits(m_stat, 1, 2) == (1 << 1);
}
int DevNozzle::GetNozzlePosId() const
{
return IsOnRack() ? (m_nozzle_id + 0x10) : m_nozzle_id;
}
NozzleDiameterType DevNozzle::GetNozzleDiameterType() const
{
if (is_approx(m_diameter, 0.2f))
return NozzleDiameterType::NOZZLE_DIAMETER_0_2;
else if (is_approx(m_diameter, 0.4f))
return NozzleDiameterType::NOZZLE_DIAMETER_0_4;
else if (is_approx(m_diameter, 0.6f))
return NozzleDiameterType::NOZZLE_DIAMETER_0_6;
else if (is_approx(m_diameter, 0.8f))
return NozzleDiameterType::NOZZLE_DIAMETER_0_8;
else
return NozzleDiameterType::NONE_DIAMETER_TYPE;
}
DevFirmwareVersionInfo DevNozzle::GetFirmwareInfo() const
{
const auto& nozzle_rack = m_nozzle_rack.lock();
if (nozzle_rack) {
if (IsOnRack()) {
return nozzle_rack->GetNozzleFirmwareInfo(m_nozzle_id);
} else if (m_nozzle_id == 0) {
return nozzle_rack->GetNozzleSystem()->GetExtruderNozzleFirmware();
}
}
return DevFirmwareVersionInfo();
}
// ---- DevNozzle: location ----------------------------------------------------------------------------
int DevNozzle::GetLogicExtruderId() const
{
int total_ext_count = GetTotalExtruderCount();
if (total_ext_count == 1) {
return LOGIC_UNIQUE_EXTRUDER_ID;
} else if (total_ext_count == 2) {
if (AtLeftExtruder()) {
return LOGIC_L_EXTRUDER_ID;
} else if (AtRightExtruder()) {
return LOGIC_R_EXTRUDER_ID;
}
}
assert(0);
return LOGIC_UNIQUE_EXTRUDER_ID;
}
int DevNozzle::GetExtruderId() const
{
int total_ext_count = GetTotalExtruderCount();
if (total_ext_count == 1) {
return MAIN_EXTRUDER_ID;
} else if (total_ext_count == 2) {
if (AtRightExtruder()) {
return MAIN_EXTRUDER_ID;
} else if (AtLeftExtruder()) {
return DEPUTY_EXTRUDER_ID;
}
}
return MAIN_EXTRUDER_ID;
}
bool DevNozzle::AtLeftExtruder() const
{
assert(GetTotalExtruderCount() == 2);
if (IsOnRack()) { return false; }
return m_nozzle_id == DEPUTY_EXTRUDER_ID;
}
bool DevNozzle::AtRightExtruder() const
{
assert(GetTotalExtruderCount() == 2);
if (IsOnRack()) { return true; }
return m_nozzle_id == MAIN_EXTRUDER_ID;
}
int DevNozzle::GetTotalExtruderCount() const
{
auto rack = m_nozzle_rack.lock();
if (rack) {
MachineObject* obj = rack->GetNozzleSystem()->GetOwner();
return obj->GetExtderSystem()->GetTotalExtderCount();
}
return 1;
}
// ---- DevNozzleSystem --------------------------------------------------------------------------------
DevNozzleSystem::DevNozzleSystem(MachineObject* owner)
: m_owner(owner), m_nozzle_rack(std::make_shared<DevNozzleRack>(this))
{
}
DevNozzle DevNozzleSystem::GetExtNozzle(int id) const
{
if (m_ext_nozzles.find(id) != m_ext_nozzles.end())
{
return m_nozzles.at(id);
return m_ext_nozzles.at(id);
}
return DevNozzle();
}
void DevNozzleSystem::Reset()
DevNozzle DevNozzleSystem::GetRackNozzle(int idx) const
{
m_nozzles.clear();
m_extder_exist = 0;
m_state = 0; // idle state
return m_nozzle_rack->GetNozzle(idx);
}
const std::map<int, DevNozzle>& DevNozzleSystem::GetRackNozzles() const
{
return m_nozzle_rack->GetRackNozzles();
}
void DevNozzleSystem::SetSupportNozzleRack(bool supported)
{
m_nozzle_rack->SetSupported(supported);
}
const std::vector<DevNozzle> DevNozzleSystem::CollectNozzles(int ext_loc, NozzleFlowType flow_type, float diameter) const
{
auto s_match = [&](const DevNozzle& nozzle) -> bool {
if (nozzle.IsEmpty() || nozzle.IsAbnormal()) {
return false;
}
if (diameter >= 0.0f && nozzle.m_diameter != diameter) {
return false;
}
if (m_owner->is_nozzle_flow_type_supported() && flow_type != nozzle.GetNozzleFlowType()) {
return false;
}
return true;
};
std::vector<DevNozzle> result;
auto ext_nozzle = GetExtNozzle(ext_loc);
if (s_match(ext_nozzle)) {
result.push_back(ext_nozzle);
}
if (ext_loc == MAIN_EXTRUDER_ID) {
auto rack_nozzles = m_nozzle_rack->GetRackNozzles();
for (auto rack_nozzle : rack_nozzles) {
if (s_match(rack_nozzle.second)) {
result.push_back(rack_nozzle.second);
}
}
}
return result;
}
std::vector<MultiNozzleUtils::NozzleGroupInfo> DevNozzleSystem::GetNozzleGroups() const
{
std::vector<MultiNozzleUtils::NozzleGroupInfo> nozzle_groups;
auto nozzle_in_extruder = this->GetExtNozzles();
for (auto& elem : nozzle_in_extruder) {
auto& nozzle = elem.second;
MultiNozzleUtils::NozzleGroupInfo info;
info.extruder_id = nozzle.GetLogicExtruderId();
info.diameter = format_diameter_to_str(nozzle.m_diameter);
info.volume_type = DevNozzle::ToNozzleVolumeType(nozzle.m_nozzle_flow);
info.nozzle_count = 1;
nozzle_groups.emplace_back(std::move(info));
}
auto nozzle_rack = this->GetNozzleRack();
if (!nozzle_rack)
return nozzle_groups;
auto nozzle_in_rack = nozzle_rack->GetRackNozzles(); // nozzles in rack
for (auto& elem : nozzle_in_rack) {
auto& nozzle = elem.second;
if (nozzle.IsUnknown() || nozzle.IsAbnormal())
continue;
int extruder_id = nozzle.GetLogicExtruderId();
std::string diameter = format_diameter_to_str(nozzle.m_diameter);
NozzleVolumeType volume_type = DevNozzle::ToNozzleVolumeType(nozzle.m_nozzle_flow);
bool found = false;
for (auto& group : nozzle_groups) {
if (group.extruder_id == extruder_id &&
group.volume_type == volume_type &&
group.diameter == diameter) {
found = true;
group.nozzle_count += 1;
break;
}
}
if (!found)
nozzle_groups.emplace_back(diameter, volume_type, extruder_id, 1);
}
return nozzle_groups;
}
bool DevNozzleSystem::IsRackMaximumInstalled() const
{
auto ext_nozzle = GetExtNozzle(MAIN_EXTRUDER_ID);
if (ext_nozzle.IsEmpty()) {
return false;
}
const auto& rack_nozzles = m_nozzle_rack->GetRackNozzles();
if (rack_nozzles.size() < 6) {
return false;
}
for (auto item : rack_nozzles) {
if (item.second.IsEmpty()) {
return false;
}
}
return true;
}
bool DevNozzleSystem::HasUnreliableNozzles() const
{
for (auto nozzle : m_ext_nozzles) {
if (!nozzle.second.IsInfoReliable()) {
return true;
}
}
if (m_nozzle_rack->HasUnreliableNozzles()) {
return true;
}
return false;
}
bool DevNozzleSystem::HasUnknownNozzles() const
{
for (auto nozzle : m_ext_nozzles) {
if (nozzle.second.IsUnknown()) {
return true;
}
}
if (m_nozzle_rack->HasUnknownNozzles()) {
return true;
}
return false;
}
void DevNozzleSystem::AddFirmwareInfoWTM(const DevFirmwareVersionInfo& info)
{
static const std::string s_wtm_prefix = "wtm/";
auto pos = info.name.find(s_wtm_prefix);
if (pos == std::string::npos) {
m_ext_nozzle_firmware_info = info;
} else {
try {
auto str = info.name.substr(s_wtm_prefix.size()); // remove "wtm/" prefix
int rack_nozzle_id = std::stoi(str) - 0x10; // rack nozzle IDs start from 0x10
m_nozzle_rack->AddNozzleFirmwareInfo(rack_nozzle_id, info);
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Invalid nozzle ID in firmware name: "
<< info.name << ", error: " << e.what();
}
}
}
void DevNozzleSystem::ClearFirmwareInfoWTM()
{
m_ext_nozzle_firmware_info = DevFirmwareVersionInfo();
m_nozzle_rack->ClearNozzleFirmwareInfo();
}
void DevNozzleSystem::ClearNozzles()
{
m_ext_nozzles.clear();
m_nozzle_rack->ClearRackNozzles();
}
// ---- parsing ----------------------------------------------------------------------------------------
static unordered_map<string, NozzleFlowType> _str2_nozzle_flow_type = {
{"S", NozzleFlowType::S_FLOW},
{"H", NozzleFlowType::H_FLOW},
{"A", NozzleFlowType::S_FLOW},
{"X", NozzleFlowType::S_FLOW}
{"X", NozzleFlowType::S_FLOW},
{"E", NozzleFlowType::H_FLOW}, // E3D high-flow
{"U", NozzleFlowType::U_FLOW}, // TPU 1.75 high-flow -> nvtTPUHighFlow
};
static unordered_map<string, NozzleType> _str2_nozzle_type = {
@@ -57,6 +435,11 @@ static void s_parse_nozzle_type(const std::string& nozzle_type_str, DevNozzle& n
nozzle.m_nozzle_type = _str2_nozzle_type[type_str];
}
}
else if (nozzle_type_str == "N/A")
{
nozzle.m_nozzle_type = NozzleType::ntUndefine;
nozzle.m_nozzle_flow = NozzleFlowType::NONE_FLOWTYPE;
}
}
@@ -66,8 +449,9 @@ void DevNozzleSystemParser::ParseV1_0(const nlohmann::json& nozzletype_json,
std::optional<int> flag_e3d)
{
//Since both the old and new protocols push data.
// assert(system->m_nozzles.size() < 2);
// assert(system->m_ext_nozzles.size() < 2);
DevNozzle nozzle;
nozzle.SetRack(system->GetNozzleRack());
nozzle.m_nozzle_id = 0;
nozzle.m_nozzle_flow = NozzleFlowType::S_FLOW; // default flow type
@@ -112,32 +496,88 @@ void DevNozzleSystemParser::ParseV1_0(const nlohmann::json& nozzletype_json,
}
}
system->m_nozzles[nozzle.m_nozzle_id] = nozzle;
system->m_ext_nozzles[nozzle.m_nozzle_id] = nozzle;
}
void DevNozzleSystemParser::ParseV2_0(const json& nozzle_json, DevNozzleSystem* system)
void DevNozzleSystemParser::ParseV2_0(const json& device_json, DevNozzleSystem* system)
{
system->Reset();
if (nozzle_json.contains("exist"))
if (device_json.contains("nozzle"))
{
system->m_extder_exist = DevUtil::get_flag_bits(nozzle_json["exist"].get<int>(), 0, 16);
const json& nozzle_json = device_json["nozzle"];
if (nozzle_json.contains("exist"))
{
system->m_extder_exist = DevUtil::get_flag_bits(nozzle_json["exist"].get<int>(), 0, 16);
}
if (nozzle_json.contains("state"))
{
system->m_state_0_4 = DevUtil::get_flag_bits(nozzle_json["state"].get<int>(), 0, 4);
int new_reading_idx = DevUtil::get_flag_bits(nozzle_json["state"].get<int>(), 8, 4);
int new_reading_count = DevUtil::get_flag_bits(nozzle_json["state"].get<int>(), 4, 4);
if (system->m_reading_count != new_reading_count || system->m_reading_idx != new_reading_idx)
{
system->m_reading_idx = new_reading_idx;
system->m_reading_count = new_reading_count;
if (system->m_reading_count == 0)
{
system->m_nozzle_rack->SendReadingFinished();
}
}
}
// replace-nozzle state: a rack nozzle standing in for the toolhead position.
// Absent for every non-rack printer's "nozzle" block => both stay std::nullopt (inert).
if (nozzle_json.contains("src_id"))
{
system->m_replace_nozzle_src = std::make_optional(nozzle_json["src_id"].get<int>());
}
if (nozzle_json.contains("tar_id"))
{
system->m_replace_nozzle_tar = std::make_optional(nozzle_json["tar_id"].get<int>());
}
system->ClearNozzles();
for (auto it = nozzle_json["info"].begin(); it != nozzle_json["info"].end(); it++)
{
DevNozzle nozzle_obj;
nozzle_obj.SetRack(system->GetNozzleRack());
const auto& njon = it.value();
nozzle_obj.m_nozzle_id = DevUtil::get_hex_bits(njon["id"].get<int>(), 0);
nozzle_obj.m_diameter = njon["diameter"].get<float>();
s_parse_nozzle_type(njon["type"].get<std::string>(), nozzle_obj);
if (njon.contains("stat"))/*maybe not contains*/
{
nozzle_obj.SetStatus(njon["stat"].get<int>());
}
DevJsonValParser::ParseVal(njon, "fila_id", nozzle_obj.m_fila_id);
DevJsonValParser::ParseVal(njon, "wear", nozzle_obj.m_wear);
if (njon.contains("p_t"))/*maybe not contains*/
{
nozzle_obj.m_nozzle_print_time = njon["p_t"].get<int>();
}
if (njon.contains("color_m"))/*maybe not contains*/
{
nozzle_obj.m_filament_clr = njon["color_m"].get<std::string>();
}
if (DevUtil::get_hex_bits(njon["id"].get<int>(), 1) == 1)
{
system->m_nozzle_rack->AddRackNozzle(nozzle_obj);
}
else
{
system->m_ext_nozzles[nozzle_obj.m_nozzle_id] = nozzle_obj;
}
}
}
if (nozzle_json.contains("state"))
if (device_json.contains("holder"))
{
system->m_state = DevUtil::get_flag_bits(nozzle_json["state"].get<int>(), 0, 4);
}
for (auto it = nozzle_json["info"].begin(); it != nozzle_json["info"].end(); it++)
{
DevNozzle nozzle_obj;
const auto& njon = it.value();
nozzle_obj.m_nozzle_id = njon["id"].get<int>();
nozzle_obj.m_diameter = njon["diameter"].get<float>();
s_parse_nozzle_type(njon["type"].get<std::string>(), nozzle_obj);
system->m_nozzles[nozzle_obj.m_nozzle_id] = nozzle_obj;
system->m_nozzle_rack->ParseRackInfo(device_json["holder"]);
}
}
}
};

View File

@@ -1,28 +1,112 @@
#pragma once
#include "DevDefs.h"
#include "DevFirmware.h"
#include "libslic3r/CommonDefs.hpp"
#include "libslic3r/MultiNozzleUtils.hpp"
#include "slic3r/Utils/json_diff.hpp"
#include <wx/string.h>
#include <map>
#include <memory>
// Device flow-type mapping note (nozzle rack): the device U_FLOW value maps to nvtTPUHighFlow
// (TPU High Flow), so a device-synced TPU-HF rack resolves to nvtTPUHighFlow and the H2C
// change_filament_gcode TPU-kit branch can activate. nvtHybrid is a slicer-only sentinel with no
// device representation, so it stays out of these device flow-type conversions.
//
// GetExtruderNozzleInfo (and its ExtruderNozzleInfos aggregate) is intentionally omitted here;
// the SelectMachine slicing-vs-installed nozzle comparison collects its per-extruder NozzleDef
// entries itself (s_get_slicing_extuder_nozzles).
namespace Slic3r
{
// Previous definitions
class MachineObject;
class DevNozzleRack;
struct DevNozzle
{
friend class DevNozzleSystemParser;
public:
int m_nozzle_id = -1;
NozzleFlowType m_nozzle_flow = NozzleFlowType::S_FLOW;// 0-common 1-high flow
NozzleType m_nozzle_type = NozzleType::ntUndefine;// 0-stainless_steel 1-hardened_steel 5-tungsten_carbide
float m_diameter = 0.0f;// unknown until reported by the printer
public:
// flow/volume conversions (Standard / High Flow / TPU High Flow)
static NozzleFlowType ToNozzleFlowType(const NozzleVolumeType& type);
static NozzleVolumeType ToNozzleVolumeType(const NozzleFlowType& type);
static wxString GetNozzleFlowTypeStr(NozzleFlowType type);
static std::string GetNozzleFlowTypeString(NozzleFlowType type);// untranslated literal ("High Flow"/"Standard"/"TPU High Flow") — the filament blacklist JSON matches these raw strings, so it must NOT use the translated GetNozzleFlowTypeStr (would break non-English locales)
static std::string ToNozzleFlowString(const NozzleFlowType& type);// untranslated "Standard"/"High Flow"/"TPU High Flow" ("" for none) — the raw literal serialized into the get_auto_nozzle_mapping payload
static wxString GetNozzleTypeStr(NozzleType type);
public:
bool IsEmpty() const { return m_nozzle_id < 0; }
void SetRack(const std::weak_ptr<DevNozzleRack>& rack) { m_nozzle_rack = rack; }
int GetNozzleId() const { return m_nozzle_id; }
int GetNozzlePosId() const;// physical position id: rack nozzle -> id + 0x10, else id
NozzleType GetNozzleType() const { return m_nozzle_type; }
NozzleFlowType GetNozzleFlowType() const { return m_nozzle_flow; }
NozzleDiameterType GetNozzleDiameterType() const;
float GetNozzleDiameter() const { return m_diameter; }
float GetNozzleWear() const { return m_wear; }
int GetNozzlePrintTime() const { return m_nozzle_print_time; }
// firmware (rack hotend WTM / extruder nozzle firmware)
DevFirmwareVersionInfo GetFirmwareInfo() const;
// display
wxString GetNozzleDiameterStr() const { return wxString::Format("%.1f mm", m_diameter); }
wxString GetNozzleFlowTypeStr() const { return GetNozzleFlowTypeStr(m_nozzle_flow); }
wxString GetNozzleTypeStr() const { return GetNozzleTypeStr(m_nozzle_type); }
std::string GetFilamentId() const { return m_fila_id; }
std::string GetFilamentColor() const { return m_filament_clr; }
// location
bool AtLeftExtruder() const;
bool AtRightExtruder() const;
int GetLogicExtruderId() const;// warning: logical extruder id
int GetExtruderId() const;// warning: physical extruder id
/* holder nozzle */
bool IsOnRack() const { return m_on_rack; }
bool IsInfoReliable() const;
bool IsNormal() const;
bool IsAbnormal() const;
bool IsUnknown() const;
void SetOnRack(bool on_rack) { m_on_rack = on_rack; }
void SetStatus(int stat) { m_stat = stat; }
private:
int GetTotalExtruderCount() const;
private:
bool m_on_rack = false;
int m_stat = 0;
float m_wear = 0.0f;
std::string m_fila_id; // main material
std::string m_filament_clr; // main color
std::weak_ptr<DevNozzleRack> m_nozzle_rack; // weak pointer to the nozzle rack
int m_nozzle_print_time{0};
};
class DevNozzleSystem
{
friend class DevNozzleSystemParser;
public:
DevNozzleSystem(MachineObject* owner) : m_owner(owner) {}
private:
enum Status : int
{
@@ -31,27 +115,81 @@ namespace Slic3r
};
public:
bool ContainsNozzle(int id) const { return m_nozzles.find(id) != m_nozzles.end(); }
DevNozzle GetNozzle(int id) const;
const std::map<int, DevNozzle>& GetNozzles() const { return m_nozzles;}
bool IsRefreshing() const { return m_state == 1; }
DevNozzleSystem(MachineObject* owner);
~DevNozzleSystem() = default;
public:
MachineObject* GetOwner() const { return m_owner; }
// nozzle by position id: pos_id < 0x10 -> extruder nozzle, else rack nozzle at (pos_id - 0x10)
DevNozzle GetNozzleByPosId(int pos_id) const { return pos_id < 0x10 ? GetExtNozzle(pos_id) : GetRackNozzle(pos_id - 0x10); }
// nozzles on extruder
bool ContainsExtNozzle(int id) const { return m_ext_nozzles.find(id) != m_ext_nozzles.end(); }
DevNozzle GetExtNozzle(int id) const;
const std::map<int, DevNozzle>& GetExtNozzles() const { return m_ext_nozzles; }
int GetExtNozzleCount() const { return (int) m_ext_nozzles.size(); }
// nozzles on rack
void SetSupportNozzleRack(bool supported);
std::shared_ptr<DevNozzleRack> GetNozzleRack() const { return m_nozzle_rack; }
DevNozzle GetRackNozzle(int idx) const;
const std::map<int, DevNozzle>& GetRackNozzles() const;
// nozzles on extruder and rack
bool IsRackMaximumInstalled() const;// true when the main extruder + all 6 rack slots hold nozzles
// grouping (drives the MultiNozzleSyncDialog options)
const std::vector<DevNozzle> CollectNozzles(int ext_loc, NozzleFlowType flow_type, float diameter = -1.0f) const;
std::vector<MultiNozzleUtils::NozzleGroupInfo> GetNozzleGroups() const;
bool IsIdle() const { return m_state_0_4 == NOZZLE_SYSTEM_IDLE; }
bool IsRefreshing() const { return m_state_0_4 == NOZZLE_SYSTEM_REFRESHING; }
bool HasUnreliableNozzles() const;// any extruder or rack nozzle whose reported info is not reliable
bool HasUnknownNozzles() const; // any extruder or rack nozzle of unknown state
/* reading */
int GetReadingIdx() const { return m_reading_idx; }
int GetReadingCount() const { return m_reading_count; }
/* firmware */
void AddFirmwareInfoWTM(const DevFirmwareVersionInfo& info);// route a "wtm/<id>" module version to the rack nozzle, else to the extruder nozzle
void ClearFirmwareInfoWTM();
DevFirmwareVersionInfo GetExtruderNozzleFirmware() const { return m_ext_nozzle_firmware_info; }
/* replace nozzle (device reports src/tar position while a rack nozzle stands in for the toolhead) */
std::optional<int> GetReplaceNozzleSrc() const { return m_replace_nozzle_src; }
std::optional<int> GetReplaceNozzleTar() const { return m_replace_nozzle_tar; }
private:
void Reset();
void ClearNozzles();
private:
MachineObject* m_owner = nullptr;
int m_extder_exist = 0; //0- none exist 1-exist, unused
int m_state = 0; //0-idle 1-checking, unused
std::map<int, DevNozzle> m_nozzles;
int m_extder_exist = 0; //0- none exist 1-exist, unused
int m_state_0_4 = 0; //0-idle 1-refreshing
std::optional<int> m_replace_nozzle_src; // replace nozzle source position (device-reported)
std::optional<int> m_replace_nozzle_tar; // replace nozzle target position (device-reported)
/* refreshing */
int m_reading_idx = 0;
int m_reading_count = 0;
// nozzles on extruder
std::map<int, DevNozzle> m_ext_nozzles;
DevFirmwareVersionInfo m_ext_nozzle_firmware_info;
// nozzles on rack
std::shared_ptr<DevNozzleRack> m_nozzle_rack;
};
class DevNozzleSystemParser
{
public:
static void ParseV1_0(const nlohmann::json& nozzletype_json, const nlohmann::json& diameter_json, DevNozzleSystem* system, std::optional<int> flag_e3d);
static void ParseV2_0(const json& nozzle_json, DevNozzleSystem* system);
static void ParseV2_0(const json& device_json, DevNozzleSystem* system);
};
};
};

View File

@@ -28,6 +28,10 @@ public:
static int get_flag_bits(std::string str, int start, int count = 1);
static int get_flag_bits(int num, int start, int count = 1, int base = 10);
// Extract the hex digit at position `pos` (0 = least-significant).
// eg. get_hex_bits(16, 1, 10) = 1
static int get_hex_bits(int num, int pos, int input_num_base = 10) { return get_flag_bits(num, pos * 4, 4, input_num_base); }
static float string_to_float(const std::string& str_value);
static std::string convertToIp(long long ip);

View File

@@ -1,14 +1,24 @@
#include "DevUtilBackend.h"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/BackgroundSlicingProcess.hpp"
#include "libslic3r/Preset.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r
{
std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> DevUtilBackend::GetNozzleGroupResult(Slic3r::GUI::Plater* plater)
{
if (plater && plater->background_process().get_current_gcode_result()) {
return plater->background_process().get_current_gcode_result()->nozzle_group_result;
}
return nullptr;
}
static std::unordered_map<std::string, DevAmsType> s_ams_type_map = {
{"0", DevAmsType::N3F},
{"1", DevAmsType::N3S},

View File

@@ -7,11 +7,13 @@
#include "DevDefs.h"
#include "DevFilaSystem.h"
#include "libslic3r/MultiNozzleUtils.hpp"
#include <optional>
#include <string>
namespace Slic3r
{
namespace GUI { class Plater; }
class DevUtilBackend
{
@@ -19,6 +21,12 @@ public:
DevUtilBackend() = delete;
public:
// for rack: the slicer's per-filament -> logical-nozzle grouping for the current plate, read off
// the post-slice GCodeProcessorResult (plater->background_process().get_current_gcode_result()).
// Returns nullptr when there is no plater / no current result.
static std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> GetNozzleGroupResult(Slic3r::GUI::Plater* plater);
// for filament preset
static std::optional<DevFilamentDryingPreset> GetFilamentDryingPreset(const std::string& fila_id);
};