mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-26 18:31:11 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adcc470cff |
@@ -1676,6 +1676,10 @@ int MachineObject::check_resume_condition()
|
||||
}
|
||||
int MachineObject::command_ams_change_filament(bool load, std::string ams_id, std::string slot_id, int old_temp, int new_temp, std::optional<int> extruder_id)
|
||||
{
|
||||
if (!orca_ams_command_supported("print.ams_change_filament")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_change_filament: connector does not advertise the command";
|
||||
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
}
|
||||
json j;
|
||||
try {
|
||||
auto tray_id = 0;
|
||||
@@ -1719,6 +1723,10 @@ int MachineObject::command_ams_change_filament(bool load, std::string ams_id, st
|
||||
|
||||
int MachineObject::command_ams_user_settings(bool start_read_opt, bool tray_read_opt, bool remain_flag)
|
||||
{
|
||||
if (!orca_ams_command_supported("print.ams_user_setting")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_user_settings: connector does not advertise the command";
|
||||
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
}
|
||||
json j;
|
||||
j["print"]["command"] = "ams_user_setting";
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
@@ -1741,8 +1749,25 @@ int MachineObject::command_ams_calibrate(int ams_id)
|
||||
return command_with_dialog(m_agent->command_ams_calibrate(get_dev_id(), ams_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
|
||||
}
|
||||
|
||||
bool MachineObject::orca_ams_command_supported(const char* command) const
|
||||
{
|
||||
if (printer_agent_id != ORCA_PRINTER_AGENT_ID)
|
||||
return true;
|
||||
// fms is the AMS axis; the advertised command set is the per-command gate.
|
||||
if (!is_support_fms)
|
||||
return false;
|
||||
return command != nullptr && supported_commands.count(command) != 0;
|
||||
}
|
||||
|
||||
int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max)
|
||||
{
|
||||
// OrcaSonar: writing slot metadata is gated on the filament_slots capability.
|
||||
// Absent/false means unsupported; Bambu keeps the legacy behaviour.
|
||||
if (printer_agent_id == ORCA_PRINTER_AGENT_ID && !is_support_filament_slots) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_filament_settings: printer does not advertise filament_slots";
|
||||
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
int tag_tray_id = 0;
|
||||
int tag_ams_id = ams_id;
|
||||
int tag_slot_id = slot_id;
|
||||
@@ -1777,6 +1802,13 @@ int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::s
|
||||
int MachineObject::command_ams_refresh_rfid(int ams_id, int slot_id)
|
||||
{
|
||||
if (!m_agent) return -1;
|
||||
// OrcaSonar: RFID read requires the connector to advertise the
|
||||
// `print.ams_get_rfid` command (which already implies fms plus the macro).
|
||||
// Unknown capability is not support; Bambu is untouched.
|
||||
if (!orca_ams_command_supported("print.ams_get_rfid")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_refresh_rfid: connector does not advertise the command";
|
||||
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
}
|
||||
return command_with_dialog(m_agent->command_ams_refresh_rfid(get_dev_id(), ams_id, slot_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
|
||||
}
|
||||
|
||||
@@ -1790,11 +1822,21 @@ int MachineObject::command_start_camera()
|
||||
int MachineObject::command_ams_select_tray(std::string tray_id)
|
||||
{
|
||||
if (!m_agent) return -1;
|
||||
// OrcaSonar: this publishes the same macro-backed print.ams_change_filament
|
||||
// as command_ams_change_filament, so it takes the same capability gate.
|
||||
if (!orca_ams_command_supported("print.ams_change_filament")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_select_tray: connector does not advertise the command";
|
||||
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
}
|
||||
return command_with_dialog(m_agent->command_ams_select_tray(get_dev_id(), tray_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
|
||||
}
|
||||
|
||||
int MachineObject::command_ams_control(std::string action)
|
||||
{
|
||||
if (!orca_ams_command_supported("print.ams_control")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_control: connector does not advertise the command";
|
||||
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
}
|
||||
if (action == "resume" && check_resume_condition()) return 0;
|
||||
|
||||
//valid actions
|
||||
@@ -1810,6 +1852,10 @@ int MachineObject::command_ams_control(std::string action)
|
||||
|
||||
int MachineObject::command_ams_drying_stop()
|
||||
{
|
||||
if (!orca_ams_command_supported("print.auto_stop_ams_dry")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_drying_stop: connector does not advertise the command";
|
||||
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
}
|
||||
json j;
|
||||
j["print"]["command"] = "auto_stop_ams_dry";
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
@@ -5490,10 +5536,14 @@ void MachineObject::parse_new_info2(const json& info)
|
||||
if (capabilities_it == info.end() || !capabilities_it->is_object())
|
||||
return;
|
||||
const auto flags_it = capabilities_it->find("flags");
|
||||
if (flags_it == capabilities_it->end() || !flags_it->is_object())
|
||||
const bool has_flags = flags_it != capabilities_it->end() && flags_it->is_object();
|
||||
// Bambu keeps the legacy behavior: a reply without flags is ignored. Orca
|
||||
// connector-scope features/commands must still be parsed, so fall through
|
||||
// with an empty flags object (every parse_bool below then no-ops).
|
||||
if (!has_flags && printer_agent_id != ORCA_PRINTER_AGENT_ID)
|
||||
return;
|
||||
|
||||
const json& flags = *flags_it;
|
||||
static const json empty_flags = json::object();
|
||||
const json& flags = has_flags ? *flags_it : empty_flags;
|
||||
BOOST_LOG_TRIVIAL(info) << "parse_new_info2: OrcaSonar capability flags=" << flags.dump();
|
||||
|
||||
auto parse_bool = [&flags](const char* name, bool& target) {
|
||||
@@ -5559,6 +5609,64 @@ void MachineObject::parse_new_info2(const json& info)
|
||||
|
||||
m_config->ParseConfig(device_config);
|
||||
m_fan->ParseV2_0(fan_config);
|
||||
|
||||
// OrcaSonar connector-scope capabilities. Absent or non-boolean means
|
||||
// unsupported. A reply without `flags` still parses them (the fall-through
|
||||
// above); Bambu (bbl) devices never enter this branch and keep the
|
||||
// flags-only path.
|
||||
if (printer_agent_id == ORCA_PRINTER_AGENT_ID) {
|
||||
auto parse_features = [this](const json& features) {
|
||||
if (!features.is_object())
|
||||
return;
|
||||
auto set_flag = [&features](const char* name, bool& target) {
|
||||
const auto it = features.find(name);
|
||||
if (it != features.end() && it->is_boolean())
|
||||
target = it->get<bool>();
|
||||
};
|
||||
set_flag("fms", is_support_fms);
|
||||
set_flag("filament_slots", is_support_filament_slots);
|
||||
set_flag("filament_mapping", is_support_filament_mapping);
|
||||
};
|
||||
|
||||
auto parse_commands = [this](const json& commands) {
|
||||
if (!commands.is_array())
|
||||
return;
|
||||
for (const auto& command : commands) {
|
||||
if (command.is_string())
|
||||
supported_commands.insert(command.get<std::string>());
|
||||
}
|
||||
};
|
||||
|
||||
// Clear before filling so a reply without commands does not retain stale values.
|
||||
supported_commands.clear();
|
||||
is_support_fms = false;
|
||||
is_support_filament_slots = false;
|
||||
is_support_filament_mapping = false;
|
||||
|
||||
const auto supported_features_it = info.find("supported_features");
|
||||
if (supported_features_it != info.end())
|
||||
parse_features(*supported_features_it);
|
||||
|
||||
const auto supported_commands_it = info.find("supported_commands");
|
||||
if (supported_commands_it != info.end())
|
||||
parse_commands(*supported_commands_it);
|
||||
|
||||
const auto protocol_it = capabilities_it->find("protocol");
|
||||
if (protocol_it != capabilities_it->end() && protocol_it->is_object()) {
|
||||
const auto protocol_features_it = protocol_it->find("features");
|
||||
if (protocol_features_it != protocol_it->end())
|
||||
parse_features(*protocol_features_it);
|
||||
|
||||
const auto protocol_commands_it = protocol_it->find("supported_commands");
|
||||
if (protocol_commands_it != protocol_it->end())
|
||||
parse_commands(*protocol_commands_it);
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "parse_new_info2: fms=" << is_support_fms
|
||||
<< " filament_slots=" << is_support_filament_slots
|
||||
<< " filament_mapping=" << is_support_filament_mapping
|
||||
<< " supported_commands=" << supported_commands.size();
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_hex_digit(char c) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
#include <optional>
|
||||
#include <boost/thread.hpp>
|
||||
@@ -646,6 +647,13 @@ public:
|
||||
bool is_support_partskip{false};
|
||||
bool is_support_refresh_nozzle{false};
|
||||
|
||||
// OrcaSonar connector-scope capabilities (printer_agent_id == "orca"). Parsed from the
|
||||
// get_capabilities reply; never consulted on Bambu paths.
|
||||
bool is_support_fms{false};
|
||||
bool is_support_filament_slots{false};
|
||||
bool is_support_filament_mapping{false};
|
||||
std::set<std::string> supported_commands;
|
||||
|
||||
// refine printer function options
|
||||
bool is_support_spaghetti_detection{false};
|
||||
bool is_support_purgechutepileup_detection{false};
|
||||
@@ -787,6 +795,10 @@ public:
|
||||
int command_refresh_nozzle();
|
||||
int command_set_chamber(int temp);
|
||||
int check_resume_condition();
|
||||
// OrcaSonar: true when a macro-backed AMS command may be sent. Bambu paths
|
||||
// always allow; an Orca device must have advertised the command (unknown
|
||||
// capability is not support).
|
||||
bool orca_ams_command_supported(const char* command) const;
|
||||
// ams controls
|
||||
//int command_ams_switch(int tray_index, int old_temp = 210, int new_temp = 210);
|
||||
int command_ams_change_filament(bool load, std::string ams_id, std::string slot_id, int old_temp = 210, int new_temp = 210, std::optional<int> extruder_id = std::nullopt);
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "libslic3r/ProjectTask.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
// True when the normalized ams_mapping2 carries any entry the agent's serializer
|
||||
// would put on the wire: every integer pair except the {255,255} unmatched
|
||||
// sentinel, external slots ({255,0}/{254,0}) included. This mirrors
|
||||
// OrcaPrinterAgent::build_filament_mapping exactly, so the GUI gate and the
|
||||
// agent gate agree; a mismatch lets an entry reach the agent and be refused late
|
||||
// with a generic publish error instead of the designed message.
|
||||
inline bool has_engaged_filament_mapping(const std::string& ams_mapping2)
|
||||
{
|
||||
const nlohmann::json mapping = nlohmann::json::parse(ams_mapping2, nullptr, false);
|
||||
if (mapping.is_discarded() || !mapping.is_array())
|
||||
return false;
|
||||
for (const auto& entry : mapping) {
|
||||
if (!entry.is_object())
|
||||
continue;
|
||||
const auto ams_id_it = entry.find("ams_id");
|
||||
const auto slot_id_it = entry.find("slot_id");
|
||||
if (ams_id_it == entry.end() || slot_id_it == entry.end())
|
||||
continue;
|
||||
if (!ams_id_it->is_number_integer() || !slot_id_it->is_number_integer())
|
||||
continue;
|
||||
if (ams_id_it->get<int>() == 255 && slot_id_it->get<int>() == 255)
|
||||
continue; // unmatched/unused sentinel: the serializer drops it
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// A used filament with no target would be silently dropped from the wire
|
||||
// mapping, so the print must be refused rather than run the wrong material.
|
||||
// m_ams_mapping_result carries exactly the filaments the slice uses.
|
||||
inline bool has_used_filament_without_target(const std::vector<FilamentInfo>& result)
|
||||
{
|
||||
for (const auto& f : result) {
|
||||
if (f.get_ams_id() < 0 || f.get_slot_id() < 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// True when at least one used filament already has a target. The used-unmapped
|
||||
// refusal applies to a partially mapped print; a print with no mapping at all
|
||||
// is handled by the existing all-invalid send-path flow.
|
||||
inline bool has_any_mapped_target(const std::vector<FilamentInfo>& result)
|
||||
{
|
||||
for (const auto& f : result) {
|
||||
if (f.get_ams_id() >= 0 && f.get_slot_id() >= 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
@@ -37,6 +37,8 @@
|
||||
#include "libslic3r/MultiNozzleUtils.hpp" // filament-change-gap model for the best-position popup
|
||||
#include "BackgroundSlicingProcess.hpp" // complete type for background_process().get_current_gcode_result()
|
||||
#include "DeviceCore/DevStorage.h"
|
||||
#include "slic3r/Utils/NetworkAgentFactory.hpp"
|
||||
#include "FilamentMappingUtils.hpp"
|
||||
|
||||
#include <wx/progdlg.h>
|
||||
#include <wx/clipbrd.h>
|
||||
@@ -3478,6 +3480,9 @@ void SelectMachineDialog::navigate_to_timelapse_page()
|
||||
this->EndModal(wxID_CANCEL);
|
||||
}
|
||||
|
||||
// Mapping helpers live in FilamentMappingUtils.hpp (shared with
|
||||
// SendMultiMachinePage); they mirror the agent serializer exactly.
|
||||
|
||||
void SelectMachineDialog::on_send_print()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "print_job: on_ok to send";
|
||||
@@ -3532,6 +3537,26 @@ void SelectMachineDialog::on_send_print()
|
||||
|
||||
get_ams_mapping_result(ams_mapping_array,ams_mapping_array2, ams_mapping_info);
|
||||
|
||||
// OrcaSonar: a mapped print requires the connector to advertise
|
||||
// filament_mapping and the index correlation to be verified. Refuse rather
|
||||
// than start with the map silently dropped; Bambu keeps its behavior.
|
||||
if (obj_->printer_agent_id == ORCA_PRINTER_AGENT_ID) {
|
||||
const bool mapping_available = obj_->is_support_filament_mapping && ORCA_FILAMENT_MAPPING_CORRELATION_VERIFIED;
|
||||
if (!mapping_available && has_engaged_filament_mapping(ams_mapping_array2)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "print_job: filament mapping unavailable (capability=" << obj_->is_support_filament_mapping
|
||||
<< ", correlation_verified=" << ORCA_FILAMENT_MAPPING_CORRELATION_VERIFIED << "); refusing mapped print";
|
||||
m_status_bar->set_status_text(_L("AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."));
|
||||
Enable_Send_Button(true);
|
||||
return;
|
||||
}
|
||||
if (has_any_mapped_target(m_ams_mapping_result) && has_used_filament_without_target(m_ams_mapping_result)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "print_job: a used filament has no AMS target; refusing print";
|
||||
m_status_bar->set_status_text(_L("A filament used by this print has no AMS mapping. Assign it before printing."));
|
||||
Enable_Send_Button(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_print_type == PrintFromType::FROM_NORMAL) {
|
||||
result = m_plater->send_gcode(m_print_plate_idx, [this](int export_stage, int current, int total, bool& cancel) {
|
||||
if (this->m_is_canceled) return;
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
#include "DeviceCore/DevManager.h"
|
||||
#include "DeviceCore/DevStorage.h"
|
||||
#include "slic3r/Utils/NetworkAgentFactory.hpp"
|
||||
#include "FilamentMappingUtils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
@@ -692,6 +694,8 @@ bool SendMultiMachinePage::get_ams_mapping_result(std::string &mapping_array_str
|
||||
return true;
|
||||
}
|
||||
|
||||
// Mapping helpers live in FilamentMappingUtils.hpp, shared with SelectMachine.
|
||||
|
||||
void SendMultiMachinePage::on_send(wxCommandEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
@@ -740,6 +744,29 @@ void SendMultiMachinePage::on_send(wxCommandEvent& event)
|
||||
|
||||
if (!wxGetApp().is_blocking_printing(obj)) {
|
||||
PrintParams params = request_params(obj);
|
||||
// OrcaSonar: a mapped print requires the connector to advertise
|
||||
// filament_mapping, and a partially mapped print must not silently drop a
|
||||
// used filament. Any entry the serializer would put on the wire engages the
|
||||
// gate, external slots ({255,0}/{254,0}) included; the extra-spool branch
|
||||
// rewrites to exactly those. Bambu is unchanged.
|
||||
if (obj->printer_agent_id == ORCA_PRINTER_AGENT_ID) {
|
||||
const bool mapping_available = obj->is_support_filament_mapping && ORCA_FILAMENT_MAPPING_CORRELATION_VERIFIED;
|
||||
if (!mapping_available && has_engaged_filament_mapping(params.ams_mapping2)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "SendMultiMachinePage: filament mapping unavailable (capability=" << obj->is_support_filament_mapping
|
||||
<< ", correlation_verified=" << ORCA_FILAMENT_MAPPING_CORRELATION_VERIFIED << "); refusing mapped print for "
|
||||
<< obj->get_dev_id();
|
||||
MessageDialog msg_wingow(nullptr, _L("AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."), "", wxICON_WARNING | wxOK);
|
||||
msg_wingow.ShowModal();
|
||||
return;
|
||||
}
|
||||
if (params.task_use_ams && has_any_mapped_target(m_ams_mapping_result) &&
|
||||
has_used_filament_without_target(m_ams_mapping_result)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "SendMultiMachinePage: a used filament has no target; refusing print for " << obj->get_dev_id();
|
||||
MessageDialog msg_wingow(nullptr, _L("A filament used by this print has no AMS mapping. Assign it before printing."), "", wxICON_WARNING | wxOK);
|
||||
msg_wingow.ShowModal();
|
||||
return;
|
||||
}
|
||||
}
|
||||
print_params.push_back(params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1373,6 +1373,12 @@ bool SyncAmsInfoDialog::get_ams_mapping_result(std::string &mapping_array_str, s
|
||||
BOOST_LOG_TRIVIAL(error) << "get_ams_mapping_result, plater is nullptr";
|
||||
}
|
||||
|
||||
// mapping_v1_json is built one entry per filament preset, in preset order, so the
|
||||
// array position is the logical filament index the generated G-code toolchange
|
||||
// references (the identifier handed to the Klipper toolchange macro; see
|
||||
// OrcaPrinterAgent::build_filament_mapping and the index-correlation test in
|
||||
// tests/slic3rutils/test_orca_printer_agent.cpp). Never re-densify after dropping
|
||||
// sentinel entries, or a used filament would be aimed at the wrong lane.
|
||||
for (int i = 0; i < wxGetApp().preset_bundle->filament_presets.size(); i++) {
|
||||
int tray_id = -1;
|
||||
json mapping_item_v1;
|
||||
|
||||
@@ -17,6 +17,14 @@ namespace Slic3r {
|
||||
static constexpr char ORCA_PRINTER_AGENT_ID[] = "orca";
|
||||
static constexpr char BBL_PRINTER_AGENT_ID[] = "bbl";
|
||||
|
||||
// Index-correlation merge gate (plan PR 3). The per-print `filament_mapping`
|
||||
// serializer is shipped disabled until a slice-level test proves that the
|
||||
// `ams_mapping2` array position equals the toolchange identifier the generated
|
||||
// G-code emits (T<filament_id> / next_filament_id). Both the GUI send gates and
|
||||
// OrcaPrinterAgent::start_sdcard_print consult this, so mapping is refused
|
||||
// visibly rather than silently dropped. Flip to true only with that test.
|
||||
static constexpr bool ORCA_FILAMENT_MAPPING_CORRELATION_VERIFIED = false;
|
||||
|
||||
// Factory function type for creating printer agents
|
||||
using PrinterAgentFactory =
|
||||
std::function<std::shared_ptr<IPrinterAgent>(std::shared_ptr<ICloudServiceAgent> cloud_agent, const std::string& log_dir)>;
|
||||
|
||||
@@ -43,6 +43,23 @@ namespace {
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
// Per-device filament_mapping capability, mirrored from the get_capabilities
|
||||
// reply in merge_capabilities and read by start_sdcard_print so the field is
|
||||
// refused defensively when the connector never advertised it or the
|
||||
// index-correlation merge gate (ORCA_FILAMENT_MAPPING_CORRELATION_VERIFIED) is
|
||||
// not satisfied.
|
||||
std::mutex g_filament_mapping_mutex;
|
||||
std::unordered_map<std::string, bool> g_filament_mapping_cache;
|
||||
|
||||
// True only when the connector's get_capabilities reply advertised
|
||||
// filament_mapping for this device. Unknown is not support.
|
||||
bool filament_mapping_advertised(const std::string& dev_id)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(g_filament_mapping_mutex);
|
||||
const auto it = g_filament_mapping_cache.find(dev_id);
|
||||
return it != g_filament_mapping_cache.end() && it->second;
|
||||
}
|
||||
|
||||
// params.filename is normally the exported .3mf archive; the sliced G-code sits
|
||||
// beside it with the same stem (".12345.0.3mf" -> ".12345.0.gcode"). params.dst_file,
|
||||
// when set, already points straight at a file (the "print a file already on the
|
||||
@@ -588,6 +605,33 @@ std::string OrcaPrinterAgent::merge_capabilities(const std::string& dev_id, cons
|
||||
std::lock_guard<std::mutex> l(nozzle_diameter_cache_mutex);
|
||||
nozzle_diameter_cache[dev_id] = nozzle_dia;
|
||||
}
|
||||
|
||||
// Per-device filament_mapping capability. Both feature maps carry it;
|
||||
// either being true means the connector advertised it.
|
||||
bool mapping_advertised = false;
|
||||
{
|
||||
const auto top_features = info_it->find("supported_features");
|
||||
if (top_features != info_it->end() && top_features->is_object()) {
|
||||
const auto it = top_features->find("filament_mapping");
|
||||
if (it != top_features->end() && it->is_boolean())
|
||||
mapping_advertised = it->get<bool>();
|
||||
}
|
||||
if (!mapping_advertised && caps_it != info_it->end() && caps_it->is_object()) {
|
||||
const auto protocol_it = caps_it->find("protocol");
|
||||
if (protocol_it != caps_it->end() && protocol_it->is_object()) {
|
||||
const auto features_it = protocol_it->find("features");
|
||||
if (features_it != protocol_it->end() && features_it->is_object()) {
|
||||
const auto it = features_it->find("filament_mapping");
|
||||
if (it != features_it->end() && it->is_boolean())
|
||||
mapping_advertised = it->get<bool>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> l(g_filament_mapping_mutex);
|
||||
g_filament_mapping_cache[dev_id] = mapping_advertised;
|
||||
}
|
||||
// The capabilities reply itself is forwarded unchanged.
|
||||
}
|
||||
else {
|
||||
@@ -1415,11 +1459,17 @@ int OrcaPrinterAgent::start_print(PrintParams params, OnUpdateStatusFn update_fn
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::start_local_print_with_record(PrintParams params,
|
||||
OnUpdateStatusFn update_fn,
|
||||
WasCancelledFn cancel_fn,
|
||||
OnWaitFn wait_fn)
|
||||
{ return BAMBU_NETWORK_SUCCESS; }
|
||||
int OrcaPrinterAgent::start_local_print_with_record(PrintParams /*params*/,
|
||||
OnUpdateStatusFn /*update_fn*/,
|
||||
WasCancelledFn /*cancel_fn*/,
|
||||
OnWaitFn /*wait_fn*/)
|
||||
{
|
||||
// OrcaSonar has no FTP "send with record" path. Report a non-success result so
|
||||
// PrintJob falls back to start_print() (cloud upload + start_sdcard_print) instead
|
||||
// of treating a print that was never sent as successful.
|
||||
BOOST_LOG_TRIVIAL(warning) << "OrcaPrinterAgent: start_local_print_with_record is unimplemented; deferring to start_print";
|
||||
return BAMBU_NETWORK_ERR_FTP_UPLOAD_FAILED;
|
||||
}
|
||||
|
||||
// Upload one G-code file to the printer's `gcodes` root over OrcaSonar's
|
||||
// Moonraker-compatible HTTP facade. No print is started here (print=false); the
|
||||
@@ -1587,6 +1637,59 @@ int OrcaPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusFn upd
|
||||
return start_sdcard_print(params, update_fn, cancel_fn);
|
||||
}
|
||||
|
||||
// Serialize PrintParams::ams_mapping2 (the dialog's mapping_v1_json, one entry per logical
|
||||
// filament in preset order) into the print.gcode_file `filament_mapping` array. The array
|
||||
// position becomes `filament_index`; {255,255} (unmatched/unused) is dropped. Returns an
|
||||
// empty array when nothing usable remains so the caller can omit the field entirely.
|
||||
nlohmann::json OrcaPrinterAgent::build_filament_mapping(const std::string& ams_mapping2)
|
||||
{
|
||||
nlohmann::json mapping = nlohmann::json::array();
|
||||
if (ams_mapping2.empty())
|
||||
return mapping;
|
||||
|
||||
const nlohmann::json parsed = nlohmann::json::parse(ams_mapping2, nullptr, false);
|
||||
if (parsed.is_discarded() || !parsed.is_array()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "OrcaPrinterAgent: ams_mapping2 is not a JSON array; not sending filament_mapping";
|
||||
return mapping;
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < parsed.size(); ++i) {
|
||||
const nlohmann::json& entry = parsed[i];
|
||||
if (!entry.is_object())
|
||||
continue;
|
||||
const auto ams_id_it = entry.find("ams_id");
|
||||
const auto slot_id_it = entry.find("slot_id");
|
||||
if (ams_id_it == entry.end() || slot_id_it == entry.end())
|
||||
continue;
|
||||
if (!ams_id_it->is_number_integer() || !slot_id_it->is_number_integer())
|
||||
continue;
|
||||
|
||||
const int ams_id = ams_id_it->get<int>();
|
||||
const int slot_id = slot_id_it->get<int>();
|
||||
if (ams_id == 255 && slot_id == 255)
|
||||
continue;
|
||||
|
||||
mapping.push_back({{"filament_index", static_cast<int>(i)}, {"ams_id", ams_id}, {"slot_id", slot_id}});
|
||||
}
|
||||
return mapping;
|
||||
}
|
||||
|
||||
// Pure builder for the print.gcode_file payload: the base command plus, only
|
||||
// when non-empty, the filament_mapping array. An empty mapping leaves the
|
||||
// payload byte-identical to today's unmapped command.
|
||||
nlohmann::json OrcaPrinterAgent::build_gcode_file_payload(const std::string& sequence_id,
|
||||
const std::string& target,
|
||||
const nlohmann::json& filament_mapping)
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["print"]["command"] = "gcode_file";
|
||||
j["print"]["sequence_id"] = sequence_id;
|
||||
j["print"]["param"] = target;
|
||||
if (filament_mapping.is_array() && !filament_mapping.empty())
|
||||
j["print"]["filament_mapping"] = filament_mapping;
|
||||
return j;
|
||||
}
|
||||
|
||||
// Start a file that already lives on the printer by publishing the canonical
|
||||
// OPCP print.gcode_file command to device/<dev_id>/request. The acknowledgement
|
||||
// and lifecycle progress arrive asynchronously as print.push_status on the
|
||||
@@ -1604,10 +1707,23 @@ int OrcaPrinterAgent::start_sdcard_print(PrintParams params, OnUpdateStatusFn up
|
||||
// otherwise start what start_send_gcode_to_sdcard just uploaded to `gcodes`.
|
||||
const std::string target = params.dst_file.empty() ? remote_gcode_name(params) : fs::path(params.dst_file).filename().string();
|
||||
|
||||
nlohmann::json j;
|
||||
j["print"]["command"] = "gcode_file";
|
||||
j["print"]["sequence_id"] = next_gcode_file_sequence_id();
|
||||
j["print"]["param"] = target;
|
||||
// Per-print mapping. A mapped print is refused when the connector did not
|
||||
// advertise filament_mapping or the index correlation is unverified: the GUI
|
||||
// send gates make this visible first, and this is the defensive gate for
|
||||
// callers that bypass them (calibration, plugin). Never start a mapped print
|
||||
// with the map silently dropped.
|
||||
const nlohmann::json filament_mapping = build_filament_mapping(params.ams_mapping2);
|
||||
if (!filament_mapping.empty()) {
|
||||
const bool mapping_capable = filament_mapping_advertised(params.dev_id);
|
||||
if (!mapping_capable || !ORCA_FILAMENT_MAPPING_CORRELATION_VERIFIED) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "OrcaPrinterAgent: refusing mapped print (capable=" << mapping_capable
|
||||
<< ", correlation_verified=" << ORCA_FILAMENT_MAPPING_CORRELATION_VERIFIED << ")";
|
||||
return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent: start_sdcard_print emitting filament_mapping entries=" << filament_mapping.size();
|
||||
}
|
||||
|
||||
nlohmann::json j = build_gcode_file_payload(next_gcode_file_sequence_id(), target, filament_mapping);
|
||||
|
||||
if (update_fn)
|
||||
update_fn(PrintingStageSending, 0, "Starting print...");
|
||||
|
||||
@@ -64,6 +64,8 @@ public:
|
||||
|
||||
// Print Job Operations
|
||||
int start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override;
|
||||
// Unimplemented on OrcaSonar: reports a non-success result so callers fall back to
|
||||
// start_print() instead of treating the missing send as success.
|
||||
int start_local_print_with_record(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override;
|
||||
int start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override;
|
||||
int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override;
|
||||
@@ -140,6 +142,18 @@ protected:
|
||||
// Pure LAN-address parsing + client-id. protected static so the test Probe reaches them.
|
||||
static bool parse_lan_endpoint(const std::string& dev_ip, std::string& host, std::string& port);
|
||||
static std::string make_lan_client_id(const std::string& dev_id);
|
||||
|
||||
// Pure serializer for PrintParams::ams_mapping2 -> print.gcode_file.filament_mapping.
|
||||
// Entries are re-keyed by their array position; {255,255} is dropped. Empty when the
|
||||
// input is empty, malformed, or has no usable entries. protected static for the test Probe.
|
||||
static nlohmann::json build_filament_mapping(const std::string& ams_mapping2);
|
||||
|
||||
// Pure builder for the print.gcode_file command payload. A non-empty mapping is
|
||||
// included as filament_mapping; an empty one is omitted so the payload is
|
||||
// byte-identical to an unmapped print. protected static for the test Probe.
|
||||
static nlohmann::json build_gcode_file_payload(const std::string& sequence_id,
|
||||
const std::string& target,
|
||||
const nlohmann::json& filament_mapping);
|
||||
// Test hook: the ws:// URL connect_printer built for the current LAN session ("" if none).
|
||||
std::string lan_connection_target() const;
|
||||
// Shared post-connect sequence: SUBSCRIBE, then pushing.start, pushall,
|
||||
|
||||
@@ -157,3 +157,119 @@ TEST_CASE("Device manager filters and rehomes devices by printer-agent ownership
|
||||
CHECK(manager.get_my_machine_list("integration-agent-a").empty());
|
||||
CHECK(manager.get_my_machine_list("integration-agent-b").count(machine.dev_id) == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Orca capability reply populates connector-scope features and commands", "[DeviceManager][integration]")
|
||||
{
|
||||
ScopedAppConfig app_config;
|
||||
NetworkAgent network(nullptr, std::make_shared<TestPrinterAgent>("orca"));
|
||||
DeviceManager manager(&network, false, &app_config.config);
|
||||
|
||||
BBLocalMachine orca_machine;
|
||||
orca_machine.dev_id = "orca-device";
|
||||
orca_machine.dev_name = "Orca device";
|
||||
orca_machine.dev_ip = "192.0.2.21";
|
||||
orca_machine.printer_type = "C11";
|
||||
MachineObject* orca_obj = manager.insert_local_device(orca_machine, "lan", "free", "", "access-code");
|
||||
REQUIRE(orca_obj != nullptr);
|
||||
orca_obj->printer_agent_id = "orca";
|
||||
|
||||
orca_obj->parse_new_info2(json::parse(R"({
|
||||
"command": "get_capabilities",
|
||||
"supported_features": {"fms": true, "filament_slots": true, "filament_mapping": true},
|
||||
"supported_commands": ["print.push_status", "print.ams_get_rfid"],
|
||||
"capabilities": {
|
||||
"flags": {},
|
||||
"protocol": {
|
||||
"features": {"filament_mapping": true},
|
||||
"supported_commands": ["print.ams_change_filament"]
|
||||
}
|
||||
}
|
||||
})"));
|
||||
CHECK(orca_obj->is_support_fms);
|
||||
CHECK(orca_obj->is_support_filament_slots);
|
||||
CHECK(orca_obj->is_support_filament_mapping);
|
||||
CHECK(orca_obj->supported_commands.count("print.push_status") == 1);
|
||||
CHECK(orca_obj->supported_commands.count("print.ams_get_rfid") == 1);
|
||||
CHECK(orca_obj->supported_commands.count("print.ams_change_filament") == 1);
|
||||
|
||||
// Absent or false reads as unsupported, and a later reply without commands clears the set.
|
||||
orca_obj->parse_new_info2(json::parse(R"({
|
||||
"command": "get_capabilities",
|
||||
"supported_features": {"fms": false, "filament_slots": false, "filament_mapping": false},
|
||||
"capabilities": {"flags": {}}
|
||||
})"));
|
||||
CHECK_FALSE(orca_obj->is_support_fms);
|
||||
CHECK_FALSE(orca_obj->is_support_filament_slots);
|
||||
CHECK_FALSE(orca_obj->is_support_filament_mapping);
|
||||
CHECK(orca_obj->supported_commands.empty());
|
||||
|
||||
// A reply without capabilities.flags must still parse the Orca features and
|
||||
// commands (fail-closed: don't retain stale "supported" values).
|
||||
orca_obj->parse_new_info2(json::parse(R"({
|
||||
"command": "get_capabilities",
|
||||
"supported_features": {"filament_mapping": true},
|
||||
"capabilities": {
|
||||
"protocol": {"supported_commands": ["print.ams_get_rfid"]}
|
||||
}
|
||||
})"));
|
||||
CHECK(orca_obj->is_support_filament_mapping);
|
||||
CHECK_FALSE(orca_obj->is_support_filament_slots);
|
||||
CHECK(orca_obj->supported_commands.count("print.ams_get_rfid") == 1);
|
||||
|
||||
// A non-Orca agent id leaves the connector-scope fields untouched.
|
||||
BBLocalMachine bbl_machine;
|
||||
bbl_machine.dev_id = "bbl-device";
|
||||
bbl_machine.dev_name = "Bambu device";
|
||||
bbl_machine.dev_ip = "192.0.2.22";
|
||||
bbl_machine.printer_type = "C11";
|
||||
MachineObject* bbl_obj = manager.insert_local_device(bbl_machine, "lan", "free", "", "access-code");
|
||||
REQUIRE(bbl_obj != nullptr);
|
||||
bbl_obj->printer_agent_id = "bbl";
|
||||
|
||||
bbl_obj->parse_new_info2(json::parse(R"({
|
||||
"command": "get_capabilities",
|
||||
"supported_features": {"fms": true, "filament_slots": true, "filament_mapping": true},
|
||||
"supported_commands": ["print.push_status"],
|
||||
"capabilities": {
|
||||
"flags": {},
|
||||
"protocol": {
|
||||
"features": {"fms": true, "filament_slots": true, "filament_mapping": true},
|
||||
"supported_commands": ["print.ams_get_rfid"]
|
||||
}
|
||||
}
|
||||
})"));
|
||||
CHECK_FALSE(bbl_obj->is_support_fms);
|
||||
CHECK_FALSE(bbl_obj->is_support_filament_slots);
|
||||
CHECK_FALSE(bbl_obj->is_support_filament_mapping);
|
||||
CHECK(bbl_obj->supported_commands.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Orca per-command AMS gate requires fms and the advertised command", "[DeviceManager][integration]")
|
||||
{
|
||||
ScopedAppConfig app_config;
|
||||
NetworkAgent network(nullptr, std::make_shared<TestPrinterAgent>("orca"));
|
||||
DeviceManager manager(&network, false, &app_config.config);
|
||||
|
||||
BBLocalMachine machine;
|
||||
machine.dev_id = "orca-gate";
|
||||
machine.dev_name = "Orca gate";
|
||||
machine.dev_ip = "192.0.2.30";
|
||||
machine.printer_type = "C11";
|
||||
MachineObject* obj = manager.insert_local_device(machine, "lan", "free", "", "access-code");
|
||||
REQUIRE(obj != nullptr);
|
||||
obj->printer_agent_id = "orca";
|
||||
|
||||
// fms off: no macro-backed AMS command is allowed even if listed.
|
||||
obj->is_support_fms = false;
|
||||
obj->supported_commands.insert("print.ams_control");
|
||||
CHECK_FALSE(obj->orca_ams_command_supported("print.ams_control"));
|
||||
|
||||
// fms on: only the commands actually advertised are allowed.
|
||||
obj->is_support_fms = true;
|
||||
CHECK(obj->orca_ams_command_supported("print.ams_control"));
|
||||
CHECK_FALSE(obj->orca_ams_command_supported("print.ams_get_rfid"));
|
||||
|
||||
// Bambu keeps the legacy permissive path.
|
||||
obj->printer_agent_id = "bbl";
|
||||
CHECK(obj->orca_ams_command_supported("print.anything"));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <slic3r/GUI/FilamentMappingUtils.hpp>
|
||||
#include <slic3r/Utils/IPrinterAgent.hpp>
|
||||
#include <slic3r/Utils/OrcaCloudServiceAgent.hpp>
|
||||
#include <slic3r/Utils/OrcaPrinterAgent.hpp>
|
||||
@@ -22,6 +23,8 @@ struct Probe : OrcaPrinterAgent {
|
||||
using OrcaPrinterAgent::parse_lan_endpoint;
|
||||
using OrcaPrinterAgent::make_lan_client_id;
|
||||
using OrcaPrinterAgent::lan_connection_target;
|
||||
using OrcaPrinterAgent::build_filament_mapping;
|
||||
using OrcaPrinterAgent::build_gcode_file_payload;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -84,6 +87,101 @@ TEST_CASE("OrcaPrinterAgent::make_lan_client_id is stable and prefixed", "[OrcaP
|
||||
CHECK(a.rfind("orcaslicer-lan-dev-1-", 0) == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("filament mapping is keyed by the ams_mapping2 array position", "[OrcaPrinterAgent]") {
|
||||
const nlohmann::json mapping = Probe::build_filament_mapping(
|
||||
R"([{"ams_id":1,"slot_id":5},{"ams_id":255,"slot_id":255},{"ams_id":255,"slot_id":0}])");
|
||||
REQUIRE(mapping.is_array());
|
||||
REQUIRE(mapping.size() == 2);
|
||||
CHECK(mapping[0]["filament_index"] == 0);
|
||||
CHECK(mapping[0]["ams_id"] == 1);
|
||||
CHECK(mapping[0]["slot_id"] == 5);
|
||||
// The unmatched middle entry is dropped; the third entry keeps index 2.
|
||||
CHECK(mapping[1]["filament_index"] == 2);
|
||||
CHECK(mapping[1]["ams_id"] == 255);
|
||||
CHECK(mapping[1]["slot_id"] == 0);
|
||||
}
|
||||
|
||||
// Index-correlation merge gate (plan PR 3). `ams_mapping2` is built one entry
|
||||
// per logical filament, so its array position is the identifier the generated
|
||||
// G-code toolchange passes to the Klipper macro (`next_filament_id`). The
|
||||
// serializer must key `filament_index` by that position and must never
|
||||
// re-densify after dropping unused/sentinel entries, or a used filament would
|
||||
// be aimed at the wrong lane. This test covers the plan's matrix: preset order
|
||||
// differing from used order, a middle filament unused, and external slots.
|
||||
TEST_CASE("filament mapping index correlates with the ams_mapping2 position", "[OrcaPrinterAgent]") {
|
||||
// Positions 0..4. Used filaments are 0, 2 and 4; 1 and 3 are unused.
|
||||
const nlohmann::json mapping = Probe::build_filament_mapping(
|
||||
R"([{"ams_id":0,"slot_id":1},{"ams_id":255,"slot_id":255},{"ams_id":2,"slot_id":3},{"ams_id":255,"slot_id":255},{"ams_id":255,"slot_id":0}])");
|
||||
REQUIRE(mapping.size() == 3);
|
||||
CHECK(mapping[0]["filament_index"] == 0);
|
||||
CHECK(mapping[1]["filament_index"] == 2);
|
||||
CHECK(mapping[2]["filament_index"] == 4); // external slot keeps its position
|
||||
CHECK(mapping[2]["ams_id"] == 255);
|
||||
CHECK(mapping[2]["slot_id"] == 0);
|
||||
// No re-densification: a used filament after a dropped sentinel keeps its
|
||||
// original logical index.
|
||||
for (const auto& entry : mapping)
|
||||
CHECK(entry.contains("filament_index"));
|
||||
}
|
||||
|
||||
TEST_CASE("filament mapping is omitted when nothing remains", "[OrcaPrinterAgent]") {
|
||||
CHECK(Probe::build_filament_mapping("").empty());
|
||||
CHECK(Probe::build_filament_mapping(R"([{"ams_id":255,"slot_id":255}])").empty());
|
||||
CHECK(Probe::build_filament_mapping("not json").empty());
|
||||
CHECK(Probe::build_filament_mapping(R"({"ams_id":1,"slot_id":0})").empty()); // not an array
|
||||
}
|
||||
|
||||
// The GUI capability gate and the agent serializer must classify the same
|
||||
// entries as engaged. External slots ({255,0}/{254,0}) are normalized as-is, so
|
||||
// they engage; only the {255,255} unmatched sentinel is dropped. A mismatch lets
|
||||
// an entry past the GUI and refused late with a generic publish error.
|
||||
TEST_CASE("the GUI mapping gate engages exactly the entries the serializer sends", "[OrcaPrinterAgent]") {
|
||||
using Slic3r::GUI::has_engaged_filament_mapping;
|
||||
CHECK_FALSE(has_engaged_filament_mapping(""));
|
||||
CHECK_FALSE(has_engaged_filament_mapping("[]"));
|
||||
CHECK_FALSE(has_engaged_filament_mapping("not json"));
|
||||
CHECK_FALSE(has_engaged_filament_mapping(R"([{"ams_id":255,"slot_id":255}])"));
|
||||
CHECK(has_engaged_filament_mapping(R"([{"ams_id":255,"slot_id":0}])")); // external main
|
||||
CHECK(has_engaged_filament_mapping(R"([{"ams_id":254,"slot_id":0}])")); // external deputy
|
||||
CHECK(has_engaged_filament_mapping(R"([{"ams_id":0,"slot_id":0}])")); // box slot
|
||||
CHECK(has_engaged_filament_mapping(R"([{"ams_id":255,"slot_id":255},{"ams_id":1,"slot_id":2}])"));
|
||||
|
||||
for (const char* s : {"", "[]", "not json", R"([{"ams_id":255,"slot_id":255}])",
|
||||
R"([{"ams_id":255,"slot_id":0}])", R"([{"ams_id":254,"slot_id":0}])",
|
||||
R"([{"ams_id":0,"slot_id":0}])",
|
||||
R"([{"ams_id":255,"slot_id":255},{"ams_id":1,"slot_id":2}])"}) {
|
||||
CHECK(has_engaged_filament_mapping(s) == !Probe::build_filament_mapping(s).empty());
|
||||
}
|
||||
}
|
||||
|
||||
// An empty mapping must leave the gcode_file payload byte-identical to today:
|
||||
// exactly command, sequence_id and param, with no filament_mapping key.
|
||||
TEST_CASE("gcode_file payload omits filament_mapping when the map is empty", "[OrcaPrinterAgent]") {
|
||||
const nlohmann::json empty = Probe::build_gcode_file_payload("7", "job.gcode", nlohmann::json::array());
|
||||
REQUIRE(empty.contains("print"));
|
||||
CHECK(empty["print"].size() == 3);
|
||||
CHECK(empty["print"]["command"] == "gcode_file");
|
||||
CHECK(empty["print"]["sequence_id"] == "7");
|
||||
CHECK(empty["print"]["param"] == "job.gcode");
|
||||
CHECK_FALSE(empty["print"].contains("filament_mapping"));
|
||||
|
||||
const nlohmann::json mapping = Probe::build_filament_mapping(R"([{"ams_id":1,"slot_id":0},{"ams_id":255,"slot_id":255}])");
|
||||
const nlohmann::json with = Probe::build_gcode_file_payload("8", "job.gcode", mapping);
|
||||
REQUIRE(with["print"].contains("filament_mapping"));
|
||||
REQUIRE(with["print"]["filament_mapping"].size() == 1);
|
||||
CHECK(with["print"]["filament_mapping"][0]["filament_index"] == 0);
|
||||
}
|
||||
|
||||
// The FTP "send with record" transport does not exist on OrcaSonar. It must
|
||||
// report a non-success result so PrintJob falls back to start_print() rather
|
||||
// than treating a print that was never sent as successful.
|
||||
TEST_CASE("start_local_print_with_record never reports silent success", "[OrcaPrinterAgent]") {
|
||||
Probe agent("/tmp");
|
||||
Slic3r::PrintParams params;
|
||||
const int rc = agent.start_local_print_with_record(params, {}, {}, {});
|
||||
CHECK(rc < 0);
|
||||
}
|
||||
|
||||
TEST_CASE("connect_printer wires up a LAN Config", "[OrcaPrinterAgent][.integration]") {
|
||||
Probe agent("/tmp");
|
||||
Slic3r::PrinterConnectionParams params{
|
||||
|
||||
Reference in New Issue
Block a user