mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-15 11:51:33 +00:00
Merge branch 'feat/ams-parsing' into feat/generic-extruders-and-ams
This commit is contained in:
@@ -4607,6 +4607,16 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
maps.erase(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_placeholder) {
|
||||
// Orca: an empty AMS slot is a placeholder, not a filament - never synthesize a
|
||||
// preset for it. Kept here only to keep index alignment with ams_infos; the sync
|
||||
// drops it so no filament slot is created or retained for an empty tray.
|
||||
ams_filament_presets.push_back("");
|
||||
ams_filament_colors.push_back("");
|
||||
ams_filament_color_types.push_back("");
|
||||
ams_multi_color_filment.push_back({});
|
||||
} else if (use_map) {
|
||||
ams_filament_presets.push_back("Generic PLA");//for unknow matieral
|
||||
auto default_unknown_color = "#CECECE";
|
||||
ams_filament_colors.push_back(default_unknown_color);
|
||||
@@ -4615,12 +4625,6 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
filament_multi_color.push_back(default_unknown_color);
|
||||
}
|
||||
ams_multi_color_filment.push_back(filament_multi_color);
|
||||
} else if (is_placeholder) {
|
||||
// Orca: push placeholders to keep index alignment with ams_infos
|
||||
ams_filament_presets.push_back("");
|
||||
ams_filament_colors.push_back("");
|
||||
ams_filament_color_types.push_back("");
|
||||
ams_multi_color_filment.push_back({});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -4984,6 +4988,10 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
result_multi_colors.push_back(
|
||||
i < ams_multi_color_filment.size() ? ams_multi_color_filment[i]
|
||||
: std::vector<std::string>{ams_filament_colors[i]});
|
||||
} else if (i < ams_infos.size() && ams_infos[i].is_placeholder) {
|
||||
// Orca: an empty AMS slot is a placeholder, not a filament, so drop it instead of
|
||||
// keeping or filling a slot for it. The device AMS panel still shows the empty tray.
|
||||
continue;
|
||||
} else if (i < exist_presets.size()) {
|
||||
// Empty tray or beyond tray count: keep existing filament
|
||||
result_colors.push_back(exist_colors[i]);
|
||||
@@ -5008,8 +5016,8 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
filament_color_type->values = result_color_types;
|
||||
this->filament_presets = result_presets;
|
||||
ams_multi_color_filment = result_multi_colors;
|
||||
filament_map->values.resize(total, 1);
|
||||
filament_volume_map->values.resize(total, static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
filament_map->values.resize(result_presets.size(), 1);
|
||||
filament_volume_map->values.resize(result_presets.size(), static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
} else {
|
||||
// BBL: existing wholesale replace
|
||||
filament_color->values = ams_filament_colors;
|
||||
|
||||
@@ -516,6 +516,13 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
|
||||
ams_id_set.insert(it->first);
|
||||
}
|
||||
|
||||
// Bambu trays index their exist bit as ams_id*4+tray_id; a generic (non-Bambu) unit
|
||||
// reports its own lane count, so its trays carry a cumulative lane index instead.
|
||||
// Same index DevFilaSystem::GetTrayIndexMap() builds and the agent sets bits with.
|
||||
const bool is_bbl_vendor = wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr &&
|
||||
GUI::wxGetApp().preset_bundle->is_bbl_vendor();
|
||||
int generic_tray_base = 0;
|
||||
|
||||
for (auto it = j_ams.begin(); it != j_ams.end(); it++)
|
||||
{
|
||||
if (!it->contains("id")) continue;
|
||||
@@ -855,7 +862,18 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
|
||||
|
||||
if (type_id < 4)
|
||||
{
|
||||
curr_tray->is_exists = (obj->tray_exist_bits & (1 << (ams_id_int * 4 + tray_id_int))) != 0 ? true : false;
|
||||
if (is_bbl_vendor)
|
||||
{
|
||||
curr_tray->is_exists = (obj->tray_exist_bits & (1 << (ams_id_int * 4 + tray_id_int))) != 0 ? true : false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// A generic unit is not four slots wide, so its trays carry a
|
||||
// cumulative lane index across units instead of ams_id*4+tray_id.
|
||||
const int tray_bit = generic_tray_base + tray_id_int;
|
||||
curr_tray->is_exists = tray_bit >= 0 && tray_bit < 32 &&
|
||||
(obj->tray_exist_bits & (1UL << tray_bit)) != 0;
|
||||
}
|
||||
}
|
||||
else if (type_id == DevAms::AMS_LITE_MIXED)
|
||||
{
|
||||
@@ -911,6 +929,9 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!is_bbl_vendor && it->contains("tray") && (*it)["tray"].is_array()) {
|
||||
generic_tray_base += static_cast<int>((*it)["tray"].size());
|
||||
}
|
||||
}
|
||||
// remove not in amsList
|
||||
for (auto it = ams_id_set.begin(); it != ams_id_set.end(); it++)
|
||||
|
||||
@@ -6071,6 +6071,8 @@ void Sidebar::on_bed_type_change(BedType bed_type)
|
||||
* - Map key encoding:
|
||||
* - Virtual trays: 0x10000 + vt_tray.id (first/main extruder), or just vt_tray.id (secondary)
|
||||
* - AMS trays: 0x10000 + (ams_id * 4 + slot_id) (main extruder), or (ams_id * 4 + slot_id) (secondary)
|
||||
* - Generic (non-Bambu) units are not four slots wide: their trays index by a cumulative lane
|
||||
* count across units instead of ams_id * 4 + slot_id
|
||||
* - The 0x10000 flag indicates the main/right extruder
|
||||
* - Map value: DynamicPrintConfig with filament properties (id, type, color, etc.)
|
||||
*
|
||||
@@ -6136,13 +6138,26 @@ std::map<int, DynamicPrintConfig> Sidebar::build_filament_ams_list(MachineObject
|
||||
return std::string();
|
||||
};
|
||||
|
||||
const bool is_bbl_vendor = wxTheApp != nullptr && wxGetApp().preset_bundle != nullptr && wxGetApp().preset_bundle->is_bbl_vendor();
|
||||
|
||||
auto list = obj->GetFilaSystem()->GetAmsList();
|
||||
for (auto ams : list) {
|
||||
int ams_id = std::stoi(ams.first);
|
||||
int extruder = ams.second->GetExtruderId() ? 0 : 0x10000; // Main (first) extruder at right
|
||||
// Bambu units are four slots wide; a generic unit reports its own lane count and its trays
|
||||
// carry a cumulative lane index. The AMS map is keyed by string id, so count preceding units
|
||||
// numerically to keep this the same index the parser and the agent use.
|
||||
int generic_tray_base = 0;
|
||||
if (!is_bbl_vendor) {
|
||||
for (const auto& other : list) {
|
||||
if (std::stoi(other.first) < ams_id)
|
||||
generic_tray_base += static_cast<int>(other.second->GetTrays().size());
|
||||
}
|
||||
}
|
||||
for (auto tray : ams.second->GetTrays()) {
|
||||
int slot_id = std::stoi(tray.first);
|
||||
filament_ams_list.emplace(extruder + (ams_id * 4 + slot_id),
|
||||
const int tray_index = is_bbl_vendor ? ams_id * 4 + slot_id : generic_tray_base + slot_id;
|
||||
filament_ams_list.emplace(extruder + tray_index,
|
||||
build_tray_config(*tray.second, get_ams_name(ams_id, slot_id), std::to_string(ams_id), std::to_string(slot_id)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,42 @@ namespace websocket = beast::websocket;
|
||||
namespace net = boost::asio;
|
||||
using tcp = net::ip::tcp;
|
||||
|
||||
// Decimal value of a string that is nothing but digits, or -1 when it is empty or not a number.
|
||||
int decimal_number(const std::string& digits)
|
||||
{
|
||||
if (digits.empty() || !std::all_of(digits.begin(), digits.end(), [](unsigned char c) { return std::isdigit(c) != 0; }))
|
||||
return -1;
|
||||
try {
|
||||
return std::stoi(digits);
|
||||
} catch (...) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Lane database keys address a lane either as "T<N>" (0-based tool index) or as "lane<N>"
|
||||
// (1-based, the AFC/Happy Hare style). Returns -1 when the key carries no usable lane number.
|
||||
int lane_index_from_key(const std::string& lane_key)
|
||||
{
|
||||
if (lane_key.size() > 1 && (lane_key[0] == 'T' || lane_key[0] == 't'))
|
||||
return decimal_number(lane_key.substr(1));
|
||||
if (lane_key.size() > 4 && lane_key.compare(0, 4, "lane") == 0) {
|
||||
const int lane_number = decimal_number(lane_key.substr(4));
|
||||
return lane_number > 0 ? lane_number - 1 : -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// AFC names toolheads "extruder", "extruder1", ... and derives the toolhead index from the name
|
||||
// itself (AFC_lane.lane_extruder_index): no suffix is 0, otherwise the suffix is the index.
|
||||
int extruder_index_from_name(const std::string& toolhead)
|
||||
{
|
||||
if (toolhead == "extruder")
|
||||
return 0;
|
||||
if (toolhead.size() > 8 && toolhead.compare(0, 8, "extruder") == 0)
|
||||
return decimal_number(toolhead.substr(8));
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct WsEndpoint
|
||||
{
|
||||
std::string host;
|
||||
@@ -443,19 +479,7 @@ int MoonrakerPrinterAgent::set_queue_on_main_fn(QueueOnMainFn fn)
|
||||
|
||||
void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays)
|
||||
{
|
||||
|
||||
// Look up MachineObject via DeviceManager
|
||||
auto* dev_manager = GUI::wxGetApp().getDeviceManager();
|
||||
if (!dev_manager) {
|
||||
return;
|
||||
}
|
||||
MachineObject* obj = dev_manager->get_my_machine(device_info.dev_id);
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build BBL-format JSON for DevFilaSystemParser::ParseV1_0
|
||||
nlohmann::json ams_json = nlohmann::json::object();
|
||||
nlohmann::json ams_array = nlohmann::json::array();
|
||||
|
||||
// Calculate ams_exist_bits and tray_exist_bits
|
||||
@@ -463,11 +487,9 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
|
||||
unsigned long tray_exist_bits = 0;
|
||||
|
||||
for (int ams_id = 0; ams_id < ams_count; ++ams_id) {
|
||||
ams_exist_bits |= (1 << ams_id);
|
||||
|
||||
nlohmann::json ams_unit = nlohmann::json::object();
|
||||
ams_unit["id"] = std::to_string(ams_id);
|
||||
ams_unit["info"] = "0002"; // treat as AMS_LITE
|
||||
if (ams_id < 32) {
|
||||
ams_exist_bits |= (1UL << ams_id);
|
||||
}
|
||||
|
||||
nlohmann::json tray_array = nlohmann::json::array();
|
||||
int max_slot_in_this_ams = std::min(3, max_lane_index - ams_id * 4);
|
||||
@@ -483,43 +505,135 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json tray_json = nlohmann::json::object();
|
||||
tray_json["id"] = std::to_string(slot_id);
|
||||
tray_json["tag_uid"] = "0000000000000000";
|
||||
|
||||
if (tray && tray->has_filament) {
|
||||
tray_exist_bits |= (1 << slot_index);
|
||||
|
||||
tray_json["tray_info_idx"] = tray->tray_info_idx;
|
||||
tray_json["tray_type"] = tray->tray_type;
|
||||
tray_json["tray_color"] = normalize_color_value(tray->tray_color);
|
||||
|
||||
// Add temperature data if provided
|
||||
if (tray->bed_temp > 0) {
|
||||
tray_json["bed_temp"] = std::to_string(tray->bed_temp);
|
||||
}
|
||||
if (tray->nozzle_temp > 0) {
|
||||
tray_json["nozzle_temp_max"] = std::to_string(tray->nozzle_temp);
|
||||
}
|
||||
} else {
|
||||
tray_json["tray_info_idx"] = "";
|
||||
tray_json["tray_type"] = "";
|
||||
tray_json["tray_color"] = "00000000";
|
||||
tray_json["tray_slot_placeholder"] = "1";
|
||||
if (tray && tray->has_filament && slot_index < 32) {
|
||||
tray_exist_bits |= (1UL << slot_index);
|
||||
}
|
||||
|
||||
tray_array.push_back(tray_json);
|
||||
static const AmsTrayData empty_tray;
|
||||
tray_array.push_back(make_ams_tray_json(std::to_string(slot_id), tray ? *tray : empty_tray));
|
||||
}
|
||||
|
||||
nlohmann::json ams_unit = nlohmann::json::object();
|
||||
ams_unit["id"] = std::to_string(ams_id);
|
||||
// Generic unit: type 1 (AMS) maps to AMSModel::GENERIC_AMS, which drives the
|
||||
// variable-lane rendering. This is not a real Bambu AMS-Lite.
|
||||
ams_unit["info"] = "0001";
|
||||
ams_unit["tray"] = tray_array;
|
||||
ams_array.push_back(ams_unit);
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::build_ams_payload: Parsed " << trays.size() << " trays";
|
||||
submit_ams_payload(ams_array, ams_exist_bits, tray_exist_bits);
|
||||
}
|
||||
|
||||
void MoonrakerPrinterAgent::build_ams_payload_grouped(const std::vector<AmsTrayData>& trays)
|
||||
{
|
||||
// One AMS box per extruder, best effort. Lane-data changers (AFC, recent Happy Hare) report an
|
||||
// extruder per lane but no physical box layout, so a box here is a logical feed group, not a
|
||||
// discovered unit. Lanes without an extruder fall back to the main extruder.
|
||||
std::map<int, std::vector<const AmsTrayData*>> boxes;
|
||||
for (const auto& tray : trays) {
|
||||
boxes[tray.extruder_id].push_back(&tray);
|
||||
}
|
||||
|
||||
nlohmann::json ams_array = nlohmann::json::array();
|
||||
unsigned long ams_exist_bits = 0;
|
||||
unsigned long tray_exist_bits = 0;
|
||||
|
||||
int ams_id = 0;
|
||||
// Cumulative lane index across all boxes, in box then slot order. A lane maps to exactly one
|
||||
// extruder, so this is the lane's identity; the parser derives the same index for generic units.
|
||||
int lane_index = 0;
|
||||
for (auto& [extruder_id, lanes] : boxes) {
|
||||
if (ams_id < 32) {
|
||||
ams_exist_bits |= (1UL << ams_id);
|
||||
}
|
||||
|
||||
// Keep a deterministic slot order within the box.
|
||||
std::stable_sort(lanes.begin(), lanes.end(),
|
||||
[](const AmsTrayData* a, const AmsTrayData* b) { return a->slot_index < b->slot_index; });
|
||||
|
||||
nlohmann::json tray_array = nlohmann::json::array();
|
||||
for (size_t slot = 0; slot < lanes.size(); ++slot) {
|
||||
const AmsTrayData& tray = *lanes[slot];
|
||||
// A logical feed group is not a four-slot Bambu AMS: the bit is the lane's cumulative
|
||||
// index, not ams_id*4+slot. DevFilaSystemParser::ParseV1_0 reads the same index.
|
||||
const int bit = lane_index++;
|
||||
if (tray.has_filament && bit < 32) {
|
||||
tray_exist_bits |= (1UL << bit);
|
||||
}
|
||||
tray_array.push_back(make_ams_tray_json(std::to_string(slot), tray));
|
||||
}
|
||||
|
||||
nlohmann::json ams_unit = nlohmann::json::object();
|
||||
ams_unit["id"] = std::to_string(ams_id);
|
||||
// info: bits 0-3 = unit type (1 = AMS, i.e. AMSModel::GENERIC_AMS), bits 8-11 = bound
|
||||
// extruder id. A logical feed group is a generic unit, not a Bambu AMS-Lite.
|
||||
const int info_val = 1 | ((extruder_id & 0x0F) << 8);
|
||||
const char* hex_digits = "0123456789ABCDEF";
|
||||
std::string info = "0000";
|
||||
info[3] = hex_digits[info_val & 0x0F];
|
||||
info[2] = hex_digits[(info_val >> 4) & 0x0F];
|
||||
info[1] = hex_digits[(info_val >> 8) & 0x0F];
|
||||
info[0] = hex_digits[(info_val >> 12) & 0x0F];
|
||||
ams_unit["info"] = info;
|
||||
ams_unit["tray"] = tray_array;
|
||||
ams_array.push_back(ams_unit);
|
||||
++ams_id;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::build_ams_payload_grouped: Parsed " << trays.size()
|
||||
<< " trays in " << boxes.size() << " extruder box(es)";
|
||||
submit_ams_payload(ams_array, ams_exist_bits, tray_exist_bits);
|
||||
}
|
||||
|
||||
nlohmann::json MoonrakerPrinterAgent::make_ams_tray_json(const std::string& tray_id, const AmsTrayData& tray)
|
||||
{
|
||||
nlohmann::json tray_json = nlohmann::json::object();
|
||||
tray_json["id"] = tray_id;
|
||||
tray_json["tag_uid"] = "0000000000000000";
|
||||
|
||||
if (tray.has_filament) {
|
||||
tray_json["tray_info_idx"] = tray.tray_info_idx;
|
||||
tray_json["tray_type"] = tray.tray_type;
|
||||
tray_json["tray_color"] = normalize_color_value(tray.tray_color);
|
||||
|
||||
// Add temperature data if provided
|
||||
if (tray.bed_temp > 0) {
|
||||
tray_json["bed_temp"] = std::to_string(tray.bed_temp);
|
||||
}
|
||||
if (tray.nozzle_temp > 0) {
|
||||
tray_json["nozzle_temp_max"] = std::to_string(tray.nozzle_temp);
|
||||
}
|
||||
} else {
|
||||
tray_json["tray_info_idx"] = "";
|
||||
tray_json["tray_type"] = "";
|
||||
tray_json["tray_color"] = "00000000";
|
||||
tray_json["tray_slot_placeholder"] = "1";
|
||||
}
|
||||
|
||||
return tray_json;
|
||||
}
|
||||
|
||||
void MoonrakerPrinterAgent::submit_ams_payload(const nlohmann::json& ams_array, unsigned long ams_exist_bits, unsigned long tray_exist_bits)
|
||||
{
|
||||
// Look up MachineObject via DeviceManager
|
||||
auto* dev_manager = GUI::wxGetApp().getDeviceManager();
|
||||
if (!dev_manager) {
|
||||
return;
|
||||
}
|
||||
MachineObject* obj = dev_manager->get_my_machine(device_info.dev_id);
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Format as hex strings (matching BBL protocol)
|
||||
std::ostringstream ams_exist_ss;
|
||||
ams_exist_ss << std::hex << std::uppercase << ams_exist_bits;
|
||||
std::ostringstream tray_exist_ss;
|
||||
tray_exist_ss << std::hex << std::uppercase << tray_exist_bits;
|
||||
|
||||
nlohmann::json ams_json = nlohmann::json::object();
|
||||
ams_json["ams"] = ams_array;
|
||||
ams_json["ams_exist_bits"] = ams_exist_ss.str();
|
||||
ams_json["tray_exist_bits"] = tray_exist_ss.str();
|
||||
@@ -530,7 +644,6 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
|
||||
|
||||
// Call the parser to populate DevFilaSystem
|
||||
DevFilaSystemParser::ParseV1_0(print_json, obj, obj->GetFilaSystem().get(), false);
|
||||
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::build_ams_payload: Parsed " << trays.size() << " trays";
|
||||
|
||||
// Set printer_type so update_sync_status() can match it against the preset's printer type.
|
||||
// Without this, the comparison fails and all sync badges are cleared.
|
||||
@@ -570,17 +683,15 @@ bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSync
|
||||
if (fetch_moonraker_filament_data(trays, max_lane_index)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: Detected Moonraker filament system with "
|
||||
<< (max_lane_index + 1) << " lanes";
|
||||
int ams_count = (max_lane_index + 4) / 4;
|
||||
build_ams_payload(ams_count, max_lane_index, trays);
|
||||
build_ams_payload_grouped(trays);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Attempt Happy Hare first (more widely adopted, supports more filament changers)
|
||||
// Fall back to the Happy Hare `mmu` object for HH builds that do not publish lane_data
|
||||
if (fetch_hh_filament_info(trays, max_lane_index)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: Detected Happy Hare MMU with "
|
||||
<< (max_lane_index + 1) << " gates";
|
||||
int ams_count = (max_lane_index + 4) / 4;
|
||||
build_ams_payload(ams_count, max_lane_index, trays);
|
||||
build_ams_payload_grouped(trays);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -686,6 +797,30 @@ int MoonrakerPrinterAgent::safe_json_int(const nlohmann::json& obj, const char*
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MoonrakerPrinterAgent::safe_json_int_flexible(const nlohmann::json& obj, const char* key, int default_value)
|
||||
{
|
||||
auto it = obj.find(key);
|
||||
if (it == obj.end())
|
||||
return default_value;
|
||||
if (it->is_number_integer())
|
||||
return it->get<int>();
|
||||
if (it->is_string()) {
|
||||
const std::string s = it->get<std::string>();
|
||||
if (s.empty())
|
||||
return default_value;
|
||||
try {
|
||||
std::size_t consumed = 0;
|
||||
const int value = std::stoi(s, &consumed);
|
||||
if (consumed != s.size()) // reject partial parses such as "2oops"
|
||||
return default_value;
|
||||
return value;
|
||||
} catch (...) {
|
||||
return default_value;
|
||||
}
|
||||
}
|
||||
return default_value;
|
||||
}
|
||||
|
||||
std::string MoonrakerPrinterAgent::safe_array_string(const nlohmann::json& arr, int idx)
|
||||
{
|
||||
if (arr.is_array() && idx >= 0 && idx < static_cast<int>(arr.size()) && arr[idx].is_string())
|
||||
@@ -789,28 +924,39 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vector<AmsTrayDat
|
||||
trays.clear();
|
||||
max_lane_index = 0;
|
||||
|
||||
// The lane_data namespace carries no extruder field; read the lane -> toolhead binding from the
|
||||
// AFC printer objects so lanes on different toolheads end up in different AMS boxes.
|
||||
std::map<std::string, int> lane_extruder;
|
||||
fetch_afc_lane_extruders(lane_extruder);
|
||||
|
||||
for (const auto& [lane_key, lane_obj] : value.items()) {
|
||||
if (!lane_obj.is_object()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract lane index from the "lane" field (tool number, 0-based)
|
||||
std::string lane_str = safe_json_string(lane_obj, "lane");
|
||||
int lane_index = -1;
|
||||
if (!lane_str.empty()) {
|
||||
try {
|
||||
lane_index = std::stoi(lane_str);
|
||||
} catch (...) {
|
||||
lane_index = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Lane tool number (0-based). AFC reports it as a string in the documented sample but it
|
||||
// can also be a number; accept either and skip lanes without a usable index. Databases
|
||||
// written without a "lane" field fall back to the key, which names the lane either as
|
||||
// "T<N>" (0-based) or as "lane<N>" (1-based).
|
||||
int lane_index = safe_json_int_flexible(lane_obj, "lane", -1);
|
||||
if (lane_index < 0) {
|
||||
continue;
|
||||
lane_index = lane_index_from_key(lane_key);
|
||||
if (lane_index < 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
AmsTrayData tray;
|
||||
tray.slot_index = lane_index;
|
||||
// AFC exposes the lane's extruder/toolhead as "extruder_index"; tolerate "extruder_id" as
|
||||
// an alias. lane_data has neither, so fall back to the AFC lane objects and only then to
|
||||
// the main extruder (best effort).
|
||||
tray.extruder_id = safe_json_int_flexible(lane_obj, "extruder_index",
|
||||
safe_json_int_flexible(lane_obj, "extruder_id", -1));
|
||||
if (tray.extruder_id < 0) {
|
||||
const auto it = lane_extruder.find(lane_key);
|
||||
tray.extruder_id = it != lane_extruder.end() ? it->second : 0;
|
||||
}
|
||||
tray.tray_color = safe_json_string(lane_obj, "color");
|
||||
tray.tray_type = safe_json_string(lane_obj, "material");
|
||||
tray.bed_temp = safe_json_int(lane_obj, "bed_temp");
|
||||
@@ -915,50 +1061,119 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& tra
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse gate data
|
||||
// Parse gate data. Every gate becomes a lane so empty/unknown gates surface
|
||||
// as empty AMS slots rather than vanishing. HH is single-extruder, so all
|
||||
// lanes belong to feed group 0.
|
||||
trays.clear();
|
||||
max_lane_index = 0;
|
||||
|
||||
for (int gate_idx = 0; gate_idx < num_gates; ++gate_idx) {
|
||||
// Check gate_status: -1 = unknown, 0 = empty, 1 or 2 = available
|
||||
int status = safe_array_int(gate_status, gate_idx);
|
||||
if (status <= 0) {
|
||||
continue; // Skip unknown or empty gates
|
||||
}
|
||||
|
||||
// Extract gate data
|
||||
std::string material = safe_array_string(gate_material, gate_idx);
|
||||
std::string color = safe_array_string(gate_color, gate_idx);
|
||||
int nozzle_temp = safe_array_int(gate_temperature, gate_idx);
|
||||
|
||||
// Skip if no material type (empty gate)
|
||||
if (material.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
AmsTrayData tray;
|
||||
tray.slot_index = gate_idx;
|
||||
tray.tray_type = material;
|
||||
tray.tray_color = color;
|
||||
tray.nozzle_temp = nozzle_temp;
|
||||
tray.bed_temp = 0; // HH doesn't provide bed temp in gate arrays
|
||||
tray.has_filament = true;
|
||||
tray.extruder_id = 0;
|
||||
|
||||
auto* bundle = GUI::wxGetApp().preset_bundle;
|
||||
tray.tray_info_idx = bundle
|
||||
? bundle->filaments.filament_id_by_type(tray.tray_type)
|
||||
: map_filament_type_to_generic_id(tray.tray_type);
|
||||
if (status > 0 && !material.empty()) {
|
||||
tray.tray_type = material;
|
||||
tray.tray_color = color;
|
||||
tray.nozzle_temp = nozzle_temp;
|
||||
tray.bed_temp = 0; // HH doesn't provide bed temp in gate arrays
|
||||
tray.has_filament = true;
|
||||
|
||||
auto* bundle = GUI::wxGetApp().preset_bundle;
|
||||
tray.tray_info_idx = bundle
|
||||
? bundle->filaments.filament_id_by_type(tray.tray_type)
|
||||
: map_filament_type_to_generic_id(tray.tray_type);
|
||||
} else {
|
||||
tray.has_filament = false;
|
||||
}
|
||||
|
||||
max_lane_index = std::max(max_lane_index, gate_idx);
|
||||
trays.push_back(tray);
|
||||
}
|
||||
|
||||
if (trays.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_hh_filament_info: No valid HH gates found";
|
||||
max_lane_index = num_gates - 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// AFC writes its filament data into the lane_data database namespace, and the records of the
|
||||
// fixture/agent write path carry no extruder field, so the lane -> toolhead binding has to be read
|
||||
// from the printer objects: each AFC_lane <name>.extruder names the toolhead its lane feeds, and
|
||||
// the toolhead's name carries its own index. Best effort: on failure every lane keeps the fallback
|
||||
// of the main extruder.
|
||||
bool MoonrakerPrinterAgent::fetch_afc_lane_extruders(std::map<std::string, int>& lane_extruder)
|
||||
{
|
||||
auto query_objects = [this](const std::string& query_string, nlohmann::json& out) {
|
||||
auto http = Http::get(join_url(device_info.base_url, "/printer/objects/query?" + query_string));
|
||||
if (!device_info.api_key.empty()) {
|
||||
http.header("X-Api-Key", device_info.api_key);
|
||||
}
|
||||
std::string body;
|
||||
unsigned status_code = 0;
|
||||
http.timeout_connect(5)
|
||||
.timeout_max(10)
|
||||
.on_complete([&](std::string response, unsigned status) {
|
||||
body = std::move(response);
|
||||
status_code = status;
|
||||
})
|
||||
.perform_sync();
|
||||
if (status_code != 200) {
|
||||
return false;
|
||||
}
|
||||
out = nlohmann::json::parse(body, nullptr, false, true);
|
||||
return !out.is_discarded();
|
||||
};
|
||||
|
||||
nlohmann::json afc_json;
|
||||
if (!query_objects("AFC", afc_json) || !afc_json.contains("result") ||
|
||||
!afc_json["result"].contains("status") || !afc_json["result"]["status"].contains("AFC")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
const auto& afc = afc_json["result"]["status"]["AFC"];
|
||||
const auto& lanes = afc.contains("lanes") ? afc["lanes"] : nlohmann::json::array();
|
||||
if (!lanes.is_array() || lanes.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Query every lane object in one request; the object names are "AFC_lane <lane name>".
|
||||
std::string query_string;
|
||||
for (const auto& lane : lanes) {
|
||||
if (!lane.is_string()) {
|
||||
return false;
|
||||
}
|
||||
if (!query_string.empty()) {
|
||||
query_string += "&";
|
||||
}
|
||||
query_string += "AFC_lane%20" + lane.get<std::string>();
|
||||
}
|
||||
|
||||
nlohmann::json lanes_json;
|
||||
if (!query_objects(query_string, lanes_json) || !lanes_json.contains("result") ||
|
||||
!lanes_json["result"].contains("status")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& status = lanes_json["result"]["status"];
|
||||
for (const auto& lane : lanes) {
|
||||
const std::string name = lane.get<std::string>();
|
||||
const std::string key = "AFC_lane " + name;
|
||||
if (!status.contains(key) || !status[key].is_object() || !status[key].contains("extruder") ||
|
||||
!status[key]["extruder"].is_string()) {
|
||||
continue;
|
||||
}
|
||||
const std::string toolhead = status[key]["extruder"].get<std::string>();
|
||||
const int toolhead_index = extruder_index_from_name(toolhead);
|
||||
if (toolhead_index >= 0) {
|
||||
lane_extruder[name] = toolhead_index;
|
||||
}
|
||||
}
|
||||
|
||||
return !lane_extruder.empty();
|
||||
}
|
||||
|
||||
int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::string& json_str)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
@@ -88,6 +89,7 @@ protected:
|
||||
// Tray data for AMS payload building
|
||||
struct AmsTrayData {
|
||||
int slot_index = 0; // 0-based slot index
|
||||
int extruder_id = 0; // Extruder this lane feeds (AFC extruder_index); 0 when unknown
|
||||
bool has_filament = false;
|
||||
std::string tray_type; // Material type (e.g., "PLA", "ASA")
|
||||
std::string tray_color; // Raw color (#RRGGBB, 0xRRGGBB, or RRGGBBAA)
|
||||
@@ -99,6 +101,10 @@ protected:
|
||||
// Build ams JSON and call parser
|
||||
void build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays);
|
||||
|
||||
// Build ams JSON with one box per extruder (best effort, for lane_data-based filament
|
||||
// changers such as AFC) and call the parser. Boxes are ordered by extruder id.
|
||||
void build_ams_payload_grouped(const std::vector<AmsTrayData>& trays);
|
||||
|
||||
// Methods that derived classes may need to override or access
|
||||
virtual bool init_device_info(const std::string& dev_id, const std::string& dev_ip, const std::string& username, const std::string& password, bool use_ssl, const std::string& port);
|
||||
virtual bool fetch_device_info(const std::string& base_url, const std::string& api_key, MoonrakerDeviceInfo& info, std::string& error) const;
|
||||
@@ -163,10 +169,22 @@ private:
|
||||
// System-specific filament fetch methods
|
||||
bool fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index);
|
||||
bool fetch_moonraker_filament_data(std::vector<AmsTrayData>& trays, int& max_lane_index);
|
||||
// Lane name -> toolhead index, read from the AFC printer objects (the lane_data database has no
|
||||
// extruder field). Returns false when AFC is not present or the objects cannot be read.
|
||||
bool fetch_afc_lane_extruders(std::map<std::string, int>& lane_extruder);
|
||||
|
||||
// Build one BBL tray JSON object (placeholder when the tray has no filament).
|
||||
static nlohmann::json make_ams_tray_json(const std::string& tray_id, const AmsTrayData& tray);
|
||||
|
||||
// Wrap the AMS boxes into the payload ParseV1_0 understands and apply the pull-mode
|
||||
// readiness state (printer_type, push counters, storage, module versions).
|
||||
void submit_ams_payload(const nlohmann::json& ams_array, unsigned long ams_exist_bits, unsigned long tray_exist_bits);
|
||||
|
||||
// JSON helper methods
|
||||
static std::string safe_json_string(const nlohmann::json& obj, const char* key);
|
||||
static int safe_json_int(const nlohmann::json& obj, const char* key);
|
||||
// Accepts either a JSON number or a numeric string (AFC reports some fields as strings).
|
||||
static int safe_json_int_flexible(const nlohmann::json& obj, const char* key, int default_value);
|
||||
static std::string safe_array_string(const nlohmann::json& arr, int idx);
|
||||
static int safe_array_int(const nlohmann::json& arr, int idx);
|
||||
static std::string normalize_color_value(const std::string& color);
|
||||
|
||||
Reference in New Issue
Block a user