feat(gui): multi-nozzle UI for H2C/A2L

Multi-nozzle sync widget, AMS rack-nozzle mapping popup, calibration rework, send-dialog nozzle mapping and extruder-count UI. Includes the fix to persist the AMS sync badge on filament cards (H2C/A2L and direct-sync printers).
This commit is contained in:
SoftFever
2026-07-09 00:06:27 +08:00
parent a098b483f6
commit 28b7127150
52 changed files with 6136 additions and 250 deletions

View File

@@ -23,6 +23,7 @@
#include "fast_float/fast_float.h"
#include "DeviceCore/DevFilaSystem.h"
#include "DeviceCore/DevFilaSwitch.h"
#include "DeviceCore/DevExtensionTool.h"
#include "DeviceCore/DevExtruderSystem.h"
#include "DeviceCore/DevNozzleSystem.h"
@@ -39,6 +40,7 @@
#include "DeviceCore/DevHMS.h"
#include "DeviceCore/DevMapping.h"
#include "DeviceCore/DevMappingNozzle.h"
#include "DeviceCore/DevManager.h"
#include "DeviceCore/DevUtil.h"
@@ -567,11 +569,14 @@ MachineObject::MachineObject(DeviceManager* manager, NetworkAgent* agent, std::s
m_extension_tool = DevExtensionTool::Create(this);
m_nozzle_system = new DevNozzleSystem(this);
m_fila_system = new DevFilaSystem(this);
m_fila_switch = new DevFilaSwitch(this);
m_hms_system = new DevHMS(this);
m_config = new DevConfig(this);
m_ctrl = new DevCtrl(this);
m_print_options = new DevPrintOptions(this);
m_nozzle_mapping_ptr = std::make_shared<DevNozzleMappingCtrl>(this);
}
}
@@ -615,6 +620,9 @@ MachineObject::~MachineObject()
delete m_fila_system;
m_fila_system = nullptr;
delete m_fila_switch;
m_fila_switch = nullptr;
delete m_hms_system;
m_hms_system = nullptr;
@@ -862,6 +870,9 @@ void MachineObject::clear_version_info()
cutting_module_version_info = DevFirmwareVersionInfo();
extinguish_version_info = DevFirmwareVersionInfo();
module_vers.clear();
// Drop cached rack-hotend (WTM) firmware alongside the module list.
// Inert for non-rack printers (rack firmware map is empty).
m_nozzle_system->ClearFirmwareInfoWTM();
}
void MachineObject::store_version_info(const DevFirmwareVersionInfo& info)
@@ -874,6 +885,11 @@ void MachineObject::store_version_info(const DevFirmwareVersionInfo& info)
cutting_module_version_info = info;
} else if (info.isExtinguishSystem()) {
extinguish_version_info = info;
} else if (info.isWTM()) {
// Route rack-hotend / extruder-nozzle firmware into the nozzle system so
// the rack upgrade UI can read per-nozzle versions. isWTM() is false for every non-rack
// printer's modules, so this branch never fires outside H2C.
m_nozzle_system->AddFirmwareInfoWTM(info);
}
module_vers.emplace(info.name, info);
@@ -1976,6 +1992,10 @@ int MachineObject::command_set_pa_calibration(const std::vector<PACalibResult> &
j["print"]["filaments"][i]["n_coef"] = std::to_string(pa_calib_values[i].n_coef);
else
j["print"]["filaments"][i]["n_coef"] = "0.0";
if (pa_calib_values[i].nozzle_pos_id >= 0) {
j["print"]["filaments"][i]["nozzle_pos"] = pa_calib_values[i].nozzle_pos_id;
j["print"]["filaments"][i]["nozzle_sn"] = pa_calib_values[i].nozzle_sn;
}
}
BOOST_LOG_TRIVIAL(info) << "extrusion_cali_set: " << j.dump();
@@ -1994,6 +2014,10 @@ int MachineObject::command_delete_pa_calibration(const PACalibIndexInfo& pa_cali
j["print"]["nozzle_id"] = _generate_nozzle_id(pa_calib.nozzle_volume_type, to_string_nozzle_diameter(pa_calib.nozzle_diameter)).ToStdString();
j["print"]["filament_id"] = pa_calib.filament_id;
j["print"]["cali_idx"] = pa_calib.cali_idx;
if (pa_calib.nozzle_pos_id >= 0) {
j["print"]["nozzle_pos"] = pa_calib.nozzle_pos_id;
j["print"]["nozzle_sn"] = pa_calib.nozzle_sn;
}
j["print"]["nozzle_diameter"] = to_string_nozzle_diameter(pa_calib.nozzle_diameter);
BOOST_LOG_TRIVIAL(info) << "extrusion_cali_del: " << j.dump();
@@ -2014,6 +2038,11 @@ int MachineObject::command_get_pa_calibration_tab(const PACalibExtruderInfo &cal
j["print"]["nozzle_id"] = _generate_nozzle_id(calib_info.nozzle_volume_type, to_string_nozzle_diameter(calib_info.nozzle_diameter)).ToStdString();
j["print"]["nozzle_diameter"] = to_string_nozzle_diameter(calib_info.nozzle_diameter);
if (calib_info.nozzle_pos_id >= 0) {
j["print"]["nozzle_pos"] = calib_info.nozzle_pos_id;
j["print"]["nozzle_sn"] = calib_info.nozzle_sn;
}
BOOST_LOG_TRIVIAL(info) << "extrusion_cali_get: " << j.dump();
request_tab_from_bbs = true;
return this->publish_json(j);
@@ -2040,6 +2069,10 @@ int MachineObject::commnad_select_pa_calibration(const PACalibIndexInfo& pa_cali
j["print"]["slot_id"] = pa_calib_info.slot_id;
j["print"]["cali_idx"] = pa_calib_info.cali_idx;
j["print"]["filament_id"] = pa_calib_info.filament_id;
if (pa_calib_info.nozzle_pos_id >= 0) {
j["print"]["nozzle_pos"] = pa_calib_info.nozzle_pos_id;
j["print"]["nozzle_sn"] = pa_calib_info.nozzle_sn;
}
j["print"]["nozzle_diameter"] = to_string_nozzle_diameter(pa_calib_info.nozzle_diameter);
BOOST_LOG_TRIVIAL(info) << "extrusion_cali_sel: " << j.dump();
@@ -2379,6 +2412,7 @@ void MachineObject::reset()
jobState_ = 0;
m_plate_index = -1;
device_cert_installed = false;
clear_auto_nozzle_mapping();// reset nozzle mapping
// reset print_json
json empty_j;
@@ -2896,6 +2930,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
}
m_fan->ParseV2_0(jj);
m_fila_switch->ParseFilaSwitchInfo(jj);
if (jj.contains("support_filament_backup")) {
if (jj["support_filament_backup"].is_boolean()) {
@@ -3000,6 +3035,8 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
if (jj.contains("command")) {
m_nozzle_mapping_ptr->ParseAutoNozzleMapping(jj);
if (jj["command"].get<std::string>() == "ams_change_filament") {
if (jj.contains("errno")) {
if (jj["errno"].is_number()) {
@@ -4082,6 +4119,14 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
pa_calib_result.nozzle_volume_type = convert_to_nozzle_type((*it)["nozzle_id"].get<std::string>());
}
if ((*it).contains("nozzle_pos")) {
pa_calib_result.nozzle_pos_id = (*it)["nozzle_pos"].get<int>();
}
if ((*it).contains("nozzle_sn")) {
pa_calib_result.nozzle_sn = (*it)["nozzle_sn"].get<std::string>();
}
if (jj["nozzle_diameter"].is_number_float()) {
pa_calib_result.nozzle_diameter = jj["nozzle_diameter"].get<float>();
} else if (jj["nozzle_diameter"].is_string()) {
@@ -4179,6 +4224,14 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
pa_calib_result.nozzle_volume_type = NozzleVolumeType::nvtStandard;
}
if (it->contains("nozzle_pos")) {
pa_calib_result.nozzle_pos_id = (*it)["nozzle_pos"].get<int>();
}
if (it->contains("nozzle_sn")) {
pa_calib_result.nozzle_sn = (*it)["nozzle_sn"].get<std::string>();
}
if ((*it)["k_value"].is_number_float())
pa_calib_result.k_value = (*it)["k_value"].get<float>();
else if ((*it)["k_value"].is_string())
@@ -5004,6 +5057,7 @@ void MachineObject::parse_new_info(json print)
is_support_user_preset = get_flag_bits(fun, 11);
is_support_door_open_check = get_flag_bits(fun, 12);
is_support_nozzle_blob_detection = get_flag_bits(fun, 13);
m_nozzle_system->SetSupportNozzleRack(get_flag_bits(fun, 60)); // H2C hotend rack support (device-side gate for the sync dialog)
is_support_upgrade_kit = get_flag_bits(fun, 14);
is_support_internal_timelapse = get_flag_bits(fun, 28);
m_support_mqtt_homing = get_flag_bits(fun, 32);
@@ -5066,7 +5120,10 @@ void MachineObject::parse_new_info(json print)
DevBed::ParseV2_0(device,m_bed);
if (device.contains("nozzle")) { DevNozzleSystemParser::ParseV2_0(device["nozzle"], m_nozzle_system); }
// Pass the whole device json: ParseV2_0 reads ext/rack nozzles from device["nozzle"] and the
// hotend-rack state from device["holder"] (rack data lives outside device["nozzle"]). It is a
// no-op for devices that report neither key, so non-rack machines are unaffected.
DevNozzleSystemParser::ParseV2_0(device, m_nozzle_system);
if (device.contains("extruder")) { ExtderSystemParser::ParseV2_0(device["extruder"], m_extder_system);}
if (device.contains("ext_tool")) { DevExtensionToolParser::ParseV2_0(device["ext_tool"], m_extension_tool); }