mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-29 11:51:24 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c71ac0870 |
@@ -1692,9 +1692,9 @@ void Layer::make_ironing()
|
|||||||
ironing_params.just_infill = false;
|
ironing_params.just_infill = false;
|
||||||
// ORCA: Get filament-specific overrides if configured, otherwise use process values
|
// ORCA: Get filament-specific overrides if configured, otherwise use process values
|
||||||
size_t extruder_idx = ironing_params.extruder - 1;
|
size_t extruder_idx = ironing_params.extruder - 1;
|
||||||
ironing_params.line_spacing = (!config.filament_ironing_spacing.is_nil(extruder_idx)
|
ironing_params.line_spacing = std::max(IRONING_SPACING_MIN, !config.filament_ironing_spacing.is_nil(extruder_idx)
|
||||||
? config.filament_ironing_spacing.get_at(extruder_idx)
|
? config.filament_ironing_spacing.get_at(extruder_idx)
|
||||||
: config.ironing_spacing);
|
: config.ironing_spacing.value);
|
||||||
ironing_params.inset = (!config.filament_ironing_inset.is_nil(extruder_idx)
|
ironing_params.inset = (!config.filament_ironing_inset.is_nil(extruder_idx)
|
||||||
? config.filament_ironing_inset.get_at(extruder_idx)
|
? config.filament_ironing_inset.get_at(extruder_idx)
|
||||||
: config.ironing_inset);
|
: config.ironing_inset);
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ void FillConcentric::_fill_surface_single(
|
|||||||
|
|
||||||
coord_t min_spacing = scale_(this->spacing) * params.multiline;
|
coord_t min_spacing = scale_(this->spacing) * params.multiline;
|
||||||
coord_t distance = coord_t(min_spacing / params.density);
|
coord_t distance = coord_t(min_spacing / params.density);
|
||||||
|
// A non-positive step never shrinks the region, so the inset loop below would not end.
|
||||||
|
if (min_spacing <= 0 || distance <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
if (params.density > 0.9999f && !params.dont_adjust) {
|
if (params.density > 0.9999f && !params.dont_adjust) {
|
||||||
distance = this->_adjust_solid_spacing(bounding_box.size()(0), distance);
|
distance = this->_adjust_solid_spacing(bounding_box.size()(0), distance);
|
||||||
@@ -108,6 +111,8 @@ void FillConcentric::_fill_surface_single(const FillParams& params,
|
|||||||
// no rotation is supported for this infill pattern
|
// no rotation is supported for this infill pattern
|
||||||
Point bbox_size = expolygon.contour.bounding_box().size();
|
Point bbox_size = expolygon.contour.bounding_box().size();
|
||||||
coord_t min_spacing = scaled<coord_t>(this->spacing);
|
coord_t min_spacing = scaled<coord_t>(this->spacing);
|
||||||
|
if (min_spacing <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
if (params.density > 0.9999f && !params.dont_adjust) {
|
if (params.density > 0.9999f && !params.dont_adjust) {
|
||||||
coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / min_spacing + 1;
|
coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / min_spacing + 1;
|
||||||
|
|||||||
@@ -178,6 +178,10 @@ enum class IroningType {
|
|||||||
Count,
|
Count,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Smallest usable ironing line spacing. Anything tighter yields an unprintable number of lines,
|
||||||
|
// and zero stops the fillers from making progress.
|
||||||
|
constexpr double IRONING_SPACING_MIN = 0.05;
|
||||||
|
|
||||||
//BBS
|
//BBS
|
||||||
enum class WallInfillOrder {
|
enum class WallInfillOrder {
|
||||||
InnerOuterInfill,
|
InnerOuterInfill,
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ struct SupportParameters {
|
|||||||
|
|
||||||
this->ironing = object_config.support_ironing;
|
this->ironing = object_config.support_ironing;
|
||||||
this->ironing_flow = support_material_interface_flow.with_height(support_material_interface_flow.height() * 0.01 * object_config.support_ironing_flow.value);
|
this->ironing_flow = support_material_interface_flow.with_height(support_material_interface_flow.height() * 0.01 * object_config.support_ironing_flow.value);
|
||||||
this->ironing_spacing = object_config.support_ironing_spacing;
|
this->ironing_spacing = std::max(IRONING_SPACING_MIN, object_config.support_ironing_spacing.value);
|
||||||
this->ironing_pattern = object_config.support_ironing_pattern;
|
this->ironing_pattern = object_config.support_ironing_pattern;
|
||||||
|
|
||||||
// Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um.
|
// Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um.
|
||||||
|
|||||||
@@ -210,6 +210,30 @@ void ConfigManipulation::check_filament_max_volumetric_speed(DynamicPrintConfig
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConfigManipulation::check_filament_ironing_spacing(DynamicPrintConfig *config)
|
||||||
|
{
|
||||||
|
const auto *opt = config->option<ConfigOptionFloatsNullable>("filament_ironing_spacing");
|
||||||
|
if (opt == nullptr)
|
||||||
|
return;
|
||||||
|
std::vector<double> values = opt->values;
|
||||||
|
bool reset = false;
|
||||||
|
for (size_t i = 0; i < values.size(); ++i)
|
||||||
|
if (!opt->is_nil(i) && values[i] < IRONING_SPACING_MIN) {
|
||||||
|
values[i] = 0.1;
|
||||||
|
reset = true;
|
||||||
|
}
|
||||||
|
if (!reset)
|
||||||
|
return;
|
||||||
|
const wxString msg_text = _(L("Ironing spacing too small\nIt has been reset to 0.1"));
|
||||||
|
MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK);
|
||||||
|
DynamicPrintConfig new_conf = *config;
|
||||||
|
is_msg_dlg_already_exist = true;
|
||||||
|
dialog.ShowModal();
|
||||||
|
new_conf.set_key_value("filament_ironing_spacing", new ConfigOptionFloatsNullable(values));
|
||||||
|
apply(config, &new_conf);
|
||||||
|
is_msg_dlg_already_exist = false;
|
||||||
|
}
|
||||||
|
|
||||||
void ConfigManipulation::check_chamber_temperature(DynamicPrintConfig* config)
|
void ConfigManipulation::check_chamber_temperature(DynamicPrintConfig* config)
|
||||||
{
|
{
|
||||||
bool support_chamber_temp_control=GUI::wxGetApp().preset_bundle->printers.get_selected_preset().config.opt_bool("support_chamber_temp_control");
|
bool support_chamber_temp_control=GUI::wxGetApp().preset_bundle->printers.get_selected_preset().config.opt_bool("support_chamber_temp_control");
|
||||||
@@ -332,7 +356,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
|||||||
}
|
}
|
||||||
|
|
||||||
//BBS: ironing_spacing shouldn't be too small or equal to zero
|
//BBS: ironing_spacing shouldn't be too small or equal to zero
|
||||||
if (config->opt_float("ironing_spacing") < 0.05)
|
if (config->opt_float("ironing_spacing") < IRONING_SPACING_MIN)
|
||||||
{
|
{
|
||||||
const wxString msg_text = _(L("Ironing spacing too small\nIt has been reset to 0.1"));
|
const wxString msg_text = _(L("Ironing spacing too small\nIt has been reset to 0.1"));
|
||||||
MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK);
|
MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK);
|
||||||
@@ -343,7 +367,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
|||||||
apply(config, &new_conf);
|
apply(config, &new_conf);
|
||||||
is_msg_dlg_already_exist = false;
|
is_msg_dlg_already_exist = false;
|
||||||
}
|
}
|
||||||
if (config->opt_float("support_ironing_spacing") < 0.05)
|
if (config->opt_float("support_ironing_spacing") < IRONING_SPACING_MIN)
|
||||||
{
|
{
|
||||||
const wxString msg_text = _(L("Ironing spacing too small\nIt has been reset to 0.1"));
|
const wxString msg_text = _(L("Ironing spacing too small\nIt has been reset to 0.1"));
|
||||||
MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK);
|
MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK);
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ public:
|
|||||||
void check_nozzle_temperature_initial_layer_range(DynamicPrintConfig* config);
|
void check_nozzle_temperature_initial_layer_range(DynamicPrintConfig* config);
|
||||||
void check_adaptive_pressure_advance_model(DynamicPrintConfig* config);
|
void check_adaptive_pressure_advance_model(DynamicPrintConfig* config);
|
||||||
void check_filament_max_volumetric_speed(DynamicPrintConfig *config);
|
void check_filament_max_volumetric_speed(DynamicPrintConfig *config);
|
||||||
|
void check_filament_ironing_spacing(DynamicPrintConfig *config);
|
||||||
void check_chamber_temperature(DynamicPrintConfig* config);
|
void check_chamber_temperature(DynamicPrintConfig* config);
|
||||||
void check_chamber_minimal_temperature(DynamicPrintConfig* config);
|
void check_chamber_minimal_temperature(DynamicPrintConfig* config);
|
||||||
bool check_layer_height(DynamicPrintConfig* config);
|
bool check_layer_height(DynamicPrintConfig* config);
|
||||||
|
|||||||
@@ -4905,6 +4905,7 @@ void TabFilament::update()
|
|||||||
return; // ys_FIXME
|
return; // ys_FIXME
|
||||||
|
|
||||||
m_config_manipulation.check_filament_max_volumetric_speed(m_config);
|
m_config_manipulation.check_filament_max_volumetric_speed(m_config);
|
||||||
|
m_config_manipulation.check_filament_ironing_spacing(m_config);
|
||||||
|
|
||||||
m_update_cnt++;
|
m_update_cnt++;
|
||||||
|
|
||||||
|
|||||||
@@ -31,15 +31,14 @@ int UserManager::parse_json(std::string payload)
|
|||||||
{
|
{
|
||||||
bool restored_json = false;
|
bool restored_json = false;
|
||||||
json j;
|
json j;
|
||||||
|
|
||||||
//bind/unbind
|
|
||||||
|
|
||||||
try {
|
|
||||||
json j_pre = json::parse(payload);
|
json j_pre = json::parse(payload);
|
||||||
if (j_pre.empty()) {
|
if (j_pre.empty()) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//bind/unbind
|
||||||
|
|
||||||
|
try {
|
||||||
if (j_pre.contains("bind")) {
|
if (j_pre.contains("bind")) {
|
||||||
if (j_pre["bind"].contains("command")) {
|
if (j_pre["bind"].contains("command")) {
|
||||||
|
|
||||||
|
|||||||
@@ -274,4 +274,13 @@ 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,6 +40,7 @@ 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,6 +146,15 @@ 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,6 +33,7 @@ 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,22 +510,12 @@ 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;
|
||||||
|
|
||||||
if (!parse_material_slots(body, slots, supports_material_station)) {
|
const auto parsed = json::parse(body, nullptr, false, true);
|
||||||
|
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();
|
||||||
@@ -552,21 +542,12 @@ bool Flashforge::parse_material_slots(const std::string& body, std::vector<Flash
|
|||||||
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 = static_cast<int>(slots.size()) + 1;
|
info.slot_id = slot.value("slotId", static_cast<int>(slots.size()) + 1);
|
||||||
if (const auto it = slot.find("slotId"); it != slot.end())
|
info.has_filament = slot.value("hasFilament", false);
|
||||||
try_parse_json_int(*it, info.slot_id);
|
info.material_name = slot.value("materialName", std::string());
|
||||||
int has_filament = 0;
|
info.material_color = slot.value("materialColor", std::string());
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -689,4 +670,13 @@ 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,8 +45,6 @@ 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:
|
||||||
@@ -70,6 +68,7 @@ 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,4 +141,13 @@ 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,6 +34,7 @@ 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,13 +3,10 @@
|
|||||||
#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>
|
||||||
@@ -175,20 +172,6 @@ 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) {
|
||||||
@@ -432,10 +415,12 @@ void PrintHostJobQueue::priv::remove_source()
|
|||||||
source_to_remove.clear();
|
source_to_remove.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PrintHostJobQueue::upload_job(PrintHostJob &job, PrintHost::ProgressFn progress_fn, PrintHost::ErrorFn error_fn, PrintHost::InfoFn info_fn)
|
void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
|
||||||
{
|
{
|
||||||
|
emit_progress(0); // Indicate the upload is starting
|
||||||
|
|
||||||
// Captured before upload_data is moved into upload() below.
|
// Captured before upload_data is moved into upload() below.
|
||||||
const std::string upload_filename = job.upload_data.source_path.filename().string();
|
const std::string upload_filename = the_job.upload_data.source_path.filename().string();
|
||||||
|
|
||||||
{
|
{
|
||||||
LifecycleEventContext ctx;
|
LifecycleEventContext ctx;
|
||||||
@@ -444,37 +429,19 @@ bool PrintHostJobQueue::upload_job(PrintHostJob &job, PrintHost::ProgressFn prog
|
|||||||
fire_lifecycle_event(LifecycleEvent::UploadStarted, ctx);
|
fire_lifecycle_event(LifecycleEvent::UploadStarted, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = false;
|
bool success = the_job.printhost->upload(std::move(the_job.upload_data),
|
||||||
std::string error;
|
[this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
|
||||||
// A throwing upload must not stop the worker, or later jobs would stay queued forever.
|
[this](wxString error) { this->error_fn(std::move(error)); },
|
||||||
try {
|
[this](wxString tag, wxString host) { this->info_fn(std::move(tag), std::move(host)); }
|
||||||
success = job.printhost->upload(std::move(job.upload_data), std::move(progress_fn), error_fn, std::move(info_fn));
|
);
|
||||||
} catch (const std::exception &e) {
|
|
||||||
error = e.what();
|
|
||||||
error_fn(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
LifecycleEventContext ctx;
|
LifecycleEventContext ctx;
|
||||||
ctx.name = upload_filename;
|
ctx.name = upload_filename;
|
||||||
ctx.code = success ? LifecycleEvtCode::Ok : LifecycleEvtCode::Error;
|
ctx.code = success ? LifecycleEvtCode::Ok : LifecycleEvtCode::Error;
|
||||||
ctx.msg = error;
|
|
||||||
fire_lifecycle_event(LifecycleEvent::UploadFinished, ctx);
|
fire_lifecycle_event(LifecycleEvent::UploadFinished, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
|
|
||||||
{
|
|
||||||
emit_progress(0); // Indicate the upload is starting
|
|
||||||
|
|
||||||
bool success = PrintHostJobQueue::upload_job(the_job,
|
|
||||||
[this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
|
|
||||||
[this](wxString error) { this->error_fn(std::move(error)); },
|
|
||||||
[this](wxString tag, wxString host) { this->info_fn(std::move(tag), std::move(host)); }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
emit_progress(100);
|
emit_progress(100);
|
||||||
if (the_job.switch_to_device_tab) {
|
if (the_job.switch_to_device_tab) {
|
||||||
|
|||||||
@@ -87,8 +87,6 @@ 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; }
|
||||||
@@ -152,10 +150,6 @@ public:
|
|||||||
void enqueue(PrintHostJob job);
|
void enqueue(PrintHostJob job);
|
||||||
void cancel(size_t id);
|
void cancel(size_t id);
|
||||||
|
|
||||||
// Uploads the job, firing UploadStarted and a matching UploadFinished. An exception thrown by
|
|
||||||
// the upload is reported through error_fn and makes the upload fail.
|
|
||||||
static bool upload_job(PrintHostJob &job, PrintHost::ProgressFn progress_fn, PrintHost::ErrorFn error_fn, PrintHost::InfoFn info_fn);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct priv;
|
struct priv;
|
||||||
std::shared_ptr<priv> p;
|
std::shared_ptr<priv> p;
|
||||||
|
|||||||
@@ -654,4 +654,13 @@ 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,6 +64,7 @@ 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -747,6 +747,61 @@ TEST_CASE("A region with ironing turned off is never ironed", "[Fill]")
|
|||||||
REQUIRE(Layer::choose_ironing_extruder(cfg, spiral_mode, /*is_topmost_layer=*/true) == -1);
|
REQUIRE(Layer::choose_ironing_extruder(cfg, spiral_mode, /*is_topmost_layer=*/true) == -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ironing path count and total length in mm, over the whole object.
|
||||||
|
static std::pair<size_t, double> ironing_extent(const Print &print)
|
||||||
|
{
|
||||||
|
size_t paths = 0;
|
||||||
|
double length = 0.;
|
||||||
|
for (const Layer *layer : print.objects().front()->layers())
|
||||||
|
for (const LayerRegion *region : layer->regions())
|
||||||
|
for (const ExtrusionEntity *entity : region->fills.flatten().entities)
|
||||||
|
if (ironing_role(entity->role())) {
|
||||||
|
++paths;
|
||||||
|
length += unscale<double>(entity->length());
|
||||||
|
}
|
||||||
|
return {paths, length};
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Ironing spacing below the minimum irons at the minimum spacing", "[Fill]")
|
||||||
|
{
|
||||||
|
const std::string pattern = GENERATE("rectilinear", "concentric");
|
||||||
|
const bool via_filament = GENERATE(false, true);
|
||||||
|
const double spacing = GENERATE(0., 0.001);
|
||||||
|
CAPTURE(pattern, via_filament, spacing);
|
||||||
|
|
||||||
|
auto ironing_for = [&pattern, via_filament](double spacing) {
|
||||||
|
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||||
|
config.set_deserialize_strict({{"ironing_type", "top"},
|
||||||
|
{"ironing_pattern", pattern},
|
||||||
|
{"layer_height", 0.2}});
|
||||||
|
// The filament override replaces the process spacing, which stays at a usable value.
|
||||||
|
if (via_filament)
|
||||||
|
config.set_deserialize_strict({{"ironing_spacing", 0.1}, {"filament_ironing_spacing", spacing}});
|
||||||
|
else
|
||||||
|
config.set_deserialize_strict({{"ironing_spacing", spacing}});
|
||||||
|
Print print;
|
||||||
|
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(20)}, print, config);
|
||||||
|
return ironing_extent(print);
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::pair<size_t, double> clamped = ironing_for(spacing);
|
||||||
|
const std::pair<size_t, double> minimum = ironing_for(IRONING_SPACING_MIN);
|
||||||
|
REQUIRE(minimum.first > 0);
|
||||||
|
CHECK(clamped.first == minimum.first);
|
||||||
|
CHECK_THAT(clamped.second, Catch::Matchers::WithinRel(minimum.second, 1e-9));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Concentric fill at zero spacing returns without paths", "[Fill]")
|
||||||
|
{
|
||||||
|
std::unique_ptr<Fill> filler(Fill::new_from_type(ipConcentric));
|
||||||
|
filler->spacing = 0.;
|
||||||
|
filler->bounding_box = BoundingBox(Point(0, 0), Point::new_scale(10, 10));
|
||||||
|
FillParams params;
|
||||||
|
params.density = 1.f;
|
||||||
|
Surface surface(stTop, ExPolygon({Point(0, 0), Point::new_scale(10, 0), Point::new_scale(10, 10), Point::new_scale(0, 10)}));
|
||||||
|
CHECK(filler->fill_surface(&surface, params).empty());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("Solid infill direction offsets every layer when no template is set", "[Fill]")
|
TEST_CASE("Solid infill direction offsets every layer when no template is set", "[Fill]")
|
||||||
{
|
{
|
||||||
auto angles_for = [](int direction) {
|
auto angles_for = [](int direction) {
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ 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,15 +1,8 @@
|
|||||||
#include <catch2/catch_all.hpp>
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
#include "libslic3r/LifecycleEvents.hpp"
|
|
||||||
#include "slic3r/Utils/PrintHost.hpp"
|
#include "slic3r/Utils/PrintHost.hpp"
|
||||||
#include "slic3r/Utils/Flashforge.hpp"
|
|
||||||
|
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
|
|
||||||
@@ -31,41 +24,6 @@ public:
|
|||||||
std::string get_host() const override { return {}; }
|
std::string get_host() const override { return {}; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class ThrowingPrintHost : public TestPrintHost
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
bool upload(PrintHostUpload, ProgressFn, ErrorFn, InfoFn) const override { throw std::runtime_error("reply could not be read"); }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct UploadEvents
|
|
||||||
{
|
|
||||||
std::vector<LifecycleEvent> events;
|
|
||||||
std::vector<LifecycleEvtCode> codes;
|
|
||||||
std::vector<std::string> errors;
|
|
||||||
bool uploaded{false};
|
|
||||||
|
|
||||||
explicit UploadEvents(std::unique_ptr<PrintHost> host)
|
|
||||||
{
|
|
||||||
set_lifecycle_hook_fn([this](LifecycleEvent event, const LifecycleEventContext& ctx) {
|
|
||||||
events.push_back(event);
|
|
||||||
codes.push_back(ctx.code);
|
|
||||||
});
|
|
||||||
|
|
||||||
PrintHostJob job;
|
|
||||||
job.printhost = std::move(host);
|
|
||||||
job.upload_data.source_path = "plate.gcode";
|
|
||||||
try {
|
|
||||||
uploaded = PrintHostJobQueue::upload_job(job, [](Http::Progress, bool&) {},
|
|
||||||
[this](wxString error) { errors.push_back(error.ToStdString()); },
|
|
||||||
[](wxString, wxString) {});
|
|
||||||
} catch (...) {
|
|
||||||
set_lifecycle_hook_fn(nullptr);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
set_lifecycle_hook_fn(nullptr);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string format_error(const std::string& body, const std::string& error, unsigned status)
|
std::string format_error(const std::string& body, const std::string& error, unsigned status)
|
||||||
{
|
{
|
||||||
return TestPrintHost().format_error(body, error, status).ToStdString();
|
return TestPrintHost().format_error(body, error, status).ToStdString();
|
||||||
@@ -88,14 +46,6 @@ 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]")
|
||||||
@@ -261,114 +211,3 @@ 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());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("An upload that throws still finishes with an error", "[PrintHost][LifecycleEvents]")
|
|
||||||
{
|
|
||||||
UploadEvents run(std::make_unique<ThrowingPrintHost>());
|
|
||||||
|
|
||||||
CHECK_FALSE(run.uploaded);
|
|
||||||
CHECK(run.errors == std::vector<std::string>{"reply could not be read"});
|
|
||||||
CHECK(run.events == std::vector<LifecycleEvent>{LifecycleEvent::UploadStarted, LifecycleEvent::UploadFinished});
|
|
||||||
CHECK(run.codes == std::vector<LifecycleEvtCode>{LifecycleEvtCode::Ok, LifecycleEvtCode::Error});
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("A successful upload finishes without an error", "[PrintHost][LifecycleEvents]")
|
|
||||||
{
|
|
||||||
UploadEvents run(std::make_unique<TestPrintHost>());
|
|
||||||
|
|
||||||
CHECK(run.uploaded);
|
|
||||||
CHECK(run.errors.empty());
|
|
||||||
CHECK(run.events == std::vector<LifecycleEvent>{LifecycleEvent::UploadStarted, LifecycleEvent::UploadFinished});
|
|
||||||
CHECK(run.codes == std::vector<LifecycleEvtCode>{LifecycleEvtCode::Ok, LifecycleEvtCode::Ok});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
#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