From d0b4a4a87da65ba079456863092a335f192bf6dd Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 4 May 2022 17:28:57 +0200 Subject: [PATCH 1/9] Preferences Dialog: Revert values, when "Cancel" button is clicked ([SPE-1230|https://dev.prusa3d.com/browse/SPE-1230]) + Fixed bug: If change "dark mode" checkbox and "Settings layout mode", than dark mode wouldn't processed. + Code refactoring for create_settings_mode_widget() --- src/slic3r/GUI/Preferences.cpp | 243 ++++++++++++++++++++++----------- src/slic3r/GUI/Preferences.hpp | 9 ++ 2 files changed, 171 insertions(+), 81 deletions(-) diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 75c5c116f..c92909a8d 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -173,6 +173,10 @@ void PreferencesDialog::build() // Add "General" tab m_optgroup_general = create_options_tab(L("General"), tabs); m_optgroup_general->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (auto it = m_values.find(opt_key); it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } if (opt_key == "default_action_on_close_application" || opt_key == "default_action_on_select_preset" || opt_key == "default_action_on_new_project") m_values[opt_key] = boost::any_cast(value) ? "none" : "discard"; else if (opt_key == "default_action_on_dirty_project") @@ -335,6 +339,10 @@ void PreferencesDialog::build() // Add "Camera" tab m_optgroup_camera = create_options_tab(L("Camera"), tabs); m_optgroup_camera->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (auto it = m_values.find(opt_key);it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; @@ -358,25 +366,38 @@ void PreferencesDialog::build() // Add "GUI" tab m_optgroup_gui = create_options_tab(L("GUI"), tabs); m_optgroup_gui->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - if (opt_key == "suppress_hyperlinks") - m_values[opt_key] = boost::any_cast(value) ? "1" : ""; - else if (opt_key == "notify_release") { + if (opt_key == "notify_release") { int val_int = boost::any_cast(value); for (const auto& item : s_keys_map_NotifyReleaseMode) { if (item.second == val_int) { m_values[opt_key] = item.first; - break; + return; } } - } else - m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; - + } if (opt_key == "use_custom_toolbar_size") { m_icon_size_sizer->ShowItems(boost::any_cast(value)); - m_optgroup_gui->parent()->Layout(); - tabs->Layout(); - this->layout(); + refresh_og(m_optgroup_gui); } + if (opt_key == "tabs_as_menu") { + bool disable_new_layout = boost::any_cast(value); + m_rb_new_settings_layout_mode->Show(!disable_new_layout); + if (disable_new_layout && m_rb_new_settings_layout_mode->GetValue()) { + m_rb_new_settings_layout_mode->SetValue(false); + m_rb_old_settings_layout_mode->SetValue(true); + } + refresh_og(m_optgroup_gui); + } + + if (auto it = m_values.find(opt_key); it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } + + if (opt_key == "suppress_hyperlinks") + m_values[opt_key] = boost::any_cast(value) ? "1" : ""; + else + m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; append_bool_option(m_optgroup_gui, "seq_top_layer_only", @@ -464,6 +485,10 @@ void PreferencesDialog::build() // Add "Render" tab m_optgroup_render = create_options_tab(L("Render"), tabs); m_optgroup_render->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (auto it = m_values.find(opt_key); it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; @@ -479,6 +504,10 @@ void PreferencesDialog::build() // Add "Dark Mode" tab m_optgroup_dark_mode = create_options_tab(_L("Dark mode (experimental)"), tabs); m_optgroup_dark_mode->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (auto it = m_values.find(opt_key); it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; @@ -509,6 +538,7 @@ void PreferencesDialog::build() auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL); this->Bind(wxEVT_BUTTON, &PreferencesDialog::accept, this, wxID_OK); + this->Bind(wxEVT_BUTTON, &PreferencesDialog::revert, this, wxID_CANCEL); for (int id : {wxID_OK, wxID_CANCEL}) wxGetApp().UpdateDarkUI(static_cast(FindWindowById(id, this))); @@ -592,19 +622,6 @@ void PreferencesDialog::accept(wxEvent&) } } - for (const std::string& key : { "default_action_on_close_application", - "default_action_on_select_preset", - "default_action_on_new_project" }) { - auto it = m_values.find(key); - if (it != m_values.end() && it->second != "none" && app_config->get(key) != "none") - m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected - } - { - auto it = m_values.find("default_action_on_dirty_project"); - if (it != m_values.end() && !it->second.empty() && !app_config->get("default_action_on_dirty_project").empty()) - m_values.erase(it); // we shouldn't change value, if this parameter was selected, and then deselected - } - #if 0 //#ifdef _WIN32 // #ysDarkMSW - Allow it when we deside to support the sustem colors for application if (m_values.find("always_dark_color_mode") != m_values.end()) wxGetApp().force_sys_colors_update(); @@ -629,11 +646,85 @@ void PreferencesDialog::accept(wxEvent&) wxGetApp().force_menu_update(); #endif //_MSW_DARK_MODE #endif // _WIN32 - if (m_settings_layout_changed) - ;// application will be recreated after Preference dialog will be destroyed - else - // Nothify the UI to update itself from the ini file. - wxGetApp().update_ui_from_settings(); + + wxGetApp().update_ui_from_settings(); + m_values.clear(); +} + +void PreferencesDialog::revert(wxEvent&) +{ + auto app_config = get_app_config(); + + for (auto value : m_values) { + bool reverted = false; + const std::string& key = value.first; + + if (key == "default_action_on_dirty_project") { + m_optgroup_general->set_value(key, app_config->get(key).empty()); + continue; + } + if (key == "default_action_on_close_application" || key == "default_action_on_select_preset" || key == "default_action_on_new_project") { + m_optgroup_general->set_value(key, app_config->get(key) == "none"); + continue; + } + if (key == "notify_release") { + m_optgroup_gui->set_value(key, s_keys_map_NotifyReleaseMode.at(app_config->get(key))); + continue; + } + if (key == "custom_toolbar_size") { + m_icon_size_slider->SetValue(atoi(app_config->get("custom_toolbar_size").c_str())); + continue; + } + if (key == "old_settings_layout_mode") { + m_rb_old_settings_layout_mode->SetValue(app_config->get(key) == "1"); + continue; + } + if (key == "new_settings_layout_mode") { + m_rb_new_settings_layout_mode->SetValue(app_config->get(key) == "1"); + continue; + } + if (key == "dlg_settings_layout_mode") { + m_rb_dlg_settings_layout_mode->SetValue(app_config->get(key) == "1"); + continue; + } + + for (auto opt_group : { m_optgroup_general, m_optgroup_camera, m_optgroup_gui +#ifdef _WIN32 + , m_optgroup_dark_mode +#endif // _WIN32 +#if ENABLE_ENVIRONMENT_MAP + , m_optgroup_render +#endif // ENABLE_ENVIRONMENT_MAP + }) { + if (reverted = opt_group->set_value(key, app_config->get(key) == "1")) + break; + } + if (!reverted) + int i=0; + if (key == "tabs_as_menu") { + m_rb_new_settings_layout_mode->Show(app_config->get(key) != "1"); + refresh_og(m_optgroup_gui); + continue; + } + + if (key == "use_custom_toolbar_size") { + m_icon_size_sizer->ShowItems(app_config->get(key) == "1"); + refresh_og(m_optgroup_gui); + } + } + + m_values.clear(); + + auto revert_colors = [](wxColourPickerCtrl* color_pckr, const wxColour& color) { + if (color_pckr->GetColour() != color) { + color_pckr->SetColour(color); + wxPostEvent(color_pckr, wxCommandEvent(wxEVT_COLOURPICKER_CHANGED)); + } + }; + revert_colors(m_sys_colour, wxGetApp().get_label_clr_sys()); + revert_colors(m_mod_colour, wxGetApp().get_label_clr_modified()); + + EndModal(wxID_CANCEL); } void PreferencesDialog::msw_rescale() @@ -669,6 +760,13 @@ void PreferencesDialog::layout() Refresh(); } +void PreferencesDialog::refresh_og(std::shared_ptr og) +{ + og->parent()->Layout(); + tabs->Layout(); + this->layout(); +} + void PreferencesDialog::create_icon_size_slider() { const auto app_config = get_app_config(); @@ -695,14 +793,14 @@ void PreferencesDialog::create_icon_size_slider() if (!isOSX) style |= wxSL_LABELS | wxSL_AUTOTICKS; - auto slider = new wxSlider(parent, wxID_ANY, def_val, 30, 100, + m_icon_size_slider = new wxSlider(parent, wxID_ANY, def_val, 30, 100, wxDefaultPosition, wxDefaultSize, style); - slider->SetTickFreq(10); - slider->SetPageSize(10); - slider->SetToolTip(_L("Select toolbar icon size in respect to the default one.")); + m_icon_size_slider->SetTickFreq(10); + m_icon_size_slider->SetPageSize(10); + m_icon_size_slider->SetToolTip(_L("Select toolbar icon size in respect to the default one.")); - m_icon_size_sizer->Add(slider, 1, wxEXPAND); + m_icon_size_sizer->Add(m_icon_size_slider, 1, wxEXPAND); wxStaticText* val_label{ nullptr }; if (isOSX) { @@ -710,15 +808,15 @@ void PreferencesDialog::create_icon_size_slider() m_icon_size_sizer->Add(val_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, em); } - slider->Bind(wxEVT_SLIDER, ([this, slider, val_label](wxCommandEvent e) { - auto val = slider->GetValue(); + m_icon_size_slider->Bind(wxEVT_SLIDER, ([this, val_label](wxCommandEvent e) { + auto val = m_icon_size_slider->GetValue(); m_values["custom_toolbar_size"] = (boost::format("%d") % val).str(); if (val_label) val_label->SetLabelText(wxString::Format("%d", val)); - }), slider->GetId()); + }), m_icon_size_slider->GetId()); - for (wxWindow* win : std::vector{ slider, label, val_label }) { + for (wxWindow* win : std::vector{ m_icon_size_slider, label, val_label }) { if (!win) continue; win->SetFont(wxGetApp().normal_font()); @@ -731,26 +829,6 @@ void PreferencesDialog::create_icon_size_slider() void PreferencesDialog::create_settings_mode_widget() { -#ifdef _MSW_DARK_MODE - bool disable_new_layout = wxGetApp().tabs_as_menu(); -#endif - std::vector choices = { _L("Old regular layout with the tab bar"), - _L("New layout, access via settings button in the top menu"), - _L("Settings in non-modal window") }; - - auto app_config = get_app_config(); - int selection = app_config->get("old_settings_layout_mode") == "1" ? 0 : - app_config->get("new_settings_layout_mode") == "1" ? 1 : - app_config->get("dlg_settings_layout_mode") == "1" ? 2 : 0; - -#ifdef _MSW_DARK_MODE - if (disable_new_layout) { - choices = { _L("Old regular layout with the tab bar"), - _L("Settings in non-modal window") }; - selection = app_config->get("dlg_settings_layout_mode") == "1" ? 1 : 0; - } -#endif - wxWindow* parent = m_optgroup_gui->parent(); wxGetApp().UpdateDarkUI(parent); @@ -762,32 +840,35 @@ void PreferencesDialog::create_settings_mode_widget() wxSizer* stb_sizer = new wxStaticBoxSizer(stb, wxVERTICAL); - int id = 0; - for (const wxString& label : choices) { - wxRadioButton* btn = new wxRadioButton(parent, wxID_ANY, label, wxDefaultPosition, wxDefaultSize, id==0 ? wxRB_GROUP : 0); - stb_sizer->Add(btn); - btn->SetValue(id == selection); - - int dlg_id = 2; -#ifdef _MSW_DARK_MODE - if (disable_new_layout) - dlg_id = 1; -#endif - - btn->Bind(wxEVT_RADIOBUTTON, [this, id, dlg_id -#ifdef _MSW_DARK_MODE - , disable_new_layout -#endif - ](wxCommandEvent& ) { - m_values["old_settings_layout_mode"] = (id == 0) ? "1" : "0"; -#ifdef _MSW_DARK_MODE - if (!disable_new_layout) -#endif - m_values["new_settings_layout_mode"] = (id == 1) ? "1" : "0"; - m_values["dlg_settings_layout_mode"] = (id == dlg_id) ? "1" : "0"; + auto app_config = get_app_config(); + std::vector choices = { _L("Old regular layout with the tab bar"), + _L("New layout, access via settings button in the top menu"), + _L("Settings in non-modal window") }; + int id = -1; + auto add_radio = [this, parent, stb_sizer, choices](wxRadioButton** rb, int id, bool select) { + *rb = new wxRadioButton(parent, wxID_ANY, choices[id], wxDefaultPosition, wxDefaultSize, id == 0 ? wxRB_GROUP : 0); + stb_sizer->Add(*rb); + (*rb)->SetValue(select); + (*rb)->Bind(wxEVT_RADIOBUTTON, [this, id](wxCommandEvent&) { + m_values["old_settings_layout_mode"] = (id == 0) ? "1" : "0"; + m_values["new_settings_layout_mode"] = (id == 1) ? "1" : "0"; + m_values["dlg_settings_layout_mode"] = (id == 2) ? "1" : "0"; }); - id++; + }; + + add_radio(&m_rb_old_settings_layout_mode, ++id, app_config->get("old_settings_layout_mode") == "1"); + add_radio(&m_rb_new_settings_layout_mode, ++id, app_config->get("new_settings_layout_mode") == "1"); + add_radio(&m_rb_dlg_settings_layout_mode, ++id, app_config->get("dlg_settings_layout_mode") == "1"); + +#ifdef _MSW_DARK_MODE + if (app_config->get("tabs_as_menu") == "1") { + m_rb_new_settings_layout_mode->Hide(); + if (m_rb_new_settings_layout_mode->GetValue()) { + m_rb_new_settings_layout_mode->SetValue(false); + m_rb_old_settings_layout_mode->SetValue(true); + } } +#endif std::string opt_key = "settings_layout_mode"; m_blinkers[opt_key] = new BlinkingBitmap(parent); diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index f8b1d1237..2f8cafeb2 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -12,6 +12,8 @@ class wxColourPickerCtrl; class wxBookCtrlBase; +class wxSlider; +class wxRadioButton; namespace Slic3r { @@ -39,6 +41,11 @@ class PreferencesDialog : public DPIDialog std::shared_ptr m_optgroup_render; #endif // ENABLE_ENVIRONMENT_MAP wxSizer* m_icon_size_sizer; + wxSlider* m_icon_size_slider {nullptr}; + wxRadioButton* m_rb_old_settings_layout_mode {nullptr}; + wxRadioButton* m_rb_new_settings_layout_mode {nullptr}; + wxRadioButton* m_rb_dlg_settings_layout_mode {nullptr}; + wxColourPickerCtrl* m_sys_colour {nullptr}; wxColourPickerCtrl* m_mod_colour {nullptr}; wxBookCtrlBase* tabs {nullptr}; @@ -58,6 +65,7 @@ public: void build(); void update_ctrls_alignment(); void accept(wxEvent&); + void revert(wxEvent&); void show(const std::string& highlight_option = std::string(), const std::string& tab_name = std::string()); protected: @@ -65,6 +73,7 @@ protected: void on_dpi_changed(const wxRect& suggested_rect) override { msw_rescale(); } void on_sys_color_changed() override; void layout(); + void refresh_og(std::shared_ptr og); void create_icon_size_slider(); void create_settings_mode_widget(); void create_settings_text_color_widget(); From 8d1a2a8fb3d6210b41fcd032f38165eed42955d1 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 5 May 2022 11:38:48 +0200 Subject: [PATCH 2/9] Implemented FR: Update toolbars "on fly", when custom toolbar size is editing from Preferences dialog (https://dev.prusa3d.com/browse/SPE-1232) + Fixed update of the color pickers --- src/slic3r/GUI/Preferences.cpp | 75 +++++++++++++++++++++++----------- src/slic3r/GUI/Preferences.hpp | 4 ++ 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index c92909a8d..8ad387367 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -9,6 +9,7 @@ #include "Notebook.hpp" #include "ButtonsDescription.hpp" #include "OG_CustomCtrl.hpp" +#include "GLCanvas3D.hpp" namespace Slic3r { @@ -54,6 +55,14 @@ PreferencesDialog::PreferencesDialog(wxWindow* parent) : m_highlighter.set_timer_owner(this, 0); } +static void update_color(wxColourPickerCtrl* color_pckr, const wxColour& color) +{ + if (color_pckr->GetColour() != color) { + color_pckr->SetColour(color); + wxPostEvent(color_pckr, wxCommandEvent(wxEVT_COLOURPICKER_CHANGED)); + } +} + void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::string()*/, const std::string& tab_name/*= std::string()*/) { int selected_tab = 0; @@ -66,6 +75,14 @@ void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::strin if (!highlight_opt_key.empty()) init_highlighter(highlight_opt_key); + // cache input values for custom toolbar size + m_custom_toolbar_size = atoi(get_app_config()->get("custom_toolbar_size").c_str()); + m_use_custom_toolbar_size = get_app_config()->get("use_custom_toolbar_size") == "1"; + + // update colors for color pickers + update_color(m_sys_colour, wxGetApp().get_label_clr_sys()); + update_color(m_mod_colour, wxGetApp().get_label_clr_modified()); + this->ShowModal(); } @@ -378,6 +395,10 @@ void PreferencesDialog::build() if (opt_key == "use_custom_toolbar_size") { m_icon_size_sizer->ShowItems(boost::any_cast(value)); refresh_og(m_optgroup_gui); + get_app_config()->set("use_custom_toolbar_size", boost::any_cast(value) ? "1" : "0"); + get_app_config()->save(); + wxGetApp().plater()->get_current_canvas3D()->render(); + return; } if (opt_key == "tabs_as_menu") { bool disable_new_layout = boost::any_cast(value); @@ -648,13 +669,31 @@ void PreferencesDialog::accept(wxEvent&) #endif // _WIN32 wxGetApp().update_ui_from_settings(); - m_values.clear(); + clear_cache(); } void PreferencesDialog::revert(wxEvent&) { auto app_config = get_app_config(); + bool save_app_config = false; + if (m_custom_toolbar_size != atoi(app_config->get("custom_toolbar_size").c_str())) { + app_config->set("custom_toolbar_size", (boost::format("%d") % m_custom_toolbar_size).str()); + m_icon_size_slider->SetValue(m_custom_toolbar_size); + save_app_config |= true; + } + if (m_use_custom_toolbar_size != (get_app_config()->get("use_custom_toolbar_size") == "1")) { + app_config->set("use_custom_toolbar_size", m_use_custom_toolbar_size ? "1" : "0"); + save_app_config |= true; + + m_optgroup_gui->set_value("use_custom_toolbar_size", m_use_custom_toolbar_size); + m_icon_size_sizer->ShowItems(m_use_custom_toolbar_size); + refresh_og(m_optgroup_gui); + } + if (save_app_config) + app_config->save(); + + for (auto value : m_values) { bool reverted = false; const std::string& key = value.first; @@ -671,10 +710,6 @@ void PreferencesDialog::revert(wxEvent&) m_optgroup_gui->set_value(key, s_keys_map_NotifyReleaseMode.at(app_config->get(key))); continue; } - if (key == "custom_toolbar_size") { - m_icon_size_slider->SetValue(atoi(app_config->get("custom_toolbar_size").c_str())); - continue; - } if (key == "old_settings_layout_mode") { m_rb_old_settings_layout_mode->SetValue(app_config->get(key) == "1"); continue; @@ -706,24 +741,9 @@ void PreferencesDialog::revert(wxEvent&) refresh_og(m_optgroup_gui); continue; } - - if (key == "use_custom_toolbar_size") { - m_icon_size_sizer->ShowItems(app_config->get(key) == "1"); - refresh_og(m_optgroup_gui); - } } - m_values.clear(); - - auto revert_colors = [](wxColourPickerCtrl* color_pckr, const wxColour& color) { - if (color_pckr->GetColour() != color) { - color_pckr->SetColour(color); - wxPostEvent(color_pckr, wxCommandEvent(wxEVT_COLOURPICKER_CHANGED)); - } - }; - revert_colors(m_sys_colour, wxGetApp().get_label_clr_sys()); - revert_colors(m_mod_colour, wxGetApp().get_label_clr_modified()); - + clear_cache(); EndModal(wxID_CANCEL); } @@ -760,6 +780,12 @@ void PreferencesDialog::layout() Refresh(); } +void PreferencesDialog::clear_cache() +{ + m_values.clear(); + m_custom_toolbar_size = -1; +} + void PreferencesDialog::refresh_og(std::shared_ptr og) { og->parent()->Layout(); @@ -808,9 +834,12 @@ void PreferencesDialog::create_icon_size_slider() m_icon_size_sizer->Add(val_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, em); } - m_icon_size_slider->Bind(wxEVT_SLIDER, ([this, val_label](wxCommandEvent e) { + m_icon_size_slider->Bind(wxEVT_SLIDER, ([this, val_label, app_config](wxCommandEvent e) { auto val = m_icon_size_slider->GetValue(); - m_values["custom_toolbar_size"] = (boost::format("%d") % val).str(); + + app_config->set("custom_toolbar_size", (boost::format("%d") % val).str()); + app_config->save(); + wxGetApp().plater()->get_current_canvas3D()->render(); if (val_label) val_label->SetLabelText(wxString::Format("%d", val)); diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index 2f8cafeb2..4a82cee00 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -55,6 +55,9 @@ class PreferencesDialog : public DPIDialog bool m_seq_top_layer_only_changed{ false }; bool m_recreate_GUI{false}; + int m_custom_toolbar_size{-1}; + bool m_use_custom_toolbar_size{false}; + public: explicit PreferencesDialog(wxWindow* paren); ~PreferencesDialog() = default; @@ -73,6 +76,7 @@ protected: void on_dpi_changed(const wxRect& suggested_rect) override { msw_rescale(); } void on_sys_color_changed() override; void layout(); + void clear_cache(); void refresh_og(std::shared_ptr og); void create_icon_size_slider(); void create_settings_mode_widget(); From bd644df2f75a7203fe543cc109a8564a43008610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 29 Apr 2022 08:05:52 +0200 Subject: [PATCH 3/9] Suppressed reports of memory leaks from AMD driver and D-Bus library. --- src/PrusaSlicer.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 4483d6010..c79d08843 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -837,6 +837,9 @@ extern "C" { "leak:libnvidia-tls.so\n" // For NVidia driver. "leak:terminator_CreateDevice\n" // For Intel Vulkan drivers. "leak:swrast_dri.so\n" // For Mesa 3D software driver. + "leak:amdgpu_dri.so\n" // For AMD driver. + "leak:libdrm_amdgpu.so\n" // For AMD driver. + "leak:libdbus-1.so\n" // For D-Bus library. Unsure if it is a leak or not. ; } } From 356bec6e5f10b8062c17cad3bb9210daf50256cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 29 Apr 2022 08:06:24 +0200 Subject: [PATCH 4/9] Added deallocation of wxBoxSizer into OptionsGroup::activate_line() when is not used. --- src/slic3r/GUI/OptionsGroup.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index a861f478f..257fe2532 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -338,10 +338,12 @@ void OptionsGroup::activate_line(Line& line) wxBOTTOM | wxTOP | (option.opt.full_width ? int(wxEXPAND) : int(wxALIGN_CENTER_VERTICAL)), (wxOSX || !staticbox) ? 0 : 2); if (is_sizer_field(field)) sizer->Add(field->getSizer(), 1, (option.opt.full_width ? int(wxEXPAND) : int(wxALIGN_CENTER_VERTICAL)), 0); - } + } else + delete sizer; return; } + bool sizer_is_used = false; bool is_multioption_line = option_set.size() > 1; for (auto opt : option_set) { ConfigOptionDef option = opt.opt; @@ -356,8 +358,9 @@ void OptionsGroup::activate_line(Line& line) wxSize(sublabel_width != -1 ? sublabel_width * wxGetApp().em_unit() : -1, -1), wxALIGN_RIGHT); label->SetBackgroundStyle(wxBG_STYLE_PAINT); label->SetFont(wxGetApp().normal_font()); - sizer_tmp->Add(label, 0, wxALIGN_CENTER_VERTICAL, 0); - } + sizer_tmp->Add(label, 0, wxALIGN_CENTER_VERTICAL, 0); + sizer_is_used = true; + } // add field const Option& opt_ref = opt; @@ -412,6 +415,9 @@ void OptionsGroup::activate_line(Line& line) if (!custom_ctrl) sizer->Add(line.extra_widget_sizer, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); //! requires verification } + + if (custom_ctrl && !sizer_is_used) + delete sizer; } // create all controls for the option group from the m_lines From 17e74141ceaf6e14b8279665f31e58031ef67c2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 29 Apr 2022 08:07:30 +0200 Subject: [PATCH 5/9] Fixed a crash in Lightning infill. --- src/libslic3r/Fill/Lightning/Generator.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Fill/Lightning/Generator.cpp b/src/libslic3r/Fill/Lightning/Generator.cpp index 6a4a675cb..e0cf9680a 100644 --- a/src/libslic3r/Fill/Lightning/Generator.cpp +++ b/src/libslic3r/Fill/Lightning/Generator.cpp @@ -116,8 +116,12 @@ void Generator::generateTrees(const PrintObject &print_object) if (layer_id == 0) return; - const Polygons& below_outlines = infill_outlines[layer_id - 1]; - outlines_locator.set_bbox(get_extents(below_outlines).inflated(SCALED_EPSILON)); + const Polygons &below_outlines = infill_outlines[layer_id - 1]; + BoundingBox below_outlines_bbox = get_extents(below_outlines).inflated(SCALED_EPSILON); + if (const BoundingBox &outlines_locator_bbox = outlines_locator.bbox(); outlines_locator_bbox.defined) + below_outlines_bbox.merge(outlines_locator_bbox); + + outlines_locator.set_bbox(below_outlines_bbox); outlines_locator.create(below_outlines, locator_cell_size); std::vector& lower_trees = m_lightning_layers[layer_id - 1].tree_roots; From d069befa1fe0cdad32ded2cbd67bfa5f47224881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 29 Apr 2022 08:08:10 +0200 Subject: [PATCH 6/9] Fixed missing layers of Lightning infill. --- src/libslic3r/Fill/Lightning/Layer.cpp | 31 +++++------------------ src/libslic3r/Fill/Lightning/TreeNode.cpp | 12 ++++----- src/libslic3r/Fill/Lightning/TreeNode.hpp | 6 ++--- 3 files changed, 16 insertions(+), 33 deletions(-) diff --git a/src/libslic3r/Fill/Lightning/Layer.cpp b/src/libslic3r/Fill/Lightning/Layer.cpp index 1e1127a79..c996b9b7b 100644 --- a/src/libslic3r/Fill/Lightning/Layer.cpp +++ b/src/libslic3r/Fill/Lightning/Layer.cpp @@ -6,6 +6,7 @@ #include "DistanceField.hpp" #include "TreeNode.hpp" +#include "../../ClipperUtils.hpp" #include "../../Geometry.hpp" #include "Utils.hpp" @@ -271,6 +272,7 @@ void Layer::reconnectRoots } } +#if 0 /*! * Moves the point \p from onto the nearest polygon or leaves the point as-is, when the comb boundary is not within the root of \p max_dist2 distance. * Given a \p distance more than zero, the point will end up inside, and conversely outside. @@ -398,6 +400,7 @@ static unsigned int moveInside(const Polygons& polygons, Point& from, int distan } return static_cast(-1); } +#endif // Returns 'added someting'. Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const @@ -405,31 +408,11 @@ Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t if (tree_roots.empty()) return {}; - Polygons result_lines; - for (const auto& tree : tree_roots) { - // If even the furthest location in the tree is inside the polygon, the entire tree must be inside of the polygon. - // (Don't take the root as that may be on the edge and cause rounding errors to register as 'outside'.) - constexpr coord_t epsilon = 5; - Point should_be_inside = tree->getLocation(); - moveInside(limit_to_outline, should_be_inside, epsilon, epsilon * epsilon); - if (inside(limit_to_outline, should_be_inside)) - tree->convertToPolylines(result_lines, line_width); - } + Polylines result_lines; + for (const auto &tree : tree_roots) + tree->convertToPolylines(result_lines, line_width); - // TODO: allow for polylines! - Polylines split_lines; - for (Polygon &line : result_lines) { - if (line.size() <= 1) - continue; - Point last = line[0]; - for (size_t point_idx = 1; point_idx < line.size(); point_idx++) { - Point here = line[point_idx]; - split_lines.push_back({ last, here }); - last = here; - } - } - - return split_lines; + return intersection_pl(result_lines, limit_to_outline); } } // namespace Slic3r::Lightning diff --git a/src/libslic3r/Fill/Lightning/TreeNode.cpp b/src/libslic3r/Fill/Lightning/TreeNode.cpp index d1820410e..822550fc4 100644 --- a/src/libslic3r/Fill/Lightning/TreeNode.cpp +++ b/src/libslic3r/Fill/Lightning/TreeNode.cpp @@ -343,16 +343,16 @@ coord_t Node::prune(const coord_t& pruning_distance) return max_distance_pruned; } -void Node::convertToPolylines(Polygons& output, const coord_t line_width) const +void Node::convertToPolylines(Polylines &output, const coord_t line_width) const { - Polygons result; + Polylines result; result.emplace_back(); convertToPolylines(0, result); removeJunctionOverlap(result, line_width); append(output, std::move(result)); } -void Node::convertToPolylines(size_t long_line_idx, Polygons& output) const +void Node::convertToPolylines(size_t long_line_idx, Polylines &output) const { if (m_children.empty()) { output[long_line_idx].points.push_back(m_p); @@ -372,12 +372,12 @@ void Node::convertToPolylines(size_t long_line_idx, Polygons& output) const } } -void Node::removeJunctionOverlap(Polygons& result_lines, const coord_t line_width) const +void Node::removeJunctionOverlap(Polylines &result_lines, const coord_t line_width) const { const coord_t reduction = line_width / 2; // TODO make configurable? size_t res_line_idx = 0; while (res_line_idx < result_lines.size()) { - Polygon &polyline = result_lines[res_line_idx]; + Polyline &polyline = result_lines[res_line_idx]; if (polyline.size() <= 1) { polyline = std::move(result_lines.back()); result_lines.pop_back(); @@ -387,7 +387,7 @@ void Node::removeJunctionOverlap(Polygons& result_lines, const coord_t line_widt coord_t to_be_reduced = reduction; Point a = polyline.back(); for (int point_idx = int(polyline.size()) - 2; point_idx >= 0; point_idx--) { - const Point b = polyline[point_idx]; + const Point b = polyline.points[point_idx]; const Point ab = b - a; const auto ab_len = coord_t(ab.cast().norm()); if (ab_len >= to_be_reduced) { diff --git a/src/libslic3r/Fill/Lightning/TreeNode.hpp b/src/libslic3r/Fill/Lightning/TreeNode.hpp index 55ef35e0a..fdb80d2e6 100644 --- a/src/libslic3r/Fill/Lightning/TreeNode.hpp +++ b/src/libslic3r/Fill/Lightning/TreeNode.hpp @@ -239,7 +239,7 @@ public: * * \param output all branches in this tree connected into polylines */ - void convertToPolylines(Polygons& output, coord_t line_width) const; + void convertToPolylines(Polylines &output, coord_t line_width) const; /*! If this was ever a direct child of the root, it'll have a previous grounding location. * @@ -258,9 +258,9 @@ protected: * \param long_line a reference to a polyline in \p output which to continue building on in the recursion * \param output all branches in this tree connected into polylines */ - void convertToPolylines(size_t long_line_idx, Polygons& output) const; + void convertToPolylines(size_t long_line_idx, Polylines &output) const; - void removeJunctionOverlap(Polygons& polylines, coord_t line_width) const; + void removeJunctionOverlap(Polylines &polylines, coord_t line_width) const; bool m_is_root; Point m_p; From 09a9d79e993baa55975e853affd25c22414c45f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 29 Apr 2022 08:09:58 +0200 Subject: [PATCH 7/9] Fix of #8227 (Lightning infill wasn't working when "Combine infill every X layers" was set to a different value than one.) --- src/libslic3r/Fill/Lightning/Generator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Fill/Lightning/Generator.cpp b/src/libslic3r/Fill/Lightning/Generator.cpp index e0cf9680a..0bdd1c7e8 100644 --- a/src/libslic3r/Fill/Lightning/Generator.cpp +++ b/src/libslic3r/Fill/Lightning/Generator.cpp @@ -63,7 +63,7 @@ void Generator::generateInitialInternalOverhangs(const PrintObject &print_object Polygons infill_area_here; for (const LayerRegion* layerm : print_object.get_layer(layer_nr)->regions()) for (const Surface& surface : layerm->fill_surfaces.surfaces) - if (surface.surface_type == stInternal) + if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) append(infill_area_here, infill_wall_offset == 0 ? surface.expolygon : offset(surface.expolygon, infill_wall_offset)); //Remove the part of the infill area that is already supported by the walls. @@ -92,7 +92,7 @@ void Generator::generateTrees(const PrintObject &print_object) for (int layer_id = int(print_object.layers().size()) - 1; layer_id >= 0; layer_id--) for (const LayerRegion *layerm : print_object.get_layer(layer_id)->regions()) for (const Surface &surface : layerm->fill_surfaces.surfaces) - if (surface.surface_type == stInternal) + if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) append(infill_outlines[layer_id], infill_wall_offset == 0 ? surface.expolygon : offset(surface.expolygon, infill_wall_offset)); // For various operations its beneficial to quickly locate nearby features on the polygon: From 5a67d0e183d1342a4bebf0780365dd172dea9a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 5 May 2022 13:52:52 +0200 Subject: [PATCH 8/9] Fixed build on Linux (GCC 11.2). --- src/libslic3r/Geometry/ConvexHull.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libslic3r/Geometry/ConvexHull.hpp b/src/libslic3r/Geometry/ConvexHull.hpp index 9ba957824..9e9088f1e 100644 --- a/src/libslic3r/Geometry/ConvexHull.hpp +++ b/src/libslic3r/Geometry/ConvexHull.hpp @@ -4,6 +4,10 @@ #include "../Polygon.hpp" namespace Slic3r { + +class ExPolygon; +using ExPolygons = std::vector; + namespace Geometry { Pointf3s convex_hull(Pointf3s points); From d4b8d4d0f3abc6be73dfd9aa9b79a035a62229d2 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 5 May 2022 17:57:48 +0200 Subject: [PATCH 9/9] Further Perl unit test porting to C++ and Perl interface reduction: Ported cooling, gap fill, thin walls and polyline unit tests. --- src/libslic3r/Config.cpp | 36 ++++ src/libslic3r/Config.hpp | 74 ++++--- src/libslic3r/ExPolygon.hpp | 2 + src/libslic3r/GCode/CoolingBuffer.hpp | 2 + src/libslic3r/Geometry/ConvexHull.hpp | 2 + t/clean_polylines.t | 83 -------- t/combineinfill.t | 56 ------ t/config.t | 20 -- t/cooling.t | 214 -------------------- t/flow.t | 83 -------- t/gaps.t | 59 ------ t/gcode.t | 9 +- t/loops.t | 57 ------ t/thin.t | 185 ----------------- tests/fff_print/CMakeLists.txt | 4 + tests/fff_print/test_cooling.cpp | 274 ++++++++++++++++++++++++++ tests/fff_print/test_custom_gcode.cpp | 220 +++++++++++++++++++++ tests/fff_print/test_data.cpp | 4 + tests/fff_print/test_data.hpp | 1 + tests/fff_print/test_fill.cpp | 2 +- tests/fff_print/test_flow.cpp | 141 +++++++++---- tests/fff_print/test_gaps.cpp | 60 ++++++ tests/fff_print/test_thin_walls.cpp | 191 ++++++++++++++++++ tests/libslic3r/CMakeLists.txt | 1 + tests/libslic3r/test_config.cpp | 22 ++- tests/libslic3r/test_geometry.cpp | 32 ++- tests/libslic3r/test_polygon.cpp | 62 ++++++ tests/libslic3r/test_polyline.cpp | 28 +++ xs/CMakeLists.txt | 1 - xs/lib/Slic3r/XS.pm | 1 - xs/src/perlglue.cpp | 1 - xs/t/01_trianglemesh.t | 30 --- xs/t/03_point.t | 6 +- xs/t/04_expolygon.t | 17 +- xs/t/05_surface.t | 7 +- xs/t/06_polygon.t | 21 -- xs/t/07_extrusionpath.t | 4 +- xs/t/08_extrusionloop.t | 3 +- xs/t/09_polyline.t | 44 +---- xs/t/10_line.t | 17 +- xs/t/12_extrusionpathcollection.t | 8 +- xs/t/17_boundingbox.t | 27 --- xs/xsp/ExPolygon.xsp | 2 - xs/xsp/GCode.xsp | 53 ----- xs/xsp/my.map | 9 - xs/xsp/typemap.xspt | 18 -- 46 files changed, 1080 insertions(+), 1113 deletions(-) delete mode 100644 t/clean_polylines.t delete mode 100644 t/config.t delete mode 100644 t/cooling.t delete mode 100644 t/flow.t delete mode 100644 t/gaps.t delete mode 100644 t/loops.t delete mode 100644 t/thin.t create mode 100644 tests/fff_print/test_cooling.cpp create mode 100644 tests/fff_print/test_custom_gcode.cpp create mode 100644 tests/fff_print/test_gaps.cpp create mode 100644 tests/fff_print/test_thin_walls.cpp create mode 100644 tests/libslic3r/test_polyline.cpp delete mode 100644 xs/t/01_trianglemesh.t delete mode 100644 xs/t/06_polygon.t delete mode 100644 xs/t/17_boundingbox.t delete mode 100644 xs/xsp/GCode.xsp diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 4b8dfa234..2cfb0740c 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -402,6 +402,42 @@ std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, s return out; } +std::string ConfigBase::SetDeserializeItem::format(std::initializer_list values) +{ + std::string out; + int i = 0; + for (int v : values) { + if (i ++ > 0) + out += ", "; + out += std::to_string(v); + } + return out; +} + +std::string ConfigBase::SetDeserializeItem::format(std::initializer_list values) +{ + std::string out; + int i = 0; + for (float v : values) { + if (i ++ > 0) + out += ", "; + out += float_to_string_decimal_point(double(v)); + } + return out; +} + +std::string ConfigBase::SetDeserializeItem::format(std::initializer_list values) +{ + std::string out; + int i = 0; + for (float v : values) { + if (i ++ > 0) + out += ", "; + out += float_to_string_decimal_point(v); + } + return out; +} + void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent) { // loop through options and apply them diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index bfd307de3..b8c046ceb 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -1950,6 +1950,11 @@ public: throw BadOptionTypeException("Conversion to a wrong type"); return static_cast(opt); } + + template T* opt(const t_config_option_key &opt_key, bool create = false) + { return dynamic_cast(this->optptr(opt_key, create)); } + template const T* opt(const t_config_option_key &opt_key) const + { return dynamic_cast(this->optptr(opt_key)); } // Apply all keys of other ConfigBase defined by this->def() to this ConfigBase. // An UnknownOptionException is thrown in case some option keys of other are not defined by this->def(), @@ -1995,11 +2000,23 @@ public: SetDeserializeItem(const std::string &opt_key, const bool value, bool append = false) : opt_key(opt_key), opt_value(value ? "1" : "0"), append(append) {} SetDeserializeItem(const char *opt_key, const int value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {} SetDeserializeItem(const std::string &opt_key, const int value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {} + SetDeserializeItem(const char *opt_key, const std::initializer_list values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {} + SetDeserializeItem(const std::string &opt_key, const std::initializer_list values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {} SetDeserializeItem(const char *opt_key, const float value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {} SetDeserializeItem(const std::string &opt_key, const float value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {} SetDeserializeItem(const char *opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {} SetDeserializeItem(const std::string &opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {} + SetDeserializeItem(const char *opt_key, const std::initializer_list values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {} + SetDeserializeItem(const std::string &opt_key, const std::initializer_list values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {} + SetDeserializeItem(const char *opt_key, const std::initializer_list values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {} + SetDeserializeItem(const std::string &opt_key, const std::initializer_list values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {} + std::string opt_key; std::string opt_value; bool append = false; + + private: + static std::string format(std::initializer_list values); + static std::string format(std::initializer_list values); + static std::string format(std::initializer_list values); }; // May throw BadOptionTypeException() if the operation fails. void set_deserialize(std::initializer_list items, ConfigSubstitutionContext& substitutions); @@ -2008,7 +2025,31 @@ public: double get_abs_value(const t_config_option_key &opt_key) const; double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const; - void setenv_() const; + + std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return this->option(opt_key, create)->value; } + const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast(this)->opt_string(opt_key); } + std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } + const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast(this)->opt_string(opt_key, idx); } + + double& opt_float(const t_config_option_key &opt_key) { return this->option(opt_key)->value; } + const double& opt_float(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key))->value; } + double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } + const double& opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } + + int& opt_int(const t_config_option_key &opt_key) { return this->option(opt_key)->value; } + int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key))->value; } + int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } + int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } + + // In ConfigManipulation::toggle_print_fff_options, it is called on option with type ConfigOptionEnumGeneric* and also ConfigOptionEnum*. + // Thus the virtual method getInt() is used to retrieve the enum value. + template + ENUM opt_enum(const t_config_option_key &opt_key) const { return static_cast(this->option(opt_key)->getInt()); } + + bool opt_bool(const t_config_option_key &opt_key) const { return this->option(opt_key)->value != 0; } + bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option(opt_key)->get_at(idx) != 0; } + + void setenv_() const; ConfigSubstitutions load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule); ConfigSubstitutions load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule); ConfigSubstitutions load_from_ini_string(const std::string &data, ForwardCompatibilitySubstitutionRule compatibility_rule); @@ -2017,10 +2058,10 @@ public: ConfigSubstitutions load_from_ini_string_commented(std::string &&data, ForwardCompatibilitySubstitutionRule compatibility_rule); ConfigSubstitutions load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule); ConfigSubstitutions load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule); - void save(const std::string &file) const; + void save(const std::string &file) const; // Set all the nullable values to nils. - void null_nullables(); + void null_nullables(); static size_t load_from_gcode_string_legacy(ConfigBase& config, const char* str, ConfigSubstitutionContext& substitutions); @@ -2129,10 +2170,6 @@ public: // Allow DynamicConfig to be instantiated on ints own without a definition. // If the definition is not defined, the method requiring the definition will throw NoDefinitionException. const ConfigDef* def() const override { return nullptr; } - template T* opt(const t_config_option_key &opt_key, bool create = false) - { return dynamic_cast(this->option(opt_key, create)); } - template const T* opt(const t_config_option_key &opt_key) const - { return dynamic_cast(this->option(opt_key)); } // Overrides ConfigResolver::optptr(). const ConfigOption* optptr(const t_config_option_key &opt_key) const override; // Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name. @@ -2163,29 +2200,6 @@ public: // Returns options being equal in the two configs, ignoring options not present in both configs. t_config_option_keys equal(const DynamicConfig &other) const; - std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return this->option(opt_key, create)->value; } - const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast(this)->opt_string(opt_key); } - std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } - const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast(this)->opt_string(opt_key, idx); } - - double& opt_float(const t_config_option_key &opt_key) { return this->option(opt_key)->value; } - const double& opt_float(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key))->value; } - double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } - const double& opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } - - int& opt_int(const t_config_option_key &opt_key) { return this->option(opt_key)->value; } - int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key))->value; } - int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } - int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } - - // In ConfigManipulation::toggle_print_fff_options, it is called on option with type ConfigOptionEnumGeneric* and also ConfigOptionEnum*. - // Thus the virtual method getInt() is used to retrieve the enum value. - template - ENUM opt_enum(const t_config_option_key &opt_key) const { return static_cast(this->option(opt_key)->getInt()); } - - bool opt_bool(const t_config_option_key &opt_key) const { return this->option(opt_key)->value != 0; } - bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option(opt_key)->get_at(idx) != 0; } - // Command line processing bool read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys = nullptr); diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 344450c4a..7eccf2ec8 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -67,6 +67,8 @@ public: void simplify(double tolerance, ExPolygons* expolygons) const; void medial_axis(double max_width, double min_width, ThickPolylines* polylines) const; void medial_axis(double max_width, double min_width, Polylines* polylines) const; + Polylines medial_axis(double max_width, double min_width) const + { Polylines out; this->medial_axis(max_width, min_width, &out); return out; } Lines lines() const; // Number of contours (outer contour with holes). diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp index 1fe040518..91a81c7f3 100644 --- a/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/src/libslic3r/GCode/CoolingBuffer.hpp @@ -26,6 +26,8 @@ public: void reset(const Vec3d &position); void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; } std::string process_layer(std::string &&gcode, size_t layer_id, bool flush); + std::string process_layer(const std::string &gcode, size_t layer_id, bool flush) + { return this->process_layer(std::string(gcode), layer_id, flush); } private: CoolingBuffer& operator=(const CoolingBuffer&) = delete; diff --git a/src/libslic3r/Geometry/ConvexHull.hpp b/src/libslic3r/Geometry/ConvexHull.hpp index 9e9088f1e..94f4e4cf2 100644 --- a/src/libslic3r/Geometry/ConvexHull.hpp +++ b/src/libslic3r/Geometry/ConvexHull.hpp @@ -1,6 +1,8 @@ #ifndef slic3r_Geometry_ConvexHull_hpp_ #define slic3r_Geometry_ConvexHull_hpp_ +#include + #include "../Polygon.hpp" namespace Slic3r { diff --git a/t/clean_polylines.t b/t/clean_polylines.t deleted file mode 100644 index 50c6f5bbd..000000000 --- a/t/clean_polylines.t +++ /dev/null @@ -1,83 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan tests => 6; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; - -{ - my $polyline = Slic3r::Polyline->new( - [0,0],[1,0],[2,0],[2,1],[2,2],[1,2],[0,2],[0,1],[0,0], - ); - $polyline->simplify(1); - is_deeply $polyline->pp, [ [0, 0], [2, 0], [2, 2], [0, 2], [0, 0] ], 'Douglas-Peucker'; -} - -{ - my $polyline = Slic3r::Polyline->new( - [0,0], [50,50], [100,0], [125,-25], [150,50], - ); - $polyline->simplify(25); - is_deeply $polyline->pp, [ [0, 0], [50, 50], [125, -25], [150, 50] ], 'Douglas-Peucker'; -} - -{ - my $gear = Slic3r::Polygon->new_scale( - [144.9694,317.1543], [145.4181,301.5633], [146.3466,296.921], [131.8436,294.1643], [131.7467,294.1464], - [121.7238,291.5082], [117.1631,290.2776], [107.9198,308.2068], [100.1735,304.5101], [104.9896,290.3672], - [106.6511,286.2133], [93.453,279.2327], [81.0065,271.4171], [67.7886,286.5055], [60.7927,280.1127], - [69.3928,268.2566], [72.7271,264.9224], [61.8152,253.9959], [52.2273,242.8494], [47.5799,245.7224], - [34.6577,252.6559], [30.3369,245.2236], [42.1712,236.3251], [46.1122,233.9605], [43.2099,228.4876], - [35.0862,211.5672], [33.1441,207.0856], [13.3923,212.1895], [10.6572,203.3273], [6.0707,204.8561], - [7.2775,204.4259], [29.6713,196.3631], [25.9815,172.1277], [25.4589,167.2745], [19.8337,167.0129], - [5.0625,166.3346], [5.0625,156.9425], [5.3701,156.9282], [21.8636,156.1628], [25.3713,156.4613], - [25.4243,155.9976], [29.3432,155.8157], [30.3838,149.3549], [26.3596,147.8137], [27.1085,141.2604], - [29.8466,126.8337], [24.5841,124.9201], [10.6664,119.8989], [13.4454,110.9264], [33.1886,116.0691], - [38.817,103.1819], [45.8311,89.8133], [30.4286,76.81], [35.7686,70.0812], [48.0879,77.6873], - [51.564,81.1635], [61.9006,69.1791], [72.3019,58.7916], [60.5509,42.5416], [68.3369,37.1532], - [77.9524,48.1338], [80.405,52.2215], [92.5632,44.5992], [93.0123,44.3223], [106.3561,37.2056], - [100.8631,17.4679], [108.759,14.3778], [107.3148,11.1283], [117.0002,32.8627], [140.9109,27.3974], - [145.7004,26.4994], [145.1346,6.1011], [154.502,5.4063], [156.9398,25.6501], [171.0557,26.2017], - [181.3139,27.323], [186.2377,27.8532], [191.6031,8.5474], [200.6724,11.2756], [197.2362,30.2334], - [220.0789,39.1906], [224.3261,41.031], [236.3506,24.4291], [243.6897,28.6723], [234.2956,46.7747], - [245.6562,55.1643], [257.2523,65.0901], [261.4374,61.5679], [273.1709,52.8031], [278.555,59.5164], - [268.4334,69.8001], [264.1615,72.3633], [268.2763,77.9442], [278.8488,93.5305], [281.4596,97.6332], - [286.4487,95.5191], [300.2821,90.5903], [303.4456,98.5849], [286.4523,107.7253], [293.7063,131.1779], - [294.9748,135.8787], [314.918,133.8172], [315.6941,143.2589], [300.9234,146.1746], [296.6419,147.0309], - [297.1839,161.7052], [296.6136,176.3942], [302.1147,177.4857], [316.603,180.3608], [317.1658,176.7341], - [315.215,189.6589], [315.1749,189.6548], [294.9411,187.5222], [291.13,201.7233], [286.2615,215.5916], - [291.1944,218.2545], [303.9158,225.1271], [299.2384,233.3694], [285.7165,227.6001], [281.7091,225.1956], - [273.8981,237.6457], [268.3486,245.2248], [267.4538,246.4414], [264.8496,250.0221], [268.6392,253.896], - [278.5017,265.2131], [272.721,271.4403], [257.2776,258.3579], [234.4345,276.5687], [242.6222,294.8315], - [234.9061,298.5798], [227.0321,286.2841], [225.2505,281.8301], [211.5387,287.8187], [202.3025,291.0935], - [197.307,292.831], [199.808,313.1906], [191.5298,315.0787], [187.3082,299.8172], [186.4201,295.3766], - [180.595,296.0487], [161.7854,297.4248], [156.8058,297.6214], [154.3395,317.8592], - ); - - my $num_points = scalar @$gear; - my $simplified = $gear->simplify(1000); - ok @$simplified == 1, 'gear simplified to a single polygon'; - ###note sprintf "original points: %d\nnew points: %d", $num_points, scalar(@{$simplified->[0]}); - ok @{$simplified->[0]} < $num_points, 'gear was further simplified using Douglas-Peucker'; -} - -{ - - my $hole_in_square = Slic3r::Polygon->new( # cw - [140, 140], - [140, 160], - [160, 160], - [160, 140], - ); - my $simplified = $hole_in_square->simplify(2); - is scalar(@$simplified), 1, 'hole simplification returns one polygon'; - ok $simplified->[0]->is_counter_clockwise, 'hole simplification turns cw polygon into ccw polygon'; -} - diff --git a/t/combineinfill.t b/t/combineinfill.t index a19e817a1..ebb430419 100644 --- a/t/combineinfill.t +++ b/t/combineinfill.t @@ -107,60 +107,4 @@ plan tests => 8; 'infill combination is idempotent'; } -# the following needs to be adapted to the new API -if (0) { - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('solid_layers', 0); - $config->set('bottom_solid_layers', 0); - $config->set('top_solid_layers', 0); - $config->set('infill_every_layers', 6); - $config->set('layer_height', 0.06); - $config->set('perimeters', 1); - - my $test = sub { - my ($shift) = @_; - - my $self = Slic3r::Test::init_print('20mm_cube', config => $config); - - $shift /= &Slic3r::SCALING_FACTOR; - my $scale = 4; # make room for fat infill lines with low layer height - - # Put a slope on the box's sides by shifting x and y coords by $tilt * (z / boxheight). - # The test here is to put such a slight slope on the walls that it should - # not trigger any extra fill on fill layers that should be empty when - # combine infill is enabled. - $_->[0] += $shift * ($_->[2] / (20 / &Slic3r::SCALING_FACTOR)) for @{$self->objects->[0]->meshes->[0]->vertices}; - $_->[1] += $shift * ($_->[2] / (20 / &Slic3r::SCALING_FACTOR)) for @{$self->objects->[0]->meshes->[0]->vertices}; - $_ = [$_->[0]*$scale, $_->[1]*$scale, $_->[2]] for @{$self->objects->[0]->meshes->[0]->vertices}; - - # copy of Print::export_gcode() up to the point - # after fill surfaces are combined - $_->slice for @{$self->objects}; - $_->make_perimeters for @{$self->objects}; - $_->detect_surfaces_type for @{$self->objects}; - $_->prepare_fill_surfaces for map @{$_->regions}, map @{$_->layers}, @{$self->objects}; - $_->process_external_surfaces for map @{$_->regions}, map @{$_->layers}, @{$self->objects}; - $_->discover_horizontal_shells for @{$self->objects}; - $_->combine_infill for @{$self->objects}; - - # Only layers with id % 6 == 0 should have fill. - my $spurious_infill = 0; - foreach my $layer (map @{$_->layers}, @{$self->objects}) { - ++$spurious_infill if ($layer->id % 6 && grep @{$_->fill_surfaces} > 0, @{$layer->regions}); - } - - $spurious_infill -= scalar(@{$self->objects->[0]->layers} - 1) % 6; - - fail "spurious fill surfaces found on layers that should have none (walls " . sprintf("%.4f", Slic3r::Geometry::rad2deg(atan2($shift, 20/&Slic3r::SCALING_FACTOR))) . " degrees off vertical)" - unless $spurious_infill == 0; - 1; - }; - - # Test with mm skew offsets for the top of the 20mm-high box - for my $shift (0, 0.0001, 1) { - ok $test->($shift), "no spurious fill surfaces with box walls " . sprintf("%.4f",Slic3r::Geometry::rad2deg(atan2($shift, 20))) . " degrees off of vertical"; - } -} - __END__ diff --git a/t/config.t b/t/config.t deleted file mode 100644 index f4a1867de..000000000 --- a/t/config.t +++ /dev/null @@ -1,20 +0,0 @@ -use Test::More tests => 1; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; -use Slic3r::Test; - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('perimeter_extrusion_width', '250%'); - ok $config->validate, 'percent extrusion width is validated'; -} - -__END__ diff --git a/t/cooling.t b/t/cooling.t deleted file mode 100644 index e46cfa2f7..000000000 --- a/t/cooling.t +++ /dev/null @@ -1,214 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan tests => 14; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(none all); -use Slic3r; -use Slic3r::Test; - -my $gcodegen; -sub buffer { - my $config = shift; - if (defined($config)) { - $config = $config->clone(); - } else { - $config = Slic3r::Config->new; - } - my $config_override = shift; - foreach my $key (keys %{$config_override}) { - $config->set($key, ${$config_override}{$key}); - } - - my $print_config = Slic3r::Config::Print->new; - $print_config->apply_dynamic($config); - - $gcodegen = Slic3r::GCode->new; - $gcodegen->apply_print_config($print_config); - $gcodegen->set_layer_count(10); - - my $extruders_ref = shift; - $extruders_ref = [ 0 ] if !defined $extruders_ref; - $gcodegen->set_extruders($extruders_ref); - return Slic3r::GCode::CoolingBuffer->new($gcodegen); -} - -my $gcode1 = "G1 X100 E1 F3000\n"; -my $print_time1 = 100 / (3000 / 60); # 2 sec -my $gcode2 = $gcode1 . "G1 X0 E1 F3000\n"; -my $print_time2 = 2 * $print_time1; # 4 sec - -my $config = Slic3r::Config::new_from_defaults; -# Default cooling settings. -$config->set('bridge_fan_speed', [ 100 ]); -$config->set('cooling', [ 1 ]); -$config->set('fan_always_on', [ 0 ]); -$config->set('fan_below_layer_time', [ 60 ]); -$config->set('max_fan_speed', [ 100 ]); -$config->set('min_print_speed', [ 10 ]); -$config->set('slowdown_below_layer_time', [ 5 ]); -# Default print speeds. -$config->set('bridge_speed', 60); -$config->set('external_perimeter_speed', '50%'); -$config->set('first_layer_speed', 30); -$config->set('gap_fill_speed', 20); -$config->set('infill_speed', 80); -$config->set('perimeter_speed', 60); -$config->set('small_perimeter_speed', 15); -$config->set('solid_infill_speed', 20); -$config->set('top_solid_infill_speed', 15); -$config->set('max_print_speed', 80); -# Override for tests. -$config->set('disable_fan_first_layers', [ 0 ]); - -{ - my $gcode_src = "G1 F3000;_EXTRUDE_SET_SPEED\nG1 X100 E1"; - # Print time of $gcode. - my $print_time = 100 / (3000 / 60); - my $buffer = buffer($config, { 'slowdown_below_layer_time' => [ $print_time * 0.999 ] }); - my $gcode = $buffer->process_layer($gcode_src, 0); - like $gcode, qr/F3000/, 'speed is not altered when elapsed time is greater than slowdown threshold'; -} - -{ - my $gcode_src = - "G1 X50 F2500\n" . - "G1 F3000;_EXTRUDE_SET_SPEED\n" . - "G1 X100 E1\n" . - ";_EXTRUDE_END\n" . - "G1 E4 F400", - # Print time of $gcode. - my $print_time = 50 / (2500 / 60) + 100 / (3000 / 60) + 4 / (400 / 60); - my $buffer = buffer($config, { 'slowdown_below_layer_time' => [ $print_time * 1.001 ] }); - my $gcode = $buffer->process_layer($gcode_src, 0); - unlike $gcode, qr/F3000/, 'speed is altered when elapsed time is lower than slowdown threshold'; - like $gcode, qr/F2500/, 'speed is not altered for travel moves'; - like $gcode, qr/F400/, 'speed is not altered for extruder-only moves'; -} - -{ - my $buffer = buffer($config, { - 'fan_below_layer_time' => [ $print_time1 * 0.88 ], - 'slowdown_below_layer_time' => [ $print_time1 * 0.99 ] - }); - my $gcode = $buffer->process_layer($gcode1, 0); - unlike $gcode, qr/M106/, 'fan is not activated when elapsed time is greater than fan threshold'; -} - -{ - my $gcode .= buffer($config, { 'slowdown_below_layer_time', [ $print_time2 * 0.99 ] })->process_layer($gcode2, 0); - like $gcode, qr/F3000/, 'slowdown is computed on all objects printing at the same Z'; -} - -{ - # use an elapsed time which is < the threshold but greater than it when summed twice - my $buffer = buffer($config, { - 'fan_below_layer_time' => [ $print_time2 * 0.65], - 'slowdown_below_layer_time' => [ $print_time2 * 0.7 ] - }); - my $gcode = $buffer->process_layer($gcode2, 0) . - $buffer->process_layer($gcode2, 1); - unlike $gcode, qr/M106/, 'fan is not activated on all objects printing at different Z'; -} - -{ - # use an elapsed time which is < the threshold even when summed twice - my $buffer = buffer($config, { - 'fan_below_layer_time' => [ $print_time2 + 1 ], - 'slowdown_below_layer_time' => [ $print_time2 + 2 ] - }); - my $gcode = $buffer->process_layer($gcode2, 0) . - $buffer->process_layer($gcode2, 1); - like $gcode, qr/M106/, 'fan is activated on all objects printing at different Z'; -} - -{ - my $buffer = buffer($config, { - 'cooling' => [ 1 , 0 ], - 'fan_below_layer_time' => [ $print_time2 + 1, $print_time2 + 1 ], - 'slowdown_below_layer_time' => [ $print_time2 + 2, $print_time2 + 2 ] - }, - [ 0, 1]); - my $gcode = $buffer->process_layer($gcode1 . "T1\nG1 X0 E1 F3000\n", 0); - like $gcode, qr/^M106/, 'fan is activated for the 1st tool'; - like $gcode, qr/.*M107/, 'fan is disabled for the 2nd tool'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('cooling', [ 1 ]); - $config->set('bridge_fan_speed', [ 100 ]); - $config->set('fan_below_layer_time', [ 0 ]); - $config->set('slowdown_below_layer_time', [ 0 ]); - $config->set('bridge_speed', 99); - $config->set('top_solid_layers', 1); # internal bridges use solid_infil speed - $config->set('bottom_solid_layers', 1); # internal bridges use solid_infil speed - - my $print = Slic3r::Test::init_print('overhang', config => $config); - my $fan = 0; - my $fan_with_incorrect_speeds = my $fan_with_incorrect_print_speeds = 0; - my $bridge_with_no_fan = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'M106') { - $fan = $args->{S}; - $fan_with_incorrect_speeds++ if $fan != 255; - } elsif ($cmd eq 'M107') { - $fan = 0; - } elsif ($info->{extruding} && $info->{dist_XY} > 0) { - $fan_with_incorrect_print_speeds++ - if ($fan > 0) && ($args->{F} // $self->F) != 60*$config->bridge_speed; - $bridge_with_no_fan++ - if !$fan && ($args->{F} // $self->F) == 60*$config->bridge_speed; - } - }); - ok !$fan_with_incorrect_speeds, 'bridge fan speed is applied correctly'; - ok !$fan_with_incorrect_print_speeds, 'bridge fan is only turned on for bridges'; - ok !$bridge_with_no_fan, 'bridge fan is turned on for all bridges'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('cooling', [ 1 ]); - $config->set('fan_below_layer_time', [ 0 ]); - $config->set('slowdown_below_layer_time', [ 10 ]); - $config->set('min_print_speed', [ 0 ]); - $config->set('start_gcode', ''); - $config->set('first_layer_speed', '100%'); - $config->set('external_perimeter_speed', 99); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my @layer_times = (0); # in seconds - my %layer_external = (); # z => 1 - Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1') { - if ($info->{dist_Z}) { - push @layer_times, 0; - $layer_external{ $args->{Z} } = 0; - } - $layer_times[-1] += abs($info->{dist_XY} || $info->{dist_E} || $info->{dist_Z} || 0) / ($args->{F} // $self->F) * 60; - if ($args->{F} && $args->{F} == $config->external_perimeter_speed*60) { - $layer_external{ $self->Z }++; - } - } - }); - @layer_times = grep $_, @layer_times; - my $all_below = none { $_ < $config->slowdown_below_layer_time->[0] } @layer_times; - ok $all_below, 'slowdown_below_layer_time is honored'; - - # check that all layers have at least one unaltered external perimeter speed -# my $external = all { $_ > 0 } values %layer_external; -# ok $external, 'slowdown_below_layer_time does not alter external perimeters'; -} - -__END__ diff --git a/t/flow.t b/t/flow.t deleted file mode 100644 index 50c491604..000000000 --- a/t/flow.t +++ /dev/null @@ -1,83 +0,0 @@ -use Test::More tests => 6; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first sum); -use Slic3r; -use Slic3r::Geometry qw(scale PI); -use Slic3r::Test; - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 1); - $config->set('brim_width', 2); - $config->set('perimeters', 3); - $config->set('fill_density', 0.4); - $config->set('bottom_solid_layers', 1); - $config->set('first_layer_extrusion_width', 2); - $config->set('first_layer_height', $config->layer_height); - $config->set('filament_diameter', [ 3.0 ]); - $config->set('nozzle_diameter', [ 0.5 ]); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my @E_per_mm = (); - Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($self->Z == $config->layer_height) { # only consider first layer - if ($info->{extruding} && $info->{dist_XY} > 0) { - push @E_per_mm, $info->{dist_E} / $info->{dist_XY}; - } - } - }); - my $E_per_mm_avg = sum(@E_per_mm) / @E_per_mm; - # allow some tolerance because solid rectilinear infill might be adjusted/stretched - ok !(defined first { abs($_ - $E_per_mm_avg) > 0.015 } @E_per_mm), - 'first_layer_extrusion_width applies to everything on first layer'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('bridge_speed', 99); - $config->set('bridge_flow_ratio', 1); - $config->set('cooling', [ 0 ]); # to prevent speeds from being altered - $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered - - my $test = sub { - my $print = Slic3r::Test::init_print('overhang', config => $config); - my @E_per_mm = (); - Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding} && $info->{dist_XY} > 0) { - if (($args->{F} // $self->F) == $config->bridge_speed*60) { - push @E_per_mm, $info->{dist_E} / $info->{dist_XY}; - } - } - }); - my $expected_mm3_per_mm = ($config->nozzle_diameter->[0]**2) * PI/4 * $config->bridge_flow_ratio; - my $expected_E_per_mm = $expected_mm3_per_mm / ((($config->filament_diameter->[0]/2)**2)*PI); - ok !(defined first { abs($_ - $expected_E_per_mm) > 0.01 } @E_per_mm), - 'expected flow when using bridge_flow_ratio = ' . $config->bridge_flow_ratio; - }; - - $config->set('bridge_flow_ratio', 0.5); - $test->(); - $config->set('bridge_flow_ratio', 2); - $test->(); - $config->set('extrusion_width', 0.4); - $config->set('bridge_flow_ratio', 1); - $test->(); - $config->set('bridge_flow_ratio', 0.5); - $test->(); - $config->set('bridge_flow_ratio', 2); - $test->(); -} - -__END__ diff --git a/t/gaps.t b/t/gaps.t deleted file mode 100644 index 2594ac087..000000000 --- a/t/gaps.t +++ /dev/null @@ -1,59 +0,0 @@ -use Test::More tests => 1; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first); -use Slic3r; -use Slic3r::Geometry qw(PI scale unscale convex_hull); -use Slic3r::Surface ':types'; -use Slic3r::Test; - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeter_speed', 66); - $config->set('external_perimeter_speed', 66); - $config->set('small_perimeter_speed', 66); - $config->set('gap_fill_speed', 99); - $config->set('perimeters', 1); - $config->set('cooling', [ 0 ]); # to prevent speeds from being altered - $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered - $config->set('perimeter_extrusion_width', 0.35); - $config->set('first_layer_extrusion_width', 0.35); - - my $print = Slic3r::Test::init_print('two_hollow_squares', config => $config); - my @perimeter_points = (); - my $last = ''; # perimeter | gap - my $gap_fills_outside_last_perimeters = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding} && $info->{dist_XY} > 0) { - my $F = $args->{F} // $self->F; - my $point = Slic3r::Point->new_scale($info->{new_X}, $info->{new_Y}); - if ($F == $config->perimeter_speed*60) { - if ($last eq 'gap') { - @perimeter_points = (); - } - push @perimeter_points, $point; - $last = 'perimeter'; - } elsif ($F == $config->gap_fill_speed*60) { - my $convex_hull = convex_hull(\@perimeter_points); - if (!$convex_hull->contains_point($point)) { - $gap_fills_outside_last_perimeters++; - } - - $last = 'gap'; - } - } - }); - is $gap_fills_outside_last_perimeters, 0, 'gap fills are printed before leaving islands'; -} - -__END__ diff --git a/t/gcode.t b/t/gcode.t index b95505e43..902c40b83 100644 --- a/t/gcode.t +++ b/t/gcode.t @@ -1,4 +1,4 @@ -use Test::More tests => 24; +use Test::More tests => 23; use strict; use warnings; @@ -13,13 +13,6 @@ use Slic3r; use Slic3r::Geometry qw(scale convex_hull); use Slic3r::Test; -{ - my $gcodegen = Slic3r::GCode->new(); - $gcodegen->set_layer_count(1); - $gcodegen->set_origin(Slic3r::Pointf->new(10, 10)); - is_deeply $gcodegen->last_pos->arrayref, [scale -10, scale -10], 'last_pos is shifted correctly'; -} - { my $config = Slic3r::Config::new_from_defaults; $config->set('wipe', [1]); diff --git a/t/loops.t b/t/loops.t deleted file mode 100644 index e662469ca..000000000 --- a/t/loops.t +++ /dev/null @@ -1,57 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan skip_all => 'temporarily disabled'; -plan tests => 4; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; -use Slic3r::Test; - -{ - # We only need to slice at one height, so we'll build a non-manifold mesh - # that still produces complete loops at that height. Triangular walls are - # enough for this purpose. - # Basically we want to check what happens when three concentric loops happen - # to be at the same height, the two external ones being ccw and the other being - # a hole, thus cw. - my (@vertices, @facets) = (); - Slic3r::Test::add_facet($_, \@vertices, \@facets) for - # external surface below the slicing Z - [ [0,0,0], [20,0,10], [0,0,10] ], - [ [20,0,0], [20,20,10], [20,0,10] ], - [ [20,20,0], [0,20,10], [20,20,10] ], - [ [0,20,0], [0,0,10], [0,20,10] ], - - # external insetted surface above the slicing Z - [ [2,2,10], [18,2,10], [2,2,20] ], - [ [18,2,10], [18,18,10], [18,2,20] ], - [ [18,18,10], [2,18,10], [18,18,20] ], - [ [2,18,10], [2,2,10], [2,18,20] ], - - # insetted hole below the slicing Z - [ [15,5,0], [5,5,10], [15,5,10] ], - [ [15,15,0], [15,5,10], [15,15,10] ], - [ [5,15,0], [15,15,10], [5,15,10] ], - [ [5,5,0], [5,15,10], [5,5,10] ]; - - my $mesh = Slic3r::TriangleMesh->new; - $mesh->ReadFromPerl(\@vertices, \@facets); - $mesh->analyze; - my @lines = map $mesh->intersect_facet($_, 10), 0..$#facets; - my $loops = Slic3r::TriangleMesh::make_loops(\@lines); - is scalar(@$loops), 3, 'correct number of loops detected'; - is scalar(grep $_->is_counter_clockwise, @$loops), 2, 'correct number of ccw loops detected'; - - my @surfaces = Slic3r::Layer::Region::_merge_loops($loops, 0); - is scalar(@surfaces), 1, 'one surface detected'; - is scalar(@{$surfaces[0]->expolygon})-1, 1, 'surface has one hole'; -} - -__END__ diff --git a/t/thin.t b/t/thin.t deleted file mode 100644 index 50e7abc95..000000000 --- a/t/thin.t +++ /dev/null @@ -1,185 +0,0 @@ -use Test::More tests => 23; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; -use List::Util qw(first sum none); -use Slic3r::Geometry qw(epsilon scale unscale scaled_epsilon Y); -use Slic3r::Test; - -# Disable this until a more robust implementation is provided. It currently -# fails on Linux 32bit because some spurious extrudates are generated. -if (0) { - my $config = Slic3r::Config::new_from_defaults; - $config->set('layer_height', 0.2); - $config->set('first_layer_height', $config->layer_height); - $config->set('extrusion_width', 0.5); - $config->set('first_layer_extrusion_width', '200%'); # check this one too - $config->set('skirts', 0); - $config->set('thin_walls', 1); - - my $print = Slic3r::Test::init_print('gt2_teeth', config => $config); - - my %extrusion_paths = (); # Z => count of continuous extrusions - my $extruding = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1') { - if ($info->{extruding} && $info->{dist_XY}) { - if (!$extruding) { - $extrusion_paths{$self->Z} //= 0; - $extrusion_paths{$self->Z}++; - } - $extruding = 1; - } else { - $extruding = 0; - } - } - }); - - ok !(first { $_ != 3 } values %extrusion_paths), - 'no superfluous thin walls are generated for toothed profile'; -} - -{ - my $square = Slic3r::Polygon->new_scale( # ccw - [100, 100], - [200, 100], - [200, 200], - [100, 200], - ); - my $hole_in_square = Slic3r::Polygon->new_scale( # cw - [140, 140], - [140, 160], - [160, 160], - [160, 140], - ); - my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); - my $res = $expolygon->medial_axis(scale 40, scale 0.5); - is scalar(@$res), 1, 'medial axis of a square shape is a single path'; - isa_ok $res->[0], 'Slic3r::Polyline', 'medial axis result is a polyline'; - ok $res->[0]->first_point->coincides_with($res->[0]->last_point), 'polyline forms a closed loop'; - ok $res->[0]->length > $hole_in_square->length && $res->[0]->length < $square->length, - 'medial axis loop has reasonable length'; -} - -{ - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( - [100, 100], - [120, 100], - [120, 200], - [100, 200], - )); - my $res = $expolygon->medial_axis(scale 20, scale 0.5); - is scalar(@$res), 1, 'medial axis of a narrow rectangle is a single line'; - ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length'; - - $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( - [100, 100], - [120, 100], - [120, 200], - [105, 200], # extra point in the short side - [100, 200], - )); - my $res2 = $expolygon->medial_axis(scale 1, scale 0.5); - is scalar(@$res), 1, 'medial axis of a narrow rectangle with an extra vertex is still a single line'; - ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has still a reasonable length'; - ok !(grep { abs($_ - scale 150) < scaled_epsilon } map $_->[Y], map @$_, @$res2), "extra vertices don't influence medial axis"; -} - -{ - my $expolygon = Slic3r::ExPolygon->new( - Slic3r::Polygon->new([1185881,829367],[1421988,1578184],[1722442,2303558],[2084981,2999998],[2506843,3662186],[2984809,4285086],[3515250,4863959],[4094122,5394400],[4717018,5872368],[5379210,6294226],[6075653,6656769],[6801033,6957229],[7549842,7193328],[8316383,7363266],[9094809,7465751],[9879211,7500000],[10663611,7465750],[11442038,7363265],[12208580,7193327],[12957389,6957228],[13682769,6656768],[14379209,6294227],[15041405,5872366],[15664297,5394401],[16243171,4863960],[16758641,4301424],[17251579,3662185],[17673439,3000000],[18035980,2303556],[18336441,1578177],[18572539,829368],[18750748,0],[19758422,0],[19727293,236479],[19538467,1088188],[19276136,1920196],[18942292,2726179],[18539460,3499999],[18070731,4235755],[17539650,4927877],[16950279,5571067],[16307090,6160437],[15614974,6691519],[14879209,7160248],[14105392,7563079],[13299407,7896927],[12467399,8159255],[11615691,8348082],[10750769,8461952],[9879211,8500000],[9007652,8461952],[8142729,8348082],[7291022,8159255],[6459015,7896927],[5653029,7563079],[4879210,7160247],[4143447,6691519],[3451331,6160437],[2808141,5571066],[2218773,4927878],[1687689,4235755],[1218962,3499999],[827499,2748020],[482284,1920196],[219954,1088186],[31126,236479],[0,0],[1005754,0]), - ); - my $res = $expolygon->medial_axis(scale 1.324888, scale 0.25); - is scalar(@$res), 1, 'medial axis of a semicircumference is a single line'; - - # check whether turns are all CCW or all CW - my @lines = @{$res->[0]->lines}; - my @angles = map { $lines[$_-1]->ccw($lines[$_]->b) } 1..$#lines; - ok !!(none { $_ < 0 } @angles) || (none { $_ > 0 } @angles), - 'all medial axis segments of a semicircumference have the same orientation'; -} - -{ - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( - [100, 100], - [120, 100], - [112, 200], - [108, 200], - )); - my $res = $expolygon->medial_axis(scale 20, scale 0.5); - is scalar(@$res), 1, 'medial axis of a narrow trapezoid is a single line'; - ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length'; -} - -{ - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( - [100, 100], - [120, 100], - [120, 180], - [200, 180], - [200, 200], - [100, 200], - )); - my $res = $expolygon->medial_axis(scale 20, scale 0.5); - is scalar(@$res), 1, 'medial axis of a L shape is a single polyline'; - my $len = unscale($res->[0]->length) + 20; # 20 is the thickness of the expolygon, which is subtracted from the ends - ok $len > 80*2 && $len < 100*2, 'medial axis has reasonable length'; -} - -{ - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( - [-203064906,-51459966],[-219312231,-51459966],[-219335477,-51459962],[-219376095,-51459962],[-219412047,-51459966], - [-219572094,-51459966],[-219624814,-51459962],[-219642183,-51459962],[-219656665,-51459966],[-220815482,-51459966], - [-220815482,-37738966],[-221117540,-37738966],[-221117540,-51762024],[-203064906,-51762024], - )); - my $polylines = $expolygon->medial_axis(819998, 102499.75); - - my $perimeter = $expolygon->contour->split_at_first_point->length; - ok sum(map $_->length, @$polylines) > $perimeter/2/4*3, 'medial axis has a reasonable length'; -} - -{ - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( - [50, 100], - [1000, 102], - [50, 104], - )); - my $res = $expolygon->medial_axis(scale 4, scale 0.5); - is scalar(@$res), 1, 'medial axis of a narrow triangle is a single line'; - ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length'; -} - -{ - # GH #2474 - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( - [91294454,31032190],[11294481,31032190],[11294481,29967810],[44969182,29967810],[89909960,29967808],[91294454,29967808] - )); - my $polylines = $expolygon->medial_axis(1871238, 500000); - is scalar(@$polylines), 1, 'medial axis is a single polyline'; - my $polyline = $polylines->[0]; - - my $expected_y = $expolygon->bounding_box->center->y; #;; - ok abs(sum(map $_->y, @$polyline) / @$polyline - $expected_y) < scaled_epsilon, #,, - 'medial axis is horizontal and is centered'; - - # order polyline from left to right - $polyline->reverse if $polyline->first_point->x > $polyline->last_point->x; - - my $polyline_bb = $polyline->bounding_box; - is $polyline->first_point->x, $polyline_bb->x_min, 'expected x_min'; - is $polyline->last_point->x, $polyline_bb->x_max, 'expected x_max'; - - is_deeply [ map $_->x, @$polyline ], [ sort map $_->x, @$polyline ], - 'medial axis is not self-overlapping'; -} - -__END__ diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index 9e039c913..4e8821287 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -3,11 +3,14 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests.cpp test_avoid_crossing_perimeters.cpp test_bridges.cpp + test_cooling.cpp + test_custom_gcode.cpp test_data.cpp test_data.hpp test_extrusion_entity.cpp test_fill.cpp test_flow.cpp + test_gaps.cpp test_gcode.cpp test_gcodefindreplace.cpp test_gcodewriter.cpp @@ -19,6 +22,7 @@ add_executable(${_TEST_NAME}_tests test_printobject.cpp test_skirt_brim.cpp test_support_material.cpp + test_thin_walls.cpp test_trianglemesh.cpp ) target_link_libraries(${_TEST_NAME}_tests test_common libslic3r) diff --git a/tests/fff_print/test_cooling.cpp b/tests/fff_print/test_cooling.cpp new file mode 100644 index 000000000..f743783b1 --- /dev/null +++ b/tests/fff_print/test_cooling.cpp @@ -0,0 +1,274 @@ +#include + +#include +#include + +#include "test_data.hpp" // get access to init_print, etc + +#include "libslic3r/Config.hpp" +#include "libslic3r/GCode.hpp" +#include "libslic3r/GCodeReader.hpp" +#include "libslic3r/GCode/CoolingBuffer.hpp" +#include "libslic3r/libslic3r.h" + +using namespace Slic3r; + +std::unique_ptr make_cooling_buffer( + GCode &gcode, + const DynamicPrintConfig &config = DynamicPrintConfig{}, + const std::vector &extruder_ids = { 0 }) +{ + PrintConfig print_config; + print_config.apply(config, true); // ignore_nonexistent + gcode.apply_print_config(print_config); + gcode.set_layer_count(10); + gcode.writer().set_extruders(extruder_ids); + gcode.writer().set_extruder(0); + return std::make_unique(gcode); +} + +SCENARIO("Cooling unit tests", "[Cooling]") { + const std::string gcode1 = "G1 X100 E1 F3000\n"; + // 2 sec + const double print_time1 = 100. / (3000. / 60.); + const std::string gcode2 = gcode1 + "G1 X0 E1 F3000\n"; + // 4 sec + const double print_time2 = 2. * print_time1; + + auto config = DynamicPrintConfig::full_print_config_with({ + // Default cooling settings. + { "bridge_fan_speed", "100" }, + { "cooling", "1" }, + { "fan_always_on", "0" }, + { "fan_below_layer_time", "60" }, + { "max_fan_speed", "100" }, + { "min_print_speed", "10" }, + { "slowdown_below_layer_time", "5" }, + // Default print speeds. + { "bridge_speed", 60 }, + { "external_perimeter_speed", "50%" }, + { "first_layer_speed", 30 }, + { "gap_fill_speed", 20 }, + { "infill_speed", 80 }, + { "perimeter_speed", 60 }, + { "small_perimeter_speed", 15 }, + { "solid_infill_speed", 20 }, + { "top_solid_infill_speed", 15 }, + { "max_print_speed", 80 }, + // Override for tests. + { "disable_fan_first_layers", "0" } + }); + + WHEN("G-code block 3") { + THEN("speed is not altered when elapsed time is greater than slowdown threshold") { + // Print time of gcode. + const double print_time = 100. / (3000. / 60.); + //FIXME slowdown_below_layer_time is rounded down significantly from 1.8s to 1s. + config.set_deserialize_strict({ { "slowdown_below_layer_time", { int(print_time * 0.999) } } }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config); + std::string gcode = buffer->process_layer("G1 F3000;_EXTRUDE_SET_SPEED\nG1 X100 E1", 0, true); + bool speed_not_altered = gcode.find("F3000") != gcode.npos; + REQUIRE(speed_not_altered); + } + } + + WHEN("G-code block 4") { + const std::string gcode_src = + "G1 X50 F2500\n" + "G1 F3000;_EXTRUDE_SET_SPEED\n" + "G1 X100 E1\n" + ";_EXTRUDE_END\n" + "G1 E4 F400"; + // Print time of gcode. + const double print_time = 50. / (2500. / 60.) + 100. / (3000. / 60.) + 4. / (400. / 60.); + config.set_deserialize_strict({ { "slowdown_below_layer_time", { int(print_time * 1.001) } } }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config); + std::string gcode = buffer->process_layer(gcode_src, 0, true); + THEN("speed is altered when elapsed time is lower than slowdown threshold") { + bool speed_is_altered = gcode.find("F3000") == gcode.npos; + REQUIRE(speed_is_altered); + } + THEN("speed is not altered for travel moves") { + bool speed_not_altered = gcode.find("F2500") != gcode.npos; + REQUIRE(speed_not_altered); + } + THEN("speed is not altered for extruder-only moves") { + bool speed_not_altered = gcode.find("F400") != gcode.npos; + REQUIRE(speed_not_altered); + } + } + + WHEN("G-code block 1") { + THEN("fan is not activated when elapsed time is greater than fan threshold") { + config.set_deserialize_strict({ + { "fan_below_layer_time" , int(print_time1 * 0.88) }, + { "slowdown_below_layer_time" , int(print_time1 * 0.99) } + }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config); + std::string gcode = buffer->process_layer(gcode1, 0, true); + bool fan_not_activated = gcode.find("M106") == gcode.npos; + REQUIRE(fan_not_activated); + } + } + WHEN("G-code block 1 with two extruders") { + config.set_deserialize_strict({ + { "cooling", "1, 0" }, + { "fan_below_layer_time", { int(print_time2 + 1.), int(print_time2 + 1.) } }, + { "slowdown_below_layer_time", { int(print_time2 + 2.), int(print_time2 + 2.) } } + }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config, { 0, 1 }); + std::string gcode = buffer->process_layer(gcode1 + "T1\nG1 X0 E1 F3000\n", 0, true); + THEN("fan is activated for the 1st tool") { + bool ok = gcode.find("M106") == 0; + REQUIRE(ok); + } + THEN("fan is disabled for the 2nd tool") { + bool ok = gcode.find("\nM107") > 0; + REQUIRE(ok); + } + } + WHEN("G-code block 2") { + THEN("slowdown is computed on all objects printing at the same Z") { + config.set_deserialize_strict({ { "slowdown_below_layer_time", int(print_time2 * 0.99) } }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config); + std::string gcode = buffer->process_layer(gcode2, 0, true); + bool ok = gcode.find("F3000") != gcode.npos; + REQUIRE(ok); + } + THEN("fan is not activated on all objects printing at different Z") { + config.set_deserialize_strict({ + { "fan_below_layer_time", int(print_time2 * 0.65) }, + { "slowdown_below_layer_time", int(print_time2 * 0.7) } + }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config); + // use an elapsed time which is < the threshold but greater than it when summed twice + std::string gcode = buffer->process_layer(gcode2, 0, true) + buffer->process_layer(gcode2, 1, true); + bool fan_not_activated = gcode.find("M106") == gcode.npos; + REQUIRE(fan_not_activated); + } + THEN("fan is activated on all objects printing at different Z") { + // use an elapsed time which is < the threshold even when summed twice + config.set_deserialize_strict({ + { "fan_below_layer_time", int(print_time2 + 1) }, + { "slowdown_below_layer_time", int(print_time2 + 1) } + }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config); + // use an elapsed time which is < the threshold but greater than it when summed twice + std::string gcode = buffer->process_layer(gcode2, 0, true) + buffer->process_layer(gcode2, 1, true); + bool fan_activated = gcode.find("M106") != gcode.npos; + REQUIRE(fan_activated); + } + } +} + +SCENARIO("Cooling integration tests", "[Cooling]") { + GIVEN("overhang") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "cooling", { 1 } }, + { "bridge_fan_speed", { 100 } }, + { "fan_below_layer_time", { 0 } }, + { "slowdown_below_layer_time", { 0 } }, + { "bridge_speed", 99 }, + // internal bridges use solid_infil speed + { "bottom_solid_layers", 1 }, + // internal bridges use solid_infil speed + }); + + GCodeReader parser; + int fan = 0; + int fan_with_incorrect_speeds = 0; + int fan_with_incorrect_print_speeds = 0; + int bridge_with_no_fan = 0; + const double bridge_speed = config.opt_float("bridge_speed") * 60; + parser.parse_buffer( + Slic3r::Test::slice({ Slic3r::Test::TestMesh::overhang }, config), + [&fan, &fan_with_incorrect_speeds, &fan_with_incorrect_print_speeds, &bridge_with_no_fan, bridge_speed] + (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (line.cmd_is("M106")) { + line.has_value('S', fan); + if (fan != 255) + ++ fan_with_incorrect_speeds; + } else if (line.cmd_is("M107")) { + fan = 0; + } else if (line.extruding(self) && line.dist_XY(self) > 0) { + if (is_approx(line.new_F(self), bridge_speed)) { + if (fan != 255) + ++ bridge_with_no_fan; + } else { + if (fan != 0) + ++ fan_with_incorrect_print_speeds; + } + } + }); + THEN("bridge fan speed is applied correctly") { + REQUIRE(fan_with_incorrect_speeds == 0); + } + THEN("bridge fan is only turned on for bridges") { + REQUIRE(fan_with_incorrect_print_speeds == 0); + } + THEN("bridge fan is turned on for all bridges") { + REQUIRE(bridge_with_no_fan == 0); + } + } + GIVEN("20mm cube") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "cooling", { 1 } }, + { "fan_below_layer_time", { 0 } }, + { "slowdown_below_layer_time", { 10 } }, + { "min_print_speed", { 0 } }, + { "start_gcode", "" }, + { "first_layer_speed", "100%" }, + { "external_perimeter_speed", 99 } + }); + GCodeReader parser; + const double external_perimeter_speed = config.opt("external_perimeter_speed")->value * 60; + std::vector layer_times; + // z => 1 + std::map layer_external; + parser.parse_buffer( + Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config), + [&layer_times, &layer_external, external_perimeter_speed] + (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (line.cmd_is("G1")) { + if (line.dist_Z(self) != 0) { + layer_times.emplace_back(0.); + layer_external[scaled(line.new_Z(self))] = 0; + } + double l = line.dist_XY(self); + if (l == 0) + l = line.dist_E(self); + if (l == 0) + l = line.dist_Z(self); + if (l > 0.) { + if (layer_times.empty()) + layer_times.emplace_back(0.); + layer_times.back() += 60. * std::abs(l) / line.new_F(self); + } + if (line.has('F') && line.f() == external_perimeter_speed) + ++ layer_external[scaled(self.z())]; + } + }); + THEN("slowdown_below_layer_time is honored") { + // Account for some inaccuracies. + const double slowdown_below_layer_time = config.opt("slowdown_below_layer_time")->values.front() - 0.2; + size_t minimum_time_honored = std::count_if(layer_times.begin(), layer_times.end(), + [slowdown_below_layer_time](double t){ return t > slowdown_below_layer_time; }); + REQUIRE(minimum_time_honored == layer_times.size()); + } + THEN("slowdown_below_layer_time does not alter external perimeters") { + // Broken by Vojtech + // check that all layers have at least one unaltered external perimeter speed + // my $external = all { $_ > 0 } values %layer_external; + // ok $external, ''; + } + } +} diff --git a/tests/fff_print/test_custom_gcode.cpp b/tests/fff_print/test_custom_gcode.cpp new file mode 100644 index 000000000..0368b9604 --- /dev/null +++ b/tests/fff_print/test_custom_gcode.cpp @@ -0,0 +1,220 @@ +#include + +#include +#include + +#include "libslic3r/Config.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/libslic3r.h" + +#include "test_data.hpp" + +using namespace Slic3r; + +#if 0 +SCENARIO("Output file format", "[CustomGCode]") +{ + WHEN("output_file_format set") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "output_filename_format", "ts_[travel_speed]_lh_[layer_height].gcode" }, + { "start_gcode", "TRAVEL:[travel_speed] HEIGHT:[layer_height]\n" } + }); + + Print print; + Model model; + Test::init_print({ Test::TestMesh::cube_2x20x10 }, print, model, config); + + std::string output_file = print.output_filepath(); + THEN("print config options are replaced in output filename") { + output_file.find(std::string("ts_") + ) + } + my ($t, $h) = map $config->$_, qw(travel_speed layer_height); + ok $output_file =~ /ts_${t}_/, ''; + ok $output_file =~ /lh_$h\./, 'region config options are replaced in output filename'; + + std::string gcode = print.gcode(print); + THEN("print config options are replaced in custom G-code") { + ok $gcode =~ /TRAVEL:$t/, ''; + } + THEN("region config options are replaced in custom G-code") { + ok $gcode =~ /HEIGHT:$h/, ''; + } + } +} + +SCENARIO("Custom G-code", "[CustomGCode]") +{ + WHEN("start_gcode and layer_gcode set") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "start_gcode", "_MY_CUSTOM_START_GCODE_" }, // to avoid dealing with the nozzle lift in start G-code + { "layer_gcode", "_MY_CUSTOM_LAYER_GCODE_" } + }); + GCodeReader parser; + bool last_move_was_z_change = false; + int num_layer_changes_not_applied = 0; + parser.parse_buffer(Slic3r::Test::slice({ Test::TestMesh::cube_2x20x10 }, config), + [&last_move_was_z_change, &num_layer_changes_not_applied](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (line.extruding(self)) { + if (! was_extruding) + seam_points.emplace_back(self.xy_scaled()); + was_extruding = true; + } else if (! line.cmd_is("M73")) { + // skips remaining time lines (M73) + was_extruding = false; + } + if (last_move_was_z_change != line.cmd_is("_MY_CUSTOM_LAYER_GCODE_")) + ++ num_layer_changes_not_applied; + last_move_was_z_change = line.dist_Z(self) > 0; + }); + THEN("custom layer G-code is applied after Z move and before other moves"); + }; + +{ + my $config = Slic3r::Config->new; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); + $config->set('extruder', 2); + $config->set('first_layer_temperature', [200,205]); + + { + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $gcode = Slic3r::Test::gcode($print); + ok $gcode =~ /M104 S205 T1/, 'temperature set correctly for non-zero yet single extruder'; + ok $gcode !~ /M104 S\d+ T0/, 'unused extruder correctly ignored'; + } + + $config->set('infill_extruder', 1); + { + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $gcode = Slic3r::Test::gcode($print); + ok $gcode =~ /M104 S200 T0/, 'temperature set correctly for first extruder'; + ok $gcode =~ /M104 S205 T1/, 'temperature set correctly for second extruder'; + } + + my @start_gcode = (qq! +;__temp0:[first_layer_temperature_0]__ +;__temp1:[first_layer_temperature_1]__ +;__temp2:[first_layer_temperature_2]__ + !, qq! +;__temp0:{first_layer_temperature[0]}__ +;__temp1:{first_layer_temperature[1]}__ +;__temp2:{first_layer_temperature[2]}__ + !); + my @syntax_description = (' (legacy syntax)', ' (new syntax)'); + for my $i (0, 1) { + $config->set('start_gcode', $start_gcode[$i]); + { + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $gcode = Slic3r::Test::gcode($print); + # we use the [infill_extruder] placeholder to make sure this test doesn't + # catch a false positive caused by the unparsed start G-code option itself + # being embedded in the G-code + ok $gcode =~ /temp0:200/, 'temperature placeholder for first extruder correctly populated' . $syntax_description[$i]; + ok $gcode =~ /temp1:205/, 'temperature placeholder for second extruder correctly populated' . $syntax_description[$i]; + ok $gcode =~ /temp2:200/, 'temperature placeholder for unused extruder populated with first value' . $syntax_description[$i]; + } + } + + $config->set('start_gcode', qq! +;substitution:{if infill_extruder==1}extruder1 + {elsif infill_extruder==2}extruder2 + {else}extruder3{endif} + !); + { + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $gcode = Slic3r::Test::gcode($print); + ok $gcode =~ /substitution:extruder1/, 'if / else / endif - first block returned'; + } +} + +{ + my $config = Slic3r::Config::new_from_defaults; + $config->set('before_layer_gcode', ';BEFORE [layer_num]'); + $config->set('layer_gcode', ';CHANGE [layer_num]'); + $config->set('support_material', 1); + $config->set('layer_height', 0.2); + my $print = Slic3r::Test::init_print('overhang', config => $config); + my $gcode = Slic3r::Test::gcode($print); + + my @before = (); + my @change = (); + foreach my $line (split /\R+/, $gcode) { + if ($line =~ /;BEFORE (\d+)/) { + push @before, $1; + } elsif ($line =~ /;CHANGE (\d+)/) { + push @change, $1; + fail 'inconsistent layer_num before and after layer change' + if $1 != $before[-1]; + } + } + is_deeply \@before, \@change, 'layer_num is consistent before and after layer changes'; + ok !defined(first { $change[$_] != $change[$_-1]+1 } 1..$#change), + 'layer_num grows continously'; # i.e. no duplicates or regressions +} + +{ + my $config = Slic3r::Config->new; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6,0.6]); + $config->set('start_gcode', qq! +;substitution:{if infill_extruder==1}if block + {elsif infill_extruder==2}elsif block 1 + {elsif infill_extruder==3}elsif block 2 + {elsif infill_extruder==4}elsif block 3 + {else}endif block{endif} + !); + my @returned = ('', 'if block', 'elsif block 1', 'elsif block 2', 'elsif block 3', 'endif block'); + for my $i (1,2,3,4,5) { + $config->set('infill_extruder', $i); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $gcode = Slic3r::Test::gcode($print); + my $found_other = 0; + for my $j (1,2,3,4,5) { + next if $i == $j; + $found_other = 1 if $gcode =~ /substitution:$returned[$j]/; + } + ok $gcode =~ /substitution:$returned[$i]/, 'if / else / endif - ' . $returned[$i] . ' returned'; + ok !$found_other, 'if / else / endif - only ' . $returned[$i] . ' returned'; + } +} + +{ + my $config = Slic3r::Config->new; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); + $config->set('start_gcode', + ';substitution:{if infill_extruder==1}{if perimeter_extruder==1}block11{else}block12{endif}' . + '{elsif infill_extruder==2}{if perimeter_extruder==1}block21{else}block22{endif}' . + '{else}{if perimeter_extruder==1}block31{else}block32{endif}{endif}:end'); + for my $i (1,2,3) { + $config->set('infill_extruder', $i); + for my $j (1,2) { + $config->set('perimeter_extruder', $j); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $gcode = Slic3r::Test::gcode($print); + ok $gcode =~ /substitution:block$i$j:end/, "two level if / else / endif - block$i$j returned"; + } + } +} + +{ + my $config = Slic3r::Config->new; + $config->set('start_gcode', + ';substitution:{if notes=="MK2"}MK2{elsif notes=="MK3"}MK3{else}MK1{endif}:end'); + for my $printer_name ("MK2", "MK3", "MK1") { + $config->set('notes', $printer_name); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $gcode = Slic3r::Test::gcode($print); + ok $gcode =~ /substitution:$printer_name:end/, "printer name $printer_name matched"; + } +} + +{ + my $config = Slic3r::Config::new_from_defaults; + $config->set('complete_objects', 1); + $config->set('between_objects_gcode', '_MY_CUSTOM_GCODE_'); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 3); + my $gcode = Slic3r::Test::gcode($print); + is scalar(() = $gcode =~ /^_MY_CUSTOM_GCODE_/gm), 2, 'between_objects_gcode is applied correctly'; +} + +#endif \ No newline at end of file diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index 6be45d238..a52583cfc 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -26,6 +26,7 @@ const std::unordered_map mesh_names { std::pair(TestMesh::V, "V"), std::pair(TestMesh::_40x10, "40x10"), std::pair(TestMesh::cube_20x20x20, "cube_20x20x20"), + std::pair(TestMesh::cube_2x20x10, "cube_2x20x10"), std::pair(TestMesh::sphere_50mm, "sphere_50mm"), std::pair(TestMesh::bridge, "bridge"), std::pair(TestMesh::bridge_with_hole, "bridge_with_hole"), @@ -49,6 +50,9 @@ TriangleMesh mesh(TestMesh m) case TestMesh::cube_20x20x20: mesh = Slic3r::make_cube(20, 20, 20); break; + case TestMesh::cube_2x20x10: + mesh = Slic3r::make_cube(2, 20, 10); + break; case TestMesh::sphere_50mm: mesh = Slic3r::make_sphere(50, PI / 243.0); break; diff --git a/tests/fff_print/test_data.hpp b/tests/fff_print/test_data.hpp index 573ae58a5..b699e5e4e 100644 --- a/tests/fff_print/test_data.hpp +++ b/tests/fff_print/test_data.hpp @@ -21,6 +21,7 @@ enum class TestMesh { V, _40x10, cube_20x20x20, + cube_2x20x10, sphere_50mm, bridge, bridge_with_hole, diff --git a/tests/fff_print/test_fill.cpp b/tests/fff_print/test_fill.cpp index c3af6e8fc..0ccb27d9a 100644 --- a/tests/fff_print/test_fill.cpp +++ b/tests/fff_print/test_fill.cpp @@ -197,7 +197,7 @@ TEST_CASE("Fill: Pattern Path Length", "[Fill]") { SCENARIO("Infill does not exceed perimeters", "[Fill]") { auto test = [](const std::string_view pattern) { - DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config_with({ + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ { "nozzle_diameter", "0.4, 0.4, 0.4, 0.4" }, { "fill_pattern", pattern }, { "top_fill_pattern", pattern }, diff --git a/tests/fff_print/test_flow.cpp b/tests/fff_print/test_flow.cpp index 81f748e19..b24b59cc6 100644 --- a/tests/fff_print/test_flow.cpp +++ b/tests/fff_print/test_flow.cpp @@ -5,8 +5,6 @@ #include "test_data.hpp" // get access to init_print, etc -#include "libslic3r/Config.hpp" -#include "libslic3r/Model.hpp" #include "libslic3r/Config.hpp" #include "libslic3r/GCodeReader.hpp" #include "libslic3r/Flow.hpp" @@ -16,61 +14,118 @@ using namespace Slic3r::Test; using namespace Slic3r; SCENARIO("Extrusion width specifics", "[Flow]") { - GIVEN("A config with a skirt, brim, some fill density, 3 perimeters, and 1 bottom solid layer and a 20mm cube mesh") { - // this is a sharedptr - DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); - config.set_deserialize_strict({ - { "brim_width", 2 }, - { "skirts", 1 }, - { "perimeters", 3 }, - { "fill_density", "40%" }, - { "first_layer_height", 0.3 } - }); - WHEN("first layer width set to 2mm") { - Slic3r::Model model; - config.set("first_layer_extrusion_width", 2); - Slic3r::Print print; - Slic3r::Test::init_print({TestMesh::cube_20x20x20}, print, model, config); - - std::vector E_per_mm_bottom; - std::string gcode = Test::gcode(print); - Slic3r::GCodeReader parser; - const double layer_height = config.opt_float("layer_height"); - parser.parse_buffer(gcode, [&E_per_mm_bottom, layer_height] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line) - { - if (self.z() == Approx(layer_height).margin(0.01)) { // only consider first layer - if (line.extruding(self) && line.dist_XY(self) > 0) { - E_per_mm_bottom.emplace_back(line.dist_E(self) / line.dist_XY(self)); - } - } - }); - THEN(" First layer width applies to everything on first layer.") { - bool pass = false; - double avg_E = std::accumulate(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), 0.0) / static_cast(E_per_mm_bottom.size()); - - pass = (std::count_if(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), [avg_E] (const double& v) { return v == Approx(avg_E); }) == 0); - REQUIRE(pass == true); - REQUIRE(E_per_mm_bottom.size() > 0); // make sure it actually passed because of extrusion - } - THEN(" First layer width does not apply to upper layer.") { + auto test = [](const DynamicPrintConfig &config) { + Slic3r::GCodeReader parser; + const double layer_height = config.opt_float("layer_height"); + std::vector E_per_mm_bottom; + parser.parse_buffer(Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config), + [&E_per_mm_bottom, layer_height] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line) + { + if (self.z() == Approx(layer_height).margin(0.01)) { // only consider first layer + if (line.extruding(self) && line.dist_XY(self) > 0) + E_per_mm_bottom.emplace_back(line.dist_E(self) / line.dist_XY(self)); } + }); + THEN("First layer width applies to everything on first layer.") { + REQUIRE(E_per_mm_bottom.size() > 0); + const double E_per_mm_avg = std::accumulate(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), 0.0) / static_cast(E_per_mm_bottom.size()); + bool pass = (std::count_if(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), [E_per_mm_avg] (const double& v) { return v == Approx(E_per_mm_avg); }) == 0); + REQUIRE(pass); + } + THEN("First layer width does not apply to upper layer.") { + } + }; + GIVEN("A config with a skirt, brim, some fill density, 3 perimeters, and 1 bottom solid layer") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "skirts", 1 }, + { "brim_width", 2 }, + { "perimeters", 3 }, + { "fill_density", "40%" }, + { "first_layer_height", 0.3 }, + { "first_layer_extrusion_width", "2" }, + }); + WHEN("Slicing a 20mm cube") { + test(config); + } + } + GIVEN("A config with more options and a 20mm cube ") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "skirts", 1 }, + { "brim_width", 2 }, + { "perimeters", 3 }, + { "fill_density", "40%" }, + { "layer_height", "0.35" }, + { "first_layer_height", "0.35" }, + { "bottom_solid_layers", 1 }, + { "first_layer_extrusion_width", "2" }, + { "filament_diameter", "3" }, + { "nozzle_diameter", "0.5" } + }); + WHEN("Slicing a 20mm cube") { + test(config); } } } -// needs gcode export + SCENARIO(" Bridge flow specifics.", "[Flow]") { + auto config = DynamicPrintConfig::full_print_config_with({ + { "bridge_speed", 99 }, + { "bridge_flow_ratio", 1 }, + // to prevent speeds from being altered + { "cooling", "0" }, + // to prevent speeds from being altered + { "first_layer_speed", "100%" } + }); + + auto test = [](const DynamicPrintConfig &config) { + GCodeReader parser; + const double bridge_speed = config.opt_float("bridge_speed") * 60.; + std::vector E_per_mm; + parser.parse_buffer(Slic3r::Test::slice({ Slic3r::Test::TestMesh::overhang }, config), + [&E_per_mm, bridge_speed](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.extruding(self) && line.dist_XY(self) > 0) { + if (is_approx(line.new_F(self), bridge_speed)) + E_per_mm.emplace_back(line.dist_E(self) / line.dist_XY(self)); + } + }); + const double nozzle_dmr = config.opt("nozzle_diameter")->get_at(0); + const double filament_dmr = config.opt("filament_diameter")->get_at(0); + const double bridge_mm_per_mm = sqr(nozzle_dmr / filament_dmr) * config.opt_float("bridge_flow_ratio"); + size_t num_errors = std::count_if(E_per_mm.begin(), E_per_mm.end(), + [bridge_mm_per_mm](double v){ return std::abs(v - bridge_mm_per_mm) > 0.01; }); + return num_errors == 0; + }; + GIVEN("A default config with no cooling and a fixed bridge speed, flow ratio and an overhang mesh.") { - WHEN("bridge_flow_ratio is set to 1.0") { + WHEN("bridge_flow_ratio is set to 0.5 and extrusion width to default") { + config.set_deserialize_strict({ { "bridge_flow_ratio", 0.5}, { "extrusion_width", "0" } }); THEN("Output flow is as expected.") { + REQUIRE(test(config)); } } - WHEN("bridge_flow_ratio is set to 0.5") { + WHEN("bridge_flow_ratio is set to 2.0 and extrusion width to default") { + config.set_deserialize_strict({ { "bridge_flow_ratio", 2.0}, { "extrusion_width", "0" } }); THEN("Output flow is as expected.") { + REQUIRE(test(config)); } } - WHEN("bridge_flow_ratio is set to 2.0") { + WHEN("bridge_flow_ratio is set to 0.5 and extrusion_width to 0.4") { + config.set_deserialize_strict({ { "bridge_flow_ratio", 0.5}, { "extrusion_width", 0.4 } }); THEN("Output flow is as expected.") { + REQUIRE(test(config)); + } + } + WHEN("bridge_flow_ratio is set to 1.0 and extrusion_width to 0.4") { + config.set_deserialize_strict({ { "bridge_flow_ratio", 1.0}, { "extrusion_width", 0.4 } }); + THEN("Output flow is as expected.") { + REQUIRE(test(config)); + } + } + WHEN("bridge_flow_ratio is set to 2 and extrusion_width to 0.4") { + config.set_deserialize_strict({ { "bridge_flow_ratio", 2.}, { "extrusion_width", 0.4 } }); + THEN("Output flow is as expected.") { + REQUIRE(test(config)); } } } diff --git a/tests/fff_print/test_gaps.cpp b/tests/fff_print/test_gaps.cpp new file mode 100644 index 000000000..a096087ce --- /dev/null +++ b/tests/fff_print/test_gaps.cpp @@ -0,0 +1,60 @@ +#include + +#include "libslic3r/GCodeReader.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" +#include "libslic3r/Layer.hpp" + +#include "test_data.hpp" // get access to init_print, etc + +using namespace Slic3r::Test; +using namespace Slic3r; + +SCENARIO("Gaps", "[Gaps]") { + GIVEN("Two hollow squares") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "skirts", 0 }, + { "perimeter_speed", 66 }, + { "external_perimeter_speed", 66 }, + { "small_perimeter_speed", 66 }, + { "gap_fill_speed", 99 }, + { "perimeters", 1 }, + // to prevent speeds from being altered + { "cooling", 0 }, + // to prevent speeds from being altered + { "first_layer_speed", "100%" }, + { "perimeter_extrusion_width", 0.35 }, + { "first_layer_extrusion_width", 0.35 } + }); + + GCodeReader parser; + const double perimeter_speed = config.opt_float("perimeter_speed") * 60; + const double gap_fill_speed = config.opt_float("gap_fill_speed") * 60; + std::string last; // perimeter or gap + Points perimeter_points; + int gap_fills_outside_last_perimeters = 0; + parser.parse_buffer( + Slic3r::Test::slice({ Slic3r::Test::TestMesh::two_hollow_squares }, config), + [&perimeter_points, &gap_fills_outside_last_perimeters, &last, perimeter_speed, gap_fill_speed] + (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (line.extruding(self) && line.dist_XY(self) > 0) { + double f = line.new_F(self); + Point point = line.new_XY_scaled(self); + if (is_approx(f, perimeter_speed)) { + if (last == "gap") + perimeter_points.clear(); + perimeter_points.emplace_back(point); + last = "perimeter"; + } else if (is_approx(f, gap_fill_speed)) { + Polygon convex_hull = Geometry::convex_hull(perimeter_points); + if (! convex_hull.contains(point)) + ++ gap_fills_outside_last_perimeters; + last = "gap"; + } + } + }); + THEN("gap fills are printed before leaving islands") { + REQUIRE(gap_fills_outside_last_perimeters == 0); + } + } +} diff --git a/tests/fff_print/test_thin_walls.cpp b/tests/fff_print/test_thin_walls.cpp new file mode 100644 index 000000000..cd80f3bd6 --- /dev/null +++ b/tests/fff_print/test_thin_walls.cpp @@ -0,0 +1,191 @@ +#include + +#include +#include + +#include "test_data.hpp" // get access to init_print, etc + +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/libslic3r.h" + +using namespace Slic3r; + +SCENARIO("Medial Axis", "[ThinWalls]") { + GIVEN("Square with hole") { + auto square = Polygon::new_scale({ {100, 100}, {200, 100}, {200, 200}, {100, 200} }); + auto hole_in_square = Polygon::new_scale({ {140, 140}, {140, 160}, {160, 160}, {160, 140} }); + ExPolygon expolygon{ square, hole_in_square }; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(40.), scaled(0.5)); + THEN("medial axis of a square shape is a single path") { + REQUIRE(res.size() == 1); + } + THEN("polyline forms a closed loop") { + REQUIRE(res.front().first_point() == res.front().last_point()); + } + THEN("medial axis loop has reasonable length") { + REQUIRE(res.front().length() > hole_in_square.length()); + REQUIRE(res.front().length() < square.length()); + } + } + } + GIVEN("narrow rectangle") { + ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 200}, {100, 200} }) }; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(20.), scaled(0.5)); + THEN("medial axis of a narrow rectangle is a single line") { + REQUIRE(res.size() == 1); + } + THEN("medial axis has reasonable length") { + REQUIRE(res.front().length() >= scaled(200.-100. - (120.-100.)) - SCALED_EPSILON); + } + } + } +#if 0 + //FIXME this test never worked + GIVEN("narrow rectangle with an extra vertex") { + ExPolygon expolygon{ Polygon::new_scale({ + {100, 100}, {120, 100}, {120, 200}, + {105, 200} /* extra point in the short side*/, + {100, 200} + })}; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(1.), scaled(0.5)); + THEN("medial axis of a narrow rectangle with an extra vertex is still a single line") { + REQUIRE(res.size() == 1); + } + THEN("medial axis has still a reasonable length") { + REQUIRE(res.front().length() >= scaled(200.-100. - (120.-100.)) - SCALED_EPSILON); + } + THEN("extra vertices don't influence medial axis") { + size_t invalid = 0; + for (const Polyline &pl : res) + for (const Point &p : pl.points) + if (std::abs(p.y() - scaled(150.)) < SCALED_EPSILON) + ++ invalid; + REQUIRE(invalid == 0); + } + } + } +#endif + GIVEN("semicircumference") { + ExPolygon expolygon{{ + {1185881,829367},{1421988,1578184},{1722442,2303558},{2084981,2999998},{2506843,3662186},{2984809,4285086},{3515250,4863959},{4094122,5394400}, + {4717018,5872368},{5379210,6294226},{6075653,6656769},{6801033,6957229},{7549842,7193328},{8316383,7363266},{9094809,7465751},{9879211,7500000}, + {10663611,7465750},{11442038,7363265},{12208580,7193327},{12957389,6957228},{13682769,6656768},{14379209,6294227},{15041405,5872366}, + {15664297,5394401},{16243171,4863960},{16758641,4301424},{17251579,3662185},{17673439,3000000},{18035980,2303556},{18336441,1578177}, + {18572539,829368},{18750748,0},{19758422,0},{19727293,236479},{19538467,1088188},{19276136,1920196},{18942292,2726179},{18539460,3499999}, + {18070731,4235755},{17539650,4927877},{16950279,5571067},{16307090,6160437},{15614974,6691519},{14879209,7160248},{14105392,7563079}, + {13299407,7896927},{12467399,8159255},{11615691,8348082},{10750769,8461952},{9879211,8500000},{9007652,8461952},{8142729,8348082}, + {7291022,8159255},{6459015,7896927},{5653029,7563079},{4879210,7160247},{4143447,6691519},{3451331,6160437},{2808141,5571066},{2218773,4927878}, + {1687689,4235755},{1218962,3499999},{827499,2748020},{482284,1920196},{219954,1088186},{31126,236479},{0,0},{1005754,0} + }}; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(1.324888), scaled(0.25)); + THEN("medial axis of a semicircumference is a single line") { + REQUIRE(res.size() == 1); + } + THEN("all medial axis segments of a semicircumference have the same orientation") { + int nccw = 0; + int ncw = 0; + for (const Polyline &pl : res) + for (size_t i = 1; i + 1 < pl.size(); ++ i) { + double cross = cross2((pl.points[i] - pl.points[i - 1]).cast(), (pl.points[i + 1] - pl.points[i]).cast()); + if (cross > 0.) + ++ nccw; + else if (cross < 0.) + ++ ncw; + } + REQUIRE((ncw == 0 || nccw == 0)); + } + } + } + GIVEN("narrow trapezoid") { + ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {112, 200}, {108, 200} }) }; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(20.), scaled(0.5)); + THEN("medial axis of a narrow trapezoid is a single line") { + REQUIRE(res.size() == 1); + } + THEN("medial axis has reasonable length") { + REQUIRE(res.front().length() >= scaled(200.-100. - (120.-100.)) - SCALED_EPSILON); + } + } + } + GIVEN("L shape") { + ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 180}, {200, 180}, {200, 200}, {100, 200}, }) }; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(20.), scaled(0.5)); + THEN("medial axis of an L shape is a single line") { + REQUIRE(res.size() == 1); + } + THEN("medial axis has reasonable length") { + // 20 is the thickness of the expolygon, which is subtracted from the ends + auto len = unscale(res.front().length()) + 20; + REQUIRE(len > 80. * 2.); + REQUIRE(len < 100. * 2.); + } + } + } + GIVEN("whatever shape") { + ExPolygon expolygon{{ + {-203064906,-51459966},{-219312231,-51459966},{-219335477,-51459962},{-219376095,-51459962},{-219412047,-51459966}, + {-219572094,-51459966},{-219624814,-51459962},{-219642183,-51459962},{-219656665,-51459966},{-220815482,-51459966}, + {-220815482,-37738966},{-221117540,-37738966},{-221117540,-51762024},{-203064906,-51762024}, + }}; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(819998., 102499.75); + THEN("medial axis is a single line") { + REQUIRE(res.size() == 1); + } + THEN("medial axis has reasonable length") { + double perimeter = expolygon.contour.split_at_first_point().length(); + REQUIRE(total_length(res) > perimeter / 2. / 4. * 3.); + } + } + } + GIVEN("narrow triangle") { + ExPolygon expolygon{ Polygon::new_scale({ {50, 100}, {1000, 102}, {50, 104} }) }; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(4.), scaled(0.5)); + THEN("medial axis of a narrow triangle is a single line") { + REQUIRE(res.size() == 1); + } + THEN("medial axis has reasonable length") { + REQUIRE(res.front().length() >= scaled(200.-100. - (120.-100.)) - SCALED_EPSILON); + } + } + } + GIVEN("GH #2474") { + ExPolygon expolygon{{ {91294454,31032190},{11294481,31032190},{11294481,29967810},{44969182,29967810},{89909960,29967808},{91294454,29967808} }}; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(1871238, 500000); + THEN("medial axis is a single line") { + REQUIRE(res.size() == 1); + } + Polyline &polyline = res.front(); + THEN("medial axis is horizontal and is centered") { + double expected_y = expolygon.contour.bounding_box().center().y(); + double center_y = 0.; + for (auto &p : polyline.points) + center_y += double(p.y()); + REQUIRE(std::abs(center_y / polyline.size() - expected_y) < SCALED_EPSILON); + } + // order polyline from left to right + if (polyline.first_point().x() > polyline.last_point().x()) + polyline.reverse(); + BoundingBox polyline_bb = polyline.bounding_box(); + THEN("expected x_min") { + REQUIRE(polyline.first_point().x() == polyline_bb.min.x()); + } + THEN("expected x_max") { + REQUIRE(polyline.last_point().x() == polyline_bb.max.x()); + } + THEN("medial axis is monotonous in x (not self intersecting)") { + Polyline sorted { polyline }; + std::sort(sorted.begin(), sorted.end()); + REQUIRE(polyline == sorted); + } + } + } +} diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 248245182..a3984b34b 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable(${_TEST_NAME}_tests test_geometry.cpp test_placeholder_parser.cpp test_polygon.cpp + test_polyline.cpp test_mutable_polygon.cpp test_mutable_priority_queue.cpp test_stl.cpp diff --git a/tests/libslic3r/test_config.cpp b/tests/libslic3r/test_config.cpp index 025459d68..50e61b3b0 100644 --- a/tests/libslic3r/test_config.cpp +++ b/tests/libslic3r/test_config.cpp @@ -1,5 +1,6 @@ #include +#include "libslic3r/Config.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/LocalesUtils.hpp" @@ -13,20 +14,20 @@ using namespace Slic3r; SCENARIO("Generic config validation performs as expected.", "[Config]") { GIVEN("A config generated from default options") { Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); - WHEN( "perimeter_extrusion_width is set to 250%, a valid value") { + WHEN("perimeter_extrusion_width is set to 250%, a valid value") { config.set_deserialize_strict("perimeter_extrusion_width", "250%"); THEN( "The config is read as valid.") { REQUIRE(config.validate().empty()); } } - WHEN( "perimeter_extrusion_width is set to -10, an invalid value") { + WHEN("perimeter_extrusion_width is set to -10, an invalid value") { config.set("perimeter_extrusion_width", -10); THEN( "Validate returns error") { REQUIRE(! config.validate().empty()); } } - WHEN( "perimeters is set to -10, an invalid value") { + WHEN("perimeters is set to -10, an invalid value") { config.set("perimeters", -10); THEN( "Validate returns error") { REQUIRE(! config.validate().empty()); @@ -36,8 +37,7 @@ SCENARIO("Generic config validation performs as expected.", "[Config]") { } SCENARIO("Config accessor functions perform as expected.", "[Config]") { - GIVEN("A config generated from default options") { - Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + auto test = [](ConfigBase &config) { WHEN("A boolean option is set to a boolean value") { REQUIRE_NOTHROW(config.set("gcode_comments", true)); THEN("The underlying value is set correctly.") { @@ -70,8 +70,8 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") { } } #if 0 - //FIXME better design accessors for vector elements. - WHEN("An integer-based option is set through the integer interface") { + //FIXME better design accessors for vector elements. + WHEN("An integer-based option is set through the integer interface") { config.set("bed_temperature", 100); THEN("The underlying value is set correctly.") { REQUIRE(config.opt("bed_temperature")->get_at(0) == 100); @@ -193,6 +193,14 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") { REQUIRE(config.opt_float("layer_height") == 0.5); } } + }; + GIVEN("DynamicPrintConfig generated from default options") { + auto config = Slic3r::DynamicPrintConfig::full_print_config(); + test(config); + } + GIVEN("FullPrintConfig generated from default options") { + Slic3r::FullPrintConfig config; + test(config); } } diff --git a/tests/libslic3r/test_geometry.cpp b/tests/libslic3r/test_geometry.cpp index 34e625e6d..41ef69aaa 100644 --- a/tests/libslic3r/test_geometry.cpp +++ b/tests/libslic3r/test_geometry.cpp @@ -162,14 +162,34 @@ TEST_CASE("Splitting a Polygon generates a polyline correctly", "[Geometry]"){ } -TEST_CASE("Bounding boxes are scaled appropriately", "[Geometry]"){ - BoundingBox bb(std::vector({Point(0, 1), Point(10, 2), Point(20, 2)})); - bb.scale(2); - REQUIRE(bb.min == Point(0,2)); - REQUIRE(bb.max == Point(40,4)); +SCENARIO("BoundingBox", "[Geometry]") { + WHEN("Bounding boxes are scaled") { + BoundingBox bb(std::vector({Point(0, 1), Point(10, 2), Point(20, 2)})); + bb.scale(2); + REQUIRE(bb.min == Point(0,2)); + REQUIRE(bb.max == Point(40,4)); + } + WHEN("BoundingBox constructed from points") { + BoundingBox bb(Points{ {100,200}, {100, 200}, {500, -600} }); + THEN("minimum is correct") { + REQUIRE(bb.min == Point{100,-600}); + } + THEN("maximum is correct") { + REQUIRE(bb.max == Point{500,200}); + } + } + WHEN("BoundingBox constructed from a single point") { + BoundingBox bb; + bb.merge({10, 10}); + THEN("minimum equals to the only defined point") { + REQUIRE(bb.min == Point{10,10}); + } + THEN("maximum equals to the only defined point") { + REQUIRE(bb.max == Point{10,10}); + } + } } - TEST_CASE("Offseting a line generates a polygon correctly", "[Geometry]"){ Slic3r::Polyline tmp = { Point(10,10), Point(20,10) }; Slic3r::Polygon area = offset(tmp,5).at(0); diff --git a/tests/libslic3r/test_polygon.cpp b/tests/libslic3r/test_polygon.cpp index f2c78cace..7774b44a6 100644 --- a/tests/libslic3r/test_polygon.cpp +++ b/tests/libslic3r/test_polygon.cpp @@ -148,3 +148,65 @@ SCENARIO("Remove collinear points from Polygon", "[Polygon]") { } } } + +SCENARIO("Simplify polygon", "[Polygon]") +{ + GIVEN("gear") { + auto gear = Polygon::new_scale({ + {144.9694,317.1543}, {145.4181,301.5633}, {146.3466,296.921}, {131.8436,294.1643}, {131.7467,294.1464}, + {121.7238,291.5082}, {117.1631,290.2776}, {107.9198,308.2068}, {100.1735,304.5101}, {104.9896,290.3672}, + {106.6511,286.2133}, {93.453,279.2327}, {81.0065,271.4171}, {67.7886,286.5055}, {60.7927,280.1127}, + {69.3928,268.2566}, {72.7271,264.9224}, {61.8152,253.9959}, {52.2273,242.8494}, {47.5799,245.7224}, + {34.6577,252.6559}, {30.3369,245.2236}, {42.1712,236.3251}, {46.1122,233.9605}, {43.2099,228.4876}, + {35.0862,211.5672}, {33.1441,207.0856}, {13.3923,212.1895}, {10.6572,203.3273}, {6.0707,204.8561}, + {7.2775,204.4259}, {29.6713,196.3631}, {25.9815,172.1277}, {25.4589,167.2745}, {19.8337,167.0129}, + {5.0625,166.3346}, {5.0625,156.9425}, {5.3701,156.9282}, {21.8636,156.1628}, {25.3713,156.4613}, + {25.4243,155.9976}, {29.3432,155.8157}, {30.3838,149.3549}, {26.3596,147.8137}, {27.1085,141.2604}, + {29.8466,126.8337}, {24.5841,124.9201}, {10.6664,119.8989}, {13.4454,110.9264}, {33.1886,116.0691}, + {38.817,103.1819}, {45.8311,89.8133}, {30.4286,76.81}, {35.7686,70.0812}, {48.0879,77.6873}, + {51.564,81.1635}, {61.9006,69.1791}, {72.3019,58.7916}, {60.5509,42.5416}, {68.3369,37.1532}, + {77.9524,48.1338}, {80.405,52.2215}, {92.5632,44.5992}, {93.0123,44.3223}, {106.3561,37.2056}, + {100.8631,17.4679}, {108.759,14.3778}, {107.3148,11.1283}, {117.0002,32.8627}, {140.9109,27.3974}, + {145.7004,26.4994}, {145.1346,6.1011}, {154.502,5.4063}, {156.9398,25.6501}, {171.0557,26.2017}, + {181.3139,27.323}, {186.2377,27.8532}, {191.6031,8.5474}, {200.6724,11.2756}, {197.2362,30.2334}, + {220.0789,39.1906}, {224.3261,41.031}, {236.3506,24.4291}, {243.6897,28.6723}, {234.2956,46.7747}, + {245.6562,55.1643}, {257.2523,65.0901}, {261.4374,61.5679}, {273.1709,52.8031}, {278.555,59.5164}, + {268.4334,69.8001}, {264.1615,72.3633}, {268.2763,77.9442}, {278.8488,93.5305}, {281.4596,97.6332}, + {286.4487,95.5191}, {300.2821,90.5903}, {303.4456,98.5849}, {286.4523,107.7253}, {293.7063,131.1779}, + {294.9748,135.8787}, {314.918,133.8172}, {315.6941,143.2589}, {300.9234,146.1746}, {296.6419,147.0309}, + {297.1839,161.7052}, {296.6136,176.3942}, {302.1147,177.4857}, {316.603,180.3608}, {317.1658,176.7341}, + {315.215,189.6589}, {315.1749,189.6548}, {294.9411,187.5222}, {291.13,201.7233}, {286.2615,215.5916}, + {291.1944,218.2545}, {303.9158,225.1271}, {299.2384,233.3694}, {285.7165,227.6001}, {281.7091,225.1956}, + {273.8981,237.6457}, {268.3486,245.2248}, {267.4538,246.4414}, {264.8496,250.0221}, {268.6392,253.896}, + {278.5017,265.2131}, {272.721,271.4403}, {257.2776,258.3579}, {234.4345,276.5687}, {242.6222,294.8315}, + {234.9061,298.5798}, {227.0321,286.2841}, {225.2505,281.8301}, {211.5387,287.8187}, {202.3025,291.0935}, + {197.307,292.831}, {199.808,313.1906}, {191.5298,315.0787}, {187.3082,299.8172}, {186.4201,295.3766}, + {180.595,296.0487}, {161.7854,297.4248}, {156.8058,297.6214}, {154.3395,317.8592} + }); + + WHEN("simplified") { + size_t num_points = gear.size(); + Polygons simplified = gear.simplify(1000.); + THEN("gear simplified to a single polygon") { + REQUIRE(simplified.size() == 1); + } + THEN("gear was reduced using Douglas-Peucker") { + //note printf "original points: %d\nnew points: %d", $num_points, scalar(@{$simplified->[0]}); + REQUIRE(simplified.front().size() < num_points); + } + } + } + GIVEN("hole in square") { + // CW oriented + auto hole_in_square = Polygon{ {140, 140}, {140, 160}, {160, 160}, {160, 140} }; + WHEN("simplified") { + Polygons simplified = hole_in_square.simplify(2.); + THEN("hole simplification returns one polygon") { + REQUIRE(simplified.size() == 1); + } + THEN("hole simplification turns cw polygon into ccw polygon") { + REQUIRE(simplified.front().is_counter_clockwise()); + } + } + } +} diff --git a/tests/libslic3r/test_polyline.cpp b/tests/libslic3r/test_polyline.cpp new file mode 100644 index 000000000..827155404 --- /dev/null +++ b/tests/libslic3r/test_polyline.cpp @@ -0,0 +1,28 @@ +#include + +#include "libslic3r/Point.hpp" +#include "libslic3r/Polyline.hpp" + +using namespace Slic3r; + +SCENARIO("Simplify polyline", "[Polyline]") +{ + GIVEN("polyline 1") { + auto polyline = Polyline{ {0,0},{1,0},{2,0},{2,1},{2,2},{1,2},{0,2},{0,1},{0,0} }; + WHEN("simplified with Douglas-Peucker") { + polyline.simplify(1.); + THEN("simplified correctly") { + REQUIRE(polyline == Polyline{ {0,0}, {2,0}, {2,2}, {0,2}, {0,0} }); + } + } + } + GIVEN("polyline 2") { + auto polyline = Polyline{ {0,0}, {50,50}, {100,0}, {125,-25}, {150,50} }; + WHEN("simplified with Douglas-Peucker") { + polyline.simplify(25.); + THEN("not simplified") { + REQUIRE(polyline == Polyline{ {0,0}, {50,50}, {125,-25}, {150,50} }); + } + } + } +} diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 1a58aab12..c2343b032 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -49,7 +49,6 @@ set(XS_XSP_FILES ${XSP_DIR}/ExtrusionEntityCollection.xsp ${XSP_DIR}/ExtrusionLoop.xsp ${XSP_DIR}/ExtrusionPath.xsp - ${XSP_DIR}/GCode.xsp ${XSP_DIR}/Geometry.xsp ${XSP_DIR}/Layer.xsp ${XSP_DIR}/Line.xsp diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 1675ac193..87fb267c5 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -158,7 +158,6 @@ for my $class (qw( Slic3r::ExtrusionLoop Slic3r::ExtrusionPath Slic3r::ExtrusionPath::Collection - Slic3r::GCode Slic3r::Geometry::BoundingBox Slic3r::Layer Slic3r::Layer::Region diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 4e293a2f9..8484b1d64 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -7,7 +7,6 @@ REGISTER_CLASS(ExPolygon, "ExPolygon"); REGISTER_CLASS(ExtrusionPath, "ExtrusionPath"); REGISTER_CLASS(ExtrusionLoop, "ExtrusionLoop"); REGISTER_CLASS(ExtrusionEntityCollection, "ExtrusionPath::Collection"); -REGISTER_CLASS(CoolingBuffer, "GCode::CoolingBuffer"); REGISTER_CLASS(GCode, "GCode"); REGISTER_CLASS(Layer, "Layer"); REGISTER_CLASS(LayerRegion, "Layer::Region"); diff --git a/xs/t/01_trianglemesh.t b/xs/t/01_trianglemesh.t deleted file mode 100644 index a071a75a2..000000000 --- a/xs/t/01_trianglemesh.t +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 4; - -my $cube = { - vertices => [ [20,20,0], [20,0,0], [0,0,0], [0,20,0], [20,20,20], [0,20,20], [0,0,20], [20,0,20] ], - facets => [ [0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5] ], -}; - -{ - my $m = Slic3r::TriangleMesh->new; - $m->ReadFromPerl($cube->{vertices}, $cube->{facets}); - my ($vertices, $facets) = ($m->vertices, $m->facets); - - is_deeply $vertices, $cube->{vertices}, 'vertices arrayref roundtrip'; - is_deeply $facets, $cube->{facets}, 'facets arrayref roundtrip'; - - { - my $m2 = $m->clone; - is_deeply $m2->vertices, $cube->{vertices}, 'cloned vertices arrayref roundtrip'; - is_deeply $m2->facets, $cube->{facets}, 'cloned facets arrayref roundtrip'; - $m2->scale(3); # check that it does not affect $m - } -} - -__END__ diff --git a/xs/t/03_point.t b/xs/t/03_point.t index c950998fb..f888349b3 100644 --- a/xs/t/03_point.t +++ b/xs/t/03_point.t @@ -4,10 +4,9 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 24; +use Test::More tests => 21; my $point = Slic3r::Point->new(10, 15); -is_deeply [ @$point ], [10, 15], 'point roundtrip'; my $point2 = $point->clone; $point2->scale(2); @@ -16,9 +15,6 @@ is_deeply [ @$point2 ], [20, 30], 'scale'; $point2->translate(10, -15); is_deeply [ @$point2 ], [30, 15], 'translate'; -ok $point->coincides_with($point->clone), 'coincides_with'; -ok !$point->coincides_with($point2), 'coincides_with'; - { my $point3 = Slic3r::Point->new(4300000, -9880845); is $point->[0], $point->x, 'x accessor'; diff --git a/xs/t/04_expolygon.t b/xs/t/04_expolygon.t index 48eaed551..9132c44b9 100644 --- a/xs/t/04_expolygon.t +++ b/xs/t/04_expolygon.t @@ -5,7 +5,7 @@ use warnings; use List::Util qw(first sum); use Slic3r::XS; -use Test::More tests => 15; +use Test::More tests => 7; use constant PI => 4 * atan2(1, 1); @@ -25,21 +25,6 @@ my $hole_in_square = [ # cw my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); ok $expolygon->is_valid, 'is_valid'; -is ref($expolygon->pp), 'ARRAY', 'expolygon pp is unblessed'; -is_deeply $expolygon->pp, [$square, $hole_in_square], 'expolygon roundtrip'; - -is ref($expolygon->arrayref), 'ARRAY', 'expolygon arrayref is unblessed'; -isa_ok $expolygon->[0], 'Slic3r::Polygon::Ref', 'expolygon polygon is blessed'; -isa_ok $expolygon->contour, 'Slic3r::Polygon::Ref', 'expolygon contour is blessed'; -isa_ok $expolygon->holes->[0], 'Slic3r::Polygon::Ref', 'expolygon hole is blessed'; -isa_ok $expolygon->[0][0], 'Slic3r::Point::Ref', 'expolygon point is blessed'; - -{ - my $expolygon2 = $expolygon->clone; - my $polygon = $expolygon2->[0]; - $polygon->scale(2); - is $expolygon2->[0][0][0], $polygon->[0][0], 'polygons are returned by reference'; -} is_deeply $expolygon->clone->pp, [$square, $hole_in_square], 'clone'; diff --git a/xs/t/05_surface.t b/xs/t/05_surface.t index 34feb4734..4d9eb5b89 100644 --- a/xs/t/05_surface.t +++ b/xs/t/05_surface.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 15; +use Test::More tests => 11; my $square = [ # ccw [100, 100], @@ -27,10 +27,6 @@ my $surface = Slic3r::Surface->new( $surface = $surface->clone; -isa_ok $surface->expolygon, 'Slic3r::ExPolygon::Ref', 'expolygon'; -is_deeply [ @{$surface->expolygon->pp} ], [$square, $hole_in_square], 'expolygon roundtrip'; -is scalar(@{$surface->polygons}), 2, 'polygons roundtrip'; - is $surface->surface_type, Slic3r::Surface::S_TYPE_INTERNAL, 'surface_type'; $surface->surface_type(Slic3r::Surface::S_TYPE_BOTTOM); is $surface->surface_type, Slic3r::Surface::S_TYPE_BOTTOM, 'modify surface_type'; @@ -59,7 +55,6 @@ is $surface->extra_perimeters, 2, 'extra_perimeters'; is scalar(@$collection), 1, 'append to collection'; my $item = $collection->[0]; - isa_ok $item, 'Slic3r::Surface::Ref'; $item->surface_type(Slic3r::Surface::S_TYPE_INTERNAL); is $item->surface_type, $collection->[0]->surface_type, 'collection returns items by reference'; } diff --git a/xs/t/06_polygon.t b/xs/t/06_polygon.t deleted file mode 100644 index 7bbcd5356..000000000 --- a/xs/t/06_polygon.t +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 3; - -my $square = [ # ccw - [100, 100], - [200, 100], - [200, 200], - [100, 200], -]; - -my $polygon = Slic3r::Polygon->new(@$square); -is ref($polygon->arrayref), 'ARRAY', 'polygon arrayref is unblessed'; -isa_ok $polygon->[0], 'Slic3r::Point::Ref', 'polygon point is blessed'; -ok ref($polygon->first_point) eq 'Slic3r::Point', 'first_point'; - -__END__ diff --git a/xs/t/07_extrusionpath.t b/xs/t/07_extrusionpath.t index 008b51b00..084b4f03e 100644 --- a/xs/t/07_extrusionpath.t +++ b/xs/t/07_extrusionpath.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 7; +use Test::More tests => 5; my $points = [ [100, 100], @@ -17,8 +17,6 @@ my $path = Slic3r::ExtrusionPath->new( role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1, ); -isa_ok $path->polyline, 'Slic3r::Polyline::Ref', 'path polyline'; -is_deeply $path->polyline->pp, $points, 'path points roundtrip'; $path->reverse; is_deeply $path->polyline->pp, [ reverse @$points ], 'reverse path'; diff --git a/xs/t/08_extrusionloop.t b/xs/t/08_extrusionloop.t index e0660a9fd..3abfbd728 100644 --- a/xs/t/08_extrusionloop.t +++ b/xs/t/08_extrusionloop.t @@ -5,7 +5,7 @@ use warnings; use List::Util qw(sum); use Slic3r::XS; -use Test::More tests => 47; +use Test::More tests => 46; { my $square = [ @@ -33,7 +33,6 @@ use Test::More tests => 47; is scalar(@$loop), 1, 'loop contains one path'; { my $path = $loop->[0]; - isa_ok $path, 'Slic3r::ExtrusionPath::Ref'; is $path->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'role'; } diff --git a/xs/t/09_polyline.t b/xs/t/09_polyline.t index 5203ec5ef..7da74b93d 100644 --- a/xs/t/09_polyline.t +++ b/xs/t/09_polyline.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 18; +use Test::More tests => 15; my $points = [ [100, 100], @@ -14,11 +14,6 @@ my $points = [ my $polyline = Slic3r::Polyline->new(@$points); -is_deeply $polyline->pp, $points, 'polyline roundtrip'; - -is ref($polyline->arrayref), 'ARRAY', 'polyline arrayref is unblessed'; -isa_ok $polyline->[0], 'Slic3r::Point::Ref', 'polyline point is blessed'; - my $lines = $polyline->lines; is_deeply [ map $_->pp, @$lines ], [ [ [100, 100], [200, 100] ], @@ -88,41 +83,4 @@ is_deeply $polyline->pp, [ @$points, @$points ], 'append_polyline'; is scalar(@$p2), 4, 'split_at'; } -# disabled because we now use a more efficient but incomplete algorithm -#if (0) { -# my $polyline = Slic3r::Polyline->new( -# map [$_,10], (0,10,20,30,40,50,60) -# ); -# { -# my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( -# [25,0], [55,0], [55,30], [25,30], -# )); -# my $p = $polyline->clone; -# $p->simplify_by_visibility($expolygon); -# is_deeply $p->pp, [ -# map [$_,10], (0,10,20,30,50,60) -# ], 'simplify_by_visibility()'; -# } -# { -# my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( -# [-15,0], [75,0], [75,30], [-15,30], -# )); -# my $p = $polyline->clone; -# $p->simplify_by_visibility($expolygon); -# is_deeply $p->pp, [ -# map [$_,10], (0,60) -# ], 'simplify_by_visibility()'; -# } -# { -# my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( -# [-15,0], [25,0], [25,30], [-15,30], -# )); -# my $p = $polyline->clone; -# $p->simplify_by_visibility($expolygon); -# is_deeply $p->pp, [ -# map [$_,10], (0,20,30,40,50,60) -# ], 'simplify_by_visibility()'; -# } -#} - __END__ diff --git a/xs/t/10_line.t b/xs/t/10_line.t index 8f82e988c..886573f7b 100644 --- a/xs/t/10_line.t +++ b/xs/t/10_line.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 40; +use Test::More tests => 35; use constant PI => 4 * atan2(1, 1); use constant EPSILON => 1E-4; @@ -15,21 +15,6 @@ my $points = [ ]; my $line = Slic3r::Line->new(@$points); -is_deeply $line->pp, $points, 'line roundtrip'; - -is ref($line->arrayref), 'ARRAY', 'line arrayref is unblessed'; -isa_ok $line->[0], 'Slic3r::Point::Ref', 'line point is blessed'; - -{ - my $clone = $line->clone; - $clone->reverse; - is_deeply $clone->pp, [ reverse @$points ], 'reverse'; -} - -{ - my $line2 = Slic3r::Line->new($line->a->clone, $line->b->clone); - is_deeply $line2->pp, $points, 'line roundtrip with cloned points'; -} { my $clone = $line->clone; diff --git a/xs/t/12_extrusionpathcollection.t b/xs/t/12_extrusionpathcollection.t index e7e0b1316..e02854245 100644 --- a/xs/t/12_extrusionpathcollection.t +++ b/xs/t/12_extrusionpathcollection.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 18; +use Test::More tests => 13; my $points = [ [100, 100], @@ -41,12 +41,6 @@ is scalar(@$collection), 3, 'append ExtrusionPath'; $collection->append($loop); is scalar(@$collection), 4, 'append ExtrusionLoop'; -isa_ok $collection->[1], 'Slic3r::ExtrusionPath::Collection::Ref', 'correct object returned for collection'; -isa_ok $collection->[2], 'Slic3r::ExtrusionPath::Ref', 'correct object returned for path'; -isa_ok $collection->[3], 'Slic3r::ExtrusionLoop::Ref', 'correct object returned for loop'; -is ref($collection->[2]->clone), 'Slic3r::ExtrusionPath', 'correct object returned for cloned path'; -is ref($collection->[3]->clone), 'Slic3r::ExtrusionLoop', 'correct object returned for cloned loop'; - is scalar(@{$collection->[1]}), 1, 'appended collection was duplicated'; { diff --git a/xs/t/17_boundingbox.t b/xs/t/17_boundingbox.t deleted file mode 100644 index 349e0024d..000000000 --- a/xs/t/17_boundingbox.t +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 5; - -{ - my @points = ( - Slic3r::Point->new(100, 200), - Slic3r::Point->new(500, -600), - ); - my $bb = Slic3r::Geometry::BoundingBox->new_from_points(\@points); - isa_ok $bb, 'Slic3r::Geometry::BoundingBox', 'new_from_points'; - is_deeply $bb->min_point->pp, [100,-600], 'min_point'; - is_deeply $bb->max_point->pp, [500,200], 'max_point'; -} - -{ - my $bb = Slic3r::Geometry::BoundingBox->new; - $bb->merge_point(Slic3r::Point->new(10, 10)); - is_deeply $bb->min_point->pp, [10,10], 'min_point equals to the only defined point'; - is_deeply $bb->max_point->pp, [10,10], 'max_point equals to the only defined point'; -} - -__END__ diff --git a/xs/xsp/ExPolygon.xsp b/xs/xsp/ExPolygon.xsp index a57bcfbcb..50b32544e 100644 --- a/xs/xsp/ExPolygon.xsp +++ b/xs/xsp/ExPolygon.xsp @@ -29,8 +29,6 @@ %code{% RETVAL = THIS->contains(*point); %}; ExPolygons simplify(double tolerance); Polygons simplify_p(double tolerance); - Polylines medial_axis(double max_width, double min_width) - %code{% THIS->medial_axis(max_width, min_width, &RETVAL); %}; %{ ExPolygon* diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp deleted file mode 100644 index 4c2583894..000000000 --- a/xs/xsp/GCode.xsp +++ /dev/null @@ -1,53 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/GCode.hpp" -#include "libslic3r/GCode/CoolingBuffer.hpp" -%} - -%name{Slic3r::GCode::CoolingBuffer} class CoolingBuffer { - CoolingBuffer(GCode* gcode) - %code{% RETVAL = new CoolingBuffer(*gcode); %}; - ~CoolingBuffer(); - std::string process_layer(std::string gcode, size_t layer_id) - %code{% RETVAL = THIS->process_layer(std::move(gcode), layer_id, true); %}; - -}; - -%name{Slic3r::GCode} class GCode { - GCode(); - ~GCode(); - void do_export(Print *print, const char *path) - %code%{ - try { - THIS->do_export(print, path); - } catch (std::exception& e) { - croak("%s\n", e.what()); - } - %}; - - Ref origin() - %code{% RETVAL = &(THIS->origin()); %}; - void set_origin(Vec2d* pointf) - %code{% THIS->set_origin(*pointf); %}; - Ref last_pos() - %code{% RETVAL = &(THIS->last_pos()); %}; - - unsigned int layer_count() const; - void set_layer_count(unsigned int value); - void set_extruders(std::vector extruders) - %code{% THIS->writer().set_extruders(extruders); THIS->writer().set_extruder(0); %}; - - void apply_print_config(StaticPrintConfig* print_config) - %code{% - if (const PrintConfig* config = dynamic_cast(print_config)) { - THIS->apply_print_config(*config); - } else { - CONFESS("A PrintConfig object was not supplied to apply_print_config()"); - } - %}; - - Ref config() - %code{% RETVAL = const_cast(static_cast(static_cast(&THIS->config()))); %}; -}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 5174bdfa9..42ca74292 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -125,18 +125,9 @@ Ref O_OBJECT_SLIC3R_T Layer* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T -CoolingBuffer* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -GCode* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - Axis T_UV ExtrusionLoopRole T_UV ExtrusionRole T_UV -FlowRole T_UV SurfaceType T_UV # we return these types whenever we want the items to be cloned diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 243e80dbc..bf1e8e2ac 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -92,18 +92,6 @@ %typemap{Layer*}; %typemap{Ref}{simple}; -%typemap{CoolingBuffer*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; - -%typemap{GCode*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; - -//%typemap{GCodePreviewData*}; -//%typemap{Ref}{simple}; -//%typemap{Clone}{simple}; - %typemap{Points}; %typemap{Pointfs}; %typemap{Lines}; @@ -167,9 +155,3 @@ $CVar = (ExtrusionRole)SvUV($PerlVar); %}; }; -%typemap{FlowRole}{parsed}{ - %cpp_type{FlowRole}; - %precall_code{% - $CVar = (FlowRole)SvUV($PerlVar); - %}; -};