mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-18 16:32:06 +00:00
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:
@@ -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)
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
};
|
||||
};
|
||||
@@ -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)
|
||||
|
||||
@@ -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; };
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
131
src/slic3r/GUI/DeviceCore/DevFilaSwitch.cpp
Normal file
131
src/slic3r/GUI/DeviceCore/DevFilaSwitch.cpp
Normal 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
|
||||
91
src/slic3r/GUI/DeviceCore/DevFilaSwitch.h
Normal file
91
src/slic3r/GUI/DeviceCore/DevFilaSwitch.h
Normal 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;
|
||||
};
|
||||
};
|
||||
@@ -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));
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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"); }
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
485
src/slic3r/GUI/DeviceCore/DevMappingNozzle.cpp
Normal file
485
src/slic3r/GUI/DeviceCore/DevMappingNozzle.cpp
Normal 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
|
||||
108
src/slic3r/GUI/DeviceCore/DevMappingNozzle.h
Normal file
108
src/slic3r/GUI/DeviceCore/DevMappingNozzle.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
112
src/slic3r/GUI/DeviceCore/DevNozzleRack.cpp
Normal file
112
src/slic3r/GUI/DeviceCore/DevNozzleRack.cpp
Normal 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);
|
||||
}
|
||||
|
||||
};
|
||||
134
src/slic3r/GUI/DeviceCore/DevNozzleRack.h
Normal file
134
src/slic3r/GUI/DeviceCore/DevNozzleRack.h
Normal 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;
|
||||
};
|
||||
};
|
||||
283
src/slic3r/GUI/DeviceCore/DevNozzleRackCtrl.cpp
Normal file
283
src/slic3r/GUI/DeviceCore/DevNozzleRackCtrl.cpp
Normal 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);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user