mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-05 23:38:54 +03:00
Compare commits
11 Commits
refactor/p
...
refactor/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ee736fe27 | ||
|
|
ced1058b31 | ||
|
|
aae83220f1 | ||
|
|
a10d9e77cf | ||
|
|
1d023216f2 | ||
|
|
dc2796209f | ||
|
|
194ef34080 | ||
|
|
4e1caa39eb | ||
|
|
38f5c84e7f | ||
|
|
4d8ce0e8a7 | ||
|
|
38cb1ae8d1 |
@@ -617,6 +617,18 @@ Polygon generate_rectange_polygon(const Vec2f &wt_box_min ,const Vec2f & wt_box_
|
||||
return res;
|
||||
}
|
||||
|
||||
const char* flush_planner_queue_command(GCodeFlavor flavor)
|
||||
{
|
||||
return flavor == gcfKlipper ? "M400\n" : "G4 S0\n";
|
||||
}
|
||||
|
||||
std::string wait_command(GCodeFlavor flavor, float seconds)
|
||||
{
|
||||
if (flavor == gcfKlipper)
|
||||
return "G4 P" + std::to_string(std::lround(seconds * 1000.f)) + "\n";
|
||||
return "G4 S" + Slic3r::float_to_string_decimal_point(seconds, 3) + "\n";
|
||||
}
|
||||
|
||||
class WipeTowerWriter
|
||||
{
|
||||
public:
|
||||
@@ -1145,7 +1157,7 @@ public:
|
||||
{
|
||||
if (time==0.f)
|
||||
return *this;
|
||||
m_gcode += "G4 S" + Slic3r::float_to_string_decimal_point(time, 3) + "\n";
|
||||
m_gcode += wait_command(m_gcode_flavor, time);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -1190,7 +1202,7 @@ public:
|
||||
|
||||
WipeTowerWriter& flush_planner_queue()
|
||||
{
|
||||
m_gcode += "G4 S0\n";
|
||||
m_gcode += flush_planner_queue_command(m_gcode_flavor);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -1333,6 +1345,8 @@ public:
|
||||
{
|
||||
std::string buffer;
|
||||
if (wait_for_moves)
|
||||
// Not flush_planner_queue_command(): this BBL precool path wants M400, which every
|
||||
// flavor it reaches understands, not the zero dwell the other flavors flush with.
|
||||
buffer += "M400\n";
|
||||
buffer += "M104";
|
||||
if (target_extruder != -1)
|
||||
|
||||
@@ -26,6 +26,12 @@ enum GCodeFlavor : unsigned char;
|
||||
Polylines construct_gap_for_skip_points(
|
||||
const Polygon& polygon, const std::vector<Vec2f>& skip_points, float wt_width, float gap_length, Polygon& insert_skip_polygon);
|
||||
|
||||
// Klipper acts on commands the instant it parses them, and its G4 reads only P (milliseconds),
|
||||
// so the zero-second and seconds-valued dwells every other flavor uses neither synchronize nor
|
||||
// pause there. Both defined in WipeTower.cpp, shared by WipeTower and WipeTower2.
|
||||
const char* flush_planner_queue_command(GCodeFlavor flavor); // finish queued moves, e.g. around M104/M109
|
||||
std::string wait_command(GCodeFlavor flavor, float seconds); // pause for `seconds`
|
||||
|
||||
class WipeTower
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -386,7 +386,8 @@ public:
|
||||
}
|
||||
|
||||
WipeTowerWriter2& switch_filament_monitoring(bool enable) {
|
||||
m_gcode += std::string("G4 S0\n") + "M591 " + (enable ? "R" : "S0") + "\n";
|
||||
flush_planner_queue();
|
||||
m_gcode += enable ? "M591 R\n" : "M591 S0\n";
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -625,7 +626,7 @@ public:
|
||||
// Set extruder temperature, don't wait by default.
|
||||
WipeTowerWriter2& set_extruder_temp(int temperature, bool wait = false)
|
||||
{
|
||||
m_gcode += "G4 S0\n"; // to flush planner queue
|
||||
flush_planner_queue();
|
||||
m_gcode += "M" + std::to_string(wait ? 109 : 104) + " S" + std::to_string(temperature) + "\n";
|
||||
return *this;
|
||||
}
|
||||
@@ -635,7 +636,7 @@ public:
|
||||
{
|
||||
if (time==0.f)
|
||||
return *this;
|
||||
m_gcode += "G4 S" + Slic3r::float_to_string_decimal_point(time, 3) + "\n";
|
||||
m_gcode += wait_command(m_gcode_flavor, time);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -677,8 +678,8 @@ public:
|
||||
}
|
||||
|
||||
WipeTowerWriter2& flush_planner_queue()
|
||||
{
|
||||
m_gcode += "G4 S0\n";
|
||||
{
|
||||
m_gcode += flush_planner_queue_command(m_gcode_flavor);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -156,6 +156,8 @@ void ConnectPrinterDialog::on_input_enter(wxCommandEvent& evt)
|
||||
void ConnectPrinterDialog::on_button_confirm(wxCommandEvent &event)
|
||||
{
|
||||
wxString code = m_textCtrl_code->GetTextCtrl()->GetValue();
|
||||
if (code.empty())
|
||||
code = "88888888";
|
||||
for (char c : code) {
|
||||
if (!(('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'))) {
|
||||
show_error(this, _L("Invalid input"));
|
||||
@@ -163,7 +165,7 @@ void ConnectPrinterDialog::on_button_confirm(wxCommandEvent &event)
|
||||
}
|
||||
}
|
||||
if (m_obj) {
|
||||
m_obj->set_user_access_code(code.ToStdString());
|
||||
m_obj->set_access_code(code.ToStdString());
|
||||
}
|
||||
EndModal(wxID_OK);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,18 @@
|
||||
|
||||
using namespace nlohmann;
|
||||
|
||||
namespace {
|
||||
// Orca: access_code and user_access_code used to be separate AppConfig keys before the two
|
||||
// fields were merged; fall back to the legacy key so existing users' saved codes aren't lost.
|
||||
std::string get_access_code_with_legacy_fallback(Slic3r::AppConfig* config, const std::string& dev_id)
|
||||
{
|
||||
std::string code = config->get("access_code", dev_id);
|
||||
if (code.empty())
|
||||
code = config->get("user_access_code", dev_id);
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
DeviceManager::DeviceManager(NetworkAgent* agent)
|
||||
@@ -48,8 +60,7 @@ namespace Slic3r
|
||||
obj->bind_sec_link = "secure";
|
||||
obj->m_is_online = true;
|
||||
obj->last_alive = Slic3r::Utils::get_current_time_utc();
|
||||
obj->set_access_code(config->get("access_code", m.dev_id), false);
|
||||
obj->set_user_access_code(config->get("user_access_code", m.dev_id), false);
|
||||
obj->set_access_code(get_access_code_with_legacy_fallback(config, m.dev_id), false);
|
||||
if (obj->has_access_right()) {
|
||||
localMachineList.insert(std::make_pair(m.dev_id, obj));
|
||||
} else {
|
||||
@@ -339,8 +350,7 @@ namespace Slic3r
|
||||
//load access code
|
||||
AppConfig* config = Slic3r::GUI::wxGetApp().app_config;
|
||||
if (config) {
|
||||
obj->set_access_code(Slic3r::GUI::wxGetApp().app_config->get("access_code", dev_id), false);
|
||||
obj->set_user_access_code(Slic3r::GUI::wxGetApp().app_config->get("user_access_code", dev_id), false);
|
||||
obj->set_access_code(get_access_code_with_legacy_fallback(config, dev_id), false);
|
||||
}
|
||||
localMachineList.insert(std::make_pair(dev_id, obj));
|
||||
|
||||
@@ -382,7 +392,6 @@ namespace Slic3r
|
||||
obj->m_is_online = true;
|
||||
obj->last_alive = Slic3r::Utils::get_current_time_utc();
|
||||
obj->set_access_code(access_code, false);
|
||||
obj->set_user_access_code(access_code, false);
|
||||
|
||||
update_local_machine(*obj);
|
||||
|
||||
@@ -611,6 +620,7 @@ namespace Slic3r
|
||||
}
|
||||
|
||||
selected_machine = dev_id;
|
||||
record_user_last_machine(selected_machine);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -875,20 +885,38 @@ namespace Slic3r
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceManager::record_user_last_machine(const std::string& dev_id)
|
||||
{
|
||||
if (Slic3r::GUI::wxGetApp().app_config) {
|
||||
Slic3r::GUI::wxGetApp().app_config->set("user_last_selected_machine", dev_id);
|
||||
}
|
||||
}
|
||||
|
||||
std::string DeviceManager::get_user_last_machine() const
|
||||
{
|
||||
if (Slic3r::GUI::wxGetApp().app_config) {
|
||||
const auto& user_last_machine = Slic3r::GUI::wxGetApp().app_config->get("user_last_selected_machine");
|
||||
if (!user_last_machine.empty()) {
|
||||
return user_last_machine;
|
||||
} else if (m_agent) {
|
||||
return m_agent->get_user_selected_machine();
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void DeviceManager::load_last_machine()
|
||||
{
|
||||
// Get all available machines, include cloud machines and lan machines that have access right
|
||||
auto all_machines = get_my_machine_list();
|
||||
if (all_machines.empty())
|
||||
// Only reconnect the remembered cloud machine. Do not select an arbitrary
|
||||
// first machine: agent swaps intentionally leave the selection empty until
|
||||
// the new agent explicitly selects its configured printer.
|
||||
if (userMachineList.empty())
|
||||
return;
|
||||
|
||||
// Reconnect the machine the user last selected, if it's still available.
|
||||
// why: no first-available fallback - auto-connecting an arbitrary machine
|
||||
// fights the agent-swap reset, which intentionally leaves nothing selected.
|
||||
const std::string last_monitor_machine = m_agent ? m_agent->get_user_selected_machine() : "";
|
||||
const auto last_machine = all_machines.find(last_monitor_machine);
|
||||
if (last_machine != all_machines.end())
|
||||
this->set_selected_machine(last_machine->second->get_dev_id());
|
||||
|
||||
const auto& last_monitor_machine = get_user_last_machine();
|
||||
if (userMachineList.find(last_monitor_machine) != userMachineList.end())
|
||||
set_selected_machine(last_monitor_machine);
|
||||
}
|
||||
|
||||
void DeviceManager::OnMachineBindStateChanged(MachineObject* obj, const std::string& new_state)
|
||||
|
||||
@@ -52,6 +52,9 @@ public:
|
||||
// swap path can reuse it instead of duplicating the two sidebar calls.
|
||||
void OnSelectedMachineLost();
|
||||
|
||||
void record_user_last_machine(const std::string& dev_id);
|
||||
std::string get_user_last_machine() const;
|
||||
|
||||
// local machine
|
||||
void set_local_selected_machine(std::string dev_id) { local_selected_machine = dev_id; };
|
||||
MachineObject* get_local_selected_machine() const { return get_local_machine(local_selected_machine); }
|
||||
|
||||
@@ -449,9 +449,7 @@ bool MachineObject::HasRecentLanMessage()
|
||||
|
||||
std::string MachineObject::get_access_code() const
|
||||
{
|
||||
if (get_user_access_code().empty())
|
||||
return access_code;
|
||||
return get_user_access_code();
|
||||
return access_code;
|
||||
}
|
||||
|
||||
void MachineObject::set_access_code(std::string code, bool only_refresh)
|
||||
@@ -470,37 +468,6 @@ void MachineObject::set_access_code(std::string code, bool only_refresh)
|
||||
}
|
||||
}
|
||||
|
||||
void MachineObject::erase_user_access_code()
|
||||
{
|
||||
this->user_access_code = "";
|
||||
AppConfig* config = GUI::wxGetApp().app_config;
|
||||
if (config) {
|
||||
GUI::wxGetApp().app_config->erase("user_access_code", get_dev_id());
|
||||
//GUI::wxGetApp().app_config->save();
|
||||
}
|
||||
}
|
||||
|
||||
void MachineObject::set_user_access_code(std::string code, bool only_refresh)
|
||||
{
|
||||
this->user_access_code = code;
|
||||
if (only_refresh && !code.empty()) {
|
||||
AppConfig* config = GUI::wxGetApp().app_config;
|
||||
if (config && !code.empty()) {
|
||||
GUI::wxGetApp().app_config->set_str("user_access_code", get_dev_id(), code);
|
||||
DeviceManager::update_local_machine(*this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string MachineObject::get_user_access_code() const
|
||||
{
|
||||
AppConfig* config = GUI::wxGetApp().app_config;
|
||||
if (config) {
|
||||
return GUI::wxGetApp().app_config->get("user_access_code", get_dev_id());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string MachineObject::get_show_printer_type() const
|
||||
{
|
||||
std::string printer_type = this->printer_type;
|
||||
@@ -2907,7 +2874,6 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
std::string access_code = j_pre["system"]["access_code"].get<std::string>();
|
||||
if (!access_code.empty()) {
|
||||
set_access_code(access_code);
|
||||
set_user_access_code(access_code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,6 @@ private:
|
||||
std::string dev_name;
|
||||
std::string dev_ip;
|
||||
std::string access_code;
|
||||
std::string user_access_code;
|
||||
|
||||
// type, time stamp, delay
|
||||
std::vector<std::tuple<std::string, uint64_t, uint64_t>> message_delay;
|
||||
@@ -228,11 +227,6 @@ public:
|
||||
std::string get_access_code() const;
|
||||
void set_access_code(std::string code, bool only_refresh = true);
|
||||
|
||||
/*user access code*/
|
||||
void set_user_access_code(std::string code, bool only_refresh = true);
|
||||
void erase_user_access_code();
|
||||
std::string get_user_access_code() const;
|
||||
|
||||
//PRINTER_TYPE printer_type = PRINTER_3DPrinter_UKNOWN;
|
||||
std::string printer_type; /* model_id */
|
||||
std::string get_show_printer_type() const;
|
||||
|
||||
@@ -2166,7 +2166,6 @@ void GUI_App::init_networking_callbacks()
|
||||
obj->is_tunnel_mqtt = tunnel;
|
||||
obj->command_request_push_all(true);
|
||||
obj->command_get_version();
|
||||
obj->erase_user_access_code();
|
||||
obj->command_get_access_code();
|
||||
if (m_agent)
|
||||
m_agent->install_device_cert(obj->get_dev_id(), obj->is_lan_mode_printer());
|
||||
@@ -2216,7 +2215,6 @@ void GUI_App::init_networking_callbacks()
|
||||
wxString text;
|
||||
if (msg == "5") {
|
||||
obj->set_access_code("");
|
||||
obj->erase_user_access_code();
|
||||
text = wxString::Format(_L("Incorrect password"));
|
||||
wxGetApp().show_dialog(text);
|
||||
} else {
|
||||
@@ -8286,7 +8284,7 @@ bool GUI_App::show_modal_ip_address_enter_dialog(bool input_sn, wxString title)
|
||||
wxGetApp().app_config->save();
|
||||
|
||||
obj->set_dev_ip(ip_address.ToStdString());
|
||||
obj->set_user_access_code(access_code.ToStdString());
|
||||
obj->set_access_code(access_code.ToStdString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1373,8 +1373,8 @@ void MainFrame::show_device(bool should_use_native) {
|
||||
|
||||
const bool use_printer_agents = wxGetApp().app_config->get_bool("use_printer_agents");
|
||||
|
||||
// The legacy page is appended when printer agents are enabled. Remove that
|
||||
// extra page before switching back to the normal native/legacy layout.
|
||||
// The web page is appended when printer agents are enabled. Remove that
|
||||
// extra page before switching back to the normal native/Web layout.
|
||||
if (!use_printer_agents) {
|
||||
if ((idx = m_tabpanel->FindPage(m_printer_view)) != wxNOT_FOUND && idx != tpMonitor) {
|
||||
m_printer_view->Show(false);
|
||||
@@ -1434,10 +1434,10 @@ void MainFrame::show_device(bool should_use_native) {
|
||||
|
||||
if ((idx = m_tabpanel->FindPage(m_printer_view)) == wxNOT_FOUND) {
|
||||
m_printer_view->Show(false);
|
||||
m_tabpanel->AddPage(m_printer_view, _L("Device (legacy)"), std::string("tab_monitor_active"),
|
||||
m_tabpanel->AddPage(m_printer_view, _L("Device (Web)"), std::string("tab_monitor_active"),
|
||||
std::string("tab_monitor_active"), false);
|
||||
} else {
|
||||
m_tabpanel->SetPageText(idx, _L("Device (legacy)"));
|
||||
m_tabpanel->SetPageText(idx, _L("Device (Web)"));
|
||||
}
|
||||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
@@ -4333,14 +4333,26 @@ void MainFrame::load_printer_url(wxString url, wxString apikey)
|
||||
void MainFrame::load_printer_url()
|
||||
{
|
||||
PresetBundle &preset_bundle = *wxGetApp().preset_bundle;
|
||||
if (preset_bundle.use_bbl_device_tab() || wxGetApp().app_config->get_bool("use_printer_agents"))
|
||||
if (preset_bundle.use_bbl_device_tab() && !wxGetApp().app_config->get_bool("use_printer_agents"))
|
||||
return;
|
||||
|
||||
auto cfg = preset_bundle.printers.get_edited_preset().config;
|
||||
if (cfg.opt_string("print_host").empty()) {
|
||||
if (auto *device_manager = wxGetApp().getDeviceManager()) {
|
||||
auto *machine = device_manager->get_selected_machine();
|
||||
if (!machine) {
|
||||
auto machines = device_manager->get_my_machine_list();
|
||||
if (machines.size() == 1)
|
||||
machine = machines.begin()->second;
|
||||
}
|
||||
if (machine && !machine->get_dev_ip().empty())
|
||||
cfg.opt_string("print_host") = machine->get_dev_ip();
|
||||
}
|
||||
}
|
||||
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
|
||||
wxString apikey;
|
||||
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
|
||||
if (cfg.has("printhost_apikey") && (host_type == htPrusaLink || host_type == htPrusaConnect))
|
||||
if (cfg.has("printhost_apikey") && host_type != htSimplyPrint)
|
||||
apikey = cfg.opt_string("printhost_apikey");
|
||||
if (!url.empty()) {
|
||||
load_printer_url(url, apikey);
|
||||
|
||||
@@ -3287,7 +3287,9 @@ void Sidebar::update_all_preset_comboboxes()
|
||||
: MainFrame::PrintSelectType::eSendGcode;
|
||||
}
|
||||
|
||||
if (!use_native_device_tab || use_printer_agents)
|
||||
if (use_printer_agents)
|
||||
p_mainframe->load_printer_url();
|
||||
else if (!use_native_device_tab)
|
||||
p_mainframe->load_printer_url(url, apikey);
|
||||
|
||||
|
||||
@@ -11236,9 +11238,14 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (new_sel == MainFrame::tpMonitor && wxGetApp().preset_bundle != nullptr) {
|
||||
const bool selecting_web_device_tab = main_frame->m_printer_view &&
|
||||
main_frame->m_tabpanel->GetPage(new_sel) == main_frame->m_printer_view;
|
||||
if (selecting_web_device_tab) {
|
||||
// Use the selected discovered machine when the preset has no host.
|
||||
main_frame->load_printer_url();
|
||||
} else if (new_sel == MainFrame::tpMonitor && wxGetApp().preset_bundle != nullptr) {
|
||||
auto cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||
wxString url = cfg.opt_string("print_host_webui").empty() ? cfg.opt_string("print_host") : cfg.opt_string("print_host_webui");
|
||||
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
|
||||
if (main_frame->m_printer_view && url.empty()) {
|
||||
// It's missing_connection page, reload so that we can replay the gif image
|
||||
main_frame->m_printer_view->reload();
|
||||
|
||||
@@ -1991,7 +1991,7 @@ void InputIpAddressDialog::workerThreadFunc(std::string str_ip, std::string str_
|
||||
if (w.expired()) return;
|
||||
|
||||
if (m_obj) {
|
||||
m_obj->set_user_access_code(str_access_code);
|
||||
m_obj->set_access_code(str_access_code);
|
||||
wxGetApp().getDeviceManager()->set_selected_machine(m_obj->get_dev_id());
|
||||
}
|
||||
|
||||
@@ -2055,6 +2055,11 @@ void InputIpAddressDialog::on_text(wxCommandEvent &evt)
|
||||
{
|
||||
auto str_ip = m_input_ip->GetTextCtrl()->GetValue();
|
||||
auto str_access_code = m_input_access_code->GetTextCtrl()->GetValue();
|
||||
|
||||
if (str_access_code.empty()) {
|
||||
str_access_code = "88888888";
|
||||
}
|
||||
|
||||
auto str_name = m_input_printer_name->GetTextCtrl()->GetValue().Strip(wxString::both);
|
||||
auto str_sn = m_input_sn->GetTextCtrl()->GetValue().Strip(wxString::both);
|
||||
bool invalid_access_code = true;
|
||||
@@ -2062,7 +2067,7 @@ void InputIpAddressDialog::on_text(wxCommandEvent &evt)
|
||||
for (char c : str_access_code) {
|
||||
if (!(('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'))) {
|
||||
invalid_access_code = false;
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -704,7 +704,6 @@ void SelectMachinePopup::update_user_devices()
|
||||
}
|
||||
|
||||
mobj->set_access_code("");
|
||||
mobj->erase_user_access_code();
|
||||
}
|
||||
|
||||
if (GUI::wxGetApp().plater())
|
||||
|
||||
@@ -1359,7 +1359,6 @@ void MoonrakerPrinterAgent::announce_printhost_device()
|
||||
if (auto* app_config = GUI::wxGetApp().app_config) {
|
||||
const std::string access_code = device_info.api_key.empty() ? "88888888" : device_info.api_key;
|
||||
app_config->set_str("access_code", device_info.dev_id, access_code);
|
||||
app_config->set_str("user_access_code", device_info.dev_id, access_code);
|
||||
}
|
||||
|
||||
nlohmann::json payload;
|
||||
|
||||
@@ -18,6 +18,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_slicing_pipeline_hook.cpp
|
||||
test_support_material.cpp
|
||||
test_trianglemesh.cpp
|
||||
test_wipe_tower.cpp
|
||||
)
|
||||
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r Catch2::Catch2WithMain)
|
||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
||||
|
||||
123
tests/fff_print/test_wipe_tower.cpp
Normal file
123
tests/fff_print/test_wipe_tower.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||
#include "libslic3r/GCode/WipeTower.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Test;
|
||||
|
||||
// Taken from the config enum map rather than hand-listed, so a flavor added to GCodeFlavor later
|
||||
// is covered here without editing this file.
|
||||
static std::vector<GCodeFlavor> non_klipper_flavors()
|
||||
{
|
||||
std::vector<GCodeFlavor> flavors;
|
||||
for (const auto &[name, value] : ConfigOptionEnum<GCodeFlavor>::get_enum_values())
|
||||
if (GCodeFlavor(value) != gcfKlipper)
|
||||
flavors.push_back(GCodeFlavor(value));
|
||||
return flavors;
|
||||
}
|
||||
|
||||
static std::string flavor_name(GCodeFlavor flavor)
|
||||
{
|
||||
return ConfigOptionEnum<GCodeFlavor>::get_enum_names()[int(flavor)];
|
||||
}
|
||||
|
||||
TEST_CASE("Klipper flushes the wipe tower planner queue with M400", "[WipeTower]")
|
||||
{
|
||||
CHECK(std::string(flush_planner_queue_command(gcfKlipper)) == "M400\n");
|
||||
}
|
||||
|
||||
TEST_CASE("Other flavors flush the wipe tower planner queue with a zero dwell", "[WipeTower]")
|
||||
{
|
||||
const GCodeFlavor flavor = GENERATE(from_range(non_klipper_flavors()));
|
||||
INFO("gcode flavor: " << flavor_name(flavor));
|
||||
CHECK(std::string(flush_planner_queue_command(flavor)) == "G4 S0\n");
|
||||
}
|
||||
|
||||
// 1.5s is exactly representable as a float, so neither form can drift when rounded.
|
||||
TEST_CASE("Klipper waits in the wipe tower with a millisecond dwell", "[WipeTower]")
|
||||
{
|
||||
CHECK(wait_command(gcfKlipper, 1.5f) == "G4 P1500\n");
|
||||
}
|
||||
|
||||
TEST_CASE("Other flavors wait in the wipe tower with a seconds dwell", "[WipeTower]")
|
||||
{
|
||||
const GCodeFlavor flavor = GENERATE(from_range(non_klipper_flavors()));
|
||||
INFO("gcode flavor: " << flavor_name(flavor));
|
||||
CHECK(wait_command(flavor, 1.5f) == "G4 S1.500\n");
|
||||
}
|
||||
|
||||
// The cases above only exercise the helpers in isolation. The one below slices a real
|
||||
// two-filament print, so it also covers the binding constraint of both changes: that the
|
||||
// configured `gcode_flavor` reaches the wipe tower writer and lands in the exported G-code.
|
||||
|
||||
// The G-code inside each WIPE_TOWER_START/WIPE_TOWER_END pair, concatenated, so an M400 emitted
|
||||
// outside the tower (e.g. GCodeProcessor's pre-heat injector) cannot create a false match.
|
||||
static std::string wipe_tower_regions(const std::string &gcode)
|
||||
{
|
||||
const std::string &start_tag = GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_Start);
|
||||
const std::string &end_tag = GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_End);
|
||||
std::string regions;
|
||||
size_t pos = 0;
|
||||
while (true) {
|
||||
size_t start = gcode.find(start_tag, pos);
|
||||
if (start == std::string::npos)
|
||||
break;
|
||||
size_t end = gcode.find(end_tag, start);
|
||||
if (end == std::string::npos)
|
||||
break;
|
||||
regions.append(gcode, start, end - start);
|
||||
pos = end + 1;
|
||||
}
|
||||
return regions;
|
||||
}
|
||||
|
||||
// A per-layer toolchange between the wall and infill filaments, same shape as
|
||||
// test_multifilament.cpp's "Each feature prints with its assigned filament", so the wipe tower
|
||||
// runs its toolchange path (and so `flush_planner_queue()`) on every layer.
|
||||
static DynamicPrintConfig wipe_tower_toolchange_config(const std::string &gcode_flavor)
|
||||
{
|
||||
return multifilament_config(2, {
|
||||
{ "sparse_infill_filament_id", 1 },
|
||||
{ "internal_solid_filament_id", 1 },
|
||||
{ "top_surface_filament_id", 1 },
|
||||
{ "bottom_surface_filament_id", 1 },
|
||||
{ "outer_wall_filament_id", 2 },
|
||||
{ "inner_wall_filament_id", 2 },
|
||||
{ "enable_prime_tower", true },
|
||||
{ "layer_height", 0.3 },
|
||||
{ "gcode_flavor", gcode_flavor },
|
||||
});
|
||||
}
|
||||
|
||||
// Slices a 10mm cube under `config`. Not plain Test::slice: a brand-new Print's first `apply()`
|
||||
// counts one filament in use, and DynamicPrintConfig::normalize_fdm_2's single-filament rule then
|
||||
// clears `enable_prime_tower`. A second apply, once init_print's regions have settled, sees both
|
||||
// filaments and the tower survives.
|
||||
static std::string slice_with_prime_tower(const DynamicPrintConfig &config)
|
||||
{
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({ cube(10) }, print, model, config);
|
||||
print.apply(model, config);
|
||||
return gcode(print);
|
||||
}
|
||||
|
||||
TEST_CASE("The wipe tower's toolchange planner flush follows the gcode flavor", "[WipeTower]")
|
||||
{
|
||||
auto [flavor, expected, unexpected] = GENERATE(table<std::string, std::string, std::string>({
|
||||
{ "klipper", "M400", "G4 S0" },
|
||||
{ "marlin", "G4 S0", "M400" } }));
|
||||
DYNAMIC_SECTION(flavor) {
|
||||
const std::string tower = wipe_tower_regions(slice_with_prime_tower(wipe_tower_toolchange_config(flavor)));
|
||||
REQUIRE_FALSE(tower.empty());
|
||||
CHECK_THAT(tower, Catch::Matchers::ContainsSubstring(expected));
|
||||
CHECK_THAT(tower, !Catch::Matchers::ContainsSubstring(unexpected));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user