Matching for Filaments by Vendor Name, Material and Special Name

This commit is contained in:
Florian Stamer
2026-05-18 14:50:27 +02:00
committed by viewit
parent ec54dcedd0
commit 257da30e47
2 changed files with 164 additions and 13 deletions

View File

@@ -4,6 +4,7 @@
#include "libslic3r/PresetBundle.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/DeviceCore/DevFilaSystem.h"
#include "slic3r/GUI/DeviceCore/DevExtruderSystem.h"
#include "slic3r/GUI/DeviceCore/DevManager.h"
#include "../GUI/DeviceCore/DevStorage.h"
#include "../GUI/DeviceCore/DevFirmware.h"
@@ -88,6 +89,88 @@ std::string map_moonraker_state(std::string state)
return "IDLE";
}
std::string normalize_filament_name_for_match(const std::string& input)
{
std::string normalized = input;
boost::trim(normalized);
// Ignore profile suffixes like " @0.4 nozzle" for name matching.
if (const auto suffix_pos = normalized.find(" @"); suffix_pos != std::string::npos) {
normalized = normalized.substr(0, suffix_pos);
}
std::transform(normalized.begin(), normalized.end(), normalized.begin(),
[](unsigned char c) { return static_cast<char>(std::toupper(c)); });
return normalized;
}
bool filament_name_match_relaxed(const std::string& wanted, const std::string& candidate)
{
if (wanted == candidate) {
return true;
}
// Allow lane names with trailing color descriptors, e.g.:
// "ELEGOO RAPID PETG GREY" -> "ELEGOO RAPID PETG".
if (!candidate.empty() && boost::starts_with(wanted, candidate + " ")) {
return true;
}
return false;
}
std::vector<std::string> vendor_match_candidates(std::string vendor)
{
std::vector<std::string> candidates;
boost::trim(vendor);
if (vendor.empty()) {
return candidates;
}
candidates.push_back(vendor);
// Also try first token (e.g. "Bambu Lab" -> "Bambu") without hardcoded aliases.
const auto first_space = vendor.find_first_of(" \t");
if (first_space != std::string::npos) {
std::string first = vendor.substr(0, first_space);
boost::trim(first);
if (!first.empty() && !boost::iequals(first, vendor)) {
candidates.push_back(first);
}
}
return candidates;
}
std::string filament_id_by_name(const Slic3r::PresetCollection& filaments, const std::string& filament_name)
{
if (filament_name.empty()) {
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher received empty filament name";
return "";
}
const std::string wanted = normalize_filament_name_for_match(filament_name);
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher lookup requested='" << filament_name
<< "' normalized='" << wanted << "'";
for (size_t i = 0; i < filaments.size(); ++i) {
const auto& preset = filaments.preset(i);
if (!preset.is_visible || !preset.is_compatible || preset.filament_id.empty()) {
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher skip preset='" << preset.name
<< "' visible=" << preset.is_visible << " compatible=" << preset.is_compatible
<< " filament_id_empty=" << preset.filament_id.empty();
continue;
}
const std::string candidate = normalize_filament_name_for_match(preset.name);
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher compare preset='" << preset.name
<< "' normalized='" << candidate << "' filament_id='" << preset.filament_id << "'";
if (filament_name_match_relaxed(wanted, candidate)) {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher matched requested='" << filament_name
<< "' normalized='" << wanted << "' to preset='" << preset.name
<< "' filament_id='" << preset.filament_id << "'";
return preset.filament_id;
}
}
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher found no match for requested='" << filament_name
<< "' normalized='" << wanted << "'";
return "";
}
} // namespace
namespace Slic3r {
@@ -448,7 +531,7 @@ int MoonrakerPrinterAgent::set_queue_on_main_fn(QueueOnMainFn fn)
return BAMBU_NETWORK_SUCCESS;
}
void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays)
void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays, int active_lane_index)
{
// Look up MachineObject via DeviceManager
@@ -499,6 +582,7 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
tray_json["tray_info_idx"] = tray->tray_info_idx;
tray_json["tray_type"] = tray->tray_type;
tray_json["tray_sub_brands"] = tray->tray_sub_brands.empty() ? tray->tray_type : tray->tray_sub_brands;
tray_json["tray_color"] = normalize_color_value(tray->tray_color);
// Add temperature data if provided
@@ -530,6 +614,11 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
ams_json["ams"] = ams_array;
ams_json["ams_exist_bits"] = ams_exist_ss.str();
ams_json["tray_exist_bits"] = tray_exist_ss.str();
if (active_lane_index >= 0) {
const std::string active_lane = std::to_string(active_lane_index);
ams_json["tray_now"] = active_lane;
ams_json["tray_tar"] = active_lane;
}
// Wrap in the expected structure for ParseV1_0
nlohmann::json print_json = nlohmann::json::object();
@@ -537,6 +626,7 @@ 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(), false);
ExtderSystemParser::ParseV1_0(print_json, obj->GetExtderSystem());
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.
@@ -570,6 +660,7 @@ bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id)
{
std::vector<AmsTrayData> trays;
int max_lane_index = 0;
int active_lane_index = -1;
// Try Moonraker filament data (more generic, supports any filament changer
// software that reports lane data to Moonraker like AFC and recent Happy
@@ -578,16 +669,16 @@ bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id)
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(ams_count, max_lane_index, trays, active_lane_index);
return true;
}
// Attempt Happy Hare first (more widely adopted, supports more filament changers)
if (fetch_hh_filament_info(trays, max_lane_index)) {
if (fetch_hh_filament_info(trays, max_lane_index, active_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(ams_count, max_lane_index, trays, active_lane_index);
return true;
}
@@ -801,13 +892,51 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vector<AmsTrayDat
tray.slot_index = lane_index;
tray.tray_color = safe_json_string(lane_obj, "color");
tray.tray_type = safe_json_string(lane_obj, "material");
const std::string lane_name = safe_json_string(lane_obj, "name");
const std::string lane_vendor = safe_json_string(lane_obj, "vendor_name");
tray.tray_sub_brands = lane_name;
tray.bed_temp = safe_json_int(lane_obj, "bed_temp");
tray.nozzle_temp = safe_json_int(lane_obj, "nozzle_temp");
tray.has_filament = !tray.tray_type.empty();
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 (bundle) {
const auto vendor_candidates = vendor_match_candidates(lane_vendor);
auto match_with_vendor_prefix = [&](const std::string& suffix) -> std::string {
if (suffix.empty()) {
return "";
}
for (const auto& vendor_candidate : vendor_candidates) {
const std::string requested = vendor_candidate + " " + suffix;
std::string match_id = filament_id_by_name(bundle->filaments, requested);
if (!match_id.empty()) {
return match_id;
}
}
return "";
};
// Prefer the most specific lane identity first, then broader vendor/material mapping.
tray.tray_info_idx = match_with_vendor_prefix(lane_name);
if (tray.tray_info_idx.empty()) {
tray.tray_info_idx = match_with_vendor_prefix(tray.tray_type);
}
if (tray.tray_info_idx.empty() && !lane_name.empty()) {
tray.tray_info_idx = filament_id_by_name(bundle->filaments, lane_name);
}
if (tray.tray_info_idx.empty()) {
tray.tray_info_idx = bundle->filaments.filament_id_by_type(tray.tray_type);
}
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_moonraker_filament_data: lane='" << lane_key
<< "' index=" << lane_index << " material='" << tray.tray_type
<< "' vendor='" << lane_vendor << "' vendor_candidates=" << vendor_candidates.size()
<< "' name='" << lane_name
<< "' mapped_by='preset_bundle' tray_info_idx='" << tray.tray_info_idx << "'";
} else {
tray.tray_info_idx = map_filament_type_to_generic_id(tray.tray_type);
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_moonraker_filament_data: lane='" << lane_key
<< "' index=" << lane_index << " material='" << tray.tray_type
<< "' mapped_by='generic_fallback' tray_info_idx='" << tray.tray_info_idx << "'";
}
max_lane_index = std::max(max_lane_index, lane_index);
trays.push_back(tray);
@@ -822,7 +951,7 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vector<AmsTrayDat
}
// Fetch filament info from Happy Hare MMU
bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index)
bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index, int& active_lane_index)
{
// Query Happy Hare MMU status
std::string url = join_url(device_info.base_url, "/printer/objects/query?mmu");
@@ -894,8 +1023,18 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& tra
// Get arrays
const auto& gate_status = mmu.contains("gate_status") ? mmu["gate_status"] : nlohmann::json::array();
const auto& gate_material = mmu.contains("gate_material") ? mmu["gate_material"] : nlohmann::json::array();
const auto& gate_filament_name = mmu.contains("gate_filament_name") ? mmu["gate_filament_name"] : nlohmann::json::array();
const auto& gate_color = mmu.contains("gate_color") ? mmu["gate_color"] : nlohmann::json::array();
const auto& gate_temperature = mmu.contains("gate_temperature") ? mmu["gate_temperature"] : nlohmann::json::array();
const int active_gate = safe_json_int(mmu, "gate");
active_lane_index = active_gate;
std::string active_filament_name;
if (mmu.contains("active_filament") && mmu["active_filament"].is_object()) {
active_filament_name = safe_json_string(mmu["active_filament"], "filament_name");
if (active_filament_name.empty()) {
active_filament_name = safe_json_string(mmu["active_filament"], "material");
}
}
if (!gate_status.is_array() || !gate_material.is_array() ||
!gate_color.is_array() || !gate_temperature.is_array()) {
@@ -916,8 +1055,13 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& tra
// Extract gate data
std::string material = safe_array_string(gate_material, gate_idx);
std::string filament_name = safe_array_string(gate_filament_name, gate_idx);
std::string color = safe_array_string(gate_color, gate_idx);
int nozzle_temp = safe_array_int(gate_temperature, gate_idx);
// For the active gate, prefer active_filament from MMU state.
if (gate_idx == active_gate && !active_filament_name.empty()) {
filament_name = active_filament_name;
}
// Skip if no material type (empty gate)
if (material.empty()) {
@@ -927,15 +1071,21 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& tra
AmsTrayData tray;
tray.slot_index = gate_idx;
tray.tray_type = material;
tray.tray_sub_brands = filament_name;
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);
if (bundle) {
tray.tray_info_idx = filament_id_by_name(bundle->filaments, filament_name);
if (tray.tray_info_idx.empty()) {
tray.tray_info_idx = bundle->filaments.filament_id_by_type(tray.tray_type);
}
} else {
tray.tray_info_idx = map_filament_type_to_generic_id(tray.tray_type);
}
max_lane_index = std::max(max_lane_index, gate_idx);
trays.push_back(tray);

View File

@@ -92,6 +92,7 @@ protected:
int slot_index = 0; // 0-based slot index
bool has_filament = false;
std::string tray_type; // Material type (e.g., "PLA", "ASA")
std::string tray_sub_brands; // Human-readable filament name
std::string tray_color; // Raw color (#RRGGBB, 0xRRGGBB, or RRGGBBAA)
std::string tray_info_idx; // Setting ID (optional)
int bed_temp = 0; // Optional
@@ -99,7 +100,7 @@ protected:
};
// Build ams JSON and call parser
void build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays);
void build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays, int active_lane_index = -1);
// Methods that derived classes may need to override or access
virtual bool init_device_info(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl);
@@ -161,7 +162,7 @@ private:
uint64_t generation);
// System-specific filament fetch methods
bool fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index);
bool fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index, int& active_lane_index);
bool fetch_moonraker_filament_data(std::vector<AmsTrayData>& trays, int& max_lane_index);
// JSON helper methods