mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-28 19:31:22 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd593317fd | ||
|
|
ec0d8c225f | ||
|
|
3040ebaac1 | ||
|
|
d347a80ef8 | ||
|
|
e340a13c18 | ||
|
|
9e16cdb23b | ||
|
|
58842bab05 | ||
|
|
04204ec0d3 | ||
|
|
9ed459e132 |
+10
-5
@@ -2504,7 +2504,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
|||||||
|
|
||||||
{
|
{
|
||||||
LifecycleEventContext ctx;
|
LifecycleEventContext ctx;
|
||||||
ctx.name = std::to_string(print->model().id().id);
|
ctx.id = std::to_string(print->model().id().id);
|
||||||
|
ctx.name = print->get_model_name();
|
||||||
ctx.code = LifecycleEvtCode::Ok;
|
ctx.code = LifecycleEvtCode::Ok;
|
||||||
ctx.msg = path;
|
ctx.msg = path;
|
||||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||||
@@ -2556,7 +2557,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
LifecycleEventContext ctx;
|
LifecycleEventContext ctx;
|
||||||
ctx.name = std::to_string(print->model().id().id);
|
ctx.id = std::to_string(print->model().id().id);
|
||||||
|
ctx.name = print->get_model_name();
|
||||||
ctx.code = LifecycleEvtCode::Error;
|
ctx.code = LifecycleEvtCode::Error;
|
||||||
ctx.msg = std::string(path) + "\n" + err_msg;
|
ctx.msg = std::string(path) + "\n" + err_msg;
|
||||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||||
@@ -2580,7 +2582,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
|||||||
boost::nowide::remove(path_tmp.c_str());
|
boost::nowide::remove(path_tmp.c_str());
|
||||||
{
|
{
|
||||||
LifecycleEventContext ctx;
|
LifecycleEventContext ctx;
|
||||||
ctx.name = std::to_string(print->model().id().id);
|
ctx.id = std::to_string(print->model().id().id);
|
||||||
|
ctx.name = print->get_model_name();
|
||||||
ctx.code = LifecycleEvtCode::Error;
|
ctx.code = LifecycleEvtCode::Error;
|
||||||
ctx.msg = std::string(path) + "\n" + ex.what();
|
ctx.msg = std::string(path) + "\n" + ex.what();
|
||||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||||
@@ -2693,7 +2696,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
|||||||
if (ret) {
|
if (ret) {
|
||||||
{
|
{
|
||||||
LifecycleEventContext ctx;
|
LifecycleEventContext ctx;
|
||||||
ctx.name = std::to_string(print->model().id().id);
|
ctx.id = std::to_string(print->model().id().id);
|
||||||
|
ctx.name = print->get_model_name();
|
||||||
ctx.code = LifecycleEvtCode::Error;
|
ctx.code = LifecycleEvtCode::Error;
|
||||||
ctx.msg = std::string(path) + "\nFailed to rename the output G-code file: " + ret.message();
|
ctx.msg = std::string(path) + "\nFailed to rename the output G-code file: " + ret.message();
|
||||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||||
@@ -2712,7 +2716,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
|||||||
|
|
||||||
{
|
{
|
||||||
LifecycleEventContext ctx;
|
LifecycleEventContext ctx;
|
||||||
ctx.name = std::to_string(print->model().id().id);
|
ctx.id = std::to_string(print->model().id().id);
|
||||||
|
ctx.name = print->get_model_name();
|
||||||
ctx.code = LifecycleEvtCode::Ok;
|
ctx.code = LifecycleEvtCode::Ok;
|
||||||
ctx.msg = path;
|
ctx.msg = path;
|
||||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||||
|
|||||||
+10
-5
@@ -2717,7 +2717,8 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
|||||||
|
|
||||||
{
|
{
|
||||||
LifecycleEventContext ctx;
|
LifecycleEventContext ctx;
|
||||||
ctx.name = std::to_string(m_model.id().id);
|
ctx.id = std::to_string(m_model.id().id);
|
||||||
|
ctx.name = get_model_name();
|
||||||
ctx.code = LifecycleEvtCode::Ok;
|
ctx.code = LifecycleEvtCode::Ok;
|
||||||
ctx.cancellation_check = [this]() { return canceled(); };
|
ctx.cancellation_check = [this]() { return canceled(); };
|
||||||
fire_lifecycle_event(LifecycleEvent::SliceStarted, ctx);
|
fire_lifecycle_event(LifecycleEvent::SliceStarted, ctx);
|
||||||
@@ -3344,7 +3345,8 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
|||||||
|
|
||||||
{
|
{
|
||||||
LifecycleEventContext ctx;
|
LifecycleEventContext ctx;
|
||||||
ctx.name = std::to_string(m_model.id().id);
|
ctx.id = std::to_string(m_model.id().id);
|
||||||
|
ctx.name = get_model_name();
|
||||||
ctx.code = LifecycleEvtCode::Ok;
|
ctx.code = LifecycleEvtCode::Ok;
|
||||||
ctx.cancellation_check = [this]() { return canceled(); };
|
ctx.cancellation_check = [this]() { return canceled(); };
|
||||||
fire_lifecycle_event(LifecycleEvent::SliceGeometryFinished, ctx);
|
fire_lifecycle_event(LifecycleEvent::SliceGeometryFinished, ctx);
|
||||||
@@ -4950,7 +4952,8 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
|
|||||||
{
|
{
|
||||||
{
|
{
|
||||||
LifecycleEventContext ctx;
|
LifecycleEventContext ctx;
|
||||||
ctx.name = std::to_string(m_model.id().id);
|
ctx.id = std::to_string(m_model.id().id);
|
||||||
|
ctx.name = get_model_name();
|
||||||
ctx.code = LifecycleEvtCode::Ok;
|
ctx.code = LifecycleEvtCode::Ok;
|
||||||
ctx.msg = file;
|
ctx.msg = file;
|
||||||
ctx.cancellation_check = [this]() { return canceled(); };
|
ctx.cancellation_check = [this]() { return canceled(); };
|
||||||
@@ -4980,7 +4983,8 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
|
|||||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": found errors when process gcode file %1%") %file.c_str();
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": found errors when process gcode file %1%") %file.c_str();
|
||||||
{
|
{
|
||||||
LifecycleEventContext ctx;
|
LifecycleEventContext ctx;
|
||||||
ctx.name = std::to_string(m_model.id().id);
|
ctx.id = std::to_string(m_model.id().id);
|
||||||
|
ctx.name = get_model_name();
|
||||||
ctx.code = LifecycleEvtCode::Error;
|
ctx.code = LifecycleEvtCode::Error;
|
||||||
ctx.msg = file + "\n" + ex.what();
|
ctx.msg = file + "\n" + ex.what();
|
||||||
ctx.cancellation_check = [this]() { return canceled(); };
|
ctx.cancellation_check = [this]() { return canceled(); };
|
||||||
@@ -4994,7 +4998,8 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
|
|||||||
|
|
||||||
{
|
{
|
||||||
LifecycleEventContext ctx;
|
LifecycleEventContext ctx;
|
||||||
ctx.name = std::to_string(m_model.id().id);
|
ctx.id = std::to_string(m_model.id().id);
|
||||||
|
ctx.name = get_model_name();
|
||||||
ctx.code = LifecycleEvtCode::Ok;
|
ctx.code = LifecycleEvtCode::Ok;
|
||||||
ctx.msg = file;
|
ctx.msg = file;
|
||||||
ctx.cancellation_check = [this]() { return canceled(); };
|
ctx.cancellation_check = [this]() { return canceled(); };
|
||||||
|
|||||||
@@ -12800,8 +12800,6 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
|
|||||||
notification_manager->set_slicing_progress_export_possible();
|
notification_manager->set_slicing_progress_export_possible();
|
||||||
|
|
||||||
// Reset the "export G-code path" name, so that the automatic background processing will be enabled again.
|
// Reset the "export G-code path" name, so that the automatic background processing will be enabled again.
|
||||||
const std::string lifecycle_job_name = this->background_process.fff_print() ?
|
|
||||||
this->background_process.fff_print()->output_filename() : std::string();
|
|
||||||
this->background_process.reset_export();
|
this->background_process.reset_export();
|
||||||
// This bool stops showing export finished notification even when process_completed_with_error is false
|
// This bool stops showing export finished notification even when process_completed_with_error is false
|
||||||
bool has_error = false;
|
bool has_error = false;
|
||||||
@@ -12848,8 +12846,18 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
|
|||||||
|
|
||||||
{
|
{
|
||||||
Slic3r::LifecycleEventContext ctx;
|
Slic3r::LifecycleEventContext ctx;
|
||||||
ctx.name = lifecycle_job_name;
|
if (const PrintBase* print = this->background_process.current_print()) {
|
||||||
ctx.code = evt.cancelled() ? Slic3r::LifecycleEvtCode::Warn : (has_error ? Slic3r::LifecycleEvtCode::Error : Slic3r::LifecycleEvtCode::Ok);
|
const Model& model = print->model();
|
||||||
|
ctx.id = std::to_string(model.id().id);
|
||||||
|
if (model.model_info)
|
||||||
|
ctx.name = model.model_info->model_name;
|
||||||
|
} else {
|
||||||
|
// Realistically Printbase* print will never be null because select_technology already asserts an active print
|
||||||
|
// and the worker thread asserts it before processing.
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": slicing completed without an active print; lifecycle event has no model ID";
|
||||||
|
}
|
||||||
|
ctx.code = evt.cancelled() ? Slic3r::LifecycleEvtCode::Warn :
|
||||||
|
(has_error ? Slic3r::LifecycleEvtCode::Error : Slic3r::LifecycleEvtCode::Ok);
|
||||||
ctx.msg = evt.cancelled() ? "cancelled" : (has_error ? lifecycle_error_msg : std::string());
|
ctx.msg = evt.cancelled() ? "cancelled" : (has_error ? lifecycle_error_msg : std::string());
|
||||||
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::SlicingJobComplete, ctx);
|
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::SlicingJobComplete, ctx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,14 +31,15 @@ int UserManager::parse_json(std::string payload)
|
|||||||
{
|
{
|
||||||
bool restored_json = false;
|
bool restored_json = false;
|
||||||
json j;
|
json j;
|
||||||
json j_pre = json::parse(payload);
|
|
||||||
if (j_pre.empty()) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//bind/unbind
|
//bind/unbind
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
json j_pre = json::parse(payload);
|
||||||
|
if (j_pre.empty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (j_pre.contains("bind")) {
|
if (j_pre.contains("bind")) {
|
||||||
if (j_pre["bind"].contains("command")) {
|
if (j_pre["bind"].contains("command")) {
|
||||||
|
|
||||||
|
|||||||
@@ -274,13 +274,4 @@ bool Duet::start_print(wxString &msg, const std::string &filename, ConnectionTyp
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Duet::get_err_code_from_body(const std::string &body) const
|
|
||||||
{
|
|
||||||
pt::ptree root;
|
|
||||||
std::istringstream iss (body); // wrap returned json to istringstream
|
|
||||||
pt::read_json(iss, root);
|
|
||||||
|
|
||||||
return root.get<int>("err", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ private:
|
|||||||
ConnectionType connect(wxString &msg) const;
|
ConnectionType connect(wxString &msg) const;
|
||||||
void disconnect(ConnectionType connectionType) const;
|
void disconnect(ConnectionType connectionType) const;
|
||||||
bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType, bool simulationMode) const;
|
bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType, bool simulationMode) const;
|
||||||
int get_err_code_from_body(const std::string &body) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,15 +146,6 @@ bool ESP3D::start_print(wxString& msg, const std::string& filename) const
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ESP3D::get_err_code_from_body(const std::string& body) const
|
|
||||||
{
|
|
||||||
pt::ptree root;
|
|
||||||
std::istringstream iss(body); // wrap returned json to istringstream
|
|
||||||
pt::read_json(iss, root);
|
|
||||||
|
|
||||||
return root.get<int>("err", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ESP3D only accepts 8.3 filenames else it crashes marlin and other undefined behaviour
|
// ESP3D only accepts 8.3 filenames else it crashes marlin and other undefined behaviour
|
||||||
std::string ESP3D::get_short_name(const std::string& filename) const
|
std::string ESP3D::get_short_name(const std::string& filename) const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ private:
|
|||||||
std::string m_console_port;
|
std::string m_console_port;
|
||||||
|
|
||||||
bool start_print(wxString& msg, const std::string& filename) const;
|
bool start_print(wxString& msg, const std::string& filename) const;
|
||||||
int get_err_code_from_body(const std::string& body) const;
|
|
||||||
std::string get_short_name(const std::string& filename) const;
|
std::string get_short_name(const std::string& filename) const;
|
||||||
std::string format_command(const std::string& path, const std::string& arg, const std::string& val) const;
|
std::string format_command(const std::string& path, const std::string& arg, const std::string& val) const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -510,12 +510,22 @@ bool Flashforge::fetch_material_slots(std::vector<FlashforgeMaterialSlot>& slots
|
|||||||
if (!request_local_api_json("detail", json{{"serialNumber", m_serial_number}, {"checkCode", m_check_code}}.dump(), body, msg))
|
if (!request_local_api_json("detail", json{{"serialNumber", m_serial_number}, {"checkCode", m_check_code}}.dump(), body, msg))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const auto parsed = json::parse(body, nullptr, false, true);
|
if (!parse_material_slots(body, slots, supports_material_station)) {
|
||||||
if (parsed.is_discarded()) {
|
|
||||||
msg = _(L("Flashforge returned an invalid JSON response."));
|
msg = _(L("Flashforge returned an invalid JSON response."));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Flashforge::parse_material_slots(const std::string& body, std::vector<FlashforgeMaterialSlot>& slots, bool* supports_material_station)
|
||||||
|
{
|
||||||
|
slots.clear();
|
||||||
|
|
||||||
|
const auto parsed = json::parse(body, nullptr, false, true);
|
||||||
|
if (parsed.is_discarded())
|
||||||
|
return false;
|
||||||
|
|
||||||
const auto& detail = parsed.contains("detail") ? parsed["detail"] : parsed;
|
const auto& detail = parsed.contains("detail") ? parsed["detail"] : parsed;
|
||||||
const auto& station = detail.contains("matlStationInfo") ? detail["matlStationInfo"] :
|
const auto& station = detail.contains("matlStationInfo") ? detail["matlStationInfo"] :
|
||||||
detail.contains("MatlStationInfo") ? detail["MatlStationInfo"] : json();
|
detail.contains("MatlStationInfo") ? detail["MatlStationInfo"] : json();
|
||||||
@@ -542,12 +552,21 @@ bool Flashforge::fetch_material_slots(std::vector<FlashforgeMaterialSlot>& slots
|
|||||||
if (supports_material_station != nullptr)
|
if (supports_material_station != nullptr)
|
||||||
*supports_material_station = reports_material_station;
|
*supports_material_station = reports_material_station;
|
||||||
|
|
||||||
|
// Fields are read leniently: firmware may send numbers as strings or flags as numbers.
|
||||||
for (const auto& slot : slot_infos) {
|
for (const auto& slot : slot_infos) {
|
||||||
|
if (!slot.is_object())
|
||||||
|
continue;
|
||||||
FlashforgeMaterialSlot info;
|
FlashforgeMaterialSlot info;
|
||||||
info.slot_id = slot.value("slotId", static_cast<int>(slots.size()) + 1);
|
info.slot_id = static_cast<int>(slots.size()) + 1;
|
||||||
info.has_filament = slot.value("hasFilament", false);
|
if (const auto it = slot.find("slotId"); it != slot.end())
|
||||||
info.material_name = slot.value("materialName", std::string());
|
try_parse_json_int(*it, info.slot_id);
|
||||||
info.material_color = slot.value("materialColor", std::string());
|
int has_filament = 0;
|
||||||
|
if (const auto it = slot.find("hasFilament"); it != slot.end() && try_parse_json_int(*it, has_filament))
|
||||||
|
info.has_filament = has_filament != 0;
|
||||||
|
if (const auto it = slot.find("materialName"); it != slot.end() && it->is_string())
|
||||||
|
info.material_name = it->get<std::string>();
|
||||||
|
if (const auto it = slot.find("materialColor"); it != slot.end() && it->is_string())
|
||||||
|
info.material_color = it->get<std::string>();
|
||||||
slots.emplace_back(std::move(info));
|
slots.emplace_back(std::move(info));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,13 +689,4 @@ std::string Flashforge::extract_host_name() const
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Flashforge::get_err_code_from_body(const std::string& body) const
|
|
||||||
{
|
|
||||||
pt::ptree root;
|
|
||||||
std::istringstream iss(body); // wrap returned json to istringstream
|
|
||||||
pt::read_json(iss, root);
|
|
||||||
|
|
||||||
return root.get<int>("err", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ public:
|
|||||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||||
std::string get_host() const override { return m_host; }
|
std::string get_host() const override { return m_host; }
|
||||||
bool fetch_material_slots(std::vector<FlashforgeMaterialSlot>& slots, bool* supports_material_station, wxString& msg) const;
|
bool fetch_material_slots(std::vector<FlashforgeMaterialSlot>& slots, bool* supports_material_station, wxString& msg) const;
|
||||||
|
// Parses a local API "detail" reply. Returns false when the body is not valid JSON.
|
||||||
|
static bool parse_material_slots(const std::string& body, std::vector<FlashforgeMaterialSlot>& slots, bool* supports_material_station);
|
||||||
static bool discover_printers(std::vector<FlashforgeDiscoveredPrinter>& printers, wxString& msg, int timeout_ms = 10000, int idle_timeout_ms = 1500, int max_retries = 3);
|
static bool discover_printers(std::vector<FlashforgeDiscoveredPrinter>& printers, wxString& msg, int timeout_ms = 10000, int idle_timeout_ms = 1500, int max_retries = 3);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -68,7 +70,6 @@ private:
|
|||||||
bool request_local_api_json(const std::string& path, const std::string& body, std::string& response_body, wxString& error_msg) const;
|
bool request_local_api_json(const std::string& path, const std::string& body, std::string& response_body, wxString& error_msg) const;
|
||||||
std::string make_http_url(const std::string& path) const;
|
std::string make_http_url(const std::string& path) const;
|
||||||
std::string extract_host_name() const;
|
std::string extract_host_name() const;
|
||||||
int get_err_code_from_body(const std::string &body) const;
|
|
||||||
bool connect(wxString& msg) const;
|
bool connect(wxString& msg) const;
|
||||||
bool start_print(wxString& msg, const std::string& filename) const;
|
bool start_print(wxString& msg, const std::string& filename) const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -141,13 +141,4 @@ bool MKS::start_print(wxString& msg, const std::string& filename) const
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int MKS::get_err_code_from_body(const std::string& body) const
|
|
||||||
{
|
|
||||||
pt::ptree root;
|
|
||||||
std::istringstream iss(body); // wrap returned json to istringstream
|
|
||||||
pt::read_json(iss, root);
|
|
||||||
|
|
||||||
return root.get<int>("err", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // Slic3r
|
} // Slic3r
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ private:
|
|||||||
|
|
||||||
std::string get_upload_url(const std::string& filename) const;
|
std::string get_upload_url(const std::string& filename) const;
|
||||||
bool start_print(wxString& msg, const std::string& filename) const;
|
bool start_print(wxString& msg, const std::string& filename) const;
|
||||||
int get_err_code_from_body(const std::string& body) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,13 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
#include <sstream>
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
#include <boost/property_tree/json_parser.hpp>
|
||||||
|
|
||||||
#include <wx/string.h>
|
#include <wx/string.h>
|
||||||
#include <wx/app.h>
|
#include <wx/app.h>
|
||||||
@@ -172,6 +175,20 @@ std::string moonraker_error_reason(const std::string &body)
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
int PrintHost::get_err_code_from_body(const std::string &body)
|
||||||
|
{
|
||||||
|
boost::property_tree::ptree root;
|
||||||
|
std::istringstream iss(body);
|
||||||
|
try {
|
||||||
|
boost::property_tree::read_json(iss, root);
|
||||||
|
} catch (const std::exception &ex) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "PrintHost: response is not valid JSON: " << ex.what();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return root.get<int>("err", 0);
|
||||||
|
}
|
||||||
|
|
||||||
wxString PrintHost::format_error(const std::string &body, const std::string &error, unsigned status) const
|
wxString PrintHost::format_error(const std::string &body, const std::string &error, unsigned status) const
|
||||||
{
|
{
|
||||||
if (status != 0) {
|
if (status != 0) {
|
||||||
@@ -303,7 +320,12 @@ void PrintHostJobQueue::priv::bg_thread_main()
|
|||||||
% job.cancelled;
|
% job.cancelled;
|
||||||
|
|
||||||
if (! job.cancelled) {
|
if (! job.cancelled) {
|
||||||
perform_job(std::move(job));
|
// A failing job must not stop the worker, or later jobs would stay queued forever.
|
||||||
|
try {
|
||||||
|
perform_job(std::move(job));
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
emit_error(e.what());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
remove_source();
|
remove_source();
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ public:
|
|||||||
|
|
||||||
static PrintHost* get_print_host(DynamicPrintConfig *config);
|
static PrintHost* get_print_host(DynamicPrintConfig *config);
|
||||||
static std::string get_print_host_webui(DynamicPrintConfig *config);
|
static std::string get_print_host_webui(DynamicPrintConfig *config);
|
||||||
|
// Reads the "err" field of a JSON reply, 0 when absent. Returns -1 when the body is not valid JSON.
|
||||||
|
static int get_err_code_from_body(const std::string &body);
|
||||||
|
|
||||||
//Support for cloud webui login
|
//Support for cloud webui login
|
||||||
virtual bool is_cloud() const { return false; }
|
virtual bool is_cloud() const { return false; }
|
||||||
|
|||||||
@@ -654,13 +654,4 @@ bool UltiMaker::start_print(wxString &msg, const std::string &filename, Connecti
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
int UltiMaker::get_err_code_from_body(const std::string &body) const
|
|
||||||
{
|
|
||||||
pt::ptree root;
|
|
||||||
std::istringstream iss (body); // wrap returned json to istringstream
|
|
||||||
pt::read_json(iss, root);
|
|
||||||
|
|
||||||
return root.get<int>("err", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ private:
|
|||||||
void set_auth(Http& http) const;
|
void set_auth(Http& http) const;
|
||||||
void disconnect(ConnectionType connectionType) const;
|
void disconnect(ConnectionType connectionType) const;
|
||||||
bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType) const;
|
bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType) const;
|
||||||
int get_err_code_from_body(const std::string &body) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@
|
|||||||
#include "libslic3r/Layer.hpp"
|
#include "libslic3r/Layer.hpp"
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
#include "libslic3r/GCodeReader.hpp"
|
#include "libslic3r/GCodeReader.hpp"
|
||||||
|
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||||
|
#include "libslic3r/Exception.hpp"
|
||||||
|
#include "libslic3r/LifecycleEvents.hpp"
|
||||||
|
|
||||||
#include "test_helpers.hpp"
|
#include "test_helpers.hpp"
|
||||||
#include "test_utils.hpp"
|
#include "test_utils.hpp"
|
||||||
@@ -22,7 +25,10 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
#include <memory>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
using namespace Slic3r::Test;
|
using namespace Slic3r::Test;
|
||||||
@@ -224,8 +230,88 @@ std::string resolved_output_name(Model& model, const std::string& format, const
|
|||||||
return print.output_filename(filename_base);
|
return print.output_filename(filename_base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ScopedLifecycleHook
|
||||||
|
{
|
||||||
|
explicit ScopedLifecycleHook(LifecycleHookFn hook) { set_lifecycle_hook_fn(std::move(hook)); }
|
||||||
|
~ScopedLifecycleHook() { set_lifecycle_hook_fn(nullptr); }
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("Slicing lifecycle events identify the model", "[Print][LifecycleEvents]")
|
||||||
|
{
|
||||||
|
struct ObservedEvent {
|
||||||
|
LifecycleEvent event;
|
||||||
|
std::string id;
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
std::vector<ObservedEvent> events;
|
||||||
|
ScopedLifecycleHook hook([&](LifecycleEvent event, const LifecycleEventContext& ctx) {
|
||||||
|
events.push_back({ event, ctx.id, ctx.name });
|
||||||
|
});
|
||||||
|
|
||||||
|
Print print;
|
||||||
|
Model model;
|
||||||
|
ModelInfo info;
|
||||||
|
info.model_name = "Lifecycle test model";
|
||||||
|
model.model_info = std::make_shared<ModelInfo>(std::move(info));
|
||||||
|
init_print({cube(20)}, print, model);
|
||||||
|
|
||||||
|
print.process();
|
||||||
|
ScopedTemporaryFile temp(".gcode");
|
||||||
|
print.export_gcode(temp.string(), nullptr, nullptr);
|
||||||
|
GCodeProcessorResult result;
|
||||||
|
print.export_gcode_from_previous_file(temp.string(), &result);
|
||||||
|
|
||||||
|
const std::string expected_id = std::to_string(print.model().id().id);
|
||||||
|
const std::vector<LifecycleEvent> expected_events = {
|
||||||
|
LifecycleEvent::SliceStarted,
|
||||||
|
LifecycleEvent::SliceGeometryFinished,
|
||||||
|
LifecycleEvent::GCodeExportStarted,
|
||||||
|
LifecycleEvent::GCodeExportFinished,
|
||||||
|
LifecycleEvent::GCodeExportStarted,
|
||||||
|
LifecycleEvent::GCodeExportFinished,
|
||||||
|
};
|
||||||
|
REQUIRE(events.size() == expected_events.size());
|
||||||
|
for (size_t i = 0; i < expected_events.size(); ++i) {
|
||||||
|
CHECK(events[i].event == expected_events[i]);
|
||||||
|
CHECK(events[i].id == expected_id);
|
||||||
|
CHECK(events[i].name == "Lifecycle test model");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Slicing lifecycle event name is empty without model metadata", "[Print][LifecycleEvents]")
|
||||||
|
{
|
||||||
|
std::string event_id;
|
||||||
|
std::string event_name = "unset";
|
||||||
|
ScopedLifecycleHook hook([&](LifecycleEvent event, const LifecycleEventContext& ctx) {
|
||||||
|
if (event == LifecycleEvent::SliceStarted) {
|
||||||
|
event_id = ctx.id;
|
||||||
|
event_name = ctx.name;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Print print;
|
||||||
|
Model model;
|
||||||
|
init_print({cube(20)}, print, model);
|
||||||
|
print.process();
|
||||||
|
|
||||||
|
CHECK(event_id == std::to_string(print.model().id().id));
|
||||||
|
CHECK(event_name.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Output filenames with numeric statistics fail before slicing finishes", "[Print][Regression]")
|
||||||
|
{
|
||||||
|
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||||
|
config.set_key_value("filename_format", new ConfigOptionString("{int(total_weight*10) / 10.0}"));
|
||||||
|
|
||||||
|
Print print;
|
||||||
|
Model model;
|
||||||
|
init_print({cube(20)}, print, model, config);
|
||||||
|
|
||||||
|
CHECK_THROWS_AS(print.output_filename(), PlaceholderParserError);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("Print: {first_object_name} names the first printable object on the plate", "[Print]")
|
TEST_CASE("Print: {first_object_name} names the first printable object on the plate", "[Print]")
|
||||||
{
|
{
|
||||||
Model model;
|
Model model;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_plugin_cloud_metadata.cpp
|
test_plugin_cloud_metadata.cpp
|
||||||
test_plugin_audit.cpp
|
test_plugin_audit.cpp
|
||||||
test_shortcuts.cpp
|
test_shortcuts.cpp
|
||||||
|
test_user_manager.cpp
|
||||||
../fff_print/test_helpers.cpp
|
../fff_print/test_helpers.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
#include <catch2/catch_all.hpp>
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
#include "slic3r/Utils/PrintHost.hpp"
|
#include "slic3r/Utils/PrintHost.hpp"
|
||||||
|
#include "slic3r/Utils/Flashforge.hpp"
|
||||||
|
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
|
|
||||||
@@ -46,6 +50,14 @@ std::string moonraker_error(int code, const std::string& message, const std::str
|
|||||||
constexpr const char* k_busy_file_403 =
|
constexpr const char* k_busy_file_403 =
|
||||||
R"JSON({"error": {"code": 403, "message": "Forbidden", "traceback": "Traceback (most recent call last):\n\n File \"/home/lava/moonraker/moonraker/components/file_manager/file_manager.py\", line 1017, in _finish_gcode_upload\n can_start = self._handle_operation_check(check_path)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nmoonraker.utils.exceptions.ServerError: File currently in use\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n\n File \"/home/lava/moonraker/moonraker/components/application.py\", line 1069, in post\n raise tornado.web.HTTPError(\ntornado.web.HTTPError: HTTP 403: Forbidden (File is loaded, upload not permitted)\n"}})JSON";
|
R"JSON({"error": {"code": 403, "message": "Forbidden", "traceback": "Traceback (most recent call last):\n\n File \"/home/lava/moonraker/moonraker/components/file_manager/file_manager.py\", line 1017, in _finish_gcode_upload\n can_start = self._handle_operation_check(check_path)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nmoonraker.utils.exceptions.ServerError: File currently in use\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n\n File \"/home/lava/moonraker/moonraker/components/application.py\", line 1069, in post\n raise tornado.web.HTTPError(\ntornado.web.HTTPError: HTTP 403: Forbidden (File is loaded, upload not permitted)\n"}})JSON";
|
||||||
|
|
||||||
|
// Replies a print host can send instead of JSON: a proxy or login page, nothing, a cut-off body.
|
||||||
|
const std::vector<std::string> non_json_replies = {
|
||||||
|
"<html><body>proxy login required</body></html>",
|
||||||
|
"",
|
||||||
|
"{\"err\":",
|
||||||
|
"{\"detail\":{\"matlStationInfo\":{\"slotInfos\":[{\"slotId\":1,",
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("A Klipper upload error shows its reason instead of a Python traceback", "[PrintHost][Regression]")
|
TEST_CASE("A Klipper upload error shows its reason instead of a Python traceback", "[PrintHost][Regression]")
|
||||||
@@ -211,3 +223,94 @@ TEST_CASE("Error bodies that are not a Moonraker envelope are left unchanged", "
|
|||||||
CHECK(format_error("", "curl:Could not connect", 0) == "curl:Could not connect");
|
CHECK(format_error("", "curl:Could not connect", 0) == "curl:Could not connect");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Print host error code is read from a JSON reply", "[PrintHost]")
|
||||||
|
{
|
||||||
|
CHECK(PrintHost::get_err_code_from_body(R"({"err":0})") == 0);
|
||||||
|
CHECK(PrintHost::get_err_code_from_body(R"({"err":2})") == 2);
|
||||||
|
CHECK(PrintHost::get_err_code_from_body(R"({"status":"ok"})") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Print host error code reports a reply that is not JSON as an error", "[PrintHost]")
|
||||||
|
{
|
||||||
|
const std::string body = GENERATE(from_range(non_json_replies));
|
||||||
|
int err = 0;
|
||||||
|
REQUIRE_NOTHROW(err = PrintHost::get_err_code_from_body(body));
|
||||||
|
CHECK(err != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Print host error code tolerates a wrongly typed err field", "[PrintHost]")
|
||||||
|
{
|
||||||
|
const std::string body = GENERATE(as<std::string>{}, R"({"err":"busy"})", R"({"err":{"code":1}})", R"([1,2])");
|
||||||
|
CHECK_NOTHROW(PrintHost::get_err_code_from_body(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Flashforge material slots are read from a well-formed reply", "[PrintHost][Flashforge]")
|
||||||
|
{
|
||||||
|
const std::string body = R"({"code":0,"detail":{"hasMatlStation":true,"matlStationInfo":{"slotCnt":2,"slotInfos":[
|
||||||
|
{"slotId":1,"hasFilament":true,"materialName":"PLA","materialColor":"#FFFFFF"},
|
||||||
|
{"slotId":2,"hasFilament":false,"materialName":"","materialColor":""}]}}})";
|
||||||
|
|
||||||
|
std::vector<FlashforgeMaterialSlot> slots;
|
||||||
|
bool supports_station = false;
|
||||||
|
REQUIRE(Flashforge::parse_material_slots(body, slots, &supports_station));
|
||||||
|
CHECK(supports_station);
|
||||||
|
REQUIRE(slots.size() == 2);
|
||||||
|
CHECK(slots[0].slot_id == 1);
|
||||||
|
CHECK(slots[0].has_filament);
|
||||||
|
CHECK(slots[0].material_name == "PLA");
|
||||||
|
CHECK(slots[0].material_color == "#FFFFFF");
|
||||||
|
CHECK(slots[1].slot_id == 2);
|
||||||
|
CHECK_FALSE(slots[1].has_filament);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Flashforge material slots accept numbers as strings and flags as numbers", "[PrintHost][Flashforge]")
|
||||||
|
{
|
||||||
|
const std::string body = R"({"detail":{"matlStationInfo":{"slotInfos":[
|
||||||
|
{"slotId":"3","hasFilament":1,"materialName":null,"materialColor":7}]}}})";
|
||||||
|
|
||||||
|
std::vector<FlashforgeMaterialSlot> slots;
|
||||||
|
REQUIRE_NOTHROW(Flashforge::parse_material_slots(body, slots, nullptr));
|
||||||
|
REQUIRE(slots.size() == 1);
|
||||||
|
CHECK(slots[0].slot_id == 3);
|
||||||
|
CHECK(slots[0].has_filament);
|
||||||
|
CHECK(slots[0].material_name.empty());
|
||||||
|
CHECK(slots[0].material_color.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Flashforge material slots skip entries that are not objects", "[PrintHost][Flashforge]")
|
||||||
|
{
|
||||||
|
const std::string body = R"({"detail":{"matlStationInfo":{"slotInfos":[5,"slot",null,[],
|
||||||
|
{"slotId":4,"hasFilament":true,"materialName":"PETG"}]}}})";
|
||||||
|
|
||||||
|
std::vector<FlashforgeMaterialSlot> slots;
|
||||||
|
REQUIRE_NOTHROW(Flashforge::parse_material_slots(body, slots, nullptr));
|
||||||
|
REQUIRE(slots.size() == 1);
|
||||||
|
CHECK(slots[0].slot_id == 4);
|
||||||
|
CHECK(slots[0].material_name == "PETG");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Flashforge material slots tolerate slot info that is not a list", "[PrintHost][Flashforge]")
|
||||||
|
{
|
||||||
|
const std::string body = GENERATE(as<std::string>{},
|
||||||
|
R"({"detail":{"matlStationInfo":{"slotInfos":5}}})",
|
||||||
|
R"({"detail":{"matlStationInfo":{"slotInfos":"none"}}})",
|
||||||
|
R"({"detail":{"matlStationInfo":7}})",
|
||||||
|
R"({"detail":"offline"})");
|
||||||
|
|
||||||
|
std::vector<FlashforgeMaterialSlot> slots;
|
||||||
|
bool ok = false;
|
||||||
|
REQUIRE_NOTHROW(ok = Flashforge::parse_material_slots(body, slots, nullptr));
|
||||||
|
CHECK(ok);
|
||||||
|
CHECK(slots.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Flashforge material slots reject a reply that is not JSON", "[PrintHost][Flashforge]")
|
||||||
|
{
|
||||||
|
const std::string body = GENERATE(from_range(non_json_replies));
|
||||||
|
std::vector<FlashforgeMaterialSlot> slots;
|
||||||
|
bool ok = true;
|
||||||
|
REQUIRE_NOTHROW(ok = Flashforge::parse_material_slots(body, slots, nullptr));
|
||||||
|
CHECK_FALSE(ok);
|
||||||
|
CHECK(slots.empty());
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "slic3r/GUI/UserManager.hpp"
|
||||||
|
|
||||||
|
using namespace Slic3r;
|
||||||
|
|
||||||
|
TEST_CASE("User message that is not JSON is rejected without throwing", "[UserManager]")
|
||||||
|
{
|
||||||
|
const std::string payload = GENERATE(as<std::string>{}, "not json", "", "<html></html>", "{\"bind\":");
|
||||||
|
UserManager manager;
|
||||||
|
int result = 0;
|
||||||
|
REQUIRE_NOTHROW(result = manager.parse_json(payload));
|
||||||
|
CHECK(result == -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("User message without a successful bind is ignored", "[UserManager]")
|
||||||
|
{
|
||||||
|
const std::string payload = GENERATE(as<std::string>{}, "{}", R"({"bind":{"command":"unbind"}})", R"({"bind":"bind"})", "[1]");
|
||||||
|
UserManager manager;
|
||||||
|
CHECK(manager.parse_json(payload) == -1);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user