diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index d016900572..52ba10ab03 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -608,6 +608,8 @@ set(SLIC3R_GUI_SOURCES Utils/OrcaPrinterAgent.hpp Utils/QidiPrinterAgent.cpp Utils/QidiPrinterAgent.hpp + Utils/SnapmakerPrinterAgent.cpp + Utils/SnapmakerPrinterAgent.hpp Utils/MoonrakerPrinterAgent.cpp Utils/MoonrakerPrinterAgent.hpp Utils/BBLCloudServiceAgent.cpp diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index 0e0ec418ac..a49c69fc10 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -723,12 +723,18 @@ bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id) return true; } +std::string MoonrakerPrinterAgent::trim_and_upper(const std::string& input) +{ + std::string result = input; + boost::trim(result); + std::transform(result.begin(), result.end(), result.begin(), + [](unsigned char c) { return static_cast(std::toupper(c)); }); + return result; +} + std::string MoonrakerPrinterAgent::map_filament_type_to_generic_id(const std::string& filament_type) { - std::string upper = filament_type; - boost::trim(upper); - std::transform(upper.begin(), upper.end(), upper.begin(), - [](unsigned char c) { return static_cast(std::toupper(c)); }); + const std::string upper = trim_and_upper(filament_type); // Map to OrcaFilamentLibrary preset IDs (compatible with all printers) // Source: resources/profiles/OrcaFilamentLibrary/filament/ diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp index 34be0dc3e3..0a560388cf 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp @@ -70,8 +70,8 @@ public: int set_queue_on_main_fn(QueueOnMainFn fn) override; // Pull-mode agent (on-demand filament sync) - virtual FilamentSyncMode get_filament_sync_mode() const override { return FilamentSyncMode::pull; } - virtual bool fetch_filament_info(std::string dev_id) override; + FilamentSyncMode get_filament_sync_mode() const override { return FilamentSyncMode::pull; } + bool fetch_filament_info(std::string dev_id) override; protected: struct MoonrakerDeviceInfo @@ -115,6 +115,9 @@ protected: std::string sanitize_filename(const std::string& filename); std::string join_url(const std::string& base_url, const std::string& path) const; + // Trim whitespace and convert to uppercase + static std::string trim_and_upper(const std::string& input); + // Map filament type to OrcaFilamentLibrary preset ID for AMS sync compatibility static std::string map_filament_type_to_generic_id(const std::string& filament_type); diff --git a/src/slic3r/Utils/NetworkAgentFactory.cpp b/src/slic3r/Utils/NetworkAgentFactory.cpp index 000a6c87f4..025aec9bf0 100644 --- a/src/slic3r/Utils/NetworkAgentFactory.cpp +++ b/src/slic3r/Utils/NetworkAgentFactory.cpp @@ -4,6 +4,7 @@ #include "BBLPrinterAgent.hpp" #include "OrcaPrinterAgent.hpp" #include "QidiPrinterAgent.hpp" +#include "SnapmakerPrinterAgent.hpp" #include "MoonrakerPrinterAgent.hpp" #include #include @@ -96,6 +97,7 @@ void NetworkAgentFactory::register_all_agents() { register_agent(); register_agent(); + register_agent(); register_agent(); // BBLPrinterAgent takes no constructor args, so register manually diff --git a/src/slic3r/Utils/QidiPrinterAgent.cpp b/src/slic3r/Utils/QidiPrinterAgent.cpp index bf724d98d8..e34dbe855a 100644 --- a/src/slic3r/Utils/QidiPrinterAgent.cpp +++ b/src/slic3r/Utils/QidiPrinterAgent.cpp @@ -4,7 +4,6 @@ #include "nlohmann/json.hpp" #include #include -#include #include #include @@ -293,9 +292,7 @@ void QidiPrinterAgent::parse_filament_sections(const std::string& content, std:: std::string QidiPrinterAgent::map_filament_type_to_setting_id(const std::string& filament_type) { - std::string upper = filament_type; - boost::trim(upper); - std::transform(upper.begin(), upper.end(), upper.begin(), [](unsigned char c) { return static_cast(std::toupper(c)); }); + const std::string upper = trim_and_upper(filament_type); if (upper == "PLA") { return "QD_1_0_1"; @@ -351,10 +348,7 @@ std::string QidiPrinterAgent::infer_series_id(const std::string& model_id, const std::string QidiPrinterAgent::normalize_filament_type(const std::string& filament_type) { - std::string trimmed = filament_type; - boost::trim(trimmed); - std::string upper = trimmed; - std::transform(upper.begin(), upper.end(), upper.begin(), [](unsigned char c) { return static_cast(std::toupper(c)); }); + const std::string upper = trim_and_upper(filament_type); if (upper.find("PLA") != std::string::npos) return "PLA"; @@ -373,7 +367,7 @@ std::string QidiPrinterAgent::normalize_filament_type(const std::string& filamen if (upper.find("PVA") != std::string::npos) return "PVA"; - return trimmed; + return upper; } } // namespace Slic3r diff --git a/src/slic3r/Utils/SnapmakerPrinterAgent.cpp b/src/slic3r/Utils/SnapmakerPrinterAgent.cpp new file mode 100644 index 0000000000..a352ee889a --- /dev/null +++ b/src/slic3r/Utils/SnapmakerPrinterAgent.cpp @@ -0,0 +1,157 @@ +#include "SnapmakerPrinterAgent.hpp" +#include "Http.hpp" + +#include "nlohmann/json.hpp" +#include + +namespace Slic3r { + +namespace { + +constexpr const char* SNAPMAKER_AGENT_VERSION = "0.0.1"; + +// Safely access a parallel array by index, returning a fallback if out of bounds. +template +T safe_at(const std::vector& vec, int index, const T& fallback) +{ + return (index >= 0 && index < static_cast(vec.size())) ? vec[index] : fallback; +} + +} // anonymous namespace + +SnapmakerPrinterAgent::SnapmakerPrinterAgent(std::string log_dir) : MoonrakerPrinterAgent(std::move(log_dir)) {} + +AgentInfo SnapmakerPrinterAgent::get_agent_info_static() +{ + return AgentInfo{"snapmaker", "Snapmaker Printer Agent", SNAPMAKER_AGENT_VERSION, "Snapmaker printer agent"}; +} + +std::string SnapmakerPrinterAgent::combine_filament_type(const std::string& type, const std::string& sub_type) +{ + const std::string base = trim_and_upper(type); + const std::string sub = trim_and_upper(sub_type); + + if (base.empty()) + return "PLA"; + + if (sub.empty() || sub == "NONE") + return base; + + if (sub == "CF") + return base + "-CF"; + if (sub == "GF") + return base + "-GF"; + if (sub == "SILK") + return base + " SILK"; + if (sub == "SNAPSPEED" || sub == "HS") + return base + " HIGH SPEED"; + + // Unrecognized sub-type (brand names like Polylite, Basic, etc.) -- use base type only + return base; +} + +bool SnapmakerPrinterAgent::fetch_filament_info(std::string dev_id) +{ + std::string url = join_url(device_info.base_url, "/printer/objects/query?print_task_config&filament_detect"); + + std::string response_body; + bool success = false; + std::string http_error; + + auto http = Http::get(url); + if (!device_info.api_key.empty()) { + http.header("X-Api-Key", device_info.api_key); + } + http.timeout_connect(5) + .timeout_max(10) + .on_complete([&](std::string body, unsigned status) { + if (status == 200) { + response_body = body; + success = true; + } else { + http_error = "HTTP error: " + std::to_string(status); + } + }) + .on_error([&](std::string body, std::string err, unsigned status) { + http_error = err; + if (status > 0) { + http_error += " (HTTP " + std::to_string(status) + ")"; + } + }) + .perform_sync(); + + if (!success) { + BOOST_LOG_TRIVIAL(warning) << "SnapmakerPrinterAgent::fetch_filament_info: HTTP request failed: " << http_error; + return false; + } + + auto json = nlohmann::json::parse(response_body, nullptr, false, true); + if (json.is_discarded()) { + BOOST_LOG_TRIVIAL(warning) << "SnapmakerPrinterAgent::fetch_filament_info: Invalid JSON response"; + return false; + } + + // Navigate to result.status.print_task_config + if (!json.contains("result") || !json["result"].contains("status") || + !json["result"]["status"].contains("print_task_config")) { + BOOST_LOG_TRIVIAL(warning) << "SnapmakerPrinterAgent::fetch_filament_info: Missing print_task_config in response"; + return false; + } + + auto& ptc = json["result"]["status"]["print_task_config"]; + + // Read parallel arrays from print_task_config + auto filament_exist = ptc.value("filament_exist", std::vector{}); + auto filament_type = ptc.value("filament_type", std::vector{}); + auto filament_sub_type = ptc.value("filament_sub_type", std::vector{}); + auto filament_color = ptc.value("filament_color_rgba", std::vector{}); + + const int slot_count = static_cast(filament_exist.size()); + if (slot_count == 0) { + BOOST_LOG_TRIVIAL(info) << "SnapmakerPrinterAgent::fetch_filament_info: No filament slots reported"; + return false; + } + + // Read NFC filament_detect data for temperature info (optional) + nlohmann::json nfc_info; + if (json["result"]["status"].contains("filament_detect") && + json["result"]["status"]["filament_detect"].contains("info")) { + nfc_info = json["result"]["status"]["filament_detect"]["info"]; + } + + static const std::string empty_str; + static const std::string default_color = "FFFFFFFF"; + + std::vector trays; + trays.reserve(slot_count); + + for (int i = 0; i < slot_count; ++i) { + AmsTrayData tray; + tray.slot_index = i; + tray.has_filament = filament_exist[i]; + + if (tray.has_filament) { + tray.tray_type = combine_filament_type(safe_at(filament_type, i, empty_str), + safe_at(filament_sub_type, i, empty_str)); + tray.tray_info_idx = map_filament_type_to_generic_id(tray.tray_type); + tray.tray_color = safe_at(filament_color, i, default_color); + + // Extract NFC temperature data if available + if (nfc_info.is_array() && i < static_cast(nfc_info.size()) && nfc_info[i].is_object()) { + auto& nfc_slot = nfc_info[i]; + std::string vendor = nfc_slot.value("VENDOR", "NONE"); + if (vendor != "NONE" && !vendor.empty()) { + tray.bed_temp = nfc_slot.value("BED_TEMP", 0); + tray.nozzle_temp = nfc_slot.value("FIRST_LAYER_TEMP", 0); + } + } + } + + trays.emplace_back(std::move(tray)); + } + + build_ams_payload(1, slot_count - 1, trays); + return true; +} + +} // namespace Slic3r diff --git a/src/slic3r/Utils/SnapmakerPrinterAgent.hpp b/src/slic3r/Utils/SnapmakerPrinterAgent.hpp new file mode 100644 index 0000000000..04c0a66b3f --- /dev/null +++ b/src/slic3r/Utils/SnapmakerPrinterAgent.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "MoonrakerPrinterAgent.hpp" + +#include + +namespace Slic3r { + +class SnapmakerPrinterAgent final : public MoonrakerPrinterAgent +{ +public: + explicit SnapmakerPrinterAgent(std::string log_dir); + ~SnapmakerPrinterAgent() override = default; + + static AgentInfo get_agent_info_static(); + AgentInfo get_agent_info() override { return get_agent_info_static(); } + + bool fetch_filament_info(std::string dev_id) override; + +private: + // Combine filament_type + filament_sub_type into a unified type string + static std::string combine_filament_type(const std::string& type, const std::string& sub_type); +}; + +} // namespace Slic3r