From 43e6e4f18c89257d2df547253fda600ed093d324 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 16 Jun 2020 12:57:49 +0200 Subject: [PATCH 01/70] Code refactoring: - PresetCombpBoxes are extracted to the separate file. - All preset icons are moved to the PresetComboBox from Preset and PresetBundle - First "steps" to add physical printers to the printers list on the sidebar. --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/GUI_ObjectList.cpp | 3 - src/slic3r/GUI/MainFrame.cpp | 12 - src/slic3r/GUI/Plater.cpp | 212 ++------ src/slic3r/GUI/Plater.hpp | 41 +- src/slic3r/GUI/Preset.cpp | 410 ++++---------- src/slic3r/GUI/Preset.hpp | 228 ++++++-- src/slic3r/GUI/PresetBundle.cpp | 270 +--------- src/slic3r/GUI/PresetBundle.hpp | 22 - src/slic3r/GUI/PresetComboBoxes.cpp | 794 ++++++++++++++++++++++++++++ src/slic3r/GUI/PresetComboBoxes.hpp | 179 +++++++ src/slic3r/GUI/Tab.cpp | 46 +- src/slic3r/GUI/Tab.hpp | 7 +- src/slic3r/GUI/wxExtensions.cpp | 88 --- src/slic3r/GUI/wxExtensions.hpp | 31 -- 15 files changed, 1304 insertions(+), 1041 deletions(-) create mode 100644 src/slic3r/GUI/PresetComboBoxes.cpp create mode 100644 src/slic3r/GUI/PresetComboBoxes.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 7e02c0fdd..98389e7da 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -80,6 +80,8 @@ set(SLIC3R_GUI_SOURCES GUI/MainFrame.cpp GUI/MainFrame.hpp GUI/Plater.cpp + GUI/PresetComboBoxes.hpp + GUI/PresetComboBoxes.cpp GUI/Plater.hpp GUI/GUI_ObjectList.cpp GUI/GUI_ObjectList.hpp diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index c11ed66ab..2f201180a 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -88,9 +88,6 @@ ObjectList::ObjectList(wxWindow* parent) : { // Fill CATEGORY_ICON { - // Note: `this` isn't passed to create_scaled_bitmap() here because of bugs in the widget, - // see note in PresetBundle::load_compatible_bitmaps() - // ptFFF CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers"); CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill"); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index ef837c200..d4ce21fc0 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -74,11 +74,6 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S SLIC3R_VERSION + _(L(" - Remember to check for updates at http://github.com/prusa3d/PrusaSlicer/releases"))); - /* Load default preset bitmaps before a tabpanel initialization, - * but after filling of an em_unit value - */ - wxGetApp().preset_bundle->load_default_preset_bitmaps(); - // initialize tabpanel and menubar init_tabpanel(); init_menubar(); @@ -540,11 +535,6 @@ void MainFrame::on_dpi_changed(const wxRect &suggested_rect) wxGetApp().update_fonts(); this->SetFont(this->normal_font()); - /* Load default preset bitmaps before a tabpanel initialization, - * but after filling of an em_unit value - */ - wxGetApp().preset_bundle->load_default_preset_bitmaps(); - // update Plater wxGetApp().plater()->msw_rescale(); @@ -586,8 +576,6 @@ void MainFrame::on_sys_color_changed() // update label colors in respect to the system mode wxGetApp().init_label_colours(); - wxGetApp().preset_bundle->load_default_preset_bitmaps(); - // update Plater wxGetApp().plater()->sys_color_changed(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 8c3a90370..339badc96 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -77,6 +76,7 @@ #include "../Utils/UndoRedo.hpp" #include "RemovableDriveManager.hpp" #include "InstanceCheck.hpp" +#include "PresetComboBoxes.hpp" #ifdef __APPLE__ #include "Gizmos/GLGizmosManager.hpp" @@ -253,153 +253,6 @@ void SlicedInfo::SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const w info_vec[idx].second->Show(show); } -PresetComboBox::PresetComboBox(wxWindow *parent, Preset::Type preset_type) : -PresetBitmapComboBox(parent, wxSize(15 * wxGetApp().em_unit(), -1)), - preset_type(preset_type), - last_selected(wxNOT_FOUND), - m_em_unit(wxGetApp().em_unit()) -{ - SetFont(wxGetApp().normal_font()); -#ifdef _WIN32 - // Workaround for ignoring CBN_EDITCHANGE events, which are processed after the content of the combo box changes, so that - // the index of the item inside CBN_EDITCHANGE may no more be valid. - EnableTextChangedEvents(false); -#endif /* _WIN32 */ - Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &evt) { - auto selected_item = evt.GetSelection(); - - auto marker = reinterpret_cast(this->GetClientData(selected_item)); - if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) { - this->SetSelection(this->last_selected); - evt.StopPropagation(); - if (marker >= LABEL_ITEM_WIZARD_PRINTERS) { - ConfigWizard::StartPage sp = ConfigWizard::SP_WELCOME; - switch (marker) { - case LABEL_ITEM_WIZARD_PRINTERS: sp = ConfigWizard::SP_PRINTERS; break; - case LABEL_ITEM_WIZARD_FILAMENTS: sp = ConfigWizard::SP_FILAMENTS; break; - case LABEL_ITEM_WIZARD_MATERIALS: sp = ConfigWizard::SP_MATERIALS; break; - } - wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); }); - } - } else if ( this->last_selected != selected_item || - wxGetApp().get_tab(this->preset_type)->get_presets()->current_is_dirty() ) { - this->last_selected = selected_item; - evt.SetInt(this->preset_type); - evt.Skip(); - } else { - evt.StopPropagation(); - } - }); - - if (preset_type == Slic3r::Preset::TYPE_FILAMENT) - { - Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) { - PresetBundle* preset_bundle = wxGetApp().preset_bundle; - const Preset* selected_preset = preset_bundle->filaments.find_preset(preset_bundle->filament_presets[extruder_idx]); - // Wide icons are shown if the currently selected preset is not compatible with the current printer, - // and red flag is drown in front of the selected preset. - bool wide_icons = selected_preset != nullptr && !selected_preset->is_compatible; - float scale = m_em_unit*0.1f; - - int shifl_Left = wide_icons ? int(scale * 16 + 0.5) : 0; -#if defined(wxBITMAPCOMBOBOX_OWNERDRAWN_BASED) - shifl_Left += int(scale * 4 + 0.5f); // IMAGE_SPACING_RIGHT = 4 for wxBitmapComboBox -> Space left of image -#endif - int icon_right_pos = shifl_Left + int(scale * (24+4) + 0.5); - int mouse_pos = event.GetLogicalPosition(wxClientDC(this)).x; - if (mouse_pos < shifl_Left || mouse_pos > icon_right_pos ) { - // Let the combo box process the mouse click. - event.Skip(); - return; - } - - // Swallow the mouse click and open the color picker. - - // get current color - DynamicPrintConfig* cfg = wxGetApp().get_tab(Preset::TYPE_PRINTER)->get_config(); - auto colors = static_cast(cfg->option("extruder_colour")->clone()); - wxColour clr(colors->values[extruder_idx]); - if (!clr.IsOk()) - clr = wxColour(0,0,0); // Don't set alfa to transparence - - auto data = new wxColourData(); - data->SetChooseFull(1); - data->SetColour(clr); - - wxColourDialog dialog(this, data); - dialog.CenterOnParent(); - if (dialog.ShowModal() == wxID_OK) - { - colors->values[extruder_idx] = dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); - - DynamicPrintConfig cfg_new = *cfg; - cfg_new.set_key_value("extruder_colour", colors); - - wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg_new); - preset_bundle->update_plater_filament_ui(extruder_idx, this); - wxGetApp().plater()->on_config_change(cfg_new); - } - }); - } - - edit_btn = new ScalableButton(parent, wxID_ANY, "cog"); - edit_btn->SetToolTip(_L("Click to edit preset")); - - edit_btn->Bind(wxEVT_BUTTON, ([preset_type, this](wxCommandEvent) - { - Tab* tab = wxGetApp().get_tab(preset_type); - if (!tab) - return; - - int page_id = wxGetApp().tab_panel()->FindPage(tab); - if (page_id == wxNOT_FOUND) - return; - - wxGetApp().tab_panel()->SetSelection(page_id); - - // Switch to Settings NotePad - wxGetApp().mainframe->select_tab(); - - /* In a case of a multi-material printing, for editing another Filament Preset - * it's needed to select this preset for the "Filament settings" Tab - */ - if (preset_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) - { - const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data(); - - // Call select_preset() only if there is new preset and not just modified - if ( !boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()) ) - { - const std::string& preset_name = wxGetApp().preset_bundle->filaments.get_preset_name_by_alias(selected_preset); - tab->select_preset(/*selected_preset*/preset_name); - } - } - })); -} - -PresetComboBox::~PresetComboBox() -{ - if (edit_btn) - edit_btn->Destroy(); -} - - -void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type) -{ - this->SetClientData(item, (void*)label_item_type); -} - -void PresetComboBox::check_selection(int selection) -{ - this->last_selected = selection; -} - -void PresetComboBox::msw_rescale() -{ - m_em_unit = wxGetApp().em_unit(); - edit_btn->msw_rescale(); -} - // Frequently changed parameters class FreqChangedParams : public OG_Settings @@ -697,12 +550,12 @@ struct Sidebar::priv ModeSizer *mode_sizer; wxFlexGridSizer *sizer_presets; - PresetComboBox *combo_print; - std::vector combos_filament; + PlaterPresetComboBox *combo_print; + std::vector combos_filament; wxBoxSizer *sizer_filaments; - PresetComboBox *combo_sla_print; - PresetComboBox *combo_sla_material; - PresetComboBox *combo_printer; + PlaterPresetComboBox *combo_sla_print; + PlaterPresetComboBox *combo_sla_material; + PlaterPresetComboBox *combo_printer; wxBoxSizer *sizer_params; FreqChangedParams *frequently_changed_parameters{ nullptr }; @@ -801,10 +654,10 @@ Sidebar::Sidebar(Plater *parent) p->sizer_filaments = new wxBoxSizer(wxVERTICAL); - auto init_combo = [this](PresetComboBox **combo, wxString label, Preset::Type preset_type, bool filament) { + auto init_combo = [this](PlaterPresetComboBox **combo, wxString label, Preset::Type preset_type, bool filament) { auto *text = new wxStaticText(p->presets_panel, wxID_ANY, label + " :"); text->SetFont(wxGetApp().small_font()); - *combo = new PresetComboBox(p->presets_panel, preset_type); + *combo = new PlaterPresetComboBox(p->presets_panel, preset_type); auto combo_and_btn_sizer = new wxBoxSizer(wxHORIZONTAL); combo_and_btn_sizer->Add(*combo, 1, wxEXPAND); @@ -941,8 +794,8 @@ Sidebar::Sidebar(Plater *parent) Sidebar::~Sidebar() {} -void Sidebar::init_filament_combo(PresetComboBox **combo, const int extr_idx) { - *combo = new PresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FILAMENT); +void Sidebar::init_filament_combo(PlaterPresetComboBox **combo, const int extr_idx) { + *combo = new PlaterPresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FILAMENT); // # copy icons from first choice // $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets; @@ -977,18 +830,18 @@ void Sidebar::update_all_preset_comboboxes() // Update the print choosers to only contain the compatible presets, update the dirty flags. if (print_tech == ptFFF) - preset_bundle.prints.update_plater_ui(p->combo_print); + p->combo_print->update(); else { - preset_bundle.sla_prints.update_plater_ui(p->combo_sla_print); - preset_bundle.sla_materials.update_plater_ui(p->combo_sla_material); + p->combo_sla_print->update(); + p->combo_sla_material->update(); } // Update the printer choosers, update the dirty flags. - preset_bundle.printers.update_plater_ui(p->combo_printer); + p->combo_printer->update(); // Update the filament choosers to only contain the compatible presets, update the color preview, // update the dirty flags. if (print_tech == ptFFF) { - for (size_t i = 0; i < p->combos_filament.size(); ++i) - preset_bundle.update_plater_filament_ui(i, p->combos_filament[i]); + for (PlaterPresetComboBox* cb : p->combos_filament) + cb->update(); } } @@ -1010,23 +863,22 @@ void Sidebar::update_presets(Preset::Type preset_type) preset_bundle.set_filament_preset(0, name); } - for (size_t i = 0; i < filament_cnt; i++) { - preset_bundle.update_plater_filament_ui(i, p->combos_filament[i]); - } + for (size_t i = 0; i < filament_cnt; i++) + p->combos_filament[i]->update(); break; } case Preset::TYPE_PRINT: - preset_bundle.prints.update_plater_ui(p->combo_print); + p->combo_print->update(); break; case Preset::TYPE_SLA_PRINT: - preset_bundle.sla_prints.update_plater_ui(p->combo_sla_print); + p->combo_sla_print->update(); break; case Preset::TYPE_SLA_MATERIAL: - preset_bundle.sla_materials.update_plater_ui(p->combo_sla_material); + p->combo_sla_material->update(); break; case Preset::TYPE_PRINTER: @@ -1062,18 +914,14 @@ void Sidebar::msw_rescale() p->mode_sizer->msw_rescale(); - // Rescale preset comboboxes in respect to the current em_unit ... - for (PresetComboBox* combo : std::vector { p->combo_print, + for (PlaterPresetComboBox* combo : std::vector { p->combo_print, p->combo_sla_print, p->combo_sla_material, p->combo_printer } ) combo->msw_rescale(); - for (PresetComboBox* combo : p->combos_filament) + for (PlaterPresetComboBox* combo : p->combos_filament) combo->msw_rescale(); - // ... then refill them and set min size to correct layout of the sidebar - update_all_preset_comboboxes(); - p->frequently_changed_parameters->msw_rescale(); p->object_list->msw_rescale(); p->object_manipulation->msw_rescale(); @@ -1096,12 +944,12 @@ void Sidebar::sys_color_changed() { // Update preset comboboxes in respect to the system color ... // combo->msw_rescale() updates icon on button, so use it - for (PresetComboBox* combo : std::vector{ p->combo_print, + for (PlaterPresetComboBox* combo : std::vector{ p->combo_print, p->combo_sla_print, p->combo_sla_material, p->combo_printer }) combo->msw_rescale(); - for (PresetComboBox* combo : p->combos_filament) + for (PlaterPresetComboBox* combo : p->combos_filament) combo->msw_rescale(); // ... then refill them and set min size to correct layout of the sidebar @@ -1458,7 +1306,7 @@ void Sidebar::update_ui_from_settings() update_sliced_info_sizer(); } -std::vector& Sidebar::combos_filament() +std::vector& Sidebar::combos_filament() { return p->combos_filament; } @@ -3339,7 +3187,7 @@ void Plater::priv::set_current_panel(wxPanel* panel) void Plater::priv::on_select_preset(wxCommandEvent &evt) { auto preset_type = static_cast(evt.GetInt()); - auto *combo = static_cast(evt.GetEventObject()); + auto *combo = static_cast(evt.GetEventObject()); // see https://github.com/prusa3d/PrusaSlicer/issues/3889 // Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender"), @@ -3368,7 +3216,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) // TODO: ? if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) { // Only update the plater UI for the 2nd and other filaments. - wxGetApp().preset_bundle->update_plater_filament_ui(idx, combo); + combo->update(); } else { wxWindowUpdateLocker noUpdates(sidebar->presets_panel()); @@ -5154,12 +5002,12 @@ void Plater::on_extruders_change(size_t num_extruders) size_t i = choices.size(); while ( i < num_extruders ) { - PresetComboBox* choice/*{ nullptr }*/; + PlaterPresetComboBox* choice/*{ nullptr }*/; sidebar().init_filament_combo(&choice, i); choices.push_back(choice); // initialize selection - wxGetApp().preset_bundle->update_plater_filament_ui(i, choice); + choice->update(); ++i; } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 5d60e006b..d2acc7632 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -6,14 +6,12 @@ #include #include -#include #include "Preset.hpp" #include "Selection.hpp" #include "libslic3r/BoundingBox.hpp" #include "Jobs/Job.hpp" -#include "wxExtensions.hpp" #include "Search.hpp" class wxButton; @@ -50,46 +48,13 @@ class Mouse3DController; struct Camera; class Bed3D; class GLToolbar; +class PlaterPresetComboBox; using t_optgroups = std::vector >; class Plater; enum class ActionButtonType : int; -class PresetComboBox : public PresetBitmapComboBox -{ -public: - PresetComboBox(wxWindow *parent, Preset::Type preset_type); - ~PresetComboBox(); - - ScalableButton* edit_btn { nullptr }; - - enum LabelItemType { - LABEL_ITEM_MARKER = 0xffffff01, - LABEL_ITEM_WIZARD_PRINTERS, - LABEL_ITEM_WIZARD_FILAMENTS, - LABEL_ITEM_WIZARD_MATERIALS, - - LABEL_ITEM_MAX, - }; - - void set_label_marker(int item, LabelItemType label_item_type = LABEL_ITEM_MARKER); - void set_extruder_idx(const int extr_idx) { extruder_idx = extr_idx; } - int get_extruder_idx() const { return extruder_idx; } - int em_unit() const { return m_em_unit; } - void check_selection(int selection); - - void msw_rescale(); - -private: - typedef std::size_t Marker; - - Preset::Type preset_type; - int last_selected; - int extruder_idx = -1; - int m_em_unit; -}; - class Sidebar : public wxPanel { ConfigOptionMode m_mode; @@ -101,7 +66,7 @@ public: Sidebar &operator=(const Sidebar &) = delete; ~Sidebar(); - void init_filament_combo(PresetComboBox **combo, const int extr_idx); + void init_filament_combo(PlaterPresetComboBox **combo, const int extr_idx); void remove_unused_filament_combos(const size_t current_extruder_count); void update_all_preset_comboboxes(); void update_presets(Slic3r::Preset::Type preset_type); @@ -139,7 +104,7 @@ public: void update_searcher(); void update_ui_from_settings(); - std::vector& combos_filament(); + std::vector& combos_filament(); Search::OptionsSearcher& get_searcher(); std::string& get_search_line(); diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index d810c399d..883dc438a 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -2,9 +2,7 @@ #include "Preset.hpp" #include "AppConfig.hpp" -#include "BitmapCache.hpp" #include "I18N.hpp" -#include "wxExtensions.hpp" #ifdef _MSC_VER #define WIN32_LEAN_AND_MEAN @@ -30,15 +28,9 @@ #include #include -#include -#include -#include -#include - #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" #include "libslic3r/PlaceholderParser.hpp" -#include "Plater.hpp" using boost::property_tree::ptree; @@ -590,10 +582,7 @@ const std::vector& Preset::sla_printer_options() PresetCollection::PresetCollection(Preset::Type type, const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name) : m_type(type), m_edited_preset(type, "", false), - m_idx_selected(0), - m_bitmap_main_frame(new wxBitmap), - m_bitmap_add(new wxBitmap), - m_bitmap_cache(new GUI::BitmapCache) + m_idx_selected(0) { // Insert just the default preset. this->add_default_preset(keys, defaults, default_name); @@ -602,12 +591,6 @@ PresetCollection::PresetCollection(Preset::Type type, const std::vectorget_selected_idx() == size_t(-1)) @@ -1119,279 +1092,15 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil // Delete the current preset, activate the first visible preset. //void PresetCollection::delete_current_preset(); -// Update the wxChoice UI component from this list of presets. -// Hide the -void PresetCollection::update_plater_ui(GUI::PresetComboBox *ui) -{ - if (ui == nullptr) - return; - - // Otherwise fill in the list from scratch. - ui->Freeze(); - ui->Clear(); - size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected - - const Preset &selected_preset = this->get_selected_preset(); - // Show wide icons if the currently selected preset is not compatible with the current printer, - // and draw a red flag in front of the selected preset. - bool wide_icons = ! selected_preset.is_compatible && m_bitmap_incompatible != nullptr; - - /* It's supposed that standard size of an icon is 16px*16px for 100% scaled display. - * So set sizes for solid_colored icons used for filament preset - * and scale them in respect to em_unit value - */ - const float scale_f = ui->em_unit() * 0.1f; - const int icon_height = 16 * scale_f + 0.5f; - const int icon_width = 16 * scale_f + 0.5f; - const int thin_space_icon_width = 4 * scale_f + 0.5f; - const int wide_space_icon_width = 6 * scale_f + 0.5f; - - std::map nonsys_presets; - wxString selected = ""; - wxString tooltip = ""; - if (!this->m_presets.front().is_visible) - ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap)); - for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++ i) { - const Preset &preset = this->m_presets[i]; - if (! preset.is_visible || (! preset.is_compatible && i != m_idx_selected)) - continue; - - std::string bitmap_key = ""; - // !!! Temporary solution, till refactoring: create and use "sla_printer" icon instead of m_bitmap_main_frame - wxBitmap main_bmp = m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap; - if (m_type == Preset::TYPE_PRINTER && preset.printer_technology()==ptSLA ) { - bitmap_key = "sla_printer"; - main_bmp = create_scaled_bitmap("sla_printer"); - } - - // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left - // to the filament color image. - if (wide_icons) - bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; - bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; - wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(preset.is_compatible ? m_bitmap_cache->mkclear(icon_width, icon_height) : *m_bitmap_incompatible); - // Paint the color bars. - bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); - bmps.emplace_back(main_bmp); - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); - bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(icon_width, icon_height)); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } - - const std::string name = preset.alias.empty() ? preset.name : preset.alias; - if (preset.is_default || preset.is_system) { - ui->Append(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), - (bmp == 0) ? main_bmp : *bmp); - if (i == m_idx_selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) { - selected_preset_item = ui->GetCount() - 1; - tooltip = wxString::FromUTF8(preset.name.c_str()); - } - } - else - { - nonsys_presets.emplace(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/); - if (i == m_idx_selected) { - selected = wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); - tooltip = wxString::FromUTF8(preset.name.c_str()); - } - } - if (i + 1 == m_num_default_presets) - ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap)); - } - if (!nonsys_presets.empty()) - { - ui->set_label_marker(ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap)); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - ui->Append(it->first, *it->second); - if (it->first == selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = ui->GetCount() - 1; - } - } - if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) { - std::string bitmap_key = ""; - // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left - // to the filament color image. - if (wide_icons) - bitmap_key += "wide,"; - bitmap_key += "edit_preset_list"; - wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(m_bitmap_cache->mkclear(icon_width, icon_height)); - // Paint the color bars. - bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); - bmps.emplace_back(*m_bitmap_main_frame); - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); -// bmps.emplace_back(m_bitmap_add ? *m_bitmap_add : wxNullBitmap); - bmps.emplace_back(create_scaled_bitmap("edit_uni")); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } - if (m_type == Preset::TYPE_SLA_MATERIAL) - ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove materials")), *bmp), GUI::PresetComboBox::LABEL_ITEM_WIZARD_MATERIALS); - else - ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove printers")), *bmp), GUI::PresetComboBox::LABEL_ITEM_WIZARD_PRINTERS); - } - - /* But, if selected_preset_item is still equal to INT_MAX, it means that - * there is no presets added to the list. - * So, select last combobox item ("Add/Remove preset") - */ - if (selected_preset_item == INT_MAX) - selected_preset_item = ui->GetCount() - 1; - - ui->SetSelection(selected_preset_item); - ui->SetToolTip(tooltip.IsEmpty() ? ui->GetString(selected_preset_item) : tooltip); - ui->check_selection(selected_preset_item); - ui->Thaw(); - - // Update control min size after rescale (changed Display DPI under MSW) - if (ui->GetMinWidth() != 20 * ui->em_unit()) - ui->SetMinSize(wxSize(20 * ui->em_unit(), ui->GetSize().GetHeight())); -} - -size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible, const int em/* = 10*/) -{ - if (ui == nullptr) - return 0; - ui->Freeze(); - ui->Clear(); - size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected - - /* It's supposed that standard size of an icon is 16px*16px for 100% scaled display. - * So set sizes for solid_colored(empty) icons used for preset - * and scale them in respect to em_unit value - */ - const float scale_f = em * 0.1f; - const int icon_height = 16 * scale_f + 0.5f; - const int icon_width = 16 * scale_f + 0.5f; - - std::map nonsys_presets; - wxString selected = ""; - if (!this->m_presets.front().is_visible) - ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap); - for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++i) { - const Preset &preset = this->m_presets[i]; - if (! preset.is_visible || (! show_incompatible && ! preset.is_compatible && i != m_idx_selected)) - continue; - std::string bitmap_key = "tab"; - - // !!! Temporary solution, till refactoring: create and use "sla_printer" icon instead of m_bitmap_main_frame - wxBitmap main_bmp = m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap; - if (m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA) { - bitmap_key = "sla_printer"; - main_bmp = create_scaled_bitmap("sla_printer"); - } - - bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; - bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; - wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - const wxBitmap* tmp_bmp = preset.is_compatible ? m_bitmap_compatible : m_bitmap_incompatible; - bmps.emplace_back((tmp_bmp == 0) ? main_bmp : *tmp_bmp); - // Paint a lock at the system presets. - bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(icon_width, icon_height)); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } - - if (preset.is_default || preset.is_system) { - ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), - (bmp == 0) ? main_bmp : *bmp); - if (i == m_idx_selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = ui->GetCount() - 1; - } - else - { - nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/); - if (i == m_idx_selected) - selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); - } - if (i + 1 == m_num_default_presets) - ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap); - } - if (!nonsys_presets.empty()) - { - ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - ui->Append(it->first, *it->second); - if (it->first == selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = ui->GetCount() - 1; - } - } - if (m_type == Preset::TYPE_PRINTER) { - wxBitmap *bmp = m_bitmap_cache->find("edit_printer_list"); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - bmps.emplace_back(*m_bitmap_main_frame); -// bmps.emplace_back(m_bitmap_add ? *m_bitmap_add : wxNullBitmap); - bmps.emplace_back(create_scaled_bitmap("edit_uni")); - bmp = m_bitmap_cache->insert("add_printer_tab", bmps); - } - ui->Append(PresetCollection::separator("Add a new printer"), *bmp); - } - - /* But, if selected_preset_item is still equal to INT_MAX, it means that - * there is no presets added to the list. - * So, select last combobox item ("Add/Remove preset") - */ - if (selected_preset_item == INT_MAX) - selected_preset_item = ui->GetCount() - 1; - - ui->SetSelection(selected_preset_item); - ui->SetToolTip(ui->GetString(selected_preset_item)); - ui->Thaw(); - return selected_preset_item; -} - -// Update a dirty floag of the current preset, update the labels of the UI component accordingly. +// Update a dirty flag of the current preset // Return true if the dirty flag changed. -bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui) +bool PresetCollection::update_dirty() { - wxWindowUpdateLocker noUpdates(ui); - // 1) Update the dirty flag of the current preset. bool was_dirty = this->get_selected_preset().is_dirty; bool is_dirty = current_is_dirty(); this->get_selected_preset().is_dirty = is_dirty; this->get_edited_preset().is_dirty = is_dirty; - // 2) Update the labels. - for (unsigned int ui_id = 0; ui_id < ui->GetCount(); ++ ui_id) { - std::string old_label = ui->GetString(ui_id).utf8_str().data(); - std::string preset_name = Preset::remove_suffix_modified(old_label); - const Preset *preset = this->find_preset(preset_name, false); -// The old_label could be the "----- system presets ------" or the "------- user presets --------" separator. -// assert(preset != nullptr); - if (preset != nullptr) { - std::string new_label = preset->is_dirty ? preset->name + g_suffix_modified : preset->name; - if (old_label != new_label) - ui->SetString(ui_id, wxString::FromUTF8(new_label.c_str())); - } - } -#ifdef __APPLE__ - // wxWidgets on OSX do not upload the text of the combo box line automatically. - // Force it to update by re-selecting. - ui->SetSelection(ui->GetSelection()); -#endif /* __APPLE __ */ + return was_dirty != is_dirty; } @@ -1605,16 +1314,6 @@ std::string PresetCollection::path_from_name(const std::string &new_name) const return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); } -void PresetCollection::clear_bitmap_cache() -{ - m_bitmap_cache->clear(); -} - -wxString PresetCollection::separator(const std::string &label) -{ - return wxString::FromUTF8(PresetCollection::separator_head()) + _(label) + wxString::FromUTF8(PresetCollection::separator_tail()); -} - const Preset& PrinterPresetCollection::default_preset_for(const DynamicPrintConfig &config) const { const ConfigOptionEnumGeneric *opt_printer_technology = config.opt("printer_technology"); @@ -1631,7 +1330,108 @@ const Preset* PrinterPresetCollection::find_by_model_id(const std::string &model return it != cend() ? &*it : nullptr; } +/* +PhysicalPrinter& PhysicalPrinterCollection::load_external_printer( + // Path to the profile source file (a G-code, an AMF or 3MF file, a config file) + const std::string& path, + // Name of the profile, derived from the source file name. + const std::string& name, + // Original name of the profile, extracted from the loaded config. Empty, if the name has not been stored. + const std::string& original_name, + // Config to initialize the preset from. + const DynamicPrintConfig& config, + // Select the preset after loading? + bool select) +{ + // Load the preset over a default preset, so that the missing fields are filled in from the default preset. + DynamicPrintConfig cfg(this->default_printer().config); + cfg.apply_only(config, cfg.keys(), true); + // Is there a preset already loaded with the name stored inside the config? + std::deque::iterator it = this->find_printer_internal(original_name); + bool found = it != m_printers.end() && it->name == original_name; + if (!found) { + // Try to match the original_name against the "renamed_from" profile names of loaded system profiles. + / * + it = this->find_preset_renamed(original_name); + found = it != m_presets.end(); + * / + } + if (found) { + if (profile_print_params_same(it->config, cfg)) { + // The preset exists and it matches the values stored inside config. + if (select) + this->select_printer(it - m_printers.begin()); + return *it; + } + if (profile_host_params_same_or_anonymized(it->config, cfg) == ProfileHostParams::Anonymized) { + // The project being loaded is anonymized. Replace the empty host keys of the loaded profile with the data from the original profile. + // See "Octoprint Settings when Opening a .3MF file" GH issue #3244 + auto opt_update = [it, &cfg](const std::string& opt_key) { + auto opt = it->config.option(opt_key); + if (opt != nullptr) + cfg.set_key_value(opt_key, opt->clone()); + }; + opt_update("print_host"); + opt_update("printhost_apikey"); + opt_update("printhost_cafile"); + } + } + // The external preset does not match an internal preset, load the external preset. + std::string new_name; + for (size_t idx = 0;; ++idx) { + std::string suffix; + if (original_name.empty()) { + if (idx > 0) + suffix = " (" + std::to_string(idx) + ")"; + } + else { + if (idx == 0) + suffix = " (" + original_name + ")"; + else + suffix = " (" + original_name + "-" + std::to_string(idx) + ")"; + } + new_name = name + suffix; + it = this->find_printer_internal(new_name); + if (it == m_printers.end() || it->name != new_name) + // Unique profile name. Insert a new profile. + break; + if (profile_print_params_same(it->config, cfg)) { + // The preset exists and it matches the values stored inside config. + if (select) + this->select_printer(it - m_printers.begin()); + return *it; + } + // Form another profile name. + } + // Insert a new profile. + PhysicalPrinter& printer = this->load_printer(path, new_name, std::move(cfg), select); + return printer; +} + +void PhysicalPrinterCollection::save_printer(const std::string& new_name) +{ + // 1) Find the printer with a new_name or create a new one, + // initialize it with the edited config. + auto it = this->find_printer_internal(new_name); + if (it != m_printers.end() && it->name == new_name) { + // Preset with the same name found. + PhysicalPrinter& printer = *it; + // Overwriting an existing preset. + printer.config = std::move(m_edited_printer.config); + } + else { + // Creating a new printer. + PhysicalPrinter& printer = *m_printers.insert(it, m_edited_printer); + std::string old_name = printer.name; + printer.name = new_name; + } + // 2) Activate the saved preset. + this->select_printer_by_name(new_name, true); + // 3) Store the active preset to disk. + this->get_selected_preset().save(); +} +*/ namespace PresetUtils { const VendorProfile::PrinterModel* system_printer_model(const Preset &preset) { diff --git a/src/slic3r/GUI/Preset.hpp b/src/slic3r/GUI/Preset.hpp index dc0078091..8a8fa024b 100644 --- a/src/slic3r/GUI/Preset.hpp +++ b/src/slic3r/GUI/Preset.hpp @@ -24,11 +24,6 @@ namespace Slic3r { class AppConfig; class PresetBundle; -namespace GUI { - class BitmapCache; - class PresetComboBox; -} - enum ConfigFileType { CONFIG_FILE_TYPE_UNKNOWN, @@ -322,18 +317,6 @@ public: // returns true if the preset was deleted successfully. bool delete_preset(const std::string& name); - // Load default bitmap to be placed at the wxBitmapComboBox of a MainFrame. - void load_bitmap_default(const std::string &file_name); - - // Load "add new printer" bitmap to be placed at the wxBitmapComboBox of a MainFrame. - void load_bitmap_add(const std::string &file_name); - - // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items. - void set_bitmap_compatible (const wxBitmap *bmp) { m_bitmap_compatible = bmp; } - void set_bitmap_incompatible(const wxBitmap *bmp) { m_bitmap_incompatible = bmp; } - void set_bitmap_lock (const wxBitmap *bmp) { m_bitmap_lock = bmp; } - void set_bitmap_lock_open (const wxBitmap *bmp) { m_bitmap_lock_open = bmp; } - // Enable / disable the "- default -" preset. void set_default_suppressed(bool default_suppressed); bool is_default_suppressed() const { return m_default_suppressed; } @@ -446,18 +429,9 @@ public: // Return a sorted list of system preset names. std::vector system_preset_names() const; - // Update the choice UI from the list of presets. - // If show_incompatible, all presets are shown, otherwise only the compatible presets are shown. - // If an incompatible preset is selected, it is shown as well. - size_t update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible, const int em = 10); - // Update the choice UI from the list of presets. - // Only the compatible presets are shown. - // If an incompatible preset is selected, it is shown as well. - void update_plater_ui(GUI::PresetComboBox *ui); - - // Update a dirty floag of the current preset, update the labels of the UI component accordingly. + // Update a dirty flag of the current preset // Return true if the dirty flag changed. - bool update_dirty_ui(wxBitmapComboBox *ui); + bool update_dirty(); // Select a profile by its name. Return true if the selection changed. // Without force, the selection is only updated if the index changes. @@ -467,16 +441,7 @@ public: // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. std::string path_from_name(const std::string &new_name) const; - void clear_bitmap_cache(); - -#ifdef __linux__ - static const char* separator_head() { return "------- "; } - static const char* separator_tail() { return " -------"; } -#else /* __linux__ */ - static const char* separator_head() { return "————— "; } - static const char* separator_tail() { return " —————"; } -#endif /* __linux__ */ - static wxString separator(const std::string &label); + size_t num_default_presets() { return m_num_default_presets; } protected: // Select a preset, if it exists. If it does not exist, select an invalid (-1) index. @@ -547,23 +512,10 @@ private: // Is the "- default -" preset suppressed? bool m_default_suppressed = true; size_t m_num_default_presets = 0; - // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items of a Plater. - // These bitmaps are not owned by PresetCollection, but by a PresetBundle. - const wxBitmap *m_bitmap_compatible = nullptr; - const wxBitmap *m_bitmap_incompatible = nullptr; - const wxBitmap *m_bitmap_lock = nullptr; - const wxBitmap *m_bitmap_lock_open = nullptr; - // Marks placed at the wxBitmapComboBox of a MainFrame. - // These bitmaps are owned by PresetCollection. - wxBitmap *m_bitmap_main_frame; - // "Add printer profile" icon, owned by PresetCollection. - wxBitmap *m_bitmap_add; + // Path to the directory to store the config files into. std::string m_dir_path; - // Caching color bitmaps for the filament combo box. - GUI::BitmapCache *m_bitmap_cache = nullptr; - // to access select_preset_by_name_strict() friend class PresetBundle; }; @@ -585,6 +537,178 @@ namespace PresetUtils { const VendorProfile::PrinterModel* system_printer_model(const Preset &preset); } // namespace PresetUtils + +////////////////////////////////////////////////////////////////////// + +class PhysicalPrinter +{ +public: + PhysicalPrinter(const std::string& name) : name(name) {} + + // Name of the Physical Printer, usually derived form the file name. + std::string name; + // File name of the Physical Printer. + std::string file; + // Name of the related Printer preset + std::string preset_name; + + // Has this profile been loaded? + bool loaded = false; + + // Configuration data, loaded from a file, or set from the defaults. + DynamicPrintConfig config; + + void save() { this->config.save(this->file); } + + // Return a printer technology, return ptFFF if the printer technology is not set. + static PrinterTechnology printer_technology(const DynamicPrintConfig& cfg) { + auto* opt = cfg.option>("printer_technology"); + // The following assert may trigger when importing some legacy profile, + // but it is safer to keep it here to capture the cases where the "printer_technology" key is queried, where it should not. + return (opt == nullptr) ? ptFFF : opt->value; + } + PrinterTechnology printer_technology() const { return printer_technology(this->config); } + + // Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection. + bool operator<(const Preset& other) const { return this->name < other.name; } + +protected: + friend class PhysicalPrinterCollection; +}; +/* +// Collections of presets of the same type (one of the Print, Filament or Printer type). +class PhysicalPrinterCollection +{ +public: + // Initialize the PresetCollection with the "- default -" preset. + PhysicalPrinterCollection(const std::vector& keys) : m_idx_selected(0) {} + ~PhysicalPrinterCollection() {} + + typedef std::deque::iterator Iterator; + typedef std::deque::const_iterator ConstIterator; + Iterator begin() { return m_printers.begin(); } + ConstIterator begin() const { return m_printers.cbegin(); } + ConstIterator cbegin() const { return m_printers.cbegin(); } + Iterator end() { return m_printers.end(); } + ConstIterator end() const { return m_printers.cend(); } + ConstIterator cend() const { return m_printers.cend(); } + + void reset(bool delete_files) {}; + + const std::deque& operator()() const { return m_printers; } + + // Load ini files of the particular type from the provided directory path. + void load_printers(const std::string& dir_path, const std::string& subdir){}; + + // Load a preset from an already parsed config file, insert it into the sorted sequence of presets + // and select it, losing previous modifications. + PhysicalPrinter& load_printer(const std::string& path, const std::string& name, const DynamicPrintConfig& config, bool select = true); + PhysicalPrinter& load_printer(const std::string& path, const std::string& name, DynamicPrintConfig&& config, bool select = true); + + PhysicalPrinter& load_external_printer( + // Path to the profile source file (a G-code, an AMF or 3MF file, a config file) + const std::string& path, + // Name of the profile, derived from the source file name. + const std::string& name, + // Original name of the profile, extracted from the loaded config. Empty, if the name has not been stored. + const std::string& original_name, + // Config to initialize the preset from. + const DynamicPrintConfig& config, + // Select the preset after loading? + bool select = true); + + // Save the printer under a new name. If the name is different from the old one, + // a new printer is stored into the list of printers. + // ? New printer is activated. + void save_printer(const std::string& new_name); + + // Delete the current preset, activate the first visible preset. + // returns true if the preset was deleted successfully. + bool delete_current_printer() {return true;} + // Delete the current preset, activate the first visible preset. + // returns true if the preset was deleted successfully. + bool delete_printer(const std::string& name) { return true; } + + // Select a printer. If an invalid index is provided, the first visible printer is selected. + PhysicalPrinter& select_printer(size_t idx); + // Return the selected preset, without the user modifications applied. + PhysicalPrinter& get_selected_preset() { return m_printers[m_idx_selected]; } + const PhysicalPrinter& get_selected_preset() const { return m_printers[m_idx_selected]; } + size_t get_selected_idx() const { return m_idx_selected; } + // Returns the name of the selected preset, or an empty string if no preset is selected. + std::string get_selected_preset_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_preset().name; } + PhysicalPrinter& get_edited_preset() { return m_edited_printer; } + const PhysicalPrinter& get_edited_preset() const { return m_edited_printer; } + + // Return a preset possibly with modifications. + PhysicalPrinter& default_printer(size_t idx = 0) { return m_printers[idx]; } + const PhysicalPrinter& default_printer(size_t idx = 0) const { return m_printers[idx]; } + + // used to update preset_choice from Tab + const std::deque& get_presets() const { return m_printers; } + size_t get_idx_selected() { return m_idx_selected; } + + // Return a preset by an index. If the preset is active, a temporary copy is returned. + PhysicalPrinter& printer(size_t idx) { return (idx == m_idx_selected) ? m_edited_printer : m_printers[idx]; } + const PhysicalPrinter& printer(size_t idx) const { return const_cast(this)->printer(idx); } + + // Return a preset by its name. If the preset is active, a temporary copy is returned. + // If a preset is not found by its name, null is returned. + PhysicalPrinter* find_printer(const std::string& name, bool first_visible_if_not_found = false); + const PhysicalPrinter* find_printer(const std::string& name, bool first_visible_if_not_found = false) const + { + return const_cast(this)->find_printer(name, first_visible_if_not_found); + } + + // Return number of presets including the "- default -" preset. + size_t size() const { return m_printers.size(); } + + // Select a profile by its name. Return true if the selection changed. + // Without force, the selection is only updated if the index changes. + // With force, the changes are reverted if the new index is the same as the old index. + bool select_printer_by_name(const std::string& name, bool force) {}; + + // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. + std::string path_from_name(const std::string& new_name) const; + +private: +// PhysicalPrinterCollection(); + PhysicalPrinterCollection(const PhysicalPrinterCollection& other); + PhysicalPrinterCollection& operator=(const PhysicalPrinterCollection& other); + + // Find a preset position in the sorted list of presets. + // The "-- default -- " preset is always the first, so it needs + // to be handled differently. + // If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name. + std::deque::iterator find_printer_internal(const std::string& name) + { + PhysicalPrinter key(name); + auto it = std::lower_bound(m_printers.begin()+0, m_printers.end(), key); + return it; + } + std::deque::const_iterator find_printer_internal(const std::string& name) const + { + return const_cast(this)->find_printer_internal(name); + } + + static std::vector dirty_options(const Preset* edited, const Preset* reference, const bool is_printer_type = false); + + // List of presets, starting with the "- default -" preset. + // Use deque to force the container to allocate an object per each entry, + // so that the addresses of the presets don't change during resizing of the container. + std::deque m_printers; + // Initially this printer contains a copy of the selected printer. Later on, this copy may be modified by the user. + PhysicalPrinter m_edited_printer; + // Selected preset. + size_t m_idx_selected; + + // Path to the directory to store the config files into. + std::string m_dir_path; +}; + +////////////////////////////////////////////////////////////////////// +*/ + } // namespace Slic3r #endif /* slic3r_Preset_hpp_ */ diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index ba806a0b2..024884b00 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -1,12 +1,10 @@ #include #include "PresetBundle.hpp" -#include "BitmapCache.hpp" #include "Plater.hpp" -#include "I18N.hpp" -#include "wxExtensions.hpp" #include +#include #include #include #include @@ -21,16 +19,13 @@ #include #include -#include #include -#include -#include -#include #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" #include "libslic3r/Model.hpp" #include "GUI_App.hpp" +#include "libslic3r/CustomGCode.hpp" // Store the print/filament/printer presets into a "presets" subdirectory of the Slic3rPE config dir. @@ -52,12 +47,7 @@ PresetBundle::PresetBundle() : filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast(FullPrintConfig::defaults())), sla_materials(Preset::TYPE_SLA_MATERIAL, Preset::sla_material_options(), static_cast(SLAFullPrintConfig::defaults())), sla_prints(Preset::TYPE_SLA_PRINT, Preset::sla_print_options(), static_cast(SLAFullPrintConfig::defaults())), - printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast(FullPrintConfig::defaults()), "- default FFF -"), - m_bitmapCompatible(new wxBitmap), - m_bitmapIncompatible(new wxBitmap), - m_bitmapLock(new wxBitmap), - m_bitmapLockOpen(new wxBitmap), - m_bitmapCache(new GUI::BitmapCache) + printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast(FullPrintConfig::defaults()), "- default FFF -") { if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) wxImage::AddHandler(new wxPNGHandler); @@ -112,16 +102,6 @@ PresetBundle::PresetBundle() : preset.inherits(); } - // Load the default preset bitmaps. - // #ys_FIXME_to_delete we'll load them later, using em_unit() -// this->prints .load_bitmap_default("cog"); -// this->sla_prints .load_bitmap_default("package_green.png"); -// this->filaments .load_bitmap_default("spool.png"); -// this->sla_materials.load_bitmap_default("package_green.png"); -// this->printers .load_bitmap_default("printer_empty.png"); -// this->printers .load_bitmap_add("add.png"); -// this->load_compatible_bitmaps(); - // Re-activate the default presets, so their "edited" preset copies will be updated with the additional configuration values above. this->prints .select_preset(0); this->sla_prints .select_preset(0); @@ -134,20 +114,6 @@ PresetBundle::PresetBundle() : PresetBundle::~PresetBundle() { - assert(m_bitmapCompatible != nullptr); - assert(m_bitmapIncompatible != nullptr); - assert(m_bitmapLock != nullptr); - assert(m_bitmapLockOpen != nullptr); - delete m_bitmapCompatible; - m_bitmapCompatible = nullptr; - delete m_bitmapIncompatible; - m_bitmapIncompatible = nullptr; - delete m_bitmapLock; - m_bitmapLock = nullptr; - delete m_bitmapLockOpen; - m_bitmapLockOpen = nullptr; - delete m_bitmapCache; - m_bitmapCache = nullptr; } void PresetBundle::reset(bool delete_files) @@ -486,36 +452,6 @@ void PresetBundle::export_selections(AppConfig &config) config.set("presets", "printer", printers.get_selected_preset_name()); } -void PresetBundle::load_compatible_bitmaps() -{ - *m_bitmapCompatible = create_scaled_bitmap("flag_green"); - *m_bitmapIncompatible = create_scaled_bitmap("flag_red"); - *m_bitmapLock = create_scaled_bitmap("lock_closed"); - *m_bitmapLockOpen = create_scaled_bitmap("lock_open"); - - prints .set_bitmap_compatible(m_bitmapCompatible); - filaments .set_bitmap_compatible(m_bitmapCompatible); - sla_prints .set_bitmap_compatible(m_bitmapCompatible); - sla_materials.set_bitmap_compatible(m_bitmapCompatible); - - prints .set_bitmap_incompatible(m_bitmapIncompatible); - filaments .set_bitmap_incompatible(m_bitmapIncompatible); - sla_prints .set_bitmap_incompatible(m_bitmapIncompatible); - sla_materials.set_bitmap_incompatible(m_bitmapIncompatible); - - prints .set_bitmap_lock(m_bitmapLock); - filaments .set_bitmap_lock(m_bitmapLock); - sla_prints .set_bitmap_lock(m_bitmapLock); - sla_materials.set_bitmap_lock(m_bitmapLock); - printers .set_bitmap_lock(m_bitmapLock); - - prints .set_bitmap_lock_open(m_bitmapLock); - filaments .set_bitmap_lock_open(m_bitmapLock); - sla_prints .set_bitmap_lock_open(m_bitmapLock); - sla_materials.set_bitmap_lock_open(m_bitmapLock); - printers .set_bitmap_lock_open(m_bitmapLock); -} - DynamicPrintConfig PresetBundle::full_config() const { return (this->printers.get_edited_preset().printer_technology() == ptFFF) ? @@ -886,7 +822,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool // 4) Load the project config values (the per extruder wipe matrix etc). this->project_config.apply_only(config, s_project_options); - update_custom_gcode_per_print_z_from_config(GUI::wxGetApp().plater()->model().custom_gcode_per_print_z, &this->project_config); + CustomGCode::update_custom_gcode_per_print_z_from_config(GUI::wxGetApp().plater()->model().custom_gcode_per_print_z, &this->project_config); break; } @@ -1544,207 +1480,11 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst // an optional "(modified)" suffix will be removed from the filament name. void PresetBundle::set_filament_preset(size_t idx, const std::string &name) { - if (name.find_first_of(PresetCollection::separator_head()) == 0) - return; - - if (idx >= filament_presets.size()) + if (idx >= filament_presets.size()) filament_presets.resize(idx + 1, filaments.default_preset().name); filament_presets[idx] = Preset::remove_suffix_modified(name); } -void PresetBundle::load_default_preset_bitmaps() -{ - // Clear bitmap cache, before load new scaled default preset bitmaps - m_bitmapCache->clear(); - this->prints.clear_bitmap_cache(); - this->sla_prints.clear_bitmap_cache(); - this->filaments.clear_bitmap_cache(); - this->sla_materials.clear_bitmap_cache(); - this->printers.clear_bitmap_cache(); - - this->prints.load_bitmap_default("cog"); - this->sla_prints.load_bitmap_default("cog"); - this->filaments.load_bitmap_default("spool.png"); - this->sla_materials.load_bitmap_default("resin"); - this->printers.load_bitmap_default("printer"); - this->printers.load_bitmap_add("add.png"); - this->load_compatible_bitmaps(); -} - -void PresetBundle::update_plater_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui) -{ - if (ui == nullptr || this->printers.get_edited_preset().printer_technology() == ptSLA || - this->filament_presets.size() <= idx_extruder ) - return; - - unsigned char rgb[3]; - std::string extruder_color = this->printers.get_edited_preset().config.opt_string("extruder_colour", idx_extruder); - if (!m_bitmapCache->parse_color(extruder_color, rgb)) - // Extruder color is not defined. - extruder_color.clear(); - - // Fill in the list from scratch. - ui->Freeze(); - ui->Clear(); - size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected - - const Preset *selected_preset = this->filaments.find_preset(this->filament_presets[idx_extruder]); - // Show wide icons if the currently selected preset is not compatible with the current printer, - // and draw a red flag in front of the selected preset. - bool wide_icons = selected_preset != nullptr && ! selected_preset->is_compatible && m_bitmapIncompatible != nullptr; - assert(selected_preset != nullptr); - std::map nonsys_presets; - wxString selected_str = ""; - if (!this->filaments().front().is_visible) - ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap)); - - /* It's supposed that standard size of an icon is 16px*16px for 100% scaled display. - * So set sizes for solid_colored icons used for filament preset - * and scale them in respect to em_unit value - */ - const float scale_f = ui->em_unit() * 0.1f; - - // To avoid the errors of number rounding for different combination of monitor configuration, - // let use scaled 8px, as a smallest icon unit - const int icon_unit = 8 * scale_f + 0.5f; - const int normal_icon_width = 2 * icon_unit; //16 * scale_f + 0.5f; - const int thin_icon_width = icon_unit; //8 * scale_f + 0.5f; - const int wide_icon_width = 3 * icon_unit; //24 * scale_f + 0.5f; - - const int space_icon_width = 2 * scale_f + 0.5f; - - // To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size, so - // set a bitmap height to m_bitmapLock->GetHeight() - // - // To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size. - // But for some display scaling (for example 125% or 175%) normal_icon_width differs from icon width. - // So: - // for nonsystem presets set a width of empty bitmap to m_bitmapLock->GetWidth() - // for compatible presets set a width of empty bitmap to m_bitmapIncompatible->GetWidth() - // - // Note, under OSX we should use a Scaled Height/Width because of Retina scale -#ifdef __APPLE__ - const int icon_height = m_bitmapLock->GetScaledHeight(); - const int lock_icon_width = m_bitmapLock->GetScaledWidth(); - const int flag_icon_width = m_bitmapIncompatible->GetScaledWidth(); -#else - const int icon_height = m_bitmapLock->GetHeight(); - const int lock_icon_width = m_bitmapLock->GetWidth(); - const int flag_icon_width = m_bitmapIncompatible->GetWidth(); -#endif - - wxString tooltip = ""; - - for (int i = this->filaments().front().is_visible ? 0 : 1; i < int(this->filaments().size()); ++i) { - const Preset &preset = this->filaments.preset(i); - bool selected = this->filament_presets[idx_extruder] == preset.name; - if (! preset.is_visible || (! preset.is_compatible && ! selected)) - continue; - // Assign an extruder color to the selected item if the extruder color is defined. - std::string filament_rgb = preset.config.opt_string("filament_colour", 0); - std::string extruder_rgb = (selected && !extruder_color.empty()) ? extruder_color : filament_rgb; - bool single_bar = filament_rgb == extruder_rgb; - std::string bitmap_key = single_bar ? filament_rgb : filament_rgb + extruder_rgb; - // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left - // to the filament color image. - if (wide_icons) - bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; - bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; - if (preset.is_dirty) - bitmap_key += ",drty"; - wxBitmap *bitmap = m_bitmapCache->find(bitmap_key); - if (bitmap == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(preset.is_compatible ? m_bitmapCache->mkclear(flag_icon_width, icon_height) : *m_bitmapIncompatible); - // Paint the color bars. - m_bitmapCache->parse_color(filament_rgb, rgb); - bmps.emplace_back(m_bitmapCache->mksolid(single_bar ? wide_icon_width : normal_icon_width, icon_height, rgb)); - if (! single_bar) { - m_bitmapCache->parse_color(extruder_rgb, rgb); - bmps.emplace_back(m_bitmapCache->mksolid(thin_icon_width, icon_height, rgb)); - } - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmapCache->mkclear(space_icon_width, icon_height)); - bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmapLock : m_bitmapCache->mkclear(lock_icon_width, icon_height)); -// (preset.is_dirty ? *m_bitmapLockOpen : *m_bitmapLock) : m_bitmapCache->mkclear(16, 16)); - bitmap = m_bitmapCache->insert(bitmap_key, bmps); - } - - const std::string name = preset.alias.empty() ? preset.name : preset.alias; - if (preset.is_default || preset.is_system) { - ui->Append(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), - (bitmap == 0) ? wxNullBitmap : *bitmap); - if (selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX ) { - selected_preset_item = ui->GetCount() - 1; - tooltip = wxString::FromUTF8(preset.name.c_str()); - } - } - else - { - nonsys_presets.emplace(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), - (bitmap == 0) ? &wxNullBitmap : bitmap); - if (selected) { - selected_str = wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); - tooltip = wxString::FromUTF8(preset.name.c_str()); - } - } - if (preset.is_default) - ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap)); - } - - if (!nonsys_presets.empty()) - { - ui->set_label_marker(ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap)); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - ui->Append(it->first, *it->second); - if (it->first == selected_str || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) { - selected_preset_item = ui->GetCount() - 1; - } - } - } - - std::string bitmap_key = ""; - if (wide_icons) - bitmap_key += "wide,"; - bitmap_key += "edit_preset_list"; - wxBitmap* bmp = m_bitmapCache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(m_bitmapCache->mkclear(flag_icon_width, icon_height)); - // Paint the color bars + a lock at the system presets. - bmps.emplace_back(m_bitmapCache->mkclear(wide_icon_width+space_icon_width, icon_height)); - bmps.emplace_back(create_scaled_bitmap("edit_uni")); - bmp = m_bitmapCache->insert(bitmap_key, bmps); - } - ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove filaments")), *bmp), GUI::PresetComboBox::LABEL_ITEM_WIZARD_FILAMENTS); - - /* But, if selected_preset_item is still equal to INT_MAX, it means that - * there is no presets added to the list. - * So, select last combobox item ("Add/Remove filaments") - */ - if (selected_preset_item == INT_MAX) - selected_preset_item = ui->GetCount() - 1; - - ui->SetSelection(selected_preset_item); - ui->SetToolTip(tooltip.IsEmpty() ? ui->GetString(selected_preset_item) : tooltip); - ui->check_selection(selected_preset_item); - ui->Thaw(); - - // Update control min size after rescale (changed Display DPI under MSW) - if (ui->GetMinWidth() != 20 * ui->em_unit()) - ui->SetMinSize(wxSize(20 * ui->em_unit(), ui->GetSize().GetHeight())); -} - void PresetBundle::set_default_suppressed(bool default_suppressed) { prints.set_default_suppressed(default_suppressed); diff --git a/src/slic3r/GUI/PresetBundle.hpp b/src/slic3r/GUI/PresetBundle.hpp index bf1bba21d..7d137bb7a 100644 --- a/src/slic3r/GUI/PresetBundle.hpp +++ b/src/slic3r/GUI/PresetBundle.hpp @@ -5,7 +5,6 @@ #include "Preset.hpp" #include -#include #include #include @@ -13,10 +12,6 @@ class wxWindow; namespace Slic3r { -namespace GUI { - class BitmapCache; -}; - // Bundle of Print + Filament + Printer presets. class PresetBundle { @@ -110,9 +105,6 @@ public: // Export a config bundle file containing all the presets and the names of the active presets. void export_configbundle(const std::string &path, bool export_system_settings = false); - // Update a filament selection combo box on the plater for an idx_extruder. - void update_plater_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui); - // Enable / disable the "- default -" preset. void set_default_suppressed(bool default_suppressed); @@ -132,8 +124,6 @@ public: void update_compatible(PresetSelectCompatibleType select_other_print_if_incompatible, PresetSelectCompatibleType select_other_filament_if_incompatible); void update_compatible(PresetSelectCompatibleType select_other_if_incompatible) { this->update_compatible(select_other_if_incompatible, select_other_if_incompatible); } - void load_default_preset_bitmaps(); - // Set the is_visible flag for printer vendors, printer models and printer variants // based on the user configuration. // If the "vendor" section is missing, enable all models and variants of the particular vendor. @@ -163,21 +153,9 @@ private: // If it is not an external config, then the config will be stored into the user profile directory. void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config); void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree); - void load_compatible_bitmaps(); DynamicPrintConfig full_fff_config() const; DynamicPrintConfig full_sla_config() const; - - // Indicator, that the preset is compatible with the selected printer. - wxBitmap *m_bitmapCompatible; - // Indicator, that the preset is NOT compatible with the selected printer. - wxBitmap *m_bitmapIncompatible; - // Indicator, that the preset is system and not modified. - wxBitmap *m_bitmapLock; - // Indicator, that the preset is system and user modified. - wxBitmap *m_bitmapLockOpen; - // Caching color bitmaps for the filament combo box. - GUI::BitmapCache *m_bitmapCache; }; } // namespace Slic3r diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp new file mode 100644 index 000000000..380edb48a --- /dev/null +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -0,0 +1,794 @@ +#include "PresetComboBoxes.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/PrintConfig.hpp" + +#include "GUI.hpp" +#include "GUI_App.hpp" +#include "Plater.hpp" +#include "MainFrame.hpp" +#include "format.hpp" +#include "Tab.hpp" +#include "PresetBundle.hpp" +#include "PrintHostDialogs.hpp" +#include "ConfigWizard.hpp" +#include "../Utils/ASCIIFolding.hpp" +#include "../Utils/PrintHost.hpp" +#include "../Utils/FixModelByWin10.hpp" +#include "../Utils/UndoRedo.hpp" +#include "RemovableDriveManager.hpp" +#include "BitmapCache.hpp" + +using Slic3r::GUI::format_wxstr; + +static const std::pair THUMBNAIL_SIZE_3MF = { 256, 256 }; + +namespace Slic3r { +namespace GUI { + +// --------------------------------- +// *** PresetComboBox *** +// --------------------------------- + +/* For PresetComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina + * (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean + * "please scale this to such and such" but rather + * "the wxImage is already sized for backing scale such and such". ) + * Unfortunately, the constructor changes the size of wxBitmap too. + * Thus We need to use unscaled size value for bitmaps that we use + * to avoid scaled size of control items. + * For this purpose control drawing methods and + * control size calculation methods (virtual) are overridden. + **/ + +PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const wxSize& size) : + wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, size, 0, nullptr, wxCB_READONLY), + m_type(preset_type), + m_last_selected(wxNOT_FOUND), + m_em_unit(wxGetApp().em_unit()), + m_preset_bundle(wxGetApp().preset_bundle), + m_bitmap_cache(new BitmapCache) +{ + SetFont(wxGetApp().normal_font()); +#ifdef _WIN32 + // Workaround for ignoring CBN_EDITCHANGE events, which are processed after the content of the combo box changes, so that + // the index of the item inside CBN_EDITCHANGE may no more be valid. + EnableTextChangedEvents(false); +#endif /* _WIN32 */ + + switch (m_type) + { + case Preset::TYPE_PRINT: { + m_collection = &m_preset_bundle->prints; + m_main_bitmap_name = "cog"; + break; + } + case Preset::TYPE_FILAMENT: { + m_collection = &m_preset_bundle->filaments; + m_main_bitmap_name = "spool"; + break; + } + case Preset::TYPE_SLA_PRINT: { + m_collection = &m_preset_bundle->sla_prints; + m_main_bitmap_name = "cog"; + break; + } + case Preset::TYPE_SLA_MATERIAL: { + m_collection = &m_preset_bundle->sla_materials; + m_main_bitmap_name = "resin"; + break; + } + case Preset::TYPE_PRINTER: { + m_collection = &m_preset_bundle->printers; + m_main_bitmap_name = "printer"; + break; + } + default: break; + } + + m_bitmapCompatible = ScalableBitmap(nullptr, "flag_green"); + m_bitmapIncompatible = ScalableBitmap(nullptr, "flag_red"); + m_bitmapLock = ScalableBitmap(nullptr, "lock_closed"); + + // parameters for an icon's drawing + fill_width_height(); +} + +PresetComboBox::~PresetComboBox() +{ + delete m_bitmap_cache; + m_bitmap_cache = nullptr; +} + +void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type) +{ + this->SetClientData(item, (void*)label_item_type); +} + +void PresetComboBox::msw_rescale() +{ + m_em_unit = wxGetApp().em_unit(); + + m_bitmapLock.msw_rescale(); + m_bitmapIncompatible.msw_rescale(); + m_bitmapCompatible.msw_rescale(); + + // parameters for an icon's drawing + fill_width_height(); + + // update the control to redraw the icons + update(); +} + +void PresetComboBox::fill_width_height() +{ + // To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size, so + // set a bitmap's height to m_bitmapLock->GetHeight() and norm_icon_width to m_bitmapLock->GetWidth() + icon_height = m_bitmapLock.GetBmpHeight(); + norm_icon_width = m_bitmapLock.GetBmpWidth(); + + /* It's supposed that standard size of an icon is 16px*16px for 100% scaled display. + * So set sizes for solid_colored icons used for filament preset + * and scale them in respect to em_unit value + */ + const float scale_f = (float)m_em_unit * 0.1f; + + thin_icon_width = lroundf(8 * scale_f); // analogue to 8px; + wide_icon_width = norm_icon_width + thin_icon_width; + + space_icon_width = lroundf(2 * scale_f); + thin_space_icon_width = 2 * space_icon_width; + wide_space_icon_width = 3 * space_icon_width; +} + +wxString PresetComboBox::separator(const std::string& label) +{ + return wxString::FromUTF8(separator_head()) + _(label) + wxString::FromUTF8(separator_tail()); +} + +#ifdef __APPLE__ +bool PresetComboBox::OnAddBitmap(const wxBitmap& bitmap) +{ + if (bitmap.IsOk()) + { + // we should use scaled! size values of bitmap + int width = (int)bitmap.GetScaledWidth(); + int height = (int)bitmap.GetScaledHeight(); + + if (m_usedImgSize.x < 0) + { + // If size not yet determined, get it from this image. + m_usedImgSize.x = width; + m_usedImgSize.y = height; + + // Adjust control size to vertically fit the bitmap + wxWindow* ctrl = GetControl(); + ctrl->InvalidateBestSize(); + wxSize newSz = ctrl->GetBestSize(); + wxSize sz = ctrl->GetSize(); + if (newSz.y > sz.y) + ctrl->SetSize(sz.x, newSz.y); + else + DetermineIndent(); + } + + wxCHECK_MSG(width == m_usedImgSize.x && height == m_usedImgSize.y, + false, + "you can only add images of same size"); + + return true; + } + + return false; +} + +void PresetComboBox::OnDrawItem(wxDC& dc, + const wxRect& rect, + int item, + int flags) const +{ + const wxBitmap& bmp = *(wxBitmap*)m_bitmaps[item]; + if (bmp.IsOk()) + { + // we should use scaled! size values of bitmap + wxCoord w = bmp.GetScaledWidth(); + wxCoord h = bmp.GetScaledHeight(); + + const int imgSpacingLeft = 4; + + // Draw the image centered + dc.DrawBitmap(bmp, + rect.x + (m_usedImgSize.x - w) / 2 + imgSpacingLeft, + rect.y + (rect.height - h) / 2, + true); + } + + wxString text = GetString(item); + if (!text.empty()) + dc.DrawText(text, + rect.x + m_imgAreaWidth + 1, + rect.y + (rect.height - dc.GetCharHeight()) / 2); +} +#endif + + +// --------------------------------- +// *** PlaterPresetComboBox *** +// --------------------------------- + +PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset_type) : + PresetComboBox(parent, preset_type, wxSize(15 * wxGetApp().em_unit(), -1)) +{ + Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &evt) { + auto selected_item = evt.GetSelection(); + + auto marker = reinterpret_cast(this->GetClientData(selected_item)); + if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) { + this->SetSelection(this->m_last_selected); + evt.StopPropagation(); + if (marker == LABEL_ITEM_PHYSICAL_PRINTERS) + { + PhysicalPrinterDialog dlg; + dlg.ShowModal(); + return; + } + if (marker >= LABEL_ITEM_WIZARD_PRINTERS) { + ConfigWizard::StartPage sp = ConfigWizard::SP_WELCOME; + switch (marker) { + case LABEL_ITEM_WIZARD_PRINTERS: sp = ConfigWizard::SP_PRINTERS; break; + case LABEL_ITEM_WIZARD_FILAMENTS: sp = ConfigWizard::SP_FILAMENTS; break; + case LABEL_ITEM_WIZARD_MATERIALS: sp = ConfigWizard::SP_MATERIALS; break; + default: break; + } + wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); }); + } + } else if ( this->m_last_selected != selected_item || m_collection->current_is_dirty() ) { + this->m_last_selected = selected_item; + evt.SetInt(this->m_type); + evt.Skip(); + } else { + evt.StopPropagation(); + } + }); + + if (m_type == Preset::TYPE_FILAMENT) + { + Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) { + const Preset* selected_preset = m_collection->find_preset(m_preset_bundle->filament_presets[m_extruder_idx]); + // Wide icons are shown if the currently selected preset is not compatible with the current printer, + // and red flag is drown in front of the selected preset. + bool wide_icons = selected_preset != nullptr && !selected_preset->is_compatible; + float scale = m_em_unit*0.1f; + + int shifl_Left = wide_icons ? int(scale * 16 + 0.5) : 0; +#if defined(wxBITMAPCOMBOBOX_OWNERDRAWN_BASED) + shifl_Left += int(scale * 4 + 0.5f); // IMAGE_SPACING_RIGHT = 4 for wxBitmapComboBox -> Space left of image +#endif + int icon_right_pos = shifl_Left + int(scale * (24+4) + 0.5); + int mouse_pos = event.GetLogicalPosition(wxClientDC(this)).x; + if (mouse_pos < shifl_Left || mouse_pos > icon_right_pos ) { + // Let the combo box process the mouse click. + event.Skip(); + return; + } + + // Swallow the mouse click and open the color picker. + + // get current color + DynamicPrintConfig* cfg = wxGetApp().get_tab(Preset::TYPE_PRINTER)->get_config(); + auto colors = static_cast(cfg->option("extruder_colour")->clone()); + wxColour clr(colors->values[m_extruder_idx]); + if (!clr.IsOk()) + clr = wxColour(0,0,0); // Don't set alfa to transparence + + auto data = new wxColourData(); + data->SetChooseFull(1); + data->SetColour(clr); + + wxColourDialog dialog(this, data); + dialog.CenterOnParent(); + if (dialog.ShowModal() == wxID_OK) + { + colors->values[m_extruder_idx] = dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); + + DynamicPrintConfig cfg_new = *cfg; + cfg_new.set_key_value("extruder_colour", colors); + + wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg_new); + this->update(); + wxGetApp().plater()->on_config_change(cfg_new); + } + }); + } + + edit_btn = new ScalableButton(parent, wxID_ANY, "cog"); + edit_btn->SetToolTip(_L("Click to edit preset")); + + edit_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent) + { + Tab* tab = wxGetApp().get_tab(m_type); + if (!tab) + return; + + int page_id = wxGetApp().tab_panel()->FindPage(tab); + if (page_id == wxNOT_FOUND) + return; + + wxGetApp().tab_panel()->SetSelection(page_id); + + // Switch to Settings NotePad + wxGetApp().mainframe->select_tab(); + + /* In a case of a multi-material printing, for editing another Filament Preset + * it's needed to select this preset for the "Filament settings" Tab + */ + if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) + { + const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data(); + + // Call select_preset() only if there is new preset and not just modified + if ( !boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()) ) + { + const std::string& preset_name = wxGetApp().preset_bundle->filaments.get_preset_name_by_alias(selected_preset); + tab->select_preset(preset_name); + } + } + }); +} + +PlaterPresetComboBox::~PlaterPresetComboBox() +{ + if (edit_btn) + edit_btn->Destroy(); +} + +// Only the compatible presets are shown. +// If an incompatible preset is selected, it is shown as well. +void PlaterPresetComboBox::update() +{ + if (m_type == Preset::TYPE_FILAMENT && + (m_collection->get_edited_preset().printer_technology() == ptSLA || + m_preset_bundle->filament_presets.size() <= m_extruder_idx) ) + return; + + // Otherwise fill in the list from scratch. + this->Freeze(); + this->Clear(); + size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected + + const Preset* selected_filament_preset; + std::string extruder_color; + if (m_type == Preset::TYPE_FILAMENT) + { + unsigned char rgb[3]; + extruder_color = m_preset_bundle->printers.get_edited_preset().config.opt_string("extruder_colour", (unsigned int)m_extruder_idx); + if (!m_bitmap_cache->parse_color(extruder_color, rgb)) + // Extruder color is not defined. + extruder_color.clear(); + selected_filament_preset = m_collection->find_preset(m_preset_bundle->filament_presets[m_extruder_idx]); + assert(selected_filament_preset); + } + + const Preset& selected_preset = m_type == Preset::TYPE_FILAMENT ? *selected_filament_preset : m_collection->get_selected_preset(); + // Show wide icons if the currently selected preset is not compatible with the current printer, + // and draw a red flag in front of the selected preset. + bool wide_icons = !selected_preset.is_compatible; + + std::map nonsys_presets; + std::map physical_printers; + + wxString selected = ""; + wxString tooltip = ""; + const std::deque& presets = m_collection->get_presets(); + + if (!presets.front().is_visible) + this->set_label_marker(this->Append(separator(L("System presets")), wxNullBitmap)); + + for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) + { + const Preset& preset = presets[i]; + bool is_selected = m_type == Preset::TYPE_FILAMENT ? + m_preset_bundle->filament_presets[m_extruder_idx] == preset.name : + i == m_collection->get_selected_idx(); + + if (!preset.is_visible || (!preset.is_compatible && !is_selected)) + continue; + + std::string bitmap_key, filament_rgb, extruder_rgb; + bool single_bar = false; + if (m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA) + bitmap_key = "sla_printer"; + else if (m_type == Preset::TYPE_FILAMENT) + { + // Assign an extruder color to the selected item if the extruder color is defined. + filament_rgb = preset.config.opt_string("filament_colour", 0); + extruder_rgb = (selected && !extruder_color.empty()) ? extruder_color : filament_rgb; + single_bar = filament_rgb == extruder_rgb; + + bitmap_key = single_bar ? filament_rgb : filament_rgb + extruder_rgb; + } + wxBitmap main_bmp = create_scaled_bitmap(m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name); + + // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left + // to the filament color image. + if (wide_icons) + bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; + bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + bitmap_key += "-h" + std::to_string(icon_height); + + wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(preset.is_compatible ? m_bitmap_cache->mkclear(norm_icon_width, icon_height) : m_bitmapIncompatible.bmp()); + + if (m_type == Preset::TYPE_FILAMENT) + { + unsigned char rgb[3]; + // Paint the color bars. + m_bitmap_cache->parse_color(filament_rgb, rgb); + bmps.emplace_back(m_bitmap_cache->mksolid(single_bar ? wide_icon_width : norm_icon_width, icon_height, rgb)); + if (!single_bar) { + m_bitmap_cache->parse_color(extruder_rgb, rgb); + bmps.emplace_back(m_bitmap_cache->mksolid(thin_icon_width, icon_height, rgb)); + } + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmap_cache->mkclear(space_icon_width, icon_height)); + } + else + { + // Paint the color bars. + bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); + bmps.emplace_back(main_bmp); + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); + } + bmps.emplace_back((preset.is_system || preset.is_default) ? m_bitmapLock.bmp() : m_bitmap_cache->mkclear(norm_icon_width, icon_height)); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } + + const std::string name = preset.alias.empty() ? preset.name : preset.alias; + if (preset.is_default || preset.is_system) { + Append(wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), + !bmp ? main_bmp : *bmp); + if (is_selected || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) { + selected_preset_item = GetCount() - 1; + tooltip = wxString::FromUTF8(preset.name.c_str()); + } + } + else + { + nonsys_presets.emplace(wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), bmp); + if (is_selected) { + selected = wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); + tooltip = wxString::FromUTF8(preset.name.c_str()); + } + } + if (i + 1 == m_collection->num_default_presets()) + set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + } + if (!nonsys_presets.empty()) + { + set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + Append(it->first, *it->second); + if (it->first == selected || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + } + } + if (!physical_printers.empty()) + { + set_label_marker(Append(separator(L("Physical printers")), wxNullBitmap)); + for (std::map::iterator it = physical_printers.begin(); it != physical_printers.end(); ++it) { + Append(it->first, *it->second); + if (it->first == selected || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + } + } + + if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) { + std::string bitmap_key = ""; + // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left + // to the filament color image. + if (wide_icons) + bitmap_key += "wide,"; + bitmap_key += "edit_preset_list"; + bitmap_key += "-h" + std::to_string(icon_height); + + wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars.update_plater_ui + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(m_bitmap_cache->mkclear(norm_icon_width, icon_height)); + // Paint the color bars. + bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); + bmps.emplace_back(create_scaled_bitmap(m_main_bitmap_name)); + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); + bmps.emplace_back(create_scaled_bitmap("edit_uni")); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } + if (m_type == Preset::TYPE_SLA_MATERIAL) + set_label_marker(Append(separator(L("Add/Remove materials")), *bmp), LABEL_ITEM_WIZARD_MATERIALS); + else + set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); + } + if (m_type == Preset::TYPE_PRINTER) { + std::string bitmap_key = ""; + if (wide_icons) + bitmap_key += "wide,"; + bitmap_key += "edit_preset_list"; + bitmap_key += "-h" + std::to_string(icon_height); + + wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(m_bitmap_cache->mkclear(norm_icon_width, icon_height)); + // Paint the color bars. + bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); + bmps.emplace_back(create_scaled_bitmap("printer")); + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); + bmps.emplace_back(create_scaled_bitmap("edit_uni")); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } + set_label_marker(Append(separator(L("Add physical printer")), *bmp), LABEL_ITEM_PHYSICAL_PRINTERS); + } + + /* But, if selected_preset_item is still equal to INT_MAX, it means that + * there is no presets added to the list. + * So, select last combobox item ("Add/Remove preset") + */ + if (selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + + SetSelection(selected_preset_item); + SetToolTip(tooltip.IsEmpty() ? GetString(selected_preset_item) : tooltip); + m_last_selected = selected_preset_item; + Thaw(); + + // Update control min size after rescale (changed Display DPI under MSW) + if (GetMinWidth() != 20 * m_em_unit) + SetMinSize(wxSize(20 * m_em_unit, GetSize().GetHeight())); +} + +void PlaterPresetComboBox::msw_rescale() +{ + PresetComboBox::msw_rescale(); + edit_btn->msw_rescale(); +} + + +// --------------------------------- +// *** PlaterPresetComboBox *** +// --------------------------------- + +TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type) : + PresetComboBox(parent, preset_type, wxSize(35 * wxGetApp().em_unit(), -1)) +{ + Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { + // see https://github.com/prusa3d/PrusaSlicer/issues/3889 + // Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender") + // m_presets_choice->GetSelection() will return first item, because search in PopupListCtrl is case-insensitive. + // So, use GetSelection() from event parameter + auto selected_item = evt.GetSelection(); + + auto marker = reinterpret_cast(this->GetClientData(selected_item)); + if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) { + this->SetSelection(this->m_last_selected); + if (marker == LABEL_ITEM_WIZARD_PRINTERS) + wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); }); + } + else if (m_last_selected != selected_item || m_collection->current_is_dirty()) { + std::string selected_string = this->GetString(selected_item).ToUTF8().data(); + Tab* tab = wxGetApp().get_tab(this->m_type); + assert (tab); + tab->select_preset(selected_string); + } + + evt.StopPropagation(); + }); +} + +// Update the choice UI from the list of presets. +// If show_incompatible, all presets are shown, otherwise only the compatible presets are shown. +// If an incompatible preset is selected, it is shown as well. +void TabPresetComboBox::update() +{ + Freeze(); + Clear(); + size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected + + const std::deque& presets = m_collection->get_presets(); + + std::map nonsys_presets; + wxString selected = ""; + if (!presets.front().is_visible) + set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + int idx_selected = m_collection->get_selected_idx(); + for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) { + const Preset& preset = presets[i]; + if (!preset.is_visible || (!show_incompatible && !preset.is_compatible && i != idx_selected)) + continue; + + std::string bitmap_key = "tab"; + wxBitmap main_bmp = create_scaled_bitmap(m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name); + if (m_type == Preset::TYPE_PRINTER) { + bitmap_key += "_printer"; + if (preset.printer_technology() == ptSLA) + bitmap_key += "_sla"; + } + bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; + bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + bitmap_key += "-h" + std::to_string(icon_height); + + wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + bmps.emplace_back(m_type == Preset::TYPE_PRINTER ? main_bmp : preset.is_compatible ? m_bitmapCompatible.bmp() : m_bitmapIncompatible.bmp()); + // Paint a lock at the system presets. + bmps.emplace_back((preset.is_system || preset.is_default) ? m_bitmapLock.bmp() : m_bitmap_cache->mkclear(norm_icon_width, icon_height)); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } + + if (preset.is_default || preset.is_system) { + Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), + (bmp == 0) ? main_bmp : *bmp); + if (i == idx_selected || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + } + else + { + nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), bmp); + if (i == idx_selected) + selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); + } + if (i + 1 == m_collection->num_default_presets()) + set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + } + if (!nonsys_presets.empty()) + { + set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + Append(it->first, *it->second); + if (it->first == selected || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + } + } + if (m_type == Preset::TYPE_PRINTER) { + std::string bitmap_key = "edit_preset_list"; + bitmap_key += "-h" + std::to_string(icon_height); + + wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + bmps.emplace_back(create_scaled_bitmap(m_main_bitmap_name)); + bmps.emplace_back(create_scaled_bitmap("edit_uni")); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } + set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); + } + + /* But, if selected_preset_item is still equal to INT_MAX, it means that + * there is no presets added to the list. + * So, select last combobox item ("Add/Remove preset") + */ + if (selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + + SetSelection(selected_preset_item); + SetToolTip(GetString(selected_preset_item)); + Thaw(); + + m_last_selected = selected_preset_item; +} + +void TabPresetComboBox::msw_rescale() +{ + PresetComboBox::msw_rescale(); + wxSize sz = wxSize(35 * m_em_unit, -1); + SetMinSize(sz); + SetSize(sz); +} + +void TabPresetComboBox::update_dirty() +{ + // 1) Update the dirty flag of the current preset. + m_collection->update_dirty(); + + // 2) Update the labels. + wxWindowUpdateLocker noUpdates(this); + for (unsigned int ui_id = 0; ui_id < GetCount(); ++ui_id) { + std::string old_label = GetString(ui_id).utf8_str().data(); + std::string preset_name = Preset::remove_suffix_modified(old_label); + const Preset* preset = m_collection->find_preset(preset_name, false); + if (preset) { + std::string new_label = preset->is_dirty ? preset->name + Preset::suffix_modified() : preset->name; + if (old_label != new_label) + SetString(ui_id, wxString::FromUTF8(new_label.c_str())); + } + } +#ifdef __APPLE__ + // wxWidgets on OSX do not upload the text of the combo box line automatically. + // Force it to update by re-selecting. + SetSelection(GetSelection()); +#endif /* __APPLE __ */ +} + + +//------------------------------------------ +// PhysicalPrinterDialog +//------------------------------------------ + + +PhysicalPrinterDialog::PhysicalPrinterDialog() + : DPIDialog(NULL, wxID_ANY, _L("Search"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +{ + SetFont(wxGetApp().normal_font()); + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + + int border = 10; + int em = em_unit(); + + printer_text = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); + printer_presets = new PlaterPresetComboBox(this, Preset::TYPE_PRINTER); + + wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); + + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + topSizer->Add(printer_text , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(printer_presets , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(btns , 0, wxEXPAND | wxALL, border); + + SetSizer(topSizer); + topSizer->SetSizeHints(this); +} + +void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) +{ + const int& em = em_unit(); + + msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); + + const wxSize& size = wxSize(40 * em, 30 * em); + SetMinSize(size); + + Fit(); + Refresh(); +} + + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp new file mode 100644 index 000000000..63110e432 --- /dev/null +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -0,0 +1,179 @@ +#ifndef slic3r_PresetComboBoxes_hpp_ +#define slic3r_PresetComboBoxes_hpp_ + +#include + +#include +#include +#include + +#include "Preset.hpp" +#include "wxExtensions.hpp" +#include "GUI_Utils.hpp" + +class wxString; +class wxTextCtrl; + +namespace Slic3r { + +namespace GUI { + +class BitmapCache; + + +// --------------------------------- +// *** PresetComboBox *** +// --------------------------------- + +// BitmapComboBox used to presets list on Sidebar and Tabs +class PresetComboBox : public wxBitmapComboBox +{ +public: + PresetComboBox(wxWindow* parent, Preset::Type preset_type, const wxSize& size = wxDefaultSize); + ~PresetComboBox(); + + enum LabelItemType { + LABEL_ITEM_MARKER = 0xffffff01, + LABEL_ITEM_PHYSICAL_PRINTERS, + LABEL_ITEM_WIZARD_PRINTERS, + LABEL_ITEM_WIZARD_FILAMENTS, + LABEL_ITEM_WIZARD_MATERIALS, + + LABEL_ITEM_MAX, + }; + + void set_label_marker(int item, LabelItemType label_item_type = LABEL_ITEM_MARKER); + int em_unit() const { return m_em_unit; } + + virtual void update() {}; + virtual void msw_rescale(); + +protected: + typedef std::size_t Marker; + + Preset::Type m_type; + std::string m_main_bitmap_name; + + PresetBundle* m_preset_bundle {nullptr}; + PresetCollection* m_collection {nullptr}; + + // Caching color bitmaps for the filament combo box. + BitmapCache* m_bitmap_cache {nullptr}; + // Indicator, that the preset is compatible with the selected printer. + ScalableBitmap m_bitmapCompatible; + // Indicator, that the preset is NOT compatible with the selected printer. + ScalableBitmap m_bitmapIncompatible; + // Indicator, that the preset is system and not modified. + ScalableBitmap m_bitmapLock; + + int m_last_selected; + int m_em_unit; + + // parameters for an icon's drawing + int icon_height; + int norm_icon_width; + int thin_icon_width; + int wide_icon_width; + int space_icon_width; + int thin_space_icon_width; + int wide_space_icon_width; + +#ifdef __linux__ + static const char* separator_head() { return "------- "; } + static const char* separator_tail() { return " -------"; } +#else // __linux__ + static const char* separator_head() { return "————— "; } + static const char* separator_tail() { return " —————"; } +#endif // __linux__ + static wxString separator(const std::string& label); + +#ifdef __APPLE__ + /* For PresetComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina + * (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean + * "please scale this to such and such" but rather + * "the wxImage is already sized for backing scale such and such". ) + * Unfortunately, the constructor changes the size of wxBitmap too. + * Thus We need to use unscaled size value for bitmaps that we use + * to avoid scaled size of control items. + * For this purpose control drawing methods and + * control size calculation methods (virtual) are overridden. + **/ + virtual bool OnAddBitmap(const wxBitmap& bitmap) override; + virtual void OnDrawItem(wxDC& dc, const wxRect& rect, int item, int flags) const override; +#endif + +private: + void fill_width_height(); +}; + + +// --------------------------------- +// *** PlaterPresetComboBox *** +// --------------------------------- + +class PlaterPresetComboBox : public PresetComboBox +{ +public: + PlaterPresetComboBox(wxWindow *parent, Preset::Type preset_type); + ~PlaterPresetComboBox(); + + ScalableButton* edit_btn { nullptr }; + + void set_extruder_idx(const int extr_idx) { m_extruder_idx = extr_idx; } + int get_extruder_idx() const { return m_extruder_idx; } + + void update() override; + void msw_rescale() override; + +private: + int m_extruder_idx = -1; +}; + + +// --------------------------------- +// *** PlaterPresetComboBox *** +// --------------------------------- + +class TabPresetComboBox : public PresetComboBox +{ +public: + TabPresetComboBox(wxWindow *parent, Preset::Type preset_type); + ~TabPresetComboBox() {} + void set_show_incompatible_presets(bool show_incompatible_presets) { + show_incompatible = show_incompatible_presets; + } + + void update() override; + void update_dirty(); + void msw_rescale() override; + +private: + bool show_incompatible{false}; +}; + + +//------------------------------------------ +// PhysicalPrinterDialog +//------------------------------------------ + +class PhysicalPrinterDialog : public DPIDialog +{ + std::string printer_name; + std::string preset_name; + + wxTextCtrl* printer_text { nullptr }; + PresetComboBox* printer_presets; + +public: + PhysicalPrinterDialog(); + ~PhysicalPrinterDialog() {} + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; + void on_sys_color_changed() override {}; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 84bc5a572..b128ec03d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -27,6 +27,7 @@ #include #include "wxExtensions.hpp" +#include "PresetComboBoxes.hpp" #include #include "GUI_App.hpp" @@ -160,10 +161,7 @@ void Tab::create_preset_tab() #endif //__WXOSX__ // preset chooser - m_presets_choice = new PresetBitmapComboBox(panel, wxSize(35 * m_em_unit, -1)); - - // search combox -// m_search = new Search::SearchCtrl(panel); + m_presets_choice = new TabPresetComboBox(panel, m_type); auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); @@ -278,35 +276,6 @@ void Tab::create_preset_tab() m_treectrl->Bind(wxEVT_TREE_SEL_CHANGED, &Tab::OnTreeSelChange, this); m_treectrl->Bind(wxEVT_KEY_DOWN, &Tab::OnKeyDown, this); - m_presets_choice->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent e) { - //! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox, - //! but the OSX version derived from wxOwnerDrawnCombo, instead of: - //! select_preset(m_presets_choice->GetStringSelection().ToUTF8().data()); - //! we doing next: - // int selected_item = m_presets_choice->GetSelection(); - - // see https://github.com/prusa3d/PrusaSlicer/issues/3889 - // Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender") - // m_presets_choice->GetSelection() will return first item, because search in PopupListCtrl is case-insensitive. - // So, use GetSelection() from event parameter - int selected_item = e.GetSelection(); - if (m_selected_preset_item == size_t(selected_item) && !m_presets->current_is_dirty()) - return; - if (selected_item >= 0) { - std::string selected_string = m_presets_choice->GetString(selected_item).ToUTF8().data(); - if (selected_string.find(PresetCollection::separator_head()) == 0 - /*selected_string == "------- System presets -------" || - selected_string == "------- User presets -------"*/) { - m_presets_choice->SetSelection(m_selected_preset_item); - if (wxString::FromUTF8(selected_string.c_str()) == PresetCollection::separator(L("Add a new printer"))) - wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER); }); - return; - } - m_selected_preset_item = selected_item; - select_preset(selected_string); - } - })); - m_btn_save_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { save_preset(); })); m_btn_delete_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { delete_preset(); })); m_btn_hide_incompatible_presets->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { @@ -778,14 +747,14 @@ void Tab::on_roll_back_value(const bool to_sys /*= true*/) // comparing the selected preset config with $self->{config}. void Tab::update_dirty() { - m_presets->update_dirty_ui(m_presets_choice); + m_presets_choice->update_dirty(); on_presets_changed(); update_changed_ui(); } void Tab::update_tab_ui() { - m_selected_preset_item = m_presets->update_tab_ui(m_presets_choice, m_show_incompatible_presets, m_em_unit); + m_presets_choice->update(); } // Load a provied DynamicConfig into the tab, modifying the active preset. @@ -850,12 +819,10 @@ void Tab::msw_rescale() m_em_unit = wxGetApp().em_unit(); m_mode_sizer->msw_rescale(); + m_presets_choice->msw_rescale(); - m_presets_choice->SetSize(35 * m_em_unit, -1); m_treectrl->SetMinSize(wxSize(20 * m_em_unit, -1)); - update_tab_ui(); - // rescale buttons and cached bitmaps for (const auto btn : m_scaled_buttons) btn->msw_rescale(); @@ -963,7 +930,7 @@ void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bo // Don't select another profile if this profile happens to become incompatible. m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); } - m_presets->update_dirty_ui(m_presets_choice); + m_presets_choice->update_dirty(); on_presets_changed(); update(); } @@ -3360,6 +3327,7 @@ void Tab::delete_preset() void Tab::toggle_show_hide_incompatible() { m_show_incompatible_presets = !m_show_incompatible_presets; + m_presets_choice->set_show_incompatible_presets(m_show_incompatible_presets); update_show_hide_incompatible_button(); update_tab_ui(); } diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 5805809bf..a0bf536c1 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -39,6 +39,8 @@ namespace Slic3r { namespace GUI { +class TabPresetComboBox; + // Single Tab page containing a{ vsizer } of{ optgroups } // package Slic3r::GUI::Tab::Page; using ConfigOptionsGroupShp = std::shared_ptr; @@ -113,7 +115,7 @@ protected: Preset::Type m_type; std::string m_name; const wxString m_title; - PresetBitmapComboBox* m_presets_choice; + TabPresetComboBox* m_presets_choice; ScalableButton* m_search_btn; ScalableButton* m_btn_save_preset; ScalableButton* m_btn_delete_preset; @@ -206,8 +208,6 @@ protected: bool m_is_nonsys_values{ true }; bool m_postpone_update_ui {false}; - size_t m_selected_preset_item{ 0 }; - void set_type(); int m_em_unit; @@ -320,7 +320,6 @@ public: DynamicPrintConfig* get_config() { return m_config; } PresetCollection* get_presets() { return m_presets; } - size_t get_selected_preset_item() { return m_selected_preset_item; } void on_value_change(const std::string& opt_key, const boost::any& value); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index ad9f0a121..39b3e154b 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -300,94 +300,6 @@ void wxCheckListBoxComboPopup::OnListBoxSelection(wxCommandEvent& evt) } -namespace Slic3r { -namespace GUI { - -// *** PresetBitmapComboBox *** - -/* For PresetBitmapComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina - * (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean - * "please scale this to such and such" but rather - * "the wxImage is already sized for backing scale such and such". ) - * Unfortunately, the constructor changes the size of wxBitmap too. - * Thus We need to use unscaled size value for bitmaps that we use - * to avoid scaled size of control items. - * For this purpose control drawing methods and - * control size calculation methods (virtual) are overridden. - **/ - -PresetBitmapComboBox::PresetBitmapComboBox(wxWindow* parent, const wxSize& size) : - wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, size, 0, nullptr, wxCB_READONLY) -{} - -#ifdef __APPLE__ -bool PresetBitmapComboBox::OnAddBitmap(const wxBitmap& bitmap) -{ - if (bitmap.IsOk()) - { - // we should use scaled! size values of bitmap - int width = (int)bitmap.GetScaledWidth(); - int height = (int)bitmap.GetScaledHeight(); - - if (m_usedImgSize.x < 0) - { - // If size not yet determined, get it from this image. - m_usedImgSize.x = width; - m_usedImgSize.y = height; - - // Adjust control size to vertically fit the bitmap - wxWindow* ctrl = GetControl(); - ctrl->InvalidateBestSize(); - wxSize newSz = ctrl->GetBestSize(); - wxSize sz = ctrl->GetSize(); - if (newSz.y > sz.y) - ctrl->SetSize(sz.x, newSz.y); - else - DetermineIndent(); - } - - wxCHECK_MSG(width == m_usedImgSize.x && height == m_usedImgSize.y, - false, - "you can only add images of same size"); - - return true; - } - - return false; -} - -void PresetBitmapComboBox::OnDrawItem(wxDC& dc, - const wxRect& rect, - int item, - int flags) const -{ - const wxBitmap& bmp = *(wxBitmap*)m_bitmaps[item]; - if (bmp.IsOk()) - { - // we should use scaled! size values of bitmap - wxCoord w = bmp.GetScaledWidth(); - wxCoord h = bmp.GetScaledHeight(); - - const int imgSpacingLeft = 4; - - // Draw the image centered - dc.DrawBitmap(bmp, - rect.x + (m_usedImgSize.x - w) / 2 + imgSpacingLeft, - rect.y + (rect.height - h) / 2, - true); - } - - wxString text = GetString(item); - if (!text.empty()) - dc.DrawText(text, - rect.x + m_imgAreaWidth + 1, - rect.y + (rect.height - dc.GetCharHeight()) / 2); -} -#endif -} -} - - // *** wxDataViewTreeCtrlComboPopup *** const unsigned int wxDataViewTreeCtrlComboPopup::DefaultWidth = 270; diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 569257e1b..17fe8992c 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -95,37 +95,6 @@ public: void OnListBoxSelection(wxCommandEvent& evt); }; -namespace Slic3r { -namespace GUI { -// *** PresetBitmapComboBox *** - -// BitmapComboBox used to presets list on Sidebar and Tabs -class PresetBitmapComboBox: public wxBitmapComboBox -{ -public: - PresetBitmapComboBox(wxWindow* parent, const wxSize& size = wxDefaultSize); - ~PresetBitmapComboBox() {} - -#ifdef __APPLE__ -protected: - /* For PresetBitmapComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina - * (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean - * "please scale this to such and such" but rather - * "the wxImage is already sized for backing scale such and such". ) - * Unfortunately, the constructor changes the size of wxBitmap too. - * Thus We need to use unscaled size value for bitmaps that we use - * to avoid scaled size of control items. - * For this purpose control drawing methods and - * control size calculation methods (virtual) are overridden. - **/ - virtual bool OnAddBitmap(const wxBitmap& bitmap) override; - virtual void OnDrawItem(wxDC& dc, const wxRect& rect, int item, int flags) const override; -#endif -}; - -} -} - // *** wxDataViewTreeCtrlComboBox *** From 19c4f3260429ba6db33411fd78b6bfc74ac2b8e9 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 16 Jun 2020 16:58:41 +0200 Subject: [PATCH 02/70] Preset and PresetBundle are moved to the _libslic3r_ folder --- src/libslic3r/CMakeLists.txt | 4 ++++ src/{slic3r/GUI => libslic3r}/Preset.cpp | 23 +++++++++++++------ src/{slic3r/GUI => libslic3r}/Preset.hpp | 14 +++-------- .../GUI => libslic3r}/PresetBundle.cpp | 17 +++----------- .../GUI => libslic3r}/PresetBundle.hpp | 3 +-- src/slic3r/CMakeLists.txt | 4 ---- src/slic3r/Config/Snapshot.cpp | 3 +-- src/slic3r/GUI/3DBed.cpp | 2 +- src/slic3r/GUI/ConfigManipulation.cpp | 2 +- src/slic3r/GUI/ConfigWizard_private.hpp | 2 +- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- src/slic3r/GUI/GUI_App.cpp | 4 ++-- src/slic3r/GUI/GUI_App.hpp | 2 +- src/slic3r/GUI/GUI_ObjectLayers.cpp | 2 +- src/slic3r/GUI/GUI_ObjectList.cpp | 2 +- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 2 +- src/slic3r/GUI/GUI_ObjectSettings.cpp | 2 +- src/slic3r/GUI/GUI_Preview.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 2 +- src/slic3r/GUI/I18N.hpp | 2 +- src/slic3r/GUI/Jobs/SLAImportJob.cpp | 2 +- src/slic3r/GUI/MainFrame.cpp | 2 +- src/slic3r/GUI/Mouse3DController.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 4 +++- src/slic3r/GUI/Plater.hpp | 2 +- src/slic3r/GUI/PresetComboBoxes.cpp | 2 +- src/slic3r/GUI/PresetComboBoxes.hpp | 2 +- src/slic3r/GUI/PresetHints.cpp | 1 - src/slic3r/GUI/PresetHints.hpp | 2 +- src/slic3r/GUI/Search.cpp | 2 +- src/slic3r/GUI/Search.hpp | 2 +- src/slic3r/GUI/Tab.cpp | 3 +-- src/slic3r/GUI/Tab.hpp | 2 +- src/slic3r/Utils/PresetUpdater.cpp | 2 +- 38 files changed, 61 insertions(+), 73 deletions(-) rename src/{slic3r/GUI => libslic3r}/Preset.cpp (99%) rename src/{slic3r/GUI => libslic3r}/Preset.hpp (99%) rename src/{slic3r/GUI => libslic3r}/PresetBundle.cpp (99%) rename src/{slic3r/GUI => libslic3r}/PresetBundle.hpp (99%) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 1a58bdbbd..1605c52cd 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -147,6 +147,10 @@ add_library(libslic3r STATIC PolygonTrimmer.hpp Polyline.cpp Polyline.hpp + Preset.cpp + Preset.hpp + PresetBundle.cpp + PresetBundle.hpp Print.cpp Print.hpp PrintBase.cpp diff --git a/src/slic3r/GUI/Preset.cpp b/src/libslic3r/Preset.cpp similarity index 99% rename from src/slic3r/GUI/Preset.cpp rename to src/libslic3r/Preset.cpp index 883dc438a..e46cd6c82 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1,8 +1,7 @@ #include #include "Preset.hpp" -#include "AppConfig.hpp" -#include "I18N.hpp" +#include "slic3r/GUI/AppConfig.hpp" #ifdef _MSC_VER #define WIN32_LEAN_AND_MEAN @@ -10,6 +9,16 @@ #include #endif /* _MSC_VER */ +// instead of #include "slic3r/GUI/I18N.hpp" : +#ifndef L +// !!! If you needed to translate some string, +// !!! please use _L(string) +// !!! _() - is a standard wxWidgets macro to translate +// !!! L() is used only for marking localizable string +// !!! It will be used in "xgettext" to create a Locating Message Catalog. +#define L(s) s +#endif /* L */ + #include #include #include @@ -28,9 +37,9 @@ #include #include -#include "libslic3r/libslic3r.h" -#include "libslic3r/Utils.hpp" -#include "libslic3r/PlaceholderParser.hpp" +#include "libslic3r.h" +#include "Utils.hpp" +#include "PlaceholderParser.hpp" using boost::property_tree::ptree; @@ -237,9 +246,9 @@ const std::string& Preset::suffix_modified() return g_suffix_modified; } -void Preset::update_suffix_modified() +void Preset::update_suffix_modified(const std::string& new_suffix_modified) { - g_suffix_modified = (" (" + _(L("modified")) + ")").ToUTF8().data(); + g_suffix_modified = new_suffix_modified; } // Remove an optional "(modified)" suffix from a name. // This converts a UI name to a unique preset identifier. diff --git a/src/slic3r/GUI/Preset.hpp b/src/libslic3r/Preset.hpp similarity index 99% rename from src/slic3r/GUI/Preset.hpp rename to src/libslic3r/Preset.hpp index 8a8fa024b..b0af2f142 100644 --- a/src/slic3r/GUI/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -8,16 +8,8 @@ #include #include -#include "libslic3r/libslic3r.h" -#include "libslic3r/PrintConfig.hpp" -#include "libslic3r/Semver.hpp" - -class wxBitmap; -class wxBitmapComboBox; -class wxChoice; -class wxItemContainer; -class wxString; -class wxWindow; +#include "PrintConfig.hpp" +#include "Semver.hpp" namespace Slic3r { @@ -231,7 +223,7 @@ public: static const std::vector& sla_material_options(); static const std::vector& sla_print_options(); - static void update_suffix_modified(); + static void update_suffix_modified(const std::string& new_suffix_modified); static const std::string& suffix_modified(); static std::string remove_suffix_modified(const std::string& name); static void normalize(DynamicPrintConfig &config); diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp similarity index 99% rename from src/slic3r/GUI/PresetBundle.cpp rename to src/libslic3r/PresetBundle.cpp index 024884b00..9da7731a4 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -1,7 +1,9 @@ #include #include "PresetBundle.hpp" -#include "Plater.hpp" +#include "libslic3r.h" +#include "Utils.hpp" +#include "Model.hpp" #include #include @@ -19,14 +21,6 @@ #include #include -#include - -#include "libslic3r/libslic3r.h" -#include "libslic3r/Utils.hpp" -#include "libslic3r/Model.hpp" -#include "GUI_App.hpp" -#include "libslic3r/CustomGCode.hpp" - // Store the print/filament/printer presets into a "presets" subdirectory of the Slic3rPE config dir. // This breaks compatibility with the upstream Slic3r if the --datadir is used to switch between the two versions. @@ -49,9 +43,6 @@ PresetBundle::PresetBundle() : sla_prints(Preset::TYPE_SLA_PRINT, Preset::sla_print_options(), static_cast(SLAFullPrintConfig::defaults())), printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast(FullPrintConfig::defaults()), "- default FFF -") { - if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) - wxImage::AddHandler(new wxPNGHandler); - // The following keys are handled by the UI, they do not have a counterpart in any StaticPrintConfig derived classes, // therefore they need to be handled differently. As they have no counterpart in StaticPrintConfig, they are not being // initialized based on PrintConfigDef(), but to empty values (zeros, empty vectors, empty strings). @@ -822,8 +813,6 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool // 4) Load the project config values (the per extruder wipe matrix etc). this->project_config.apply_only(config, s_project_options); - CustomGCode::update_custom_gcode_per_print_z_from_config(GUI::wxGetApp().plater()->model().custom_gcode_per_print_z, &this->project_config); - break; } case ptSLA: diff --git a/src/slic3r/GUI/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp similarity index 99% rename from src/slic3r/GUI/PresetBundle.hpp rename to src/libslic3r/PresetBundle.hpp index 7d137bb7a..19d4093d6 100644 --- a/src/slic3r/GUI/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -1,14 +1,13 @@ #ifndef slic3r_PresetBundle_hpp_ #define slic3r_PresetBundle_hpp_ -#include "AppConfig.hpp" #include "Preset.hpp" #include #include #include -class wxWindow; +#include "slic3r/GUI/AppConfig.hpp" namespace Slic3r { diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 98389e7da..49e069285 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -61,10 +61,6 @@ set(SLIC3R_GUI_SOURCES GUI/GLToolbar.cpp GUI/Preferences.cpp GUI/Preferences.hpp - GUI/Preset.cpp - GUI/Preset.hpp - GUI/PresetBundle.cpp - GUI/PresetBundle.hpp GUI/PresetHints.cpp GUI/PresetHints.hpp GUI/GUI.cpp diff --git a/src/slic3r/Config/Snapshot.cpp b/src/slic3r/Config/Snapshot.cpp index 2264afa7d..f7d313418 100644 --- a/src/slic3r/Config/Snapshot.cpp +++ b/src/slic3r/Config/Snapshot.cpp @@ -1,6 +1,5 @@ #include "Snapshot.hpp" #include "../GUI/AppConfig.hpp" -#include "../GUI/PresetBundle.hpp" #include @@ -11,7 +10,7 @@ #include #include - +#include "libslic3r/PresetBundle.hpp" #include "libslic3r/libslic3r.h" #include "libslic3r/Time.hpp" #include "libslic3r/Config.hpp" diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 6c070ca99..f2f9f6301 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -7,7 +7,7 @@ #include "libslic3r/BoundingBox.hpp" #include "GUI_App.hpp" -#include "PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" #include "GLCanvas3D.hpp" #include diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index a0df4c659..cd8463a77 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -2,7 +2,7 @@ #include "ConfigManipulation.hpp" #include "I18N.hpp" #include "GUI_App.hpp" -#include "PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" #include diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index c99c5952b..be2919861 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -20,9 +20,9 @@ #include #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/PresetBundle.hpp" #include "slic3r/Utils/PresetUpdater.hpp" #include "AppConfig.hpp" -#include "PresetBundle.hpp" #include "BedShapeDialog.hpp" #include "GUI.hpp" #include "wxExtensions.hpp" diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b4e672c4f..b5cd9fb2a 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -13,11 +13,11 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/Technologies.hpp" #include "libslic3r/Tesselate.hpp" +#include "libslic3r/PresetBundle.hpp" #include "slic3r/GUI/3DScene.hpp" #include "slic3r/GUI/BackgroundSlicingProcess.hpp" #include "slic3r/GUI/GLShader.hpp" #include "slic3r/GUI/GUI.hpp" -#include "slic3r/GUI/PresetBundle.hpp" #include "slic3r/GUI/Tab.hpp" #include "slic3r/GUI/GUI_Preview.hpp" #include "slic3r/GUI/OpenGLManager.hpp" diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 3c000f62e..157875e70 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -31,11 +31,11 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/I18N.hpp" +#include "libslic3r/PresetBundle.hpp" #include "GUI.hpp" #include "GUI_Utils.hpp" #include "AppConfig.hpp" -#include "PresetBundle.hpp" #include "3DScene.hpp" #include "MainFrame.hpp" #include "Plater.hpp" @@ -935,7 +935,7 @@ bool GUI_App::load_language(wxString language, bool initial) m_imgui->set_language(into_u8(language_info->CanonicalName)); //FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only. wxSetlocale(LC_NUMERIC, "C"); - Preset::update_suffix_modified(); + Preset::update_suffix_modified((" (" + _L("modified") + ")").ToUTF8().data()); return true; } diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index c2b257f45..23567695c 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -3,10 +3,10 @@ #include #include -#include "Preset.hpp" #include "ImGuiWrapper.hpp" #include "ConfigWizard.hpp" #include "OpenGLManager.hpp" +#include "libslic3r/Preset.hpp" #include #include diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index b1a5512d4..90a725fbf 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -3,7 +3,7 @@ #include "OptionsGroup.hpp" #include "GUI_App.hpp" -#include "PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" #include "libslic3r/Model.hpp" #include "GLCanvas3D.hpp" #include "Plater.hpp" diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 2f201180a..b87565b03 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1,4 +1,5 @@ #include "libslic3r/libslic3r.h" +#include "libslic3r/PresetBundle.hpp" #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectLayers.hpp" @@ -7,7 +8,6 @@ #include "Plater.hpp" #include "OptionsGroup.hpp" -#include "PresetBundle.hpp" #include "Tab.hpp" #include "wxExtensions.hpp" #include "libslic3r/Model.hpp" diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 2c35fc316..7243e8c73 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -6,7 +6,7 @@ #include "OptionsGroup.hpp" #include "GUI_App.hpp" #include "wxExtensions.hpp" -#include "PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/Geometry.hpp" #include "Selection.hpp" diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index ef78123a4..398cd51d4 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -4,8 +4,8 @@ #include "OptionsGroup.hpp" #include "GUI_App.hpp" #include "wxExtensions.hpp" -#include "PresetBundle.hpp" #include "Plater.hpp" +#include "libslic3r/PresetBundle.hpp" #include "libslic3r/Model.hpp" #include diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index c1e8b4c33..f068ef37d 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -8,7 +8,7 @@ #include "BackgroundSlicingProcess.hpp" #include "OpenGLManager.hpp" #include "GLCanvas3D.hpp" -#include "PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" #include "DoubleSlider.hpp" #include "Plater.hpp" diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index cd4285724..7aa516845 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -6,9 +6,9 @@ #include #include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/PresetBundle.hpp" #include "slic3r/GUI/Camera.hpp" #include "slic3r/GUI/Plater.hpp" +#include "libslic3r/PresetBundle.hpp" #include "libslic3r/Model.hpp" diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 658db64ca..273384da2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -9,7 +9,7 @@ #include "slic3r/GUI/GUI_ObjectSettings.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" #include "libslic3r/Model.hpp" diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 908fe27b1..2856bb35d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -16,7 +16,7 @@ #include "slic3r/GUI/GUI_ObjectSettings.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" #include "libslic3r/SLAPrint.hpp" diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 051e9cf88..6742f5cde 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -8,7 +8,7 @@ #include "slic3r/GUI/Camera.hpp" #include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" #include diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 511c68735..c33ba2850 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -5,7 +5,6 @@ #include "slic3r/GUI/Camera.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_ObjectManipulation.hpp" -#include "slic3r/GUI/PresetBundle.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/Utils/UndoRedo.hpp" @@ -19,6 +18,7 @@ #include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/PresetBundle.hpp" #include diff --git a/src/slic3r/GUI/I18N.hpp b/src/slic3r/GUI/I18N.hpp index 25e46930b..7bad6880e 100644 --- a/src/slic3r/GUI/I18N.hpp +++ b/src/slic3r/GUI/I18N.hpp @@ -12,7 +12,7 @@ #ifndef L // !!! If you needed to translate some wxString, -// !!! please use _(L(string)) +// !!! please use _L(string) // !!! _() - is a standard wxWidgets macro to translate // !!! L() is used only for marking localizable string // !!! It will be used in "xgettext" to create a Locating Message Catalog. diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index 4e9f08ff2..cc779df2a 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -4,11 +4,11 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/AppConfig.hpp" #include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/PresetBundle.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/Utils/SLAImport.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/PresetBundle.hpp" #include #include diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index d4ce21fc0..08caf299b 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -15,9 +15,9 @@ #include "libslic3r/Print.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/SLAPrint.hpp" +#include "libslic3r/PresetBundle.hpp" #include "Tab.hpp" -#include "PresetBundle.hpp" #include "ProgressStatusBar.hpp" #include "3DScene.hpp" #include "AppConfig.hpp" diff --git a/src/slic3r/GUI/Mouse3DController.cpp b/src/slic3r/GUI/Mouse3DController.cpp index baa9356b6..91d2414d5 100644 --- a/src/slic3r/GUI/Mouse3DController.cpp +++ b/src/slic3r/GUI/Mouse3DController.cpp @@ -1,9 +1,9 @@ #include "libslic3r/libslic3r.h" +#include "libslic3r/PresetBundle.hpp" #include "Mouse3DController.hpp" #include "Camera.hpp" #include "GUI_App.hpp" -#include "PresetBundle.hpp" #include "AppConfig.hpp" #include "GLCanvas3D.hpp" #include "Plater.hpp" diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 339badc96..a78683bd4 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -43,6 +43,7 @@ #include "libslic3r/PrintConfig.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/Utils.hpp" +#include "libslic3r/PresetBundle.hpp" #include "GUI.hpp" #include "GUI_App.hpp" @@ -65,7 +66,6 @@ #include "Jobs/ArrangeJob.hpp" #include "Jobs/RotoptimizeJob.hpp" #include "Jobs/SLAImportJob.hpp" -#include "PresetBundle.hpp" #include "BackgroundSlicingProcess.hpp" #include "ProgressStatusBar.hpp" #include "PrintHostDialogs.hpp" @@ -2120,6 +2120,8 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (!config.empty()) { Preset::normalize(config); wxGetApp().preset_bundle->load_config_model(filename.string(), std::move(config)); + if (printer_technology == ptFFF) + CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, &wxGetApp().preset_bundle->project_config); wxGetApp().load_current_presets(); is_project_file = true; } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index d2acc7632..38fc67982 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -7,9 +7,9 @@ #include -#include "Preset.hpp" #include "Selection.hpp" +#include "libslic3r/Preset.hpp" #include "libslic3r/BoundingBox.hpp" #include "Jobs/Job.hpp" #include "Search.hpp" diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 380edb48a..51b5a0c8d 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -15,6 +15,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/PresetBundle.hpp" #include "GUI.hpp" #include "GUI_App.hpp" @@ -22,7 +23,6 @@ #include "MainFrame.hpp" #include "format.hpp" #include "Tab.hpp" -#include "PresetBundle.hpp" #include "PrintHostDialogs.hpp" #include "ConfigWizard.hpp" #include "../Utils/ASCIIFolding.hpp" diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 63110e432..3d9a13490 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -7,7 +7,7 @@ #include #include -#include "Preset.hpp" +#include "libslic3r/Preset.hpp" #include "wxExtensions.hpp" #include "GUI_Utils.hpp" diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp index 24afeb526..c40c4c6ac 100644 --- a/src/slic3r/GUI/PresetHints.cpp +++ b/src/slic3r/GUI/PresetHints.cpp @@ -4,7 +4,6 @@ #include "libslic3r/Slicing.hpp" #include "libslic3r/libslic3r.h" -#include "PresetBundle.hpp" #include "PresetHints.hpp" #include diff --git a/src/slic3r/GUI/PresetHints.hpp b/src/slic3r/GUI/PresetHints.hpp index be049c2c8..a61310f40 100644 --- a/src/slic3r/GUI/PresetHints.hpp +++ b/src/slic3r/GUI/PresetHints.hpp @@ -3,7 +3,7 @@ #include -#include "PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" namespace Slic3r { diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 613a39cce..242e3d725 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -9,10 +9,10 @@ #include "wx/dataview.h" #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/PresetBundle.hpp" #include "GUI_App.hpp" #include "Plater.hpp" #include "Tab.hpp" -#include "PresetBundle.hpp" #define FTS_FUZZY_MATCH_IMPLEMENTATION #include "fts_fuzzy_match.h" diff --git a/src/slic3r/GUI/Search.hpp b/src/slic3r/GUI/Search.hpp index 9701e6808..8202222e9 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -14,8 +14,8 @@ #include #include "GUI_Utils.hpp" -#include "Preset.hpp" #include "wxExtensions.hpp" +#include "libslic3r/Preset.hpp" namespace Slic3r { diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index b128ec03d..88c11030d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1,8 +1,8 @@ // #include "libslic3r/GCodeSender.hpp" #include "slic3r/Utils/Serial.hpp" #include "Tab.hpp" -#include "PresetBundle.hpp" #include "PresetHints.hpp" +#include "libslic3r/PresetBundle.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Model.hpp" @@ -32,7 +32,6 @@ #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" -#include "ConfigWizard.hpp" #include "Plater.hpp" #include "MainFrame.hpp" #include "format.hpp" diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index a0bf536c1..bc15efa35 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -33,8 +33,8 @@ #include "Event.hpp" #include "wxExtensions.hpp" #include "ConfigManipulation.hpp" -#include "Preset.hpp" #include "OptionsGroup.hpp" +#include "libslic3r/Preset.hpp" namespace Slic3r { namespace GUI { diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index c32613c46..dec251858 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -19,9 +19,9 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/format.hpp" #include "libslic3r/Utils.hpp" +#include "libslic3r/PresetBundle.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/I18N.hpp" -#include "slic3r/GUI/PresetBundle.hpp" #include "slic3r/GUI/UpdateDialogs.hpp" #include "slic3r/GUI/ConfigWizard.hpp" #include "slic3r/GUI/GUI_App.hpp" From 7c7dcab03299932cab681fe82996ac96e579f0df Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 18 Jun 2020 11:39:25 +0200 Subject: [PATCH 03/70] First filling of the PhysicalPrinterDialog + Fixed scaling of the icons for the BitmapComboBoxes + Fixed calling of the blinking icons on the Tabs --- src/slic3r/GUI/Field.cpp | 1 + src/slic3r/GUI/PresetComboBoxes.cpp | 46 +++++++++++++++++------------ src/slic3r/GUI/PresetComboBoxes.hpp | 7 ++--- src/slic3r/GUI/Tab.cpp | 4 ++- 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 3a06c3056..8ab82e20d 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -295,6 +295,7 @@ void Field::msw_rescale(bool rescale_sidetext) { m_Undo_to_sys_btn->msw_rescale(); m_Undo_btn->msw_rescale(); + m_blinking_bmp->msw_rescale(); // update em_unit value m_em_unit = em_unit(m_parent); diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 51b5a0c8d..6c38c866d 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -58,7 +58,7 @@ PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, size, 0, nullptr, wxCB_READONLY), m_type(preset_type), m_last_selected(wxNOT_FOUND), - m_em_unit(wxGetApp().em_unit()), + m_em_unit(em_unit(this)), m_preset_bundle(wxGetApp().preset_bundle), m_bitmap_cache(new BitmapCache) { @@ -99,9 +99,9 @@ PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const default: break; } - m_bitmapCompatible = ScalableBitmap(nullptr, "flag_green"); - m_bitmapIncompatible = ScalableBitmap(nullptr, "flag_red"); - m_bitmapLock = ScalableBitmap(nullptr, "lock_closed"); + m_bitmapCompatible = ScalableBitmap(this, "flag_green"); + m_bitmapIncompatible = ScalableBitmap(this, "flag_red"); + m_bitmapLock = ScalableBitmap(this, "lock_closed"); // parameters for an icon's drawing fill_width_height(); @@ -120,7 +120,7 @@ void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type) void PresetComboBox::msw_rescale() { - m_em_unit = wxGetApp().em_unit(); + m_em_unit = em_unit(this); m_bitmapLock.msw_rescale(); m_bitmapIncompatible.msw_rescale(); @@ -241,7 +241,7 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset evt.StopPropagation(); if (marker == LABEL_ITEM_PHYSICAL_PRINTERS) { - PhysicalPrinterDialog dlg; + PhysicalPrinterDialog dlg(_L("New Physical Printer"), this->m_last_selected); dlg.ShowModal(); return; } @@ -360,7 +360,7 @@ PlaterPresetComboBox::~PlaterPresetComboBox() void PlaterPresetComboBox::update() { if (m_type == Preset::TYPE_FILAMENT && - (m_collection->get_edited_preset().printer_technology() == ptSLA || + (m_preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA || m_preset_bundle->filament_presets.size() <= m_extruder_idx) ) return; @@ -586,13 +586,13 @@ void PlaterPresetComboBox::msw_rescale() // --------------------------------- -// *** PlaterPresetComboBox *** +// *** TabPresetComboBox *** // --------------------------------- -TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type) : +TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type, bool is_from_physical_printer/* = false*/) : PresetComboBox(parent, preset_type, wxSize(35 * wxGetApp().em_unit(), -1)) { - Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { + Bind(wxEVT_COMBOBOX, [this, is_from_physical_printer](wxCommandEvent& evt) { // see https://github.com/prusa3d/PrusaSlicer/issues/3889 // Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender") // m_presets_choice->GetSelection() will return first item, because search in PopupListCtrl is case-insensitive. @@ -603,9 +603,16 @@ TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type) if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) { this->SetSelection(this->m_last_selected); if (marker == LABEL_ITEM_WIZARD_PRINTERS) - wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); }); + wxTheApp->CallAfter([this, is_from_physical_printer]() { + wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); + if (is_from_physical_printer) + update(); + }); } - else if (m_last_selected != selected_item || m_collection->current_is_dirty()) { + else if ( is_from_physical_printer) { + // do nothing + } + else if (m_last_selected != selected_item || m_collection->current_is_dirty() ) { std::string selected_string = this->GetString(selected_item).ToUTF8().data(); Tab* tab = wxGetApp().get_tab(this->m_type); assert (tab); @@ -638,7 +645,7 @@ void TabPresetComboBox::update() continue; std::string bitmap_key = "tab"; - wxBitmap main_bmp = create_scaled_bitmap(m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name); + wxBitmap main_bmp = create_scaled_bitmap(m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name, this); if (m_type == Preset::TYPE_PRINTER) { bitmap_key += "_printer"; if (preset.printer_technology() == ptSLA) @@ -694,8 +701,8 @@ void TabPresetComboBox::update() if (bmp == nullptr) { // Create the bitmap with color bars. std::vector bmps; - bmps.emplace_back(create_scaled_bitmap(m_main_bitmap_name)); - bmps.emplace_back(create_scaled_bitmap("edit_uni")); + bmps.emplace_back(create_scaled_bitmap(m_main_bitmap_name, this)); + bmps.emplace_back(create_scaled_bitmap("edit_uni", this)); bmp = m_bitmap_cache->insert(bitmap_key, bmps); } set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); @@ -753,8 +760,8 @@ void TabPresetComboBox::update_dirty() //------------------------------------------ -PhysicalPrinterDialog::PhysicalPrinterDialog() - : DPIDialog(NULL, wxID_ANY, _L("Search"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +PhysicalPrinterDialog::PhysicalPrinterDialog(const wxString& printer_name, int last_selected_preset) + : DPIDialog(NULL, wxID_ANY, _L("PhysicalPrinter"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { SetFont(wxGetApp().normal_font()); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -762,8 +769,9 @@ PhysicalPrinterDialog::PhysicalPrinterDialog() int border = 10; int em = em_unit(); - printer_text = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); - printer_presets = new PlaterPresetComboBox(this, Preset::TYPE_PRINTER); + printer_text = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); + printer_presets = new TabPresetComboBox(this, Preset::TYPE_PRINTER, true); + printer_presets->update(); wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 3d9a13490..38b98d658 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -43,7 +43,6 @@ public: }; void set_label_marker(int item, LabelItemType label_item_type = LABEL_ITEM_MARKER); - int em_unit() const { return m_em_unit; } virtual void update() {}; virtual void msw_rescale(); @@ -131,13 +130,13 @@ private: // --------------------------------- -// *** PlaterPresetComboBox *** +// *** TabPresetComboBox *** // --------------------------------- class TabPresetComboBox : public PresetComboBox { public: - TabPresetComboBox(wxWindow *parent, Preset::Type preset_type); + TabPresetComboBox(wxWindow *parent, Preset::Type preset_type, bool is_from_physical_printer = false); ~TabPresetComboBox() {} void set_show_incompatible_presets(bool show_incompatible_presets) { show_incompatible = show_incompatible_presets; @@ -165,7 +164,7 @@ class PhysicalPrinterDialog : public DPIDialog PresetComboBox* printer_presets; public: - PhysicalPrinterDialog(); + PhysicalPrinterDialog(const wxString& printer_name, int last_selected_preset); ~PhysicalPrinterDialog() {} protected: diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 88c11030d..bb19e139d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -815,7 +815,7 @@ void Tab::update_visibility() void Tab::msw_rescale() { - m_em_unit = wxGetApp().em_unit(); + m_em_unit = em_unit(m_parent); m_mode_sizer->msw_rescale(); m_presets_choice->msw_rescale(); @@ -827,6 +827,8 @@ void Tab::msw_rescale() btn->msw_rescale(); for (const auto bmp : m_scaled_bitmaps) bmp->msw_rescale(); + for (const auto ikon : m_blinking_ikons) + ikon.second->msw_rescale(); for (ScalableBitmap& bmp : m_mode_bitmap_cache) bmp.msw_rescale(); From 02624689ce345dc20d24194d4c6b4d8c438d7907 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 24 Jun 2020 08:50:01 +0200 Subject: [PATCH 04/70] Physical Printers. - save/load printers - consistency between selection on Tab and Plater --- src/libslic3r/Preset.cpp | 250 ++++++++++++-------- src/libslic3r/Preset.hpp | 109 ++++----- src/libslic3r/PresetBundle.cpp | 24 +- src/libslic3r/PresetBundle.hpp | 1 + src/libslic3r/PrintConfig.cpp | 20 ++ src/slic3r/GUI/Plater.cpp | 30 ++- src/slic3r/GUI/PresetComboBoxes.cpp | 342 ++++++++++++++++++++++++---- src/slic3r/GUI/PresetComboBoxes.hpp | 37 ++- src/slic3r/GUI/Tab.cpp | 10 + 9 files changed, 600 insertions(+), 223 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index e46cd6c82..94c9577df 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1339,108 +1339,178 @@ const Preset* PrinterPresetCollection::find_by_model_id(const std::string &model return it != cend() ? &*it : nullptr; } -/* -PhysicalPrinter& PhysicalPrinterCollection::load_external_printer( - // Path to the profile source file (a G-code, an AMF or 3MF file, a config file) - const std::string& path, - // Name of the profile, derived from the source file name. - const std::string& name, - // Original name of the profile, extracted from the loaded config. Empty, if the name has not been stored. - const std::string& original_name, - // Config to initialize the preset from. - const DynamicPrintConfig& config, - // Select the preset after loading? - bool select) -{ - // Load the preset over a default preset, so that the missing fields are filled in from the default preset. - DynamicPrintConfig cfg(this->default_printer().config); - cfg.apply_only(config, cfg.keys(), true); - // Is there a preset already loaded with the name stored inside the config? - std::deque::iterator it = this->find_printer_internal(original_name); - bool found = it != m_printers.end() && it->name == original_name; - if (!found) { - // Try to match the original_name against the "renamed_from" profile names of loaded system profiles. - / * - it = this->find_preset_renamed(original_name); - found = it != m_presets.end(); - * / - } - if (found) { - if (profile_print_params_same(it->config, cfg)) { - // The preset exists and it matches the values stored inside config. - if (select) - this->select_printer(it - m_printers.begin()); - return *it; - } - if (profile_host_params_same_or_anonymized(it->config, cfg) == ProfileHostParams::Anonymized) { - // The project being loaded is anonymized. Replace the empty host keys of the loaded profile with the data from the original profile. - // See "Octoprint Settings when Opening a .3MF file" GH issue #3244 - auto opt_update = [it, &cfg](const std::string& opt_key) { - auto opt = it->config.option(opt_key); - if (opt != nullptr) - cfg.set_key_value(opt_key, opt->clone()); - }; - opt_update("print_host"); - opt_update("printhost_apikey"); - opt_update("printhost_cafile"); - } - } - // The external preset does not match an internal preset, load the external preset. - std::string new_name; - for (size_t idx = 0;; ++idx) { - std::string suffix; - if (original_name.empty()) { - if (idx > 0) - suffix = " (" + std::to_string(idx) + ")"; - } - else { - if (idx == 0) - suffix = " (" + original_name + ")"; - else - suffix = " (" + original_name + "-" + std::to_string(idx) + ")"; - } - new_name = name + suffix; - it = this->find_printer_internal(new_name); - if (it == m_printers.end() || it->name != new_name) - // Unique profile name. Insert a new profile. - break; - if (profile_print_params_same(it->config, cfg)) { - // The preset exists and it matches the values stored inside config. - if (select) - this->select_printer(it - m_printers.begin()); - return *it; - } - // Form another profile name. - } - // Insert a new profile. - PhysicalPrinter& printer = this->load_printer(path, new_name, std::move(cfg), select); - return printer; +// ------------------------- +// *** PhysicalPrinter *** +// ------------------------- + +const std::vector& PhysicalPrinter::printer_options() +{ + static std::vector s_opts; + if (s_opts.empty()) { + s_opts = { + "preset_name", + "printer_technology", + "host_type", + "print_host", + "printhost_apikey", + "printhost_cafile", + "login", + "password" + }; + } + return s_opts; } -void PhysicalPrinterCollection::save_printer(const std::string& new_name) +const std::string& PhysicalPrinter::get_preset_name() +{ + return config.opt_string("preset_name"); +} + +void PhysicalPrinter::update_from_preset(const Preset& preset) +{ + config.apply_only(preset.config, printer_options(), false); + // add preset name to the options list + config.set_key_value("preset_name", new ConfigOptionString(preset.name)); +} + +void PhysicalPrinter::update_from_config(const DynamicPrintConfig& new_config) +{ + config.apply_only(new_config, printer_options(), false); +} + +PhysicalPrinter::PhysicalPrinter(const std::string& name, const Preset& preset) : + name(name) +{ + update_from_preset(preset); +} + + +// ----------------------------------- +// *** PhysicalPrinterCollection *** +// ----------------------------------- + +PhysicalPrinterCollection::PhysicalPrinterCollection( const std::vector& keys) +{ +} + +// Load all presets found in dir_path. +// Throws an exception on error. +void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const std::string& subdir) +{ + boost::filesystem::path dir = boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred(); + m_dir_path = dir.string(); + std::string errors_cummulative; + // Store the loaded printers into a new vector, otherwise the binary search for already existing presets would be broken. + std::deque printers_loaded; + for (auto& dir_entry : boost::filesystem::directory_iterator(dir)) + if (Slic3r::is_ini_file(dir_entry)) { + std::string name = dir_entry.path().filename().string(); + // Remove the .ini suffix. + name.erase(name.size() - 4); + if (this->find_printer(name, false)) { + // This happens when there's is a preset (most likely legacy one) with the same name as a system preset + // that's already been loaded from a bundle. + BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name; + continue; + } + try { + PhysicalPrinter printer(name); + printer.file = dir_entry.path().string(); + // Load the preset file, apply preset values on top of defaults. + try { + DynamicPrintConfig config; + config.load_from_ini(printer.file); + printer.update_from_config(config); + printer.loaded = true; + } + catch (const std::ifstream::failure& err) { + throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + printer.file + "\n\tReason: " + err.what()); + } + catch (const std::runtime_error& err) { + throw std::runtime_error(std::string("Failed loading the preset file: ") + printer.file + "\n\tReason: " + err.what()); + } + printers_loaded.emplace_back(printer); + } + catch (const std::runtime_error& err) { + errors_cummulative += err.what(); + errors_cummulative += "\n"; + } + } + m_printers.insert(m_printers.end(), std::make_move_iterator(printers_loaded.begin()), std::make_move_iterator(printers_loaded.end())); + std::sort(m_printers.begin(), m_printers.end()); +//! this->select_preset(first_visible_idx()); + if (!errors_cummulative.empty()) + throw std::runtime_error(errors_cummulative); +} + +PhysicalPrinter* PhysicalPrinterCollection::find_printer( const std::string& name, bool first_visible_if_not_found) +{ + PhysicalPrinter key(name); + auto it = this->find_printer_internal(name); + // Ensure that a temporary copy is returned if the preset found is currently selected. + return (it != m_printers.end() && it->name == key.name) ? &this->printer(it - m_printers.begin()) : + first_visible_if_not_found ? &this->printer(0) : nullptr; +} + +// Generate a file path from a profile name. Add the ".ini" suffix if it is missing. +std::string PhysicalPrinterCollection::path_from_name(const std::string& new_name) const +{ + std::string file_name = boost::iends_with(new_name, ".ini") ? new_name : (new_name + ".ini"); + return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); +} + +void PhysicalPrinterCollection::save_printer(const PhysicalPrinter& edited_printer) { // 1) Find the printer with a new_name or create a new one, // initialize it with the edited config. - auto it = this->find_printer_internal(new_name); - if (it != m_printers.end() && it->name == new_name) { - // Preset with the same name found. - PhysicalPrinter& printer = *it; + auto it = this->find_printer_internal(edited_printer.name); + if (it != m_printers.end() && it->name == edited_printer.name) { + // Printer with the same name found. // Overwriting an existing preset. - printer.config = std::move(m_edited_printer.config); + it->config = std::move(edited_printer.config); } else { // Creating a new printer. - PhysicalPrinter& printer = *m_printers.insert(it, m_edited_printer); - std::string old_name = printer.name; - printer.name = new_name; + it = m_printers.insert(it, edited_printer); } - // 2) Activate the saved preset. - this->select_printer_by_name(new_name, true); - // 3) Store the active preset to disk. - this->get_selected_preset().save(); + assert(it != m_printers.end()); + + // 2) Save printer + PhysicalPrinter& printer = *it; + if (printer.file.empty()) + printer.file = this->path_from_name(printer.name); + printer.save(); + + // update idx_selected + m_idx_selected = it - m_printers.begin(); } -*/ + +bool PhysicalPrinterCollection::delete_printer(const std::string& name) +{ + auto it = this->find_printer_internal(name); + + const PhysicalPrinter& printer = *it; + if (it == m_printers.end()) + return false; + + // Erase the preset file. + boost::nowide::remove(printer.file.c_str()); + m_printers.erase(it); + return true; +} + +PhysicalPrinter& PhysicalPrinterCollection::select_printer_by_name(const std::string& name) +{ + auto it = this->find_printer_internal(name); + assert(it != m_printers.end()); + + // update idx_selected + m_idx_selected = it - m_printers.begin(); + return *it; +} + + namespace PresetUtils { const VendorProfile::PrinterModel* system_printer_model(const Preset &preset) { diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index b0af2f142..c08a1a0fb 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -535,14 +535,14 @@ namespace PresetUtils { class PhysicalPrinter { public: - PhysicalPrinter(const std::string& name) : name(name) {} + PhysicalPrinter() {} + PhysicalPrinter(const std::string& name) : name(name){} + PhysicalPrinter(const std::string& name, const Preset& preset); // Name of the Physical Printer, usually derived form the file name. std::string name; // File name of the Physical Printer. std::string file; - // Name of the related Printer preset - std::string preset_name; // Has this profile been loaded? bool loaded = false; @@ -550,7 +550,13 @@ public: // Configuration data, loaded from a file, or set from the defaults. DynamicPrintConfig config; + static const std::vector& printer_options(); + const std::string& get_preset_name(); + void save() { this->config.save(this->file); } + void save_to(const std::string& file_name) const { this->config.save(file_name); } + void update_from_preset(const Preset& preset); + void update_from_config(const DynamicPrintConfig &new_config); // Return a printer technology, return ptFFF if the printer technology is not set. static PrinterTechnology printer_technology(const DynamicPrintConfig& cfg) { @@ -562,18 +568,22 @@ public: PrinterTechnology printer_technology() const { return printer_technology(this->config); } // Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection. - bool operator<(const Preset& other) const { return this->name < other.name; } + bool operator<(const PhysicalPrinter& other) const { return this->name < other.name; } protected: friend class PhysicalPrinterCollection; }; -/* -// Collections of presets of the same type (one of the Print, Filament or Printer type). + + +// --------------------------------- +// *** PhysicalPrinterCollection *** +// --------------------------------- + +// Collections of physical printers class PhysicalPrinterCollection { public: - // Initialize the PresetCollection with the "- default -" preset. - PhysicalPrinterCollection(const std::vector& keys) : m_idx_selected(0) {} + PhysicalPrinterCollection(const std::vector& keys); ~PhysicalPrinterCollection() {} typedef std::deque::iterator Iterator; @@ -585,63 +595,39 @@ public: ConstIterator end() const { return m_printers.cend(); } ConstIterator cend() const { return m_printers.cend(); } + bool empty() const {return m_printers.empty(); } + void reset(bool delete_files) {}; const std::deque& operator()() const { return m_printers; } // Load ini files of the particular type from the provided directory path. - void load_printers(const std::string& dir_path, const std::string& subdir){}; - - // Load a preset from an already parsed config file, insert it into the sorted sequence of presets - // and select it, losing previous modifications. - PhysicalPrinter& load_printer(const std::string& path, const std::string& name, const DynamicPrintConfig& config, bool select = true); - PhysicalPrinter& load_printer(const std::string& path, const std::string& name, DynamicPrintConfig&& config, bool select = true); - - PhysicalPrinter& load_external_printer( - // Path to the profile source file (a G-code, an AMF or 3MF file, a config file) - const std::string& path, - // Name of the profile, derived from the source file name. - const std::string& name, - // Original name of the profile, extracted from the loaded config. Empty, if the name has not been stored. - const std::string& original_name, - // Config to initialize the preset from. - const DynamicPrintConfig& config, - // Select the preset after loading? - bool select = true); + void load_printers(const std::string& dir_path, const std::string& subdir); // Save the printer under a new name. If the name is different from the old one, // a new printer is stored into the list of printers. - // ? New printer is activated. - void save_printer(const std::string& new_name); + // New printer is activated. + void save_printer(const PhysicalPrinter& printer); // Delete the current preset, activate the first visible preset. // returns true if the preset was deleted successfully. - bool delete_current_printer() {return true;} - // Delete the current preset, activate the first visible preset. - // returns true if the preset was deleted successfully. - bool delete_printer(const std::string& name) { return true; } + bool delete_printer(const std::string& name); - // Select a printer. If an invalid index is provided, the first visible printer is selected. - PhysicalPrinter& select_printer(size_t idx); // Return the selected preset, without the user modifications applied. - PhysicalPrinter& get_selected_preset() { return m_printers[m_idx_selected]; } - const PhysicalPrinter& get_selected_preset() const { return m_printers[m_idx_selected]; } + PhysicalPrinter& get_selected_printer() { return m_printers[m_idx_selected]; } + const PhysicalPrinter& get_selected_printer() const { return m_printers[m_idx_selected]; } size_t get_selected_idx() const { return m_idx_selected; } // Returns the name of the selected preset, or an empty string if no preset is selected. - std::string get_selected_preset_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_preset().name; } - PhysicalPrinter& get_edited_preset() { return m_edited_printer; } - const PhysicalPrinter& get_edited_preset() const { return m_edited_printer; } + std::string get_selected_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().name; } + DynamicPrintConfig* get_selected_printer_config() { return (m_idx_selected == size_t(-1)) ? nullptr : &(this->get_selected_printer().config); } - // Return a preset possibly with modifications. - PhysicalPrinter& default_printer(size_t idx = 0) { return m_printers[idx]; } - const PhysicalPrinter& default_printer(size_t idx = 0) const { return m_printers[idx]; } + // select printer with name and return reference on it + PhysicalPrinter& select_printer_by_name(const std::string& name); + bool has_selection() const { return m_idx_selected != size_t(-1); } + void unselect_printer() { m_idx_selected = size_t(-1); } - // used to update preset_choice from Tab - const std::deque& get_presets() const { return m_printers; } - size_t get_idx_selected() { return m_idx_selected; } - - // Return a preset by an index. If the preset is active, a temporary copy is returned. - PhysicalPrinter& printer(size_t idx) { return (idx == m_idx_selected) ? m_edited_printer : m_printers[idx]; } + // Return a printer by an index. If the printer is active, a temporary copy is returned. + PhysicalPrinter& printer(size_t idx) { return m_printers[idx]; } const PhysicalPrinter& printer(size_t idx) const { return const_cast(this)->printer(idx); } // Return a preset by its name. If the preset is active, a temporary copy is returned. @@ -652,20 +638,10 @@ public: return const_cast(this)->find_printer(name, first_visible_if_not_found); } - // Return number of presets including the "- default -" preset. - size_t size() const { return m_printers.size(); } - - // Select a profile by its name. Return true if the selection changed. - // Without force, the selection is only updated if the index changes. - // With force, the changes are reverted if the new index is the same as the old index. - bool select_printer_by_name(const std::string& name, bool force) {}; - // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. std::string path_from_name(const std::string& new_name) const; private: -// PhysicalPrinterCollection(); - PhysicalPrinterCollection(const PhysicalPrinterCollection& other); PhysicalPrinterCollection& operator=(const PhysicalPrinterCollection& other); // Find a preset position in the sorted list of presets. @@ -674,8 +650,8 @@ private: // If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name. std::deque::iterator find_printer_internal(const std::string& name) { - PhysicalPrinter key(name); - auto it = std::lower_bound(m_printers.begin()+0, m_printers.end(), key); + PhysicalPrinter printer(name); + auto it = std::lower_bound(m_printers.begin(), m_printers.end(), printer); return it; } std::deque::const_iterator find_printer_internal(const std::string& name) const @@ -683,23 +659,18 @@ private: return const_cast(this)->find_printer_internal(name); } - static std::vector dirty_options(const Preset* edited, const Preset* reference, const bool is_printer_type = false); - - // List of presets, starting with the "- default -" preset. + // List of printers // Use deque to force the container to allocate an object per each entry, // so that the addresses of the presets don't change during resizing of the container. std::deque m_printers; - // Initially this printer contains a copy of the selected printer. Later on, this copy may be modified by the user. - PhysicalPrinter m_edited_printer; - // Selected preset. - size_t m_idx_selected; + + // Selected printer. + size_t m_idx_selected = size_t(-1); // Path to the directory to store the config files into. std::string m_dir_path; }; -////////////////////////////////////////////////////////////////////// -*/ } // namespace Slic3r diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 9da7731a4..7c4fa1cb2 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -41,7 +41,8 @@ PresetBundle::PresetBundle() : filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast(FullPrintConfig::defaults())), sla_materials(Preset::TYPE_SLA_MATERIAL, Preset::sla_material_options(), static_cast(SLAFullPrintConfig::defaults())), sla_prints(Preset::TYPE_SLA_PRINT, Preset::sla_print_options(), static_cast(SLAFullPrintConfig::defaults())), - printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast(FullPrintConfig::defaults()), "- default FFF -") + printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast(FullPrintConfig::defaults()), "- default FFF -"), + physical_printers(PhysicalPrinter::printer_options()) { // The following keys are handled by the UI, they do not have a counterpart in any StaticPrintConfig derived classes, // therefore they need to be handled differently. As they have no counterpart in StaticPrintConfig, they are not being @@ -139,14 +140,16 @@ void PresetBundle::setup_directories() data_dir / "presets" / "filament", data_dir / "presets" / "sla_print", data_dir / "presets" / "sla_material", - data_dir / "presets" / "printer" + data_dir / "presets" / "printer", + data_dir / "presets" / "physical_printer" #else // Store the print/filament/printer presets at the same location as the upstream Slic3r. data_dir / "print", data_dir / "filament", data_dir / "sla_print", data_dir / "sla_material", - data_dir / "printer" + data_dir / "printer", + data_dir / "physical_printer" #endif }; for (const boost::filesystem::path &path : paths) { @@ -196,6 +199,11 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_ } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } + try { + this->physical_printers.load_printers(dir_user_presets, "physical_printer"); + } catch (const std::runtime_error &err) { + errors_cummulative += err.what(); + } this->update_multi_material_filament_presets(); this->update_compatible(PresetSelectCompatibleType::Never); if (! errors_cummulative.empty()) @@ -422,6 +430,14 @@ void PresetBundle::load_selections(AppConfig &config, const std::string &preferr // exist. this->update_compatible(PresetSelectCompatibleType::Always); this->update_multi_material_filament_presets(); + + // Parse the initial physical printer name. + std::string initial_physical_printer_name = remove_ini_suffix(config.get("extras", "physical_printer")); + + // Activate physical printer from the config + const PhysicalPrinter* initial_physical_printer = physical_printers.find_printer(initial_physical_printer_name); + if (initial_physical_printer) + physical_printers.select_printer_by_name(initial_physical_printer_name); } // Export selections (current print, current filaments, current printer) into config.ini @@ -441,6 +457,8 @@ void PresetBundle::export_selections(AppConfig &config) config.set("presets", "sla_print", sla_prints.get_selected_preset_name()); config.set("presets", "sla_material", sla_materials.get_selected_preset_name()); config.set("presets", "printer", printers.get_selected_preset_name()); + + config.set("extras", "physical_printer", physical_printers.get_selected_printer_name()); } DynamicPrintConfig PresetBundle::full_config() const diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 19d4093d6..2906584d3 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -39,6 +39,7 @@ public: PresetCollection& materials(PrinterTechnology pt) { return pt == ptFFF ? this->filaments : this->sla_materials; } const PresetCollection& materials(PrinterTechnology pt) const { return pt == ptFFF ? this->filaments : this->sla_materials; } PrinterPresetCollection printers; + PhysicalPrinterCollection physical_printers; // Filament preset names for a multi-extruder or multi-material print. // extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size() std::vector filament_presets; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index d6a23d75d..93ef170fb 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -130,6 +130,26 @@ void PrintConfigDef::init_common_params() def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.2)); + + // Options used by physical printers + + def = this->add("login", coString); + def->label = L("Login"); +// def->tooltip = L(""); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("password", coString); + def->label = L("Password"); +// def->tooltip = L(""); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("preset_name", coString); + def->label = L("Printer preset name"); + def->tooltip = L("Related printer preset name"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); } void PrintConfigDef::init_fff_params() diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a78683bd4..048e17926 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -942,8 +942,6 @@ void Sidebar::msw_rescale() void Sidebar::sys_color_changed() { - // Update preset comboboxes in respect to the system color ... - // combo->msw_rescale() updates icon on button, so use it for (PlaterPresetComboBox* combo : std::vector{ p->combo_print, p->combo_sla_print, p->combo_sla_material, @@ -952,12 +950,8 @@ void Sidebar::sys_color_changed() for (PlaterPresetComboBox* combo : p->combos_filament) combo->msw_rescale(); - // ... then refill them and set min size to correct layout of the sidebar - update_all_preset_comboboxes(); - p->object_list->sys_color_changed(); p->object_manipulation->sys_color_changed(); -// p->object_settings->msw_rescale(); p->object_layers->sys_color_changed(); // btn...->msw_rescale() updates icon on button, so use it @@ -3208,12 +3202,23 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) //! instead of //! combo->GetStringSelection().ToUTF8().data()); - const std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type, + std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type, Preset::remove_suffix_modified(combo->GetString(selection).ToUTF8().data())); if (preset_type == Preset::TYPE_FILAMENT) { wxGetApp().preset_bundle->set_filament_preset(idx, preset_name); } + + if (preset_type == Preset::TYPE_PRINTER) { + if(combo->is_selected_physical_printer()) { + // Select related printer preset on the Printer Settings Tab + const std::string printer_name = combo->GetString(selection).ToUTF8().data(); + PhysicalPrinter& printer = wxGetApp().preset_bundle->physical_printers.select_printer_by_name(printer_name); + preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type, printer.get_preset_name()); + } + else + wxGetApp().preset_bundle->physical_printers.unselect_printer(); + } // TODO: ? if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) { @@ -3974,7 +3979,12 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const this->ready_to_slice = ready_to_slice; wxWindowUpdateLocker noUpdater(sidebar); - const auto prin_host_opt = config->option("print_host"); + + DynamicPrintConfig* selected_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config(); + if (!selected_printer_config) + selected_printer_config = config; + + const auto prin_host_opt = selected_printer_config->option("print_host"); const bool send_gcode_shown = prin_host_opt != nullptr && !prin_host_opt->value.empty(); // when a background processing is ON, export_btn and/or send_btn are showing @@ -4893,7 +4903,9 @@ void Plater::send_gcode() { if (p->model.objects.empty()) { return; } - PrintHostJob upload_job(p->config); + // if physical_printer is selected, send gcode for this printer + DynamicPrintConfig* physical_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config(); + PrintHostJob upload_job(physical_printer_config ? physical_printer_config : p->config); if (upload_job.empty()) { return; } // Obtain default output path diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 6c38c866d..ce25d5690 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -31,6 +31,7 @@ #include "../Utils/UndoRedo.hpp" #include "RemovableDriveManager.hpp" #include "BitmapCache.hpp" +#include "BonjourDialog.hpp" using Slic3r::GUI::format_wxstr; @@ -241,8 +242,9 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset evt.StopPropagation(); if (marker == LABEL_ITEM_PHYSICAL_PRINTERS) { - PhysicalPrinterDialog dlg(_L("New Physical Printer"), this->m_last_selected); - dlg.ShowModal(); + PhysicalPrinterDialog dlg(wxEmptyString); + if (dlg.ShowModal() == wxID_OK) + this->update(); return; } if (marker >= LABEL_ITEM_WIZARD_PRINTERS) { @@ -255,7 +257,7 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset } wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); }); } - } else if ( this->m_last_selected != selected_item || m_collection->current_is_dirty() ) { + } else if (marker == LABEL_ITEM_PHYSICAL_PRINTER || this->m_last_selected != selected_item || m_collection->current_is_dirty() ) { this->m_last_selected = selected_item; evt.SetInt(this->m_type); evt.Skip(); @@ -319,6 +321,16 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset edit_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent) { + // In a case of a physical printer, for its editing open PhysicalPrinterDialog + if (m_type == Preset::TYPE_PRINTER && this->is_selected_physical_printer()) + { + PhysicalPrinterDialog dlg(this->GetString(this->GetSelection())); + if (dlg.ShowModal() == wxID_OK) { + update(); + return; + } + } + Tab* tab = wxGetApp().get_tab(m_type); if (!tab) return; @@ -355,6 +367,13 @@ PlaterPresetComboBox::~PlaterPresetComboBox() edit_btn->Destroy(); } +bool PlaterPresetComboBox::is_selected_physical_printer() +{ + auto selected_item = this->GetSelection(); + auto marker = reinterpret_cast(this->GetClientData(selected_item)); + return marker == LABEL_ITEM_PHYSICAL_PRINTER; +} + // Only the compatible presets are shown. // If an incompatible preset is selected, it is shown as well. void PlaterPresetComboBox::update() @@ -388,7 +407,6 @@ void PlaterPresetComboBox::update() bool wide_icons = !selected_preset.is_compatible; std::map nonsys_presets; - std::map physical_printers; wxString selected = ""; wxString tooltip = ""; @@ -400,9 +418,11 @@ void PlaterPresetComboBox::update() for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) { const Preset& preset = presets[i]; - bool is_selected = m_type == Preset::TYPE_FILAMENT ? - m_preset_bundle->filament_presets[m_extruder_idx] == preset.name : - i == m_collection->get_selected_idx(); + bool is_selected = m_type == Preset::TYPE_FILAMENT ? + m_preset_bundle->filament_presets[m_extruder_idx] == preset.name : + // The case, when some physical printer is selected + m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection() ? false : + i == m_collection->get_selected_idx(); if (!preset.is_visible || (!preset.is_compatible && !is_selected)) continue; @@ -495,17 +515,6 @@ void PlaterPresetComboBox::update() selected_preset_item = GetCount() - 1; } } - if (!physical_printers.empty()) - { - set_label_marker(Append(separator(L("Physical printers")), wxNullBitmap)); - for (std::map::iterator it = physical_printers.begin(); it != physical_printers.end(); ++it) { - Append(it->first, *it->second); - if (it->first == selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; - } - } if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) { std::string bitmap_key = ""; @@ -536,8 +545,45 @@ void PlaterPresetComboBox::update() else set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); } - if (m_type == Preset::TYPE_PRINTER) { - std::string bitmap_key = ""; + + if (m_type == Preset::TYPE_PRINTER) + { + // add Physical printers, if any exists + if (!m_preset_bundle->physical_printers.empty()) { + set_label_marker(Append(separator(L("Physical printers")), wxNullBitmap)); + const PhysicalPrinterCollection& ph_printers = m_preset_bundle->physical_printers; + + for (PhysicalPrinterCollection::ConstIterator it = ph_printers.begin(); it != ph_printers.end(); ++it) { + std::string bitmap_key = it->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; + if (wide_icons) + bitmap_key += "wide,"; + bitmap_key += "-h" + std::to_string(icon_height); + + wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(m_bitmap_cache->mkclear(norm_icon_width, icon_height)); + // Paint the color bars. + bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); + bmps.emplace_back(create_scaled_bitmap(it->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name)); + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width+norm_icon_width, icon_height)); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } + + set_label_marker(Append(wxString::FromUTF8((it->name).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); + if (ph_printers.has_selection() && it->name == ph_printers.get_selected_printer_name() || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + } + } + + // add LABEL_ITEM_PHYSICAL_PRINTERS + std::string bitmap_key; if (wide_icons) bitmap_key += "wide,"; bitmap_key += "edit_preset_list"; @@ -589,10 +635,10 @@ void PlaterPresetComboBox::msw_rescale() // *** TabPresetComboBox *** // --------------------------------- -TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type, bool is_from_physical_printer/* = false*/) : +TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type) : PresetComboBox(parent, preset_type, wxSize(35 * wxGetApp().em_unit(), -1)) { - Bind(wxEVT_COMBOBOX, [this, is_from_physical_printer](wxCommandEvent& evt) { + Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { // see https://github.com/prusa3d/PrusaSlicer/issues/3889 // Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender") // m_presets_choice->GetSelection() will return first item, because search in PopupListCtrl is case-insensitive. @@ -603,21 +649,17 @@ TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type, if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) { this->SetSelection(this->m_last_selected); if (marker == LABEL_ITEM_WIZARD_PRINTERS) - wxTheApp->CallAfter([this, is_from_physical_printer]() { + wxTheApp->CallAfter([this]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); - if (is_from_physical_printer) + + // update combobox if its parent is a PhysicalPrinterDialog + PhysicalPrinterDialog* parent = dynamic_cast(this->GetParent()); + if (parent != nullptr) update(); }); } - else if ( is_from_physical_printer) { - // do nothing - } - else if (m_last_selected != selected_item || m_collection->current_is_dirty() ) { - std::string selected_string = this->GetString(selected_item).ToUTF8().data(); - Tab* tab = wxGetApp().get_tab(this->m_type); - assert (tab); - tab->select_preset(selected_string); - } + else if (on_selection_changed && (m_last_selected != selected_item || m_collection->current_is_dirty()) ) + on_selection_changed(selected_item); evt.StopPropagation(); }); @@ -760,35 +802,212 @@ void TabPresetComboBox::update_dirty() //------------------------------------------ -PhysicalPrinterDialog::PhysicalPrinterDialog(const wxString& printer_name, int last_selected_preset) +PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) : DPIDialog(NULL, wxID_ANY, _L("PhysicalPrinter"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { SetFont(wxGetApp().normal_font()); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); int border = 10; - int em = em_unit(); - printer_text = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); - printer_presets = new TabPresetComboBox(this, Preset::TYPE_PRINTER, true); - printer_presets->update(); + m_printer_presets = new TabPresetComboBox(this, Preset::TYPE_PRINTER); + m_printer_presets->set_selection_changed_function([this](int selection) { + std::string selected_string = Preset::remove_suffix_modified(m_printer_presets->GetString(selection).ToUTF8().data()); + Preset* preset = wxGetApp().preset_bundle->printers.find_preset(selected_string); + assert(preset); + Preset& edited_preset = wxGetApp().preset_bundle->printers.get_edited_preset(); + if (preset->name == edited_preset.name) + preset = &edited_preset; + m_printer.update_from_preset(*preset); + + // update values + m_optgroup->reload_config(); + update_octoprint_visible(); + }); + m_printer_presets->update(); + + wxString preset_name = m_printer_presets->GetString(m_printer_presets->GetSelection()); + + if (printer_name.IsEmpty()) + printer_name = preset_name + " - "+_L("Physical Printer"); + m_printer_name = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); + + PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; + PhysicalPrinter* printer = printers.find_printer(into_u8(printer_name)); + if (!printer) { + const Preset& preset = wxGetApp().preset_bundle->printers.get_edited_preset(); + printer = new PhysicalPrinter(into_u8(printer_name), preset); + } + assert(printer); + m_printer = *printer; + + m_config = &m_printer.config; + + m_optgroup = new ConfigOptionsGroup(this, _L("Print Host upload"), m_config); + build_printhost_settings(m_optgroup); + m_optgroup->reload_config(); wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); + wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); + btnOK->Bind(wxEVT_BUTTON, &PhysicalPrinterDialog::OnOK, this); wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); - topSizer->Add(printer_text , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(printer_presets , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(btns , 0, wxEXPAND | wxALL, border); + topSizer->Add(m_printer_name , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(m_printer_presets , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(m_optgroup->sizer , 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(btns , 0, wxEXPAND | wxALL, border); SetSizer(topSizer); topSizer->SetSizeHints(this); } +void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup) +{ + m_optgroup->append_single_option_line("host_type"); + + auto create_sizer_with_btn = [this](wxWindow* parent, ScalableButton** btn, const std::string& icon_name, const wxString& label) { + *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); + (*btn)->SetFont(wxGetApp().normal_font()); + + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(*btn); + return sizer; + }; + + auto printhost_browse = [=](wxWindow* parent) + { + auto sizer = create_sizer_with_btn(parent, &m_printhost_browse_btn, "browse", _L("Browse") + " " + dots); + m_printhost_browse_btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent& e) { + BonjourDialog dialog(this, Preset::printer_technology(m_printer.config)); + if (dialog.show_and_lookup()) { + m_optgroup->set_value("print_host", std::move(dialog.get_selected()), true); + m_optgroup->get_field("print_host")->field_changed(); + } + }); + + return sizer; + }; + + auto print_host_test = [=](wxWindow* parent) { + auto sizer = create_sizer_with_btn(parent, &m_printhost_test_btn, "test", _L("Test")); + + m_printhost_test_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { + std::unique_ptr host(PrintHost::get_print_host(m_config)); + if (!host) { + const wxString text = _L("Could not get a valid Printer Host reference"); + show_error(this, text); + return; + } + wxString msg; + if (host->test(msg)) { + show_info(this, host->get_test_ok_msg(), _L("Success!")); + } + else { + show_error(this, host->get_test_failed_msg(msg)); + } + }); + + return sizer; + }; + + // Set a wider width for a better alignment + Option option = m_optgroup->get_option("print_host"); + option.opt.width = Field::def_width_wider(); + Line host_line = m_optgroup->create_single_option_line(option); + host_line.append_widget(printhost_browse); + host_line.append_widget(print_host_test); + m_optgroup->append_line(host_line); + option = m_optgroup->get_option("printhost_apikey"); + option.opt.width = Field::def_width_wider(); + m_optgroup->append_single_option_line(option); + + const auto ca_file_hint = _u8L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate."); + + if (Http::ca_file_supported()) { + option = m_optgroup->get_option("printhost_cafile"); + option.opt.width = Field::def_width_wider(); + Line cafile_line = m_optgroup->create_single_option_line(option); + + auto printhost_cafile_browse = [=](wxWindow* parent) { + auto sizer = create_sizer_with_btn(parent, &m_printhost_cafile_browse_btn, "browse", _L("Browse") + " " + dots); + m_printhost_cafile_browse_btn->Bind(wxEVT_BUTTON, [this, m_optgroup](wxCommandEvent e) { + static const auto filemasks = _L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*"); + wxFileDialog openFileDialog(this, _L("Open CA certificate file"), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (openFileDialog.ShowModal() != wxID_CANCEL) { + m_optgroup->set_value("printhost_cafile", std::move(openFileDialog.GetPath()), true); + m_optgroup->get_field("printhost_cafile")->field_changed(); + } + }); + + return sizer; + }; + + cafile_line.append_widget(printhost_cafile_browse); + m_optgroup->append_line(cafile_line); + + Line cafile_hint{ "", "" }; + cafile_hint.full_width = 1; + cafile_hint.widget = [this, ca_file_hint](wxWindow* parent) { + auto txt = new wxStaticText(parent, wxID_ANY, ca_file_hint); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(txt); + return sizer; + }; + m_optgroup->append_line(cafile_hint); + } + else { + Line line{ "", "" }; + line.full_width = 1; + + line.widget = [ca_file_hint](wxWindow* parent) { + std::string info = _u8L("HTTPS CA File") + ":\n\t" + + (boost::format(_u8L("On this system, %s uses HTTPS certificates from the system Certificate Store or Keychain.")) % SLIC3R_APP_NAME).str() + + "\n\t" + _u8L("To use a custom CA file, please import your CA file into Certificate Store / Keychain."); + + auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\n\t%2%") % info % ca_file_hint).str())); + txt->SetFont(wxGetApp().normal_font()); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(txt, 1, wxEXPAND); + return sizer; + }; + + m_optgroup->append_line(line); + } + + for (const std::string& opt_key : std::vector{ "login", "password" }) { + option = m_optgroup->get_option(opt_key); + option.opt.width = Field::def_width_wider(); + m_optgroup->append_single_option_line(option); + } + + update_octoprint_visible(); +} + +void PhysicalPrinterDialog::update_octoprint_visible() +{ + const PrinterTechnology tech = Preset::printer_technology(m_printer.config); + // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) + Field* host_type = m_optgroup->get_field("host_type"); + if (tech == ptFFF) + host_type->enable(); + else { + host_type->set_value(int(PrintHostType::htOctoPrint), false); + host_type->disable(); + } +} + void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) { const int& em = em_unit(); + m_printhost_browse_btn->msw_rescale(); + m_printhost_test_btn->msw_rescale(); + if (m_printhost_cafile_browse_btn) + m_printhost_cafile_browse_btn->msw_rescale(); + + m_optgroup->msw_rescale(); + msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); const wxSize& size = wxSize(40 * em, 30 * em); @@ -798,5 +1017,46 @@ void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) Refresh(); } +void PhysicalPrinterDialog::OnOK(wxEvent& event) +{ + wxString printer_name = m_printer_name->GetValue(); + if (printer_name.IsEmpty()) { + show_error(this, _L("The supplied name is empty. It can't be saved.")); + return; + } + + PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; + const PhysicalPrinter* existing = printers.find_printer(into_u8(printer_name)); + if (existing && into_u8(printer_name) != printers.get_selected_printer_name()) + { + wxString msg_text = from_u8((boost::format(_u8L("Printer with name \"%1%\" already exists.")) % printer_name).str()); + msg_text += "\n" + _L("Replace?"); + wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); + + if (dialog.ShowModal() == wxID_NO) + return; + + // Remove the printer from the list. + printers.delete_printer(into_u8(printer_name)); + } + + //upadte printer name, if it was changed + m_printer.name = into_u8(printer_name); + + // save new physical printer + printers.save_printer(m_printer); + + // update selection on the tab only when it was changed + if (m_printer.get_preset_name() != wxGetApp().preset_bundle->printers.get_selected_preset_name()) { + Tab* tab = wxGetApp().get_tab(Preset::TYPE_PRINTER); + if (tab) { + wxString preset_name = m_printer_presets->GetString(m_printer_presets->GetSelection()); + tab->select_preset(into_u8(preset_name)); + } + } + + event.Skip(); +} + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 38b98d658..e1597bcfc 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -13,6 +13,7 @@ class wxString; class wxTextCtrl; +class ScalableButton; namespace Slic3r { @@ -33,7 +34,8 @@ public: ~PresetComboBox(); enum LabelItemType { - LABEL_ITEM_MARKER = 0xffffff01, + LABEL_ITEM_PHYSICAL_PRINTER = 0xffffff01, + LABEL_ITEM_MARKER, LABEL_ITEM_PHYSICAL_PRINTERS, LABEL_ITEM_WIZARD_PRINTERS, LABEL_ITEM_WIZARD_FILAMENTS, @@ -121,11 +123,13 @@ public: void set_extruder_idx(const int extr_idx) { m_extruder_idx = extr_idx; } int get_extruder_idx() const { return m_extruder_idx; } + bool is_selected_physical_printer(); + void update() override; void msw_rescale() override; private: - int m_extruder_idx = -1; + int m_extruder_idx = -1; }; @@ -135,8 +139,11 @@ private: class TabPresetComboBox : public PresetComboBox { + bool show_incompatible {false}; + std::function on_selection_changed { nullptr }; + public: - TabPresetComboBox(wxWindow *parent, Preset::Type preset_type, bool is_from_physical_printer = false); + TabPresetComboBox(wxWindow *parent, Preset::Type preset_type); ~TabPresetComboBox() {} void set_show_incompatible_presets(bool show_incompatible_presets) { show_incompatible = show_incompatible_presets; @@ -146,25 +153,33 @@ public: void update_dirty(); void msw_rescale() override; -private: - bool show_incompatible{false}; + void set_selection_changed_function(std::function sel_changed) { on_selection_changed = sel_changed; } }; //------------------------------------------ // PhysicalPrinterDialog //------------------------------------------ - +class ConfigOptionsGroup; class PhysicalPrinterDialog : public DPIDialog { - std::string printer_name; - std::string preset_name; + PhysicalPrinter m_printer; + DynamicPrintConfig* m_config { nullptr }; - wxTextCtrl* printer_text { nullptr }; - PresetComboBox* printer_presets; + wxTextCtrl* m_printer_name { nullptr }; + TabPresetComboBox* m_printer_presets { nullptr }; + ConfigOptionsGroup* m_optgroup { nullptr }; + + ScalableButton* m_printhost_browse_btn; + ScalableButton* m_printhost_test_btn; + ScalableButton* m_printhost_cafile_browse_btn {nullptr}; + + void build_printhost_settings(ConfigOptionsGroup* optgroup); + void update_octoprint_visible(); + void OnOK(wxEvent& event); public: - PhysicalPrinterDialog(const wxString& printer_name, int last_selected_preset); + PhysicalPrinterDialog(wxString printer_name); ~PhysicalPrinterDialog() {} protected: diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index bb19e139d..da5d51dc5 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -161,6 +161,13 @@ void Tab::create_preset_tab() // preset chooser m_presets_choice = new TabPresetComboBox(panel, m_type); + m_presets_choice->set_selection_changed_function([this](int selection) { + // unselect pthysical printer, if it was selected + m_preset_bundle->physical_printers.unselect_printer(); + // select preset + std::string selected_string = m_presets_choice->GetString(selection).ToUTF8().data(); + select_preset(selected_string); + }); auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); @@ -3022,6 +3029,9 @@ void Tab::select_preset(std::string preset_name, bool delete_current) if (canceled) { update_tab_ui(); + // unselect physical printer selection to the correct synchronization of the printer presets between Tab and Plater + if (m_type == Preset::TYPE_PRINTER) + m_preset_bundle->physical_printers.unselect_printer(); // Trigger the on_presets_changed event so that we also restore the previous value in the plater selector, // if this action was initiated from the plater. on_presets_changed(); From 89035febfaf926394eaef82db007694dc87c9713 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 24 Jun 2020 09:20:04 +0200 Subject: [PATCH 05/70] Fixed includes --- src/slic3r/Utils/FixModelByWin10.cpp | 2 +- xs/xsp/Model.xsp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 0de526432..a3683a84d 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -30,10 +30,10 @@ #include "libslic3r/Model.hpp" #include "libslic3r/Print.hpp" +#include "libslic3r/PresetBundle.hpp" #include "libslic3r/Format/3mf.hpp" #include "../GUI/GUI.hpp" #include "../GUI/I18N.hpp" -#include "../GUI/PresetBundle.hpp" #include #include diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 4fb35578d..844b7c95e 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -12,7 +12,7 @@ #include "libslic3r/Format/OBJ.hpp" #include "libslic3r/Format/PRUS.hpp" #include "libslic3r/Format/STL.hpp" -#include "slic3r/GUI/PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" %} %name{Slic3r::Model} class Model { From 8ac839f427ef2c0d1470de51e8d9515689296e83 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 24 Jun 2020 12:28:00 +0200 Subject: [PATCH 06/70] Physical printers: Delete selected printer + Added context menu for the cog-button near the printer presets --- src/libslic3r/Preset.cpp | 21 +++++++-- src/libslic3r/Preset.hpp | 3 ++ src/slic3r/GUI/PresetComboBoxes.cpp | 72 +++++++++++++++++++++-------- src/slic3r/GUI/PresetComboBoxes.hpp | 2 + 4 files changed, 76 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 94c9577df..e17130f69 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1489,17 +1489,32 @@ void PhysicalPrinterCollection::save_printer(const PhysicalPrinter& edited_print bool PhysicalPrinterCollection::delete_printer(const std::string& name) { auto it = this->find_printer_internal(name); + if (it == m_printers.end()) + return false; const PhysicalPrinter& printer = *it; - if (it == m_printers.end()) - return false; - // Erase the preset file. boost::nowide::remove(printer.file.c_str()); m_printers.erase(it); return true; } +bool PhysicalPrinterCollection::delete_selected_printer() +{ + if (!has_selection()) + return false; + const PhysicalPrinter& printer = this->get_selected_printer(); + + // Erase the preset file. + boost::nowide::remove(printer.file.c_str()); + // Remove the preset from the list. + m_printers.erase(m_printers.begin() + m_idx_selected); + // unselect all printers + unselect_printer(); + + return true; +} + PhysicalPrinter& PhysicalPrinterCollection::select_printer_by_name(const std::string& name) { auto it = this->find_printer_internal(name); diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index c08a1a0fb..a5837a9fe 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -612,6 +612,9 @@ public: // Delete the current preset, activate the first visible preset. // returns true if the preset was deleted successfully. bool delete_printer(const std::string& name); + // Delete the selected preset + // returns true if the preset was deleted successfully. + bool delete_selected_printer(); // Return the selected preset, without the user modifications applied. PhysicalPrinter& get_selected_printer() { return m_printers[m_idx_selected]; } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index ce25d5690..e9da7dc9d 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "libslic3r/libslic3r.h" #include "libslic3r/PrintConfig.hpp" @@ -322,28 +323,14 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset edit_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent) { // In a case of a physical printer, for its editing open PhysicalPrinterDialog - if (m_type == Preset::TYPE_PRINTER && this->is_selected_physical_printer()) - { - PhysicalPrinterDialog dlg(this->GetString(this->GetSelection())); - if (dlg.ShowModal() == wxID_OK) { - update(); - return; - } + if (m_type == Preset::TYPE_PRINTER && this->is_selected_physical_printer()) { + this->show_edit_menu(); + return; } - Tab* tab = wxGetApp().get_tab(m_type); - if (!tab) + if (!switch_to_tab()) return; - int page_id = wxGetApp().tab_panel()->FindPage(tab); - if (page_id == wxNOT_FOUND) - return; - - wxGetApp().tab_panel()->SetSelection(page_id); - - // Switch to Settings NotePad - wxGetApp().mainframe->select_tab(); - /* In a case of a multi-material printing, for editing another Filament Preset * it's needed to select this preset for the "Filament settings" Tab */ @@ -355,7 +342,7 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset if ( !boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()) ) { const std::string& preset_name = wxGetApp().preset_bundle->filaments.get_preset_name_by_alias(selected_preset); - tab->select_preset(preset_name); + wxGetApp().get_tab(m_type)->select_preset(preset_name); } } }); @@ -374,6 +361,53 @@ bool PlaterPresetComboBox::is_selected_physical_printer() return marker == LABEL_ITEM_PHYSICAL_PRINTER; } +bool PlaterPresetComboBox::switch_to_tab() +{ + Tab* tab = wxGetApp().get_tab(m_type); + if (!tab) + return false; + + int page_id = wxGetApp().tab_panel()->FindPage(tab); + if (page_id == wxNOT_FOUND) + return false; + + wxGetApp().tab_panel()->SetSelection(page_id); + // Switch to Settings NotePad + wxGetApp().mainframe->select_tab(); + return true; +} + +void PlaterPresetComboBox::show_edit_menu() +{ + wxMenu* menu = new wxMenu(); + + append_menu_item(menu, wxID_ANY, _L("Edit related printer profile"), "", + [this](wxCommandEvent&) { this->switch_to_tab(); }, "cog", menu, []() { return true; }, wxGetApp().plater()); + + append_menu_item(menu, wxID_ANY, _L("Edit physical printer"), "", + [this](wxCommandEvent&) { + PhysicalPrinterDialog dlg(this->GetString(this->GetSelection())); + if (dlg.ShowModal() == wxID_OK) + update(); + }, "cog", menu, []() { return true; }, wxGetApp().plater()); + + append_menu_item(menu, wxID_ANY, _L("Delete physical printer"), "", + [this](wxCommandEvent&) { + const std::string& printer_name = m_preset_bundle->physical_printers.get_selected_printer_name(); + if (printer_name.empty()) + return; + + const wxString msg = from_u8((boost::format(_u8L("Are you sure you want to delete \"%1%\" printer?")) % printer_name).str()); + if (wxMessageDialog(this, msg, _L("Delete Physical Printer"), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal() != wxID_YES) + return; + + m_preset_bundle->physical_printers.delete_selected_printer(); + update(); + }, "cross", menu, []() { return true; }, wxGetApp().plater()); + + wxGetApp().plater()->PopupMenu(menu); +} + // Only the compatible presets are shown. // If an incompatible preset is selected, it is shown as well. void PlaterPresetComboBox::update() diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index e1597bcfc..9261e92ef 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -124,6 +124,8 @@ public: int get_extruder_idx() const { return m_extruder_idx; } bool is_selected_physical_printer(); + bool switch_to_tab(); + void show_edit_menu(); void update() override; void msw_rescale() override; From 1a2926050fee0b5ab118054aaa4ccb65614d2063 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 25 Jun 2020 12:58:59 +0200 Subject: [PATCH 07/70] PhysicalPrinter. PhysicalPrinterDialog improvements --- src/libslic3r/Preset.cpp | 3 +- src/libslic3r/PrintConfig.cpp | 11 +++++++ src/libslic3r/PrintConfig.hpp | 13 ++++++++ src/slic3r/GUI/Field.cpp | 2 ++ src/slic3r/GUI/Field.hpp | 2 ++ src/slic3r/GUI/GUI.cpp | 2 ++ src/slic3r/GUI/OptionsGroup.cpp | 19 +++++++----- src/slic3r/GUI/OptionsGroup.hpp | 7 +++++ src/slic3r/GUI/PresetComboBoxes.cpp | 46 +++++++++++++++++++++-------- src/slic3r/GUI/PresetComboBoxes.hpp | 2 +- 10 files changed, 85 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index e17130f69..abc508b48 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1354,7 +1354,8 @@ const std::vector& PhysicalPrinter::printer_options() "host_type", "print_host", "printhost_apikey", - "printhost_cafile", + "printhost_cafile", + "authorization_type", "login", "password" }; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 93ef170fb..01b17ec0d 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -150,6 +150,17 @@ void PrintConfigDef::init_common_params() def->tooltip = L("Related printer preset name"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("")); + + def = this->add("authorization_type", coEnum); + def->label = L("Authorization Type"); +// def->tooltip = L(""); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("key"); + def->enum_values.push_back("user"); + def->enum_labels.push_back("KeyPassword"); + def->enum_labels.push_back("UserPassword"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(atKeyPassword)); } void PrintConfigDef::init_fff_params() diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index f28ef2a22..9b5c47512 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -33,6 +33,10 @@ enum PrintHostType { htOctoPrint, htDuet, htFlashAir, htAstroBox }; +enum AuthorizationType { + atKeyPassword, atUserPassword +}; + enum InfillPattern : int { ipRectilinear, ipMonotonous, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipCount, @@ -109,6 +113,15 @@ template<> inline const t_config_enum_values& ConfigOptionEnum::g return keys_map; } +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { + static t_config_enum_values keys_map; + if (keys_map.empty()) { + keys_map["key"] = atKeyPassword; + keys_map["user"] = atUserPassword; + } + return keys_map; +} + template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 8ab82e20d..9cb3d726d 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -1080,6 +1080,8 @@ boost::any& Choice::get_value() m_value = static_cast(ret_enum); else if (m_opt_id.compare("support_pillar_connection_mode") == 0) m_value = static_cast(ret_enum); + else if (m_opt_id == "authorization_type") + m_value = static_cast(ret_enum); } else if (m_opt.gui_type == "f_enum_open") { const int ret_enum = field->GetSelection(); diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index 484b2059f..1a4997756 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -151,6 +151,8 @@ public: virtual wxSizer* getSizer() { return nullptr; } virtual wxWindow* getWindow() { return nullptr; } + wxStaticText* getLabel() { return m_Label; } + bool is_matched(const std::string& string, const std::string& pattern); void get_value_by_opt_type(wxString& str, const bool check_value = true); diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index b9516b12f..88c457668 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -194,6 +194,8 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); else if(opt_key.compare("support_pillar_connection_mode") == 0) config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); + else if(opt_key == "authorization_type") + config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); } break; case coPoints:{ diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 819c214a8..1bebb8827 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -729,31 +729,34 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config opt_key == "fill_pattern" ) { ret = static_cast(config.option>(opt_key)->value); } - else if (opt_key.compare("ironing_type") == 0 ) { + else if (opt_key == "ironing_type") { ret = static_cast(config.option>(opt_key)->value); } - else if (opt_key.compare("gcode_flavor") == 0 ) { + else if (opt_key == "gcode_flavor") { ret = static_cast(config.option>(opt_key)->value); } - else if (opt_key.compare("support_material_pattern") == 0) { + else if (opt_key == "support_material_pattern") { ret = static_cast(config.option>(opt_key)->value); } - else if (opt_key.compare("seam_position") == 0) { + else if (opt_key == "seam_position") { ret = static_cast(config.option>(opt_key)->value); } - else if (opt_key.compare("host_type") == 0) { + else if (opt_key == "host_type") { ret = static_cast(config.option>(opt_key)->value); } - else if (opt_key.compare("display_orientation") == 0) { + else if (opt_key == "display_orientation") { ret = static_cast(config.option>(opt_key)->value); } - else if (opt_key.compare("support_pillar_connection_mode") == 0) { + else if (opt_key == "support_pillar_connection_mode") { ret = static_cast(config.option>(opt_key)->value); } + else if (opt_key == "authorization_type") { + ret = static_cast(config.option>(opt_key)->value); + } } break; case coPoints: - if (opt_key.compare("bed_shape") == 0) + if (opt_key == "bed_shape") ret = config.option(opt_key)->values; else ret = config.option(opt_key)->get_at(idx); diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 2e6f9aa0f..edd4a15bc 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -149,6 +149,13 @@ public: return true; } + void show_field(const t_config_option_key& opt_key, bool show = true) { + Field* field = get_field(opt_key); + field->getWindow()->Show(show); + field->getLabel()->Show(show); + } + void hide_field(const t_config_option_key& opt_key) { show_field(opt_key, false); } + void set_name(const wxString& new_name) { stb->SetLabel(new_name); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index e9da7dc9d..b39582ee0 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -837,7 +837,7 @@ void TabPresetComboBox::update_dirty() PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) - : DPIDialog(NULL, wxID_ANY, _L("PhysicalPrinter"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) + : DPIDialog(NULL, wxID_ANY, _L("Physical Printer"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { SetFont(wxGetApp().normal_font()); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -856,7 +856,7 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) // update values m_optgroup->reload_config(); - update_octoprint_visible(); + update(); }); m_printer_presets->update(); @@ -898,6 +898,11 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup) { + m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (opt_key == "authorization_type") + this->update(); + }; + m_optgroup->append_single_option_line("host_type"); auto create_sizer_with_btn = [this](wxWindow* parent, ScalableButton** btn, const std::string& icon_name, const wxString& label) { @@ -952,6 +957,9 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr host_line.append_widget(printhost_browse); host_line.append_widget(print_host_test); m_optgroup->append_line(host_line); + + m_optgroup->append_single_option_line("authorization_type"); + option = m_optgroup->get_option("printhost_apikey"); option.opt.width = Field::def_width_wider(); m_optgroup->append_single_option_line(option); @@ -999,7 +1007,8 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr (boost::format(_u8L("On this system, %s uses HTTPS certificates from the system Certificate Store or Keychain.")) % SLIC3R_APP_NAME).str() + "\n\t" + _u8L("To use a custom CA file, please import your CA file into Certificate Store / Keychain."); - auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\n\t%2%") % info % ca_file_hint).str())); + //auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\n\t%2%") % info % ca_file_hint).str())); + auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\t%2%") % info % ca_file_hint).str())); txt->SetFont(wxGetApp().normal_font()); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(txt, 1, wxEXPAND); @@ -1015,20 +1024,33 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr m_optgroup->append_single_option_line(option); } - update_octoprint_visible(); + update(); } -void PhysicalPrinterDialog::update_octoprint_visible() +void PhysicalPrinterDialog::update() { const PrinterTechnology tech = Preset::printer_technology(m_printer.config); // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) - Field* host_type = m_optgroup->get_field("host_type"); - if (tech == ptFFF) - host_type->enable(); - else { - host_type->set_value(int(PrintHostType::htOctoPrint), false); - host_type->disable(); + if (tech == ptFFF) { + m_optgroup->show_field("host_type"); + m_optgroup->hide_field("authorization_type"); + for (const std::string& opt_key : std::vector{ "login", "password" }) + m_optgroup->hide_field(opt_key); } + else { + m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false); + m_optgroup->hide_field("host_type"); + + m_optgroup->show_field("authorization_type"); + + AuthorizationType auth_type = m_config->option>("authorization_type")->value; + m_optgroup->show_field("printhost_apikey", auth_type == AuthorizationType::atKeyPassword); + + for (const std::string& opt_key : std::vector{ "login", "password" }) + m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword); + } + + this->Layout(); } void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) @@ -1044,7 +1066,7 @@ void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); - const wxSize& size = wxSize(40 * em, 30 * em); + const wxSize& size = wxSize(45 * em, 35 * em); SetMinSize(size); Fit(); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 9261e92ef..196c4368e 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -177,7 +177,7 @@ class PhysicalPrinterDialog : public DPIDialog ScalableButton* m_printhost_cafile_browse_btn {nullptr}; void build_printhost_settings(ConfigOptionsGroup* optgroup); - void update_octoprint_visible(); + void update(); void OnOK(wxEvent& event); public: From d96b5f360661feaa2f964d9f4b18d1e586d2607b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 26 Jun 2020 09:58:39 +0200 Subject: [PATCH 08/70] PhysicalPrinter : Next improvements: * Create full printer name as a PrinterName + RelatedPresetName * Added printer model to the PhysicalPrinter.config => Enable to select just between presets with same printer model * When physical printer is selected and create new preset ask if should we use this preset for selected ph_printer or just to switch for it --- src/libslic3r/Preset.cpp | 33 +++++- src/libslic3r/Preset.hpp | 24 +++- src/slic3r/GUI/PresetComboBoxes.cpp | 175 ++++++++++++++++++++-------- src/slic3r/GUI/PresetComboBoxes.hpp | 9 ++ src/slic3r/GUI/Tab.cpp | 33 +++++- src/slic3r/GUI/Tab.hpp | 1 + src/slic3r/GUI/wxExtensions.cpp | 7 +- src/slic3r/GUI/wxExtensions.hpp | 4 +- 8 files changed, 225 insertions(+), 61 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index abc508b48..9af3dacf0 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -1351,6 +1352,7 @@ const std::vector& PhysicalPrinter::printer_options() s_opts = { "preset_name", "printer_technology", + "printer_model", "host_type", "print_host", "printhost_apikey", @@ -1363,21 +1365,28 @@ const std::vector& PhysicalPrinter::printer_options() return s_opts; } -const std::string& PhysicalPrinter::get_preset_name() +const std::string& PhysicalPrinter::get_preset_name() const { return config.opt_string("preset_name"); } +const std::string& PhysicalPrinter::get_printer_model() const +{ + return config.opt_string("printer_model"); +} + void PhysicalPrinter::update_from_preset(const Preset& preset) { config.apply_only(preset.config, printer_options(), false); // add preset name to the options list config.set_key_value("preset_name", new ConfigOptionString(preset.name)); + update_full_name(); } void PhysicalPrinter::update_from_config(const DynamicPrintConfig& new_config) { config.apply_only(new_config, printer_options(), false); + update_full_name(); } PhysicalPrinter::PhysicalPrinter(const std::string& name, const Preset& preset) : @@ -1386,6 +1395,24 @@ PhysicalPrinter::PhysicalPrinter(const std::string& name, const Preset& preset) update_from_preset(preset); } +void PhysicalPrinter::set_name(const std::string& name) +{ + this->name = name; + update_full_name(); +} + +void PhysicalPrinter::update_full_name() +{ + full_name = name + " * " + get_preset_name(); +} + +std::string PhysicalPrinter::get_short_name(std::string full_name) +{ + int pos = full_name.find_first_of(" * "); + boost::erase_tail(full_name, full_name.length() - pos); + return full_name; +} + // ----------------------------------- // *** PhysicalPrinterCollection *** @@ -1470,6 +1497,7 @@ void PhysicalPrinterCollection::save_printer(const PhysicalPrinter& edited_print // Printer with the same name found. // Overwriting an existing preset. it->config = std::move(edited_printer.config); + it->full_name = edited_printer.full_name; } else { // Creating a new printer. @@ -1516,8 +1544,9 @@ bool PhysicalPrinterCollection::delete_selected_printer() return true; } -PhysicalPrinter& PhysicalPrinterCollection::select_printer_by_name(const std::string& name) +PhysicalPrinter& PhysicalPrinterCollection::select_printer_by_name(std::string name) { + name = PhysicalPrinter::get_short_name(name); auto it = this->find_printer_internal(name); assert(it != m_printers.end()); diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index a5837a9fe..6eb1fd2db 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -538,20 +538,24 @@ public: PhysicalPrinter() {} PhysicalPrinter(const std::string& name) : name(name){} PhysicalPrinter(const std::string& name, const Preset& preset); + void set_name(const std::string &name); + void update_full_name(); // Name of the Physical Printer, usually derived form the file name. std::string name; + // Full name of the Physical Printer, included related preset name + std::string full_name; // File name of the Physical Printer. std::string file; + // Configuration data, loaded from a file, or set from the defaults. + DynamicPrintConfig config; // Has this profile been loaded? bool loaded = false; - // Configuration data, loaded from a file, or set from the defaults. - DynamicPrintConfig config; - static const std::vector& printer_options(); - const std::string& get_preset_name(); + const std::string& get_preset_name() const; + const std::string& get_printer_model() const; void save() { this->config.save(this->file); } void save_to(const std::string& file_name) const { this->config.save(file_name); } @@ -570,6 +574,9 @@ public: // Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection. bool operator<(const PhysicalPrinter& other) const { return this->name < other.name; } + // get printer name from the full name uncluded preset name + static std::string get_short_name(std::string full_name); + protected: friend class PhysicalPrinterCollection; }; @@ -622,10 +629,17 @@ public: size_t get_selected_idx() const { return m_idx_selected; } // Returns the name of the selected preset, or an empty string if no preset is selected. std::string get_selected_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().name; } + // Returns the full name of the selected preset, or an empty string if no preset is selected. + std::string get_selected_full_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().full_name; } + // Returns the printer model of the selected preset, or an empty string if no preset is selected. + std::string get_selected_printer_model() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_printer_model(); } + // Returns the printer model of the selected preset, or an empty string if no preset is selected. + std::string get_selected_printer_preset_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_preset_name(); } + // Returns the config of the selected preset, or nullptr if no preset is selected. DynamicPrintConfig* get_selected_printer_config() { return (m_idx_selected == size_t(-1)) ? nullptr : &(this->get_selected_printer().config); } // select printer with name and return reference on it - PhysicalPrinter& select_printer_by_name(const std::string& name); + PhysicalPrinter& select_printer_by_name(std::string name); bool has_selection() const { return m_idx_selected != size_t(-1); } void unselect_printer() { m_idx_selected = size_t(-1); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index b39582ee0..88dd4b739 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -104,6 +104,7 @@ PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const m_bitmapCompatible = ScalableBitmap(this, "flag_green"); m_bitmapIncompatible = ScalableBitmap(this, "flag_red"); m_bitmapLock = ScalableBitmap(this, "lock_closed"); + m_bitmapLockDisabled = ScalableBitmap(this, "lock_closed", 16, true); // parameters for an icon's drawing fill_width_height(); @@ -125,6 +126,7 @@ void PresetComboBox::msw_rescale() m_em_unit = em_unit(this); m_bitmapLock.msw_rescale(); + m_bitmapLockDisabled.msw_rescale(); m_bitmapIncompatible.msw_rescale(); m_bitmapCompatible.msw_rescale(); @@ -241,6 +243,7 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) { this->SetSelection(this->m_last_selected); evt.StopPropagation(); + /* if (marker == LABEL_ITEM_PHYSICAL_PRINTERS) { PhysicalPrinterDialog dlg(wxEmptyString); @@ -258,6 +261,19 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset } wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); }); } + */ + if (marker == LABEL_ITEM_WIZARD_PRINTERS) + show_add_menu(); + else + { + ConfigWizard::StartPage sp = ConfigWizard::SP_WELCOME; + switch (marker) { + case LABEL_ITEM_WIZARD_FILAMENTS: sp = ConfigWizard::SP_FILAMENTS; break; + case LABEL_ITEM_WIZARD_MATERIALS: sp = ConfigWizard::SP_MATERIALS; break; + default: break; + } + wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); }); + } } else if (marker == LABEL_ITEM_PHYSICAL_PRINTER || this->m_last_selected != selected_item || m_collection->current_is_dirty() ) { this->m_last_selected = selected_item; evt.SetInt(this->m_type); @@ -377,6 +393,25 @@ bool PlaterPresetComboBox::switch_to_tab() return true; } +void PlaterPresetComboBox::show_add_menu() +{ + wxMenu* menu = new wxMenu(); + + append_menu_item(menu, wxID_ANY, _L("Add/Remove logical printers"), "", + [this](wxCommandEvent&) { + wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); }); + }, "edit_uni", menu, []() { return true; }, wxGetApp().plater()); + + append_menu_item(menu, wxID_ANY, _L("Add physical printer"), "", + [this](wxCommandEvent&) { + PhysicalPrinterDialog dlg(wxEmptyString); + if (dlg.ShowModal() == wxID_OK) + update(); + }, "edit_uni", menu, []() { return true; }, wxGetApp().plater()); + + wxGetApp().plater()->PopupMenu(menu); +} + void PlaterPresetComboBox::show_edit_menu() { wxMenu* menu = new wxMenu(); @@ -393,7 +428,7 @@ void PlaterPresetComboBox::show_edit_menu() append_menu_item(menu, wxID_ANY, _L("Delete physical printer"), "", [this](wxCommandEvent&) { - const std::string& printer_name = m_preset_bundle->physical_printers.get_selected_printer_name(); + const std::string& printer_name = m_preset_bundle->physical_printers.get_selected_full_printer_name(); if (printer_name.empty()) return; @@ -550,36 +585,6 @@ void PlaterPresetComboBox::update() } } - if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) { - std::string bitmap_key = ""; - // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left - // to the filament color image. - if (wide_icons) - bitmap_key += "wide,"; - bitmap_key += "edit_preset_list"; - bitmap_key += "-h" + std::to_string(icon_height); - - wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars.update_plater_ui - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(m_bitmap_cache->mkclear(norm_icon_width, icon_height)); - // Paint the color bars. - bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); - bmps.emplace_back(create_scaled_bitmap(m_main_bitmap_name)); - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); - bmps.emplace_back(create_scaled_bitmap("edit_uni")); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } - if (m_type == Preset::TYPE_SLA_MATERIAL) - set_label_marker(Append(separator(L("Add/Remove materials")), *bmp), LABEL_ITEM_WIZARD_MATERIALS); - else - set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); - } - if (m_type == Preset::TYPE_PRINTER) { // add Physical printers, if any exists @@ -608,7 +613,7 @@ void PlaterPresetComboBox::update() bmp = m_bitmap_cache->insert(bitmap_key, bmps); } - set_label_marker(Append(wxString::FromUTF8((it->name).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); + set_label_marker(Append(wxString::FromUTF8((it->full_name).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); if (ph_printers.has_selection() && it->name == ph_printers.get_selected_printer_name() || // just in case: mark selected_preset_item as a first added element selected_preset_item == INT_MAX) @@ -616,6 +621,7 @@ void PlaterPresetComboBox::update() } } +/* // add LABEL_ITEM_PHYSICAL_PRINTERS std::string bitmap_key; if (wide_icons) @@ -639,6 +645,37 @@ void PlaterPresetComboBox::update() bmp = m_bitmap_cache->insert(bitmap_key, bmps); } set_label_marker(Append(separator(L("Add physical printer")), *bmp), LABEL_ITEM_PHYSICAL_PRINTERS); +*/ + } + + if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) { + std::string bitmap_key = ""; + // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left + // to the filament color image. + if (wide_icons) + bitmap_key += "wide,"; + bitmap_key += "edit_preset_list"; + bitmap_key += "-h" + std::to_string(icon_height); + + wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars.update_plater_ui + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(m_bitmap_cache->mkclear(norm_icon_width, icon_height)); + // Paint the color bars. + bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); + bmps.emplace_back(create_scaled_bitmap(m_main_bitmap_name)); + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); + bmps.emplace_back(create_scaled_bitmap("edit_uni")); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } + if (m_type == Preset::TYPE_SLA_MATERIAL) + set_label_marker(Append(separator(L("Add/Remove materials")), *bmp), LABEL_ITEM_WIZARD_MATERIALS); + else + set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); } /* But, if selected_preset_item is still equal to INT_MAX, it means that @@ -680,7 +717,7 @@ TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type) auto selected_item = evt.GetSelection(); auto marker = reinterpret_cast(this->GetClientData(selected_item)); - if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) { + if (marker >= LABEL_ITEM_DISABLED && marker < LABEL_ITEM_MAX) { this->SetSelection(this->m_last_selected); if (marker == LABEL_ITEM_WIZARD_PRINTERS) wxTheApp->CallAfter([this]() { @@ -710,7 +747,7 @@ void TabPresetComboBox::update() const std::deque& presets = m_collection->get_presets(); - std::map nonsys_presets; + std::map> nonsys_presets; wxString selected = ""; if (!presets.front().is_visible) set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); @@ -719,13 +756,24 @@ void TabPresetComboBox::update() const Preset& preset = presets[i]; if (!preset.is_visible || (!show_incompatible && !preset.is_compatible && i != idx_selected)) continue; + + // marker used for disable incompatible printer models for the selected physical printer + bool is_enabled = true; + // check this value just for printer presets, when physical printer is selected + if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) { + is_enabled = m_enable_all ? true : + preset.name == m_preset_bundle->physical_printers.get_selected_printer_preset_name() || + preset.config.opt_string("printer_model") == m_preset_bundle->physical_printers.get_selected_printer_model(); + } std::string bitmap_key = "tab"; - wxBitmap main_bmp = create_scaled_bitmap(m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name, this); + wxBitmap main_bmp = create_scaled_bitmap(m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name, this, 16, !is_enabled); if (m_type == Preset::TYPE_PRINTER) { bitmap_key += "_printer"; if (preset.printer_technology() == ptSLA) bitmap_key += "_sla"; + if (!is_enabled) + bitmap_key += "_disabled"; } bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; @@ -737,13 +785,14 @@ void TabPresetComboBox::update() std::vector bmps; bmps.emplace_back(m_type == Preset::TYPE_PRINTER ? main_bmp : preset.is_compatible ? m_bitmapCompatible.bmp() : m_bitmapIncompatible.bmp()); // Paint a lock at the system presets. - bmps.emplace_back((preset.is_system || preset.is_default) ? m_bitmapLock.bmp() : m_bitmap_cache->mkclear(norm_icon_width, icon_height)); + bmps.emplace_back((preset.is_system || preset.is_default) ? (is_enabled ? m_bitmapLock.bmp() : m_bitmapLockDisabled.bmp()) : m_bitmap_cache->mkclear(norm_icon_width, icon_height)); bmp = m_bitmap_cache->insert(bitmap_key, bmps); } if (preset.is_default || preset.is_system) { - Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), - (bmp == 0) ? main_bmp : *bmp); + int item_id = Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), !bmp ? main_bmp : *bmp); + if (!is_enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); if (i == idx_selected || // just in case: mark selected_preset_item as a first added element selected_preset_item == INT_MAX) @@ -751,7 +800,8 @@ void TabPresetComboBox::update() } else { - nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), bmp); + std::pair pair(bmp, is_enabled); + nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), std::pair(bmp, is_enabled)); if (i == idx_selected) selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); } @@ -761,8 +811,11 @@ void TabPresetComboBox::update() if (!nonsys_presets.empty()) { set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - Append(it->first, *it->second); + for (std::map>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + int item_id = Append(it->first, *it->second.first); + bool is_enabled = it->second.second; + if (!is_enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); if (it->first == selected || // just in case: mark selected_preset_item as a first added element selected_preset_item == INT_MAX) @@ -845,6 +898,17 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) int border = 10; m_printer_presets = new TabPresetComboBox(this, Preset::TYPE_PRINTER); + + if (printer_name.IsEmpty()) { + // if printer_name is empty it means that new printer is created, so enable all items in the preset list + m_printer_presets->set_enable_all(); + printer_name = _L("My Printer Device"); + } + else { + std::string full_name = into_u8(printer_name); + printer_name = from_u8(PhysicalPrinter::get_short_name(full_name)); + } + m_printer_presets->set_selection_changed_function([this](int selection) { std::string selected_string = Preset::remove_suffix_modified(m_printer_presets->GetString(selection).ToUTF8().data()); Preset* preset = wxGetApp().preset_bundle->printers.find_preset(selected_string); @@ -854,17 +918,22 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) preset = &edited_preset; m_printer.update_from_preset(*preset); + update_printer_name(); + // update values m_optgroup->reload_config(); update(); }); m_printer_presets->update(); - wxString preset_name = m_printer_presets->GetString(m_printer_presets->GetSelection()); + wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _("Descriptive name for the printer device") + ":"); + m_printer_name = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize); + m_printer_name->Bind(wxEVT_TEXT, [this](wxEvent&) { this->update_printer_name(); }); - if (printer_name.IsEmpty()) - printer_name = preset_name + " - "+_L("Physical Printer"); - m_printer_name = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); + wxStaticText* label_bottom = new wxStaticText(this, wxID_ANY, _("This printer name will be shown in the presets list") + ":"); + m_full_printer_name = new wxStaticText(this, wxID_ANY, ""); + + update_printer_name(); PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; PhysicalPrinter* printer = printers.find_printer(into_u8(printer_name)); @@ -887,8 +956,11 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(m_printer_name , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(m_printer_presets , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(label_bottom , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(m_full_printer_name , 0, wxEXPAND | wxLEFT | wxRIGHT, border); topSizer->Add(m_optgroup->sizer , 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(btns , 0, wxEXPAND | wxALL, border); @@ -1053,6 +1125,15 @@ void PhysicalPrinterDialog::update() this->Layout(); } +void PhysicalPrinterDialog::update_printer_name() +{ + wxString printer_name = m_printer_name->GetValue(); + wxString preset_name = m_printer_presets->GetString(m_printer_presets->GetSelection()); + + m_full_printer_name->SetLabelText("\t" + printer_name + " * " + preset_name); + this->Layout(); +} + void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) { const int& em = em_unit(); @@ -1096,8 +1177,8 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) printers.delete_printer(into_u8(printer_name)); } - //upadte printer name, if it was changed - m_printer.name = into_u8(printer_name); + //update printer name, if it was changed + m_printer.set_name(into_u8(printer_name)); // save new physical printer printers.save_printer(m_printer); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 196c4368e..653c0e540 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -13,6 +13,7 @@ class wxString; class wxTextCtrl; +class wxStatictext; class ScalableButton; namespace Slic3r { @@ -35,6 +36,7 @@ public: enum LabelItemType { LABEL_ITEM_PHYSICAL_PRINTER = 0xffffff01, + LABEL_ITEM_DISABLED, LABEL_ITEM_MARKER, LABEL_ITEM_PHYSICAL_PRINTERS, LABEL_ITEM_WIZARD_PRINTERS, @@ -66,6 +68,8 @@ protected: ScalableBitmap m_bitmapIncompatible; // Indicator, that the preset is system and not modified. ScalableBitmap m_bitmapLock; + // Disabled analogue of the m_bitmapLock . + ScalableBitmap m_bitmapLockDisabled; int m_last_selected; int m_em_unit; @@ -125,6 +129,7 @@ public: bool is_selected_physical_printer(); bool switch_to_tab(); + void show_add_menu(); void show_edit_menu(); void update() override; @@ -142,6 +147,7 @@ private: class TabPresetComboBox : public PresetComboBox { bool show_incompatible {false}; + bool m_enable_all {false}; std::function on_selection_changed { nullptr }; public: @@ -156,6 +162,7 @@ public: void msw_rescale() override; void set_selection_changed_function(std::function sel_changed) { on_selection_changed = sel_changed; } + void set_enable_all(bool enable=true) { m_enable_all = enable; } }; @@ -169,6 +176,7 @@ class PhysicalPrinterDialog : public DPIDialog DynamicPrintConfig* m_config { nullptr }; wxTextCtrl* m_printer_name { nullptr }; + wxStaticText* m_full_printer_name { nullptr }; TabPresetComboBox* m_printer_presets { nullptr }; ConfigOptionsGroup* m_optgroup { nullptr }; @@ -178,6 +186,7 @@ class PhysicalPrinterDialog : public DPIDialog void build_printhost_settings(ConfigOptionsGroup* optgroup); void update(); + void update_printer_name(); void OnOK(wxEvent& event); public: diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index da5d51dc5..595283e98 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -162,10 +162,9 @@ void Tab::create_preset_tab() // preset chooser m_presets_choice = new TabPresetComboBox(panel, m_type); m_presets_choice->set_selection_changed_function([this](int selection) { - // unselect pthysical printer, if it was selected - m_preset_bundle->physical_printers.unselect_printer(); - // select preset std::string selected_string = m_presets_choice->GetString(selection).ToUTF8().data(); + update_physical_printers(selected_string); + // select preset select_preset(selected_string); }); @@ -763,6 +762,32 @@ void Tab::update_tab_ui() m_presets_choice->update(); } +void Tab::update_physical_printers(std::string preset_name) +{ + if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) + { + std::string printer_name = m_preset_bundle->physical_printers.get_selected_full_printer_name(); + wxString msg_text = from_u8((boost::format(_u8L("You have selected physical printer \"%1%\".")) % printer_name).str()); + msg_text += "\n\n" + _L("Would you like to change related preset for this printer?") + "\n\n" + + _L("Select YES if you want to change related preset for this printer \n" + "or NO to switch to the another preset (logical printer)."); + wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); + + if (dialog.ShowModal() == wxID_YES) { + preset_name = Preset::remove_suffix_modified(preset_name); + Preset* preset = m_presets->find_preset(preset_name); + assert(preset); + Preset& edited_preset = m_presets->get_edited_preset(); + if (preset->name == edited_preset.name) + preset = &edited_preset; + m_preset_bundle->physical_printers.get_selected_printer().update_from_preset(*preset); + } + else + // unselect physical printer, if it was selected + m_preset_bundle->physical_printers.unselect_printer(); + } +} + // Load a provied DynamicConfig into the tab, modifying the active preset. // This could be used for example by setting a Wipe Tower position by interactive manipulation in the 3D view. void Tab::load_config(const DynamicPrintConfig& config) @@ -3269,6 +3294,8 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) // Mark the print & filament enabled if they are compatible with the currently selected preset. // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); + //update physical printer's related printer preset if it's needed + update_physical_printers(name); // Add the new item into the UI component, remove dirty flags and activate the saved item. update_tab_ui(); // Update the selection boxes at the plater. diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index bc15efa35..69720ff65 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -306,6 +306,7 @@ public: void load_initial_data(); void update_dirty(); void update_tab_ui(); + void update_physical_printers(std::string preset_name); void load_config(const DynamicPrintConfig& config); virtual void reload_config(); void update_mode(); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 39b3e154b..67b5a18f7 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -731,11 +731,12 @@ void MenuWithSeparators::SetSecondSeparator() // ---------------------------------------------------------------------------- ScalableBitmap::ScalableBitmap( wxWindow *parent, const std::string& icon_name/* = ""*/, - const int px_cnt/* = 16*/): + const int px_cnt/* = 16*/, + const bool grayscale/* = false*/): m_parent(parent), m_icon_name(icon_name), m_px_cnt(px_cnt) { - m_bmp = create_scaled_bitmap(icon_name, parent, px_cnt); + m_bmp = create_scaled_bitmap(icon_name, parent, px_cnt, grayscale); } wxSize ScalableBitmap::GetBmpSize() const @@ -768,7 +769,7 @@ int ScalableBitmap::GetBmpHeight() const void ScalableBitmap::msw_rescale() { - m_bmp = create_scaled_bitmap(m_icon_name, m_parent, m_px_cnt); + m_bmp = create_scaled_bitmap(m_icon_name, m_parent, m_px_cnt, m_grayscale); } // ---------------------------------------------------------------------------- diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 17fe8992c..9be3361bd 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -130,7 +130,8 @@ public: ScalableBitmap() {}; ScalableBitmap( wxWindow *parent, const std::string& icon_name = "", - const int px_cnt = 16); + const int px_cnt = 16, + const bool grayscale = false); ~ScalableBitmap() {} @@ -151,6 +152,7 @@ private: wxBitmap m_bmp = wxBitmap(); std::string m_icon_name = ""; int m_px_cnt {16}; + bool m_grayscale {false}; }; From 6d4a0d91fce7bc8e85302d9b0f8d14b882c0cb9c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 26 Jun 2020 16:58:53 +0200 Subject: [PATCH 09/70] Fixed typo in PresetComboBox.hpp and added missed include in libslic3r.h --- src/libslic3r/libslic3r.h | 1 + src/slic3r/GUI/PresetComboBoxes.hpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index db375ec14..3ea70d998 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -5,6 +5,7 @@ // this needs to be included early for MSVC (listing it in Build.PL is not enough) #include +#include #include #include #include diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 653c0e540..dce22bc82 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -13,7 +13,7 @@ class wxString; class wxTextCtrl; -class wxStatictext; +class wxStaticText; class ScalableButton; namespace Slic3r { From 0b88e86634bd291f524c24912061c9c1e9e71ef3 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 30 Jun 2020 14:12:47 +0200 Subject: [PATCH 10/70] PhysicalPrinter improvements: * implemented PresetForPrinter class --- src/libslic3r/Preset.cpp | 9 ++ src/libslic3r/Preset.hpp | 1 + src/slic3r/GUI/PresetComboBoxes.cpp | 154 ++++++++++++++++++++++------ src/slic3r/GUI/PresetComboBoxes.hpp | 51 +++++++-- 4 files changed, 175 insertions(+), 40 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 9af3dacf0..ec3e93338 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1375,6 +1375,15 @@ const std::string& PhysicalPrinter::get_printer_model() const return config.opt_string("printer_model"); } +bool PhysicalPrinter::has_empty_config() const +{ + return config.opt_string("print_host" ).empty() && + config.opt_string("printhost_apikey").empty() && + config.opt_string("printhost_cafile").empty() && + config.opt_string("login" ).empty() && + config.opt_string("password" ).empty(); +} + void PhysicalPrinter::update_from_preset(const Preset& preset) { config.apply_only(preset.config, printer_options(), false); diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 6eb1fd2db..a076a9a21 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -556,6 +556,7 @@ public: static const std::vector& printer_options(); const std::string& get_preset_name() const; const std::string& get_printer_model() const; + bool has_empty_config() const; void save() { this->config.save(this->file); } void save_to(const std::string& file_name) const { this->config.save(file_name); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 88dd4b739..dc5365a13 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -884,6 +884,76 @@ void TabPresetComboBox::update_dirty() } +//------------------------------------------ +// PresetForPrinter +//------------------------------------------ + +PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, bool is_all_enable) : + m_parent(parent) +{ + m_sizer = new wxBoxSizer(wxVERTICAL); + + m_delete_preset_btn = new ScalableButton(parent, wxID_ANY, "cross", "", wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); + m_delete_preset_btn->SetFont(wxGetApp().normal_font()); + m_delete_preset_btn->SetToolTip(_L("Delete this preset from this printer device")); + m_delete_preset_btn->Bind(wxEVT_BUTTON, &PresetForPrinter::DeletePreset, this); + + m_presets_list = new TabPresetComboBox(parent, Preset::TYPE_PRINTER); + + if (is_all_enable) + m_presets_list->set_enable_all(); + + m_presets_list->set_selection_changed_function([this](int selection) { + std::string selected_string = Preset::remove_suffix_modified(m_presets_list->GetString(selection).ToUTF8().data()); + Preset* preset = wxGetApp().preset_bundle->printers.find_preset(selected_string); + assert(preset); + Preset& edited_preset = wxGetApp().preset_bundle->printers.get_edited_preset(); + if (preset->name == edited_preset.name) + preset = &edited_preset; + + // if created physical printer doesn't have any settings, use the settings from the selected preset + if (m_parent->get_printer()->has_empty_config()) { + // update Print Host upload from the selected preset + m_parent->get_printer()->update_from_preset(*preset); + // update values in parent (PhysicalPrinterDialog) + m_parent->update(); + } + + update_full_printer_name(); + }); + + m_full_printer_name = new wxStaticText(parent, wxID_ANY, ""); + + m_presets_list->update(); +} + +PresetForPrinter::~PresetForPrinter() +{ + m_presets_list->Destroy(); + m_delete_preset_btn->Destroy(); + m_full_printer_name->Destroy(); +} + +void PresetForPrinter::DeletePreset(wxEvent& event) +{ + +} + +void PresetForPrinter::update_full_printer_name() +{ + wxString printer_name = m_parent->get_printer_name(); + wxString preset_name = m_presets_list->GetString(m_presets_list->GetSelection()); + + m_full_printer_name->SetLabelText(printer_name + " * " + preset_name); +} + +void PresetForPrinter::msw_rescale() +{ + m_presets_list->msw_rescale(); + m_delete_preset_btn->msw_rescale(); +} + + //------------------------------------------ // PhysicalPrinterDialog //------------------------------------------ @@ -896,44 +966,31 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); int border = 10; + m_info_string = _("This printer name will be shown in the presets list") + ":\n"; - m_printer_presets = new TabPresetComboBox(this, Preset::TYPE_PRINTER); + TabPresetComboBox* printer_presets = new TabPresetComboBox(this, Preset::TYPE_PRINTER); if (printer_name.IsEmpty()) { - // if printer_name is empty it means that new printer is created, so enable all items in the preset list - m_printer_presets->set_enable_all(); printer_name = _L("My Printer Device"); + // if printer_name is empty it means that new printer is created, so enable all items in the preset list + m_presets.emplace_back(new PresetForPrinter(this, true)); } else { std::string full_name = into_u8(printer_name); printer_name = from_u8(PhysicalPrinter::get_short_name(full_name)); } - m_printer_presets->set_selection_changed_function([this](int selection) { - std::string selected_string = Preset::remove_suffix_modified(m_printer_presets->GetString(selection).ToUTF8().data()); - Preset* preset = wxGetApp().preset_bundle->printers.find_preset(selected_string); - assert(preset); - Preset& edited_preset = wxGetApp().preset_bundle->printers.get_edited_preset(); - if (preset->name == edited_preset.name) - preset = &edited_preset; - m_printer.update_from_preset(*preset); - - update_printer_name(); - - // update values - m_optgroup->reload_config(); - update(); - }); - m_printer_presets->update(); - wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _("Descriptive name for the printer device") + ":"); + + m_add_preset_btn = new ScalableButton(this, wxID_ANY, "add_copies", "", wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); + m_add_preset_btn->SetFont(wxGetApp().normal_font()); + m_add_preset_btn->SetToolTip(_L("Add preset for this printer device")); + m_add_preset_btn->Bind(wxEVT_BUTTON, &PhysicalPrinterDialog::AddPreset, this); + m_printer_name = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize); - m_printer_name->Bind(wxEVT_TEXT, [this](wxEvent&) { this->update_printer_name(); }); + m_printer_name->Bind(wxEVT_TEXT, [this](wxEvent&) { this->update_full_printer_names(); }); - wxStaticText* label_bottom = new wxStaticText(this, wxID_ANY, _("This printer name will be shown in the presets list") + ":"); - m_full_printer_name = new wxStaticText(this, wxID_ANY, ""); - - update_printer_name(); + update_full_printer_names(); PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; PhysicalPrinter* printer = printers.find_printer(into_u8(printer_name)); @@ -948,19 +1005,27 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) m_optgroup = new ConfigOptionsGroup(this, _L("Print Host upload"), m_config); build_printhost_settings(m_optgroup); - m_optgroup->reload_config(); + //m_optgroup->reload_config(); wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); btnOK->Bind(wxEVT_BUTTON, &PhysicalPrinterDialog::OnOK, this); + wxBoxSizer* nameSizer = new wxBoxSizer(wxHORIZONTAL); + nameSizer->Add(m_printer_name, 1, wxEXPAND); + nameSizer->Add(m_add_preset_btn, 0, wxEXPAND | wxLEFT, border); + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(m_printer_name , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(nameSizer , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + for (PresetForPrinter* preset : m_presets) + topSizer->Add(preset->sizer(), 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);; + /* topSizer->Add(m_printer_presets , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(label_bottom , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(m_full_printer_name , 0, wxEXPAND | wxLEFT | wxRIGHT, border); + */ topSizer->Add(m_optgroup->sizer , 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(btns , 0, wxEXPAND | wxALL, border); @@ -968,6 +1033,14 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) topSizer->SetSizeHints(this); } +PhysicalPrinterDialog::~PhysicalPrinterDialog() +{ + for (PresetForPrinter* preset : m_presets) { + delete preset; + preset = nullptr; + } +} + void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup) { m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { @@ -1101,6 +1174,8 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr void PhysicalPrinterDialog::update() { + m_optgroup->reload_config(); + const PrinterTechnology tech = Preset::printer_technology(m_printer.config); // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) if (tech == ptFFF) { @@ -1125,12 +1200,17 @@ void PhysicalPrinterDialog::update() this->Layout(); } -void PhysicalPrinterDialog::update_printer_name() -{ - wxString printer_name = m_printer_name->GetValue(); - wxString preset_name = m_printer_presets->GetString(m_printer_presets->GetSelection()); - m_full_printer_name->SetLabelText("\t" + printer_name + " * " + preset_name); +wxString PhysicalPrinterDialog::get_printer_name() +{ + return m_info_string + m_printer_name->GetValue() + "\t"; +} + +void PhysicalPrinterDialog::update_full_printer_names() +{ + for (PresetForPrinter* preset : m_presets) + preset->update_full_printer_name(); + this->Layout(); } @@ -1147,6 +1227,9 @@ void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); + for (PresetForPrinter* preset : m_presets) + preset->msw_rescale(); + const wxSize& size = wxSize(45 * em, 35 * em); SetMinSize(size); @@ -1184,6 +1267,7 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) printers.save_printer(m_printer); // update selection on the tab only when it was changed + /* if (m_printer.get_preset_name() != wxGetApp().preset_bundle->printers.get_selected_preset_name()) { Tab* tab = wxGetApp().get_tab(Preset::TYPE_PRINTER); if (tab) { @@ -1191,9 +1275,15 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) tab->select_preset(into_u8(preset_name)); } } + */ event.Skip(); } +void PhysicalPrinterDialog::AddPreset(wxEvent& event) +{ + +} + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index dce22bc82..3d5ae298f 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -166,32 +166,67 @@ public: }; +//------------------------------------------ +// PresetForPrinter +//------------------------------------------ +class PhysicalPrinterDialog; +class PresetForPrinter +{ + PhysicalPrinterDialog* m_parent { nullptr }; + + TabPresetComboBox* m_presets_list { nullptr }; + ScalableButton* m_delete_preset_btn { nullptr }; + wxStaticText* m_full_printer_name { nullptr }; + + wxBoxSizer* m_sizer { nullptr }; + + void DeletePreset(wxEvent& event); + +public: + PresetForPrinter(PhysicalPrinterDialog* parent, bool is_all_enable); + ~PresetForPrinter(); + + wxBoxSizer* sizer() { return m_sizer; } + void update_full_printer_name(); + + void msw_rescale(); + void on_sys_color_changed() {}; +}; + + //------------------------------------------ // PhysicalPrinterDialog //------------------------------------------ + class ConfigOptionsGroup; class PhysicalPrinterDialog : public DPIDialog { PhysicalPrinter m_printer; DynamicPrintConfig* m_config { nullptr }; + wxString m_info_string; wxTextCtrl* m_printer_name { nullptr }; - wxStaticText* m_full_printer_name { nullptr }; - TabPresetComboBox* m_printer_presets { nullptr }; + std::vector m_presets; + ConfigOptionsGroup* m_optgroup { nullptr }; - ScalableButton* m_printhost_browse_btn; - ScalableButton* m_printhost_test_btn; - ScalableButton* m_printhost_cafile_browse_btn {nullptr}; + ScalableButton* m_add_preset_btn {nullptr}; + ScalableButton* m_printhost_browse_btn {nullptr}; + ScalableButton* m_printhost_test_btn {nullptr}; + ScalableButton* m_printhost_cafile_browse_btn {nullptr}; void build_printhost_settings(ConfigOptionsGroup* optgroup); - void update(); - void update_printer_name(); void OnOK(wxEvent& event); + void AddPreset(wxEvent& event); public: PhysicalPrinterDialog(wxString printer_name); - ~PhysicalPrinterDialog() {} + ~PhysicalPrinterDialog(); + + void update(); + wxString get_printer_name(); + void update_full_printer_names(); + PhysicalPrinter* get_printer() {return &m_printer; } protected: void on_dpi_changed(const wxRect& suggested_rect) override; From c5197f33502effe0bd19a257db6235ca8df41edd Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 14 Jul 2020 15:34:08 +0200 Subject: [PATCH 11/70] PhysicalPrinterDialog is completed --- src/libslic3r/Preset.cpp | 82 +++++++++++++-- src/libslic3r/Preset.hpp | 28 +++-- src/slic3r/GUI/PresetComboBoxes.cpp | 156 ++++++++++++++++++++++------ src/slic3r/GUI/PresetComboBoxes.hpp | 13 ++- 4 files changed, 230 insertions(+), 49 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index ec3e93338..c9f5bd0af 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1345,6 +1345,11 @@ const Preset* PrinterPresetCollection::find_by_model_id(const std::string &model // *** PhysicalPrinter *** // ------------------------- +std::string PhysicalPrinter::separator() +{ + return " * "; +} + const std::vector& PhysicalPrinter::printer_options() { static std::vector s_opts; @@ -1370,9 +1375,9 @@ const std::string& PhysicalPrinter::get_preset_name() const return config.opt_string("preset_name"); } -const std::string& PhysicalPrinter::get_printer_model() const +const std::set& PhysicalPrinter::get_preset_names() const { - return config.opt_string("printer_model"); + return preset_names; } bool PhysicalPrinter::has_empty_config() const @@ -1384,20 +1389,62 @@ bool PhysicalPrinter::has_empty_config() const config.opt_string("password" ).empty(); } +void PhysicalPrinter::update_preset_names_in_config() +{ + if (!preset_names.empty()) { + std::string name; + for (auto el : preset_names) + name += el + ";"; + name.pop_back(); + config.set_key_value("preset_name", new ConfigOptionString(name)); + } +} + +void PhysicalPrinter::save(const std::string& file_name_from, const std::string& file_name_to) +{ + // rename the file + boost::nowide::rename(file_name_from.data(), file_name_to.data()); + this->file = file_name_to; + // save configuration + this->config.save(this->file); +} + void PhysicalPrinter::update_from_preset(const Preset& preset) { config.apply_only(preset.config, printer_options(), false); - // add preset name to the options list - config.set_key_value("preset_name", new ConfigOptionString(preset.name)); + // add preset names to the options list + auto ret = preset_names.emplace(preset.name); + update_preset_names_in_config(); + update_full_name(); } void PhysicalPrinter::update_from_config(const DynamicPrintConfig& new_config) { config.apply_only(new_config, printer_options(), false); + + std::string str = config.opt_string("preset_name"); + std::set values{}; + if (!str.empty()) { + boost::split(values, str, boost::is_any_of(";")); + for (const std::string& val : values) + preset_names.emplace(val); + } + preset_names = values; + update_full_name(); } +void PhysicalPrinter::reset_presets() +{ + return preset_names.clear(); +} + +bool PhysicalPrinter::add_preset(const std::string& preset_name) +{ + return preset_names.emplace(preset_name).second; +} + PhysicalPrinter::PhysicalPrinter(const std::string& name, const Preset& preset) : name(name) { @@ -1412,16 +1459,23 @@ void PhysicalPrinter::set_name(const std::string& name) void PhysicalPrinter::update_full_name() { - full_name = name + " * " + get_preset_name(); + full_name = name + separator() + get_preset_name(); } std::string PhysicalPrinter::get_short_name(std::string full_name) { - int pos = full_name.find_first_of(" * "); + int pos = full_name.find(separator()); boost::erase_tail(full_name, full_name.length() - pos); return full_name; } +std::string PhysicalPrinter::get_preset_name(std::string full_name) +{ + int pos = full_name.find(separator()); + boost::erase_head(full_name, pos + 2); + return full_name; +} + // ----------------------------------- // *** PhysicalPrinterCollection *** @@ -1497,15 +1551,18 @@ std::string PhysicalPrinterCollection::path_from_name(const std::string& new_nam return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); } -void PhysicalPrinterCollection::save_printer(const PhysicalPrinter& edited_printer) +void PhysicalPrinterCollection::save_printer(const PhysicalPrinter& edited_printer, const std::string& renamed_from) { + std::string name = renamed_from.empty() ? edited_printer.name : renamed_from; // 1) Find the printer with a new_name or create a new one, // initialize it with the edited config. - auto it = this->find_printer_internal(edited_printer.name); - if (it != m_printers.end() && it->name == edited_printer.name) { + auto it = this->find_printer_internal(name); + if (it != m_printers.end() && it->name == name) { // Printer with the same name found. // Overwriting an existing preset. it->config = std::move(edited_printer.config); + it->name = edited_printer.name; + it->preset_names = edited_printer.preset_names; it->full_name = edited_printer.full_name; } else { @@ -1518,7 +1575,12 @@ void PhysicalPrinterCollection::save_printer(const PhysicalPrinter& edited_print PhysicalPrinter& printer = *it; if (printer.file.empty()) printer.file = this->path_from_name(printer.name); - printer.save(); + + if (printer.file == this->path_from_name(printer.name)) + printer.save(); + else + // if printer was renamed, we should rename a file and than save the config + printer.save(printer.file, this->path_from_name(printer.name)); // update idx_selected m_idx_selected = it - m_printers.begin(); diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index a076a9a21..d583bed20 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -549,20 +549,33 @@ public: std::string file; // Configuration data, loaded from a file, or set from the defaults. DynamicPrintConfig config; + // set of presets used with this physical printer + std::set preset_names; + + static std::string separator(); // Has this profile been loaded? bool loaded = false; - static const std::vector& printer_options(); - const std::string& get_preset_name() const; - const std::string& get_printer_model() const; + static const std::vector& printer_options(); + const std::string& get_preset_name() const; + + const std::set& get_preset_names() const; + bool has_empty_config() const; + void update_preset_names_in_config(); void save() { this->config.save(this->file); } - void save_to(const std::string& file_name) const { this->config.save(file_name); } + void save(const std::string& file_name_from, const std::string& file_name_to); + void update_from_preset(const Preset& preset); void update_from_config(const DynamicPrintConfig &new_config); + // add preset to the preset_names + // return false, if preset with this name is already exist in the set + bool add_preset(const std::string& preset_name); + void reset_presets(); + // Return a printer technology, return ptFFF if the printer technology is not set. static PrinterTechnology printer_technology(const DynamicPrintConfig& cfg) { auto* opt = cfg.option>("printer_technology"); @@ -578,6 +591,9 @@ public: // get printer name from the full name uncluded preset name static std::string get_short_name(std::string full_name); + // get preset name from the full name uncluded printer name + static std::string get_preset_name(std::string full_name); + protected: friend class PhysicalPrinterCollection; }; @@ -615,7 +631,7 @@ public: // Save the printer under a new name. If the name is different from the old one, // a new printer is stored into the list of printers. // New printer is activated. - void save_printer(const PhysicalPrinter& printer); + void save_printer(const PhysicalPrinter& printer, const std::string& renamed_from); // Delete the current preset, activate the first visible preset. // returns true if the preset was deleted successfully. @@ -633,8 +649,6 @@ public: // Returns the full name of the selected preset, or an empty string if no preset is selected. std::string get_selected_full_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().full_name; } // Returns the printer model of the selected preset, or an empty string if no preset is selected. - std::string get_selected_printer_model() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_printer_model(); } - // Returns the printer model of the selected preset, or an empty string if no preset is selected. std::string get_selected_printer_preset_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_preset_name(); } // Returns the config of the selected preset, or nullptr if no preset is selected. DynamicPrintConfig* get_selected_printer_config() { return (m_idx_selected == size_t(-1)) ? nullptr : &(this->get_selected_printer().config); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index dc5365a13..1473bf6da 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -41,6 +41,8 @@ static const std::pair THUMBNAIL_SIZE_3MF = { 256, 2 namespace Slic3r { namespace GUI { +#define BORDER_W 10 + // --------------------------------- // *** PresetComboBox *** // --------------------------------- @@ -762,8 +764,8 @@ void TabPresetComboBox::update() // check this value just for printer presets, when physical printer is selected if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) { is_enabled = m_enable_all ? true : - preset.name == m_preset_bundle->physical_printers.get_selected_printer_preset_name() || - preset.config.opt_string("printer_model") == m_preset_bundle->physical_printers.get_selected_printer_model(); + preset.name == m_preset_bundle->physical_printers.get_selected_printer_preset_name()/* || + preset.config.opt_string("printer_model") == m_preset_bundle->physical_printers.get_selected_printer_model()*/; } std::string bitmap_key = "tab"; @@ -888,7 +890,7 @@ void TabPresetComboBox::update_dirty() // PresetForPrinter //------------------------------------------ -PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, bool is_all_enable) : +PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name) : m_parent(parent) { m_sizer = new wxBoxSizer(wxVERTICAL); @@ -899,8 +901,7 @@ PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, bool is_all_en m_delete_preset_btn->Bind(wxEVT_BUTTON, &PresetForPrinter::DeletePreset, this); m_presets_list = new TabPresetComboBox(parent, Preset::TYPE_PRINTER); - - if (is_all_enable) + if (preset_name.empty()) m_presets_list->set_enable_all(); m_presets_list->set_selection_changed_function([this](int selection) { @@ -921,22 +922,37 @@ PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, bool is_all_en update_full_printer_name(); }); + m_presets_list->update(); + m_presets_list->SetStringSelection(from_u8(preset_name)); + + m_info_line = new wxStaticText(parent, wxID_ANY, _L("This printer will be shown in the presets list as") + ":"); m_full_printer_name = new wxStaticText(parent, wxID_ANY, ""); + m_full_printer_name->SetFont(wxGetApp().bold_font()); - m_presets_list->update(); + wxBoxSizer* preset_sizer = new wxBoxSizer(wxHORIZONTAL); + preset_sizer->Add(m_presets_list , 1, wxEXPAND); + preset_sizer->Add(m_delete_preset_btn , 0, wxEXPAND | wxLEFT, BORDER_W); + + wxBoxSizer* name_sizer = new wxBoxSizer(wxHORIZONTAL); + name_sizer->Add(m_info_line, 0, wxEXPAND); + name_sizer->Add(m_full_printer_name, 0, wxEXPAND | wxLEFT, BORDER_W); + + m_sizer->Add(preset_sizer , 0, wxEXPAND); + m_sizer->Add(name_sizer, 0, wxEXPAND); } PresetForPrinter::~PresetForPrinter() { m_presets_list->Destroy(); m_delete_preset_btn->Destroy(); + m_info_line->Destroy(); m_full_printer_name->Destroy(); } void PresetForPrinter::DeletePreset(wxEvent& event) { - + m_parent->DeletePreset(this); } void PresetForPrinter::update_full_printer_name() @@ -947,6 +963,22 @@ void PresetForPrinter::update_full_printer_name() m_full_printer_name->SetLabelText(printer_name + " * " + preset_name); } +std::string PresetForPrinter::get_preset_name() +{ + return into_u8(m_presets_list->GetString(m_presets_list->GetSelection())); +} + +void PresetForPrinter::DisableDeleteBtn() +{ + m_delete_preset_btn->Enable(false); +} + +void PresetForPrinter::EnableDeleteBtn() +{ + if (!m_delete_preset_btn->IsEnabled()) + m_delete_preset_btn->Enable(); +} + void PresetForPrinter::msw_rescale() { m_presets_list->msw_rescale(); @@ -965,22 +997,17 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) SetFont(wxGetApp().normal_font()); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - int border = 10; - m_info_string = _("This printer name will be shown in the presets list") + ":\n"; - - TabPresetComboBox* printer_presets = new TabPresetComboBox(this, Preset::TYPE_PRINTER); - if (printer_name.IsEmpty()) { printer_name = _L("My Printer Device"); // if printer_name is empty it means that new printer is created, so enable all items in the preset list - m_presets.emplace_back(new PresetForPrinter(this, true)); + m_presets.emplace_back(new PresetForPrinter(this, "")); } else { std::string full_name = into_u8(printer_name); printer_name = from_u8(PhysicalPrinter::get_short_name(full_name)); } - wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _("Descriptive name for the printer device") + ":"); + wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _L("Descriptive name for the printer device") + ":"); m_add_preset_btn = new ScalableButton(this, wxID_ANY, "add_copies", "", wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); m_add_preset_btn->SetFont(wxGetApp().normal_font()); @@ -990,17 +1017,26 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) m_printer_name = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize); m_printer_name->Bind(wxEVT_TEXT, [this](wxEvent&) { this->update_full_printer_names(); }); - update_full_printer_names(); - PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; PhysicalPrinter* printer = printers.find_printer(into_u8(printer_name)); if (!printer) { const Preset& preset = wxGetApp().preset_bundle->printers.get_edited_preset(); printer = new PhysicalPrinter(into_u8(printer_name), preset); } + else + { + const std::set& preset_names = printer->get_preset_names(); + for (const std::string& preset_name : preset_names) + m_presets.emplace_back(new PresetForPrinter(this, preset_name)); + } assert(printer); m_printer = *printer; + if (m_presets.size() == 1) + m_presets.front()->DisableDeleteBtn(); + + update_full_printer_names(); + m_config = &m_printer.config; m_optgroup = new ConfigOptionsGroup(this, _L("Print Host upload"), m_config); @@ -1013,21 +1049,19 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) wxBoxSizer* nameSizer = new wxBoxSizer(wxHORIZONTAL); nameSizer->Add(m_printer_name, 1, wxEXPAND); - nameSizer->Add(m_add_preset_btn, 0, wxEXPAND | wxLEFT, border); + nameSizer->Add(m_add_preset_btn, 0, wxEXPAND | wxLEFT, BORDER_W); + + m_presets_sizer = new wxBoxSizer(wxVERTICAL); + for (PresetForPrinter* preset : m_presets) + m_presets_sizer->Add(preset->sizer(), 1, wxEXPAND | wxTOP, BORDER_W); wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); - topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(nameSizer , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - for (PresetForPrinter* preset : m_presets) - topSizer->Add(preset->sizer(), 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);; - /* - topSizer->Add(m_printer_presets , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(label_bottom , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(m_full_printer_name , 0, wxEXPAND | wxLEFT | wxRIGHT, border); - */ - topSizer->Add(m_optgroup->sizer , 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(btns , 0, wxEXPAND | wxALL, border); + topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); + topSizer->Add(nameSizer , 0, wxEXPAND | wxLEFT | wxRIGHT, BORDER_W); + topSizer->Add(m_presets_sizer , 0, wxEXPAND | wxLEFT | wxRIGHT, BORDER_W); + topSizer->Add(m_optgroup->sizer , 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); + topSizer->Add(btns , 0, wxEXPAND | wxALL, BORDER_W); SetSizer(topSizer); topSizer->SetSizeHints(this); @@ -1203,7 +1237,7 @@ void PhysicalPrinterDialog::update() wxString PhysicalPrinterDialog::get_printer_name() { - return m_info_string + m_printer_name->GetValue() + "\t"; + return m_printer_name->GetValue(); } void PhysicalPrinterDialog::update_full_printer_names() @@ -1260,11 +1294,39 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) printers.delete_printer(into_u8(printer_name)); } + std::set repeat_presets; + m_printer.reset_presets(); + for (PresetForPrinter* preset : m_presets) { + if (!m_printer.add_preset(preset->get_preset_name())) + repeat_presets.emplace(preset->get_preset_name()); + } + // update preset_names in printer config + m_printer.update_preset_names_in_config(); + + if (!repeat_presets.empty()) + { + wxString repeatable_presets = "\n"; + for (const std::string& preset_name : repeat_presets) + repeatable_presets += " " + from_u8(preset_name) + "\n"; + repeatable_presets += "\n"; + + wxString msg_text = from_u8((boost::format(_u8L("Next printer preset(s) is(are) duplicated:%1%" + "It(they) will be added just once for the printer \"%2%\".")) % repeatable_presets % printer_name).str()); + wxMessageDialog dialog(nullptr, msg_text, _L("Infornation"), wxICON_INFORMATION | wxOK); + dialog.ShowModal(); + } + + std::string renamed_from; + // temporary save previous printer name if it was edited + if (m_printer.name != _u8L("My Printer Device") && + m_printer.name != into_u8(printer_name)) + renamed_from = m_printer.name; + //update printer name, if it was changed m_printer.set_name(into_u8(printer_name)); // save new physical printer - printers.save_printer(m_printer); + printers.save_printer(m_printer, renamed_from); // update selection on the tab only when it was changed /* @@ -1282,7 +1344,41 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) void PhysicalPrinterDialog::AddPreset(wxEvent& event) { + // if printer_name is empty it means that new printer is created, so enable all items in the preset list + m_presets.emplace_back(new PresetForPrinter(this, "")); + // enable DELETE button for the first preset, if was disabled + m_presets.front()->EnableDeleteBtn(); + m_presets_sizer->Add(m_presets.back()->sizer(), 1, wxEXPAND | wxTOP, BORDER_W); + update_full_printer_names(); + + this->Fit(); +} + +void PhysicalPrinterDialog::DeletePreset(PresetForPrinter* preset_for_printer) +{ + if (m_presets.size() == 1) { + wxString msg_text = _L("It's not possible to delete last related preset for the printer."); + wxMessageDialog dialog(nullptr, msg_text, _L("Infornation"), wxICON_INFORMATION | wxOK); + dialog.ShowModal(); + return; + } + + assert(preset_for_printer); + auto it = std::find(m_presets.begin(), m_presets.end(), preset_for_printer); + if (it == m_presets.end()) + return; + + const int remove_id = it - m_presets.begin(); + m_presets_sizer->Remove(remove_id); + delete preset_for_printer; + m_presets.erase(it); + + if (m_presets.size() == 1) + m_presets.front()->DisableDeleteBtn(); + + this->Layout(); + this->Fit(); } diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 3d5ae298f..c818b6b91 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -15,6 +15,7 @@ class wxString; class wxTextCtrl; class wxStaticText; class ScalableButton; +class wxBoxSizer; namespace Slic3r { @@ -169,6 +170,7 @@ public: //------------------------------------------ // PresetForPrinter //------------------------------------------ +static std::string g_info_string = " (modified)"; class PhysicalPrinterDialog; class PresetForPrinter { @@ -176,6 +178,7 @@ class PresetForPrinter TabPresetComboBox* m_presets_list { nullptr }; ScalableButton* m_delete_preset_btn { nullptr }; + wxStaticText* m_info_line { nullptr }; wxStaticText* m_full_printer_name { nullptr }; wxBoxSizer* m_sizer { nullptr }; @@ -183,11 +186,14 @@ class PresetForPrinter void DeletePreset(wxEvent& event); public: - PresetForPrinter(PhysicalPrinterDialog* parent, bool is_all_enable); + PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name); ~PresetForPrinter(); wxBoxSizer* sizer() { return m_sizer; } void update_full_printer_name(); + std::string get_preset_name(); + void DisableDeleteBtn(); + void EnableDeleteBtn(); void msw_rescale(); void on_sys_color_changed() {}; @@ -203,7 +209,6 @@ class PhysicalPrinterDialog : public DPIDialog { PhysicalPrinter m_printer; DynamicPrintConfig* m_config { nullptr }; - wxString m_info_string; wxTextCtrl* m_printer_name { nullptr }; std::vector m_presets; @@ -215,6 +220,8 @@ class PhysicalPrinterDialog : public DPIDialog ScalableButton* m_printhost_test_btn {nullptr}; ScalableButton* m_printhost_cafile_browse_btn {nullptr}; + wxBoxSizer* m_presets_sizer {nullptr}; + void build_printhost_settings(ConfigOptionsGroup* optgroup); void OnOK(wxEvent& event); void AddPreset(wxEvent& event); @@ -228,6 +235,8 @@ public: void update_full_printer_names(); PhysicalPrinter* get_printer() {return &m_printer; } + void DeletePreset(PresetForPrinter* preset_for_printer); + protected: void on_dpi_changed(const wxRect& suggested_rect) override; void on_sys_color_changed() override {}; From 5eac36a31066df16fc581206951fbfe675e177ab Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 17 Jul 2020 14:32:38 +0200 Subject: [PATCH 12/70] Update for PresetComboBoxes All "Printer-PresetName" pairs are like a separated items now + some code refactoring for PresetComboBoxes::update() --- src/libslic3r/Preset.cpp | 60 ++-- src/libslic3r/Preset.hpp | 41 ++- src/libslic3r/PresetBundle.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 21 +- src/slic3r/GUI/PresetComboBoxes.cpp | 514 ++++++++++++++++++---------- src/slic3r/GUI/PresetComboBoxes.hpp | 49 ++- src/slic3r/GUI/Tab.cpp | 67 +++- src/slic3r/GUI/Tab.hpp | 5 +- 8 files changed, 489 insertions(+), 270 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index c9f5bd0af..ad7615f6d 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1370,11 +1370,6 @@ const std::vector& PhysicalPrinter::printer_options() return s_opts; } -const std::string& PhysicalPrinter::get_preset_name() const -{ - return config.opt_string("preset_name"); -} - const std::set& PhysicalPrinter::get_preset_names() const { return preset_names; @@ -1415,8 +1410,6 @@ void PhysicalPrinter::update_from_preset(const Preset& preset) // add preset names to the options list auto ret = preset_names.emplace(preset.name); update_preset_names_in_config(); - - update_full_name(); } void PhysicalPrinter::update_from_config(const DynamicPrintConfig& new_config) @@ -1431,8 +1424,6 @@ void PhysicalPrinter::update_from_config(const DynamicPrintConfig& new_config) preset_names.emplace(val); } preset_names = values; - - update_full_name(); } void PhysicalPrinter::reset_presets() @@ -1454,26 +1445,26 @@ PhysicalPrinter::PhysicalPrinter(const std::string& name, const Preset& preset) void PhysicalPrinter::set_name(const std::string& name) { this->name = name; - update_full_name(); } -void PhysicalPrinter::update_full_name() +std::string PhysicalPrinter::get_full_name(std::string preset_name) const { - full_name = name + separator() + get_preset_name(); + return name + separator() + preset_name; } std::string PhysicalPrinter::get_short_name(std::string full_name) { int pos = full_name.find(separator()); - boost::erase_tail(full_name, full_name.length() - pos); + if (pos > 0) + boost::erase_tail(full_name, full_name.length() - pos); return full_name; } -std::string PhysicalPrinter::get_preset_name(std::string full_name) +std::string PhysicalPrinter::get_preset_name(std::string name) { - int pos = full_name.find(separator()); - boost::erase_head(full_name, pos + 2); - return full_name; + int pos = name.find(separator()); + boost::erase_head(name, pos + 3); + return Preset::remove_suffix_modified(name); } @@ -1563,7 +1554,6 @@ void PhysicalPrinterCollection::save_printer(const PhysicalPrinter& edited_print it->config = std::move(edited_printer.config); it->name = edited_printer.name; it->preset_names = edited_printer.preset_names; - it->full_name = edited_printer.full_name; } else { // Creating a new printer. @@ -1615,17 +1605,43 @@ bool PhysicalPrinterCollection::delete_selected_printer() return true; } -PhysicalPrinter& PhysicalPrinterCollection::select_printer_by_name(std::string name) +std::string PhysicalPrinterCollection::get_selected_full_printer_name() const { - name = PhysicalPrinter::get_short_name(name); - auto it = this->find_printer_internal(name); + return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_full_name(m_selected_preset); +} + +PhysicalPrinter& PhysicalPrinterCollection::select_printer_by_name(const std::string& full_name) +{ + std::string printer_name = PhysicalPrinter::get_short_name(full_name); + auto it = this->find_printer_internal(printer_name); assert(it != m_printers.end()); // update idx_selected - m_idx_selected = it - m_printers.begin(); + m_idx_selected = it - m_printers.begin(); + // update name of the currently selected preset + m_selected_preset = it->get_preset_name(full_name); + if (m_selected_preset.empty()) + m_selected_preset = *it->preset_names.begin(); return *it; } +bool PhysicalPrinterCollection::has_selection() const +{ + return m_idx_selected != size_t(-1); +} + +void PhysicalPrinterCollection::unselect_printer() +{ + m_idx_selected = size_t(-1); + m_selected_preset.clear(); +} + +bool PhysicalPrinterCollection::is_selected(PhysicalPrinterCollection::ConstIterator it, const std::string& preset_name) const +{ + return m_idx_selected == it - m_printers.begin() && + m_selected_preset == preset_name; +} + namespace PresetUtils { const VendorProfile::PrinterModel* system_printer_model(const Preset &preset) diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index d583bed20..d30ea7059 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -539,12 +539,9 @@ public: PhysicalPrinter(const std::string& name) : name(name){} PhysicalPrinter(const std::string& name, const Preset& preset); void set_name(const std::string &name); - void update_full_name(); // Name of the Physical Printer, usually derived form the file name. std::string name; - // Full name of the Physical Printer, included related preset name - std::string full_name; // File name of the Physical Printer. std::string file; // Configuration data, loaded from a file, or set from the defaults. @@ -558,8 +555,6 @@ public: bool loaded = false; static const std::vector& printer_options(); - const std::string& get_preset_name() const; - const std::set& get_preset_names() const; bool has_empty_config() const; @@ -588,6 +583,9 @@ public: // Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection. bool operator<(const PhysicalPrinter& other) const { return this->name < other.name; } + // get full printer name included a name of the preset + std::string get_full_name(std::string preset_name) const; + // get printer name from the full name uncluded preset name static std::string get_short_name(std::string full_name); @@ -641,22 +639,29 @@ public: bool delete_selected_printer(); // Return the selected preset, without the user modifications applied. - PhysicalPrinter& get_selected_printer() { return m_printers[m_idx_selected]; } - const PhysicalPrinter& get_selected_printer() const { return m_printers[m_idx_selected]; } - size_t get_selected_idx() const { return m_idx_selected; } + PhysicalPrinter& get_selected_printer() { return m_printers[m_idx_selected]; } + const PhysicalPrinter& get_selected_printer() const { return m_printers[m_idx_selected]; } + + size_t get_selected_idx() const { return m_idx_selected; } // Returns the name of the selected preset, or an empty string if no preset is selected. - std::string get_selected_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().name; } - // Returns the full name of the selected preset, or an empty string if no preset is selected. - std::string get_selected_full_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().full_name; } + std::string get_selected_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().name; } + // Returns the config of the selected printer, or nullptr if no printer is selected. + DynamicPrintConfig* get_selected_printer_config() { return (m_idx_selected == size_t(-1)) ? nullptr : &(this->get_selected_printer().config); } + // Returns the config of the selected printer, or nullptr if no printer is selected. + PrinterTechnology get_selected_printer_technology() { return (m_idx_selected == size_t(-1)) ? PrinterTechnology::ptAny : this->get_selected_printer().printer_technology(); } + + // Each physical printer can have a several related preset, + // so, use the next functions to get an exact names of selections in the list: + // Returns the full name of the selected printer, or an empty string if no preset is selected. + std::string get_selected_full_printer_name() const; // Returns the printer model of the selected preset, or an empty string if no preset is selected. - std::string get_selected_printer_preset_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_preset_name(); } - // Returns the config of the selected preset, or nullptr if no preset is selected. - DynamicPrintConfig* get_selected_printer_config() { return (m_idx_selected == size_t(-1)) ? nullptr : &(this->get_selected_printer().config); } + std::string get_selected_printer_preset_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : m_selected_preset; } // select printer with name and return reference on it - PhysicalPrinter& select_printer_by_name(std::string name); - bool has_selection() const { return m_idx_selected != size_t(-1); } - void unselect_printer() { m_idx_selected = size_t(-1); } + PhysicalPrinter& select_printer_by_name(const std::string& full_name); + bool has_selection() const; + void unselect_printer() ; + bool is_selected(ConstIterator it, const std::string &preset_name) const; // Return a printer by an index. If the printer is active, a temporary copy is returned. PhysicalPrinter& printer(size_t idx) { return m_printers[idx]; } @@ -698,6 +703,8 @@ private: // Selected printer. size_t m_idx_selected = size_t(-1); + // The name of the preset which is currently select for this printer + std::string m_selected_preset; // Path to the directory to store the config files into. std::string m_dir_path; diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 7c4fa1cb2..7969966e5 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -458,7 +458,7 @@ void PresetBundle::export_selections(AppConfig &config) config.set("presets", "sla_material", sla_materials.get_selected_preset_name()); config.set("presets", "printer", printers.get_selected_preset_name()); - config.set("extras", "physical_printer", physical_printers.get_selected_printer_name()); + config.set("extras", "physical_printer", physical_printers.get_selected_full_printer_name()); } DynamicPrintConfig PresetBundle::full_config() const diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 048e17926..5673eece7 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3208,24 +3208,21 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) if (preset_type == Preset::TYPE_FILAMENT) { wxGetApp().preset_bundle->set_filament_preset(idx, preset_name); } - - if (preset_type == Preset::TYPE_PRINTER) { - if(combo->is_selected_physical_printer()) { - // Select related printer preset on the Printer Settings Tab - const std::string printer_name = combo->GetString(selection).ToUTF8().data(); - PhysicalPrinter& printer = wxGetApp().preset_bundle->physical_printers.select_printer_by_name(printer_name); - preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type, printer.get_preset_name()); - } - else - wxGetApp().preset_bundle->physical_printers.unselect_printer(); - } + bool select_preset = !combo->selection_is_changed_according_to_physical_printers(); // TODO: ? if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) { // Only update the plater UI for the 2nd and other filaments. combo->update(); } - else { + else if (select_preset) { + if (preset_type == Preset::TYPE_PRINTER) { + PhysicalPrinterCollection& physical_printers = wxGetApp().preset_bundle->physical_printers; + if(combo->is_selected_physical_printer()) + preset_name = physical_printers.get_selected_printer_preset_name(); + else + physical_printers.unselect_printer(); + } wxWindowUpdateLocker noUpdates(sidebar->presets_panel()); wxGetApp().get_tab(preset_type)->select_preset(preset_name); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 1473bf6da..e08cf101d 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -63,8 +63,7 @@ PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const m_type(preset_type), m_last_selected(wxNOT_FOUND), m_em_unit(em_unit(this)), - m_preset_bundle(wxGetApp().preset_bundle), - m_bitmap_cache(new BitmapCache) + m_preset_bundle(wxGetApp().preset_bundle) { SetFont(wxGetApp().normal_font()); #ifdef _WIN32 @@ -105,17 +104,31 @@ PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const m_bitmapCompatible = ScalableBitmap(this, "flag_green"); m_bitmapIncompatible = ScalableBitmap(this, "flag_red"); - m_bitmapLock = ScalableBitmap(this, "lock_closed"); - m_bitmapLockDisabled = ScalableBitmap(this, "lock_closed", 16, true); // parameters for an icon's drawing fill_width_height(); + + Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { + // see https://github.com/prusa3d/PrusaSlicer/issues/3889 + // Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender") + // m_presets_choice->GetSelection() will return first item, because search in PopupListCtrl is case-insensitive. + // So, use GetSelection() from event parameter + auto selected_item = evt.GetSelection(); + + auto marker = reinterpret_cast(this->GetClientData(selected_item)); + if (marker >= LABEL_ITEM_DISABLED && marker < LABEL_ITEM_MAX) + this->SetSelection(this->m_last_selected); + else if (on_selection_changed && (m_last_selected != selected_item || m_collection->current_is_dirty())) { + m_last_selected = selected_item; + on_selection_changed(selected_item); + evt.StopPropagation(); + } + evt.Skip(); + }); } PresetComboBox::~PresetComboBox() { - delete m_bitmap_cache; - m_bitmap_cache = nullptr; } void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type) @@ -123,12 +136,96 @@ void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type) this->SetClientData(item, (void*)label_item_type); } +void PresetComboBox::update(const std::string& select_preset_name) +{ + Freeze(); + Clear(); + size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected + + const std::deque& presets = m_collection->get_presets(); + + std::map> nonsys_presets; + wxString selected = ""; + if (!presets.front().is_visible) + set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + + for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) + { + const Preset& preset = presets[i]; + if (!preset.is_visible || !preset.is_compatible) + continue; + + // marker used for disable incompatible printer models for the selected physical printer + bool is_enabled = true; + + std::string bitmap_key = "tab"; + std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; + + if (m_type == Preset::TYPE_PRINTER) { + bitmap_key += "_printer"; + if (preset.printer_technology() == ptSLA) + bitmap_key += "_sla"; + if (!is_enabled) + bitmap_key += "_disabled"; + } + bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; + bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + + wxBitmap* bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default); + assert(bmp); + + if (preset.is_default || preset.is_system) { + int item_id = Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); + if (!is_enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); + if (preset.name == select_preset_name ||//i == idx_selected || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + } + else + { + std::pair pair(bmp, is_enabled); + nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), std::pair(bmp, is_enabled)); + if (preset.name == select_preset_name) + selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); + } + if (i + 1 == m_collection->num_default_presets()) + set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + } + if (!nonsys_presets.empty()) + { + set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); + for (std::map>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + int item_id = Append(it->first, *it->second.first); + bool is_enabled = it->second.second; + if (!is_enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); + if (it->first == selected || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + } + } + + /* But, if selected_preset_item is still equal to INT_MAX, it means that + * there is no presets added to the list. + * So, select last combobox item ("Add/Remove preset") + */ + if (selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + + SetSelection(selected_preset_item); + SetToolTip(GetString(selected_preset_item)); + Thaw(); + + m_last_selected = selected_preset_item; +} + void PresetComboBox::msw_rescale() { m_em_unit = em_unit(this); - m_bitmapLock.msw_rescale(); - m_bitmapLockDisabled.msw_rescale(); m_bitmapIncompatible.msw_rescale(); m_bitmapCompatible.msw_rescale(); @@ -142,9 +239,9 @@ void PresetComboBox::msw_rescale() void PresetComboBox::fill_width_height() { // To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size, so - // set a bitmap's height to m_bitmapLock->GetHeight() and norm_icon_width to m_bitmapLock->GetWidth() - icon_height = m_bitmapLock.GetBmpHeight(); - norm_icon_width = m_bitmapLock.GetBmpWidth(); + // set a bitmap's height to m_bitmapCompatible->GetHeight() and norm_icon_width to m_bitmapCompatible->GetWidth() + icon_height = m_bitmapCompatible.GetBmpHeight(); + norm_icon_width = m_bitmapCompatible.GetBmpWidth(); /* It's supposed that standard size of an icon is 16px*16px for 100% scaled display. * So set sizes for solid_colored icons used for filament preset @@ -165,6 +262,110 @@ wxString PresetComboBox::separator(const std::string& label) return wxString::FromUTF8(separator_head()) + _(label) + wxString::FromUTF8(separator_tail()); } +wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, bool wide_icons, const std::string& main_icon_name, + bool is_compatible/* = true*/, bool is_system/* = false*/, bool is_single_bar/* = false*/, + std::string filament_rgb/* = ""*/, std::string extruder_rgb/* = ""*/) +{ + bitmap_key += ",h" + std::to_string(icon_height); + + wxBitmap* bmp = bitmap_cache().find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(is_compatible ? bitmap_cache().mkclear(norm_icon_width, icon_height) : m_bitmapIncompatible.bmp()); + + if (m_type == Preset::TYPE_FILAMENT) + { + unsigned char rgb[3]; + // Paint the color bars. + bitmap_cache().parse_color(filament_rgb, rgb); + bmps.emplace_back(bitmap_cache().mksolid(is_single_bar ? wide_icon_width : norm_icon_width, icon_height, rgb)); + if (!is_single_bar) { + bitmap_cache().parse_color(extruder_rgb, rgb); + bmps.emplace_back(bitmap_cache().mksolid(thin_icon_width, icon_height, rgb)); + } + // Paint a lock at the system presets. + bmps.emplace_back(bitmap_cache().mkclear(space_icon_width, icon_height)); + } + else + { + // Paint the color bars. + bmps.emplace_back(bitmap_cache().mkclear(thin_space_icon_width, icon_height)); + bmps.emplace_back(create_scaled_bitmap(main_icon_name)); + // Paint a lock at the system presets. + bmps.emplace_back(bitmap_cache().mkclear(wide_space_icon_width, icon_height)); + } + bmps.emplace_back(is_system ? create_scaled_bitmap("lock_closed") : bitmap_cache().mkclear(norm_icon_width, icon_height)); + bmp = bitmap_cache().insert(bitmap_key, bmps); + } + + return bmp; +} + +wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, const std::string& main_icon_name, const std::string& next_icon_name, + bool is_enabled/* = true*/, bool is_compatible/* = true*/, bool is_system/* = false*/) +{ + bitmap_key += ",h" + std::to_string(icon_height); + + wxBitmap* bmp = bitmap_cache().find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + bmps.emplace_back(m_type == Preset::TYPE_PRINTER ? create_scaled_bitmap(main_icon_name, this, 16, !is_enabled) : + is_compatible ? m_bitmapCompatible.bmp() : m_bitmapIncompatible.bmp()); + // Paint a lock at the system presets. + bmps.emplace_back(is_system ? create_scaled_bitmap(next_icon_name, this, 16, !is_enabled) : bitmap_cache().mkclear(norm_icon_width, icon_height)); + bmp = bitmap_cache().insert(bitmap_key, bmps); + } + + return bmp; +} + +bool PresetComboBox::is_selected_physical_printer() +{ + auto selected_item = this->GetSelection(); + auto marker = reinterpret_cast(this->GetClientData(selected_item)); + return marker == LABEL_ITEM_PHYSICAL_PRINTER; +} + +bool PresetComboBox::selection_is_changed_according_to_physical_printers() +{ + if (m_type != Preset::TYPE_PRINTER || !is_selected_physical_printer()) + return false; + + PhysicalPrinterCollection& physical_printers = m_preset_bundle->physical_printers; + + std::string selected_string = this->GetString(this->GetSelection()).ToUTF8().data(); + + std::string old_printer_full_name, old_printer_preset; + if (physical_printers.has_selection()) { + old_printer_full_name = physical_printers.get_selected_full_printer_name(); + old_printer_preset = physical_printers.get_selected_printer_preset_name(); + } + else + old_printer_preset = m_collection->get_edited_preset().name; + // Select related printer preset on the Printer Settings Tab + physical_printers.select_printer_by_name(selected_string); + std::string preset_name = physical_printers.get_selected_printer_preset_name(); + + // if new preset wasn't selected, there is no need to call update preset selection + if (old_printer_preset == preset_name) { + // we need just to update according Plater<->Tab PresetComboBox + if (dynamic_cast(this)!=nullptr) + wxGetApp().get_tab(m_type)->update_preset_choice(); + else if (dynamic_cast(this)!=nullptr) + wxGetApp().sidebar().update_presets(m_type); + return true; + } + + Tab* tab = wxGetApp().get_tab(Preset::TYPE_PRINTER); + if (tab) + tab->select_preset(preset_name, false, old_printer_full_name); + return true; +} + #ifdef __APPLE__ bool PresetComboBox::OnAddBitmap(const wxBitmap& bitmap) { @@ -245,25 +446,6 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) { this->SetSelection(this->m_last_selected); evt.StopPropagation(); - /* - if (marker == LABEL_ITEM_PHYSICAL_PRINTERS) - { - PhysicalPrinterDialog dlg(wxEmptyString); - if (dlg.ShowModal() == wxID_OK) - this->update(); - return; - } - if (marker >= LABEL_ITEM_WIZARD_PRINTERS) { - ConfigWizard::StartPage sp = ConfigWizard::SP_WELCOME; - switch (marker) { - case LABEL_ITEM_WIZARD_PRINTERS: sp = ConfigWizard::SP_PRINTERS; break; - case LABEL_ITEM_WIZARD_FILAMENTS: sp = ConfigWizard::SP_FILAMENTS; break; - case LABEL_ITEM_WIZARD_MATERIALS: sp = ConfigWizard::SP_MATERIALS; break; - default: break; - } - wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); }); - } - */ if (marker == LABEL_ITEM_WIZARD_PRINTERS) show_add_menu(); else @@ -372,13 +554,6 @@ PlaterPresetComboBox::~PlaterPresetComboBox() edit_btn->Destroy(); } -bool PlaterPresetComboBox::is_selected_physical_printer() -{ - auto selected_item = this->GetSelection(); - auto marker = reinterpret_cast(this->GetClientData(selected_item)); - return marker == LABEL_ITEM_PHYSICAL_PRINTER; -} - bool PlaterPresetComboBox::switch_to_tab() { Tab* tab = wxGetApp().get_tab(m_type); @@ -465,7 +640,7 @@ void PlaterPresetComboBox::update() { unsigned char rgb[3]; extruder_color = m_preset_bundle->printers.get_edited_preset().config.opt_string("extruder_colour", (unsigned int)m_extruder_idx); - if (!m_bitmap_cache->parse_color(extruder_color, rgb)) + if (!bitmap_cache().parse_color(extruder_color, rgb)) // Extruder color is not defined. extruder_color.clear(); selected_filament_preset = m_collection->find_preset(m_preset_bundle->filament_presets[m_extruder_idx]); @@ -499,64 +674,33 @@ void PlaterPresetComboBox::update() continue; std::string bitmap_key, filament_rgb, extruder_rgb; + std::string bitmap_type_name = bitmap_key = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; + bool single_bar = false; - if (m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA) - bitmap_key = "sla_printer"; - else if (m_type == Preset::TYPE_FILAMENT) + if (m_type == Preset::TYPE_FILAMENT) { // Assign an extruder color to the selected item if the extruder color is defined. filament_rgb = preset.config.opt_string("filament_colour", 0); extruder_rgb = (selected && !extruder_color.empty()) ? extruder_color : filament_rgb; single_bar = filament_rgb == extruder_rgb; - bitmap_key = single_bar ? filament_rgb : filament_rgb + extruder_rgb; + bitmap_key += single_bar ? filament_rgb : filament_rgb + extruder_rgb; } - wxBitmap main_bmp = create_scaled_bitmap(m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name); // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left // to the filament color image. if (wide_icons) bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; - bitmap_key += "-h" + std::to_string(icon_height); - wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(preset.is_compatible ? m_bitmap_cache->mkclear(norm_icon_width, icon_height) : m_bitmapIncompatible.bmp()); - - if (m_type == Preset::TYPE_FILAMENT) - { - unsigned char rgb[3]; - // Paint the color bars. - m_bitmap_cache->parse_color(filament_rgb, rgb); - bmps.emplace_back(m_bitmap_cache->mksolid(single_bar ? wide_icon_width : norm_icon_width, icon_height, rgb)); - if (!single_bar) { - m_bitmap_cache->parse_color(extruder_rgb, rgb); - bmps.emplace_back(m_bitmap_cache->mksolid(thin_icon_width, icon_height, rgb)); - } - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(space_icon_width, icon_height)); - } - else - { - // Paint the color bars. - bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); - bmps.emplace_back(main_bmp); - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); - } - bmps.emplace_back((preset.is_system || preset.is_default) ? m_bitmapLock.bmp() : m_bitmap_cache->mkclear(norm_icon_width, icon_height)); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } + wxBitmap* bmp = get_bmp(bitmap_key, wide_icons, bitmap_type_name, + preset.is_compatible, preset.is_system || preset.is_default, + single_bar, filament_rgb, extruder_rgb); + assert(bmp); const std::string name = preset.alias.empty() ? preset.name : preset.alias; if (preset.is_default || preset.is_system) { - Append(wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), - !bmp ? main_bmp : *bmp); + Append(wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); if (is_selected || // just in case: mark selected_preset_item as a first added element selected_preset_item == INT_MAX) { @@ -595,59 +739,26 @@ void PlaterPresetComboBox::update() const PhysicalPrinterCollection& ph_printers = m_preset_bundle->physical_printers; for (PhysicalPrinterCollection::ConstIterator it = ph_printers.begin(); it != ph_printers.end(); ++it) { - std::string bitmap_key = it->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - if (wide_icons) - bitmap_key += "wide,"; - bitmap_key += "-h" + std::to_string(icon_height); - - wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; + for (const std::string preset_name : it->get_preset_names()) { + Preset* preset = m_collection->find_preset(preset_name); + if (!preset) + continue; + std::string main_icon_name, bitmap_key = main_icon_name = preset->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(m_bitmap_cache->mkclear(norm_icon_width, icon_height)); - // Paint the color bars. - bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); - bmps.emplace_back(create_scaled_bitmap(it->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name)); - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width+norm_icon_width, icon_height)); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } + bitmap_key += ",cmpt"; + bitmap_key += ",nsyst"; - set_label_marker(Append(wxString::FromUTF8((it->full_name).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); - if (ph_printers.has_selection() && it->name == ph_printers.get_selected_printer_name() || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; + wxBitmap* bmp = get_bmp(bitmap_key, wide_icons, main_icon_name); + assert(bmp); + + set_label_marker(Append(wxString::FromUTF8((it->get_full_name(preset_name) + (preset->is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); + if (ph_printers.is_selected(it, preset_name) || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + } } } - -/* - // add LABEL_ITEM_PHYSICAL_PRINTERS - std::string bitmap_key; - if (wide_icons) - bitmap_key += "wide,"; - bitmap_key += "edit_preset_list"; - bitmap_key += "-h" + std::to_string(icon_height); - - wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(m_bitmap_cache->mkclear(norm_icon_width, icon_height)); - // Paint the color bars. - bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); - bmps.emplace_back(create_scaled_bitmap("printer")); - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); - bmps.emplace_back(create_scaled_bitmap("edit_uni")); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } - set_label_marker(Append(separator(L("Add physical printer")), *bmp), LABEL_ITEM_PHYSICAL_PRINTERS); -*/ } if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) { @@ -657,23 +768,10 @@ void PlaterPresetComboBox::update() if (wide_icons) bitmap_key += "wide,"; bitmap_key += "edit_preset_list"; - bitmap_key += "-h" + std::to_string(icon_height); - wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars.update_plater_ui - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(m_bitmap_cache->mkclear(norm_icon_width, icon_height)); - // Paint the color bars. - bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); - bmps.emplace_back(create_scaled_bitmap(m_main_bitmap_name)); - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); - bmps.emplace_back(create_scaled_bitmap("edit_uni")); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } + wxBitmap* bmp = get_bmp(bitmap_key, wide_icons, "edit_uni"); + assert(bmp); + if (m_type == Preset::TYPE_SLA_MATERIAL) set_label_marker(Append(separator(L("Add/Remove materials")), *bmp), LABEL_ITEM_WIZARD_MATERIALS); else @@ -731,8 +829,10 @@ TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type) update(); }); } - else if (on_selection_changed && (m_last_selected != selected_item || m_collection->current_is_dirty()) ) + else if (on_selection_changed && (m_last_selected != selected_item || m_collection->current_is_dirty()) ) { + m_last_selected = selected_item; on_selection_changed(selected_item); + } evt.StopPropagation(); }); @@ -754,7 +854,12 @@ void TabPresetComboBox::update() if (!presets.front().is_visible) set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); int idx_selected = m_collection->get_selected_idx(); - for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) { + + std::string sel_preset_name = m_preset_bundle->physical_printers.get_selected_printer_preset_name(); + PrinterTechnology proper_pt = (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) ? + m_collection->find_preset(sel_preset_name)->printer_technology() : ptAny; + for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) + { const Preset& preset = presets[i]; if (!preset.is_visible || (!show_incompatible && !preset.is_compatible && i != idx_selected)) continue; @@ -762,14 +867,12 @@ void TabPresetComboBox::update() // marker used for disable incompatible printer models for the selected physical printer bool is_enabled = true; // check this value just for printer presets, when physical printer is selected - if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) { - is_enabled = m_enable_all ? true : - preset.name == m_preset_bundle->physical_printers.get_selected_printer_preset_name()/* || - preset.config.opt_string("printer_model") == m_preset_bundle->physical_printers.get_selected_printer_model()*/; - } + if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) + is_enabled = m_enable_all ? true : preset.printer_technology() == proper_pt;//m_preset_bundle->physical_printers.get_selected_printer_technology(); std::string bitmap_key = "tab"; - wxBitmap main_bmp = create_scaled_bitmap(m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name, this, 16, !is_enabled); + std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; + if (m_type == Preset::TYPE_PRINTER) { bitmap_key += "_printer"; if (preset.printer_technology() == ptSLA) @@ -779,20 +882,12 @@ void TabPresetComboBox::update() } bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; - bitmap_key += "-h" + std::to_string(icon_height); - wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - bmps.emplace_back(m_type == Preset::TYPE_PRINTER ? main_bmp : preset.is_compatible ? m_bitmapCompatible.bmp() : m_bitmapIncompatible.bmp()); - // Paint a lock at the system presets. - bmps.emplace_back((preset.is_system || preset.is_default) ? (is_enabled ? m_bitmapLock.bmp() : m_bitmapLockDisabled.bmp()) : m_bitmap_cache->mkclear(norm_icon_width, icon_height)); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } + wxBitmap* bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default); + assert(bmp); if (preset.is_default || preset.is_system) { - int item_id = Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), !bmp ? main_bmp : *bmp); + int item_id = Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); if (!is_enabled) set_label_marker(item_id, LABEL_ITEM_DISABLED); if (i == idx_selected || @@ -824,18 +919,38 @@ void TabPresetComboBox::update() selected_preset_item = GetCount() - 1; } } - if (m_type == Preset::TYPE_PRINTER) { - std::string bitmap_key = "edit_preset_list"; - bitmap_key += "-h" + std::to_string(icon_height); - wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - bmps.emplace_back(create_scaled_bitmap(m_main_bitmap_name, this)); - bmps.emplace_back(create_scaled_bitmap("edit_uni", this)); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); + if (m_type == Preset::TYPE_PRINTER) + { + // add Physical printers, if any exists + if (!m_preset_bundle->physical_printers.empty()) { + set_label_marker(Append(separator(L("Physical printers")), wxNullBitmap)); + const PhysicalPrinterCollection& ph_printers = m_preset_bundle->physical_printers; + + for (PhysicalPrinterCollection::ConstIterator it = ph_printers.begin(); it != ph_printers.end(); ++it) { + for (const std::string preset_name : it->get_preset_names()) { + Preset* preset = m_collection->find_preset(preset_name); + if (!preset) + continue; + std::string main_icon_name = preset->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; + std::string bitmap_key = main_icon_name + ",nsyst"; + + wxBitmap* bmp = get_bmp(bitmap_key, main_icon_name, "", true, true, false); + assert(bmp); + + set_label_marker(Append(wxString::FromUTF8((it->get_full_name(preset_name) + (preset->is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); + if (ph_printers.is_selected(it, preset_name) || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + } + } } + + // add "Add/Remove printers" item + wxBitmap* bmp = get_bmp("edit_preset_list", m_main_bitmap_name, "edit_uni", true, true, true); + assert(bmp); + set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); } @@ -869,11 +984,26 @@ void TabPresetComboBox::update_dirty() // 2) Update the labels. wxWindowUpdateLocker noUpdates(this); for (unsigned int ui_id = 0; ui_id < GetCount(); ++ui_id) { + auto marker = reinterpret_cast(this->GetClientData(ui_id)); + if (marker >= LABEL_ITEM_MARKER) + continue; + std::string old_label = GetString(ui_id).utf8_str().data(); std::string preset_name = Preset::remove_suffix_modified(old_label); + std::string ph_printer_name; + + if (marker == LABEL_ITEM_PHYSICAL_PRINTER) { + ph_printer_name = PhysicalPrinter::get_short_name(preset_name); + preset_name = PhysicalPrinter::get_preset_name(preset_name); + } + const Preset* preset = m_collection->find_preset(preset_name, false); if (preset) { std::string new_label = preset->is_dirty ? preset->name + Preset::suffix_modified() : preset->name; + + if (marker == LABEL_ITEM_PHYSICAL_PRINTER) + new_label = ph_printer_name + PhysicalPrinter::separator() + new_label; + if (old_label != new_label) SetString(ui_id, wxString::FromUTF8(new_label.c_str())); } @@ -885,6 +1015,12 @@ void TabPresetComboBox::update_dirty() #endif /* __APPLE __ */ } +void TabPresetComboBox::update_physical_printers( const std::string& preset_name) +{ + if (m_type == Preset::TYPE_PRINTER && update_ph_printers) + update_ph_printers(preset_name); +} + //------------------------------------------ // PresetForPrinter @@ -900,9 +1036,7 @@ PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, const std::str m_delete_preset_btn->SetToolTip(_L("Delete this preset from this printer device")); m_delete_preset_btn->Bind(wxEVT_BUTTON, &PresetForPrinter::DeletePreset, this); - m_presets_list = new TabPresetComboBox(parent, Preset::TYPE_PRINTER); - if (preset_name.empty()) - m_presets_list->set_enable_all(); + m_presets_list = new PresetComboBox(parent, Preset::TYPE_PRINTER); m_presets_list->set_selection_changed_function([this](int selection) { std::string selected_string = Preset::remove_suffix_modified(m_presets_list->GetString(selection).ToUTF8().data()); @@ -922,8 +1056,7 @@ PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, const std::str update_full_printer_name(); }); - m_presets_list->update(); - m_presets_list->SetStringSelection(from_u8(preset_name)); + m_presets_list->update(preset_name); m_info_line = new wxStaticText(parent, wxID_ANY, _L("This printer will be shown in the presets list as") + ":"); @@ -997,11 +1130,10 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) SetFont(wxGetApp().normal_font()); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - if (printer_name.IsEmpty()) { - printer_name = _L("My Printer Device"); - // if printer_name is empty it means that new printer is created, so enable all items in the preset list - m_presets.emplace_back(new PresetForPrinter(this, "")); - } + m_default_name = _L("My Printer Device"); + + if (printer_name.IsEmpty()) + printer_name = m_default_name; else { std::string full_name = into_u8(printer_name); printer_name = from_u8(PhysicalPrinter::get_short_name(full_name)); @@ -1022,6 +1154,8 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) if (!printer) { const Preset& preset = wxGetApp().preset_bundle->printers.get_edited_preset(); printer = new PhysicalPrinter(into_u8(printer_name), preset); + // if printer_name is empty it means that new printer is created, so enable all items in the preset list + m_presets.emplace_back(new PresetForPrinter(this, preset.name)); } else { @@ -1275,7 +1409,11 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) { wxString printer_name = m_printer_name->GetValue(); if (printer_name.IsEmpty()) { - show_error(this, _L("The supplied name is empty. It can't be saved.")); + warning_catcher(this, _L("The supplied name is empty. It can't be saved.")); + return; + } + if (printer_name == m_default_name) { + warning_catcher(this, _L("You should to change a name of your printer device. It can't be saved.")); return; } @@ -1311,9 +1449,10 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) repeatable_presets += "\n"; wxString msg_text = from_u8((boost::format(_u8L("Next printer preset(s) is(are) duplicated:%1%" - "It(they) will be added just once for the printer \"%2%\".")) % repeatable_presets % printer_name).str()); - wxMessageDialog dialog(nullptr, msg_text, _L("Infornation"), wxICON_INFORMATION | wxOK); - dialog.ShowModal(); + "Should I add it(they) just once for the printer \"%2%\" and close the Editing Dialog?")) % repeatable_presets % printer_name).str()); + wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); + if (dialog.ShowModal() == wxID_NO) + return; } std::string renamed_from; @@ -1344,8 +1483,7 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) void PhysicalPrinterDialog::AddPreset(wxEvent& event) { - // if printer_name is empty it means that new printer is created, so enable all items in the preset list - m_presets.emplace_back(new PresetForPrinter(this, "")); + m_presets.emplace_back(new PresetForPrinter(this)); // enable DELETE button for the first preset, if was disabled m_presets.front()->EnableDeleteBtn(); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index c818b6b91..1f5ef026d 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -10,6 +10,7 @@ #include "libslic3r/Preset.hpp" #include "wxExtensions.hpp" #include "GUI_Utils.hpp" +#include "BitmapCache.hpp" class wxString; class wxTextCtrl; @@ -21,8 +22,6 @@ namespace Slic3r { namespace GUI { -class BitmapCache; - // --------------------------------- // *** PresetComboBox *** @@ -49,11 +48,22 @@ public: void set_label_marker(int item, LabelItemType label_item_type = LABEL_ITEM_MARKER); - virtual void update() {}; + void set_selection_changed_function(std::function sel_changed) { on_selection_changed = sel_changed; } + + bool is_selected_physical_printer(); + + // Return true, if physical printer was selected + // and next internal selection was accomplished + bool selection_is_changed_according_to_physical_printers(); + + void update(const std::string& select_preset); + + virtual void update(){}; virtual void msw_rescale(); protected: typedef std::size_t Marker; + std::function on_selection_changed { nullptr }; Preset::Type m_type; std::string m_main_bitmap_name; @@ -61,16 +71,16 @@ protected: PresetBundle* m_preset_bundle {nullptr}; PresetCollection* m_collection {nullptr}; - // Caching color bitmaps for the filament combo box. - BitmapCache* m_bitmap_cache {nullptr}; + // Caching bitmaps for the all bitmaps, used in preset comboboxes + static BitmapCache& bitmap_cache() { + static BitmapCache bmps; + return bmps; + } + // Indicator, that the preset is compatible with the selected printer. ScalableBitmap m_bitmapCompatible; // Indicator, that the preset is NOT compatible with the selected printer. ScalableBitmap m_bitmapIncompatible; - // Indicator, that the preset is system and not modified. - ScalableBitmap m_bitmapLock; - // Disabled analogue of the m_bitmapLock . - ScalableBitmap m_bitmapLockDisabled; int m_last_selected; int m_em_unit; @@ -93,6 +103,13 @@ protected: #endif // __linux__ static wxString separator(const std::string& label); + wxBitmap* get_bmp( std::string bitmap_key, bool wide_icons, const std::string& main_icon_name, + bool is_compatible = true, bool is_system = false, bool is_single_bar = false, + std::string filament_rgb = "", std::string extruder_rgb = ""); + + wxBitmap* get_bmp( std::string bitmap_key, const std::string& main_icon_name, const std::string& next_icon_name, + bool is_enabled = true, bool is_compatible = true, bool is_system = false); + #ifdef __APPLE__ /* For PresetComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina * (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean @@ -128,7 +145,6 @@ public: void set_extruder_idx(const int extr_idx) { m_extruder_idx = extr_idx; } int get_extruder_idx() const { return m_extruder_idx; } - bool is_selected_physical_printer(); bool switch_to_tab(); void show_add_menu(); void show_edit_menu(); @@ -149,7 +165,8 @@ class TabPresetComboBox : public PresetComboBox { bool show_incompatible {false}; bool m_enable_all {false}; - std::function on_selection_changed { nullptr }; + + std::function update_ph_printers { nullptr }; public: TabPresetComboBox(wxWindow *parent, Preset::Type preset_type); @@ -157,12 +174,15 @@ public: void set_show_incompatible_presets(bool show_incompatible_presets) { show_incompatible = show_incompatible_presets; } + void set_update_physical_printers_function(std::function update_fn) { + update_ph_printers = update_fn; + } void update() override; void update_dirty(); + void update_physical_printers(const std::string& preset_name); void msw_rescale() override; - void set_selection_changed_function(std::function sel_changed) { on_selection_changed = sel_changed; } void set_enable_all(bool enable=true) { m_enable_all = enable; } }; @@ -176,7 +196,7 @@ class PresetForPrinter { PhysicalPrinterDialog* m_parent { nullptr }; - TabPresetComboBox* m_presets_list { nullptr }; + PresetComboBox* m_presets_list { nullptr }; ScalableButton* m_delete_preset_btn { nullptr }; wxStaticText* m_info_line { nullptr }; wxStaticText* m_full_printer_name { nullptr }; @@ -186,7 +206,7 @@ class PresetForPrinter void DeletePreset(wxEvent& event); public: - PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name); + PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name = ""); ~PresetForPrinter(); wxBoxSizer* sizer() { return m_sizer; } @@ -208,6 +228,7 @@ class ConfigOptionsGroup; class PhysicalPrinterDialog : public DPIDialog { PhysicalPrinter m_printer; + wxString m_default_name; DynamicPrintConfig* m_config { nullptr }; wxTextCtrl* m_printer_name { nullptr }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 595283e98..dbea9b3f5 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -162,10 +162,20 @@ void Tab::create_preset_tab() // preset chooser m_presets_choice = new TabPresetComboBox(panel, m_type); m_presets_choice->set_selection_changed_function([this](int selection) { - std::string selected_string = m_presets_choice->GetString(selection).ToUTF8().data(); - update_physical_printers(selected_string); - // select preset - select_preset(selected_string); + if (!m_presets_choice->selection_is_changed_according_to_physical_printers()) + { + // for the printer presets set callback for the updating of the physical printers + if (m_type == Preset::TYPE_PRINTER) + m_presets_choice->set_update_physical_printers_function([this](std::string preset_name) { update_physical_printers(preset_name);}); + + // select preset + select_preset(m_presets_choice->GetString(selection).ToUTF8().data()); + + // Disable callback for the updating of the physical printers to avoid a case, + // when select_preset is called from the others than this place + if (m_type == Preset::TYPE_PRINTER) + m_presets_choice->set_update_physical_printers_function(nullptr); + } }); auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); @@ -764,9 +774,11 @@ void Tab::update_tab_ui() void Tab::update_physical_printers(std::string preset_name) { - if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) + PhysicalPrinterCollection& physical_printers = wxGetApp().preset_bundle->physical_printers; + if (physical_printers.has_selection() && + Preset::remove_suffix_modified(preset_name) != physical_printers.get_selected_printer_preset_name()) { - std::string printer_name = m_preset_bundle->physical_printers.get_selected_full_printer_name(); + std::string printer_name = physical_printers.get_selected_full_printer_name(); wxString msg_text = from_u8((boost::format(_u8L("You have selected physical printer \"%1%\".")) % printer_name).str()); msg_text += "\n\n" + _L("Would you like to change related preset for this printer?") + "\n\n" + _L("Select YES if you want to change related preset for this printer \n" @@ -780,12 +792,14 @@ void Tab::update_physical_printers(std::string preset_name) Preset& edited_preset = m_presets->get_edited_preset(); if (preset->name == edited_preset.name) preset = &edited_preset; - m_preset_bundle->physical_printers.get_selected_printer().update_from_preset(*preset); + physical_printers.get_selected_printer().update_from_preset(*preset); + physical_printers.select_printer_by_name(physical_printers.get_selected_printer().get_full_name(preset_name)); + return; } - else - // unselect physical printer, if it was selected - m_preset_bundle->physical_printers.unselect_printer(); } + + // unselect physical printer, if it was selected + m_preset_bundle->physical_printers.unselect_printer(); } // Load a provied DynamicConfig into the tab, modifying the active preset. @@ -2148,11 +2162,10 @@ void TabPrinter::build_fff() line.append_widget(serial_test); optgroup->append_line(line); } -#endif optgroup = page->new_optgroup(L("Print Host upload")); build_printhost(optgroup.get()); - +#endif optgroup = page->new_optgroup(L("Firmware")); optgroup->append_single_option_line("gcode_flavor"); optgroup->append_single_option_line("silent_mode"); @@ -2310,8 +2323,10 @@ void TabPrinter::build_sla() optgroup->append_single_option_line("min_initial_exposure_time"); optgroup->append_single_option_line("max_initial_exposure_time"); + /* optgroup = page->new_optgroup(L("Print Host upload")); build_printhost(optgroup.get()); + */ const int notes_field_height = 25; // 250 @@ -2699,11 +2714,13 @@ void TabPrinter::update_fff() m_serial_test_btn->Disable(); } + /* { std::unique_ptr host(PrintHost::get_print_host(m_config)); m_print_host_test_btn->Enable(!m_config->opt_string("print_host").empty() && host->can_test()); m_printhost_browse_btn->Enable(host->has_auto_discovery()); } + */ bool have_multiple_extruders = m_extruders_count > 1; get_field("toolchange_gcode")->toggle(have_multiple_extruders); @@ -2942,10 +2959,15 @@ void Tab::update_page_tree_visibility() } +void Tab::update_preset_choice() +{ + m_presets_choice->update(); +} + // Called by the UI combo box when the user switches profiles, and also to delete the current profile. // Select a preset by a name.If !defined(name), then the default preset is selected. // If the current profile is modified, user is asked to save the changes. -void Tab::select_preset(std::string preset_name, bool delete_current) +void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, const std::string& last_selected_ph_printer_name/* =""*/) { if (preset_name.empty()) { if (delete_current) { @@ -3054,9 +3076,22 @@ void Tab::select_preset(std::string preset_name, bool delete_current) if (canceled) { update_tab_ui(); + /* // unselect physical printer selection to the correct synchronization of the printer presets between Tab and Plater if (m_type == Preset::TYPE_PRINTER) m_preset_bundle->physical_printers.unselect_printer(); + */ + + + // Check if preset really was changed. + // If preset selection was canceled and previously was selected physical printer, we should select it back + if (m_type == Preset::TYPE_PRINTER && !last_selected_ph_printer_name.empty()) { + if (m_presets->get_edited_preset().name == PhysicalPrinter::get_preset_name(last_selected_ph_printer_name)) { + m_preset_bundle->physical_printers.select_printer_by_name(last_selected_ph_printer_name); + m_presets_choice->update(); + } + } + // Trigger the on_presets_changed event so that we also restore the previous value in the plater selector, // if this action was initiated from the plater. on_presets_changed(); @@ -3098,6 +3133,10 @@ void Tab::select_preset(std::string preset_name, bool delete_current) else if (printer_technology == ptSLA && m_dependent_tabs.front() != Preset::Type::TYPE_SLA_PRINT) m_dependent_tabs = { Preset::Type::TYPE_SLA_PRINT, Preset::Type::TYPE_SLA_MATERIAL }; } + + //update physical printer's related printer preset if it's needed + m_presets_choice->update_physical_printers(preset_name); + load_current_preset(); } } @@ -3295,7 +3334,7 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); //update physical printer's related printer preset if it's needed - update_physical_printers(name); + m_presets_choice->update_physical_printers(name); // Add the new item into the UI component, remove dirty flags and activate the saved item. update_tab_ui(); // Update the selection boxes at the plater. diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 69720ff65..82df31b59 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -275,8 +275,9 @@ public: void load_current_preset(); void rebuild_page_tree(); void update_page_tree_visibility(); - // Select a new preset, possibly delete the current one. - void select_preset(std::string preset_name = "", bool delete_current = false); + void update_preset_choice(); + // Select a new preset, possibly delete the current one. + void select_preset(std::string preset_name = "", bool delete_current = false, const std::string& last_selected_ph_printer_name = ""); bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, const std::string& new_printer_name = ""); bool may_switch_to_SLA_preset(); From 72ec414f1e4492d67465efe368a4a224a21ed7eb Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 20 Jul 2020 14:56:09 +0200 Subject: [PATCH 13/70] PhysicalPrinters improvements: - Added possibility to correct delete presets considering with the physical printers - Smart switching to the printer preset if physical printer was selected --- src/libslic3r/Preset.cpp | 55 +++++++++- src/libslic3r/Preset.hpp | 9 +- src/slic3r/GUI/PresetComboBoxes.cpp | 154 ++++++++++++++++++++++++---- src/slic3r/GUI/PresetComboBoxes.hpp | 32 +++++- src/slic3r/GUI/Tab.cpp | 141 ++++++++++++++----------- src/slic3r/GUI/Tab.hpp | 2 +- 6 files changed, 302 insertions(+), 91 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index ad7615f6d..a55dc24ca 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1436,6 +1436,11 @@ bool PhysicalPrinter::add_preset(const std::string& preset_name) return preset_names.emplace(preset_name).second; } +bool PhysicalPrinter::delete_preset(const std::string& preset_name) +{ + return preset_names.erase(preset_name) > 0; +} + PhysicalPrinter::PhysicalPrinter(const std::string& name, const Preset& preset) : name(name) { @@ -1521,7 +1526,6 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const } m_printers.insert(m_printers.end(), std::make_move_iterator(printers_loaded.begin()), std::make_move_iterator(printers_loaded.end())); std::sort(m_printers.begin(), m_printers.end()); -//! this->select_preset(first_visible_idx()); if (!errors_cummulative.empty()) throw std::runtime_error(errors_cummulative); } @@ -1542,8 +1546,11 @@ std::string PhysicalPrinterCollection::path_from_name(const std::string& new_nam return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); } -void PhysicalPrinterCollection::save_printer(const PhysicalPrinter& edited_printer, const std::string& renamed_from) +void PhysicalPrinterCollection::save_printer(PhysicalPrinter& edited_printer, const std::string& renamed_from/* = ""*/) { + // controll and update preset_names in edited_printer config + edited_printer.update_preset_names_in_config(); + std::string name = renamed_from.empty() ? edited_printer.name : renamed_from; // 1) Find the printer with a new_name or create a new one, // initialize it with the edited config. @@ -1605,6 +1612,33 @@ bool PhysicalPrinterCollection::delete_selected_printer() return true; } +bool PhysicalPrinterCollection::delete_preset_from_printers( const std::string& preset_name, bool first_check /*=true*/) +{ + if (first_check) { + for (auto printer: m_printers) + if (printer.preset_names.size()==1 && *printer.preset_names.begin() == preset_name) + return false; + } + + std::vector printers_for_delete; + for (PhysicalPrinter& printer : m_printers) + if (printer.preset_names.size() == 1 && *printer.preset_names.begin() == preset_name) + printers_for_delete.emplace_back(printer.name); + else if (printer.delete_preset(preset_name)) { + if (printer.name == get_selected_printer_name() && + preset_name == get_selected_printer_preset_name()) + select_printer(printer); + save_printer(printer); + } + + if (!printers_for_delete.empty()) { + for (const std::string& printer_name : printers_for_delete) + delete_printer(printer_name); + unselect_printer(); + } + return true; +} + std::string PhysicalPrinterCollection::get_selected_full_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_full_name(m_selected_preset); @@ -1625,6 +1659,23 @@ PhysicalPrinter& PhysicalPrinterCollection::select_printer_by_name(const std::st return *it; } +PhysicalPrinter& PhysicalPrinterCollection::select_printer(const std::string& printer_name) +{ + auto it = this->find_printer_internal(printer_name); + assert(it != m_printers.end()); + + // update idx_selected + m_idx_selected = it - m_printers.begin(); + // update name of the currently selected preset + m_selected_preset = *it->preset_names.begin(); + return *it; +} + +PhysicalPrinter& PhysicalPrinterCollection::select_printer(const PhysicalPrinter& printer) +{ + return select_printer(printer.name); +} + bool PhysicalPrinterCollection::has_selection() const { return m_idx_selected != size_t(-1); diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index d30ea7059..e8afb0f6f 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -569,6 +569,7 @@ public: // add preset to the preset_names // return false, if preset with this name is already exist in the set bool add_preset(const std::string& preset_name); + bool delete_preset(const std::string& preset_name); void reset_presets(); // Return a printer technology, return ptFFF if the printer technology is not set. @@ -629,7 +630,7 @@ public: // Save the printer under a new name. If the name is different from the old one, // a new printer is stored into the list of printers. // New printer is activated. - void save_printer(const PhysicalPrinter& printer, const std::string& renamed_from); + void save_printer(PhysicalPrinter& printer, const std::string& renamed_from = ""); // Delete the current preset, activate the first visible preset. // returns true if the preset was deleted successfully. @@ -637,6 +638,10 @@ public: // Delete the selected preset // returns true if the preset was deleted successfully. bool delete_selected_printer(); + // Delete preset_name preset from all printers: + // If there is last preset for the printer and first_check== false, then delete this printer + // returns true if all presets were deleted successfully. + bool delete_preset_from_printers(const std::string& preset_name, bool first_check = true); // Return the selected preset, without the user modifications applied. PhysicalPrinter& get_selected_printer() { return m_printers[m_idx_selected]; } @@ -659,6 +664,8 @@ public: // select printer with name and return reference on it PhysicalPrinter& select_printer_by_name(const std::string& full_name); + PhysicalPrinter& select_printer(const std::string &printer_name); + PhysicalPrinter& select_printer(const PhysicalPrinter& printer); bool has_selection() const; void unselect_printer() ; bool is_selected(ConstIterator it, const std::string &preset_name) const; diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index e08cf101d..bc1f48dd6 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -357,6 +357,8 @@ bool PresetComboBox::selection_is_changed_according_to_physical_printers() wxGetApp().get_tab(m_type)->update_preset_choice(); else if (dynamic_cast(this)!=nullptr) wxGetApp().sidebar().update_presets(m_type); + + this->update(); return true; } @@ -614,6 +616,8 @@ void PlaterPresetComboBox::show_edit_menu() return; m_preset_bundle->physical_printers.delete_selected_printer(); + + wxGetApp().get_tab(m_type)->update_preset_choice(); update(); }, "cross", menu, []() { return true; }, wxGetApp().plater()); @@ -855,9 +859,16 @@ void TabPresetComboBox::update() set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); int idx_selected = m_collection->get_selected_idx(); - std::string sel_preset_name = m_preset_bundle->physical_printers.get_selected_printer_preset_name(); - PrinterTechnology proper_pt = (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) ? - m_collection->find_preset(sel_preset_name)->printer_technology() : ptAny; + PrinterTechnology proper_pt = ptAny; + if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) { + std::string sel_preset_name = m_preset_bundle->physical_printers.get_selected_printer_preset_name(); + Preset* preset = m_collection->find_preset(sel_preset_name); + if (preset) + proper_pt = preset->printer_technology(); + else + m_preset_bundle->physical_printers.unselect_printer(); + } + for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) { const Preset& preset = presets[i]; @@ -868,7 +879,7 @@ void TabPresetComboBox::update() bool is_enabled = true; // check this value just for printer presets, when physical printer is selected if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) - is_enabled = m_enable_all ? true : preset.printer_technology() == proper_pt;//m_preset_bundle->physical_printers.get_selected_printer_technology(); + is_enabled = m_enable_all ? true : preset.printer_technology() == proper_pt; std::string bitmap_key = "tab"; std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; @@ -1017,8 +1028,49 @@ void TabPresetComboBox::update_dirty() void TabPresetComboBox::update_physical_printers( const std::string& preset_name) { - if (m_type == Preset::TYPE_PRINTER && update_ph_printers) - update_ph_printers(preset_name); + if (m_type != Preset::TYPE_PRINTER || !m_allow_to_update_physical_printers) + return; + + m_allow_to_update_physical_printers = false; + + PhysicalPrinterCollection& physical_printers = m_preset_bundle->physical_printers; + if (!physical_printers.has_selection()) + return; + + std::string printer_preset_name = physical_printers.get_selected_printer_preset_name(); + + if (Preset::remove_suffix_modified(preset_name) == printer_preset_name) { + if (!this->is_selected_physical_printer()) + physical_printers.unselect_printer(); + } + else + { + ChangePresetForPhysicalPrinterDialog dlg(Preset::remove_suffix_modified(preset_name)); + if(dlg.ShowModal() == wxID_OK) + { + if (dlg.m_selection == ChangePresetForPhysicalPrinterDialog::Switch) + // unselect physical printer, if it was selected + m_preset_bundle->physical_printers.unselect_printer(); + else + { + PhysicalPrinter printer = physical_printers.get_selected_printer(); + + if (dlg.m_selection == ChangePresetForPhysicalPrinterDialog::ChangePreset) + printer.delete_preset(printer_preset_name); + + if (printer.add_preset(preset_name)) + physical_printers.save_printer(printer); + else { + wxMessageDialog dialog(nullptr, _L("This preset is already exist for this physical printer. Please, select another one."), _L("Information"), wxICON_INFORMATION | wxOK); + dialog.ShowModal(); + } + + physical_printers.select_printer_by_name(printer.get_full_name(preset_name)); + } + } + else + wxGetApp().get_tab(Preset::TYPE_PRINTER)->select_preset(printer_preset_name); + } } @@ -1123,7 +1175,6 @@ void PresetForPrinter::msw_rescale() // PhysicalPrinterDialog //------------------------------------------ - PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) : DPIDialog(NULL, wxID_ANY, _L("Physical Printer"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { @@ -1427,9 +1478,6 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) if (dialog.ShowModal() == wxID_NO) return; - - // Remove the printer from the list. - printers.delete_printer(into_u8(printer_name)); } std::set repeat_presets; @@ -1438,8 +1486,6 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) if (!m_printer.add_preset(preset->get_preset_name())) repeat_presets.emplace(preset->get_preset_name()); } - // update preset_names in printer config - m_printer.update_preset_names_in_config(); if (!repeat_presets.empty()) { @@ -1467,16 +1513,10 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) // save new physical printer printers.save_printer(m_printer, renamed_from); - // update selection on the tab only when it was changed - /* - if (m_printer.get_preset_name() != wxGetApp().preset_bundle->printers.get_selected_preset_name()) { - Tab* tab = wxGetApp().get_tab(Preset::TYPE_PRINTER); - if (tab) { - wxString preset_name = m_printer_presets->GetString(m_printer_presets->GetSelection()); - tab->select_preset(into_u8(preset_name)); - } - } - */ + printers.select_printer(m_printer); + + // refresh preset list on Printer Settings Tab + wxGetApp().get_tab(Preset::TYPE_PRINTER)->update_preset_choice(); event.Skip(); } @@ -1520,4 +1560,74 @@ void PhysicalPrinterDialog::DeletePreset(PresetForPrinter* preset_for_printer) } +//----------------------------------------------- +// ChangePresetForPhysicalPrinterDialog +//----------------------------------------------- + +ChangePresetForPhysicalPrinterDialog::ChangePresetForPhysicalPrinterDialog(const std::string& preset_name) + : DPIDialog(nullptr, wxID_ANY, _L("Warning"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING/* | wxRESIZE_BORDER*/) +{ + SetFont(wxGetApp().normal_font()); + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + + PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; + std::string printer_name = printers.get_selected_printer_name(); + std::string old_preset_name = printers.get_selected_printer_preset_name(); + + wxString msg_text = from_u8((boost::format(_u8L("You have selected physical printer \"%1%\"\n" + "with related printer preset \"%2%\"")) % + printer_name % old_preset_name).str()); + wxStaticText* label_top = new wxStaticText(this, wxID_ANY, msg_text); + label_top->SetFont(wxGetApp().bold_font()); + + wxString choices[] = { from_u8((boost::format(_u8L("Just switch to \"%1%\"")) % preset_name).str()), + from_u8((boost::format(_u8L("Change \"%1%\" to \"%2%\" for this physical printer")) % old_preset_name % preset_name).str()), + from_u8((boost::format(_u8L("Add \"%1%\" as a next preset for the the physical printer")) % preset_name).str()) }; + + wxRadioBox* selection_type_box = new wxRadioBox(this, wxID_ANY, _L("What would you like to do?"), wxDefaultPosition, wxDefaultSize, WXSIZEOF(choices), choices, + 3, wxRA_SPECIFY_ROWS); + selection_type_box->SetFont(wxGetApp().normal_font()); + selection_type_box->SetSelection(0); + + selection_type_box->Bind(wxEVT_RADIOBOX, [this](wxCommandEvent& e) { + int selection = e.GetSelection(); + m_selection = (SelectionType)selection; + }); + + auto radio_sizer = new wxBoxSizer(wxHORIZONTAL); + radio_sizer->Add(selection_type_box, 1, wxALIGN_CENTER_VERTICAL); + + wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); + wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); + btnOK->Bind(wxEVT_BUTTON, &ChangePresetForPhysicalPrinterDialog::OnOK, this); + + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + topSizer->Add(label_top, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); + topSizer->Add(radio_sizer, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); + topSizer->Add(btns, 0, wxEXPAND | wxALL, BORDER_W); + + SetSizer(topSizer); + topSizer->SetSizeHints(this); +} + +void ChangePresetForPhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) +{ + const int& em = em_unit(); + + msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); + + const wxSize& size = wxSize(45 * em, 35 * em); + SetMinSize(size); + + Fit(); + Refresh(); +} + +void ChangePresetForPhysicalPrinterDialog::OnOK(wxEvent& event) +{ + event.Skip(); +} + + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 1f5ef026d..1cea97e41 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -166,7 +166,7 @@ class TabPresetComboBox : public PresetComboBox bool show_incompatible {false}; bool m_enable_all {false}; - std::function update_ph_printers { nullptr }; + bool m_allow_to_update_physical_printers {false}; public: TabPresetComboBox(wxWindow *parent, Preset::Type preset_type); @@ -174,8 +174,8 @@ public: void set_show_incompatible_presets(bool show_incompatible_presets) { show_incompatible = show_incompatible_presets; } - void set_update_physical_printers_function(std::function update_fn) { - update_ph_printers = update_fn; + void allow_to_update_physical_printers() { + m_allow_to_update_physical_printers = m_type == Preset::TYPE_PRINTER; } void update() override; @@ -263,6 +263,32 @@ protected: void on_sys_color_changed() override {}; }; + +//------------------------------------------------ +// ChangePresetForPhysicalPrinterDialog +//------------------------------------------------ + +class ChangePresetForPhysicalPrinterDialog : public DPIDialog +{ + void OnOK(wxEvent& event); + +public: + + enum SelectionType + { + Switch, + ChangePreset, + AddPreset + } m_selection {Switch}; + + ChangePresetForPhysicalPrinterDialog(const std::string& preset_name); + ~ChangePresetForPhysicalPrinterDialog() {} + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; + void on_sys_color_changed() override {}; +}; + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index dbea9b3f5..e498f56b7 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -164,17 +164,14 @@ void Tab::create_preset_tab() m_presets_choice->set_selection_changed_function([this](int selection) { if (!m_presets_choice->selection_is_changed_according_to_physical_printers()) { - // for the printer presets set callback for the updating of the physical printers + // For the printer presets allow to update a physical printer if it is needed. + // After call of the update_physical_printers() this possibility will be disabled again to avoid a case, + // when select_preset is called from the others than this place if (m_type == Preset::TYPE_PRINTER) - m_presets_choice->set_update_physical_printers_function([this](std::string preset_name) { update_physical_printers(preset_name);}); + m_presets_choice->allow_to_update_physical_printers(); // select preset select_preset(m_presets_choice->GetString(selection).ToUTF8().data()); - - // Disable callback for the updating of the physical printers to avoid a case, - // when select_preset is called from the others than this place - if (m_type == Preset::TYPE_PRINTER) - m_presets_choice->set_update_physical_printers_function(nullptr); } }); @@ -772,36 +769,6 @@ void Tab::update_tab_ui() m_presets_choice->update(); } -void Tab::update_physical_printers(std::string preset_name) -{ - PhysicalPrinterCollection& physical_printers = wxGetApp().preset_bundle->physical_printers; - if (physical_printers.has_selection() && - Preset::remove_suffix_modified(preset_name) != physical_printers.get_selected_printer_preset_name()) - { - std::string printer_name = physical_printers.get_selected_full_printer_name(); - wxString msg_text = from_u8((boost::format(_u8L("You have selected physical printer \"%1%\".")) % printer_name).str()); - msg_text += "\n\n" + _L("Would you like to change related preset for this printer?") + "\n\n" + - _L("Select YES if you want to change related preset for this printer \n" - "or NO to switch to the another preset (logical printer)."); - wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); - - if (dialog.ShowModal() == wxID_YES) { - preset_name = Preset::remove_suffix_modified(preset_name); - Preset* preset = m_presets->find_preset(preset_name); - assert(preset); - Preset& edited_preset = m_presets->get_edited_preset(); - if (preset->name == edited_preset.name) - preset = &edited_preset; - physical_printers.get_selected_printer().update_from_preset(*preset); - physical_printers.select_printer_by_name(physical_printers.get_selected_printer().get_full_name(preset_name)); - return; - } - } - - // unselect physical printer, if it was selected - m_preset_bundle->physical_printers.unselect_printer(); -} - // Load a provied DynamicConfig into the tab, modifying the active preset. // This could be used for example by setting a Wipe Tower position by interactive manipulation in the 3D view. void Tab::load_config(const DynamicPrintConfig& config) @@ -2822,7 +2789,8 @@ void Tab::load_current_preset() { const Preset& preset = m_presets->get_edited_preset(); - (preset.is_default || preset.is_system) ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); +// (preset.is_default || preset.is_system) ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); + update_delete_preset_btn(); update(); if (m_type == Slic3r::Preset::TYPE_PRINTER) { @@ -2959,9 +2927,23 @@ void Tab::update_page_tree_visibility() } +void Tab::update_delete_preset_btn() +{ + if (m_type == Preset::TYPE_PRINTER && m_presets_choice->is_selected_physical_printer() && + m_preset_bundle->physical_printers.has_selection()) { + // we can't delete last preset from the physical printer + m_btn_delete_preset->Enable(m_preset_bundle->physical_printers.get_selected_printer().preset_names.size() > 1); + } + else { + const Preset& preset = m_presets->get_edited_preset(); + m_btn_delete_preset->Enable(!preset.is_default && !preset.is_system); + } +} + void Tab::update_preset_choice() { m_presets_choice->update(); + update_delete_preset_btn(); } // Called by the UI combo box when the user switches profiles, and also to delete the current profile. @@ -2971,16 +2953,55 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, { if (preset_name.empty()) { if (delete_current) { - // Find an alternate preset to be selected after the current preset is deleted. - const std::deque &presets = this->m_presets->get_presets(); - size_t idx_current = this->m_presets->get_idx_selected(); - // Find the next visible preset. - size_t idx_new = idx_current + 1; - if (idx_new < presets.size()) - for (; idx_new < presets.size() && ! presets[idx_new].is_visible; ++ idx_new) ; - if (idx_new == presets.size()) - for (idx_new = idx_current - 1; idx_new > 0 && ! presets[idx_new].is_visible; -- idx_new); - preset_name = presets[idx_new].name; + PhysicalPrinterCollection& physical_printers = m_preset_bundle->physical_printers; + if (m_presets_choice->is_selected_physical_printer()) { + PhysicalPrinter& printer = physical_printers.get_selected_printer(); + + if (printer.preset_names.size()==1) { + wxMessageDialog dialog(nullptr, _L("It's a last for this physical printer. We can't delete it"), _L("Information"), wxICON_INFORMATION | wxOK); + dialog.ShowModal(); + } + else { + // just delete this preset from the current physical printer + printer.delete_preset(m_presets->get_edited_preset().name); + // select first from the possible presets for this printer + physical_printers.select_printer(printer); + + preset_name = physical_printers.get_selected_printer_preset_name(); + // revert delete_current value to avoid deleting of the new selected preset + delete_current = false; + } + } + else { + // Check preset for delete in physical printers + // Ask a customer about next action , if there is a printer with just one preset and this preset is equal to delete + if (m_type == Preset::TYPE_PRINTER && !physical_printers.empty() ) + { + // try to delete selected preset from the all printers it has + if (!physical_printers.delete_preset_from_printers(m_presets->get_edited_preset().name)) + { + wxMessageDialog dialog(nullptr, _L("There is/are a physical printer(s), which has/have one and only this printer preset.\n" + "This/Those printer(s) will be deletede after deleting of the selected preset.\n" + "Are you sure you want to delete the selected preset?"), _L("Warning"), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); + if (dialog.ShowModal() == wxID_NO) + return; + + // delete selected preset from printers and printer, if it's needed + physical_printers.delete_preset_from_printers(m_presets->get_edited_preset().name, false); + } + } + + // Find an alternate preset to be selected after the current preset is deleted. + const std::deque &presets = this->m_presets->get_presets(); + size_t idx_current = this->m_presets->get_idx_selected(); + // Find the next visible preset. + size_t idx_new = idx_current + 1; + if (idx_new < presets.size()) + for (; idx_new < presets.size() && ! presets[idx_new].is_visible; ++ idx_new) ; + if (idx_new == presets.size()) + for (idx_new = idx_current - 1; idx_new > 0 && ! presets[idx_new].is_visible; -- idx_new); + preset_name = presets[idx_new].name; + } } else { // If no name is provided, select the "-- default --" preset. preset_name = m_presets->default_preset().name; @@ -3075,23 +3096,16 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, } if (canceled) { - update_tab_ui(); - /* - // unselect physical printer selection to the correct synchronization of the printer presets between Tab and Plater - if (m_type == Preset::TYPE_PRINTER) - m_preset_bundle->physical_printers.unselect_printer(); - */ - - - // Check if preset really was changed. - // If preset selection was canceled and previously was selected physical printer, we should select it back - if (m_type == Preset::TYPE_PRINTER && !last_selected_ph_printer_name.empty()) { - if (m_presets->get_edited_preset().name == PhysicalPrinter::get_preset_name(last_selected_ph_printer_name)) { + if (m_type == Preset::TYPE_PRINTER) { + if (!last_selected_ph_printer_name.empty() && + m_presets->get_edited_preset().name == PhysicalPrinter::get_preset_name(last_selected_ph_printer_name)) { + // If preset selection was canceled and previously was selected physical printer, we should select it back m_preset_bundle->physical_printers.select_printer_by_name(last_selected_ph_printer_name); - m_presets_choice->update(); } } + update_tab_ui(); + // Trigger the on_presets_changed event so that we also restore the previous value in the plater selector, // if this action was initiated from the plater. on_presets_changed(); @@ -3334,6 +3348,7 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); //update physical printer's related printer preset if it's needed + m_presets_choice->allow_to_update_physical_printers(); m_presets_choice->update_physical_printers(name); // Add the new item into the UI component, remove dirty flags and activate the saved item. update_tab_ui(); @@ -3389,7 +3404,9 @@ void Tab::delete_preset() // Don't let the user delete the ' - default - ' configuration. std::string action = current_preset.is_external ? _utf8(L("remove")) : _utf8(L("delete")); // TRN remove/delete - const wxString msg = from_u8((boost::format(_utf8(L("Are you sure you want to %1% the selected preset?"))) % action).str()); + const wxString msg = m_presets_choice->is_selected_physical_printer() ? + from_u8((boost::format(_utf8(L("Are you sure you want to delete \"%1%\" preset from the physical printer?"))) % current_preset.name).str()) : + from_u8((boost::format(_utf8(L("Are you sure you want to %1% the selected preset?"))) % action).str()); action = current_preset.is_external ? _utf8(L("Remove")) : _utf8(L("Delete")); // TRN Remove/Delete wxString title = from_u8((boost::format(_utf8(L("%1% Preset"))) % action).str()); //action + _(L(" Preset")); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 82df31b59..57129955b 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -275,6 +275,7 @@ public: void load_current_preset(); void rebuild_page_tree(); void update_page_tree_visibility(); + void update_delete_preset_btn(); void update_preset_choice(); // Select a new preset, possibly delete the current one. void select_preset(std::string preset_name = "", bool delete_current = false, const std::string& last_selected_ph_printer_name = ""); @@ -307,7 +308,6 @@ public: void load_initial_data(); void update_dirty(); void update_tab_ui(); - void update_physical_printers(std::string preset_name); void load_config(const DynamicPrintConfig& config); virtual void reload_config(); void update_mode(); From 3d990a918904fb1ab10afa4f95f2885350821994 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 20 Jul 2020 16:27:39 +0200 Subject: [PATCH 14/70] First try to convert a user printer profiles to the physical printers --- src/libslic3r/Preset.cpp | 33 +++++++++++++++++++++++++++-- src/libslic3r/Preset.hpp | 1 + src/libslic3r/PresetBundle.cpp | 1 + src/slic3r/GUI/PresetComboBoxes.cpp | 6 ++++++ src/slic3r/GUI/PresetComboBoxes.hpp | 9 +++----- 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index a55dc24ca..401de0fc3 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1481,7 +1481,7 @@ PhysicalPrinterCollection::PhysicalPrinterCollection( const std::vectorfind_printer(name, false)) { // This happens when there's is a preset (most likely legacy one) with the same name as a system preset // that's already been loaded from a bundle. - BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name; + BOOST_LOG_TRIVIAL(warning) << "Printer already present, not loading: " << name; continue; } try { @@ -1530,6 +1530,35 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const throw std::runtime_error(errors_cummulative); } +// if there is saved user presets, contains information about "Print Host upload", +// Create default printers with this presets +// Throws an exception on error. +void PhysicalPrinterCollection::load_printers(const PrinterPresetCollection& printer_presets, std::string def_printer_name/* = ""*/) +{ + if (def_printer_name.empty()) + def_printer_name = "Printer"; + + int cnt=0; + std::string errors_cummulative; + // Store the loaded printers into a new vector + std::deque printers_loaded; + for (const Preset& preset: printer_presets) { + const DynamicPrintConfig& config = preset.config; + if (!config.opt_string("print_host").empty() || + !config.opt_string("printhost_apikey").empty() || + !config.opt_string("printhost_cafile").empty() ) { + PhysicalPrinter printer((boost::format("%1% %2%") % def_printer_name % ++cnt ).str(), preset); + printer.loaded = true; + printers_loaded.emplace_back(printer); + + save_printer(printer); + } + } + + if (!errors_cummulative.empty()) + throw std::runtime_error(errors_cummulative); +} + PhysicalPrinter* PhysicalPrinterCollection::find_printer( const std::string& name, bool first_visible_if_not_found) { PhysicalPrinter key(name); diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index e8afb0f6f..02f831136 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -626,6 +626,7 @@ public: // Load ini files of the particular type from the provided directory path. void load_printers(const std::string& dir_path, const std::string& subdir); + void load_printers(const PrinterPresetCollection &printer_presets, std::string def_printer_name = ""); // Save the printer under a new name. If the name is different from the old one, // a new printer is stored into the list of printers. diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 7969966e5..0e215a2ae 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -201,6 +201,7 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_ } try { this->physical_printers.load_printers(dir_user_presets, "physical_printer"); + this->physical_printers.load_printers(this->printers); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index bc1f48dd6..2d74344bd 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -131,6 +131,12 @@ PresetComboBox::~PresetComboBox() { } +BitmapCache& PresetComboBox::bitmap_cache() +{ + static BitmapCache bmps; + return bmps; +} + void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type) { this->SetClientData(item, (void*)label_item_type); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 1cea97e41..89d043f7b 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -10,7 +10,7 @@ #include "libslic3r/Preset.hpp" #include "wxExtensions.hpp" #include "GUI_Utils.hpp" -#include "BitmapCache.hpp" +//#include "BitmapCache.hpp" class wxString; class wxTextCtrl; @@ -22,7 +22,7 @@ namespace Slic3r { namespace GUI { - +class BitmapCache; // --------------------------------- // *** PresetComboBox *** // --------------------------------- @@ -72,10 +72,7 @@ protected: PresetCollection* m_collection {nullptr}; // Caching bitmaps for the all bitmaps, used in preset comboboxes - static BitmapCache& bitmap_cache() { - static BitmapCache bmps; - return bmps; - } + static BitmapCache& bitmap_cache(); // Indicator, that the preset is compatible with the selected printer. ScalableBitmap m_bitmapCompatible; From 6d28d68e4a87e3e53baf4d5c84d19a8bf4e56a61 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 21 Jul 2020 16:21:18 +0200 Subject: [PATCH 15/70] PhysicalPrinter : Implemented synchronizations from user printer profiles with "Print Host upload" information to the new physical printers --- src/libslic3r/Preset.cpp | 102 ++++++++++++++++++++++------ src/libslic3r/Preset.hpp | 11 ++- src/libslic3r/PresetBundle.cpp | 4 +- src/slic3r/GUI/GUI_App.cpp | 44 ++++++++++++ src/slic3r/GUI/GUI_App.hpp | 1 + src/slic3r/GUI/PresetComboBoxes.cpp | 5 +- 6 files changed, 138 insertions(+), 29 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 401de0fc3..ae74bffd7 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1370,6 +1370,37 @@ const std::vector& PhysicalPrinter::printer_options() return s_opts; } +const std::vector& PhysicalPrinter::print_host_options() +{ + static std::vector s_opts; + if (s_opts.empty()) { + s_opts = { + "print_host", + "printhost_apikey", + "printhost_cafile" + }; + } + return s_opts; +} + +bool PhysicalPrinter::has_print_host_information(const PrinterPresetCollection& printer_presets) +{ + for (const Preset& preset : printer_presets) + if (has_print_host_information(preset.config)) + return true; + + return false; +} + +bool PhysicalPrinter::has_print_host_information(const DynamicPrintConfig& config) +{ + for (const std::string& opt : print_host_options()) + if (!config.opt_string(opt).empty()) + return true; + + return false; +} + const std::set& PhysicalPrinter::get_preset_names() const { return preset_names; @@ -1532,42 +1563,69 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const // if there is saved user presets, contains information about "Print Host upload", // Create default printers with this presets -// Throws an exception on error. -void PhysicalPrinterCollection::load_printers(const PrinterPresetCollection& printer_presets, std::string def_printer_name/* = ""*/) +// Note! "Print Host upload" options will be cleared after physical printer creations +void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollection& printer_presets, std::string def_printer_name) { - if (def_printer_name.empty()) - def_printer_name = "Printer"; - int cnt=0; - std::string errors_cummulative; - // Store the loaded printers into a new vector - std::deque printers_loaded; - for (const Preset& preset: printer_presets) { - const DynamicPrintConfig& config = preset.config; - if (!config.opt_string("print_host").empty() || - !config.opt_string("printhost_apikey").empty() || - !config.opt_string("printhost_cafile").empty() ) { - PhysicalPrinter printer((boost::format("%1% %2%") % def_printer_name % ++cnt ).str(), preset); - printer.loaded = true; - printers_loaded.emplace_back(printer); + for (Preset& preset: printer_presets) { + DynamicPrintConfig& config = preset.config; + const std::vector& options = PhysicalPrinter::print_host_options(); - save_printer(printer); + for(const std::string& option : options) { + if (!config.opt_string(option).empty()) { + // check if printer with those "Print Host upload" options already exist + PhysicalPrinter* existed_printer = find_printer_with_same_config(config); + if (existed_printer) + // just add preset for this printer + existed_printer->add_preset(preset.name); + else { + // create new printer from this preset + PhysicalPrinter printer((boost::format("%1% %2%") % def_printer_name % ++cnt ).str(), preset); + printer.loaded = true; + save_printer(printer); + } + + // erase "Print Host upload" information from the preset + for (const std::string& opt : options) + config.opt_string(opt).clear(); + // save changes for preset + preset.save(); + + // update those changes for edited preset if it's equal to the preset + Preset& edited = printer_presets.get_edited_preset(); + if (preset.name == edited.name) { + for (const std::string& opt : options) + edited.config.opt_string(opt).clear(); + } + + break; + } } } - - if (!errors_cummulative.empty()) - throw std::runtime_error(errors_cummulative); } PhysicalPrinter* PhysicalPrinterCollection::find_printer( const std::string& name, bool first_visible_if_not_found) { - PhysicalPrinter key(name); auto it = this->find_printer_internal(name); // Ensure that a temporary copy is returned if the preset found is currently selected. - return (it != m_printers.end() && it->name == key.name) ? &this->printer(it - m_printers.begin()) : + return (it != m_printers.end() && it->name == name) ? &this->printer(it - m_printers.begin()) : first_visible_if_not_found ? &this->printer(0) : nullptr; } +PhysicalPrinter* PhysicalPrinterCollection::find_printer_with_same_config(const DynamicPrintConfig& config) +{ + for (const PhysicalPrinter& printer :*this) { + bool is_equal = true; + for (const std::string& opt : PhysicalPrinter::print_host_options()) + if (is_equal && printer.config.opt_string(opt) != config.opt_string(opt)) + is_equal = false; + + if (is_equal) + return find_printer(printer.name); + } + return nullptr; +} + // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. std::string PhysicalPrinterCollection::path_from_name(const std::string& new_name) const { diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 02f831136..98b805b4e 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -549,12 +549,15 @@ public: // set of presets used with this physical printer std::set preset_names; - static std::string separator(); - // Has this profile been loaded? bool loaded = false; + static std::string separator(); static const std::vector& printer_options(); + static const std::vector& print_host_options(); + static bool has_print_host_information(const PrinterPresetCollection& printer_presets); + static bool has_print_host_information(const DynamicPrintConfig& config); + const std::set& get_preset_names() const; bool has_empty_config() const; @@ -626,7 +629,7 @@ public: // Load ini files of the particular type from the provided directory path. void load_printers(const std::string& dir_path, const std::string& subdir); - void load_printers(const PrinterPresetCollection &printer_presets, std::string def_printer_name = ""); + void load_printers_from_presets(PrinterPresetCollection &printer_presets, std::string def_printer_name); // Save the printer under a new name. If the name is different from the old one, // a new printer is stored into the list of printers. @@ -704,6 +707,8 @@ private: return const_cast(this)->find_printer_internal(name); } + PhysicalPrinter* find_printer_with_same_config( const DynamicPrintConfig &config); + // List of printers // Use deque to force the container to allocate an object per each entry, // so that the addresses of the presets don't change during resizing of the container. diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 0e215a2ae..d074c77d7 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -201,7 +201,6 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_ } try { this->physical_printers.load_printers(dir_user_presets, "physical_printer"); - this->physical_printers.load_printers(this->printers); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } @@ -436,8 +435,7 @@ void PresetBundle::load_selections(AppConfig &config, const std::string &preferr std::string initial_physical_printer_name = remove_ini_suffix(config.get("extras", "physical_printer")); // Activate physical printer from the config - const PhysicalPrinter* initial_physical_printer = physical_printers.find_printer(initial_physical_printer_name); - if (initial_physical_printer) + if (!initial_physical_printer_name.empty()) physical_printers.select_printer_by_name(initial_physical_printer_name); } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 089a38c6f..f7689c6e9 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -28,6 +28,9 @@ #include #include +#include +#include + #include "libslic3r/Utils.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/I18N.hpp" @@ -629,6 +632,43 @@ void GUI_App::set_auto_toolbar_icon_scale(float scale) const app_config->set("auto_toolbar_size", val); } +// check user printer_presets for the containing information about "Print Host upload" +void GUI_App::check_printer_presets() +{ + if (!PhysicalPrinter::has_print_host_information(preset_bundle->printers)) + return; + + wxString msg_text = _L("You have presets with saved options for \"Print Host upload\".\n" + "But from this version of PrusaSlicer we don't show/use this information in Printer Settings.\n" + "Now, this information will be exposed in physical printers settings.") + "\n\n" + + _L("Enter the name for the Printer device used by defaul during its creation.\n" + "Note: This name can be changed later from the physical printers settings") + ":"; + wxString msg_header = _L("Name for printer device"); + + // get custom gcode + wxTextEntryDialog dlg(nullptr, msg_text, msg_header, _L("Printer"), wxTextEntryDialogStyle); + + // detect TextCtrl and OK button + wxTextCtrl* textctrl{ nullptr }; + wxWindowList& dlg_items = dlg.GetChildren(); + for (auto item : dlg_items) { + textctrl = dynamic_cast(item); + if (textctrl) + break; + } + + if (textctrl) { + textctrl->SetSelection(0, textctrl->GetLastPosition()); + + wxButton* btn_OK = static_cast(dlg.FindWindowById(wxID_OK)); + btn_OK->Bind(wxEVT_UPDATE_UI, [textctrl](wxUpdateUIEvent& evt) { + evt.Enable(!textctrl->IsEmpty()); + }, btn_OK->GetId()); + } + if (dlg.ShowModal() == wxID_OK) + preset_bundle->physical_printers.load_printers_from_presets(preset_bundle->printers, into_u8(dlg.GetValue())); +} + void GUI_App::recreate_GUI(const wxString& msg_name) { mainframe->shutdown(); @@ -1171,6 +1211,10 @@ bool GUI_App::checked_tab(Tab* tab) // Update UI / Tabs to reflect changes in the currently loaded presets void GUI_App::load_current_presets() { + // check printer_presets for the containing information about "Print Host upload" + // and create physical printer from it, if any exists + check_printer_presets(); + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); this->plater()->set_printer_technology(printer_technology); for (Tab *tab : tabs_list) diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 23567695c..db551610b 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -150,6 +150,7 @@ public: wxSize get_min_size() const; float toolbar_icon_scale(const bool is_limited = false) const; void set_auto_toolbar_icon_scale(float scale) const; + void check_printer_presets(); void recreate_GUI(const wxString& message); void system_info(); diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 2d74344bd..e527ef9c9 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -359,8 +359,11 @@ bool PresetComboBox::selection_is_changed_according_to_physical_printers() // if new preset wasn't selected, there is no need to call update preset selection if (old_printer_preset == preset_name) { // we need just to update according Plater<->Tab PresetComboBox - if (dynamic_cast(this)!=nullptr) + if (dynamic_cast(this)!=nullptr) { wxGetApp().get_tab(m_type)->update_preset_choice(); + // Synchronize config.ini with the current selections. + m_preset_bundle->export_selections(*wxGetApp().app_config); + } else if (dynamic_cast(this)!=nullptr) wxGetApp().sidebar().update_presets(m_type); From f7119c42f46b76aea67d914f48be663e4ab0a742 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 22 Jul 2020 13:23:44 +0200 Subject: [PATCH 16/70] PresetComboBox class:: code refactoring --- src/slic3r/GUI/PresetComboBoxes.cpp | 165 ++++++++++------------------ src/slic3r/GUI/PresetComboBoxes.hpp | 4 + 2 files changed, 65 insertions(+), 104 deletions(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index e527ef9c9..4cf9ac40b 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -142,11 +142,36 @@ void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type) this->SetClientData(item, (void*)label_item_type); } +void PresetComboBox::invalidate_selection() +{ + m_last_selected = INT_MAX; // this value means that no one item is selected +} + +void PresetComboBox::validate_selection(bool predicate/*=false*/) +{ + if (predicate || + // just in case: mark m_last_selected as a first added element + m_last_selected == INT_MAX) + m_last_selected = GetCount() - 1; +} + +void PresetComboBox::update_selection() +{ + /* If selected_preset_item is still equal to INT_MAX, it means that + * there is no presets added to the list. + * So, select last combobox item ("Add/Remove preset") + */ + validate_selection(); + + SetSelection(m_last_selected); + SetToolTip(GetString(m_last_selected)); +} + void PresetComboBox::update(const std::string& select_preset_name) { Freeze(); Clear(); - size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected + invalidate_selection(); const std::deque& presets = m_collection->get_presets(); @@ -164,18 +189,13 @@ void PresetComboBox::update(const std::string& select_preset_name) // marker used for disable incompatible printer models for the selected physical printer bool is_enabled = true; - std::string bitmap_key = "tab"; - std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - + std::string bitmap_key = "cb"; if (m_type == Preset::TYPE_PRINTER) { bitmap_key += "_printer"; if (preset.printer_technology() == ptSLA) bitmap_key += "_sla"; - if (!is_enabled) - bitmap_key += "_disabled"; } - bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; - bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; wxBitmap* bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default); assert(bmp); @@ -184,10 +204,7 @@ void PresetComboBox::update(const std::string& select_preset_name) int item_id = Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); if (!is_enabled) set_label_marker(item_id, LABEL_ITEM_DISABLED); - if (preset.name == select_preset_name ||//i == idx_selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; + validate_selection(preset.name == select_preset_name); } else { @@ -207,25 +224,12 @@ void PresetComboBox::update(const std::string& select_preset_name) bool is_enabled = it->second.second; if (!is_enabled) set_label_marker(item_id, LABEL_ITEM_DISABLED); - if (it->first == selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; + validate_selection(it->first == selected); } } - /* But, if selected_preset_item is still equal to INT_MAX, it means that - * there is no presets added to the list. - * So, select last combobox item ("Add/Remove preset") - */ - if (selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; - - SetSelection(selected_preset_item); - SetToolTip(GetString(selected_preset_item)); + update_selection(); Thaw(); - - m_last_selected = selected_preset_item; } void PresetComboBox::msw_rescale() @@ -272,6 +276,12 @@ wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, bool wide_icons, con bool is_compatible/* = true*/, bool is_system/* = false*/, bool is_single_bar/* = false*/, std::string filament_rgb/* = ""*/, std::string extruder_rgb/* = ""*/) { + // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left + // to the filament color image. + if (wide_icons) + bitmap_key += is_compatible ? ",cmpt" : ",ncmpt"; + + bitmap_key += is_system ? ",syst" : ",nsyst"; bitmap_key += ",h" + std::to_string(icon_height); wxBitmap* bmp = bitmap_cache().find(bitmap_key); @@ -313,6 +323,9 @@ wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, bool wide_icons, con wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, const std::string& main_icon_name, const std::string& next_icon_name, bool is_enabled/* = true*/, bool is_compatible/* = true*/, bool is_system/* = false*/) { + bitmap_key += !is_enabled ? "_disabled" : ""; + bitmap_key += is_compatible ? ",cmpt" : ",ncmpt"; + bitmap_key += is_system ? ",syst" : ",nsyst"; bitmap_key += ",h" + std::to_string(icon_height); wxBitmap* bmp = bitmap_cache().find(bitmap_key); @@ -645,7 +658,7 @@ void PlaterPresetComboBox::update() // Otherwise fill in the list from scratch. this->Freeze(); this->Clear(); - size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected + invalidate_selection(); const Preset* selected_filament_preset; std::string extruder_color; @@ -700,12 +713,6 @@ void PlaterPresetComboBox::update() bitmap_key += single_bar ? filament_rgb : filament_rgb + extruder_rgb; } - // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left - // to the filament color image. - if (wide_icons) - bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; - bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; - wxBitmap* bmp = get_bmp(bitmap_key, wide_icons, bitmap_type_name, preset.is_compatible, preset.is_system || preset.is_default, single_bar, filament_rgb, extruder_rgb); @@ -714,12 +721,9 @@ void PlaterPresetComboBox::update() const std::string name = preset.alias.empty() ? preset.name : preset.alias; if (preset.is_default || preset.is_system) { Append(wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); - if (is_selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) { - selected_preset_item = GetCount() - 1; + validate_selection(is_selected); + if (is_selected) tooltip = wxString::FromUTF8(preset.name.c_str()); - } } else { @@ -737,10 +741,7 @@ void PlaterPresetComboBox::update() set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { Append(it->first, *it->second); - if (it->first == selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; + validate_selection(it->first == selected); } } @@ -757,32 +758,18 @@ void PlaterPresetComboBox::update() if (!preset) continue; std::string main_icon_name, bitmap_key = main_icon_name = preset->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - if (wide_icons) - bitmap_key += ",cmpt"; - bitmap_key += ",nsyst"; - - wxBitmap* bmp = get_bmp(bitmap_key, wide_icons, main_icon_name); + wxBitmap* bmp = get_bmp(main_icon_name, wide_icons, main_icon_name); assert(bmp); set_label_marker(Append(wxString::FromUTF8((it->get_full_name(preset_name) + (preset->is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); - if (ph_printers.is_selected(it, preset_name) || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; + validate_selection(ph_printers.is_selected(it, preset_name)); } } } } if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) { - std::string bitmap_key = ""; - // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left - // to the filament color image. - if (wide_icons) - bitmap_key += "wide,"; - bitmap_key += "edit_preset_list"; - - wxBitmap* bmp = get_bmp(bitmap_key, wide_icons, "edit_uni"); + wxBitmap* bmp = get_bmp("edit_preset_list", wide_icons, "edit_uni"); assert(bmp); if (m_type == Preset::TYPE_SLA_MATERIAL) @@ -791,18 +778,12 @@ void PlaterPresetComboBox::update() set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); } - /* But, if selected_preset_item is still equal to INT_MAX, it means that - * there is no presets added to the list. - * So, select last combobox item ("Add/Remove preset") - */ - if (selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; - - SetSelection(selected_preset_item); - SetToolTip(tooltip.IsEmpty() ? GetString(selected_preset_item) : tooltip); - m_last_selected = selected_preset_item; + update_selection(); Thaw(); + if (!tooltip.IsEmpty()) + SetToolTip(tooltip); + // Update control min size after rescale (changed Display DPI under MSW) if (GetMinWidth() != 20 * m_em_unit) SetMinSize(wxSize(20 * m_em_unit, GetSize().GetHeight())); @@ -858,7 +839,7 @@ void TabPresetComboBox::update() { Freeze(); Clear(); - size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected + invalidate_selection(); const std::deque& presets = m_collection->get_presets(); @@ -890,18 +871,13 @@ void TabPresetComboBox::update() if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) is_enabled = m_enable_all ? true : preset.printer_technology() == proper_pt; - std::string bitmap_key = "tab"; - std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - + std::string bitmap_key = "tab"; if (m_type == Preset::TYPE_PRINTER) { bitmap_key += "_printer"; if (preset.printer_technology() == ptSLA) bitmap_key += "_sla"; - if (!is_enabled) - bitmap_key += "_disabled"; } - bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; - bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; wxBitmap* bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default); assert(bmp); @@ -910,10 +886,7 @@ void TabPresetComboBox::update() int item_id = Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); if (!is_enabled) set_label_marker(item_id, LABEL_ITEM_DISABLED); - if (i == idx_selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; + validate_selection(i == idx_selected); } else { @@ -933,10 +906,7 @@ void TabPresetComboBox::update() bool is_enabled = it->second.second; if (!is_enabled) set_label_marker(item_id, LABEL_ITEM_DISABLED); - if (it->first == selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; + validate_selection(it->first == selected); } } @@ -953,39 +923,26 @@ void TabPresetComboBox::update() if (!preset) continue; std::string main_icon_name = preset->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - std::string bitmap_key = main_icon_name + ",nsyst"; - wxBitmap* bmp = get_bmp(bitmap_key, main_icon_name, "", true, true, false); + wxBitmap* bmp = get_bmp(main_icon_name, main_icon_name, "", true, true, false); assert(bmp); set_label_marker(Append(wxString::FromUTF8((it->get_full_name(preset_name) + (preset->is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); - if (ph_printers.is_selected(it, preset_name) || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; + validate_selection(ph_printers.is_selected(it, preset_name)); } } } // add "Add/Remove printers" item - wxBitmap* bmp = get_bmp("edit_preset_list", m_main_bitmap_name, "edit_uni", true, true, true); + std::string icon_name = "edit_uni"; + wxBitmap* bmp = get_bmp("edit_preset_list, tab,", icon_name, ""); assert(bmp); set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); } - /* But, if selected_preset_item is still equal to INT_MAX, it means that - * there is no presets added to the list. - * So, select last combobox item ("Add/Remove preset") - */ - if (selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; - - SetSelection(selected_preset_item); - SetToolTip(GetString(selected_preset_item)); + update_selection(); Thaw(); - - m_last_selected = selected_preset_item; } void TabPresetComboBox::msw_rescale() diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 89d043f7b..9a70818e1 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -91,6 +91,10 @@ protected: int thin_space_icon_width; int wide_space_icon_width; + void invalidate_selection(); + void validate_selection(bool predicate = false); + void update_selection(); + #ifdef __linux__ static const char* separator_head() { return "------- "; } static const char* separator_tail() { return " -------"; } From 299b783601c0584992aafb00689718c1ab51627e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 22 Jul 2020 16:28:34 +0200 Subject: [PATCH 17/70] PhysicalPrinterDialog: Select first related preset for the printer, if printer was just created or previously selected preset doesn't exist now --- src/slic3r/GUI/PresetComboBoxes.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 4cf9ac40b..2c2b1d7a3 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -1479,10 +1479,12 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) // save new physical printer printers.save_printer(m_printer, renamed_from); - printers.select_printer(m_printer); - - // refresh preset list on Printer Settings Tab - wxGetApp().get_tab(Preset::TYPE_PRINTER)->update_preset_choice(); + if (m_printer.preset_names.find(printers.get_selected_printer_preset_name()) == m_printer.preset_names.end()) { + // select first preset for this printer + printers.select_printer(m_printer); + // refresh preset list on Printer Settings Tab + wxGetApp().get_tab(Preset::TYPE_PRINTER)->select_preset(printers.get_selected_printer_preset_name()); + } event.Skip(); } From a4c12b90f19786c00efb909819718f4801c47170 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 23 Jul 2020 12:17:18 +0200 Subject: [PATCH 18/70] PhysicalPrinterCollection: Use select_preset() instead of select_preset_by_name() + changed signature for select_preset() --- src/libslic3r/Preset.cpp | 31 +++++++++++------------------ src/libslic3r/Preset.hpp | 14 ++++++------- src/libslic3r/PresetBundle.cpp | 2 +- src/slic3r/GUI/PresetComboBoxes.cpp | 4 ++-- src/slic3r/GUI/Tab.cpp | 2 +- 5 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index ae74bffd7..7e30831fe 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1731,34 +1731,27 @@ std::string PhysicalPrinterCollection::get_selected_full_printer_name() const return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_full_name(m_selected_preset); } -PhysicalPrinter& PhysicalPrinterCollection::select_printer_by_name(const std::string& full_name) +void PhysicalPrinterCollection::select_printer(const std::string& full_name) { std::string printer_name = PhysicalPrinter::get_short_name(full_name); auto it = this->find_printer_internal(printer_name); - assert(it != m_printers.end()); + if (it == m_printers.end()) { + unselect_printer(); + return; + } // update idx_selected - m_idx_selected = it - m_printers.begin(); + m_idx_selected = it - m_printers.begin(); + // update name of the currently selected preset - m_selected_preset = it->get_preset_name(full_name); - if (m_selected_preset.empty()) + if (printer_name == full_name) + // use first preset in the list m_selected_preset = *it->preset_names.begin(); - return *it; + else + m_selected_preset = it->get_preset_name(full_name); } -PhysicalPrinter& PhysicalPrinterCollection::select_printer(const std::string& printer_name) -{ - auto it = this->find_printer_internal(printer_name); - assert(it != m_printers.end()); - - // update idx_selected - m_idx_selected = it - m_printers.begin(); - // update name of the currently selected preset - m_selected_preset = *it->preset_names.begin(); - return *it; -} - -PhysicalPrinter& PhysicalPrinterCollection::select_printer(const PhysicalPrinter& printer) +void PhysicalPrinterCollection::select_printer(const PhysicalPrinter& printer) { return select_printer(printer.name); } diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 98b805b4e..6b5a2a511 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -666,13 +666,13 @@ public: // Returns the printer model of the selected preset, or an empty string if no preset is selected. std::string get_selected_printer_preset_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : m_selected_preset; } - // select printer with name and return reference on it - PhysicalPrinter& select_printer_by_name(const std::string& full_name); - PhysicalPrinter& select_printer(const std::string &printer_name); - PhysicalPrinter& select_printer(const PhysicalPrinter& printer); - bool has_selection() const; - void unselect_printer() ; - bool is_selected(ConstIterator it, const std::string &preset_name) const; + // Select printer by the full printer name, which contains name of printer, separator and name of selected preset + // If full_name doesn't contain name of selected preset, then select first preset in the list for this printer + void select_printer(const std::string& full_name); + void select_printer(const PhysicalPrinter& printer); + bool has_selection() const; + void unselect_printer() ; + bool is_selected(ConstIterator it, const std::string &preset_name) const; // Return a printer by an index. If the printer is active, a temporary copy is returned. PhysicalPrinter& printer(size_t idx) { return m_printers[idx]; } diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index d074c77d7..108985704 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -436,7 +436,7 @@ void PresetBundle::load_selections(AppConfig &config, const std::string &preferr // Activate physical printer from the config if (!initial_physical_printer_name.empty()) - physical_printers.select_printer_by_name(initial_physical_printer_name); + physical_printers.select_printer(initial_physical_printer_name); } // Export selections (current print, current filaments, current printer) into config.ini diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 2c2b1d7a3..f6a2a036b 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -366,7 +366,7 @@ bool PresetComboBox::selection_is_changed_according_to_physical_printers() else old_printer_preset = m_collection->get_edited_preset().name; // Select related printer preset on the Printer Settings Tab - physical_printers.select_printer_by_name(selected_string); + physical_printers.select_printer(selected_string); std::string preset_name = physical_printers.get_selected_printer_preset_name(); // if new preset wasn't selected, there is no need to call update preset selection @@ -1031,7 +1031,7 @@ void TabPresetComboBox::update_physical_printers( const std::string& preset_name dialog.ShowModal(); } - physical_printers.select_printer_by_name(printer.get_full_name(preset_name)); + physical_printers.select_printer(printer.get_full_name(preset_name)); } } else diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index e498f56b7..dd63dc141 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3100,7 +3100,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, if (!last_selected_ph_printer_name.empty() && m_presets->get_edited_preset().name == PhysicalPrinter::get_preset_name(last_selected_ph_printer_name)) { // If preset selection was canceled and previously was selected physical printer, we should select it back - m_preset_bundle->physical_printers.select_printer_by_name(last_selected_ph_printer_name); + m_preset_bundle->physical_printers.select_printer(last_selected_ph_printer_name); } } From 257e77ed407a13cd43eb9f233ee40d53d29c701e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 23 Jul 2020 12:44:08 +0200 Subject: [PATCH 19/70] Fixed a typo --- src/slic3r/GUI/GUI_App.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index f7689c6e9..1b7278bd7 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -641,7 +641,7 @@ void GUI_App::check_printer_presets() wxString msg_text = _L("You have presets with saved options for \"Print Host upload\".\n" "But from this version of PrusaSlicer we don't show/use this information in Printer Settings.\n" "Now, this information will be exposed in physical printers settings.") + "\n\n" + - _L("Enter the name for the Printer device used by defaul during its creation.\n" + _L("Enter the name for the Printer device used by default during its creation.\n" "Note: This name can be changed later from the physical printers settings") + ":"; wxString msg_header = _L("Name for printer device"); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index dd63dc141..3f566eacb 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2981,7 +2981,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, if (!physical_printers.delete_preset_from_printers(m_presets->get_edited_preset().name)) { wxMessageDialog dialog(nullptr, _L("There is/are a physical printer(s), which has/have one and only this printer preset.\n" - "This/Those printer(s) will be deletede after deleting of the selected preset.\n" + "This/Those printer(s) will be deleted after deleting of the selected preset.\n" "Are you sure you want to delete the selected preset?"), _L("Warning"), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); if (dialog.ShowModal() == wxID_NO) return; From b155c3c4f829f3e77321939ebe356969ee35ce5f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 24 Jul 2020 16:34:25 +0200 Subject: [PATCH 20/70] PhysicalPrinterDialog :: next improvement --- src/slic3r/CMakeLists.txt | 4 +- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 556 +++++++++++++++++++++++ src/slic3r/GUI/PhysicalPrinterDialog.hpp | 105 +++++ src/slic3r/GUI/PresetComboBoxes.cpp | 539 ++-------------------- src/slic3r/GUI/PresetComboBoxes.hpp | 89 +--- 5 files changed, 702 insertions(+), 591 deletions(-) create mode 100644 src/slic3r/GUI/PhysicalPrinterDialog.cpp create mode 100644 src/slic3r/GUI/PhysicalPrinterDialog.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 49e069285..20ea4e33a 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -76,9 +76,11 @@ set(SLIC3R_GUI_SOURCES GUI/MainFrame.cpp GUI/MainFrame.hpp GUI/Plater.cpp + GUI/Plater.hpp GUI/PresetComboBoxes.hpp GUI/PresetComboBoxes.cpp - GUI/Plater.hpp + GUI/PhysicalPrinterDialog.hpp + GUI/PhysicalPrinterDialog.cpp GUI/GUI_ObjectList.cpp GUI/GUI_ObjectList.hpp GUI/GUI_ObjectManipulation.cpp diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp new file mode 100644 index 000000000..7d3c92c13 --- /dev/null +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -0,0 +1,556 @@ +#include "PhysicalPrinterDialog.hpp" +#include "PresetComboBoxes.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/PresetBundle.hpp" + +#include "GUI.hpp" +#include "GUI_App.hpp" +#include "MainFrame.hpp" +#include "format.hpp" +#include "Tab.hpp" +#include "wxExtensions.hpp" +#include "PrintHostDialogs.hpp" +#include "../Utils/ASCIIFolding.hpp" +#include "../Utils/PrintHost.hpp" +#include "../Utils/FixModelByWin10.hpp" +#include "../Utils/UndoRedo.hpp" +#include "RemovableDriveManager.hpp" +#include "BitmapCache.hpp" +#include "BonjourDialog.hpp" + +using Slic3r::GUI::format_wxstr; + +//static const std::pair THUMBNAIL_SIZE_3MF = { 256, 256 }; + +namespace Slic3r { +namespace GUI { + +#define BORDER_W 10 + +//------------------------------------------ +// PresetForPrinter +//------------------------------------------ + +PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name) : + m_parent(parent) +{ + m_sizer = new wxBoxSizer(wxVERTICAL); + + m_delete_preset_btn = new ScalableButton(parent, wxID_ANY, "cross", "", wxDefaultSize, wxDefaultPosition, /*wxBU_LEFT | */wxBU_EXACTFIT); + m_delete_preset_btn->SetFont(wxGetApp().normal_font()); + m_delete_preset_btn->SetToolTip(_L("Delete this preset from this printer device")); + m_delete_preset_btn->Bind(wxEVT_BUTTON, &PresetForPrinter::DeletePreset, this); + + m_presets_list = new PresetComboBox(parent, Preset::TYPE_PRINTER); + m_presets_list->set_printer_technology(parent->get_printer_technology()); + + m_presets_list->set_selection_changed_function([this](int selection) { + std::string selected_string = Preset::remove_suffix_modified(m_presets_list->GetString(selection).ToUTF8().data()); + Preset* preset = wxGetApp().preset_bundle->printers.find_preset(selected_string); + assert(preset); + Preset& edited_preset = wxGetApp().preset_bundle->printers.get_edited_preset(); + if (preset->name == edited_preset.name) + preset = &edited_preset; + + // if created physical printer doesn't have any settings, use the settings from the selected preset + if (m_parent->get_printer()->has_empty_config()) { + // update Print Host upload from the selected preset + m_parent->get_printer()->update_from_preset(*preset); + // update values in parent (PhysicalPrinterDialog) + m_parent->update(); + } + + // update PrinterTechnology if it was changed + if (m_presets_list->set_printer_technology(preset->printer_technology())) + m_parent->set_printer_technology(preset->printer_technology()); + + update_full_printer_name(); + }); + m_presets_list->update(preset_name); + + m_info_line = new wxStaticText(parent, wxID_ANY, _L("This printer will be shown in the presets list as") + ":"); + + m_full_printer_name = new wxStaticText(parent, wxID_ANY, ""); + m_full_printer_name->SetFont(wxGetApp().bold_font()); + + wxBoxSizer* preset_sizer = new wxBoxSizer(wxHORIZONTAL); + preset_sizer->Add(m_presets_list , 1, wxEXPAND); + preset_sizer->Add(m_delete_preset_btn , 0, wxEXPAND | wxLEFT, BORDER_W); + + wxBoxSizer* name_sizer = new wxBoxSizer(wxHORIZONTAL); + name_sizer->Add(m_info_line, 0, wxEXPAND); + name_sizer->Add(m_full_printer_name, 0, wxEXPAND | wxLEFT, BORDER_W); + + m_sizer->Add(preset_sizer , 0, wxEXPAND); + m_sizer->Add(name_sizer, 0, wxEXPAND); +} + +PresetForPrinter::~PresetForPrinter() +{ + m_presets_list->Destroy(); + m_delete_preset_btn->Destroy(); + m_info_line->Destroy(); + m_full_printer_name->Destroy(); +} + +void PresetForPrinter::DeletePreset(wxEvent& event) +{ + m_parent->DeletePreset(this); +} + +void PresetForPrinter::update_full_printer_name() +{ + wxString printer_name = m_parent->get_printer_name(); + wxString preset_name = m_presets_list->GetString(m_presets_list->GetSelection()); + + m_full_printer_name->SetLabelText(printer_name + " * " + preset_name); +} + +std::string PresetForPrinter::get_preset_name() +{ + return into_u8(m_presets_list->GetString(m_presets_list->GetSelection())); +} + +void PresetForPrinter::SuppressDelete() +{ + m_delete_preset_btn->Enable(false); + + // this case means that now we have only one related preset for the printer + // So, allow any selection + m_presets_list->set_printer_technology(ptAny); + m_presets_list->update(); +} + +void PresetForPrinter::AllowDelete() +{ + if (!m_delete_preset_btn->IsEnabled()) + m_delete_preset_btn->Enable(); + + m_presets_list->set_printer_technology(m_parent->get_printer_technology()); + m_presets_list->update(); +} + +void PresetForPrinter::msw_rescale() +{ + m_presets_list->msw_rescale(); + m_delete_preset_btn->msw_rescale(); +} + + +//------------------------------------------ +// PhysicalPrinterDialog +//------------------------------------------ + +PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) + : DPIDialog(NULL, wxID_ANY, _L("Physical Printer"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +{ + SetFont(wxGetApp().normal_font()); + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + + m_default_name = _L("My Printer Device"); + + if (printer_name.IsEmpty()) + printer_name = m_default_name; + else { + std::string full_name = into_u8(printer_name); + printer_name = from_u8(PhysicalPrinter::get_short_name(full_name)); + } + + wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _L("Descriptive name for the printer device") + ":"); + + m_add_preset_btn = new ScalableButton(this, wxID_ANY, "add_copies", "", wxDefaultSize, wxDefaultPosition, /*wxBU_LEFT | */wxBU_EXACTFIT); + m_add_preset_btn->SetFont(wxGetApp().normal_font()); + m_add_preset_btn->SetToolTip(_L("Add preset for this printer device")); + m_add_preset_btn->Bind(wxEVT_BUTTON, &PhysicalPrinterDialog::AddPreset, this); + + m_printer_name = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize); + m_printer_name->Bind(wxEVT_TEXT, [this](wxEvent&) { this->update_full_printer_names(); }); + + PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; + PhysicalPrinter* printer = printers.find_printer(into_u8(printer_name)); + if (!printer) { + const Preset& preset = wxGetApp().preset_bundle->printers.get_edited_preset(); + printer = new PhysicalPrinter(into_u8(printer_name), preset); + // if printer_name is empty it means that new printer is created, so enable all items in the preset list + m_presets.emplace_back(new PresetForPrinter(this, preset.name)); + } + else + { + const std::set& preset_names = printer->get_preset_names(); + for (const std::string& preset_name : preset_names) + m_presets.emplace_back(new PresetForPrinter(this, preset_name)); + } + assert(printer); + m_printer = *printer; + + if (m_presets.size() == 1) + m_presets.front()->SuppressDelete(); + + update_full_printer_names(); + + m_config = &m_printer.config; + + m_optgroup = new ConfigOptionsGroup(this, _L("Print Host upload"), m_config); + build_printhost_settings(m_optgroup); + //m_optgroup->reload_config(); + + wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); + wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); + btnOK->Bind(wxEVT_BUTTON, &PhysicalPrinterDialog::OnOK, this); + + wxBoxSizer* nameSizer = new wxBoxSizer(wxHORIZONTAL); + nameSizer->Add(m_printer_name, 1, wxEXPAND); + nameSizer->Add(m_add_preset_btn, 0, wxEXPAND | wxLEFT, BORDER_W); + + m_presets_sizer = new wxBoxSizer(wxVERTICAL); + for (PresetForPrinter* preset : m_presets) + m_presets_sizer->Add(preset->sizer(), 1, wxEXPAND | wxTOP, BORDER_W); + + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); + topSizer->Add(nameSizer , 0, wxEXPAND | wxLEFT | wxRIGHT, BORDER_W); + topSizer->Add(m_presets_sizer , 0, wxEXPAND | wxLEFT | wxRIGHT, BORDER_W); + topSizer->Add(m_optgroup->sizer , 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); + topSizer->Add(btns , 0, wxEXPAND | wxALL, BORDER_W); + + SetSizer(topSizer); + topSizer->SetSizeHints(this); +} + +PhysicalPrinterDialog::~PhysicalPrinterDialog() +{ + for (PresetForPrinter* preset : m_presets) { + delete preset; + preset = nullptr; + } +} + +void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup) +{ + m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (opt_key == "authorization_type") + this->update(); + }; + + m_optgroup->append_single_option_line("host_type"); + + auto create_sizer_with_btn = [this](wxWindow* parent, ScalableButton** btn, const std::string& icon_name, const wxString& label) { + *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); + (*btn)->SetFont(wxGetApp().normal_font()); + + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(*btn); + return sizer; + }; + + auto printhost_browse = [=](wxWindow* parent) + { + auto sizer = create_sizer_with_btn(parent, &m_printhost_browse_btn, "browse", _L("Browse") + " " + dots); + m_printhost_browse_btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent& e) { + BonjourDialog dialog(this, Preset::printer_technology(m_printer.config)); + if (dialog.show_and_lookup()) { + m_optgroup->set_value("print_host", std::move(dialog.get_selected()), true); + m_optgroup->get_field("print_host")->field_changed(); + } + }); + + return sizer; + }; + + auto print_host_test = [=](wxWindow* parent) { + auto sizer = create_sizer_with_btn(parent, &m_printhost_test_btn, "test", _L("Test")); + + m_printhost_test_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { + std::unique_ptr host(PrintHost::get_print_host(m_config)); + if (!host) { + const wxString text = _L("Could not get a valid Printer Host reference"); + show_error(this, text); + return; + } + wxString msg; + if (host->test(msg)) { + show_info(this, host->get_test_ok_msg(), _L("Success!")); + } + else { + show_error(this, host->get_test_failed_msg(msg)); + } + }); + + return sizer; + }; + + // Set a wider width for a better alignment + Option option = m_optgroup->get_option("print_host"); + option.opt.width = Field::def_width_wider(); + Line host_line = m_optgroup->create_single_option_line(option); + host_line.append_widget(printhost_browse); + host_line.append_widget(print_host_test); + m_optgroup->append_line(host_line); + + m_optgroup->append_single_option_line("authorization_type"); + + option = m_optgroup->get_option("printhost_apikey"); + option.opt.width = Field::def_width_wider(); + m_optgroup->append_single_option_line(option); + + const auto ca_file_hint = _u8L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate."); + + if (Http::ca_file_supported()) { + option = m_optgroup->get_option("printhost_cafile"); + option.opt.width = Field::def_width_wider(); + Line cafile_line = m_optgroup->create_single_option_line(option); + + auto printhost_cafile_browse = [=](wxWindow* parent) { + auto sizer = create_sizer_with_btn(parent, &m_printhost_cafile_browse_btn, "browse", _L("Browse") + " " + dots); + m_printhost_cafile_browse_btn->Bind(wxEVT_BUTTON, [this, m_optgroup](wxCommandEvent e) { + static const auto filemasks = _L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*"); + wxFileDialog openFileDialog(this, _L("Open CA certificate file"), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (openFileDialog.ShowModal() != wxID_CANCEL) { + m_optgroup->set_value("printhost_cafile", std::move(openFileDialog.GetPath()), true); + m_optgroup->get_field("printhost_cafile")->field_changed(); + } + }); + + return sizer; + }; + + cafile_line.append_widget(printhost_cafile_browse); + m_optgroup->append_line(cafile_line); + + Line cafile_hint{ "", "" }; + cafile_hint.full_width = 1; + cafile_hint.widget = [this, ca_file_hint](wxWindow* parent) { + auto txt = new wxStaticText(parent, wxID_ANY, ca_file_hint); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(txt); + return sizer; + }; + m_optgroup->append_line(cafile_hint); + } + else { + Line line{ "", "" }; + line.full_width = 1; + + line.widget = [ca_file_hint](wxWindow* parent) { + std::string info = _u8L("HTTPS CA File") + ":\n\t" + + (boost::format(_u8L("On this system, %s uses HTTPS certificates from the system Certificate Store or Keychain.")) % SLIC3R_APP_NAME).str() + + "\n\t" + _u8L("To use a custom CA file, please import your CA file into Certificate Store / Keychain."); + + //auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\n\t%2%") % info % ca_file_hint).str())); + auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\t%2%") % info % ca_file_hint).str())); + txt->SetFont(wxGetApp().normal_font()); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(txt, 1, wxEXPAND); + return sizer; + }; + + m_optgroup->append_line(line); + } + + for (const std::string& opt_key : std::vector{ "login", "password" }) { + option = m_optgroup->get_option(opt_key); + option.opt.width = Field::def_width_wider(); + m_optgroup->append_single_option_line(option); + } + + update(); +} + +void PhysicalPrinterDialog::update() +{ + m_optgroup->reload_config(); + + const PrinterTechnology tech = Preset::printer_technology(*m_config); + // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) + if (tech == ptFFF) { + m_optgroup->show_field("host_type"); + m_optgroup->hide_field("authorization_type"); + for (const std::string& opt_key : std::vector{ "login", "password" }) + m_optgroup->hide_field(opt_key); + } + else { + m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false); + m_optgroup->hide_field("host_type"); + + m_optgroup->show_field("authorization_type"); + + AuthorizationType auth_type = m_config->option>("authorization_type")->value; + m_optgroup->show_field("printhost_apikey", auth_type == AuthorizationType::atKeyPassword); + + for (const std::string& opt_key : std::vector{ "login", "password" }) + m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword); + } + + this->Layout(); +} + + +wxString PhysicalPrinterDialog::get_printer_name() +{ + return m_printer_name->GetValue(); +} + +void PhysicalPrinterDialog::update_full_printer_names() +{ + for (PresetForPrinter* preset : m_presets) + preset->update_full_printer_name(); + + this->Layout(); +} + +void PhysicalPrinterDialog::set_printer_technology(PrinterTechnology pt) +{ + m_config->set_key_value("printer_technology", new ConfigOptionEnum(pt)); + update(); +} + +PrinterTechnology PhysicalPrinterDialog::get_printer_technology() +{ + return m_printer.printer_technology(); +} + +void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) +{ + const int& em = em_unit(); + + m_printhost_browse_btn->msw_rescale(); + m_printhost_test_btn->msw_rescale(); + if (m_printhost_cafile_browse_btn) + m_printhost_cafile_browse_btn->msw_rescale(); + + m_optgroup->msw_rescale(); + + msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); + + for (PresetForPrinter* preset : m_presets) + preset->msw_rescale(); + + const wxSize& size = wxSize(45 * em, 35 * em); + SetMinSize(size); + + Fit(); + Refresh(); +} + +void PhysicalPrinterDialog::OnOK(wxEvent& event) +{ + wxString printer_name = m_printer_name->GetValue(); + if (printer_name.IsEmpty()) { + warning_catcher(this, _L("The supplied name is empty. It can't be saved.")); + return; + } + if (printer_name == m_default_name) { + warning_catcher(this, _L("You should to change a name of your printer device. It can't be saved.")); + return; + } + + PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; + const PhysicalPrinter* existing = printers.find_printer(into_u8(printer_name)); + if (existing && into_u8(printer_name) != printers.get_selected_printer_name()) + { + wxString msg_text = from_u8((boost::format(_u8L("Printer with name \"%1%\" already exists.")) % printer_name).str()); + msg_text += "\n" + _L("Replace?"); + wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); + + if (dialog.ShowModal() == wxID_NO) + return; + } + + std::set repeat_presets; + m_printer.reset_presets(); + for (PresetForPrinter* preset : m_presets) { + if (!m_printer.add_preset(preset->get_preset_name())) + repeat_presets.emplace(preset->get_preset_name()); + } + + if (!repeat_presets.empty()) + { + wxString repeatable_presets = "\n"; + for (const std::string& preset_name : repeat_presets) + repeatable_presets += " " + from_u8(preset_name) + "\n"; + repeatable_presets += "\n"; + + wxString msg_text = from_u8((boost::format(_u8L("Next printer preset(s) is(are) duplicated:%1%" + "Should I add it(they) just once for the printer \"%2%\" and close the Editing Dialog?")) % repeatable_presets % printer_name).str()); + wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); + if (dialog.ShowModal() == wxID_NO) + return; + } + + std::string renamed_from; + // temporary save previous printer name if it was edited + if (m_printer.name != _u8L("My Printer Device") && + m_printer.name != into_u8(printer_name)) + renamed_from = m_printer.name; + + //update printer name, if it was changed + m_printer.set_name(into_u8(printer_name)); + + // save new physical printer + printers.save_printer(m_printer, renamed_from); + + if (m_printer.preset_names.find(printers.get_selected_printer_preset_name()) == m_printer.preset_names.end()) { + // select first preset for this printer + printers.select_printer(m_printer); + // refresh preset list on Printer Settings Tab + wxGetApp().get_tab(Preset::TYPE_PRINTER)->select_preset(printers.get_selected_printer_preset_name()); + } + + event.Skip(); +} + +void PhysicalPrinterDialog::AddPreset(wxEvent& event) +{ + m_presets.emplace_back(new PresetForPrinter(this)); + // enable DELETE button for the first preset, if was disabled + m_presets.front()->AllowDelete(); + + m_presets_sizer->Add(m_presets.back()->sizer(), 1, wxEXPAND | wxTOP, BORDER_W); + update_full_printer_names(); + + this->Fit(); +} + +void PhysicalPrinterDialog::DeletePreset(PresetForPrinter* preset_for_printer) +{ + if (m_presets.size() == 1) { + wxString msg_text = _L("It's not possible to delete last related preset for the printer."); + wxMessageDialog dialog(nullptr, msg_text, _L("Infornation"), wxICON_INFORMATION | wxOK); + dialog.ShowModal(); + return; + } + + assert(preset_for_printer); + auto it = std::find(m_presets.begin(), m_presets.end(), preset_for_printer); + if (it == m_presets.end()) + return; + + const int remove_id = it - m_presets.begin(); + m_presets_sizer->Remove(remove_id); + delete preset_for_printer; + m_presets.erase(it); + + if (m_presets.size() == 1) + m_presets.front()->SuppressDelete(); + + this->Layout(); + this->Fit(); +} + + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.hpp b/src/slic3r/GUI/PhysicalPrinterDialog.hpp new file mode 100644 index 000000000..3d0cf2d9f --- /dev/null +++ b/src/slic3r/GUI/PhysicalPrinterDialog.hpp @@ -0,0 +1,105 @@ +#ifndef slic3r_PhysicalPrinterDialog_hpp_ +#define slic3r_PhysicalPrinterDialog_hpp_ + +#include + +#include + +#include "libslic3r/Preset.hpp" +#include "GUI_Utils.hpp" + +class wxString; +class wxTextCtrl; +class wxStaticText; +class ScalableButton; +class wxBoxSizer; + +namespace Slic3r { + +namespace GUI { + +class PresetComboBox; + +//------------------------------------------ +// PresetForPrinter +//------------------------------------------ +//static std::string g_info_string = " (modified)"; +class PhysicalPrinterDialog; +class PresetForPrinter +{ + PhysicalPrinterDialog* m_parent { nullptr }; + + PresetComboBox* m_presets_list { nullptr }; + ScalableButton* m_delete_preset_btn { nullptr }; + wxStaticText* m_info_line { nullptr }; + wxStaticText* m_full_printer_name { nullptr }; + + wxBoxSizer* m_sizer { nullptr }; + + void DeletePreset(wxEvent& event); + +public: + PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name = ""); + ~PresetForPrinter(); + + wxBoxSizer* sizer() { return m_sizer; } + void update_full_printer_name(); + std::string get_preset_name(); + void SuppressDelete(); + void AllowDelete(); + + void msw_rescale(); + void on_sys_color_changed() {}; +}; + + +//------------------------------------------ +// PhysicalPrinterDialog +//------------------------------------------ + +class ConfigOptionsGroup; +class PhysicalPrinterDialog : public DPIDialog +{ + PhysicalPrinter m_printer; + wxString m_default_name; + DynamicPrintConfig* m_config { nullptr }; + + wxTextCtrl* m_printer_name { nullptr }; + std::vector m_presets; + + ConfigOptionsGroup* m_optgroup { nullptr }; + + ScalableButton* m_add_preset_btn {nullptr}; + ScalableButton* m_printhost_browse_btn {nullptr}; + ScalableButton* m_printhost_test_btn {nullptr}; + ScalableButton* m_printhost_cafile_browse_btn {nullptr}; + + wxBoxSizer* m_presets_sizer {nullptr}; + + void build_printhost_settings(ConfigOptionsGroup* optgroup); + void OnOK(wxEvent& event); + void AddPreset(wxEvent& event); + +public: + PhysicalPrinterDialog(wxString printer_name); + ~PhysicalPrinterDialog(); + + void update(); + wxString get_printer_name(); + void update_full_printer_names(); + PhysicalPrinter* get_printer() {return &m_printer; } + void set_printer_technology(PrinterTechnology pt); + PrinterTechnology get_printer_technology(); + + void DeletePreset(PresetForPrinter* preset_for_printer); + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; + void on_sys_color_changed() override {}; +}; + + +} // namespace GUI +} // namespace Slic3r + +#endif diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index f6a2a036b..3edc3947a 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -24,20 +24,15 @@ #include "MainFrame.hpp" #include "format.hpp" #include "Tab.hpp" -#include "PrintHostDialogs.hpp" #include "ConfigWizard.hpp" #include "../Utils/ASCIIFolding.hpp" -#include "../Utils/PrintHost.hpp" #include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" -#include "RemovableDriveManager.hpp" #include "BitmapCache.hpp" -#include "BonjourDialog.hpp" +#include "PhysicalPrinterDialog.hpp" using Slic3r::GUI::format_wxstr; -static const std::pair THUMBNAIL_SIZE_3MF = { 256, 256 }; - namespace Slic3r { namespace GUI { @@ -142,6 +137,15 @@ void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type) this->SetClientData(item, (void*)label_item_type); } +bool PresetComboBox::set_printer_technology(PrinterTechnology pt) +{ + if (printer_technology != pt) { + printer_technology = pt; + return true; + } + return false; +} + void PresetComboBox::invalidate_selection() { m_last_selected = INT_MAX; // this value means that no one item is selected @@ -187,7 +191,7 @@ void PresetComboBox::update(const std::string& select_preset_name) continue; // marker used for disable incompatible printer models for the selected physical printer - bool is_enabled = true; + bool is_enabled = m_type == Preset::TYPE_PRINTER && printer_technology != ptAny ? preset.printer_technology() == printer_technology : true; std::string bitmap_key = "cb"; if (m_type == Preset::TYPE_PRINTER) { @@ -204,13 +208,13 @@ void PresetComboBox::update(const std::string& select_preset_name) int item_id = Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); if (!is_enabled) set_label_marker(item_id, LABEL_ITEM_DISABLED); - validate_selection(preset.name == select_preset_name); + validate_selection(preset.name == select_preset_name || (select_preset_name.empty() && is_enabled)); } else { std::pair pair(bmp, is_enabled); nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), std::pair(bmp, is_enabled)); - if (preset.name == select_preset_name) + if (preset.name == select_preset_name || (select_preset_name.empty() && is_enabled)) selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); } if (i + 1 == m_collection->num_default_presets()) @@ -232,6 +236,11 @@ void PresetComboBox::update(const std::string& select_preset_name) Thaw(); } +void PresetComboBox::update() +{ + this->update(into_u8(this->GetString(this->GetSelection()))); +} + void PresetComboBox::msw_rescale() { m_em_unit = em_unit(this); @@ -547,7 +556,7 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset edit_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent) { // In a case of a physical printer, for its editing open PhysicalPrinterDialog - if (m_type == Preset::TYPE_PRINTER && this->is_selected_physical_printer()) { + if (m_type == Preset::TYPE_PRINTER/* && this->is_selected_physical_printer()*/) { this->show_edit_menu(); return; } @@ -598,7 +607,7 @@ void PlaterPresetComboBox::show_add_menu() { wxMenu* menu = new wxMenu(); - append_menu_item(menu, wxID_ANY, _L("Add/Remove logical printers"), "", + append_menu_item(menu, wxID_ANY, _L("Add/Remove presets"), "", [this](wxCommandEvent&) { wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); }); }, "edit_uni", menu, []() { return true; }, wxGetApp().plater()); @@ -617,9 +626,10 @@ void PlaterPresetComboBox::show_edit_menu() { wxMenu* menu = new wxMenu(); - append_menu_item(menu, wxID_ANY, _L("Edit related printer profile"), "", + append_menu_item(menu, wxID_ANY, _L("Edit preset"), "", [this](wxCommandEvent&) { this->switch_to_tab(); }, "cog", menu, []() { return true; }, wxGetApp().plater()); + if (this->is_selected_physical_printer()) { append_menu_item(menu, wxID_ANY, _L("Edit physical printer"), "", [this](wxCommandEvent&) { PhysicalPrinterDialog dlg(this->GetString(this->GetSelection())); @@ -642,6 +652,12 @@ void PlaterPresetComboBox::show_edit_menu() wxGetApp().get_tab(m_type)->update_preset_choice(); update(); }, "cross", menu, []() { return true; }, wxGetApp().plater()); + } + else + append_menu_item(menu, wxID_ANY, _L("Add/Remove presets"), "", + [this](wxCommandEvent&) { + wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); }); + }, "edit_uni", menu, []() { return true; }, wxGetApp().plater()); wxGetApp().plater()->PopupMenu(menu); } @@ -768,7 +784,7 @@ void PlaterPresetComboBox::update() } } - if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) { + if (/*m_type == Preset::TYPE_PRINTER || */m_type == Preset::TYPE_SLA_MATERIAL) { wxBitmap* bmp = get_bmp("edit_preset_list", wide_icons, "edit_uni"); assert(bmp); @@ -867,9 +883,6 @@ void TabPresetComboBox::update() // marker used for disable incompatible printer models for the selected physical printer bool is_enabled = true; - // check this value just for printer presets, when physical printer is selected - if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) - is_enabled = m_enable_all ? true : preset.printer_technology() == proper_pt; std::string bitmap_key = "tab"; if (m_type == Preset::TYPE_PRINTER) { @@ -1040,494 +1053,6 @@ void TabPresetComboBox::update_physical_printers( const std::string& preset_name } -//------------------------------------------ -// PresetForPrinter -//------------------------------------------ - -PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name) : - m_parent(parent) -{ - m_sizer = new wxBoxSizer(wxVERTICAL); - - m_delete_preset_btn = new ScalableButton(parent, wxID_ANY, "cross", "", wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); - m_delete_preset_btn->SetFont(wxGetApp().normal_font()); - m_delete_preset_btn->SetToolTip(_L("Delete this preset from this printer device")); - m_delete_preset_btn->Bind(wxEVT_BUTTON, &PresetForPrinter::DeletePreset, this); - - m_presets_list = new PresetComboBox(parent, Preset::TYPE_PRINTER); - - m_presets_list->set_selection_changed_function([this](int selection) { - std::string selected_string = Preset::remove_suffix_modified(m_presets_list->GetString(selection).ToUTF8().data()); - Preset* preset = wxGetApp().preset_bundle->printers.find_preset(selected_string); - assert(preset); - Preset& edited_preset = wxGetApp().preset_bundle->printers.get_edited_preset(); - if (preset->name == edited_preset.name) - preset = &edited_preset; - - // if created physical printer doesn't have any settings, use the settings from the selected preset - if (m_parent->get_printer()->has_empty_config()) { - // update Print Host upload from the selected preset - m_parent->get_printer()->update_from_preset(*preset); - // update values in parent (PhysicalPrinterDialog) - m_parent->update(); - } - - update_full_printer_name(); - }); - m_presets_list->update(preset_name); - - m_info_line = new wxStaticText(parent, wxID_ANY, _L("This printer will be shown in the presets list as") + ":"); - - m_full_printer_name = new wxStaticText(parent, wxID_ANY, ""); - m_full_printer_name->SetFont(wxGetApp().bold_font()); - - wxBoxSizer* preset_sizer = new wxBoxSizer(wxHORIZONTAL); - preset_sizer->Add(m_presets_list , 1, wxEXPAND); - preset_sizer->Add(m_delete_preset_btn , 0, wxEXPAND | wxLEFT, BORDER_W); - - wxBoxSizer* name_sizer = new wxBoxSizer(wxHORIZONTAL); - name_sizer->Add(m_info_line, 0, wxEXPAND); - name_sizer->Add(m_full_printer_name, 0, wxEXPAND | wxLEFT, BORDER_W); - - m_sizer->Add(preset_sizer , 0, wxEXPAND); - m_sizer->Add(name_sizer, 0, wxEXPAND); -} - -PresetForPrinter::~PresetForPrinter() -{ - m_presets_list->Destroy(); - m_delete_preset_btn->Destroy(); - m_info_line->Destroy(); - m_full_printer_name->Destroy(); -} - -void PresetForPrinter::DeletePreset(wxEvent& event) -{ - m_parent->DeletePreset(this); -} - -void PresetForPrinter::update_full_printer_name() -{ - wxString printer_name = m_parent->get_printer_name(); - wxString preset_name = m_presets_list->GetString(m_presets_list->GetSelection()); - - m_full_printer_name->SetLabelText(printer_name + " * " + preset_name); -} - -std::string PresetForPrinter::get_preset_name() -{ - return into_u8(m_presets_list->GetString(m_presets_list->GetSelection())); -} - -void PresetForPrinter::DisableDeleteBtn() -{ - m_delete_preset_btn->Enable(false); -} - -void PresetForPrinter::EnableDeleteBtn() -{ - if (!m_delete_preset_btn->IsEnabled()) - m_delete_preset_btn->Enable(); -} - -void PresetForPrinter::msw_rescale() -{ - m_presets_list->msw_rescale(); - m_delete_preset_btn->msw_rescale(); -} - - -//------------------------------------------ -// PhysicalPrinterDialog -//------------------------------------------ - -PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) - : DPIDialog(NULL, wxID_ANY, _L("Physical Printer"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) -{ - SetFont(wxGetApp().normal_font()); - SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - - m_default_name = _L("My Printer Device"); - - if (printer_name.IsEmpty()) - printer_name = m_default_name; - else { - std::string full_name = into_u8(printer_name); - printer_name = from_u8(PhysicalPrinter::get_short_name(full_name)); - } - - wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _L("Descriptive name for the printer device") + ":"); - - m_add_preset_btn = new ScalableButton(this, wxID_ANY, "add_copies", "", wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); - m_add_preset_btn->SetFont(wxGetApp().normal_font()); - m_add_preset_btn->SetToolTip(_L("Add preset for this printer device")); - m_add_preset_btn->Bind(wxEVT_BUTTON, &PhysicalPrinterDialog::AddPreset, this); - - m_printer_name = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize); - m_printer_name->Bind(wxEVT_TEXT, [this](wxEvent&) { this->update_full_printer_names(); }); - - PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; - PhysicalPrinter* printer = printers.find_printer(into_u8(printer_name)); - if (!printer) { - const Preset& preset = wxGetApp().preset_bundle->printers.get_edited_preset(); - printer = new PhysicalPrinter(into_u8(printer_name), preset); - // if printer_name is empty it means that new printer is created, so enable all items in the preset list - m_presets.emplace_back(new PresetForPrinter(this, preset.name)); - } - else - { - const std::set& preset_names = printer->get_preset_names(); - for (const std::string& preset_name : preset_names) - m_presets.emplace_back(new PresetForPrinter(this, preset_name)); - } - assert(printer); - m_printer = *printer; - - if (m_presets.size() == 1) - m_presets.front()->DisableDeleteBtn(); - - update_full_printer_names(); - - m_config = &m_printer.config; - - m_optgroup = new ConfigOptionsGroup(this, _L("Print Host upload"), m_config); - build_printhost_settings(m_optgroup); - //m_optgroup->reload_config(); - - wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); - wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); - btnOK->Bind(wxEVT_BUTTON, &PhysicalPrinterDialog::OnOK, this); - - wxBoxSizer* nameSizer = new wxBoxSizer(wxHORIZONTAL); - nameSizer->Add(m_printer_name, 1, wxEXPAND); - nameSizer->Add(m_add_preset_btn, 0, wxEXPAND | wxLEFT, BORDER_W); - - m_presets_sizer = new wxBoxSizer(wxVERTICAL); - for (PresetForPrinter* preset : m_presets) - m_presets_sizer->Add(preset->sizer(), 1, wxEXPAND | wxTOP, BORDER_W); - - wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); - - topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); - topSizer->Add(nameSizer , 0, wxEXPAND | wxLEFT | wxRIGHT, BORDER_W); - topSizer->Add(m_presets_sizer , 0, wxEXPAND | wxLEFT | wxRIGHT, BORDER_W); - topSizer->Add(m_optgroup->sizer , 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); - topSizer->Add(btns , 0, wxEXPAND | wxALL, BORDER_W); - - SetSizer(topSizer); - topSizer->SetSizeHints(this); -} - -PhysicalPrinterDialog::~PhysicalPrinterDialog() -{ - for (PresetForPrinter* preset : m_presets) { - delete preset; - preset = nullptr; - } -} - -void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup) -{ - m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - if (opt_key == "authorization_type") - this->update(); - }; - - m_optgroup->append_single_option_line("host_type"); - - auto create_sizer_with_btn = [this](wxWindow* parent, ScalableButton** btn, const std::string& icon_name, const wxString& label) { - *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); - (*btn)->SetFont(wxGetApp().normal_font()); - - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(*btn); - return sizer; - }; - - auto printhost_browse = [=](wxWindow* parent) - { - auto sizer = create_sizer_with_btn(parent, &m_printhost_browse_btn, "browse", _L("Browse") + " " + dots); - m_printhost_browse_btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent& e) { - BonjourDialog dialog(this, Preset::printer_technology(m_printer.config)); - if (dialog.show_and_lookup()) { - m_optgroup->set_value("print_host", std::move(dialog.get_selected()), true); - m_optgroup->get_field("print_host")->field_changed(); - } - }); - - return sizer; - }; - - auto print_host_test = [=](wxWindow* parent) { - auto sizer = create_sizer_with_btn(parent, &m_printhost_test_btn, "test", _L("Test")); - - m_printhost_test_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { - std::unique_ptr host(PrintHost::get_print_host(m_config)); - if (!host) { - const wxString text = _L("Could not get a valid Printer Host reference"); - show_error(this, text); - return; - } - wxString msg; - if (host->test(msg)) { - show_info(this, host->get_test_ok_msg(), _L("Success!")); - } - else { - show_error(this, host->get_test_failed_msg(msg)); - } - }); - - return sizer; - }; - - // Set a wider width for a better alignment - Option option = m_optgroup->get_option("print_host"); - option.opt.width = Field::def_width_wider(); - Line host_line = m_optgroup->create_single_option_line(option); - host_line.append_widget(printhost_browse); - host_line.append_widget(print_host_test); - m_optgroup->append_line(host_line); - - m_optgroup->append_single_option_line("authorization_type"); - - option = m_optgroup->get_option("printhost_apikey"); - option.opt.width = Field::def_width_wider(); - m_optgroup->append_single_option_line(option); - - const auto ca_file_hint = _u8L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate."); - - if (Http::ca_file_supported()) { - option = m_optgroup->get_option("printhost_cafile"); - option.opt.width = Field::def_width_wider(); - Line cafile_line = m_optgroup->create_single_option_line(option); - - auto printhost_cafile_browse = [=](wxWindow* parent) { - auto sizer = create_sizer_with_btn(parent, &m_printhost_cafile_browse_btn, "browse", _L("Browse") + " " + dots); - m_printhost_cafile_browse_btn->Bind(wxEVT_BUTTON, [this, m_optgroup](wxCommandEvent e) { - static const auto filemasks = _L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*"); - wxFileDialog openFileDialog(this, _L("Open CA certificate file"), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (openFileDialog.ShowModal() != wxID_CANCEL) { - m_optgroup->set_value("printhost_cafile", std::move(openFileDialog.GetPath()), true); - m_optgroup->get_field("printhost_cafile")->field_changed(); - } - }); - - return sizer; - }; - - cafile_line.append_widget(printhost_cafile_browse); - m_optgroup->append_line(cafile_line); - - Line cafile_hint{ "", "" }; - cafile_hint.full_width = 1; - cafile_hint.widget = [this, ca_file_hint](wxWindow* parent) { - auto txt = new wxStaticText(parent, wxID_ANY, ca_file_hint); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(txt); - return sizer; - }; - m_optgroup->append_line(cafile_hint); - } - else { - Line line{ "", "" }; - line.full_width = 1; - - line.widget = [ca_file_hint](wxWindow* parent) { - std::string info = _u8L("HTTPS CA File") + ":\n\t" + - (boost::format(_u8L("On this system, %s uses HTTPS certificates from the system Certificate Store or Keychain.")) % SLIC3R_APP_NAME).str() + - "\n\t" + _u8L("To use a custom CA file, please import your CA file into Certificate Store / Keychain."); - - //auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\n\t%2%") % info % ca_file_hint).str())); - auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\t%2%") % info % ca_file_hint).str())); - txt->SetFont(wxGetApp().normal_font()); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(txt, 1, wxEXPAND); - return sizer; - }; - - m_optgroup->append_line(line); - } - - for (const std::string& opt_key : std::vector{ "login", "password" }) { - option = m_optgroup->get_option(opt_key); - option.opt.width = Field::def_width_wider(); - m_optgroup->append_single_option_line(option); - } - - update(); -} - -void PhysicalPrinterDialog::update() -{ - m_optgroup->reload_config(); - - const PrinterTechnology tech = Preset::printer_technology(m_printer.config); - // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) - if (tech == ptFFF) { - m_optgroup->show_field("host_type"); - m_optgroup->hide_field("authorization_type"); - for (const std::string& opt_key : std::vector{ "login", "password" }) - m_optgroup->hide_field(opt_key); - } - else { - m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false); - m_optgroup->hide_field("host_type"); - - m_optgroup->show_field("authorization_type"); - - AuthorizationType auth_type = m_config->option>("authorization_type")->value; - m_optgroup->show_field("printhost_apikey", auth_type == AuthorizationType::atKeyPassword); - - for (const std::string& opt_key : std::vector{ "login", "password" }) - m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword); - } - - this->Layout(); -} - - -wxString PhysicalPrinterDialog::get_printer_name() -{ - return m_printer_name->GetValue(); -} - -void PhysicalPrinterDialog::update_full_printer_names() -{ - for (PresetForPrinter* preset : m_presets) - preset->update_full_printer_name(); - - this->Layout(); -} - -void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) -{ - const int& em = em_unit(); - - m_printhost_browse_btn->msw_rescale(); - m_printhost_test_btn->msw_rescale(); - if (m_printhost_cafile_browse_btn) - m_printhost_cafile_browse_btn->msw_rescale(); - - m_optgroup->msw_rescale(); - - msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); - - for (PresetForPrinter* preset : m_presets) - preset->msw_rescale(); - - const wxSize& size = wxSize(45 * em, 35 * em); - SetMinSize(size); - - Fit(); - Refresh(); -} - -void PhysicalPrinterDialog::OnOK(wxEvent& event) -{ - wxString printer_name = m_printer_name->GetValue(); - if (printer_name.IsEmpty()) { - warning_catcher(this, _L("The supplied name is empty. It can't be saved.")); - return; - } - if (printer_name == m_default_name) { - warning_catcher(this, _L("You should to change a name of your printer device. It can't be saved.")); - return; - } - - PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; - const PhysicalPrinter* existing = printers.find_printer(into_u8(printer_name)); - if (existing && into_u8(printer_name) != printers.get_selected_printer_name()) - { - wxString msg_text = from_u8((boost::format(_u8L("Printer with name \"%1%\" already exists.")) % printer_name).str()); - msg_text += "\n" + _L("Replace?"); - wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); - - if (dialog.ShowModal() == wxID_NO) - return; - } - - std::set repeat_presets; - m_printer.reset_presets(); - for (PresetForPrinter* preset : m_presets) { - if (!m_printer.add_preset(preset->get_preset_name())) - repeat_presets.emplace(preset->get_preset_name()); - } - - if (!repeat_presets.empty()) - { - wxString repeatable_presets = "\n"; - for (const std::string& preset_name : repeat_presets) - repeatable_presets += " " + from_u8(preset_name) + "\n"; - repeatable_presets += "\n"; - - wxString msg_text = from_u8((boost::format(_u8L("Next printer preset(s) is(are) duplicated:%1%" - "Should I add it(they) just once for the printer \"%2%\" and close the Editing Dialog?")) % repeatable_presets % printer_name).str()); - wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); - if (dialog.ShowModal() == wxID_NO) - return; - } - - std::string renamed_from; - // temporary save previous printer name if it was edited - if (m_printer.name != _u8L("My Printer Device") && - m_printer.name != into_u8(printer_name)) - renamed_from = m_printer.name; - - //update printer name, if it was changed - m_printer.set_name(into_u8(printer_name)); - - // save new physical printer - printers.save_printer(m_printer, renamed_from); - - if (m_printer.preset_names.find(printers.get_selected_printer_preset_name()) == m_printer.preset_names.end()) { - // select first preset for this printer - printers.select_printer(m_printer); - // refresh preset list on Printer Settings Tab - wxGetApp().get_tab(Preset::TYPE_PRINTER)->select_preset(printers.get_selected_printer_preset_name()); - } - - event.Skip(); -} - -void PhysicalPrinterDialog::AddPreset(wxEvent& event) -{ - m_presets.emplace_back(new PresetForPrinter(this)); - // enable DELETE button for the first preset, if was disabled - m_presets.front()->EnableDeleteBtn(); - - m_presets_sizer->Add(m_presets.back()->sizer(), 1, wxEXPAND | wxTOP, BORDER_W); - update_full_printer_names(); - - this->Fit(); -} - -void PhysicalPrinterDialog::DeletePreset(PresetForPrinter* preset_for_printer) -{ - if (m_presets.size() == 1) { - wxString msg_text = _L("It's not possible to delete last related preset for the printer."); - wxMessageDialog dialog(nullptr, msg_text, _L("Infornation"), wxICON_INFORMATION | wxOK); - dialog.ShowModal(); - return; - } - - assert(preset_for_printer); - auto it = std::find(m_presets.begin(), m_presets.end(), preset_for_printer); - if (it == m_presets.end()) - return; - - const int remove_id = it - m_presets.begin(); - m_presets_sizer->Remove(remove_id); - delete preset_for_printer; - m_presets.erase(it); - - if (m_presets.size() == 1) - m_presets.front()->DisableDeleteBtn(); - - this->Layout(); - this->Fit(); -} - - //----------------------------------------------- // ChangePresetForPhysicalPrinterDialog //----------------------------------------------- @@ -1548,9 +1073,9 @@ ChangePresetForPhysicalPrinterDialog::ChangePresetForPhysicalPrinterDialog(const wxStaticText* label_top = new wxStaticText(this, wxID_ANY, msg_text); label_top->SetFont(wxGetApp().bold_font()); - wxString choices[] = { from_u8((boost::format(_u8L("Just switch to \"%1%\"")) % preset_name).str()), - from_u8((boost::format(_u8L("Change \"%1%\" to \"%2%\" for this physical printer")) % old_preset_name % preset_name).str()), - from_u8((boost::format(_u8L("Add \"%1%\" as a next preset for the the physical printer")) % preset_name).str()) }; + wxString choices[] = { from_u8((boost::format(_u8L("Change \"%1%\" to \"%2%\" for this physical printer")) % old_preset_name % preset_name).str()), + from_u8((boost::format(_u8L("Add \"%1%\" as a next preset for the the physical printer")) % preset_name).str()), + from_u8((boost::format(_u8L("Just switch to \"%1%\"")) % preset_name).str()) }; wxRadioBox* selection_type_box = new wxRadioBox(this, wxID_ANY, _L("What would you like to do?"), wxDefaultPosition, wxDefaultSize, WXSIZEOF(choices), choices, 3, wxRA_SPECIFY_ROWS); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 9a70818e1..6b8017f31 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -1,16 +1,12 @@ #ifndef slic3r_PresetComboBoxes_hpp_ #define slic3r_PresetComboBoxes_hpp_ -#include - -#include #include #include #include "libslic3r/Preset.hpp" #include "wxExtensions.hpp" #include "GUI_Utils.hpp" -//#include "BitmapCache.hpp" class wxString; class wxTextCtrl; @@ -23,6 +19,7 @@ namespace Slic3r { namespace GUI { class BitmapCache; + // --------------------------------- // *** PresetComboBox *** // --------------------------------- @@ -47,8 +44,9 @@ public: }; void set_label_marker(int item, LabelItemType label_item_type = LABEL_ITEM_MARKER); + bool set_printer_technology(PrinterTechnology pt); - void set_selection_changed_function(std::function sel_changed) { on_selection_changed = sel_changed; } + void set_selection_changed_function(std::function sel_changed) { on_selection_changed = sel_changed; } bool is_selected_physical_printer(); @@ -58,7 +56,7 @@ public: void update(const std::string& select_preset); - virtual void update(){}; + virtual void update(); virtual void msw_rescale(); protected: @@ -91,6 +89,8 @@ protected: int thin_space_icon_width; int wide_space_icon_width; + PrinterTechnology printer_technology {ptAny}; + void invalidate_selection(); void validate_selection(bool predicate = false); void update_selection(); @@ -188,83 +188,6 @@ public: }; -//------------------------------------------ -// PresetForPrinter -//------------------------------------------ -static std::string g_info_string = " (modified)"; -class PhysicalPrinterDialog; -class PresetForPrinter -{ - PhysicalPrinterDialog* m_parent { nullptr }; - - PresetComboBox* m_presets_list { nullptr }; - ScalableButton* m_delete_preset_btn { nullptr }; - wxStaticText* m_info_line { nullptr }; - wxStaticText* m_full_printer_name { nullptr }; - - wxBoxSizer* m_sizer { nullptr }; - - void DeletePreset(wxEvent& event); - -public: - PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name = ""); - ~PresetForPrinter(); - - wxBoxSizer* sizer() { return m_sizer; } - void update_full_printer_name(); - std::string get_preset_name(); - void DisableDeleteBtn(); - void EnableDeleteBtn(); - - void msw_rescale(); - void on_sys_color_changed() {}; -}; - - -//------------------------------------------ -// PhysicalPrinterDialog -//------------------------------------------ - -class ConfigOptionsGroup; -class PhysicalPrinterDialog : public DPIDialog -{ - PhysicalPrinter m_printer; - wxString m_default_name; - DynamicPrintConfig* m_config { nullptr }; - - wxTextCtrl* m_printer_name { nullptr }; - std::vector m_presets; - - ConfigOptionsGroup* m_optgroup { nullptr }; - - ScalableButton* m_add_preset_btn {nullptr}; - ScalableButton* m_printhost_browse_btn {nullptr}; - ScalableButton* m_printhost_test_btn {nullptr}; - ScalableButton* m_printhost_cafile_browse_btn {nullptr}; - - wxBoxSizer* m_presets_sizer {nullptr}; - - void build_printhost_settings(ConfigOptionsGroup* optgroup); - void OnOK(wxEvent& event); - void AddPreset(wxEvent& event); - -public: - PhysicalPrinterDialog(wxString printer_name); - ~PhysicalPrinterDialog(); - - void update(); - wxString get_printer_name(); - void update_full_printer_names(); - PhysicalPrinter* get_printer() {return &m_printer; } - - void DeletePreset(PresetForPrinter* preset_for_printer); - -protected: - void on_dpi_changed(const wxRect& suggested_rect) override; - void on_sys_color_changed() override {}; -}; - - //------------------------------------------------ // ChangePresetForPhysicalPrinterDialog //------------------------------------------------ From 953d1417a0a751ffcb9fc5c93d9e42a203a0cda4 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 11 Jun 2020 13:09:34 +0200 Subject: [PATCH 21/70] TriangleSelector: draft of interface --- src/libslic3r/Model.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 66 ++++++++++++++++++ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 71 ++++++++++++++++++++ 3 files changed, 138 insertions(+) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index e5930fb8a..be298ae4b 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -394,6 +394,7 @@ enum class ModelVolumeType : int { }; enum class FacetSupportType : int8_t { + // Maximum is 3. The value is serialized in TriangleSelector into 2 bits! NONE = 0, ENFORCER = 1, BLOCKER = 2 diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index cd4285724..00e236b64 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -805,6 +805,10 @@ void GLGizmoFdmSupports::on_set_state() activate_internal_undo_redo_stack(true); }); } + + TriangleSelector ts{TriangleMesh()}; + ts.test(); + } if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off // we are actually shutting down @@ -854,5 +858,67 @@ void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const +void TriangleSelector::test() +{ + DivisionNode node; + while (true) { + std::cout << "Zadej pocet stran a spec stranu: "; + int num; + int spec; + std::cin >> num >> spec; + node.set_division(num, spec); + std::cout << node.number_of_split_sides() << " " + << (node.number_of_split_sides()==1 ? node.side_to_split() : node.side_to_keep()) + << std::endl << std::endl; + } +} + + +void TriangleSelector::DivisionNode::set_division(int sides_to_split, int special_side_idx) +{ + assert(sides_to_split >=0 && sides_to_split <= 3); + assert(special_side_idx >=-1 && special_side_idx < 3); + + // If splitting one or two sides, second argument must be provided. + assert(sides_to_split != 1 || special_side_idx != -1); + assert(sides_to_split != 2 || special_side_idx != -1); + + division_type = sides_to_split | (special_side_idx != -1 ? (special_side_idx << 2) : 0 ); +} + + + +void TriangleSelector::DivisionNode::set_type(FacetSupportType type) +{ + // If this is not a leaf-node, this makes no sense and + // the bits are used for storing index of an edge. + assert(number_of_split_sides() == 0); + division_type = type | (type << 2); +} + + + +int TriangleSelector::DivisionNode::side_to_keep() const +{ + assert(number_of_split_sides() == 2); + return (division_type & 0b1100) >> 2; +} + + + +int TriangleSelector::DivisionNode::side_to_split() const +{ + assert(number_of_split_sides() == 1); + return (division_type & 0b1100) >> 2; +} + + + +FacetSupportType TriangleSelector::DivisionNode::get_type() const +{ + assert(number_of_split_sides() == 0); // this must be leaf + return (division_type & 0b1100) >> 2; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index c4f5b153e..f815a8063 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -19,6 +19,77 @@ namespace GUI { enum class SLAGizmoEventType : unsigned char; class ClippingPlane; + +// Following class holds information about selected triangles. It also has power +// to recursively subdivide the triangles and make the selection finer. +class TriangleSelector { +public: + void test(); + explicit TriangleSelector(const TriangleMesh& mesh) {} + + // Select all triangles inside the circle, subdivide where needed. + void select_patch(const Vec3f& hit, // point where to start + int facet_idx, // facet that point belongs to + const Vec3f& dir, // direction of the ray + float radius_sqr, // squared radius of the cursor + bool enforcer); // enforcer or blocker? + + void unselect_all(); + + // Remove all unnecessary data (such as vertices that are not needed + // because the selection has been made larger. + void garbage_collect(); + +private: + // A struct to hold information about how a triangle was divided. + struct DivisionNode { + // Index of triangle this describes. + int triangle_idx; + + // Bitmask encoding which sides are split. + unsigned char division_type; + // bits 0 and 1 : 00 - no division + // 01 - one-edge split + // 10 - two-edge split + // 11 - three-edge split + // bits 2 and 3 : decimal 0, 1 or 2 identifying the special edge (one that + // splits in one-edge split or one that stays in two-edge split). + + // Pointers to children nodes (not all are always used). + std::array children; + + // Set the division type. + void set_division(int sides_to_split, int special_side_idx = -1); + + // Helpers that decode the division_type bitmask. + int number_of_split_sides() const { return division_type & 0b11; } + int side_to_keep() const; + int side_to_split() const; + }; + + // Triangle and pointer to how it's divided (nullptr = not divided). + // The ptr is nullptr for all new triangles, it is only valid for + // the original (undivided) triangles. + struct Triangle { + stl_triangle_vertex_indices verts_idxs; + DivisionNode* div_info; + }; + + // Lists of vertices and triangles, both original and new + std::vector m_vertices; + std::vector m_triangles; + + // Number of original vertices and triangles. + int m_orig_size_vertices; + int m_orig_size_indices; + + // Limits for stopping the recursion. + float m_max_edge_length; + int m_max_recursion_depth; +}; + + + class GLGizmoFdmSupports : public GLGizmoBase { private: From d2b2446b077fb90b7211cc4a2a3e20e1f02e7fdc Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 16 Jun 2020 16:10:26 +0200 Subject: [PATCH 22/70] TriangleSelector: first partially working implementation --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 500 ++++++++++++++----- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 65 ++- 2 files changed, 422 insertions(+), 143 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 00e236b64..1b045f38e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -16,7 +16,6 @@ namespace Slic3r { namespace GUI { -static constexpr size_t MaxVertexBuffers = 50; GLGizmoFdmSupports::GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) @@ -90,12 +89,13 @@ void GLGizmoFdmSupports::set_fdm_support_data(ModelObject* model_object, const S void GLGizmoFdmSupports::on_render() const { - const Selection& selection = m_parent.get_selection(); + //const Selection& selection = m_parent.get_selection(); glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); - render_triangles(selection); + //render_triangles(selection); + m_triangle_selector->render(); m_c->object_clipper()->render_cut(); render_cursor_circle(); @@ -146,13 +146,13 @@ void GLGizmoFdmSupports::render_triangles(const Selection& selection) const glsafe(::glMultMatrixd(trafo_matrix.data())); // Now render both enforcers and blockers. - for (int i=0; i<2; ++i) { - glsafe(::glColor4f(i ? 1.f : 0.2f, 0.2f, i ? 0.2f : 1.0f, 0.5f)); - for (const GLIndexedVertexArray& iva : m_ivas[mesh_id][i]) { - if (iva.has_VBOs()) - iva.render(); - } - } + //for (int i=0; i<2; ++i) { + // glsafe(::glColor4f(i ? 1.f : 0.2f, 0.2f, i ? 0.2f : 1.0f, 0.5f)); + // for (const GLIndexedVertexArray& iva : m_ivas[mesh_id][i]) { + if (m_iva.has_VBOs()) + m_iva.render(); + // } + //} glsafe(::glPopMatrix()); if (is_left_handed) glsafe(::glFrontFace(GL_CCW)); @@ -209,7 +209,8 @@ void GLGizmoFdmSupports::render_cursor_circle() const void GLGizmoFdmSupports::update_model_object() const { - ModelObject* mo = m_c->selection_info()->model_object(); + return; + /*ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { ++idx; @@ -217,7 +218,7 @@ void GLGizmoFdmSupports::update_model_object() const continue; for (int i=0; im_supported_facets.set_facet(i, m_selected_facets[idx][i]); - } + }*/ } @@ -226,13 +227,15 @@ void GLGizmoFdmSupports::update_from_model_object() wxBusyCursor wait; const ModelObject* mo = m_c->selection_info()->model_object(); - size_t num_of_volumes = 0; + /*size_t num_of_volumes = 0; for (const ModelVolume* mv : mo->volumes) if (mv->is_model_part()) ++num_of_volumes; - m_selected_facets.resize(num_of_volumes); + m_selected_facets.resize(num_of_volumes);*/ - m_ivas.clear(); + m_triangle_selector = std::make_unique(mo->volumes.front()->mesh()); + + /*m_ivas.clear(); m_ivas.resize(num_of_volumes); for (size_t i=0; ivolumes) { if (! mv->is_model_part()) continue; ++mesh_id; if (mesh_id == closest_hit_mesh_id) { - mesh = &mv->mesh(); + //mesh = &mv->mesh(); break; } } - bool update_both = false; + // FIXME: just for now, only process first mesh + if (mesh_id != 0) + return false; const Transform3d& trafo_matrix = trafo_matrices[mesh_id]; @@ -426,80 +435,10 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.; const float limit = pow(m_cursor_radius/avg_scaling , 2.f); - const std::pair& hit_and_facet = { closest_hit, closest_facet }; - // Calculate direction from camera to the hit (in mesh coords): - Vec3f dir = ((trafo_matrix.inverse() * camera.get_position()).cast() - hit_and_facet.first).normalized(); + Vec3f dir = ((trafo_matrix.inverse() * camera.get_position()).cast() - closest_hit).normalized(); - // A lambda to calculate distance from the centerline: - auto squared_distance_from_line = [&hit_and_facet, &dir](const Vec3f& point) -> float { - Vec3f diff = hit_and_facet.first - point; - return (diff - diff.dot(dir) * dir).squaredNorm(); - }; - - // A lambda to determine whether this facet is potentionally visible (still can be obscured) - auto faces_camera = [&dir, &mesh](const size_t& facet) -> bool { - return (mesh->stl.facet_start[facet].normal.dot(dir) > 0.); - }; - // Now start with the facet the pointer points to and check all adjacent facets. - std::vector facets_to_select{hit_and_facet.second}; - std::vector visited(m_selected_facets[mesh_id].size(), false); // keep track of facets we already processed - size_t facet_idx = 0; // index into facets_to_select - while (facet_idx < facets_to_select.size()) { - size_t facet = facets_to_select[facet_idx]; - if (! visited[facet]) { - // check all three vertices and in case they're close enough, - // add neighboring facets to be proccessed later - for (size_t i=0; i<3; ++i) { - float dist = squared_distance_from_line( - mesh->its.vertices[mesh->its.indices[facet](i)]); - if (dist < limit) { - for (int n=0; n<3; ++n) { - if (faces_camera(mesh->stl.neighbors_start[facet].neighbor[n])) - facets_to_select.push_back(mesh->stl.neighbors_start[facet].neighbor[n]); - } - } - } - visited[facet] = true; - } - ++facet_idx; - } - - std::vector new_facets; - new_facets.reserve(facets_to_select.size()); - - // Now just select all facets that passed and remember which - // ones have really changed state. - for (size_t next_facet : facets_to_select) { - FacetSupportType& facet = m_selected_facets[mesh_id][next_facet]; - - if (facet != new_state) { - if (facet != FacetSupportType::NONE) { - // this triangle is currently in the other VBA. - // Both VBAs need to be refreshed. - update_both = true; - } - facet = new_state; - new_facets.push_back(next_facet); - } - } - - if (! new_facets.empty()) { - if (new_state != FacetSupportType::NONE) { - // append triangles into the respective VBA - update_vertex_buffers(mesh, mesh_id, new_state, &new_facets); - if (update_both) { - auto other = new_state == FacetSupportType::ENFORCER - ? FacetSupportType::BLOCKER - : FacetSupportType::ENFORCER; - update_vertex_buffers(mesh, mesh_id, other); // regenerate the other VBA - } - } - else { - update_vertex_buffers(mesh, mesh_id, FacetSupportType::ENFORCER); - update_vertex_buffers(mesh, mesh_id, FacetSupportType::BLOCKER); - } - } + m_triangle_selector->select_patch(closest_hit, closest_facet, dir, limit, new_state); return true; } @@ -529,7 +468,7 @@ void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, FacetSupportType type, const std::vector* new_facets) { - std::vector& ivas = m_ivas[mesh_id][type == FacetSupportType::ENFORCER ? 0 : 1]; + //std::vector& ivas = m_ivas[mesh_id][type == FacetSupportType::ENFORCER ? 0 : 1]; // lambda to push facet into vertex buffer auto push_facet = [this, &mesh, &mesh_id](size_t idx, GLIndexedVertexArray& iva) { @@ -543,24 +482,26 @@ void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, }; - if (ivas.size() == MaxVertexBuffers || ! new_facets) { + //if (ivas.size() == MaxVertexBuffers || ! new_facets) { // If there are too many or they should be regenerated, make one large // GLVertexBufferArray. - ivas.clear(); // destructors release geometry - ivas.push_back(GLIndexedVertexArray()); + //ivas.clear(); // destructors release geometry + //ivas.push_back(GLIndexedVertexArray()); + + m_iva.release_geometry(); + m_iva.clear(); bool pushed = false; for (size_t facet_idx=0; facet_idxselection_info()->model_object(); @@ -615,6 +558,7 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwr update_model_object(); m_parent.set_as_dirty(); m_setting_angle = false; +*/ } @@ -670,7 +614,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SameLine(); if (m_imgui->button(m_desc.at("remove_all"))) { - ModelObject* mo = m_c->selection_info()->model_object(); + /*ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { ++idx; @@ -681,7 +625,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l update_vertex_buffers(&mv->mesh(), idx, FacetSupportType::BLOCKER); m_parent.set_as_dirty(); } - } + }*/ } const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; @@ -805,10 +749,6 @@ void GLGizmoFdmSupports::on_set_state() activate_internal_undo_redo_stack(true); }); } - - TriangleSelector ts{TriangleMesh()}; - ts.test(); - } if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off // we are actually shutting down @@ -818,7 +758,7 @@ void GLGizmoFdmSupports::on_set_state() } activate_internal_undo_redo_stack(false); m_old_mo_id = -1; - m_ivas.clear(); + m_iva.release_geometry(); m_selected_facets.clear(); } m_old_state = m_state; @@ -858,22 +798,6 @@ void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const -void TriangleSelector::test() -{ - DivisionNode node; - while (true) { - std::cout << "Zadej pocet stran a spec stranu: "; - int num; - int spec; - std::cin >> num >> spec; - node.set_division(num, spec); - std::cout << node.number_of_split_sides() << " " - << (node.number_of_split_sides()==1 ? node.side_to_split() : node.side_to_keep()) - << std::endl << std::endl; - } -} - - void TriangleSelector::DivisionNode::set_division(int sides_to_split, int special_side_idx) { assert(sides_to_split >=0 && sides_to_split <= 3); @@ -883,17 +807,17 @@ void TriangleSelector::DivisionNode::set_division(int sides_to_split, int specia assert(sides_to_split != 1 || special_side_idx != -1); assert(sides_to_split != 2 || special_side_idx != -1); - division_type = sides_to_split | (special_side_idx != -1 ? (special_side_idx << 2) : 0 ); + division_type = sides_to_split | ((special_side_idx != -1 ? special_side_idx : 0 ) <<2); } -void TriangleSelector::DivisionNode::set_type(FacetSupportType type) +void TriangleSelector::DivisionNode::set_state(FacetSupportType type) { // If this is not a leaf-node, this makes no sense and // the bits are used for storing index of an edge. assert(number_of_split_sides() == 0); - division_type = type | (type << 2); + division_type = (int8_t(type) << 2); } @@ -914,10 +838,326 @@ int TriangleSelector::DivisionNode::side_to_split() const -FacetSupportType TriangleSelector::DivisionNode::get_type() const +FacetSupportType TriangleSelector::DivisionNode::get_state() const { assert(number_of_split_sides() == 0); // this must be leaf - return (division_type & 0b1100) >> 2; + return FacetSupportType((division_type & 0b1100) >> 2); +} + + + +void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec3f& dir, + float radius_sqr, FacetSupportType new_state) +{ + assert(facet_start < m_orig_size_indices); + + // Save current cursor center, squared radius and camera direction, + // so we don't have to pass it around. + m_cursor = {hit, dir, radius_sqr}; + + // Now start with the facet the pointer points to and check all adjacent facets. + std::vector facets_to_check{facet_start}; + std::vector visited(m_orig_size_indices, false); // keep track of facets we already processed + int facet_idx = 0; // index into facets_to_check + while (facet_idx < int(facets_to_check.size())) { + int facet = facets_to_check[facet_idx]; + if (! visited[facet]) { + int num_of_inside_vertices = vertices_inside(facet); + // select the facet... + select_triangle(facet, new_state, num_of_inside_vertices, facet == facet_start); + + // ...and add neighboring facets to be proccessed later + for (int n=0; n<3; ++n) { + if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) + facets_to_check.push_back(m_mesh->stl.neighbors_start[facet].neighbor[n]); + } + } + visited[facet] = true; + ++facet_idx; + } +} + + + +// Selects either the whole triangle (discarding any children it has), or divides +// the triangle recursively, selecting just subtriangles truly inside the circle. +// This is done by an actual recursive call. +void TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, + int num_of_inside_vertices, bool cursor_inside) +{ + assert(facet_idx < m_triangles.size()); +//cursor_inside=false; + if (num_of_inside_vertices == -1) + num_of_inside_vertices = vertices_inside(facet_idx); + + if (num_of_inside_vertices == 0 && ! cursor_inside) + return; // FIXME: just an edge can be inside + + if (num_of_inside_vertices == 3) { + // dump any subdivision and select whole triangle + undivide_triangle(facet_idx); + m_triangles[facet_idx].div_info->set_state(type); + } else { + // the triangle is partially inside, let's recursively divide it + // (if not already) and try selecting its children. + split_triangle(facet_idx); + assert(facet_idx < m_triangles.size()); + int num_of_children = m_triangles[facet_idx].div_info->number_of_split_sides() + 1; + if (num_of_children != 1) { + for (int i=0; ichildren[i], type, -1, cursor_inside); + } + } + } + //if (m_triangles[facet_idx].div_info->number_of_split_sides() != 0) + // remove_needless(m_triangles[facet_idx].div_info->children[0]); +} + + +bool TriangleSelector::split_triangle(int facet_idx) +{ + if (m_triangles[facet_idx].div_info->number_of_split_sides() != 0) { + // The triangle was divided already. + return 0; + } + + FacetSupportType old_type = m_triangles[facet_idx].div_info->get_state(); + + const double limit_squared = 4; + + stl_triangle_vertex_indices& facet = m_triangles[facet_idx].verts_idxs; + const stl_vertex* pts[3] = { &m_vertices[facet[0]], &m_vertices[facet[1]], &m_vertices[facet[2]]}; + double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), (*pts[0]-*pts[2]).squaredNorm(), (*pts[1]-*pts[0]).squaredNorm() }; + + std::vector sides_to_split; + int side_to_keep = -1; + for (int pt_idx = 0; pt_idx<3; ++pt_idx) { + if (sides[pt_idx] > limit_squared) + sides_to_split.push_back(pt_idx); + else + side_to_keep = pt_idx; + } + if (sides_to_split.empty()) { + m_triangles[facet_idx].div_info->set_division(0); + return 0; + } + + // indices of triangle vertices + std::vector verts_idxs; + int idx = sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]; + for (int j=0; j<3; ++j) { + verts_idxs.push_back(facet[idx++]); + if (idx == 3) + idx = 0; + } + + + if (sides_to_split.size() == 1) { + m_vertices.emplace_back((m_vertices[verts_idxs[1]] + m_vertices[verts_idxs[2]])/2.); + verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1); + + m_triangles.emplace_back(verts_idxs[0], verts_idxs[1], verts_idxs[2]); + m_triangles.emplace_back(verts_idxs[2], verts_idxs[3], verts_idxs[0]); + } + + if (sides_to_split.size() == 2) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]] + m_vertices[verts_idxs[1]])/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + + m_vertices.emplace_back((m_vertices[verts_idxs[0]] + m_vertices[verts_idxs[3]])/2.); + verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1); + + m_triangles.emplace_back(verts_idxs[0], verts_idxs[1], verts_idxs[4]); + m_triangles.emplace_back(verts_idxs[1], verts_idxs[2], verts_idxs[4]); + m_triangles.emplace_back(verts_idxs[2], verts_idxs[3], verts_idxs[4]); + } + + if (sides_to_split.size() == 3) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]] + m_vertices[verts_idxs[1]])/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[2]] + m_vertices[verts_idxs[3]])/2.); + verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[4]] + m_vertices[verts_idxs[0]])/2.); + verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1); + + m_triangles.emplace_back(verts_idxs[0], verts_idxs[1], verts_idxs[5]); + m_triangles.emplace_back(verts_idxs[1], verts_idxs[2], verts_idxs[3]); + m_triangles.emplace_back(verts_idxs[3], verts_idxs[4], verts_idxs[5]); + m_triangles.emplace_back(verts_idxs[1], verts_idxs[3], verts_idxs[5]); + } + + // Save how the triangle was split. Second argument makes sense only for one + // or two split sides, otherwise the value is ignored. + m_triangles[facet_idx].div_info->set_division(sides_to_split.size(), + sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]); + + // And save the children. All children should start in the same state as the triangle we just split. + assert(! sides_to_split.empty() && int(sides_to_split.size()) <= 3); + for (int i=0; i<=int(sides_to_split.size()); ++i) { + m_triangles[facet_idx].div_info->children[i] = m_triangles.size()-1-i; + m_triangles[m_triangles.size()-1-i].div_info->parent = facet_idx; + m_triangles[m_triangles[facet_idx].div_info->children[i]].div_info->set_state(old_type); + } + + +#ifndef NDEBUG + int split_sides = m_triangles[facet_idx].div_info->number_of_split_sides(); + if (split_sides != 0) { + // check that children are range + for (int i=0; i<=split_sides; ++i) + assert(m_triangles[facet_idx].div_info->children[i] >= 0 && m_triangles[facet_idx].div_info->children[i] < int(m_triangles.size())); + + } +#endif + + return 1; +} + + +// Calculate distance of a point from a line. +bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const +{ + Vec3f diff = m_cursor.center - point; + return (diff - diff.dot(m_cursor.dir) * m_cursor.dir).squaredNorm() < m_cursor.radius_sqr; +} + + + +// Determine whether this facet is potentially visible (still can be obscured). +bool TriangleSelector::faces_camera(int facet) const +{ + assert(facet < m_orig_size_indices); + // The normal is cached in mesh->stl, use it. + return (m_mesh->stl.facet_start[facet].normal.dot(m_cursor.dir) > 0.); +} + + + +// How many vertices of a triangle are inside the circle? +int TriangleSelector::vertices_inside(int facet_idx) const +{ + int inside = 0; + for (size_t i=0; i<3; ++i) { + if (is_point_inside_cursor(m_vertices[m_triangles[facet_idx].verts_idxs[i]])) + ++inside; + } + return inside; +} + + +// Is mouse pointer inside a triangle? +/*bool TriangleSelector::is_pointer_inside_triangle(int facet_idx) const +{ + +}*/ + + + +// Recursively remove all subtriangles. +void TriangleSelector::undivide_triangle(int facet_idx) +{ + assert(facet_idx < m_triangles.size()); + auto& dn_ptr = m_triangles[facet_idx].div_info; + assert(dn_ptr); + + if (dn_ptr->number_of_split_sides() != 0) { + for (int i=0; i<=dn_ptr->number_of_split_sides(); ++i) { + undivide_triangle(dn_ptr->children[i]); + m_triangles[dn_ptr->children[i]].div_info->valid = false; + } + } + + dn_ptr->set_division(0); // not split +} + + +void TriangleSelector::remove_needless(int child_facet) +{ + assert(m_triangles[child_facet].div_info->number_of_split_sides() == 0); + int parent = m_triangles[child_facet].div_info->parent; + if (parent == -1) + return; // root + // Check type of all valid children. + FacetSupportType type = m_triangles[m_triangles[parent].div_info->children[0]].div_info->get_state(); + for (int i=0; i<=m_triangles[parent].div_info->number_of_split_sides(); ++i) + if (m_triangles[m_triangles[parent].div_info->children[0]].div_info->get_state() != type) + return; // not all children are the same + + // All children are the same, let's kill them. + undivide_triangle(parent); + m_triangles[parent].div_info->set_state(type); + + // And not try the same for grandparent. + remove_needless(parent); +} + + +TriangleSelector::TriangleSelector(const TriangleMesh& mesh) +{ + for (const stl_vertex& vert : mesh.its.vertices) + m_vertices.push_back(vert); + for (const stl_triangle_vertex_indices& ind : mesh.its.indices) + m_triangles.emplace_back(Triangle(ind[0], ind[1], ind[2])); + m_orig_size_vertices = m_vertices.size(); + m_orig_size_indices = m_triangles.size(); + m_mesh = &mesh; +} + + +void TriangleSelector::render() const +{ + ::glColor3f(0.f, 0.f, 1.f); + ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + + Vec3d offset = wxGetApp().model().objects.front()->instances.front()->get_transformation().get_offset(); + ::glTranslatef(offset.x(), offset.y(), offset.z()); + ::glScalef(1.01f, 1.01f, 1.01f); + + ::glBegin( GL_TRIANGLES); + + for (int tr_id=0; tr_idvalid) { + for (int i=0; i<3; ++i) + ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], m_vertices[tr.verts_idxs[i]][1], m_vertices[tr.verts_idxs[i]][2]); + } + } + ::glEnd(); + ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + + ::glBegin( GL_TRIANGLES); + for (int tr_id=0; tr_idvalid) + continue; + + if (tr.div_info->number_of_split_sides() == 0) { + if (tr.div_info->get_state() == FacetSupportType::ENFORCER) + ::glColor4f(0.f, 0.f, 1.f, 0.2f); + else if (tr.div_info->get_state() == FacetSupportType::BLOCKER) + ::glColor4f(1.f, 0.f, 0.f, 0.2f); + else + continue; + } + else + continue; + + + if (tr.div_info->valid) { + for (int i=0; i<3; ++i) + ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], m_vertices[tr.verts_idxs[i]][1], m_vertices[tr.verts_idxs[i]][2]); + } + } + ::glEnd(); +} + + +TriangleSelector::DivisionNode::DivisionNode() +{ + set_division(0); + set_state(FacetSupportType::NONE); } } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index f815a8063..07c2e86d5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -24,15 +24,17 @@ class ClippingPlane; // to recursively subdivide the triangles and make the selection finer. class TriangleSelector { public: - void test(); - explicit TriangleSelector(const TriangleMesh& mesh) {} + void render() const; + // Create new object on a TriangleMesh. The referenced mesh must + // stay valid, a ptr to it is saved and used. + explicit TriangleSelector(const TriangleMesh& mesh); // Select all triangles inside the circle, subdivide where needed. void select_patch(const Vec3f& hit, // point where to start - int facet_idx, // facet that point belongs to + int facet_start, // facet that point belongs to const Vec3f& dir, // direction of the ray float radius_sqr, // squared radius of the cursor - bool enforcer); // enforcer or blocker? + FacetSupportType new_state); // enforcer or blocker? void unselect_all(); @@ -43,11 +45,15 @@ public: private: // A struct to hold information about how a triangle was divided. struct DivisionNode { + DivisionNode(); // Index of triangle this describes. - int triangle_idx; + bool valid{true}; + + // Index of parent triangle (-1: original) + int parent{-1}; // Bitmask encoding which sides are split. - unsigned char division_type; + int8_t division_type; // bits 0 and 1 : 00 - no division // 01 - one-edge split // 10 - two-edge split @@ -55,29 +61,37 @@ private: // bits 2 and 3 : decimal 0, 1 or 2 identifying the special edge (one that // splits in one-edge split or one that stays in two-edge split). - // Pointers to children nodes (not all are always used). - std::array children; + // Children triangles (0 = no child) + std::array children; // Set the division type. void set_division(int sides_to_split, int special_side_idx = -1); + void set_state(FacetSupportType state); // Helpers that decode the division_type bitmask. int number_of_split_sides() const { return division_type & 0b11; } int side_to_keep() const; int side_to_split() const; + + FacetSupportType get_state() const; }; // Triangle and pointer to how it's divided (nullptr = not divided). // The ptr is nullptr for all new triangles, it is only valid for // the original (undivided) triangles. struct Triangle { + Triangle(int a, int b, int c) + : verts_idxs{stl_triangle_vertex_indices(a, b, c)}, + div_info{std::make_unique()} + {} stl_triangle_vertex_indices verts_idxs; - DivisionNode* div_info; + std::unique_ptr div_info; }; // Lists of vertices and triangles, both original and new std::vector m_vertices; std::vector m_triangles; + const TriangleMesh* m_mesh; // Number of original vertices and triangles. int m_orig_size_vertices; @@ -86,6 +100,32 @@ private: // Limits for stopping the recursion. float m_max_edge_length; int m_max_recursion_depth; + + // Caches for cursor position, radius and direction. + struct Cursor { + Vec3f center; + Vec3f dir; + float radius_sqr; + }; + + Cursor m_cursor; + + // Private functions: + void select_triangle(int facet_idx, FacetSupportType type, + int num_of_inside_vertices = -1, + bool cursor_inside = false); + + bool is_point_inside_cursor(const Vec3f& point) const; + + int vertices_inside(int facet_idx) const; + + bool faces_camera(int facet) const; + + void undivide_triangle(int facet_idx); + + bool split_triangle(int facet_idx); + + void remove_needless(int child_facet); }; @@ -107,10 +147,7 @@ private: // individual facets (one of the enum values above). std::vector> m_selected_facets; - // Vertex buffer arrays for each model-part volume. There is a vector of - // arrays so that adding triangles can be done without regenerating all - // other triangles. Enforcers and blockers are of course separate. - std::vector, 2>> m_ivas; + GLIndexedVertexArray m_iva; void update_vertex_buffers(const TriangleMesh* mesh, int mesh_id, @@ -148,6 +185,8 @@ private: bool m_setting_angle = false; bool m_internal_stack_active = false; bool m_schedule_update = false; + + std::unique_ptr m_triangle_selector; // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. From c3db84e3821d7c94fb4f615cbc7bedf10695a59b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 19 Jun 2020 09:20:58 +0200 Subject: [PATCH 23/70] TriangleSelector: Improvements --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 254 ++++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 70 ++--- 2 files changed, 176 insertions(+), 148 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 1b045f38e..1c77f437c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -436,9 +436,11 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const float limit = pow(m_cursor_radius/avg_scaling , 2.f); // Calculate direction from camera to the hit (in mesh coords): - Vec3f dir = ((trafo_matrix.inverse() * camera.get_position()).cast() - closest_hit).normalized(); + Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); + Vec3f dir = (closest_hit - camera_pos).normalized(); - m_triangle_selector->select_patch(closest_hit, closest_facet, dir, limit, new_state); + m_triangle_selector->select_patch(closest_hit, closest_facet, camera_pos, + dir, limit, new_state); return true; } @@ -567,6 +569,12 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (! m_c->selection_info()->model_object()) return; + m_imgui->begin(std::string("TriangleSelector DEBUG"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + static float edge_limit = 1.f; + m_imgui->slider_float("Edge limit (mm): ", &edge_limit, 0.1f, 8.f); + m_triangle_selector->set_edge_limit(edge_limit); + m_imgui->end(); + const float approx_height = m_imgui->scaled(18.0f); y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); @@ -798,7 +806,7 @@ void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const -void TriangleSelector::DivisionNode::set_division(int sides_to_split, int special_side_idx) +void TriangleSelector::Triangle::set_division(int sides_to_split, int special_side_idx) { assert(sides_to_split >=0 && sides_to_split <= 3); assert(special_side_idx >=-1 && special_side_idx < 3); @@ -812,48 +820,49 @@ void TriangleSelector::DivisionNode::set_division(int sides_to_split, int specia -void TriangleSelector::DivisionNode::set_state(FacetSupportType type) +void TriangleSelector::Triangle::set_state(FacetSupportType type) { // If this is not a leaf-node, this makes no sense and // the bits are used for storing index of an edge. - assert(number_of_split_sides() == 0); + assert(! is_split()); division_type = (int8_t(type) << 2); } -int TriangleSelector::DivisionNode::side_to_keep() const +int TriangleSelector::Triangle::side_to_keep() const { assert(number_of_split_sides() == 2); - return (division_type & 0b1100) >> 2; + return division_type >> 2; } -int TriangleSelector::DivisionNode::side_to_split() const +int TriangleSelector::Triangle::side_to_split() const { assert(number_of_split_sides() == 1); - return (division_type & 0b1100) >> 2; + return division_type >> 2; } -FacetSupportType TriangleSelector::DivisionNode::get_state() const +FacetSupportType TriangleSelector::Triangle::get_state() const { - assert(number_of_split_sides() == 0); // this must be leaf - return FacetSupportType((division_type & 0b1100) >> 2); + assert(! is_split()); // this must be leaf + return FacetSupportType(division_type >> 2); } -void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec3f& dir, +void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, + const Vec3f& source, const Vec3f& dir, float radius_sqr, FacetSupportType new_state) { assert(facet_start < m_orig_size_indices); // Save current cursor center, squared radius and camera direction, // so we don't have to pass it around. - m_cursor = {hit, dir, radius_sqr}; + m_cursor = {hit, source, dir, radius_sqr}; // Now start with the facet the pointer points to and check all adjacent facets. std::vector facets_to_check{facet_start}; @@ -862,14 +871,12 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec while (facet_idx < int(facets_to_check.size())) { int facet = facets_to_check[facet_idx]; if (! visited[facet]) { - int num_of_inside_vertices = vertices_inside(facet); - // select the facet... - select_triangle(facet, new_state, num_of_inside_vertices, facet == facet_start); - - // ...and add neighboring facets to be proccessed later - for (int n=0; n<3; ++n) { - if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) - facets_to_check.push_back(m_mesh->stl.neighbors_start[facet].neighbor[n]); + if (select_triangle(facet, new_state, facet == facet_start)) { + // add neighboring facets to list to be proccessed later + for (int n=0; n<3; ++n) { + if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) + facets_to_check.push_back(m_mesh->stl.neighbors_start[facet].neighbor[n]); + } } } visited[facet] = true; @@ -879,53 +886,71 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec -// Selects either the whole triangle (discarding any children it has), or divides +// Selects either the whole triangle (discarding any children it had), or divides // the triangle recursively, selecting just subtriangles truly inside the circle. -// This is done by an actual recursive call. -void TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, - int num_of_inside_vertices, bool cursor_inside) +// This is done by an actual recursive call. Returns false if the triangle is +// outside the cursor. +bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool cursor_inside) { - assert(facet_idx < m_triangles.size()); -//cursor_inside=false; - if (num_of_inside_vertices == -1) - num_of_inside_vertices = vertices_inside(facet_idx); + bool out = false; + assert(facet_idx < int(m_triangles.size())); + + Triangle& tr = m_triangles[facet_idx]; + if (! tr.valid) + return false; + + cursor_inside = is_pointer_in_triangle(facet_idx); + + int num_of_inside_vertices = vertices_inside(facet_idx); if (num_of_inside_vertices == 0 && ! cursor_inside) - return; // FIXME: just an edge can be inside + return out; // FIXME: just an edge can be inside if (num_of_inside_vertices == 3) { // dump any subdivision and select whole triangle undivide_triangle(facet_idx); - m_triangles[facet_idx].div_info->set_state(type); + tr.set_state(type); } else { // the triangle is partially inside, let's recursively divide it // (if not already) and try selecting its children. + + + if (! tr.is_split() && tr.get_state() == type) { + // This is leaf triangle that is already of correct type as a whole. + // No need to split, all children would end up selected anyway. + return true; + } + split_triangle(facet_idx); - assert(facet_idx < m_triangles.size()); - int num_of_children = m_triangles[facet_idx].div_info->number_of_split_sides() + 1; + assert(facet_idx < int(m_triangles.size())); + int num_of_children = tr.number_of_split_sides() + 1; if (num_of_children != 1) { - for (int i=0; ichildren[i], type, -1, cursor_inside); - } + for (int i=0; inumber_of_split_sides() != 0) - // remove_needless(m_triangles[facet_idx].div_info->children[0]); + + // In case that all siblings are leafs and have the same state now, + // they may be removed and substituted by the parent triangle. + //remove_if_needless(facet_idx); + return true; } bool TriangleSelector::split_triangle(int facet_idx) { - if (m_triangles[facet_idx].div_info->number_of_split_sides() != 0) { + if (m_triangles[facet_idx].is_split()) { // The triangle was divided already. - return 0; + return false; } - FacetSupportType old_type = m_triangles[facet_idx].div_info->get_state(); + Triangle& tr = m_triangles[facet_idx]; - const double limit_squared = 4; + FacetSupportType old_type = tr.get_state(); - stl_triangle_vertex_indices& facet = m_triangles[facet_idx].verts_idxs; + const double limit_squared = m_edge_limit_sqr; + + stl_triangle_vertex_indices& facet = tr.verts_idxs; const stl_vertex* pts[3] = { &m_vertices[facet[0]], &m_vertices[facet[1]], &m_vertices[facet[2]]}; double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), (*pts[0]-*pts[2]).squaredNorm(), (*pts[1]-*pts[0]).squaredNorm() }; @@ -938,8 +963,8 @@ bool TriangleSelector::split_triangle(int facet_idx) side_to_keep = pt_idx; } if (sides_to_split.empty()) { - m_triangles[facet_idx].div_info->set_division(0); - return 0; + tr.set_division(0); + return false; } // indices of triangle vertices @@ -988,29 +1013,18 @@ bool TriangleSelector::split_triangle(int facet_idx) // Save how the triangle was split. Second argument makes sense only for one // or two split sides, otherwise the value is ignored. - m_triangles[facet_idx].div_info->set_division(sides_to_split.size(), + tr.set_division(sides_to_split.size(), sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]); // And save the children. All children should start in the same state as the triangle we just split. assert(! sides_to_split.empty() && int(sides_to_split.size()) <= 3); for (int i=0; i<=int(sides_to_split.size()); ++i) { - m_triangles[facet_idx].div_info->children[i] = m_triangles.size()-1-i; - m_triangles[m_triangles.size()-1-i].div_info->parent = facet_idx; - m_triangles[m_triangles[facet_idx].div_info->children[i]].div_info->set_state(old_type); + tr.children[i] = m_triangles.size()-1-i; + m_triangles[tr.children[i]].parent = facet_idx; + m_triangles[tr.children[i]].set_state(old_type); } - -#ifndef NDEBUG - int split_sides = m_triangles[facet_idx].div_info->number_of_split_sides(); - if (split_sides != 0) { - // check that children are range - for (int i=0; i<=split_sides; ++i) - assert(m_triangles[facet_idx].div_info->children[i] >= 0 && m_triangles[facet_idx].div_info->children[i] < int(m_triangles.size())); - - } -#endif - - return 1; + return true; } @@ -1022,6 +1036,29 @@ bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const } +// Is pointer in a triangle? +bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const +{ + auto signed_volume_sign = [](const Vec3f& a, const Vec3f& b, + const Vec3f& c, const Vec3f& d) -> bool { + return ((b-a).cross(c-a)).dot(d-a) > 0.; + }; + + const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]]; + const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]]; + const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]]; + const Vec3f& q1 = m_cursor.center + m_cursor.dir; + const Vec3f q2 = m_cursor.center - m_cursor.dir; + + if (signed_volume_sign(q1,p1,p2,p3) != signed_volume_sign(q2,p1,p2,p3)) { + bool pos = signed_volume_sign(q1,q2,p1,p2); + if (signed_volume_sign(q1,q2,p2,p3) == pos && signed_volume_sign(q1,q2,p3,p1) == pos) + return true; + } + return false; +} + + // Determine whether this facet is potentially visible (still can be obscured). bool TriangleSelector::faces_camera(int facet) const @@ -1056,39 +1093,41 @@ int TriangleSelector::vertices_inside(int facet_idx) const // Recursively remove all subtriangles. void TriangleSelector::undivide_triangle(int facet_idx) { - assert(facet_idx < m_triangles.size()); - auto& dn_ptr = m_triangles[facet_idx].div_info; - assert(dn_ptr); + assert(facet_idx < int(m_triangles.size())); + Triangle& tr = m_triangles[facet_idx]; - if (dn_ptr->number_of_split_sides() != 0) { - for (int i=0; i<=dn_ptr->number_of_split_sides(); ++i) { - undivide_triangle(dn_ptr->children[i]); - m_triangles[dn_ptr->children[i]].div_info->valid = false; + if (tr.is_split()) { + for (int i=0; i<=tr.number_of_split_sides(); ++i) { + undivide_triangle(tr.children[i]); + m_triangles[tr.children[i]].valid = false; } } - dn_ptr->set_division(0); // not split + tr.set_division(0); // not split } -void TriangleSelector::remove_needless(int child_facet) +void TriangleSelector::remove_if_needless(int child_facet) { - assert(m_triangles[child_facet].div_info->number_of_split_sides() == 0); - int parent = m_triangles[child_facet].div_info->parent; + if (m_triangles[child_facet].is_split() || ! m_triangles[child_facet].valid) + return; + int parent = m_triangles[child_facet].parent; if (parent == -1) return; // root - // Check type of all valid children. - FacetSupportType type = m_triangles[m_triangles[parent].div_info->children[0]].div_info->get_state(); - for (int i=0; i<=m_triangles[parent].div_info->number_of_split_sides(); ++i) - if (m_triangles[m_triangles[parent].div_info->children[0]].div_info->get_state() != type) + FacetSupportType child_type = m_triangles[child_facet].get_state(); + + // Check type of all valid children, if they're same, they are needless. + for (int i=0; i<=m_triangles[parent].number_of_split_sides(); ++i) + if (m_triangles[m_triangles[parent].children[i]].is_split() + || m_triangles[m_triangles[parent].children[i]].get_state() != child_type) return; // not all children are the same - // All children are the same, let's kill them. + // All children are the same, kill them. undivide_triangle(parent); - m_triangles[parent].div_info->set_state(type); + m_triangles[parent].set_state(child_type); - // And not try the same for grandparent. - remove_needless(parent); + // And now try the same for parent (which has just become leaf). + remove_if_needless(parent); } @@ -1114,51 +1153,40 @@ void TriangleSelector::render() const ::glScalef(1.01f, 1.01f, 1.01f); ::glBegin( GL_TRIANGLES); - - for (int tr_id=0; tr_idvalid) { - for (int i=0; i<3; ++i) - ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], m_vertices[tr.verts_idxs[i]][1], m_vertices[tr.verts_idxs[i]][2]); - } + if (! tr.valid) + continue; + + if (tr_id == m_orig_size_indices-1) + ::glColor3f(1.f, 0.f, 0.f); + + for (int i=0; i<3; ++i) + ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], + m_vertices[tr.verts_idxs[i]][1], + m_vertices[tr.verts_idxs[i]][2]); } ::glEnd(); + ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); ::glBegin( GL_TRIANGLES); - for (int tr_id=0; tr_idvalid) + for (const Triangle& tr : m_triangles) { + if (! tr.valid || tr.is_split() || tr.get_state() == FacetSupportType::NONE) continue; - if (tr.div_info->number_of_split_sides() == 0) { - if (tr.div_info->get_state() == FacetSupportType::ENFORCER) - ::glColor4f(0.f, 0.f, 1.f, 0.2f); - else if (tr.div_info->get_state() == FacetSupportType::BLOCKER) - ::glColor4f(1.f, 0.f, 0.f, 0.2f); - else - continue; - } + if (tr.get_state() == FacetSupportType::ENFORCER) + ::glColor4f(0.f, 0.f, 1.f, 0.2f); else - continue; + ::glColor4f(1.f, 0.f, 0.f, 0.2f); - - if (tr.div_info->valid) { - for (int i=0; i<3; ++i) - ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], m_vertices[tr.verts_idxs[i]][1], m_vertices[tr.verts_idxs[i]][2]); - } + for (int i=0; i<3; ++i) + ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], + m_vertices[tr.verts_idxs[i]][1], + m_vertices[tr.verts_idxs[i]][2]); } ::glEnd(); } - -TriangleSelector::DivisionNode::DivisionNode() -{ - set_division(0); - set_state(FacetSupportType::NONE); -} - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 07c2e86d5..3e1b076c5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -24,16 +24,18 @@ class ClippingPlane; // to recursively subdivide the triangles and make the selection finer. class TriangleSelector { public: + void set_edge_limit(float edge_limit) { m_edge_limit_sqr = std::pow(edge_limit, 2.f); } void render() const; // Create new object on a TriangleMesh. The referenced mesh must // stay valid, a ptr to it is saved and used. explicit TriangleSelector(const TriangleMesh& mesh); // Select all triangles inside the circle, subdivide where needed. - void select_patch(const Vec3f& hit, // point where to start - int facet_start, // facet that point belongs to - const Vec3f& dir, // direction of the ray - float radius_sqr, // squared radius of the cursor + void select_patch(const Vec3f& hit, // point where to start + int facet_start, // facet that point belongs to + const Vec3f& source, // camera position (mesh coords) + const Vec3f& dir, // direction of the ray (mesh coords) + float radius_sqr, // squared radius of the cursor FacetSupportType new_state); // enforcer or blocker? void unselect_all(); @@ -43,49 +45,44 @@ public: void garbage_collect(); private: - // A struct to hold information about how a triangle was divided. - struct DivisionNode { - DivisionNode(); - // Index of triangle this describes. + // Triangle and info about how it's split. + struct Triangle { + public: + Triangle(int a, int b, int c) + : verts_idxs{stl_triangle_vertex_indices(a, b, c)}, + division_type{0} + {} + stl_triangle_vertex_indices verts_idxs; + + // Is this triangle valid or marked to remove? bool valid{true}; // Index of parent triangle (-1: original) int parent{-1}; - // Bitmask encoding which sides are split. - int8_t division_type; - // bits 0 and 1 : 00 - no division - // 01 - one-edge split - // 10 - two-edge split - // 11 - three-edge split - // bits 2 and 3 : decimal 0, 1 or 2 identifying the special edge (one that - // splits in one-edge split or one that stays in two-edge split). - // Children triangles (0 = no child) std::array children; // Set the division type. void set_division(int sides_to_split, int special_side_idx = -1); - void set_state(FacetSupportType state); - // Helpers that decode the division_type bitmask. + // Get/set current state. + void set_state(FacetSupportType state); + FacetSupportType get_state() const; + + // Get info on how it's split. + bool is_split() const { return number_of_split_sides() != 0; } int number_of_split_sides() const { return division_type & 0b11; } int side_to_keep() const; int side_to_split() const; - FacetSupportType get_state() const; - }; - - // Triangle and pointer to how it's divided (nullptr = not divided). - // The ptr is nullptr for all new triangles, it is only valid for - // the original (undivided) triangles. - struct Triangle { - Triangle(int a, int b, int c) - : verts_idxs{stl_triangle_vertex_indices(a, b, c)}, - div_info{std::make_unique()} - {} - stl_triangle_vertex_indices verts_idxs; - std::unique_ptr div_info; + private: + // Bitmask encoding which sides are split. + int8_t division_type; + // bits 0, 1 : decimal 0, 1, 2 or 3 (how many sides are split) + // bits 2, 3 (non-leaf): decimal 0, 1 or 2 identifying the special edge + // (one that splits in one-edge split or one that stays in two-edge split). + // bits 2, 3 (leaf): FacetSupportType value }; // Lists of vertices and triangles, both original and new @@ -93,6 +90,8 @@ private: std::vector m_triangles; const TriangleMesh* m_mesh; + float m_edge_limit_sqr = 1.f; + // Number of original vertices and triangles. int m_orig_size_vertices; int m_orig_size_indices; @@ -104,6 +103,7 @@ private: // Caches for cursor position, radius and direction. struct Cursor { Vec3f center; + Vec3f source; Vec3f dir; float radius_sqr; }; @@ -111,8 +111,7 @@ private: Cursor m_cursor; // Private functions: - void select_triangle(int facet_idx, FacetSupportType type, - int num_of_inside_vertices = -1, + bool select_triangle(int facet_idx, FacetSupportType type, bool cursor_inside = false); bool is_point_inside_cursor(const Vec3f& point) const; @@ -125,7 +124,8 @@ private: bool split_triangle(int facet_idx); - void remove_needless(int child_facet); + void remove_if_needless(int child_facet); + bool is_pointer_in_triangle(int facet_idx) const; }; From bed28bb2fff974cd2097b47d404b3cbeb69d570b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 22 Jun 2020 11:45:51 +0200 Subject: [PATCH 24/70] TriangleSelector: even more progress --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 36 ++++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 7 +--- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 1c77f437c..95f46b3fe 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -859,6 +859,7 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, float radius_sqr, FacetSupportType new_state) { assert(facet_start < m_orig_size_indices); + assert(is_approx(dir.norm(), 1.f)); // Save current cursor center, squared radius and camera direction, // so we don't have to pass it around. @@ -892,7 +893,6 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, // outside the cursor. bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool cursor_inside) { - bool out = false; assert(facet_idx < int(m_triangles.size())); Triangle& tr = m_triangles[facet_idx]; @@ -903,10 +903,12 @@ bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, boo int num_of_inside_vertices = vertices_inside(facet_idx); - if (num_of_inside_vertices == 0 && ! cursor_inside) - return out; // FIXME: just an edge can be inside + if (num_of_inside_vertices == 0 + && ! cursor_inside + && ! is_edge_inside_cursor(facet_idx)) + return false; - if (num_of_inside_vertices == 3) { + if (vertices_inside(facet_idx) == 3) { // dump any subdivision and select whole triangle undivide_triangle(facet_idx); tr.set_state(type); @@ -914,7 +916,6 @@ bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, boo // the triangle is partially inside, let's recursively divide it // (if not already) and try selecting its children. - if (! tr.is_split() && tr.get_state() == type) { // This is leaf triangle that is already of correct type as a whole. // No need to split, all children would end up selected anyway. @@ -1065,11 +1066,10 @@ bool TriangleSelector::faces_camera(int facet) const { assert(facet < m_orig_size_indices); // The normal is cached in mesh->stl, use it. - return (m_mesh->stl.facet_start[facet].normal.dot(m_cursor.dir) > 0.); + return (m_mesh->stl.facet_start[facet].normal.dot(m_cursor.dir) < 0.); } - // How many vertices of a triangle are inside the circle? int TriangleSelector::vertices_inside(int facet_idx) const { @@ -1082,11 +1082,27 @@ int TriangleSelector::vertices_inside(int facet_idx) const } -// Is mouse pointer inside a triangle? -/*bool TriangleSelector::is_pointer_inside_triangle(int facet_idx) const +// Is edge inside cursor? +bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const { + Vec3f pts[3]; + for (int i=0; i<3; ++i) + pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]]; -}*/ + const Vec3f& p = m_cursor.center; + + for (int side = 0; side < 3; ++side) { + const Vec3f& a = pts[side]; + const Vec3f& b = pts[side<2 ? side+1 : 0]; + Vec3f s = (b-a).normalized(); + float t = (p-a).dot(s); + Vec3f vector = a+t*s - p; + float dist_sqr = vector.squaredNorm(); + if (dist_sqr < m_cursor.radius_sqr && t>=0.f && t<=(b-a).norm()) + return true; + } + return false; +} diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 3e1b076c5..a50328d3b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -113,19 +113,14 @@ private: // Private functions: bool select_triangle(int facet_idx, FacetSupportType type, bool cursor_inside = false); - bool is_point_inside_cursor(const Vec3f& point) const; - int vertices_inside(int facet_idx) const; - bool faces_camera(int facet) const; - void undivide_triangle(int facet_idx); - bool split_triangle(int facet_idx); - void remove_if_needless(int child_facet); bool is_pointer_in_triangle(int facet_idx) const; + bool is_edge_inside_cursor(int facet_idx) const; }; From fb73bb1c6637fc6deb350e987b3308bb32a70e36 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 23 Jun 2020 16:07:33 +0200 Subject: [PATCH 25/70] TriangleSelector: remerging triangles, bugfixes --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 202 ++++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 18 +- 2 files changed, 140 insertions(+), 80 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 95f46b3fe..8972e9246 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -95,7 +95,10 @@ void GLGizmoFdmSupports::on_render() const glsafe(::glEnable(GL_DEPTH_TEST)); //render_triangles(selection); - m_triangle_selector->render(); + + if (m_triangle_selector && ! m_setting_angle) + m_triangle_selector->render(m_imgui); + m_c->object_clipper()->render_cut(); render_cursor_circle(); @@ -569,12 +572,6 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (! m_c->selection_info()->model_object()) return; - m_imgui->begin(std::string("TriangleSelector DEBUG"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - static float edge_limit = 1.f; - m_imgui->slider_float("Edge limit (mm): ", &edge_limit, 0.1f, 8.f); - m_triangle_selector->set_edge_limit(edge_limit); - m_imgui->end(); - const float approx_height = m_imgui->scaled(18.0f); y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); @@ -740,9 +737,7 @@ CommonGizmosDataID GLGizmoFdmSupports::on_get_requirements() const int(CommonGizmosDataID::SelectionInfo) | int(CommonGizmosDataID::InstancesHider) | int(CommonGizmosDataID::Raycaster) - | int(CommonGizmosDataID::HollowedMesh) - | int(CommonGizmosDataID::ObjectClipper) - | int(CommonGizmosDataID::SupportsClipper)); + | int(CommonGizmosDataID::ObjectClipper)); } @@ -872,7 +867,7 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, while (facet_idx < int(facets_to_check.size())) { int facet = facets_to_check[facet_idx]; if (! visited[facet]) { - if (select_triangle(facet, new_state, facet == facet_start)) { + if (select_triangle(facet, new_state)) { // add neighboring facets to list to be proccessed later for (int n=0; n<3; ++n) { if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) @@ -891,49 +886,54 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, // the triangle recursively, selecting just subtriangles truly inside the circle. // This is done by an actual recursive call. Returns false if the triangle is // outside the cursor. -bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool cursor_inside) +bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool recursive_call) { assert(facet_idx < int(m_triangles.size())); - Triangle& tr = m_triangles[facet_idx]; - if (! tr.valid) + Triangle* tr = &m_triangles[facet_idx]; + if (! tr->valid) return false; - cursor_inside = is_pointer_in_triangle(facet_idx); - int num_of_inside_vertices = vertices_inside(facet_idx); if (num_of_inside_vertices == 0 - && ! cursor_inside + && ! is_pointer_in_triangle(facet_idx) && ! is_edge_inside_cursor(facet_idx)) return false; - if (vertices_inside(facet_idx) == 3) { + if (num_of_inside_vertices == 3) { // dump any subdivision and select whole triangle undivide_triangle(facet_idx); - tr.set_state(type); + tr->set_state(type); } else { // the triangle is partially inside, let's recursively divide it // (if not already) and try selecting its children. - if (! tr.is_split() && tr.get_state() == type) { + if (! tr->is_split() && tr->get_state() == type) { // This is leaf triangle that is already of correct type as a whole. // No need to split, all children would end up selected anyway. return true; } split_triangle(facet_idx); - assert(facet_idx < int(m_triangles.size())); - int num_of_children = tr.number_of_split_sides() + 1; + tr = &m_triangles[facet_idx]; // might have been invalidated + + + int num_of_children = tr->number_of_split_sides() + 1; if (num_of_children != 1) { - for (int i=0; ichildren.size())); + assert(tr->children[i] < int(m_triangles.size())); + + select_triangle(tr->children[i], type, true); + tr = &m_triangles[facet_idx]; // might have been invalidated + } } } - - // In case that all siblings are leafs and have the same state now, + // In case that all children are leafs and have the same state now, // they may be removed and substituted by the parent triangle. - //remove_if_needless(facet_idx); + if (! recursive_call) + remove_useless_children(facet_idx); return true; } @@ -945,13 +945,13 @@ bool TriangleSelector::split_triangle(int facet_idx) return false; } - Triangle& tr = m_triangles[facet_idx]; + Triangle* tr = &m_triangles[facet_idx]; - FacetSupportType old_type = tr.get_state(); + FacetSupportType old_type = tr->get_state(); const double limit_squared = m_edge_limit_sqr; - stl_triangle_vertex_indices& facet = tr.verts_idxs; + stl_triangle_vertex_indices& facet = tr->verts_idxs; const stl_vertex* pts[3] = { &m_vertices[facet[0]], &m_vertices[facet[1]], &m_vertices[facet[2]]}; double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), (*pts[0]-*pts[2]).squaredNorm(), (*pts[1]-*pts[0]).squaredNorm() }; @@ -964,7 +964,7 @@ bool TriangleSelector::split_triangle(int facet_idx) side_to_keep = pt_idx; } if (sides_to_split.empty()) { - tr.set_division(0); + tr->set_division(0); return false; } @@ -1012,17 +1012,19 @@ bool TriangleSelector::split_triangle(int facet_idx) m_triangles.emplace_back(verts_idxs[1], verts_idxs[3], verts_idxs[5]); } + tr = &m_triangles[facet_idx]; // may have been invalidated + // Save how the triangle was split. Second argument makes sense only for one // or two split sides, otherwise the value is ignored. - tr.set_division(sides_to_split.size(), + tr->set_division(sides_to_split.size(), sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]); // And save the children. All children should start in the same state as the triangle we just split. assert(! sides_to_split.empty() && int(sides_to_split.size()) <= 3); for (int i=0; i<=int(sides_to_split.size()); ++i) { - tr.children[i] = m_triangles.size()-1-i; - m_triangles[tr.children[i]].parent = facet_idx; - m_triangles[tr.children[i]].set_state(old_type); + tr->children[i] = m_triangles.size()-1-i; + m_triangles[tr->children[i]].parent = facet_idx; + m_triangles[tr->children[i]].set_state(old_type); } return true; @@ -1097,7 +1099,10 @@ bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const Vec3f s = (b-a).normalized(); float t = (p-a).dot(s); Vec3f vector = a+t*s - p; - float dist_sqr = vector.squaredNorm(); + + // vector is 3D vector from center to the intersection. What we want to + // measure is length of its projection onto plane perpendicular to dir. + float dist_sqr = vector.squaredNorm() - std::pow(vector.dot(m_cursor.dir), 2.f); if (dist_sqr < m_cursor.radius_sqr && t>=0.f && t<=(b-a).norm()) return true; } @@ -1117,33 +1122,47 @@ void TriangleSelector::undivide_triangle(int facet_idx) undivide_triangle(tr.children[i]); m_triangles[tr.children[i]].valid = false; } + tr.set_division(0); // not split } - - tr.set_division(0); // not split } -void TriangleSelector::remove_if_needless(int child_facet) +void TriangleSelector::remove_useless_children(int facet_idx) { - if (m_triangles[child_facet].is_split() || ! m_triangles[child_facet].valid) + // Check that all children are leafs of the same type. If not, try to + // make them (recursive call). Remove them if sucessful. + + assert(facet_idx < int(m_triangles.size()) && m_triangles[facet_idx].valid); + Triangle& tr = m_triangles[facet_idx]; + + if (! tr.is_split()) { + // This is a leaf, there nothing to do. This can happen during the + // first (non-recursive call). Shouldn't otherwise. return; - int parent = m_triangles[child_facet].parent; - if (parent == -1) - return; // root - FacetSupportType child_type = m_triangles[child_facet].get_state(); + } - // Check type of all valid children, if they're same, they are needless. - for (int i=0; i<=m_triangles[parent].number_of_split_sides(); ++i) - if (m_triangles[m_triangles[parent].children[i]].is_split() - || m_triangles[m_triangles[parent].children[i]].get_state() != child_type) - return; // not all children are the same + // Call this for all non-leaf children. + for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + assert(child_idx < int(m_triangles.size()) && m_triangles[child_idx].valid); + if (m_triangles[tr.children[child_idx]].is_split()) + remove_useless_children(tr.children[child_idx]); + } - // All children are the same, kill them. - undivide_triangle(parent); - m_triangles[parent].set_state(child_type); - // And now try the same for parent (which has just become leaf). - remove_if_needless(parent); + // Return if a child is not leaf or two children differ in type. + FacetSupportType first_child_type; + for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + if (m_triangles[tr.children[child_idx]].is_split()) + return; + if (child_idx == 0) + first_child_type = m_triangles[tr.children[0]].get_state(); + else if (m_triangles[tr.children[child_idx]].get_state() != first_child_type) + return; + } + + // If we got here, the children can be removed. + undivide_triangle(facet_idx); + tr.set_state(first_child_type); } @@ -1158,33 +1177,12 @@ TriangleSelector::TriangleSelector(const TriangleMesh& mesh) m_mesh = &mesh; } - -void TriangleSelector::render() const +void TriangleSelector::render(ImGuiWrapper* imgui) { - ::glColor3f(0.f, 0.f, 1.f); - ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); - Vec3d offset = wxGetApp().model().objects.front()->instances.front()->get_transformation().get_offset(); ::glTranslatef(offset.x(), offset.y(), offset.z()); - ::glScalef(1.01f, 1.01f, 1.01f); + ::glScalef(1.005f, 1.005f, 1.005f); - ::glBegin( GL_TRIANGLES); - for (int tr_id=0; tr_idbegin(std::string("TriangleSelector dialog (DEV ONLY)"), + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + static float edge_limit = 1.f; + imgui->text("Edge limit (mm): "); + imgui->slider_float("", &edge_limit, 0.1f, 8.f); + set_edge_limit(edge_limit); + imgui->checkbox("Show triangles: ", m_show_triangles); + + int valid_triangles = std::count_if(m_triangles.begin(), m_triangles.end(), + [](const Triangle& tr) { return tr.valid; }); + imgui->text("Valid triangles: " + std::to_string(valid_triangles) + + "/" + std::to_string(m_triangles.size())); + imgui->text("Number of vertices: " + std::to_string(m_vertices.size())); + + imgui->end(); + + if (m_show_triangles) { + ::glColor3f(0.f, 0.f, 1.f); + ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + + ::glBegin( GL_TRIANGLES); + for (int tr_id=0; tr_id +#define PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + + namespace Slic3r { enum class FacetSupportType : int8_t; @@ -25,7 +28,7 @@ class ClippingPlane; class TriangleSelector { public: void set_edge_limit(float edge_limit) { m_edge_limit_sqr = std::pow(edge_limit, 2.f); } - void render() const; + // Create new object on a TriangleMesh. The referenced mesh must // stay valid, a ptr to it is saved and used. explicit TriangleSelector(const TriangleMesh& mesh); @@ -40,10 +43,19 @@ public: void unselect_all(); + // Render current selection. Transformation matrices are supposed + // to be already set. + void render(ImGuiWrapper* imgui = nullptr); + // Remove all unnecessary data (such as vertices that are not needed // because the selection has been made larger. void garbage_collect(); +#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + void render_debug(ImGuiWrapper* imgui); + bool m_show_triangles{true}; +#endif + private: // Triangle and info about how it's split. struct Triangle { @@ -112,13 +124,13 @@ private: // Private functions: bool select_triangle(int facet_idx, FacetSupportType type, - bool cursor_inside = false); + bool recursive_call = false); bool is_point_inside_cursor(const Vec3f& point) const; int vertices_inside(int facet_idx) const; bool faces_camera(int facet) const; void undivide_triangle(int facet_idx); bool split_triangle(int facet_idx); - void remove_if_needless(int child_facet); + void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. bool is_pointer_in_triangle(int facet_idx) const; bool is_edge_inside_cursor(int facet_idx) const; }; From b9321856f387d6ef07ee9d769fe9674d83b8e31b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 24 Jun 2020 12:24:32 +0200 Subject: [PATCH 26/70] TriangleSelector: Reusing of previously calculated triangle divisions, partial garbage collection implementation --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 144 ++++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 38 ++--- 2 files changed, 117 insertions(+), 65 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 8972e9246..2296dd570 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -800,51 +800,29 @@ void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const } - +// sides_to_split==-1 : just restore previous split void TriangleSelector::Triangle::set_division(int sides_to_split, int special_side_idx) { - assert(sides_to_split >=0 && sides_to_split <= 3); + assert(sides_to_split >=-1 && sides_to_split <= 3); assert(special_side_idx >=-1 && special_side_idx < 3); // If splitting one or two sides, second argument must be provided. assert(sides_to_split != 1 || special_side_idx != -1); assert(sides_to_split != 2 || special_side_idx != -1); - division_type = sides_to_split | ((special_side_idx != -1 ? special_side_idx : 0 ) <<2); -} - - - -void TriangleSelector::Triangle::set_state(FacetSupportType type) -{ - // If this is not a leaf-node, this makes no sense and - // the bits are used for storing index of an edge. - assert(! is_split()); - division_type = (int8_t(type) << 2); -} - - - -int TriangleSelector::Triangle::side_to_keep() const -{ - assert(number_of_split_sides() == 2); - return division_type >> 2; -} - - - -int TriangleSelector::Triangle::side_to_split() const -{ - assert(number_of_split_sides() == 1); - return division_type >> 2; -} - - - -FacetSupportType TriangleSelector::Triangle::get_state() const -{ - assert(! is_split()); // this must be leaf - return FacetSupportType(division_type >> 2); + if (sides_to_split != -1) { + this->number_of_splits = sides_to_split; + if (sides_to_split != 0) { + assert(old_number_of_splits == 0); + this->special_side_idx = special_side_idx; + this->old_number_of_splits = sides_to_split; + } + } + else { + assert(old_number_of_splits != 0); + this->number_of_splits = old_number_of_splits; + // indices of children should still be there. + } } @@ -938,17 +916,29 @@ bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, boo } -bool TriangleSelector::split_triangle(int facet_idx) +void TriangleSelector::split_triangle(int facet_idx) { if (m_triangles[facet_idx].is_split()) { - // The triangle was divided already. - return false; + // The triangle is divided already. + return; } Triangle* tr = &m_triangles[facet_idx]; FacetSupportType old_type = tr->get_state(); + if (tr->was_split_before() != 0) { + // This triangle is not split at the moment, but was at one point + // in history. We can just restore it and resurrect its children. + tr->set_division(-1); + for (int i=0; i<=tr->number_of_split_sides(); ++i) { + m_triangles[tr->children[i]].set_state(old_type); + m_triangles[tr->children[i]].valid = true; + } + return; + } + + // If we got here, we are about to actually split the triangle. const double limit_squared = m_edge_limit_sqr; stl_triangle_vertex_indices& facet = tr->verts_idxs; @@ -964,8 +954,9 @@ bool TriangleSelector::split_triangle(int facet_idx) side_to_keep = pt_idx; } if (sides_to_split.empty()) { + // This shall be unselected. tr->set_division(0); - return false; + return; } // indices of triangle vertices @@ -1023,11 +1014,8 @@ bool TriangleSelector::split_triangle(int facet_idx) assert(! sides_to_split.empty() && int(sides_to_split.size()) <= 3); for (int i=0; i<=int(sides_to_split.size()); ++i) { tr->children[i] = m_triangles.size()-1-i; - m_triangles[tr->children[i]].parent = facet_idx; m_triangles[tr->children[i]].set_state(old_type); } - - return true; } @@ -1166,6 +1154,45 @@ void TriangleSelector::remove_useless_children(int facet_idx) } + +void TriangleSelector::garbage_collect() +{ + // First make a map from old to new triangle indices. + int new_idx = m_orig_size_indices; + std::vector new_triangle_indices(m_triangles.size(), -1); + std::vector invalid_vertices(m_vertices.size(), false); + for (int i = m_orig_size_indices; itext("Edge limit (mm): "); imgui->slider_float("", &edge_limit, 0.1f, 8.f); set_edge_limit(edge_limit); - imgui->checkbox("Show triangles: ", m_show_triangles); + imgui->checkbox("Show split triangles: ", m_show_triangles); + imgui->checkbox("Show invalid triangles: ", m_show_invalid); int valid_triangles = std::count_if(m_triangles.begin(), m_triangles.end(), [](const Triangle& tr) { return tr.valid; }); imgui->text("Valid triangles: " + std::to_string(valid_triangles) + "/" + std::to_string(m_triangles.size())); imgui->text("Number of vertices: " + std::to_string(m_vertices.size())); + if (imgui->button("Force garbage collection")) + garbage_collect(); imgui->end(); if (m_show_triangles) { - ::glColor3f(0.f, 0.f, 1.f); ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); ::glBegin( GL_TRIANGLES); for (int tr_id=0; tr_id children; @@ -79,22 +80,25 @@ private: void set_division(int sides_to_split, int special_side_idx = -1); // Get/set current state. - void set_state(FacetSupportType state); - FacetSupportType get_state() const; + void set_state(FacetSupportType type) { assert(! is_split()); state = type; } + FacetSupportType get_state() const { assert(! is_split()); return state; } // Get info on how it's split. bool is_split() const { return number_of_split_sides() != 0; } - int number_of_split_sides() const { return division_type & 0b11; } - int side_to_keep() const; - int side_to_split() const; + int number_of_split_sides() const { return number_of_splits; } + int side_to_keep() const { assert(number_of_split_sides() == 2); return special_side_idx; } + int side_to_split() const { assert(number_of_split_sides() == 1); return special_side_idx; } + bool was_split_before() const { return old_number_of_splits != 0; } + void forget_history() { old_number_of_splits = 0; } private: - // Bitmask encoding which sides are split. - int8_t division_type; - // bits 0, 1 : decimal 0, 1, 2 or 3 (how many sides are split) - // bits 2, 3 (non-leaf): decimal 0, 1 or 2 identifying the special edge - // (one that splits in one-edge split or one that stays in two-edge split). - // bits 2, 3 (leaf): FacetSupportType value + int number_of_splits; + int special_side_idx; + FacetSupportType state; + + // How many children were spawned during last split? + // Is not reset on remerging the triangle. + int old_number_of_splits; }; // Lists of vertices and triangles, both original and new @@ -129,7 +133,7 @@ private: int vertices_inside(int facet_idx) const; bool faces_camera(int facet) const; void undivide_triangle(int facet_idx); - bool split_triangle(int facet_idx); + void split_triangle(int facet_idx); void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. bool is_pointer_in_triangle(int facet_idx) const; bool is_edge_inside_cursor(int facet_idx) const; From da6acd73e21407a8956d64b332b8fba47a94465f Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 24 Jun 2020 14:47:53 +0200 Subject: [PATCH 27/70] TriangleSelector: Vertices are reference-counted and garbage collected Garbage collection is triggered automatically when more than half of all triangles are invalid --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 133 +++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 44 +++--- 2 files changed, 120 insertions(+), 57 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 2296dd570..10d045920 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -908,10 +908,20 @@ bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, boo } } } - // In case that all children are leafs and have the same state now, - // they may be removed and substituted by the parent triangle. - if (! recursive_call) + + if (! recursive_call) { + // In case that all children are leafs and have the same state now, + // they may be removed and substituted by the parent triangle. remove_useless_children(facet_idx); + + // Make sure that we did not lose track of invalid triangles. + assert(m_invalid_triangles == std::count_if(m_triangles.begin(), m_triangles.end(), + [](const Triangle& tr) { return ! tr.valid; })); + + // Do garbage collection maybe? + if (2*m_invalid_triangles > int(m_triangles.size())) + garbage_collect(); + } return true; } @@ -934,6 +944,7 @@ void TriangleSelector::split_triangle(int facet_idx) for (int i=0; i<=tr->number_of_split_sides(); ++i) { m_triangles[tr->children[i]].set_state(old_type); m_triangles[tr->children[i]].valid = true; + --m_invalid_triangles; } return; } @@ -941,9 +952,11 @@ void TriangleSelector::split_triangle(int facet_idx) // If we got here, we are about to actually split the triangle. const double limit_squared = m_edge_limit_sqr; - stl_triangle_vertex_indices& facet = tr->verts_idxs; - const stl_vertex* pts[3] = { &m_vertices[facet[0]], &m_vertices[facet[1]], &m_vertices[facet[2]]}; - double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), (*pts[0]-*pts[2]).squaredNorm(), (*pts[1]-*pts[0]).squaredNorm() }; + std::array& facet = tr->verts_idxs; + const stl_vertex* pts[3] = { &m_vertices[facet[0]].v, &m_vertices[facet[1]].v, &m_vertices[facet[2]].v}; + double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), + (*pts[0]-*pts[2]).squaredNorm(), + (*pts[1]-*pts[0]).squaredNorm() }; std::vector sides_to_split; int side_to_keep = -1; @@ -970,37 +983,37 @@ void TriangleSelector::split_triangle(int facet_idx) if (sides_to_split.size() == 1) { - m_vertices.emplace_back((m_vertices[verts_idxs[1]] + m_vertices[verts_idxs[2]])/2.); + m_vertices.emplace_back((m_vertices[verts_idxs[1]].v + m_vertices[verts_idxs[2]].v)/2.); verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1); - m_triangles.emplace_back(verts_idxs[0], verts_idxs[1], verts_idxs[2]); - m_triangles.emplace_back(verts_idxs[2], verts_idxs[3], verts_idxs[0]); + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[2]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[0]); } if (sides_to_split.size() == 2) { - m_vertices.emplace_back((m_vertices[verts_idxs[0]] + m_vertices[verts_idxs[1]])/2.); + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[0]] + m_vertices[verts_idxs[3]])/2.); + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[3]].v)/2.); verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1); - m_triangles.emplace_back(verts_idxs[0], verts_idxs[1], verts_idxs[4]); - m_triangles.emplace_back(verts_idxs[1], verts_idxs[2], verts_idxs[4]); - m_triangles.emplace_back(verts_idxs[2], verts_idxs[3], verts_idxs[4]); + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[4]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[4]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[4]); } if (sides_to_split.size() == 3) { - m_vertices.emplace_back((m_vertices[verts_idxs[0]] + m_vertices[verts_idxs[1]])/2.); + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[2]] + m_vertices[verts_idxs[3]])/2.); + m_vertices.emplace_back((m_vertices[verts_idxs[2]].v + m_vertices[verts_idxs[3]].v)/2.); verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[4]] + m_vertices[verts_idxs[0]])/2.); + m_vertices.emplace_back((m_vertices[verts_idxs[4]].v + m_vertices[verts_idxs[0]].v)/2.); verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1); - m_triangles.emplace_back(verts_idxs[0], verts_idxs[1], verts_idxs[5]); - m_triangles.emplace_back(verts_idxs[1], verts_idxs[2], verts_idxs[3]); - m_triangles.emplace_back(verts_idxs[3], verts_idxs[4], verts_idxs[5]); - m_triangles.emplace_back(verts_idxs[1], verts_idxs[3], verts_idxs[5]); + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[3]); + push_triangle(verts_idxs[3], verts_idxs[4], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[3], verts_idxs[5]); } tr = &m_triangles[facet_idx]; // may have been invalidated @@ -1035,9 +1048,9 @@ bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const return ((b-a).cross(c-a)).dot(d-a) > 0.; }; - const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]]; - const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]]; - const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]]; + const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]].v; + const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]].v; + const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]].v; const Vec3f& q1 = m_cursor.center + m_cursor.dir; const Vec3f q2 = m_cursor.center - m_cursor.dir; @@ -1065,7 +1078,7 @@ int TriangleSelector::vertices_inside(int facet_idx) const { int inside = 0; for (size_t i=0; i<3; ++i) { - if (is_point_inside_cursor(m_vertices[m_triangles[facet_idx].verts_idxs[i]])) + if (is_point_inside_cursor(m_vertices[m_triangles[facet_idx].verts_idxs[i]].v)) ++inside; } return inside; @@ -1077,7 +1090,7 @@ bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const { Vec3f pts[3]; for (int i=0; i<3; ++i) - pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]]; + pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]].v; const Vec3f& p = m_cursor.center; @@ -1109,6 +1122,7 @@ void TriangleSelector::undivide_triangle(int facet_idx) for (int i=0; i<=tr.number_of_split_sides(); ++i) { undivide_triangle(tr.children[i]); m_triangles[tr.children[i]].valid = false; + ++m_invalid_triangles; } tr.set_division(0); // not split } @@ -1138,7 +1152,7 @@ void TriangleSelector::remove_useless_children(int facet_idx) // Return if a child is not leaf or two children differ in type. - FacetSupportType first_child_type; + FacetSupportType first_child_type = FacetSupportType::NONE; for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { if (m_triangles[tr.children[child_idx]].is_split()) return; @@ -1160,13 +1174,26 @@ void TriangleSelector::garbage_collect() // First make a map from old to new triangle indices. int new_idx = m_orig_size_indices; std::vector new_triangle_indices(m_triangles.size(), -1); - std::vector invalid_vertices(m_vertices.size(), false); for (int i = m_orig_size_indices; i new_vertices_indices(m_vertices.size(), -1); + for (int i=m_orig_size_vertices; i= 0); + if (m_vertices[i].ref_cnt != 0) { + new_vertices_indices[i] = new_idx; + ++new_idx; } } @@ -1174,6 +1201,9 @@ void TriangleSelector::garbage_collect() m_triangles.erase(std::remove_if(m_triangles.begin()+m_orig_size_indices, m_triangles.end(), [](const Triangle& tr) { return ! tr.valid; }), m_triangles.end()); + m_vertices.erase(std::remove_if(m_vertices.begin()+m_orig_size_vertices, m_vertices.end(), + [](const Vertex& vert) { return vert.ref_cnt == 0; }), + m_vertices.end()); // Now go through all remaining triangles and update changed indices. for (Triangle& tr : m_triangles) { @@ -1187,20 +1217,32 @@ void TriangleSelector::garbage_collect() } } + // Update indices into m_vertices. The original vertices are never + // touched and need not be reindexed. + for (int& idx : tr.verts_idxs) { + if (idx >= m_orig_size_vertices) { + assert(new_vertices_indices[idx] != -1); + idx = new_vertices_indices[idx]; + } + } + // If this triangle was split before, forget it. // Children referenced in the cache are dead by now. tr.forget_history(); } + + m_invalid_triangles = 0; } TriangleSelector::TriangleSelector(const TriangleMesh& mesh) { for (const stl_vertex& vert : mesh.its.vertices) - m_vertices.push_back(vert); + m_vertices.emplace_back(vert); for (const stl_triangle_vertex_indices& ind : mesh.its.indices) - m_triangles.emplace_back(Triangle(ind[0], ind[1], ind[2])); + push_triangle(ind[0], ind[1], ind[2]); m_orig_size_vertices = m_vertices.size(); m_orig_size_indices = m_triangles.size(); + m_invalid_triangles = 0; m_mesh = &mesh; } @@ -1222,9 +1264,9 @@ void TriangleSelector::render(ImGuiWrapper* imgui) ::glColor4f(1.f, 0.f, 0.f, 0.2f); for (int i=0; i<3; ++i) - ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], - m_vertices[tr.verts_idxs[i]][1], - m_vertices[tr.verts_idxs[i]][2]); + ::glVertex3f(m_vertices[tr.verts_idxs[i]].v[0], + m_vertices[tr.verts_idxs[i]].v[1], + m_vertices[tr.verts_idxs[i]].v[2]); } ::glEnd(); @@ -1250,6 +1292,18 @@ void TriangleSelector::set_edge_limit(float edge_limit) } } + + +void TriangleSelector::push_triangle(int a, int b, int c) +{ + for (int i : {a, b, c}) { + assert(i >= 0 && i < int(m_vertices.size())); + ++m_vertices[i].ref_cnt; + } + m_triangles.emplace_back(a, b, c); +} + + #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void TriangleSelector::render_debug(ImGuiWrapper* imgui) { @@ -1262,11 +1316,10 @@ void TriangleSelector::render_debug(ImGuiWrapper* imgui) imgui->checkbox("Show split triangles: ", m_show_triangles); imgui->checkbox("Show invalid triangles: ", m_show_invalid); - int valid_triangles = std::count_if(m_triangles.begin(), m_triangles.end(), - [](const Triangle& tr) { return tr.valid; }); + int valid_triangles = m_triangles.size() - m_invalid_triangles; imgui->text("Valid triangles: " + std::to_string(valid_triangles) + "/" + std::to_string(m_triangles.size())); - imgui->text("Number of vertices: " + std::to_string(m_vertices.size())); + imgui->text("Vertices: " + std::to_string(m_vertices.size())); if (imgui->button("Force garbage collection")) garbage_collect(); @@ -1290,9 +1343,9 @@ void TriangleSelector::render_debug(ImGuiWrapper* imgui) ::glColor3f(0.f, 0.f, 1.f); for (int i=0; i<3; ++i) - ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], - m_vertices[tr.verts_idxs[i]][1], - m_vertices[tr.verts_idxs[i]][2]); + ::glVertex3f(m_vertices[tr.verts_idxs[i]].v[0], + m_vertices[tr.verts_idxs[i]].v[1], + m_vertices[tr.verts_idxs[i]].v[2]); } ::glEnd(); ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index fa03f7987..fb14f3c5e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -33,7 +33,7 @@ public: // stay valid, a ptr to it is saved and used. explicit TriangleSelector(const TriangleMesh& mesh); - // Select all triangles inside the circle, subdivide where needed. + // Select all triangles fully inside the circle, subdivide where needed. void select_patch(const Vec3f& hit, // point where to start int facet_start, // facet that point belongs to const Vec3f& source, // camera position (mesh coords) @@ -41,14 +41,11 @@ public: float radius_sqr, // squared radius of the cursor FacetSupportType new_state); // enforcer or blocker? - void unselect_all(); - // Render current selection. Transformation matrices are supposed // to be already set. void render(ImGuiWrapper* imgui = nullptr); - // Remove all unnecessary data (such as vertices that are not needed - // because the selection has been made larger. + // Remove all unnecessary data. void garbage_collect(); #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG @@ -59,21 +56,24 @@ public: private: // Triangle and info about how it's split. - struct Triangle { + class Triangle { public: + // Use TriangleSelector::push_triangle to create a new triangle. + // It increments/decrements reference counter on vertices. Triangle(int a, int b, int c) - : verts_idxs{stl_triangle_vertex_indices(a, b, c)}, + : verts_idxs{a, b, c}, state{FacetSupportType(0)}, number_of_splits{0}, special_side_idx{0}, old_number_of_splits{0} {} - stl_triangle_vertex_indices verts_idxs; + // Indices into m_vertices. + std::array verts_idxs; - // Is this triangle valid or marked to remove? + // Is this triangle valid or marked to be removed? bool valid{true}; - // Children triangles (0 = no child) + // Children triangles. std::array children; // Set the division type. @@ -101,22 +101,31 @@ private: int old_number_of_splits; }; + struct Vertex { + explicit Vertex(const stl_vertex& vert) + : v{vert}, + ref_cnt{0} + {} + stl_vertex v; + int ref_cnt; + }; + // Lists of vertices and triangles, both original and new - std::vector m_vertices; + std::vector m_vertices; std::vector m_triangles; const TriangleMesh* m_mesh; + // Number of invalid triangles (to trigger garbage collection). + int m_invalid_triangles; + + // Limiting length of triangle side (squared). float m_edge_limit_sqr = 1.f; // Number of original vertices and triangles. int m_orig_size_vertices; int m_orig_size_indices; - // Limits for stopping the recursion. - float m_max_edge_length; - int m_max_recursion_depth; - - // Caches for cursor position, radius and direction. + // Cache for cursor position, radius and direction. struct Cursor { Vec3f center; Vec3f source; @@ -130,13 +139,14 @@ private: bool select_triangle(int facet_idx, FacetSupportType type, bool recursive_call = false); bool is_point_inside_cursor(const Vec3f& point) const; - int vertices_inside(int facet_idx) const; + int vertices_inside(int facet_idx) const; bool faces_camera(int facet) const; void undivide_triangle(int facet_idx); void split_triangle(int facet_idx); void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. bool is_pointer_in_triangle(int facet_idx) const; bool is_edge_inside_cursor(int facet_idx) const; + void push_triangle(int a, int b, int c); }; From 814f8be92f410e2cd5a7928a64a5a032072da342 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 1 Jul 2020 09:09:25 +0200 Subject: [PATCH 28/70] TriangleSelector: getting ready for frontend/backend separation --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 279 +++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 11 +- 2 files changed, 207 insertions(+), 83 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 10d045920..f2a6bb8ad 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -972,63 +972,12 @@ void TriangleSelector::split_triangle(int facet_idx) return; } - // indices of triangle vertices - std::vector verts_idxs; - int idx = sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]; - for (int j=0; j<3; ++j) { - verts_idxs.push_back(facet[idx++]); - if (idx == 3) - idx = 0; - } - - - if (sides_to_split.size() == 1) { - m_vertices.emplace_back((m_vertices[verts_idxs[1]].v + m_vertices[verts_idxs[2]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1); - - push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[2]); - push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[0]); - } - - if (sides_to_split.size() == 2) { - m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); - - m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[3]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1); - - push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[4]); - push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[4]); - push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[4]); - } - - if (sides_to_split.size() == 3) { - m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[2]].v + m_vertices[verts_idxs[3]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[4]].v + m_vertices[verts_idxs[0]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1); - - push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[5]); - push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[3]); - push_triangle(verts_idxs[3], verts_idxs[4], verts_idxs[5]); - push_triangle(verts_idxs[1], verts_idxs[3], verts_idxs[5]); - } - - tr = &m_triangles[facet_idx]; // may have been invalidated - - // Save how the triangle was split. Second argument makes sense only for one + // Save how the triangle will be split. Second argument makes sense only for one // or two split sides, otherwise the value is ignored. tr->set_division(sides_to_split.size(), sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]); - // And save the children. All children should start in the same state as the triangle we just split. - assert(! sides_to_split.empty() && int(sides_to_split.size()) <= 3); - for (int i=0; i<=int(sides_to_split.size()); ++i) { - tr->children[i] = m_triangles.size()-1-i; - m_triangles[tr->children[i]].set_state(old_type); - } + perform_split(facet_idx, old_type); } @@ -1253,22 +1202,45 @@ void TriangleSelector::render(ImGuiWrapper* imgui) ::glScalef(1.005f, 1.005f, 1.005f); - ::glBegin( GL_TRIANGLES); + int enf_cnt = 0; + int blc_cnt = 0; + for (const Triangle& tr : m_triangles) { if (! tr.valid || tr.is_split() || tr.get_state() == FacetSupportType::NONE) continue; - if (tr.get_state() == FacetSupportType::ENFORCER) - ::glColor4f(0.f, 0.f, 1.f, 0.2f); - else - ::glColor4f(1.f, 0.f, 0.f, 0.2f); + GLIndexedVertexArray& va = tr.get_state() == FacetSupportType::ENFORCER + ? m_iva_enforcers + : m_iva_blockers; + int& cnt = tr.get_state() == FacetSupportType::ENFORCER + ? enf_cnt + : blc_cnt; for (int i=0; i<3; ++i) - ::glVertex3f(m_vertices[tr.verts_idxs[i]].v[0], - m_vertices[tr.verts_idxs[i]].v[1], - m_vertices[tr.verts_idxs[i]].v[2]); + va.push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), + double(m_vertices[tr.verts_idxs[i]].v[1]), + double(m_vertices[tr.verts_idxs[i]].v[2]), + 0., 0., 1.); + va.push_triangle(cnt, + cnt+1, + cnt+2); + cnt += 3; } - ::glEnd(); + + m_iva_enforcers.finalize_geometry(true); + m_iva_blockers.finalize_geometry(true); + + if (m_iva_enforcers.has_VBOs()) { + ::glColor4f(0.f, 0.f, 1.f, 0.2f); + m_iva_enforcers.render(); + } + m_iva_enforcers.release_geometry(); + + if (m_iva_blockers.has_VBOs()) { + ::glColor4f(1.f, 0.f, 0.f, 0.2f); + m_iva_blockers.render(); + } + m_iva_blockers.release_geometry(); #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG if (imgui) @@ -1304,6 +1276,110 @@ void TriangleSelector::push_triangle(int a, int b, int c) } +void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) +{ + Triangle* tr = &m_triangles[facet_idx]; + + assert(tr->is_split()); + + // Read info about how to split this triangle. + int sides_to_split = tr->number_of_split_sides(); + + // indices of triangle vertices + std::vector verts_idxs; + int idx = tr->special_side(); + for (int j=0; j<3; ++j) { + verts_idxs.push_back(tr->verts_idxs[idx++]); + if (idx == 3) + idx = 0; + } + + if (sides_to_split == 1) { + m_vertices.emplace_back((m_vertices[verts_idxs[1]].v + m_vertices[verts_idxs[2]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[2]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[0]); + } + + if (sides_to_split == 2) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[3]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[4]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[4]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[4]); + } + + if (sides_to_split == 3) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[2]].v + m_vertices[verts_idxs[3]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[4]].v + m_vertices[verts_idxs[0]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[3]); + push_triangle(verts_idxs[3], verts_idxs[4], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[3], verts_idxs[5]); + } + + tr = &m_triangles[facet_idx]; // may have been invalidated + + // And save the children. All children should start in the same state as the triangle we just split. + assert(sides_to_split <= 3); + for (int i=0; i<=sides_to_split; ++i) { + tr->children[i] = m_triangles.size()-1-i; + m_triangles[tr->children[i]].set_state(old_state); + } +} + + +std::map TriangleSelector::serialize() const +{ + std::map out; + for (int i=0; i serialize_recursive; + serialize_recursive = [this, &stored_triangles, &data, &serialize_recursive](int facet_idx) { + const Triangle& tr = m_triangles[facet_idx]; + int split_sides = tr.number_of_split_sides(); + assert( split_sides > 0 && split_sides <= 3); + data |= (split_sides << (stored_triangles * 4)); + + if (tr.is_split()) { + assert(split_sides > 0); + assert(tr.special_side() >= 0 && tr.special_side() <= 3); + data |= (tr.special_side() << (stored_triangles * 4 + 2)); + ++stored_triangles; + for (int child_idx=0; child_idx<=split_sides; ++child_idx) + serialize_recursive(tr.children[child_idx]); + } else { + assert(int8_t(tr.get_state()) <= 3); + data |= (int8_t(tr.get_state()) << (stored_triangles * 4 + 2)); + ++stored_triangles; + } + }; + + serialize_recursive(i); + out[i] = data; + } + + return out; +} + + #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void TriangleSelector::render_debug(ImGuiWrapper* imgui) { @@ -1323,33 +1399,74 @@ void TriangleSelector::render_debug(ImGuiWrapper* imgui) if (imgui->button("Force garbage collection")) garbage_collect(); + if (imgui->button("Serialize")) { + auto map = serialize(); + for (auto& [idx, data] : map) + std::cout << idx << "\t" << data << std::endl; + } + imgui->end(); - if (m_show_triangles) { - ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + if (! m_show_triangles) + return; - ::glBegin( GL_TRIANGLES); - for (int tr_id=0; tr_id cnts; - for (int i=0; i<3; ++i) - ::glVertex3f(m_vertices[tr.verts_idxs[i]].v[0], - m_vertices[tr.verts_idxs[i]].v[1], - m_vertices[tr.verts_idxs[i]].v[2]); + ::glScalef(1.01f, 1.01f, 1.01f); + + for (int tr_id=0; tr_idpush_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), + double(m_vertices[tr.verts_idxs[i]].v[1]), + double(m_vertices[tr.verts_idxs[i]].v[2]), + 0., 0., 1.); + va->push_triangle(*cnt, + *cnt+1, + *cnt+2); + *cnt += 3; } + + ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + for (vtype i : {ORIGINAL, SPLIT, INVALID}) { + GLIndexedVertexArray& va = m_varrays[i]; + va.finalize_geometry(true); + if (va.has_VBOs()) { + switch (i) { + case ORIGINAL : ::glColor3f(0.f, 0.f, 1.f); break; + case SPLIT : ::glColor3f(1.f, 0.f, 0.f); break; + case INVALID : ::glColor3f(1.f, 1.f, 0.f); break; + } + va.render(); + } + } + ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); } #endif diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index fb14f3c5e..f3a66eca9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -48,6 +48,9 @@ public: // Remove all unnecessary data. void garbage_collect(); + // Store the division trees in compact form. + std::map serialize() const; + #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void render_debug(ImGuiWrapper* imgui); bool m_show_triangles{true}; @@ -86,8 +89,7 @@ private: // Get info on how it's split. bool is_split() const { return number_of_split_sides() != 0; } int number_of_split_sides() const { return number_of_splits; } - int side_to_keep() const { assert(number_of_split_sides() == 2); return special_side_idx; } - int side_to_split() const { assert(number_of_split_sides() == 1); return special_side_idx; } + int special_side() const { assert(is_split()); return special_side_idx; } bool was_split_before() const { return old_number_of_splits != 0; } void forget_history() { old_number_of_splits = 0; } @@ -115,6 +117,10 @@ private: std::vector m_triangles; const TriangleMesh* m_mesh; + GLIndexedVertexArray m_iva_enforcers; + GLIndexedVertexArray m_iva_blockers; + std::array m_varrays; + // Number of invalid triangles (to trigger garbage collection). int m_invalid_triangles; @@ -147,6 +153,7 @@ private: bool is_pointer_in_triangle(int facet_idx) const; bool is_edge_inside_cursor(int facet_idx) const; void push_triangle(int a, int b, int c); + void perform_split(int facet_idx, FacetSupportType old_state); }; From b250c08ec9c76fe3e1895e3ed40536c4c884ecb4 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 2 Jul 2020 12:30:12 +0200 Subject: [PATCH 29/70] TriangleSelector: Serialization and deserialization --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 153 ++++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 11 +- 2 files changed, 143 insertions(+), 21 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index f2a6bb8ad..01427ec09 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -1184,15 +1184,26 @@ void TriangleSelector::garbage_collect() } TriangleSelector::TriangleSelector(const TriangleMesh& mesh) + : m_mesh{&mesh} { - for (const stl_vertex& vert : mesh.its.vertices) + reset(); + +} + + +void TriangleSelector::reset() +{ + if (! m_orig_size_indices != 0) // unless this is run from constructor + garbage_collect(); + m_vertices.clear(); + m_triangles.clear(); + for (const stl_vertex& vert : m_mesh->its.vertices) m_vertices.emplace_back(vert); - for (const stl_triangle_vertex_indices& ind : mesh.its.indices) + for (const stl_triangle_vertex_indices& ind : m_mesh->its.indices) push_triangle(ind[0], ind[1], ind[2]); m_orig_size_vertices = m_vertices.size(); m_orig_size_indices = m_triangles.size(); m_invalid_triangles = 0; - m_mesh = &mesh; } void TriangleSelector::render(ImGuiWrapper* imgui) @@ -1339,35 +1350,56 @@ void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) } -std::map TriangleSelector::serialize() const +std::map> TriangleSelector::serialize() const { - std::map out; + // Each original triangle of the mesh is assigned a number encoding its state + // or how it is split. Each triangle is encoded by 4 bits (xxyy): + // leaf triangle: xx = FacetSupportType, yy = 0 + // non-leaf: xx = special side, yy = number of split sides + // These are bitwise appended and formed into one 64-bit integer. + + // The function returns a map from original triangle indices to + // stream of bits encoding state and offsprings. + + std::map> out; for (int i=0; i data; // complete encoding of this mesh triangle + int stored_triangles = 0; // how many have been already encoded std::function serialize_recursive; - serialize_recursive = [this, &stored_triangles, &data, &serialize_recursive](int facet_idx) { + serialize_recursive = [this, &serialize_recursive, &stored_triangles, &data](int facet_idx) { const Triangle& tr = m_triangles[facet_idx]; + + // Always save number of split sides. It is zero for unsplit triangles. int split_sides = tr.number_of_split_sides(); - assert( split_sides > 0 && split_sides <= 3); - data |= (split_sides << (stored_triangles * 4)); + assert(split_sides >= 0 && split_sides <= 3); + + //data |= (split_sides << (stored_triangles * 4)); + data.push_back(split_sides & 0b01); + data.push_back(split_sides & 0b10); if (tr.is_split()) { + // If this triangle is split, save which side is split (in case + // of one split) or kept (in case of two splits). The value will + // be ignored for 3-side split. assert(split_sides > 0); assert(tr.special_side() >= 0 && tr.special_side() <= 3); - data |= (tr.special_side() << (stored_triangles * 4 + 2)); + data.push_back(tr.special_side() & 0b01); + data.push_back(tr.special_side() & 0b10); ++stored_triangles; + // Now save all children. for (int child_idx=0; child_idx<=split_sides; ++child_idx) serialize_recursive(tr.children[child_idx]); } else { - assert(int8_t(tr.get_state()) <= 3); - data |= (int8_t(tr.get_state()) << (stored_triangles * 4 + 2)); + // In case this is leaf, we better save information about its state. + assert(int(tr.get_state()) <= 3); + data.push_back(int(tr.get_state()) & 0b01); + data.push_back(int(tr.get_state()) & 0b10); ++stored_triangles; } }; @@ -1379,6 +1411,90 @@ std::map TriangleSelector::serialize() const return out; } +void TriangleSelector::deserialize(const std::map> data) +{ + reset(); // dump any current state + for (const auto& [triangle_id, code] : data) { + assert(triangle_id < int(m_triangles.size())); + int processed_triangles = 0; + struct ProcessingInfo { + int facet_id = 0; + int processed_children = 0; + int total_children = 0; + }; + + // Vector to store all parents that have offsprings. + std::vector parents; + + while (true) { + // Read next triangle info. + int next_code = 0; + for (int i=3; i>=0; --i) { + next_code = next_code << 1; + next_code |= int(code[4 * processed_triangles + i]); + } + ++processed_triangles; + + int num_of_split_sides = (next_code & 0b11); + int num_of_children = num_of_split_sides != 0 ? num_of_split_sides + 1 : 0; + bool is_split = num_of_children != 0; + FacetSupportType state = FacetSupportType(next_code >> 2); + int special_side = (next_code >> 2); + + // Take care of the first iteration separately, so handling of the others is simpler. + if (parents.empty()) { + if (! is_split) { + // root is not split. just set the state and that's it. + m_triangles[triangle_id].set_state(state); + break; + } else { + // root is split, add it into list of parents and split it. + // then go to the next. + parents.push_back({triangle_id, 0, num_of_children}); + m_triangles[triangle_id].set_division(num_of_children-1, special_side); + perform_split(triangle_id, FacetSupportType::NONE); + continue; + } + } + + // This is not the first iteration. This triangle is a child of last seen parent. + assert(! parents.empty()); + assert(parents.back().processed_children < parents.back().total_children); + + if (is_split) { + // split the triangle and save it as parent of the next ones. + const ProcessingInfo& last = parents.back(); + int this_idx = m_triangles[last.facet_id].children[last.processed_children]; + m_triangles[this_idx].set_division(num_of_children-1, special_side); + perform_split(this_idx, FacetSupportType::NONE); + parents.push_back({this_idx, 0, num_of_children}); + } else { + // this triangle belongs to last split one + m_triangles[m_triangles[parents.back().facet_id].children[parents.back().processed_children]].set_state(state); + ++parents.back().processed_children; + } + + + // If all children of the past parent triangle are claimed, move to grandparent. + while (parents.back().processed_children == parents.back().total_children) { + parents.pop_back(); + + if (parents.empty()) + break; + + // And increment the grandparent children counter, because + // we have just finished that branch and got back here. + ++parents.back().processed_children; + } + + // In case we popped back the root, we should be done. + if (parents.empty()) + break; + } + + } +} + #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void TriangleSelector::render_debug(ImGuiWrapper* imgui) @@ -1399,10 +1515,9 @@ void TriangleSelector::render_debug(ImGuiWrapper* imgui) if (imgui->button("Force garbage collection")) garbage_collect(); - if (imgui->button("Serialize")) { + if (imgui->button("Serialize - deserialize")) { auto map = serialize(); - for (auto& [idx, data] : map) - std::cout << idx << "\t" << data << std::endl; + deserialize(map); } imgui->end(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index f3a66eca9..099c9a30c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -45,11 +45,18 @@ public: // to be already set. void render(ImGuiWrapper* imgui = nullptr); + // Clear everything and make the tree empty. + void reset(); + // Remove all unnecessary data. void garbage_collect(); - // Store the division trees in compact form. - std::map serialize() const; + // Store the division trees in compact form (a long stream of + // bits for each triangle of the original mesh). + std::map> serialize() const; + + // Load serialized data. Assumes that correct mesh is loaded. + void deserialize(const std::map> data); #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void render_debug(ImGuiWrapper* imgui); From 6baff45759bc9dd69e2a836bef59f62101d58f77 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 9 Jul 2020 16:25:34 +0200 Subject: [PATCH 30/70] TriangleSelector: Separated frontend/backend, support of multiple volumes, etc. --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Model.cpp | 19 +- src/libslic3r/Model.hpp | 6 +- src/libslic3r/TriangleSelector.cpp | 654 ++++++++++++++++ src/libslic3r/TriangleSelector.hpp | 149 ++++ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 736 +------------------ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 145 +--- 7 files changed, 856 insertions(+), 855 deletions(-) create mode 100644 src/libslic3r/TriangleSelector.cpp create mode 100644 src/libslic3r/TriangleSelector.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 881466b39..9f566b405 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -187,6 +187,8 @@ add_library(libslic3r STATIC Utils.hpp Time.cpp Time.hpp + TriangleSelector.cpp + TriangleSelector.hpp MTUtils.hpp VoronoiOffset.cpp VoronoiOffset.hpp diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 0719cac8c..b6bae489b 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2,6 +2,7 @@ #include "ModelArrange.hpp" #include "Geometry.hpp" #include "MTUtils.hpp" +#include "TriangleSelector.hpp" #include "Format/AMF.hpp" #include "Format/OBJ.hpp" @@ -1833,25 +1834,21 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const std::vector FacetsAnnotation::get_facets(FacetSupportType type) const { std::vector out; - for (auto& [facet_idx, this_type] : m_data) + /*for (auto& [facet_idx, this_type] : m_data) if (this_type == type) out.push_back(facet_idx); - return out; + */return out; } -void FacetsAnnotation::set_facet(int idx, FacetSupportType type) +void FacetsAnnotation::set(const TriangleSelector& selector) { - bool changed = true; - - if (type == FacetSupportType::NONE) - changed = m_data.erase(idx) != 0; - else - m_data[idx] = type; - - if (changed) + std::map> sel_map = selector.serialize(); + if (sel_map != m_data) { + m_data = sel_map; update_timestamp(); + } } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index be298ae4b..de20e0fdc 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -39,6 +39,7 @@ class ModelVolume; class ModelWipeTower; class Print; class SLAPrint; +class TriangleSelector; namespace UndoRedo { class StackImpl; @@ -404,8 +405,9 @@ class FacetsAnnotation { public: using ClockType = std::chrono::steady_clock; + const std::map>& get_data() const { return m_data; } + void set(const TriangleSelector& selector); std::vector get_facets(FacetSupportType type) const; - void set_facet(int idx, FacetSupportType type); void clear(); ClockType::time_point get_timestamp() const { return timestamp; } @@ -419,7 +421,7 @@ public: } private: - std::map m_data; + std::map> m_data; ClockType::time_point timestamp; void update_timestamp() { diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp new file mode 100644 index 000000000..340f5a29a --- /dev/null +++ b/src/libslic3r/TriangleSelector.cpp @@ -0,0 +1,654 @@ +#include "TriangleSelector.hpp" +#include "Model.hpp" + + +namespace Slic3r { + + + +// sides_to_split==-1 : just restore previous split +void TriangleSelector::Triangle::set_division(int sides_to_split, int special_side_idx) +{ + assert(sides_to_split >=-1 && sides_to_split <= 3); + assert(special_side_idx >=-1 && special_side_idx < 3); + + // If splitting one or two sides, second argument must be provided. + assert(sides_to_split != 1 || special_side_idx != -1); + assert(sides_to_split != 2 || special_side_idx != -1); + + if (sides_to_split != -1) { + this->number_of_splits = sides_to_split; + if (sides_to_split != 0) { + assert(old_number_of_splits == 0); + this->special_side_idx = special_side_idx; + this->old_number_of_splits = sides_to_split; + } + } + else { + assert(old_number_of_splits != 0); + this->number_of_splits = old_number_of_splits; + // indices of children should still be there. + } +} + + + +void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, + const Vec3f& source, const Vec3f& dir, + float radius_sqr, FacetSupportType new_state) +{ + assert(facet_start < m_orig_size_indices); + assert(is_approx(dir.norm(), 1.f)); + + // Save current cursor center, squared radius and camera direction, + // so we don't have to pass it around. + m_cursor = {hit, source, dir, radius_sqr}; + + // Now start with the facet the pointer points to and check all adjacent facets. + std::vector facets_to_check{facet_start}; + std::vector visited(m_orig_size_indices, false); // keep track of facets we already processed + int facet_idx = 0; // index into facets_to_check + while (facet_idx < int(facets_to_check.size())) { + int facet = facets_to_check[facet_idx]; + if (! visited[facet]) { + if (select_triangle(facet, new_state)) { + // add neighboring facets to list to be proccessed later + for (int n=0; n<3; ++n) { + if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) + facets_to_check.push_back(m_mesh->stl.neighbors_start[facet].neighbor[n]); + } + } + } + visited[facet] = true; + ++facet_idx; + } +} + + + +// Selects either the whole triangle (discarding any children it had), or divides +// the triangle recursively, selecting just subtriangles truly inside the circle. +// This is done by an actual recursive call. Returns false if the triangle is +// outside the cursor. +bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool recursive_call) +{ + assert(facet_idx < int(m_triangles.size())); + + Triangle* tr = &m_triangles[facet_idx]; + if (! tr->valid) + return false; + + int num_of_inside_vertices = vertices_inside(facet_idx); + + if (num_of_inside_vertices == 0 + && ! is_pointer_in_triangle(facet_idx) + && ! is_edge_inside_cursor(facet_idx)) + return false; + + if (num_of_inside_vertices == 3) { + // dump any subdivision and select whole triangle + undivide_triangle(facet_idx); + tr->set_state(type); + } else { + // the triangle is partially inside, let's recursively divide it + // (if not already) and try selecting its children. + + if (! tr->is_split() && tr->get_state() == type) { + // This is leaf triangle that is already of correct type as a whole. + // No need to split, all children would end up selected anyway. + return true; + } + + split_triangle(facet_idx); + tr = &m_triangles[facet_idx]; // might have been invalidated + + + int num_of_children = tr->number_of_split_sides() + 1; + if (num_of_children != 1) { + for (int i=0; ichildren.size())); + assert(tr->children[i] < int(m_triangles.size())); + + select_triangle(tr->children[i], type, true); + tr = &m_triangles[facet_idx]; // might have been invalidated + } + } + } + + if (! recursive_call) { + // In case that all children are leafs and have the same state now, + // they may be removed and substituted by the parent triangle. + remove_useless_children(facet_idx); + + // Make sure that we did not lose track of invalid triangles. + assert(m_invalid_triangles == std::count_if(m_triangles.begin(), m_triangles.end(), + [](const Triangle& tr) { return ! tr.valid; })); + + // Do garbage collection maybe? + if (2*m_invalid_triangles > int(m_triangles.size())) + garbage_collect(); + } + return true; +} + + +void TriangleSelector::split_triangle(int facet_idx) +{ + if (m_triangles[facet_idx].is_split()) { + // The triangle is divided already. + return; + } + + Triangle* tr = &m_triangles[facet_idx]; + + FacetSupportType old_type = tr->get_state(); + + if (tr->was_split_before() != 0) { + // This triangle is not split at the moment, but was at one point + // in history. We can just restore it and resurrect its children. + tr->set_division(-1); + for (int i=0; i<=tr->number_of_split_sides(); ++i) { + m_triangles[tr->children[i]].set_state(old_type); + m_triangles[tr->children[i]].valid = true; + --m_invalid_triangles; + } + return; + } + + // If we got here, we are about to actually split the triangle. + const double limit_squared = m_edge_limit_sqr; + + std::array& facet = tr->verts_idxs; + const stl_vertex* pts[3] = { &m_vertices[facet[0]].v, &m_vertices[facet[1]].v, &m_vertices[facet[2]].v}; + double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), + (*pts[0]-*pts[2]).squaredNorm(), + (*pts[1]-*pts[0]).squaredNorm() }; + + std::vector sides_to_split; + int side_to_keep = -1; + for (int pt_idx = 0; pt_idx<3; ++pt_idx) { + if (sides[pt_idx] > limit_squared) + sides_to_split.push_back(pt_idx); + else + side_to_keep = pt_idx; + } + if (sides_to_split.empty()) { + // This shall be unselected. + tr->set_division(0); + return; + } + + // Save how the triangle will be split. Second argument makes sense only for one + // or two split sides, otherwise the value is ignored. + tr->set_division(sides_to_split.size(), + sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]); + + perform_split(facet_idx, old_type); +} + + +// Calculate distance of a point from a line. +bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const +{ + Vec3f diff = m_cursor.center - point; + return (diff - diff.dot(m_cursor.dir) * m_cursor.dir).squaredNorm() < m_cursor.radius_sqr; +} + + +// Is pointer in a triangle? +bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const +{ + auto signed_volume_sign = [](const Vec3f& a, const Vec3f& b, + const Vec3f& c, const Vec3f& d) -> bool { + return ((b-a).cross(c-a)).dot(d-a) > 0.; + }; + + const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]].v; + const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]].v; + const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]].v; + const Vec3f& q1 = m_cursor.center + m_cursor.dir; + const Vec3f q2 = m_cursor.center - m_cursor.dir; + + if (signed_volume_sign(q1,p1,p2,p3) != signed_volume_sign(q2,p1,p2,p3)) { + bool pos = signed_volume_sign(q1,q2,p1,p2); + if (signed_volume_sign(q1,q2,p2,p3) == pos && signed_volume_sign(q1,q2,p3,p1) == pos) + return true; + } + return false; +} + + + +// Determine whether this facet is potentially visible (still can be obscured). +bool TriangleSelector::faces_camera(int facet) const +{ + assert(facet < m_orig_size_indices); + // The normal is cached in mesh->stl, use it. + return (m_mesh->stl.facet_start[facet].normal.dot(m_cursor.dir) < 0.); +} + + +// How many vertices of a triangle are inside the circle? +int TriangleSelector::vertices_inside(int facet_idx) const +{ + int inside = 0; + for (size_t i=0; i<3; ++i) { + if (is_point_inside_cursor(m_vertices[m_triangles[facet_idx].verts_idxs[i]].v)) + ++inside; + } + return inside; +} + + +// Is edge inside cursor? +bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const +{ + Vec3f pts[3]; + for (int i=0; i<3; ++i) + pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]].v; + + const Vec3f& p = m_cursor.center; + + for (int side = 0; side < 3; ++side) { + const Vec3f& a = pts[side]; + const Vec3f& b = pts[side<2 ? side+1 : 0]; + Vec3f s = (b-a).normalized(); + float t = (p-a).dot(s); + Vec3f vector = a+t*s - p; + + // vector is 3D vector from center to the intersection. What we want to + // measure is length of its projection onto plane perpendicular to dir. + float dist_sqr = vector.squaredNorm() - std::pow(vector.dot(m_cursor.dir), 2.f); + if (dist_sqr < m_cursor.radius_sqr && t>=0.f && t<=(b-a).norm()) + return true; + } + return false; +} + + + +// Recursively remove all subtriangles. +void TriangleSelector::undivide_triangle(int facet_idx) +{ + assert(facet_idx < int(m_triangles.size())); + Triangle& tr = m_triangles[facet_idx]; + + if (tr.is_split()) { + for (int i=0; i<=tr.number_of_split_sides(); ++i) { + undivide_triangle(tr.children[i]); + m_triangles[tr.children[i]].valid = false; + ++m_invalid_triangles; + } + tr.set_division(0); // not split + } +} + + +void TriangleSelector::remove_useless_children(int facet_idx) +{ + // Check that all children are leafs of the same type. If not, try to + // make them (recursive call). Remove them if sucessful. + + assert(facet_idx < int(m_triangles.size()) && m_triangles[facet_idx].valid); + Triangle& tr = m_triangles[facet_idx]; + + if (! tr.is_split()) { + // This is a leaf, there nothing to do. This can happen during the + // first (non-recursive call). Shouldn't otherwise. + return; + } + + // Call this for all non-leaf children. + for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + assert(child_idx < int(m_triangles.size()) && m_triangles[child_idx].valid); + if (m_triangles[tr.children[child_idx]].is_split()) + remove_useless_children(tr.children[child_idx]); + } + + + // Return if a child is not leaf or two children differ in type. + FacetSupportType first_child_type = FacetSupportType::NONE; + for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + if (m_triangles[tr.children[child_idx]].is_split()) + return; + if (child_idx == 0) + first_child_type = m_triangles[tr.children[0]].get_state(); + else if (m_triangles[tr.children[child_idx]].get_state() != first_child_type) + return; + } + + // If we got here, the children can be removed. + undivide_triangle(facet_idx); + tr.set_state(first_child_type); +} + + + +void TriangleSelector::garbage_collect() +{ + // First make a map from old to new triangle indices. + int new_idx = m_orig_size_indices; + std::vector new_triangle_indices(m_triangles.size(), -1); + for (int i = m_orig_size_indices; i new_vertices_indices(m_vertices.size(), -1); + for (int i=m_orig_size_vertices; i= 0); + if (m_vertices[i].ref_cnt != 0) { + new_vertices_indices[i] = new_idx; + ++new_idx; + } + } + + // We can remove all invalid triangles and vertices that are no longer referenced. + m_triangles.erase(std::remove_if(m_triangles.begin()+m_orig_size_indices, m_triangles.end(), + [](const Triangle& tr) { return ! tr.valid; }), + m_triangles.end()); + m_vertices.erase(std::remove_if(m_vertices.begin()+m_orig_size_vertices, m_vertices.end(), + [](const Vertex& vert) { return vert.ref_cnt == 0; }), + m_vertices.end()); + + // Now go through all remaining triangles and update changed indices. + for (Triangle& tr : m_triangles) { + assert(tr.valid); + + if (tr.is_split()) { + // There are children. Update their indices. + for (int j=0; j<=tr.number_of_split_sides(); ++j) { + assert(new_triangle_indices[tr.children[j]] != -1); + tr.children[j] = new_triangle_indices[tr.children[j]]; + } + } + + // Update indices into m_vertices. The original vertices are never + // touched and need not be reindexed. + for (int& idx : tr.verts_idxs) { + if (idx >= m_orig_size_vertices) { + assert(new_vertices_indices[idx] != -1); + idx = new_vertices_indices[idx]; + } + } + + // If this triangle was split before, forget it. + // Children referenced in the cache are dead by now. + tr.forget_history(); + } + + m_invalid_triangles = 0; +} + +TriangleSelector::TriangleSelector(const TriangleMesh& mesh) + : m_mesh{&mesh} +{ + reset(); +} + + +void TriangleSelector::reset() +{ + if (! m_orig_size_indices != 0) // unless this is run from constructor + garbage_collect(); + m_vertices.clear(); + m_triangles.clear(); + for (const stl_vertex& vert : m_mesh->its.vertices) + m_vertices.emplace_back(vert); + for (const stl_triangle_vertex_indices& ind : m_mesh->its.indices) + push_triangle(ind[0], ind[1], ind[2]); + m_orig_size_vertices = m_vertices.size(); + m_orig_size_indices = m_triangles.size(); + m_invalid_triangles = 0; +} + + + + + +void TriangleSelector::set_edge_limit(float edge_limit) +{ + float new_limit_sqr = std::pow(edge_limit, 2.f); + + if (new_limit_sqr != m_edge_limit_sqr) { + m_edge_limit_sqr = new_limit_sqr; + + // The way how triangles split may be different now, forget + // all cached splits. + garbage_collect(); + } +} + + + +void TriangleSelector::push_triangle(int a, int b, int c) +{ + for (int i : {a, b, c}) { + assert(i >= 0 && i < int(m_vertices.size())); + ++m_vertices[i].ref_cnt; + } + m_triangles.emplace_back(a, b, c); +} + + +void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) +{ + Triangle* tr = &m_triangles[facet_idx]; + + assert(tr->is_split()); + + // Read info about how to split this triangle. + int sides_to_split = tr->number_of_split_sides(); + + // indices of triangle vertices + std::vector verts_idxs; + int idx = tr->special_side(); + for (int j=0; j<3; ++j) { + verts_idxs.push_back(tr->verts_idxs[idx++]); + if (idx == 3) + idx = 0; + } + + if (sides_to_split == 1) { + m_vertices.emplace_back((m_vertices[verts_idxs[1]].v + m_vertices[verts_idxs[2]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[2]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[0]); + } + + if (sides_to_split == 2) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[3]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[4]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[4]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[4]); + } + + if (sides_to_split == 3) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[2]].v + m_vertices[verts_idxs[3]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[4]].v + m_vertices[verts_idxs[0]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[3]); + push_triangle(verts_idxs[3], verts_idxs[4], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[3], verts_idxs[5]); + } + + tr = &m_triangles[facet_idx]; // may have been invalidated + + // And save the children. All children should start in the same state as the triangle we just split. + assert(sides_to_split <= 3); + for (int i=0; i<=sides_to_split; ++i) { + tr->children[i] = m_triangles.size()-1-i; + m_triangles[tr->children[i]].set_state(old_state); + } +} + + +std::map> TriangleSelector::serialize() const +{ + // Each original triangle of the mesh is assigned a number encoding its state + // or how it is split. Each triangle is encoded by 4 bits (xxyy): + // leaf triangle: xx = FacetSupportType, yy = 0 + // non-leaf: xx = special side, yy = number of split sides + // These are bitwise appended and formed into one 64-bit integer. + + // The function returns a map from original triangle indices to + // stream of bits encoding state and offsprings. + + std::map> out; + for (int i=0; i data; // complete encoding of this mesh triangle + int stored_triangles = 0; // how many have been already encoded + + std::function serialize_recursive; + serialize_recursive = [this, &serialize_recursive, &stored_triangles, &data](int facet_idx) { + const Triangle& tr = m_triangles[facet_idx]; + + // Always save number of split sides. It is zero for unsplit triangles. + int split_sides = tr.number_of_split_sides(); + assert(split_sides >= 0 && split_sides <= 3); + + //data |= (split_sides << (stored_triangles * 4)); + data.push_back(split_sides & 0b01); + data.push_back(split_sides & 0b10); + + if (tr.is_split()) { + // If this triangle is split, save which side is split (in case + // of one split) or kept (in case of two splits). The value will + // be ignored for 3-side split. + assert(split_sides > 0); + assert(tr.special_side() >= 0 && tr.special_side() <= 3); + data.push_back(tr.special_side() & 0b01); + data.push_back(tr.special_side() & 0b10); + ++stored_triangles; + // Now save all children. + for (int child_idx=0; child_idx<=split_sides; ++child_idx) + serialize_recursive(tr.children[child_idx]); + } else { + // In case this is leaf, we better save information about its state. + assert(int(tr.get_state()) <= 3); + data.push_back(int(tr.get_state()) & 0b01); + data.push_back(int(tr.get_state()) & 0b10); + ++stored_triangles; + } + }; + + serialize_recursive(i); + out[i] = data; + } + + return out; +} + +void TriangleSelector::deserialize(const std::map> data) +{ + reset(); // dump any current state + for (const auto& [triangle_id, code] : data) { + assert(triangle_id < int(m_triangles.size())); + int processed_triangles = 0; + struct ProcessingInfo { + int facet_id = 0; + int processed_children = 0; + int total_children = 0; + }; + + // Vector to store all parents that have offsprings. + std::vector parents; + + while (true) { + // Read next triangle info. + int next_code = 0; + for (int i=3; i>=0; --i) { + next_code = next_code << 1; + next_code |= int(code[4 * processed_triangles + i]); + } + ++processed_triangles; + + int num_of_split_sides = (next_code & 0b11); + int num_of_children = num_of_split_sides != 0 ? num_of_split_sides + 1 : 0; + bool is_split = num_of_children != 0; + FacetSupportType state = FacetSupportType(next_code >> 2); + int special_side = (next_code >> 2); + + // Take care of the first iteration separately, so handling of the others is simpler. + if (parents.empty()) { + if (! is_split) { + // root is not split. just set the state and that's it. + m_triangles[triangle_id].set_state(state); + break; + } else { + // root is split, add it into list of parents and split it. + // then go to the next. + parents.push_back({triangle_id, 0, num_of_children}); + m_triangles[triangle_id].set_division(num_of_children-1, special_side); + perform_split(triangle_id, FacetSupportType::NONE); + continue; + } + } + + // This is not the first iteration. This triangle is a child of last seen parent. + assert(! parents.empty()); + assert(parents.back().processed_children < parents.back().total_children); + + if (is_split) { + // split the triangle and save it as parent of the next ones. + const ProcessingInfo& last = parents.back(); + int this_idx = m_triangles[last.facet_id].children[last.processed_children]; + m_triangles[this_idx].set_division(num_of_children-1, special_side); + perform_split(this_idx, FacetSupportType::NONE); + parents.push_back({this_idx, 0, num_of_children}); + } else { + // this triangle belongs to last split one + m_triangles[m_triangles[parents.back().facet_id].children[parents.back().processed_children]].set_state(state); + ++parents.back().processed_children; + } + + + // If all children of the past parent triangle are claimed, move to grandparent. + while (parents.back().processed_children == parents.back().total_children) { + parents.pop_back(); + + if (parents.empty()) + break; + + // And increment the grandparent children counter, because + // we have just finished that branch and got back here. + ++parents.back().processed_children; + } + + // In case we popped back the root, we should be done. + if (parents.empty()) + break; + } + + } +} + + + + +} // namespace Slic3r diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp new file mode 100644 index 000000000..943029548 --- /dev/null +++ b/src/libslic3r/TriangleSelector.hpp @@ -0,0 +1,149 @@ +#ifndef libslic3r_TriangleSelector_hpp_ +#define libslic3r_TriangleSelector_hpp_ + +#define PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + + +#include "Point.hpp" +#include "TriangleMesh.hpp" + +namespace Slic3r { + +enum class FacetSupportType : int8_t; + + + +// Following class holds information about selected triangles. It also has power +// to recursively subdivide the triangles and make the selection finer. +class TriangleSelector { +public: + void set_edge_limit(float edge_limit); + + // Create new object on a TriangleMesh. The referenced mesh must + // stay valid, a ptr to it is saved and used. + explicit TriangleSelector(const TriangleMesh& mesh); + + // Select all triangles fully inside the circle, subdivide where needed. + void select_patch(const Vec3f& hit, // point where to start + int facet_start, // facet that point belongs to + const Vec3f& source, // camera position (mesh coords) + const Vec3f& dir, // direction of the ray (mesh coords) + float radius_sqr, // squared radius of the cursor + FacetSupportType new_state); // enforcer or blocker? + + + // Clear everything and make the tree empty. + void reset(); + + // Remove all unnecessary data. + void garbage_collect(); + + // Store the division trees in compact form (a long stream of + // bits for each triangle of the original mesh). + std::map> serialize() const; + + // Load serialized data. Assumes that correct mesh is loaded. + void deserialize(const std::map> data); + + +protected: + // Triangle and info about how it's split. + class Triangle { + public: + // Use TriangleSelector::push_triangle to create a new triangle. + // It increments/decrements reference counter on vertices. + Triangle(int a, int b, int c) + : verts_idxs{a, b, c}, + state{FacetSupportType(0)}, + number_of_splits{0}, + special_side_idx{0}, + old_number_of_splits{0} + {} + // Indices into m_vertices. + std::array verts_idxs; + + // Is this triangle valid or marked to be removed? + bool valid{true}; + + // Children triangles. + std::array children; + + // Set the division type. + void set_division(int sides_to_split, int special_side_idx = -1); + + // Get/set current state. + void set_state(FacetSupportType type) { assert(! is_split()); state = type; } + FacetSupportType get_state() const { assert(! is_split()); return state; } + + // Get info on how it's split. + bool is_split() const { return number_of_split_sides() != 0; } + int number_of_split_sides() const { return number_of_splits; } + int special_side() const { assert(is_split()); return special_side_idx; } + bool was_split_before() const { return old_number_of_splits != 0; } + void forget_history() { old_number_of_splits = 0; } + + private: + int number_of_splits; + int special_side_idx; + FacetSupportType state; + + // How many children were spawned during last split? + // Is not reset on remerging the triangle. + int old_number_of_splits; + }; + + struct Vertex { + explicit Vertex(const stl_vertex& vert) + : v{vert}, + ref_cnt{0} + {} + stl_vertex v; + int ref_cnt; + }; + + // Lists of vertices and triangles, both original and new + std::vector m_vertices; + std::vector m_triangles; + const TriangleMesh* m_mesh; + + // Number of invalid triangles (to trigger garbage collection). + int m_invalid_triangles; + + // Limiting length of triangle side (squared). + float m_edge_limit_sqr = 1.f; + + // Number of original vertices and triangles. + int m_orig_size_vertices; + int m_orig_size_indices; + + // Cache for cursor position, radius and direction. + struct Cursor { + Vec3f center; + Vec3f source; + Vec3f dir; + float radius_sqr; + }; + + Cursor m_cursor; + + // Private functions: + bool select_triangle(int facet_idx, FacetSupportType type, + bool recursive_call = false); + bool is_point_inside_cursor(const Vec3f& point) const; + int vertices_inside(int facet_idx) const; + bool faces_camera(int facet) const; + void undivide_triangle(int facet_idx); + void split_triangle(int facet_idx); + void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. + bool is_pointer_in_triangle(int facet_idx) const; + bool is_edge_inside_cursor(int facet_idx) const; + void push_triangle(int a, int b, int c); + void perform_split(int facet_idx, FacetSupportType old_state); +}; + + + + +} // namespace Slic3r + +#endif // libslic3r_TriangleSelector_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 01427ec09..ca7695907 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -89,15 +89,12 @@ void GLGizmoFdmSupports::set_fdm_support_data(ModelObject* model_object, const S void GLGizmoFdmSupports::on_render() const { - //const Selection& selection = m_parent.get_selection(); + const Selection& selection = m_parent.get_selection(); glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); - //render_triangles(selection); - - if (m_triangle_selector && ! m_setting_angle) - m_triangle_selector->render(m_imgui); + render_triangles(selection); m_c->object_clipper()->render_cut(); render_cursor_circle(); @@ -148,14 +145,9 @@ void GLGizmoFdmSupports::render_triangles(const Selection& selection) const glsafe(::glPushMatrix()); glsafe(::glMultMatrixd(trafo_matrix.data())); - // Now render both enforcers and blockers. - //for (int i=0; i<2; ++i) { - // glsafe(::glColor4f(i ? 1.f : 0.2f, 0.2f, i ? 0.2f : 1.0f, 0.5f)); - // for (const GLIndexedVertexArray& iva : m_ivas[mesh_id][i]) { - if (m_iva.has_VBOs()) - m_iva.render(); - // } - //} + if (! m_setting_angle) + m_triangle_selectors[mesh_id]->render(m_imgui); + glsafe(::glPopMatrix()); if (is_left_handed) glsafe(::glFrontFace(GL_CCW)); @@ -212,16 +204,14 @@ void GLGizmoFdmSupports::render_cursor_circle() const void GLGizmoFdmSupports::update_model_object() const { - return; - /*ModelObject* mo = m_c->selection_info()->model_object(); + ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { ++idx; if (! mv->is_model_part()) continue; - for (int i=0; im_supported_facets.set_facet(i, m_selected_facets[idx][i]); - }*/ + mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); + } } @@ -230,21 +220,7 @@ void GLGizmoFdmSupports::update_from_model_object() wxBusyCursor wait; const ModelObject* mo = m_c->selection_info()->model_object(); - /*size_t num_of_volumes = 0; - for (const ModelVolume* mv : mo->volumes) - if (mv->is_model_part()) - ++num_of_volumes; - m_selected_facets.resize(num_of_volumes);*/ - - m_triangle_selector = std::make_unique(mo->volumes.front()->mesh()); - - /*m_ivas.clear(); - m_ivas.resize(num_of_volumes); - for (size_t i=0; ivolumes) { @@ -256,17 +232,9 @@ void GLGizmoFdmSupports::update_from_model_object() // This mesh does not account for the possible Z up SLA offset. const TriangleMesh* mesh = &mv->mesh(); - m_selected_facets[volume_id].assign(mesh->its.indices.size(), FacetSupportType::NONE); - - // Load current state from ModelVolume. - for (FacetSupportType type : {FacetSupportType::ENFORCER, FacetSupportType::BLOCKER}) { - const std::vector& list = mv->m_supported_facets.get_facets(type); - for (int i : list) - m_selected_facets[volume_id][i] = type; - } - update_vertex_buffers(mesh, volume_id, FacetSupportType::ENFORCER); - update_vertex_buffers(mesh, volume_id, FacetSupportType::BLOCKER); - }*/ + m_triangle_selectors.emplace_back(std::make_unique(*mesh)); + m_triangle_selectors.back()->deserialize(mv->m_supported_facets.get_data()); + } } @@ -321,7 +289,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous || action == SLAGizmoEventType::RightDown || (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) { - if (! m_triangle_selector) + if (m_triangle_selectors.empty()) return false; FacetSupportType new_state = FacetSupportType::NONE; @@ -426,23 +394,20 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } } - // FIXME: just for now, only process first mesh - if (mesh_id != 0) - return false; - const Transform3d& trafo_matrix = trafo_matrices[mesh_id]; // Calculate how far can a point be from the line (in mesh coords). // FIXME: The scaling of the mesh can be non-uniform. const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor(); const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.; - const float limit = pow(m_cursor_radius/avg_scaling , 2.f); + const float limit = std::pow(m_cursor_radius/avg_scaling , 2.f); // Calculate direction from camera to the hit (in mesh coords): Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); Vec3f dir = (closest_hit - camera_pos).normalized(); - m_triangle_selector->select_patch(closest_hit, closest_facet, camera_pos, + assert(mesh_id < int(m_triangle_selectors.size())); + m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos, dir, limit, new_state); return true; @@ -468,7 +433,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } -void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, +/*void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, int mesh_id, FacetSupportType type, const std::vector* new_facets) @@ -506,7 +471,7 @@ void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, if (pushed) m_iva.finalize_geometry(true); - /*} else { + } else { // we are only appending - let's make new vertex array and let the old ones live ivas.push_back(GLIndexedVertexArray()); for (size_t facet_idx : *new_facets) @@ -516,9 +481,9 @@ void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, ivas.back().finalize_geometry(true); else ivas.pop_back(); - }*/ + } -} +}*/ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwrite, bool block) @@ -761,8 +726,8 @@ void GLGizmoFdmSupports::on_set_state() } activate_internal_undo_redo_stack(false); m_old_mo_id = -1; - m_iva.release_geometry(); - m_selected_facets.clear(); + //m_iva.release_geometry(); + m_triangle_selectors.clear(); } m_old_state = m_state; } @@ -800,422 +765,14 @@ void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const } -// sides_to_split==-1 : just restore previous split -void TriangleSelector::Triangle::set_division(int sides_to_split, int special_side_idx) +void TriangleSelectorGUI::render(ImGuiWrapper* imgui) { - assert(sides_to_split >=-1 && sides_to_split <= 3); - assert(special_side_idx >=-1 && special_side_idx < 3); - - // If splitting one or two sides, second argument must be provided. - assert(sides_to_split != 1 || special_side_idx != -1); - assert(sides_to_split != 2 || special_side_idx != -1); - - if (sides_to_split != -1) { - this->number_of_splits = sides_to_split; - if (sides_to_split != 0) { - assert(old_number_of_splits == 0); - this->special_side_idx = special_side_idx; - this->old_number_of_splits = sides_to_split; - } - } - else { - assert(old_number_of_splits != 0); - this->number_of_splits = old_number_of_splits; - // indices of children should still be there. - } -} - - - -void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, - const Vec3f& source, const Vec3f& dir, - float radius_sqr, FacetSupportType new_state) -{ - assert(facet_start < m_orig_size_indices); - assert(is_approx(dir.norm(), 1.f)); - - // Save current cursor center, squared radius and camera direction, - // so we don't have to pass it around. - m_cursor = {hit, source, dir, radius_sqr}; - - // Now start with the facet the pointer points to and check all adjacent facets. - std::vector facets_to_check{facet_start}; - std::vector visited(m_orig_size_indices, false); // keep track of facets we already processed - int facet_idx = 0; // index into facets_to_check - while (facet_idx < int(facets_to_check.size())) { - int facet = facets_to_check[facet_idx]; - if (! visited[facet]) { - if (select_triangle(facet, new_state)) { - // add neighboring facets to list to be proccessed later - for (int n=0; n<3; ++n) { - if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) - facets_to_check.push_back(m_mesh->stl.neighbors_start[facet].neighbor[n]); - } - } - } - visited[facet] = true; - ++facet_idx; - } -} - - - -// Selects either the whole triangle (discarding any children it had), or divides -// the triangle recursively, selecting just subtriangles truly inside the circle. -// This is done by an actual recursive call. Returns false if the triangle is -// outside the cursor. -bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool recursive_call) -{ - assert(facet_idx < int(m_triangles.size())); - - Triangle* tr = &m_triangles[facet_idx]; - if (! tr->valid) - return false; - - int num_of_inside_vertices = vertices_inside(facet_idx); - - if (num_of_inside_vertices == 0 - && ! is_pointer_in_triangle(facet_idx) - && ! is_edge_inside_cursor(facet_idx)) - return false; - - if (num_of_inside_vertices == 3) { - // dump any subdivision and select whole triangle - undivide_triangle(facet_idx); - tr->set_state(type); - } else { - // the triangle is partially inside, let's recursively divide it - // (if not already) and try selecting its children. - - if (! tr->is_split() && tr->get_state() == type) { - // This is leaf triangle that is already of correct type as a whole. - // No need to split, all children would end up selected anyway. - return true; - } - - split_triangle(facet_idx); - tr = &m_triangles[facet_idx]; // might have been invalidated - - - int num_of_children = tr->number_of_split_sides() + 1; - if (num_of_children != 1) { - for (int i=0; ichildren.size())); - assert(tr->children[i] < int(m_triangles.size())); - - select_triangle(tr->children[i], type, true); - tr = &m_triangles[facet_idx]; // might have been invalidated - } - } - } - - if (! recursive_call) { - // In case that all children are leafs and have the same state now, - // they may be removed and substituted by the parent triangle. - remove_useless_children(facet_idx); - - // Make sure that we did not lose track of invalid triangles. - assert(m_invalid_triangles == std::count_if(m_triangles.begin(), m_triangles.end(), - [](const Triangle& tr) { return ! tr.valid; })); - - // Do garbage collection maybe? - if (2*m_invalid_triangles > int(m_triangles.size())) - garbage_collect(); - } - return true; -} - - -void TriangleSelector::split_triangle(int facet_idx) -{ - if (m_triangles[facet_idx].is_split()) { - // The triangle is divided already. - return; - } - - Triangle* tr = &m_triangles[facet_idx]; - - FacetSupportType old_type = tr->get_state(); - - if (tr->was_split_before() != 0) { - // This triangle is not split at the moment, but was at one point - // in history. We can just restore it and resurrect its children. - tr->set_division(-1); - for (int i=0; i<=tr->number_of_split_sides(); ++i) { - m_triangles[tr->children[i]].set_state(old_type); - m_triangles[tr->children[i]].valid = true; - --m_invalid_triangles; - } - return; - } - - // If we got here, we are about to actually split the triangle. - const double limit_squared = m_edge_limit_sqr; - - std::array& facet = tr->verts_idxs; - const stl_vertex* pts[3] = { &m_vertices[facet[0]].v, &m_vertices[facet[1]].v, &m_vertices[facet[2]].v}; - double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), - (*pts[0]-*pts[2]).squaredNorm(), - (*pts[1]-*pts[0]).squaredNorm() }; - - std::vector sides_to_split; - int side_to_keep = -1; - for (int pt_idx = 0; pt_idx<3; ++pt_idx) { - if (sides[pt_idx] > limit_squared) - sides_to_split.push_back(pt_idx); - else - side_to_keep = pt_idx; - } - if (sides_to_split.empty()) { - // This shall be unselected. - tr->set_division(0); - return; - } - - // Save how the triangle will be split. Second argument makes sense only for one - // or two split sides, otherwise the value is ignored. - tr->set_division(sides_to_split.size(), - sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]); - - perform_split(facet_idx, old_type); -} - - -// Calculate distance of a point from a line. -bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const -{ - Vec3f diff = m_cursor.center - point; - return (diff - diff.dot(m_cursor.dir) * m_cursor.dir).squaredNorm() < m_cursor.radius_sqr; -} - - -// Is pointer in a triangle? -bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const -{ - auto signed_volume_sign = [](const Vec3f& a, const Vec3f& b, - const Vec3f& c, const Vec3f& d) -> bool { - return ((b-a).cross(c-a)).dot(d-a) > 0.; - }; - - const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]].v; - const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]].v; - const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]].v; - const Vec3f& q1 = m_cursor.center + m_cursor.dir; - const Vec3f q2 = m_cursor.center - m_cursor.dir; - - if (signed_volume_sign(q1,p1,p2,p3) != signed_volume_sign(q2,p1,p2,p3)) { - bool pos = signed_volume_sign(q1,q2,p1,p2); - if (signed_volume_sign(q1,q2,p2,p3) == pos && signed_volume_sign(q1,q2,p3,p1) == pos) - return true; - } - return false; -} - - - -// Determine whether this facet is potentially visible (still can be obscured). -bool TriangleSelector::faces_camera(int facet) const -{ - assert(facet < m_orig_size_indices); - // The normal is cached in mesh->stl, use it. - return (m_mesh->stl.facet_start[facet].normal.dot(m_cursor.dir) < 0.); -} - - -// How many vertices of a triangle are inside the circle? -int TriangleSelector::vertices_inside(int facet_idx) const -{ - int inside = 0; - for (size_t i=0; i<3; ++i) { - if (is_point_inside_cursor(m_vertices[m_triangles[facet_idx].verts_idxs[i]].v)) - ++inside; - } - return inside; -} - - -// Is edge inside cursor? -bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const -{ - Vec3f pts[3]; - for (int i=0; i<3; ++i) - pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]].v; - - const Vec3f& p = m_cursor.center; - - for (int side = 0; side < 3; ++side) { - const Vec3f& a = pts[side]; - const Vec3f& b = pts[side<2 ? side+1 : 0]; - Vec3f s = (b-a).normalized(); - float t = (p-a).dot(s); - Vec3f vector = a+t*s - p; - - // vector is 3D vector from center to the intersection. What we want to - // measure is length of its projection onto plane perpendicular to dir. - float dist_sqr = vector.squaredNorm() - std::pow(vector.dot(m_cursor.dir), 2.f); - if (dist_sqr < m_cursor.radius_sqr && t>=0.f && t<=(b-a).norm()) - return true; - } - return false; -} - - - -// Recursively remove all subtriangles. -void TriangleSelector::undivide_triangle(int facet_idx) -{ - assert(facet_idx < int(m_triangles.size())); - Triangle& tr = m_triangles[facet_idx]; - - if (tr.is_split()) { - for (int i=0; i<=tr.number_of_split_sides(); ++i) { - undivide_triangle(tr.children[i]); - m_triangles[tr.children[i]].valid = false; - ++m_invalid_triangles; - } - tr.set_division(0); // not split - } -} - - -void TriangleSelector::remove_useless_children(int facet_idx) -{ - // Check that all children are leafs of the same type. If not, try to - // make them (recursive call). Remove them if sucessful. - - assert(facet_idx < int(m_triangles.size()) && m_triangles[facet_idx].valid); - Triangle& tr = m_triangles[facet_idx]; - - if (! tr.is_split()) { - // This is a leaf, there nothing to do. This can happen during the - // first (non-recursive call). Shouldn't otherwise. - return; - } - - // Call this for all non-leaf children. - for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { - assert(child_idx < int(m_triangles.size()) && m_triangles[child_idx].valid); - if (m_triangles[tr.children[child_idx]].is_split()) - remove_useless_children(tr.children[child_idx]); - } - - - // Return if a child is not leaf or two children differ in type. - FacetSupportType first_child_type = FacetSupportType::NONE; - for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { - if (m_triangles[tr.children[child_idx]].is_split()) - return; - if (child_idx == 0) - first_child_type = m_triangles[tr.children[0]].get_state(); - else if (m_triangles[tr.children[child_idx]].get_state() != first_child_type) - return; - } - - // If we got here, the children can be removed. - undivide_triangle(facet_idx); - tr.set_state(first_child_type); -} - - - -void TriangleSelector::garbage_collect() -{ - // First make a map from old to new triangle indices. - int new_idx = m_orig_size_indices; - std::vector new_triangle_indices(m_triangles.size(), -1); - for (int i = m_orig_size_indices; i new_vertices_indices(m_vertices.size(), -1); - for (int i=m_orig_size_vertices; i= 0); - if (m_vertices[i].ref_cnt != 0) { - new_vertices_indices[i] = new_idx; - ++new_idx; - } - } - - // We can remove all invalid triangles and vertices that are no longer referenced. - m_triangles.erase(std::remove_if(m_triangles.begin()+m_orig_size_indices, m_triangles.end(), - [](const Triangle& tr) { return ! tr.valid; }), - m_triangles.end()); - m_vertices.erase(std::remove_if(m_vertices.begin()+m_orig_size_vertices, m_vertices.end(), - [](const Vertex& vert) { return vert.ref_cnt == 0; }), - m_vertices.end()); - - // Now go through all remaining triangles and update changed indices. - for (Triangle& tr : m_triangles) { - assert(tr.valid); - - if (tr.is_split()) { - // There are children. Update their indices. - for (int j=0; j<=tr.number_of_split_sides(); ++j) { - assert(new_triangle_indices[tr.children[j]] != -1); - tr.children[j] = new_triangle_indices[tr.children[j]]; - } - } - - // Update indices into m_vertices. The original vertices are never - // touched and need not be reindexed. - for (int& idx : tr.verts_idxs) { - if (idx >= m_orig_size_vertices) { - assert(new_vertices_indices[idx] != -1); - idx = new_vertices_indices[idx]; - } - } - - // If this triangle was split before, forget it. - // Children referenced in the cache are dead by now. - tr.forget_history(); - } - - m_invalid_triangles = 0; -} - -TriangleSelector::TriangleSelector(const TriangleMesh& mesh) - : m_mesh{&mesh} -{ - reset(); - -} - - -void TriangleSelector::reset() -{ - if (! m_orig_size_indices != 0) // unless this is run from constructor - garbage_collect(); - m_vertices.clear(); - m_triangles.clear(); - for (const stl_vertex& vert : m_mesh->its.vertices) - m_vertices.emplace_back(vert); - for (const stl_triangle_vertex_indices& ind : m_mesh->its.indices) - push_triangle(ind[0], ind[1], ind[2]); - m_orig_size_vertices = m_vertices.size(); - m_orig_size_indices = m_triangles.size(); - m_invalid_triangles = 0; -} - -void TriangleSelector::render(ImGuiWrapper* imgui) -{ - Vec3d offset = wxGetApp().model().objects.front()->instances.front()->get_transformation().get_offset(); - ::glTranslatef(offset.x(), offset.y(), offset.z()); - ::glScalef(1.005f, 1.005f, 1.005f); - - int enf_cnt = 0; int blc_cnt = 0; + m_iva_enforcers.release_geometry(); + m_iva_blockers.release_geometry(); + for (const Triangle& tr : m_triangles) { if (! tr.valid || tr.is_split() || tr.get_state() == FacetSupportType::NONE) continue; @@ -1245,13 +802,13 @@ void TriangleSelector::render(ImGuiWrapper* imgui) ::glColor4f(0.f, 0.f, 1.f, 0.2f); m_iva_enforcers.render(); } - m_iva_enforcers.release_geometry(); + if (m_iva_blockers.has_VBOs()) { ::glColor4f(1.f, 0.f, 0.f, 0.2f); m_iva_blockers.render(); } - m_iva_blockers.release_geometry(); + #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG if (imgui) @@ -1262,242 +819,9 @@ void TriangleSelector::render(ImGuiWrapper* imgui) } -void TriangleSelector::set_edge_limit(float edge_limit) -{ - float new_limit_sqr = std::pow(edge_limit, 2.f); - - if (new_limit_sqr != m_edge_limit_sqr) { - m_edge_limit_sqr = new_limit_sqr; - - // The way how triangles split may be different now, forget - // all cached splits. - garbage_collect(); - } -} - - - -void TriangleSelector::push_triangle(int a, int b, int c) -{ - for (int i : {a, b, c}) { - assert(i >= 0 && i < int(m_vertices.size())); - ++m_vertices[i].ref_cnt; - } - m_triangles.emplace_back(a, b, c); -} - - -void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) -{ - Triangle* tr = &m_triangles[facet_idx]; - - assert(tr->is_split()); - - // Read info about how to split this triangle. - int sides_to_split = tr->number_of_split_sides(); - - // indices of triangle vertices - std::vector verts_idxs; - int idx = tr->special_side(); - for (int j=0; j<3; ++j) { - verts_idxs.push_back(tr->verts_idxs[idx++]); - if (idx == 3) - idx = 0; - } - - if (sides_to_split == 1) { - m_vertices.emplace_back((m_vertices[verts_idxs[1]].v + m_vertices[verts_idxs[2]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1); - - push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[2]); - push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[0]); - } - - if (sides_to_split == 2) { - m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); - - m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[3]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1); - - push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[4]); - push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[4]); - push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[4]); - } - - if (sides_to_split == 3) { - m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[2]].v + m_vertices[verts_idxs[3]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[4]].v + m_vertices[verts_idxs[0]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1); - - push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[5]); - push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[3]); - push_triangle(verts_idxs[3], verts_idxs[4], verts_idxs[5]); - push_triangle(verts_idxs[1], verts_idxs[3], verts_idxs[5]); - } - - tr = &m_triangles[facet_idx]; // may have been invalidated - - // And save the children. All children should start in the same state as the triangle we just split. - assert(sides_to_split <= 3); - for (int i=0; i<=sides_to_split; ++i) { - tr->children[i] = m_triangles.size()-1-i; - m_triangles[tr->children[i]].set_state(old_state); - } -} - - -std::map> TriangleSelector::serialize() const -{ - // Each original triangle of the mesh is assigned a number encoding its state - // or how it is split. Each triangle is encoded by 4 bits (xxyy): - // leaf triangle: xx = FacetSupportType, yy = 0 - // non-leaf: xx = special side, yy = number of split sides - // These are bitwise appended and formed into one 64-bit integer. - - // The function returns a map from original triangle indices to - // stream of bits encoding state and offsprings. - - std::map> out; - for (int i=0; i data; // complete encoding of this mesh triangle - int stored_triangles = 0; // how many have been already encoded - - std::function serialize_recursive; - serialize_recursive = [this, &serialize_recursive, &stored_triangles, &data](int facet_idx) { - const Triangle& tr = m_triangles[facet_idx]; - - // Always save number of split sides. It is zero for unsplit triangles. - int split_sides = tr.number_of_split_sides(); - assert(split_sides >= 0 && split_sides <= 3); - - //data |= (split_sides << (stored_triangles * 4)); - data.push_back(split_sides & 0b01); - data.push_back(split_sides & 0b10); - - if (tr.is_split()) { - // If this triangle is split, save which side is split (in case - // of one split) or kept (in case of two splits). The value will - // be ignored for 3-side split. - assert(split_sides > 0); - assert(tr.special_side() >= 0 && tr.special_side() <= 3); - data.push_back(tr.special_side() & 0b01); - data.push_back(tr.special_side() & 0b10); - ++stored_triangles; - // Now save all children. - for (int child_idx=0; child_idx<=split_sides; ++child_idx) - serialize_recursive(tr.children[child_idx]); - } else { - // In case this is leaf, we better save information about its state. - assert(int(tr.get_state()) <= 3); - data.push_back(int(tr.get_state()) & 0b01); - data.push_back(int(tr.get_state()) & 0b10); - ++stored_triangles; - } - }; - - serialize_recursive(i); - out[i] = data; - } - - return out; -} - -void TriangleSelector::deserialize(const std::map> data) -{ - reset(); // dump any current state - for (const auto& [triangle_id, code] : data) { - assert(triangle_id < int(m_triangles.size())); - int processed_triangles = 0; - struct ProcessingInfo { - int facet_id = 0; - int processed_children = 0; - int total_children = 0; - }; - - // Vector to store all parents that have offsprings. - std::vector parents; - - while (true) { - // Read next triangle info. - int next_code = 0; - for (int i=3; i>=0; --i) { - next_code = next_code << 1; - next_code |= int(code[4 * processed_triangles + i]); - } - ++processed_triangles; - - int num_of_split_sides = (next_code & 0b11); - int num_of_children = num_of_split_sides != 0 ? num_of_split_sides + 1 : 0; - bool is_split = num_of_children != 0; - FacetSupportType state = FacetSupportType(next_code >> 2); - int special_side = (next_code >> 2); - - // Take care of the first iteration separately, so handling of the others is simpler. - if (parents.empty()) { - if (! is_split) { - // root is not split. just set the state and that's it. - m_triangles[triangle_id].set_state(state); - break; - } else { - // root is split, add it into list of parents and split it. - // then go to the next. - parents.push_back({triangle_id, 0, num_of_children}); - m_triangles[triangle_id].set_division(num_of_children-1, special_side); - perform_split(triangle_id, FacetSupportType::NONE); - continue; - } - } - - // This is not the first iteration. This triangle is a child of last seen parent. - assert(! parents.empty()); - assert(parents.back().processed_children < parents.back().total_children); - - if (is_split) { - // split the triangle and save it as parent of the next ones. - const ProcessingInfo& last = parents.back(); - int this_idx = m_triangles[last.facet_id].children[last.processed_children]; - m_triangles[this_idx].set_division(num_of_children-1, special_side); - perform_split(this_idx, FacetSupportType::NONE); - parents.push_back({this_idx, 0, num_of_children}); - } else { - // this triangle belongs to last split one - m_triangles[m_triangles[parents.back().facet_id].children[parents.back().processed_children]].set_state(state); - ++parents.back().processed_children; - } - - - // If all children of the past parent triangle are claimed, move to grandparent. - while (parents.back().processed_children == parents.back().total_children) { - parents.pop_back(); - - if (parents.empty()) - break; - - // And increment the grandparent children counter, because - // we have just finished that branch and got back here. - ++parents.back().processed_children; - } - - // In case we popped back the root, we should be done. - if (parents.empty()) - break; - } - - } -} - #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG -void TriangleSelector::render_debug(ImGuiWrapper* imgui) +void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui) { imgui->begin(std::string("TriangleSelector dialog (DEV ONLY)"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); @@ -1585,5 +909,7 @@ void TriangleSelector::render_debug(ImGuiWrapper* imgui) } #endif + + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 099c9a30c..7020bb7d4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -6,11 +6,11 @@ #include "slic3r/GUI/3DScene.hpp" #include "libslic3r/ObjectID.hpp" +#include "libslic3r/TriangleSelector.hpp" #include -#define PRUSASLICER_TRIANGLE_SELECTOR_DEBUG namespace Slic3r { @@ -23,144 +23,26 @@ enum class SLAGizmoEventType : unsigned char; class ClippingPlane; -// Following class holds information about selected triangles. It also has power -// to recursively subdivide the triangles and make the selection finer. -class TriangleSelector { + +class TriangleSelectorGUI : public TriangleSelector { public: - void set_edge_limit(float edge_limit); - - // Create new object on a TriangleMesh. The referenced mesh must - // stay valid, a ptr to it is saved and used. - explicit TriangleSelector(const TriangleMesh& mesh); - - // Select all triangles fully inside the circle, subdivide where needed. - void select_patch(const Vec3f& hit, // point where to start - int facet_start, // facet that point belongs to - const Vec3f& source, // camera position (mesh coords) - const Vec3f& dir, // direction of the ray (mesh coords) - float radius_sqr, // squared radius of the cursor - FacetSupportType new_state); // enforcer or blocker? + explicit TriangleSelectorGUI(const TriangleMesh& mesh) + : TriangleSelector(mesh) {} // Render current selection. Transformation matrices are supposed // to be already set. void render(ImGuiWrapper* imgui = nullptr); - // Clear everything and make the tree empty. - void reset(); - - // Remove all unnecessary data. - void garbage_collect(); - - // Store the division trees in compact form (a long stream of - // bits for each triangle of the original mesh). - std::map> serialize() const; - - // Load serialized data. Assumes that correct mesh is loaded. - void deserialize(const std::map> data); - #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void render_debug(ImGuiWrapper* imgui); - bool m_show_triangles{true}; + bool m_show_triangles{false}; bool m_show_invalid{false}; #endif private: - // Triangle and info about how it's split. - class Triangle { - public: - // Use TriangleSelector::push_triangle to create a new triangle. - // It increments/decrements reference counter on vertices. - Triangle(int a, int b, int c) - : verts_idxs{a, b, c}, - state{FacetSupportType(0)}, - number_of_splits{0}, - special_side_idx{0}, - old_number_of_splits{0} - {} - // Indices into m_vertices. - std::array verts_idxs; - - // Is this triangle valid or marked to be removed? - bool valid{true}; - - // Children triangles. - std::array children; - - // Set the division type. - void set_division(int sides_to_split, int special_side_idx = -1); - - // Get/set current state. - void set_state(FacetSupportType type) { assert(! is_split()); state = type; } - FacetSupportType get_state() const { assert(! is_split()); return state; } - - // Get info on how it's split. - bool is_split() const { return number_of_split_sides() != 0; } - int number_of_split_sides() const { return number_of_splits; } - int special_side() const { assert(is_split()); return special_side_idx; } - bool was_split_before() const { return old_number_of_splits != 0; } - void forget_history() { old_number_of_splits = 0; } - - private: - int number_of_splits; - int special_side_idx; - FacetSupportType state; - - // How many children were spawned during last split? - // Is not reset on remerging the triangle. - int old_number_of_splits; - }; - - struct Vertex { - explicit Vertex(const stl_vertex& vert) - : v{vert}, - ref_cnt{0} - {} - stl_vertex v; - int ref_cnt; - }; - - // Lists of vertices and triangles, both original and new - std::vector m_vertices; - std::vector m_triangles; - const TriangleMesh* m_mesh; - GLIndexedVertexArray m_iva_enforcers; GLIndexedVertexArray m_iva_blockers; std::array m_varrays; - - // Number of invalid triangles (to trigger garbage collection). - int m_invalid_triangles; - - // Limiting length of triangle side (squared). - float m_edge_limit_sqr = 1.f; - - // Number of original vertices and triangles. - int m_orig_size_vertices; - int m_orig_size_indices; - - // Cache for cursor position, radius and direction. - struct Cursor { - Vec3f center; - Vec3f source; - Vec3f dir; - float radius_sqr; - }; - - Cursor m_cursor; - - // Private functions: - bool select_triangle(int facet_idx, FacetSupportType type, - bool recursive_call = false); - bool is_point_inside_cursor(const Vec3f& point) const; - int vertices_inside(int facet_idx) const; - bool faces_camera(int facet) const; - void undivide_triangle(int facet_idx); - void split_triangle(int facet_idx); - void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. - bool is_pointer_in_triangle(int facet_idx) const; - bool is_edge_inside_cursor(int facet_idx) const; - void push_triangle(int a, int b, int c); - void perform_split(int facet_idx, FacetSupportType old_state); }; @@ -178,17 +60,8 @@ private: static constexpr float CursorRadiusMax = 8.f; static constexpr float CursorRadiusStep = 0.2f; - // For each model-part volume, store a list of statuses of - // individual facets (one of the enum values above). - std::vector> m_selected_facets; - - GLIndexedVertexArray m_iva; - - void update_vertex_buffers(const TriangleMesh* mesh, - int mesh_id, - FacetSupportType type, // enforcers / blockers - const std::vector* new_facets = nullptr); // nullptr -> regenerate all - + // For each model-part volume, store status and division of the triangles. + std::vector> m_triangle_selectors; public: GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); @@ -220,8 +93,6 @@ private: bool m_setting_angle = false; bool m_internal_stack_active = false; bool m_schedule_update = false; - - std::unique_ptr m_triangle_selector; // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. From 0756a7e4b3b8776c5885c6589a376e0fd191335b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 14 Jul 2020 14:02:39 +0200 Subject: [PATCH 31/70] TriangleSelector: 'Select by angle' and 'reset selection' functions fixed --- src/libslic3r/TriangleSelector.cpp | 9 ++ src/libslic3r/TriangleSelector.hpp | 3 + src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 94 ++++---------------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 3 +- 4 files changed, 31 insertions(+), 78 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 340f5a29a..9210bde08 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -132,6 +132,15 @@ bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, boo } + +void TriangleSelector::set_facet(int facet_idx, FacetSupportType state) +{ + assert(facet_idx < m_orig_size_indices); + undivide_triangle(facet_idx); + assert(! m_triangles[facet_idx].is_split()); + m_triangles[facet_idx].set_state(state); +} + void TriangleSelector::split_triangle(int facet_idx) { if (m_triangles[facet_idx].is_split()) { diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 943029548..f3e23bea2 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -32,6 +32,9 @@ public: FacetSupportType new_state); // enforcer or blocker? + // Set facet of the mesh to a given state. Only works for original triangles. + void set_facet(int facet_idx, FacetSupportType state); + // Clear everything and make the tree empty. void reset(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index ca7695907..417d101ea 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -48,7 +48,7 @@ bool GLGizmoFdmSupports::on_init() m_desc["block"] = _L("Block supports"); m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; m_desc["remove"] = _L("Remove selection"); - m_desc["remove_all"] = _L("Remove all"); + m_desc["remove_all"] = _L("Remove all selection"); return true; } @@ -207,9 +207,9 @@ void GLGizmoFdmSupports::update_model_object() const ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { - ++idx; if (! mv->is_model_part()) continue; + ++idx; mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); } } @@ -433,63 +433,9 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } -/*void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, - int mesh_id, - FacetSupportType type, - const std::vector* new_facets) + +void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) { - //std::vector& ivas = m_ivas[mesh_id][type == FacetSupportType::ENFORCER ? 0 : 1]; - - // lambda to push facet into vertex buffer - auto push_facet = [this, &mesh, &mesh_id](size_t idx, GLIndexedVertexArray& iva) { - for (int i=0; i<3; ++i) - iva.push_geometry( - mesh->its.vertices[mesh->its.indices[idx](i)].cast(), - m_c->raycaster()->raycasters()[mesh_id]->get_triangle_normal(idx).cast() - ); - size_t num = iva.triangle_indices_size; - iva.push_triangle(num, num+1, num+2); - }; - - - //if (ivas.size() == MaxVertexBuffers || ! new_facets) { - // If there are too many or they should be regenerated, make one large - // GLVertexBufferArray. - //ivas.clear(); // destructors release geometry - //ivas.push_back(GLIndexedVertexArray()); - - m_iva.release_geometry(); - m_iva.clear(); - - bool pushed = false; - for (size_t facet_idx=0; facet_idxempty()) - ivas.back().finalize_geometry(true); - else - ivas.pop_back(); - } - -}*/ - - -void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwrite, bool block) -{ - return; -/* float threshold = (M_PI/180.)*threshold_deg; const Selection& selection = m_parent.get_selection(); const ModelObject* mo = m_c->selection_info()->model_object(); @@ -512,13 +458,12 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwr int idx = -1; for (const stl_facet& facet : mv->mesh().stl.facet_start) { ++idx; - if (facet.normal.dot(down) > dot_limit && (overwrite || m_selected_facets[mesh_id][idx] == FacetSupportType::NONE)) - m_selected_facets[mesh_id][idx] = block - ? FacetSupportType::BLOCKER - : FacetSupportType::ENFORCER; + if (facet.normal.dot(down) > dot_limit) + m_triangle_selectors[mesh_id]->set_facet(idx, + block + ? FacetSupportType::BLOCKER + : FacetSupportType::ENFORCER); } - update_vertex_buffers(&mv->mesh(), mesh_id, FacetSupportType::ENFORCER); - update_vertex_buffers(&mv->mesh(), mesh_id, FacetSupportType::BLOCKER); } activate_internal_undo_redo_stack(true); @@ -528,7 +473,6 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwr update_model_object(); m_parent.set_as_dirty(); m_setting_angle = false; -*/ } @@ -584,18 +528,17 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SameLine(); if (m_imgui->button(m_desc.at("remove_all"))) { - /*ModelObject* mo = m_c->selection_info()->model_object(); + Plater::TakeSnapshot(wxGetApp().plater(), wxString(_L("Reset selection"))); + ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { - ++idx; if (mv->is_model_part()) { - m_selected_facets[idx].assign(m_selected_facets[idx].size(), FacetSupportType::NONE); - mv->m_supported_facets.clear(); - update_vertex_buffers(&mv->mesh(), idx, FacetSupportType::ENFORCER); - update_vertex_buffers(&mv->mesh(), idx, FacetSupportType::BLOCKER); - m_parent.set_as_dirty(); + ++idx; + m_triangle_selectors[idx]->reset(); } - }*/ + } + update_model_object(); + m_parent.set_as_dirty(); } const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; @@ -651,12 +594,11 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SameLine(); if (m_imgui->slider_float("", &m_angle_threshold_deg, 0.f, 90.f, "%.f")) m_parent.set_slope_range({90.f - m_angle_threshold_deg, 90.f - m_angle_threshold_deg}); - m_imgui->checkbox(wxString("Overwrite already selected facets"), m_overwrite_selected); if (m_imgui->button("Enforce")) - select_facets_by_angle(m_angle_threshold_deg, m_overwrite_selected, false); + select_facets_by_angle(m_angle_threshold_deg, false); ImGui::SameLine(); if (m_imgui->button("Block")) - select_facets_by_angle(m_angle_threshold_deg, m_overwrite_selected, true); + select_facets_by_angle(m_angle_threshold_deg, true); ImGui::SameLine(); if (m_imgui->button("Cancel")) m_setting_angle = false; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 7020bb7d4..2d1442164 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -82,8 +82,7 @@ private: void update_from_model_object(); void activate_internal_undo_redo_stack(bool activate); - void select_facets_by_angle(float threshold, bool overwrite, bool block); - bool m_overwrite_selected = false; + void select_facets_by_angle(float threshold, bool block); float m_angle_threshold_deg = 45.f; bool is_mesh_point_clipped(const Vec3d& point) const; From 3b91d11ddfcc362552a62e8fe03c11b1115db3fd Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 15 Jul 2020 10:28:20 +0200 Subject: [PATCH 32/70] TriangleSelector: backend is aware of divided triangles --- src/libslic3r/Model.cpp | 13 ++++++------- src/libslic3r/Model.hpp | 2 +- src/libslic3r/PrintObject.cpp | 10 +++++----- src/libslic3r/TriangleSelector.cpp | 19 +++++++++++++++++++ src/libslic3r/TriangleSelector.hpp | 2 ++ 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index b6bae489b..d0410c2d4 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1831,13 +1831,12 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const } -std::vector FacetsAnnotation::get_facets(FacetSupportType type) const +indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, FacetSupportType type) const { - std::vector out; - /*for (auto& [facet_idx, this_type] : m_data) - if (this_type == type) - out.push_back(facet_idx); - */return out; + TriangleSelector selector(mv.mesh()); + selector.deserialize(m_data); + indexed_triangle_set out = selector.get_facets(type); + return out; } @@ -1932,7 +1931,7 @@ bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject return true; } return false; -}; +} extern bool model_has_multi_part_objects(const Model &model) { diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index de20e0fdc..3127af5ba 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -407,7 +407,7 @@ public: const std::map>& get_data() const { return m_data; } void set(const TriangleSelector& selector); - std::vector get_facets(FacetSupportType type) const; + indexed_triangle_set get_facets(const ModelVolume& mv, FacetSupportType type) const; void clear(); ClockType::time_point get_timestamp() const { return timestamp; } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index d2bdb6d53..1a2edcf6e 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2673,8 +2673,8 @@ void PrintObject::project_and_append_custom_supports( FacetSupportType type, std::vector& expolys) const { for (const ModelVolume* mv : this->model_object()->volumes) { - const std::vector custom_facets = mv->m_supported_facets.get_facets(type); - if (custom_facets.empty()) + const indexed_triangle_set custom_facets = mv->m_supported_facets.get_facets(*mv, type); + if (custom_facets.indices.empty()) continue; const TriangleMesh& mesh = mv->mesh(); @@ -2705,11 +2705,11 @@ void PrintObject::project_and_append_custom_supports( }; // Vector to collect resulting projections from each triangle. - std::vector projections_of_triangles(custom_facets.size()); + std::vector projections_of_triangles(custom_facets.indices.size()); // Iterate over all triangles. tbb::parallel_for( - tbb::blocked_range(0, custom_facets.size()), + tbb::blocked_range(0, custom_facets.indices.size()), [&](const tbb::blocked_range& range) { for (size_t idx = range.begin(); idx < range.end(); ++ idx) { @@ -2717,7 +2717,7 @@ void PrintObject::project_and_append_custom_supports( // Transform the triangle into worlds coords. for (int i=0; i<3; ++i) - facet[i] = tr * mesh.its.vertices[mesh.its.indices[custom_facets[idx]](i)]; + facet[i] = tr * custom_facets.vertices[custom_facets.indices[idx](i)]; // Ignore triangles with upward-pointing normal. if ((facet[1]-facet[0]).cross(facet[2]-facet[0]).z() > 0.) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 9210bde08..6e3f9f518 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -512,6 +512,25 @@ void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) } + +indexed_triangle_set TriangleSelector::get_facets(FacetSupportType state) const +{ + indexed_triangle_set out; + for (const Triangle& tr : m_triangles) { + if (tr.valid && ! tr.is_split() && tr.get_state() == state) { + stl_triangle_vertex_indices indices; + for (int i=0; i<3; ++i) { + out.vertices.emplace_back(m_vertices[tr.verts_idxs[i]].v); + indices[i] = out.vertices.size() - 1; + } + out.indices.emplace_back(indices); + } + } + return out; +} + + + std::map> TriangleSelector::serialize() const { // Each original triangle of the mesh is assigned a number encoding its state diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index f3e23bea2..dc2ad9127 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -31,6 +31,8 @@ public: float radius_sqr, // squared radius of the cursor FacetSupportType new_state); // enforcer or blocker? + // Get facets currently in the given state. + indexed_triangle_set get_facets(FacetSupportType state) const; // Set facet of the mesh to a given state. Only works for original triangles. void set_facet(int facet_idx, FacetSupportType state); From afb5d929c47b54567c8ef5b2d1d8376621b60048 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 15 Jul 2020 12:28:52 +0200 Subject: [PATCH 33/70] TriangleSelector: Schedule restarting background process after edit --- src/libslic3r/Model.cpp | 4 +++- src/libslic3r/Model.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 6 +++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index d0410c2d4..4ff0a5c1b 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1841,13 +1841,15 @@ indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, FacetSu -void FacetsAnnotation::set(const TriangleSelector& selector) +bool FacetsAnnotation::set(const TriangleSelector& selector) { std::map> sel_map = selector.serialize(); if (sel_map != m_data) { m_data = sel_map; update_timestamp(); + return true; } + return false; } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 3127af5ba..16f3f00ad 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -406,7 +406,7 @@ public: using ClockType = std::chrono::steady_clock; const std::map>& get_data() const { return m_data; } - void set(const TriangleSelector& selector); + bool set(const TriangleSelector& selector); indexed_triangle_set get_facets(const ModelVolume& mv, FacetSupportType type) const; void clear(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 417d101ea..13c9cfef8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -204,14 +204,18 @@ void GLGizmoFdmSupports::render_cursor_circle() const void GLGizmoFdmSupports::update_model_object() const { + bool updated = false; ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { if (! mv->is_model_part()) continue; ++idx; - mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); + updated |= mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); } + + if (updated) + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } From 5a1d9aee15f507be774f96c2a98ca0adda8cc1e4 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 21 Jul 2020 09:01:17 +0200 Subject: [PATCH 34/70] TriangleSelector: Fix of a macOS crash Calling reset() from constructor relied on uninitialized variable --- src/libslic3r/TriangleSelector.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index dc2ad9127..877bc122c 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -118,8 +118,8 @@ protected: float m_edge_limit_sqr = 1.f; // Number of original vertices and triangles. - int m_orig_size_vertices; - int m_orig_size_indices; + int m_orig_size_vertices = 0; + int m_orig_size_indices = 0; // Cache for cursor position, radius and direction. struct Cursor { From 74a1aeff8e8421fd9523bd9ff788e33e5692c6e1 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 23 Jul 2020 08:17:04 +0200 Subject: [PATCH 35/70] TriangleSelector: bugfix - backend did not correctly account for mirrorring --- src/libslic3r/PrintObject.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 1a2edcf6e..273fc9c10 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2677,10 +2677,10 @@ void PrintObject::project_and_append_custom_supports( if (custom_facets.indices.empty()) continue; - const TriangleMesh& mesh = mv->mesh(); const Transform3f& tr1 = mv->get_matrix().cast(); const Transform3f& tr2 = this->trafo().cast(); const Transform3f tr = tr2 * tr1; + const float tr_det_sign = (tr.matrix().determinant() > 0. ? 1.f : -1.f); // The projection will be at most a pentagon. Let's minimize heap @@ -2719,8 +2719,9 @@ void PrintObject::project_and_append_custom_supports( for (int i=0; i<3; ++i) facet[i] = tr * custom_facets.vertices[custom_facets.indices[idx](i)]; - // Ignore triangles with upward-pointing normal. - if ((facet[1]-facet[0]).cross(facet[2]-facet[0]).z() > 0.) + // Ignore triangles with upward-pointing normal. Don't forget about mirroring. + float z_comp = (facet[1]-facet[0]).cross(facet[2]-facet[0]).z(); + if (tr_det_sign * z_comp > 0.) continue; // Sort the three vertices according to z-coordinate. From 7ddb64783b6094e3b1c5a8006e977c5308a6b841 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 24 Jul 2020 17:45:49 +0200 Subject: [PATCH 36/70] TriangleSelector: edge limit is derived from cursor size --- src/libslic3r/TriangleSelector.cpp | 10 ++++++++-- src/libslic3r/TriangleSelector.hpp | 5 +++-- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 2 +- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 6e3f9f518..50d775ed8 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -35,14 +35,20 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec3f& source, const Vec3f& dir, - float radius_sqr, FacetSupportType new_state) + float radius, FacetSupportType new_state) { assert(facet_start < m_orig_size_indices); assert(is_approx(dir.norm(), 1.f)); // Save current cursor center, squared radius and camera direction, // so we don't have to pass it around. - m_cursor = {hit, source, dir, radius_sqr}; + m_cursor = {hit, source, dir, radius*radius}; + + // In case user changed cursor size since last time, update triangle edge limit. + if (m_old_cursor_radius != radius) { + set_edge_limit(radius / 5.f); + m_old_cursor_radius = radius; + } // Now start with the facet the pointer points to and check all adjacent facets. std::vector facets_to_check{facet_start}; diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 877bc122c..fb90cff76 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -1,7 +1,7 @@ #ifndef libslic3r_TriangleSelector_hpp_ #define libslic3r_TriangleSelector_hpp_ -#define PRUSASLICER_TRIANGLE_SELECTOR_DEBUG +// #define PRUSASLICER_TRIANGLE_SELECTOR_DEBUG #include "Point.hpp" @@ -28,7 +28,7 @@ public: int facet_start, // facet that point belongs to const Vec3f& source, // camera position (mesh coords) const Vec3f& dir, // direction of the ray (mesh coords) - float radius_sqr, // squared radius of the cursor + float radius, // radius of the cursor FacetSupportType new_state); // enforcer or blocker? // Get facets currently in the given state. @@ -130,6 +130,7 @@ protected: }; Cursor m_cursor; + float m_old_cursor_radius; // Private functions: bool select_triangle(int facet_idx, FacetSupportType type, diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 13c9cfef8..3769e9660 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -404,7 +404,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // FIXME: The scaling of the mesh can be non-uniform. const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor(); const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.; - const float limit = std::pow(m_cursor_radius/avg_scaling , 2.f); + const float limit = m_cursor_radius/avg_scaling; // Calculate direction from camera to the hit (in mesh coords): Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 2d1442164..ce24ea8d2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -56,7 +56,7 @@ private: GLUquadricObj* m_quadric; float m_cursor_radius = 2.f; - static constexpr float CursorRadiusMin = 0.f; + static constexpr float CursorRadiusMin = 0.4f; // cannot be zero static constexpr float CursorRadiusMax = 8.f; static constexpr float CursorRadiusStep = 0.2f; From 248fba82a4892e85ffcfecd24b82bd0f9ddd8135 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 24 Jul 2020 16:53:05 +0200 Subject: [PATCH 37/70] TriangleSelector: 3MF loading and saving --- src/libslic3r/Format/3mf.cpp | 17 +++++++++ src/libslic3r/Model.cpp | 58 ++++++++++++++++++++++++++++++ src/libslic3r/Model.hpp | 2 ++ src/libslic3r/TriangleSelector.cpp | 1 + 4 files changed, 78 insertions(+) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index edf55ba37..3612e6898 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -86,6 +86,7 @@ const char* OBJECTID_ATTR = "objectid"; const char* TRANSFORM_ATTR = "transform"; const char* PRINTABLE_ATTR = "printable"; const char* INSTANCESCOUNT_ATTR = "instances_count"; +const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports"; const char* KEY_ATTR = "key"; const char* VALUE_ATTR = "value"; @@ -283,6 +284,7 @@ namespace Slic3r { { std::vector vertices; std::vector triangles; + std::vector custom_supports; bool empty() { @@ -293,6 +295,7 @@ namespace Slic3r { { vertices.clear(); triangles.clear(); + custom_supports.clear(); } }; @@ -1539,6 +1542,8 @@ namespace Slic3r { m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V1_ATTR)); m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V2_ATTR)); m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V3_ATTR)); + + m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); return true; } @@ -1872,6 +1877,13 @@ namespace Slic3r { volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); volume->calculate_convex_hull(); + // recreate custom supports from previously loaded attribute + assert(geometry.custom_supports.size() == triangles_count); + for (unsigned i=0; im_supported_facets.set_triangle_from_string(i, geometry.custom_supports[i]); + } + // apply the remaining volume's metadata for (const Metadata& metadata : volume_data.metadata) { @@ -2383,6 +2395,11 @@ namespace Slic3r { { stream << "v" << j + 1 << "=\"" << its.indices[i][j] + volume_it->second.first_vertex_id << "\" "; } + + std::string custom_supports_data_string = volume->m_supported_facets.get_triangle_as_string(i); + if (! custom_supports_data_string.empty()) + stream << CUSTOM_SUPPORTS_ATTR << "=\"" << custom_supports_data_string << "\" "; + stream << "/>\n"; } } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 4ff0a5c1b..3beb74f23 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1862,6 +1862,64 @@ void FacetsAnnotation::clear() +// Following function takes data from a triangle and encodes it as string +// of hexadecimal numbers (one digit per triangle). Used for 3MF export, +// changing it may break backwards compatibility !!!!! +std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const +{ + std::string out; + + auto triangle_it = m_data.find(triangle_idx); + if (triangle_it != m_data.end()) { + const std::vector& code = triangle_it->second; + int offset = 0; + while (offset < int(code.size())) { + int next_code = 0; + for (int i=3; i>=0; --i) { + next_code = next_code << 1; + next_code |= int(code[offset + i]); + } + offset += 4; + + assert(next_code >=0 && next_code <= 15); + char digit = next_code < 10 ? next_code + '0' : (next_code-10)+'A'; + out.insert(out.begin(), digit); + } + } + return out; +} + + + +// Recover triangle splitting & state from string of hexadecimal values previously +// generated by get_triangle_as_string. Used to load from 3MF. +void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::string& str) +{ + assert(! str.empty()); + m_data[triangle_id] = std::vector(); // zero current state or create new + std::vector& code = m_data[triangle_id]; + + for (auto it = str.crbegin(); it != str.crend(); ++it) { + const char ch = *it; + int dec = 0; + if (ch >= '0' && ch<='9') + dec = int(ch - '0'); + else if (ch >='A' && ch <= 'F') + dec = 10 + int(ch - 'A'); + else + assert(false); + + // Convert to binary and append into code. + for (int i=0; i<4; ++i) { + code.insert(code.end(), bool(dec & (1 << i))); + } + } + + +} + + + // Test whether the two models contain the same number of ModelObjects with the same set of IDs // ordered in the same order. In that case it is not necessary to kill the background processing. bool model_object_list_equal(const Model &model_old, const Model &model_new) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 16f3f00ad..92dc84d17 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -409,6 +409,8 @@ public: bool set(const TriangleSelector& selector); indexed_triangle_set get_facets(const ModelVolume& mv, FacetSupportType type) const; void clear(); + std::string get_triangle_as_string(int i) const; + void set_triangle_from_string(int triangle_id, const std::string& str); ClockType::time_point get_timestamp() const { return timestamp; } bool is_same_as(const FacetsAnnotation& other) const { diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 50d775ed8..763bf5861 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -603,6 +603,7 @@ void TriangleSelector::deserialize(const std::map> data) reset(); // dump any current state for (const auto& [triangle_id, code] : data) { assert(triangle_id < int(m_triangles.size())); + assert(! code.empty()); int processed_triangles = 0; struct ProcessingInfo { int facet_id = 0; From 67d2f438458c2185d01d8db02fe74f5f56e209db Mon Sep 17 00:00:00 2001 From: David Kocik Date: Sun, 14 Jun 2020 23:14:44 +0200 Subject: [PATCH 38/70] Showing Eject button only after exporting is finished. Fix of #4212 --- src/slic3r/GUI/Plater.cpp | 5 ++++- src/slic3r/GUI/RemovableDriveManager.cpp | 2 ++ src/slic3r/GUI/RemovableDriveManager.hpp | 6 ++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ec13610b8..e4e43bb7c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3450,7 +3450,7 @@ void Plater::priv::on_slicing_completed(wxCommandEvent &) break; default: break; } -} +} void Plater::priv::on_process_completed(wxCommandEvent &evt) { @@ -3510,7 +3510,10 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) show_action_buttons(true); } else if (this->writing_to_removable_device || wxGetApp().get_mode() == comSimple) + { + wxGetApp().removable_drive_manager()->set_exporting_finished(true); show_action_buttons(false); + } this->writing_to_removable_device = false; } diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index d67ac4a22..d865fe347 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -393,6 +393,7 @@ bool RemovableDriveManager::set_and_verify_last_save_path(const std::string &pat #endif // REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS m_last_save_path = this->get_removable_drive_from_path(path); + m_exporting_finished = false; return ! m_last_save_path.empty(); } @@ -407,6 +408,7 @@ RemovableDriveManager::RemovableDrivesStatus RemovableDriveManager::status() } if (! out.has_eject) m_last_save_path.clear(); + out.has_eject = out.has_eject && m_exporting_finished; return out; } diff --git a/src/slic3r/GUI/RemovableDriveManager.hpp b/src/slic3r/GUI/RemovableDriveManager.hpp index e1a8d6faf..26ee12e40 100644 --- a/src/slic3r/GUI/RemovableDriveManager.hpp +++ b/src/slic3r/GUI/RemovableDriveManager.hpp @@ -83,7 +83,7 @@ public: // Public to be accessible from RemovableDriveManagerMM::on_device_unmount OSX notification handler. // It would be better to make this method private and friend to RemovableDriveManagerMM, but RemovableDriveManagerMM is an ObjectiveC class. void update(); - + void set_exporting_finished(bool b) { m_exporting_finished = b; } #ifdef _WIN32 // Called by Win32 Volume arrived / detached callback. void volumes_changed(); @@ -121,7 +121,9 @@ private: std::vector::const_iterator find_last_save_path_drive_data() const; // Set with set_and_verify_last_save_path() to a removable drive path to be ejected. std::string m_last_save_path; - + // Verifies that exporting was finished so drive can be ejected. + // Set false by set_and_verify_last_save_path() that is called just before exporting. + bool m_exporting_finished; #if __APPLE__ void register_window_osx(); void unregister_window_osx(); From 1dc3561e2cc52fbe32aa13b2e74c09f6de621a1b Mon Sep 17 00:00:00 2001 From: David Kocik Date: Sun, 14 Jun 2020 23:53:17 +0200 Subject: [PATCH 39/70] added 's' in https when pointing users to our github --- src/slic3r/GUI/MainFrame.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 64a1319d4..687634d11 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -106,7 +106,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S m_statusbar->embed(this); m_statusbar->set_status_text(_(L("Version")) + " " + SLIC3R_VERSION + - _(L(" - Remember to check for updates at http://github.com/prusa3d/PrusaSlicer/releases"))); + _(L(" - Remember to check for updates at https://github.com/prusa3d/PrusaSlicer/releases"))); /* Load default preset bitmaps before a tabpanel initialization, * but after filling of an em_unit value @@ -1141,7 +1141,7 @@ void MainFrame::init_menubar() append_menu_item(helpMenu, wxID_ANY, _(L("Prusa 3D &Drivers")), _(L("Open the Prusa3D drivers download page in your browser")), [this](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/downloads"); }); append_menu_item(helpMenu, wxID_ANY, _(L("Software &Releases")), _(L("Open the software releases page in your browser")), - [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://github.com/prusa3d/PrusaSlicer/releases"); }); + [this](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); }); //# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{ //# wxTheApp->check_version(1); //# }); @@ -1158,7 +1158,7 @@ void MainFrame::init_menubar() append_menu_item(helpMenu, wxID_ANY, _(L("Show &Configuration Folder")), _(L("Show user configuration folder (datadir)")), [this](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); }); append_menu_item(helpMenu, wxID_ANY, _(L("Report an I&ssue")), wxString::Format(_(L("Report an issue on %s")), SLIC3R_APP_NAME), - [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://github.com/prusa3d/slic3r/issues/new"); }); + [this](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/slic3r/issues/new"); }); append_menu_item(helpMenu, wxID_ANY, wxString::Format(_(L("&About %s")), SLIC3R_APP_NAME), _(L("Show about dialog")), [this](wxCommandEvent&) { Slic3r::GUI::about(); }); helpMenu->AppendSeparator(); From 864ecf750cd1dab68d50ac8ee5d4bdac77d4f4c8 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 15 Jun 2020 00:34:12 +0200 Subject: [PATCH 40/70] Deleted default value in Plater::export_gcode(bool prefer_removable). Only place where it is not sure what value should be is in btn_reslice - i chose true --- src/slic3r/GUI/MainFrame.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 2 +- src/slic3r/GUI/Plater.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 687634d11..521dcab80 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -909,7 +909,7 @@ void MainFrame::init_menubar() wxMenu* export_menu = new wxMenu(); wxMenuItem* item_export_gcode = append_menu_item(export_menu, wxID_ANY, _(L("Export &G-code")) + dots +"\tCtrl+G", _(L("Export current plate as G-code")), - [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(); }, "export_gcode", nullptr, + [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(false); }, "export_gcode", nullptr, [this](){return can_export_gcode(); }, this); m_changeable_menu_items.push_back(item_export_gcode); wxMenuItem* item_send_gcode = append_menu_item(export_menu, wxID_ANY, _(L("S&end G-code")) + dots +"\tCtrl+Shift+G", _(L("Send to print current plate as G-code")), diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e4e43bb7c..761f574e1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -929,7 +929,7 @@ Sidebar::Sidebar(Plater *parent) { const bool export_gcode_after_slicing = wxGetKeyState(WXK_SHIFT); if (export_gcode_after_slicing) - p->plater->export_gcode(); + p->plater->export_gcode(true); else p->plater->reslice(); p->plater->select_view_3D("Preview"); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 5d60e006b..a08b19fa3 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -220,7 +220,7 @@ public: void cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); - void export_gcode(bool prefer_removable = true); + void export_gcode(bool prefer_removable); void export_stl(bool extended = false, bool selection_only = false); void export_amf(); void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); From 18594261d293651bf5f8234cfefe692e7709a103 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 27 Jul 2020 12:18:21 +0200 Subject: [PATCH 41/70] Added handling of mouse wheel events to ImGuiWrapper --- src/slic3r/GUI/GLCanvas3D.cpp | 5 +++++ src/slic3r/GUI/ImGuiWrapper.cpp | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b4e672c4f..2d4527784 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3300,6 +3300,11 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) if (evt.MiddleIsDown()) return; + if (wxGetApp().imgui()->update_mouse_data(evt)) { + m_dirty = true; + return; + } + #if ENABLE_RETINA_GL const float scale = m_retina_helper->get_scale_factor(); evt.SetX(evt.GetX() * scale); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 51a9a6d4e..88dd02ccb 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -176,6 +176,9 @@ bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt) io.MouseDown[0] = evt.LeftIsDown(); io.MouseDown[1] = evt.RightIsDown(); io.MouseDown[2] = evt.MiddleIsDown(); + float wheel_delta = static_cast(evt.GetWheelDelta()); + if (wheel_delta != 0.0f) + io.MouseWheel = static_cast(evt.GetWheelRotation()) / wheel_delta; unsigned buttons = (evt.LeftIsDown() ? 1 : 0) | (evt.RightIsDown() ? 2 : 0) | (evt.MiddleIsDown() ? 4 : 0); m_mouse_buttons = buttons; From 924bda6ec08f8940347423b7f8b3e1e1d3115e88 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 27 Jul 2020 20:06:10 +0200 Subject: [PATCH 42/70] ChangePresetForPhysicalPrinterDialog and SavePresetWindow are merged to SavePresetDialog --- src/slic3r/GUI/PresetComboBoxes.cpp | 301 ++++++++++++++++++++-------- src/slic3r/GUI/PresetComboBoxes.hpp | 55 +++-- src/slic3r/GUI/Tab.cpp | 61 +----- 3 files changed, 253 insertions(+), 164 deletions(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 3edc3947a..01c83921a 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -1005,106 +1005,135 @@ void TabPresetComboBox::update_dirty() #endif /* __APPLE __ */ } -void TabPresetComboBox::update_physical_printers( const std::string& preset_name) -{ - if (m_type != Preset::TYPE_PRINTER || !m_allow_to_update_physical_printers) - return; - - m_allow_to_update_physical_printers = false; - - PhysicalPrinterCollection& physical_printers = m_preset_bundle->physical_printers; - if (!physical_printers.has_selection()) - return; - - std::string printer_preset_name = physical_printers.get_selected_printer_preset_name(); - - if (Preset::remove_suffix_modified(preset_name) == printer_preset_name) { - if (!this->is_selected_physical_printer()) - physical_printers.unselect_printer(); - } - else - { - ChangePresetForPhysicalPrinterDialog dlg(Preset::remove_suffix_modified(preset_name)); - if(dlg.ShowModal() == wxID_OK) - { - if (dlg.m_selection == ChangePresetForPhysicalPrinterDialog::Switch) - // unselect physical printer, if it was selected - m_preset_bundle->physical_printers.unselect_printer(); - else - { - PhysicalPrinter printer = physical_printers.get_selected_printer(); - - if (dlg.m_selection == ChangePresetForPhysicalPrinterDialog::ChangePreset) - printer.delete_preset(printer_preset_name); - - if (printer.add_preset(preset_name)) - physical_printers.save_printer(printer); - else { - wxMessageDialog dialog(nullptr, _L("This preset is already exist for this physical printer. Please, select another one."), _L("Information"), wxICON_INFORMATION | wxOK); - dialog.ShowModal(); - } - - physical_printers.select_printer(printer.get_full_name(preset_name)); - } - } - else - wxGetApp().get_tab(Preset::TYPE_PRINTER)->select_preset(printer_preset_name); - } -} - //----------------------------------------------- -// ChangePresetForPhysicalPrinterDialog +// SavePresetDialog //----------------------------------------------- -ChangePresetForPhysicalPrinterDialog::ChangePresetForPhysicalPrinterDialog(const std::string& preset_name) - : DPIDialog(nullptr, wxID_ANY, _L("Warning"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING/* | wxRESIZE_BORDER*/) +SavePresetDialog::SavePresetDialog(TabPresetComboBox* preset_cb, const std::string& suffix) + : DPIDialog(nullptr, wxID_ANY, _L("Save preset"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER), + m_preset_cb(preset_cb) { SetFont(wxGetApp().normal_font()); - SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - - PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; - std::string printer_name = printers.get_selected_printer_name(); - std::string old_preset_name = printers.get_selected_printer_preset_name(); - - wxString msg_text = from_u8((boost::format(_u8L("You have selected physical printer \"%1%\"\n" - "with related printer preset \"%2%\"")) % - printer_name % old_preset_name).str()); - wxStaticText* label_top = new wxStaticText(this, wxID_ANY, msg_text); - label_top->SetFont(wxGetApp().bold_font()); - - wxString choices[] = { from_u8((boost::format(_u8L("Change \"%1%\" to \"%2%\" for this physical printer")) % old_preset_name % preset_name).str()), - from_u8((boost::format(_u8L("Add \"%1%\" as a next preset for the the physical printer")) % preset_name).str()), - from_u8((boost::format(_u8L("Just switch to \"%1%\"")) % preset_name).str()) }; - - wxRadioBox* selection_type_box = new wxRadioBox(this, wxID_ANY, _L("What would you like to do?"), wxDefaultPosition, wxDefaultSize, WXSIZEOF(choices), choices, - 3, wxRA_SPECIFY_ROWS); - selection_type_box->SetFont(wxGetApp().normal_font()); - selection_type_box->SetSelection(0); - - selection_type_box->Bind(wxEVT_RADIOBOX, [this](wxCommandEvent& e) { - int selection = e.GetSelection(); - m_selection = (SelectionType)selection; - }); - - auto radio_sizer = new wxBoxSizer(wxHORIZONTAL); - radio_sizer->Add(selection_type_box, 1, wxALIGN_CENTER_VERTICAL); - - wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); - wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); - btnOK->Bind(wxEVT_BUTTON, &ChangePresetForPhysicalPrinterDialog::OnOK, this); + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); - topSizer->Add(label_top, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); - topSizer->Add(radio_sizer, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); - topSizer->Add(btns, 0, wxEXPAND | wxALL, BORDER_W); + add_common_items(topSizer, suffix); + add_items_for_edit_ph_printer(topSizer); + + // add dialog's buttons + wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); + wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); + btnOK->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); }); + btnOK->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { + evt.Enable(!m_combo->GetValue().IsEmpty()); }); + + topSizer->Add(btns, 0, wxEXPAND | wxALL, BORDER_W); SetSizer(topSizer); topSizer->SetSizeHints(this); } -void ChangePresetForPhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) +void SavePresetDialog::add_common_items(wxBoxSizer* sizer, const std::string& suffix) +{ + const PresetCollection* presets = m_preset_cb->presets(); + const Preset& sel_preset = presets->get_selected_preset(); + std::string preset_name = sel_preset.is_default ? "Untitled" : + sel_preset.is_system ? (boost::format(("%1% - %2%")) % sel_preset.name % suffix).str() : + sel_preset.name; + + // if name contains extension + if (boost::iends_with(preset_name, ".ini")) { + size_t len = preset_name.length() - 4; + preset_name.resize(len); + } + + std::vector values; + for (const Preset& preset : *presets) { + if (preset.is_default || preset.is_system || preset.is_external) + continue; + values.push_back(preset.name); + } + + wxStaticText* label_top = new wxStaticText(this, wxID_ANY, from_u8((boost::format(_utf8(L("Save %s as:"))) % into_u8(wxGetApp().get_tab(m_preset_cb->type())->title())).str())); + m_combo = new wxComboBox(this, wxID_ANY, from_u8(preset_name), + wxDefaultPosition, wxDefaultSize, 0, 0, wxTE_PROCESS_ENTER); + for (auto value : values) + m_combo->Append(from_u8(value)); + + m_combo->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent&) { accept(); }); + m_combo->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { + update(normalize_utf8_nfc(m_combo->GetValue().ToUTF8())); + this->Layout(); + this->Fit(); + }); + + sizer->Add(label_top, 0, wxEXPAND | wxALL, BORDER_W); + sizer->Add(m_combo, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, BORDER_W); +} + +void SavePresetDialog::add_items_for_edit_ph_printer(wxBoxSizer* sizer) +{ + if (m_preset_cb->type() != Preset::TYPE_PRINTER || !m_preset_cb->is_selected_physical_printer()) + return; + + PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; + m_ph_printer_name = printers.get_selected_printer_name(); + m_old_preset_name = printers.get_selected_printer_preset_name(); + + wxString msg_text = from_u8((boost::format(_u8L("You have selected physical printer \"%1%\" \n" + "with related printer preset \"%2%\"")) % + m_ph_printer_name % m_old_preset_name).str()); + m_label = new wxStaticText(this, wxID_ANY, msg_text); + m_label->SetFont(wxGetApp().bold_font()); + + wxString choices[] = {"","",""}; + + m_action_radio_box = new wxRadioBox(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, + WXSIZEOF(choices), choices, 3, wxRA_SPECIFY_ROWS); + m_action_radio_box->SetFont(wxGetApp().normal_font()); + m_action_radio_box->SetSelection(0); + m_action_radio_box->Bind(wxEVT_RADIOBOX, [this](wxCommandEvent& e) { + m_action = (ActionType)e.GetSelection(); }); + m_action = ChangePreset; + + m_radio_sizer = new wxBoxSizer(wxHORIZONTAL); + m_radio_sizer->Add(m_action_radio_box, 1, wxALIGN_CENTER_VERTICAL); + + sizer->Add(m_label, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); + sizer->Add(m_radio_sizer, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); + + update(m_preset_name); +} + +void SavePresetDialog::update(const std::string& preset_name) +{ + if (m_preset_cb->type() != Preset::TYPE_PRINTER || !m_preset_cb->is_selected_physical_printer()) + return; + + bool show = m_old_preset_name != preset_name; + + m_label->Show(show); + m_radio_sizer->ShowItems(show); + if (!show) { + this->SetMinSize(wxSize(100,50)); + return; + } + + wxString msg_text = from_u8((boost::format(_u8L("What would you like to do with \"%1%\" preset after saving?")) % preset_name).str()); + m_action_radio_box->SetLabel(msg_text); + + wxString choices[] = { from_u8((boost::format(_u8L("Change \"%1%\" to \"%2%\" for this physical printer \"%3%\"")) % m_old_preset_name % preset_name % m_ph_printer_name).str()), + from_u8((boost::format(_u8L("Add \"%1%\" as a next preset for the the physical printer \"%2%\"")) % preset_name % m_ph_printer_name).str()), + from_u8((boost::format(_u8L("Just switch to \"%1%\" preset")) % preset_name).str()) }; + + int n = 0; + for(const wxString& label: choices) + m_action_radio_box->SetString(n++, label); +} + +void SavePresetDialog::on_dpi_changed(const wxRect& suggested_rect) { const int& em = em_unit(); @@ -1117,10 +1146,106 @@ void ChangePresetForPhysicalPrinterDialog::on_dpi_changed(const wxRect& suggeste Refresh(); } -void ChangePresetForPhysicalPrinterDialog::OnOK(wxEvent& event) +bool SavePresetDialog::preset_name_is_accepted() { - event.Skip(); + const char* unusable_symbols = "<>[]:/\\|?*\""; + const std::string unusable_suffix = PresetCollection::get_suffix_modified();//"(modified)"; + for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { + if (m_preset_name.find_first_of(unusable_symbols[i]) != std::string::npos) { + show_error(this, _L("The supplied name is not valid;") + "\n" + + _L("the following characters are not allowed:") + " " + unusable_symbols); + return false; + } + } + + if (m_preset_name.find(unusable_suffix) != std::string::npos) { + show_error(this, _L("The supplied name is not valid;") + "\n" + + _L("the following suffix is not allowed:") + "\n\t" + + from_u8(PresetCollection::get_suffix_modified())); + return false; + } + + if (m_preset_name == "- default -") { + show_error(this, _L("The supplied name is not available.")); + return false; + } + return true; +} + +bool SavePresetDialog::preset_is_possible_to_save() +{ + const Preset* existing = m_preset_cb->presets()->find_preset(m_preset_name, false); + if (existing && (existing->is_default || existing->is_system)) { + show_error(this, _L("Cannot overwrite a system profile.")); + return false; + } + if (existing && (existing->is_external)) { + show_error(this, _(L("Cannot overwrite an external profile."))); + return false; + } + if (existing && m_preset_name != m_preset_cb->presets()->get_selected_preset_name()) + { + wxString msg_text = GUI::from_u8((boost::format(_utf8(L("Preset with name \"%1%\" already exists."))) % m_preset_name).str()); + msg_text += "\n" + _L("Replace?"); + wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); + + if (dialog.ShowModal() == wxID_NO) + return false; + + // Remove the preset from the list. + m_preset_cb->presets()->delete_preset(m_preset_name); + } + return true; +} + +void SavePresetDialog::update_physical_printers() +{ + if (m_action == UndefAction) + return; + + PhysicalPrinterCollection& physical_printers = wxGetApp().preset_bundle->physical_printers; + if (!physical_printers.has_selection()) + return; + + std::string printer_preset_name = physical_printers.get_selected_printer_preset_name(); + + if (m_action == Switch) + // unselect physical printer, if it was selected + physical_printers.unselect_printer(); + else + { + PhysicalPrinter printer = physical_printers.get_selected_printer(); + + if (m_action == ChangePreset) + printer.delete_preset(printer_preset_name); + + if (printer.add_preset(m_preset_name)) + physical_printers.save_printer(printer); + else { + wxMessageDialog dialog(nullptr, _L("This preset is already exist for this physical printer. Please, select another one."), _L("Information"), wxICON_INFORMATION | wxOK); + dialog.ShowModal(); + } + + physical_printers.select_printer(printer.get_full_name(m_preset_name)); + } +} + +void SavePresetDialog::accept() +{ + m_preset_name = normalize_utf8_nfc(m_combo->GetValue().ToUTF8()); + + if (m_preset_name.empty()) + return; + + if (!preset_name_is_accepted() || + !preset_is_possible_to_save()) + return; + + update_physical_printers(); + + EndModal(wxID_OK); } + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 6b8017f31..fa4554a5e 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -13,6 +13,7 @@ class wxTextCtrl; class wxStaticText; class ScalableButton; class wxBoxSizer; +class wxComboBox; namespace Slic3r { @@ -167,50 +168,68 @@ class TabPresetComboBox : public PresetComboBox bool show_incompatible {false}; bool m_enable_all {false}; - bool m_allow_to_update_physical_printers {false}; - public: TabPresetComboBox(wxWindow *parent, Preset::Type preset_type); ~TabPresetComboBox() {} void set_show_incompatible_presets(bool show_incompatible_presets) { show_incompatible = show_incompatible_presets; } - void allow_to_update_physical_printers() { - m_allow_to_update_physical_printers = m_type == Preset::TYPE_PRINTER; - } void update() override; void update_dirty(); - void update_physical_printers(const std::string& preset_name); void msw_rescale() override; void set_enable_all(bool enable=true) { m_enable_all = enable; } + + PresetCollection* presets() const { return m_collection; } + Preset::Type type() const { return m_type; } }; //------------------------------------------------ -// ChangePresetForPhysicalPrinterDialog +// SavePresetDialog //------------------------------------------------ -class ChangePresetForPhysicalPrinterDialog : public DPIDialog +class SavePresetDialog : public DPIDialog { - void OnOK(wxEvent& event); + enum ActionType + { + ChangePreset, + AddPreset, + Switch, + UndefAction + }; + + TabPresetComboBox* m_preset_cb {nullptr}; + std::string m_preset_name; + wxComboBox* m_combo {nullptr}; + wxStaticText* m_label {nullptr}; + wxRadioBox* m_action_radio_box {nullptr}; + wxBoxSizer* m_radio_sizer {nullptr}; + ActionType m_action {UndefAction}; + + std::string m_ph_printer_name; + std::string m_old_preset_name; public: - enum SelectionType - { - Switch, - ChangePreset, - AddPreset - } m_selection {Switch}; + SavePresetDialog(TabPresetComboBox* preset_cb, const std::string& suffix); + ~SavePresetDialog() {} - ChangePresetForPhysicalPrinterDialog(const std::string& preset_name); - ~ChangePresetForPhysicalPrinterDialog() {} + std::string get_name() { return m_preset_name; } protected: void on_dpi_changed(const wxRect& suggested_rect) override; - void on_sys_color_changed() override {}; + void on_sys_color_changed() override {} + +private: + void add_common_items(wxBoxSizer *sizer, const std::string &suffix); + void add_items_for_edit_ph_printer(wxBoxSizer *sizer); + void update(const std::string &preset_name); + bool preset_name_is_accepted(); + bool preset_is_possible_to_save(); + void update_physical_printers(); + void accept(); }; } // namespace GUI diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 3f566eacb..a97c10f7d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -164,11 +164,8 @@ void Tab::create_preset_tab() m_presets_choice->set_selection_changed_function([this](int selection) { if (!m_presets_choice->selection_is_changed_according_to_physical_printers()) { - // For the printer presets allow to update a physical printer if it is needed. - // After call of the update_physical_printers() this possibility will be disabled again to avoid a case, - // when select_preset is called from the others than this place - if (m_type == Preset::TYPE_PRINTER) - m_presets_choice->allow_to_update_physical_printers(); + if (m_type == Preset::TYPE_PRINTER && !m_presets_choice->is_selected_physical_printer()) + m_preset_bundle->physical_printers.unselect_printer(); // select preset select_preset(m_presets_choice->GetString(selection).ToUTF8().data()); @@ -3148,9 +3145,6 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, m_dependent_tabs = { Preset::Type::TYPE_SLA_PRINT, Preset::Type::TYPE_SLA_MATERIAL }; } - //update physical printer's related printer preset if it's needed - m_presets_choice->update_physical_printers(preset_name); - load_current_preset(); } } @@ -3290,56 +3284,10 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) std::string suffix = detach ? _utf8(L("Detached")) : _CTX_utf8(L_CONTEXT("Copy", "PresetName"), "PresetName"); if (name.empty()) { - const Preset &preset = m_presets->get_selected_preset(); - auto default_name = preset.is_default ? "Untitled" : -// preset.is_system ? (boost::format(_CTX_utf8(L_CONTEXT("%1% - Copy", "PresetName"), "PresetName")) % preset.name).str() : - preset.is_system ? (boost::format(("%1% - %2%")) % preset.name % suffix).str() : - preset.name; - - bool have_extention = boost::iends_with(default_name, ".ini"); - if (have_extention) { - size_t len = default_name.length()-4; - default_name.resize(len); - } - //[map $_->name, grep !$_->default && !$_->external, @{$self->{presets}}], - std::vector values; - for (size_t i = 0; i < m_presets->size(); ++i) { - const Preset &preset = m_presets->preset(i); - if (preset.is_default || preset.is_system || preset.is_external) - continue; - values.push_back(preset.name); - } - - SavePresetWindow dlg(parent()); - dlg.build(title(), default_name, values); + SavePresetDialog dlg(m_presets_choice, suffix); if (dlg.ShowModal() != wxID_OK) return; name = dlg.get_name(); - if (name == "") { - show_error(this, _(L("The supplied name is empty. It can't be saved."))); - return; - } - const Preset *existing = m_presets->find_preset(name, false); - if (existing && (existing->is_default || existing->is_system)) { - show_error(this, _(L("Cannot overwrite a system profile."))); - return; - } - if (existing && (existing->is_external)) { - show_error(this, _(L("Cannot overwrite an external profile."))); - return; - } - if (existing && name != preset.name) - { - wxString msg_text = GUI::from_u8((boost::format(_utf8(L("Preset with name \"%1%\" already exists."))) % name).str()); - msg_text += "\n" + _(L("Replace?")); - wxMessageDialog dialog(nullptr, msg_text, _(L("Warning")), wxICON_WARNING | wxYES | wxNO); - - if (dialog.ShowModal() == wxID_NO) - return; - - // Remove the preset from the list. - m_presets->delete_preset(name); - } } // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini @@ -3347,9 +3295,6 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) // Mark the print & filament enabled if they are compatible with the currently selected preset. // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); - //update physical printer's related printer preset if it's needed - m_presets_choice->allow_to_update_physical_printers(); - m_presets_choice->update_physical_printers(name); // Add the new item into the UI component, remove dirty flags and activate the saved item. update_tab_ui(); // Update the selection boxes at the plater. From 68ae95509fc4410baa9069cb407b8bb756e2be10 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 28 Jul 2020 11:31:16 +0200 Subject: [PATCH 43/70] Improved InfoMsg for a delete preset: * Now we show a list of printers name with selected preset + Added a edit_button for the editing of the physical printer fro the Settings Tab + Show whole list of the loaded presets with "Print host upload" --- src/libslic3r/Preset.cpp | 65 ++++++---- src/libslic3r/Preset.hpp | 11 +- src/slic3r/GUI/GUI_App.cpp | 38 ++---- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 2 + src/slic3r/GUI/PresetComboBoxes.cpp | 15 ++- src/slic3r/GUI/PresetComboBoxes.hpp | 2 +- src/slic3r/GUI/Tab.cpp | 153 ++++++++++++++--------- src/slic3r/GUI/Tab.hpp | 3 +- 8 files changed, 173 insertions(+), 116 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 7e30831fe..c7acf8f49 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1383,13 +1383,14 @@ const std::vector& PhysicalPrinter::print_host_options() return s_opts; } -bool PhysicalPrinter::has_print_host_information(const PrinterPresetCollection& printer_presets) +std::vector PhysicalPrinter::presets_with_print_host_information(const PrinterPresetCollection& printer_presets) { + std::vector presets; for (const Preset& preset : printer_presets) if (has_print_host_information(preset.config)) - return true; + presets.emplace_back(preset.name); - return false; + return presets; } bool PhysicalPrinter::has_print_host_information(const DynamicPrintConfig& config) @@ -1564,7 +1565,7 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const // if there is saved user presets, contains information about "Print Host upload", // Create default printers with this presets // Note! "Print Host upload" options will be cleared after physical printer creations -void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollection& printer_presets, std::string def_printer_name) +void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollection& printer_presets) { int cnt=0; for (Preset& preset: printer_presets) { @@ -1579,8 +1580,12 @@ void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollecti // just add preset for this printer existed_printer->add_preset(preset.name); else { + std::string new_printer_name = (boost::format("Printer %1%") % ++cnt ).str(); + while (find_printer(new_printer_name)) + new_printer_name = (boost::format("Printer %1%") % ++cnt).str(); + // create new printer from this preset - PhysicalPrinter printer((boost::format("%1% %2%") % def_printer_name % ++cnt ).str(), preset); + PhysicalPrinter printer(new_printer_name, preset); printer.loaded = true; save_printer(printer); } @@ -1699,33 +1704,51 @@ bool PhysicalPrinterCollection::delete_selected_printer() return true; } -bool PhysicalPrinterCollection::delete_preset_from_printers( const std::string& preset_name, bool first_check /*=true*/) +bool PhysicalPrinterCollection::delete_preset_from_printers( const std::string& preset_name) { - if (first_check) { - for (auto printer: m_printers) - if (printer.preset_names.size()==1 && *printer.preset_names.begin() == preset_name) - return false; - } - std::vector printers_for_delete; - for (PhysicalPrinter& printer : m_printers) + for (PhysicalPrinter& printer : m_printers) { if (printer.preset_names.size() == 1 && *printer.preset_names.begin() == preset_name) printers_for_delete.emplace_back(printer.name); - else if (printer.delete_preset(preset_name)) { - if (printer.name == get_selected_printer_name() && - preset_name == get_selected_printer_preset_name()) - select_printer(printer); + else if (printer.delete_preset(preset_name)) save_printer(printer); - } + } - if (!printers_for_delete.empty()) { + if (!printers_for_delete.empty()) for (const std::string& printer_name : printers_for_delete) delete_printer(printer_name); - unselect_printer(); - } + + unselect_printer(); return true; } +// Get list of printers which have more than one preset and "preset_name" preset is one of them +std::vector PhysicalPrinterCollection::get_printers_with_preset(const std::string& preset_name) +{ + std::vector printers; + + for (auto printer : m_printers) { + if (printer.preset_names.size() == 1) + continue; + if (printer.preset_names.find(preset_name) != printer.preset_names.end()) + printers.emplace_back(printer.name); + } + + return printers; +} + +// Get list of printers which has only "preset_name" preset +std::vector PhysicalPrinterCollection::get_printers_with_only_preset(const std::string& preset_name) +{ + std::vector printers; + + for (auto printer : m_printers) + if (printer.preset_names.size() == 1 && *printer.preset_names.begin() == preset_name) + printers.emplace_back(printer.name); + + return printers; +} + std::string PhysicalPrinterCollection::get_selected_full_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_full_name(m_selected_preset); diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 6b5a2a511..e34fca4dd 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -555,7 +555,7 @@ public: static std::string separator(); static const std::vector& printer_options(); static const std::vector& print_host_options(); - static bool has_print_host_information(const PrinterPresetCollection& printer_presets); + static std::vector presets_with_print_host_information(const PrinterPresetCollection& printer_presets); static bool has_print_host_information(const DynamicPrintConfig& config); const std::set& get_preset_names() const; @@ -629,7 +629,7 @@ public: // Load ini files of the particular type from the provided directory path. void load_printers(const std::string& dir_path, const std::string& subdir); - void load_printers_from_presets(PrinterPresetCollection &printer_presets, std::string def_printer_name); + void load_printers_from_presets(PrinterPresetCollection &printer_presets); // Save the printer under a new name. If the name is different from the old one, // a new printer is stored into the list of printers. @@ -645,7 +645,12 @@ public: // Delete preset_name preset from all printers: // If there is last preset for the printer and first_check== false, then delete this printer // returns true if all presets were deleted successfully. - bool delete_preset_from_printers(const std::string& preset_name, bool first_check = true); + bool delete_preset_from_printers(const std::string& preset_name); + + // Get list of printers which have more than one preset and "preset_name" preset is one of them + std::vector get_printers_with_preset( const std::string &preset_name); + // Get list of printers which has only "preset_name" preset + std::vector get_printers_with_only_preset( const std::string &preset_name); // Return the selected preset, without the user modifications applied. PhysicalPrinter& get_selected_printer() { return m_printers[m_idx_selected]; } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 1b7278bd7..410614b94 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -635,38 +635,22 @@ void GUI_App::set_auto_toolbar_icon_scale(float scale) const // check user printer_presets for the containing information about "Print Host upload" void GUI_App::check_printer_presets() { - if (!PhysicalPrinter::has_print_host_information(preset_bundle->printers)) + std::vector preset_names = PhysicalPrinter::presets_with_print_host_information(preset_bundle->printers); + if (preset_names.empty()) return; - wxString msg_text = _L("You have presets with saved options for \"Print Host upload\".\n" - "But from this version of PrusaSlicer we don't show/use this information in Printer Settings.\n" + wxString msg_text = _L("You have next presets with saved options for \"Print Host upload\"") + ":"; + for (const std::string& preset_name : preset_names) + msg_text += "\n \"" + from_u8(preset_name) + "\","; + msg_text.RemoveLast(); + msg_text += "\n\n" + _L("But from this version of PrusaSlicer we don't show/use this information in Printer Settings.\n" "Now, this information will be exposed in physical printers settings.") + "\n\n" + - _L("Enter the name for the Printer device used by default during its creation.\n" - "Note: This name can be changed later from the physical printers settings") + ":"; - wxString msg_header = _L("Name for printer device"); + _L("By default new Printer devices will be named as \"Printer N\" during its creation.\n" + "Note: This name can be changed later from the physical printers settings"); - // get custom gcode - wxTextEntryDialog dlg(nullptr, msg_text, msg_header, _L("Printer"), wxTextEntryDialogStyle); + wxMessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal(); - // detect TextCtrl and OK button - wxTextCtrl* textctrl{ nullptr }; - wxWindowList& dlg_items = dlg.GetChildren(); - for (auto item : dlg_items) { - textctrl = dynamic_cast(item); - if (textctrl) - break; - } - - if (textctrl) { - textctrl->SetSelection(0, textctrl->GetLastPosition()); - - wxButton* btn_OK = static_cast(dlg.FindWindowById(wxID_OK)); - btn_OK->Bind(wxEVT_UPDATE_UI, [textctrl](wxUpdateUIEvent& evt) { - evt.Enable(!textctrl->IsEmpty()); - }, btn_OK->GetId()); - } - if (dlg.ShowModal() == wxID_OK) - preset_bundle->physical_printers.load_printers_from_presets(preset_bundle->printers, into_u8(dlg.GetValue())); + preset_bundle->physical_printers.load_printers_from_presets(preset_bundle->printers); } void GUI_App::recreate_GUI(const wxString& msg_name) diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 7d3c92c13..f14f49801 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -510,6 +510,8 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) // refresh preset list on Printer Settings Tab wxGetApp().get_tab(Preset::TYPE_PRINTER)->select_preset(printers.get_selected_printer_preset_name()); } + else + wxGetApp().get_tab(Preset::TYPE_PRINTER)->update_preset_choice(); event.Skip(); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 01c83921a..666f10194 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -171,7 +171,7 @@ void PresetComboBox::update_selection() SetToolTip(GetString(m_last_selected)); } -void PresetComboBox::update(const std::string& select_preset_name) +void PresetComboBox::update(std::string select_preset_name) { Freeze(); Clear(); @@ -192,6 +192,8 @@ void PresetComboBox::update(const std::string& select_preset_name) // marker used for disable incompatible printer models for the selected physical printer bool is_enabled = m_type == Preset::TYPE_PRINTER && printer_technology != ptAny ? preset.printer_technology() == printer_technology : true; + if (select_preset_name.empty() && is_enabled) + select_preset_name = preset.name; std::string bitmap_key = "cb"; if (m_type == Preset::TYPE_PRINTER) { @@ -208,7 +210,7 @@ void PresetComboBox::update(const std::string& select_preset_name) int item_id = Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); if (!is_enabled) set_label_marker(item_id, LABEL_ITEM_DISABLED); - validate_selection(preset.name == select_preset_name || (select_preset_name.empty() && is_enabled)); + validate_selection(preset.name == select_preset_name); } else { @@ -659,6 +661,13 @@ void PlaterPresetComboBox::show_edit_menu() wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); }); }, "edit_uni", menu, []() { return true; }, wxGetApp().plater()); + append_menu_item(menu, wxID_ANY, _L("Add physical printer"), "", + [this](wxCommandEvent&) { + PhysicalPrinterDialog dlg(wxEmptyString); + if (dlg.ShowModal() == wxID_OK) + update(); + }, "edit_uni", menu, []() { return true; }, wxGetApp().plater()); + wxGetApp().plater()->PopupMenu(menu); } @@ -784,7 +793,7 @@ void PlaterPresetComboBox::update() } } - if (/*m_type == Preset::TYPE_PRINTER || */m_type == Preset::TYPE_SLA_MATERIAL) { + if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) { wxBitmap* bmp = get_bmp("edit_preset_list", wide_icons, "edit_uni"); assert(bmp); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index fa4554a5e..a30d9f6e9 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -55,7 +55,7 @@ public: // and next internal selection was accomplished bool selection_is_changed_according_to_physical_printers(); - void update(const std::string& select_preset); + void update(std::string select_preset); virtual void update(); virtual void msw_rescale(); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a97c10f7d..4dbe7cc74 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -35,6 +35,7 @@ #include "Plater.hpp" #include "MainFrame.hpp" #include "format.hpp" +#include "PhysicalPrinterDialog.hpp" namespace Slic3r { namespace GUI { @@ -180,6 +181,8 @@ void Tab::create_preset_tab() add_scaled_button(panel, &m_btn_save_preset, "save"); add_scaled_button(panel, &m_btn_delete_preset, "cross"); + if (m_type == Preset::Type::TYPE_PRINTER) + add_scaled_button(panel, &m_btn_edit_ph_printer, "cog"); m_show_incompatible_presets = false; add_scaled_bitmap(this, m_bmp_show_incompatible_presets, "flag_red"); @@ -191,6 +194,8 @@ void Tab::create_preset_tab() m_btn_save_preset->SetToolTip(from_u8((boost::format(_utf8(L("Save current %s"))) % m_title).str())); m_btn_delete_preset->SetToolTip(_(L("Delete this preset"))); m_btn_delete_preset->Disable(); + if (m_btn_edit_ph_printer) + m_btn_edit_ph_printer->Disable(); add_scaled_button(panel, &m_question_btn, "question"); m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information \n" @@ -245,6 +250,10 @@ void Tab::create_preset_tab() m_hsizer->Add(m_btn_save_preset, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->AddSpacer(int(4 * scale_factor)); m_hsizer->Add(m_btn_delete_preset, 0, wxALIGN_CENTER_VERTICAL); + if (m_btn_edit_ph_printer) { + m_hsizer->AddSpacer(int(4 * scale_factor)); + m_hsizer->Add(m_btn_edit_ph_printer, 0, wxALIGN_CENTER_VERTICAL); + } m_hsizer->AddSpacer(int(/*16*/8 * scale_factor)); m_hsizer->Add(m_btn_hide_incompatible_presets, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->AddSpacer(int(8 * scale_factor)); @@ -291,6 +300,13 @@ void Tab::create_preset_tab() toggle_show_hide_incompatible(); })); + if (m_btn_edit_ph_printer) + m_btn_edit_ph_printer->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { + PhysicalPrinterDialog dlg(m_presets_choice->GetString(m_presets_choice->GetSelection())); + if (dlg.ShowModal() == wxID_OK) + update_tab_ui(); + })); + // Fill cache for mode bitmaps m_mode_bitmap_cache.reserve(3); m_mode_bitmap_cache.push_back(ScalableBitmap(this, "mode_simple" , mode_icon_px_size())); @@ -2786,8 +2802,7 @@ void Tab::load_current_preset() { const Preset& preset = m_presets->get_edited_preset(); -// (preset.is_default || preset.is_system) ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); - update_delete_preset_btn(); + update_btns_enabling(); update(); if (m_type == Slic3r::Preset::TYPE_PRINTER) { @@ -2924,23 +2939,25 @@ void Tab::update_page_tree_visibility() } -void Tab::update_delete_preset_btn() +void Tab::update_btns_enabling() { - if (m_type == Preset::TYPE_PRINTER && m_presets_choice->is_selected_physical_printer() && - m_preset_bundle->physical_printers.has_selection()) { - // we can't delete last preset from the physical printer + // we can't delete last preset from the physical printer + if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) m_btn_delete_preset->Enable(m_preset_bundle->physical_printers.get_selected_printer().preset_names.size() > 1); - } else { const Preset& preset = m_presets->get_edited_preset(); m_btn_delete_preset->Enable(!preset.is_default && !preset.is_system); } + + // we can edit physical printer only if it's selected in the list + if (m_btn_edit_ph_printer) + m_btn_edit_ph_printer->Enable(m_preset_bundle->physical_printers.has_selection()); } void Tab::update_preset_choice() { m_presets_choice->update(); - update_delete_preset_btn(); + update_btns_enabling(); } // Called by the UI combo box when the user switches profiles, and also to delete the current profile. @@ -2950,55 +2967,16 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, { if (preset_name.empty()) { if (delete_current) { - PhysicalPrinterCollection& physical_printers = m_preset_bundle->physical_printers; - if (m_presets_choice->is_selected_physical_printer()) { - PhysicalPrinter& printer = physical_printers.get_selected_printer(); - - if (printer.preset_names.size()==1) { - wxMessageDialog dialog(nullptr, _L("It's a last for this physical printer. We can't delete it"), _L("Information"), wxICON_INFORMATION | wxOK); - dialog.ShowModal(); - } - else { - // just delete this preset from the current physical printer - printer.delete_preset(m_presets->get_edited_preset().name); - // select first from the possible presets for this printer - physical_printers.select_printer(printer); - - preset_name = physical_printers.get_selected_printer_preset_name(); - // revert delete_current value to avoid deleting of the new selected preset - delete_current = false; - } - } - else { - // Check preset for delete in physical printers - // Ask a customer about next action , if there is a printer with just one preset and this preset is equal to delete - if (m_type == Preset::TYPE_PRINTER && !physical_printers.empty() ) - { - // try to delete selected preset from the all printers it has - if (!physical_printers.delete_preset_from_printers(m_presets->get_edited_preset().name)) - { - wxMessageDialog dialog(nullptr, _L("There is/are a physical printer(s), which has/have one and only this printer preset.\n" - "This/Those printer(s) will be deleted after deleting of the selected preset.\n" - "Are you sure you want to delete the selected preset?"), _L("Warning"), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); - if (dialog.ShowModal() == wxID_NO) - return; - - // delete selected preset from printers and printer, if it's needed - physical_printers.delete_preset_from_printers(m_presets->get_edited_preset().name, false); - } - } - - // Find an alternate preset to be selected after the current preset is deleted. - const std::deque &presets = this->m_presets->get_presets(); - size_t idx_current = this->m_presets->get_idx_selected(); - // Find the next visible preset. - size_t idx_new = idx_current + 1; - if (idx_new < presets.size()) - for (; idx_new < presets.size() && ! presets[idx_new].is_visible; ++ idx_new) ; - if (idx_new == presets.size()) - for (idx_new = idx_current - 1; idx_new > 0 && ! presets[idx_new].is_visible; -- idx_new); - preset_name = presets[idx_new].name; - } + // Find an alternate preset to be selected after the current preset is deleted. + const std::deque &presets = this->m_presets->get_presets(); + size_t idx_current = this->m_presets->get_idx_selected(); + // Find the next visible preset. + size_t idx_new = idx_current + 1; + if (idx_new < presets.size()) + for (; idx_new < presets.size() && ! presets[idx_new].is_visible; ++ idx_new) ; + if (idx_new == presets.size()) + for (idx_new = idx_current - 1; idx_new > 0 && ! presets[idx_new].is_visible; -- idx_new); + preset_name = presets[idx_new].name; } else { // If no name is provided, select the "-- default --" preset. preset_name = m_presets->default_preset().name; @@ -3349,15 +3327,70 @@ void Tab::delete_preset() // Don't let the user delete the ' - default - ' configuration. std::string action = current_preset.is_external ? _utf8(L("remove")) : _utf8(L("delete")); // TRN remove/delete - const wxString msg = m_presets_choice->is_selected_physical_printer() ? - from_u8((boost::format(_utf8(L("Are you sure you want to delete \"%1%\" preset from the physical printer?"))) % current_preset.name).str()) : - from_u8((boost::format(_utf8(L("Are you sure you want to %1% the selected preset?"))) % action).str()); + + PhysicalPrinterCollection& physical_printers = m_preset_bundle->physical_printers; + wxString msg; + if (m_presets_choice->is_selected_physical_printer()) + msg = from_u8((boost::format(_u8L("Are you sure you want to delete \"%1%\" preset from the physical printer \"%2%\"?")) + % current_preset.name % physical_printers.get_selected_printer_name()).str()); + else + { + if (m_type == Preset::TYPE_PRINTER && !physical_printers.empty()) + { + // Check preset for delete in physical printers + // Ask a customer about next action, if there is a printer with just one preset and this preset is equal to delete + std::vector ph_printers = physical_printers.get_printers_with_preset(current_preset.name); + std::vector ph_printers_only = physical_printers.get_printers_with_only_preset(current_preset.name); + + if (!ph_printers.empty()) { + msg += _L("Next physical printer(s) has/have selected preset") + ":"; + for (const std::string& printer : ph_printers) + msg += "\n \"" + from_u8(printer) + "\","; + msg.RemoveLast(); + msg += "\n" + _L("Note, that selected preset will be deleted from this/those printer(s) too.")+ "\n\n"; + } + + if (!ph_printers_only.empty()) { + msg += _L("Next physical printer(s) has/have one and only selected preset") + ":"; + for (const std::string& printer : ph_printers_only) + msg += "\n \"" + from_u8(printer) + "\","; + msg.RemoveLast(); + msg += "\n" + _L("Note, that this/those printer(s) will be deleted after deleting of the selected preset.") + "\n\n"; + } + } + + msg += from_u8((boost::format(_u8L("Are you sure you want to %1% the selected preset?")) % action).str()); + } + action = current_preset.is_external ? _utf8(L("Remove")) : _utf8(L("Delete")); // TRN Remove/Delete wxString title = from_u8((boost::format(_utf8(L("%1% Preset"))) % action).str()); //action + _(L(" Preset")); if (current_preset.is_default || wxID_YES != wxMessageDialog(parent(), msg, title, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal()) return; + + // if we just delete preset from the physical printer + if (m_presets_choice->is_selected_physical_printer()) { + PhysicalPrinter& printer = physical_printers.get_selected_printer(); + + if (printer.preset_names.size() == 1) { + wxMessageDialog dialog(nullptr, _L("It's a last for this physical printer. We can't delete it"), _L("Information"), wxICON_INFORMATION | wxOK); + dialog.ShowModal(); + return; + } + // just delete this preset from the current physical printer + printer.delete_preset(m_presets->get_edited_preset().name); + // select first from the possible presets for this printer + physical_printers.select_printer(printer); + + this->select_preset(physical_printers.get_selected_printer_preset_name()); + return; + } + + // delete selected preset from printers and printer, if it's needed + if (m_type == Preset::TYPE_PRINTER && !physical_printers.empty()) + physical_printers.delete_preset_from_printers(current_preset.name); + // Select will handle of the preset dependencies, of saving & closing the depending profiles, and // finally of deleting the preset. this->select_preset("", true); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 57129955b..24f25e2d7 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -119,6 +119,7 @@ protected: ScalableButton* m_search_btn; ScalableButton* m_btn_save_preset; ScalableButton* m_btn_delete_preset; + ScalableButton* m_btn_edit_ph_printer {nullptr}; ScalableButton* m_btn_hide_incompatible_presets; wxBoxSizer* m_hsizer; wxBoxSizer* m_left_sizer; @@ -275,7 +276,7 @@ public: void load_current_preset(); void rebuild_page_tree(); void update_page_tree_visibility(); - void update_delete_preset_btn(); + void update_btns_enabling(); void update_preset_choice(); // Select a new preset, possibly delete the current one. void select_preset(std::string preset_name = "", bool delete_current = false, const std::string& last_selected_ph_printer_name = ""); From 96a364c3e6356d7107d6c51b4c1763c81d0d3435 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 29 Jul 2020 16:05:30 +0200 Subject: [PATCH 44/70] SavePresetDialog: Improvements --- src/slic3r/GUI/PresetComboBoxes.cpp | 303 ++++++++++++++++------------ src/slic3r/GUI/PresetComboBoxes.hpp | 64 ++++-- src/slic3r/GUI/Tab.cpp | 2 +- 3 files changed, 229 insertions(+), 140 deletions(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 666f10194..35acbfd3a 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -1016,41 +1016,21 @@ void TabPresetComboBox::update_dirty() //----------------------------------------------- -// SavePresetDialog +// SavePresetDialog::Item //----------------------------------------------- -SavePresetDialog::SavePresetDialog(TabPresetComboBox* preset_cb, const std::string& suffix) - : DPIDialog(nullptr, wxID_ANY, _L("Save preset"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER), - m_preset_cb(preset_cb) +SavePresetDialog::Item::Item(Preset::Type type, const std::string& suffix, wxBoxSizer* sizer, SavePresetDialog* parent): + m_type(type), + m_parent(parent) { - SetFont(wxGetApp().normal_font()); - SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + Tab* tab = wxGetApp().get_tab(m_type); + assert(tab); + m_presets = tab->get_presets(); - wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); - - add_common_items(topSizer, suffix); - add_items_for_edit_ph_printer(topSizer); - - // add dialog's buttons - wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); - wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); - btnOK->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); }); - btnOK->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { - evt.Enable(!m_combo->GetValue().IsEmpty()); }); - - topSizer->Add(btns, 0, wxEXPAND | wxALL, BORDER_W); - - SetSizer(topSizer); - topSizer->SetSizeHints(this); -} - -void SavePresetDialog::add_common_items(wxBoxSizer* sizer, const std::string& suffix) -{ - const PresetCollection* presets = m_preset_cb->presets(); - const Preset& sel_preset = presets->get_selected_preset(); - std::string preset_name = sel_preset.is_default ? "Untitled" : - sel_preset.is_system ? (boost::format(("%1% - %2%")) % sel_preset.name % suffix).str() : - sel_preset.name; + const Preset& sel_preset = m_presets->get_selected_preset(); + std::string preset_name = sel_preset.is_default ? "Untitled" : + sel_preset.is_system ? (boost::format(("%1% - %2%")) % sel_preset.name % suffix).str() : + sel_preset.name; // if name contains extension if (boost::iends_with(preset_name, ".ini")) { @@ -1059,34 +1039,166 @@ void SavePresetDialog::add_common_items(wxBoxSizer* sizer, const std::string& su } std::vector values; - for (const Preset& preset : *presets) { + for (const Preset& preset : *m_presets) { if (preset.is_default || preset.is_system || preset.is_external) continue; values.push_back(preset.name); } - wxStaticText* label_top = new wxStaticText(this, wxID_ANY, from_u8((boost::format(_utf8(L("Save %s as:"))) % into_u8(wxGetApp().get_tab(m_preset_cb->type())->title())).str())); - m_combo = new wxComboBox(this, wxID_ANY, from_u8(preset_name), - wxDefaultPosition, wxDefaultSize, 0, 0, wxTE_PROCESS_ENTER); + wxStaticText* label_top = new wxStaticText(m_parent, wxID_ANY, from_u8((boost::format(_utf8(L("Save %s as:"))) % into_u8(tab->title())).str())); + + m_valid_bmp = new wxStaticBitmap(m_parent, wxID_ANY, create_scaled_bitmap("tick_mark", m_parent)); + + m_combo = new wxComboBox(m_parent, wxID_ANY, from_u8(preset_name)/*, + wxDefaultPosition, wxDefaultSize, 0, 0, wxTE_PROCESS_ENTER*/); for (auto value : values) m_combo->Append(from_u8(value)); - m_combo->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent&) { accept(); }); - m_combo->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { - update(normalize_utf8_nfc(m_combo->GetValue().ToUTF8())); - this->Layout(); - this->Fit(); - }); + m_combo->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { update(); }); - sizer->Add(label_top, 0, wxEXPAND | wxALL, BORDER_W); - sizer->Add(m_combo, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, BORDER_W); + m_valid_label = new wxStaticText(m_parent, wxID_ANY, ""); + m_valid_label->SetFont(wxGetApp().bold_font()); + + wxBoxSizer* combo_sizer = new wxBoxSizer(wxHORIZONTAL); + combo_sizer->Add(m_valid_bmp, 0, wxEXPAND | wxRIGHT, BORDER_W); + combo_sizer->Add(m_combo, 1, wxEXPAND, BORDER_W); + + sizer->Add(label_top, 0, wxEXPAND | wxTOP| wxBOTTOM, BORDER_W); + sizer->Add(combo_sizer, 0, wxEXPAND | wxBOTTOM, BORDER_W); + sizer->Add(m_valid_label, 0, wxEXPAND | wxLEFT, 3*BORDER_W/* + m_valid_bmp->GetBitmap().GetWidth()*/); + + if (m_type == Preset::TYPE_PRINTER) + m_parent->add_info_for_edit_ph_printer(sizer); + + update(); } -void SavePresetDialog::add_items_for_edit_ph_printer(wxBoxSizer* sizer) +void SavePresetDialog::Item::update() { - if (m_preset_cb->type() != Preset::TYPE_PRINTER || !m_preset_cb->is_selected_physical_printer()) - return; + m_preset_name = into_u8(m_combo->GetValue()); + m_valid_type = Valid; + wxString info_line; + + const char* unusable_symbols = "<>[]:/\\|?*\""; + + const std::string unusable_suffix = PresetCollection::get_suffix_modified();//"(modified)"; + for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { + if (m_preset_name.find_first_of(unusable_symbols[i]) != std::string::npos) { + info_line = _L("The supplied name is not valid;") + "\n" + + _L("the following characters are not allowed:") + " " + unusable_symbols; + m_valid_type = NoValid; + break; + } + } + + if (m_valid_type == Valid && m_preset_name.find(unusable_suffix) != std::string::npos) { + info_line = _L("The supplied name is not valid;") + "\n" + + _L("the following suffix is not allowed:") + "\n\t" + + from_u8(PresetCollection::get_suffix_modified()); + m_valid_type = NoValid; + } + + if (m_valid_type == Valid && m_preset_name == "- default -") { + info_line = _L("The supplied name is not available."); + m_valid_type = NoValid; + } + + const Preset* existing = m_presets->find_preset(m_preset_name, false); + if (m_valid_type == Valid && existing && (existing->is_default || existing->is_system)) { + info_line = _L("Cannot overwrite a system profile."); + m_valid_type = NoValid; + } + if (m_valid_type == Valid && existing && (existing->is_external)) { + info_line = _L("Cannot overwrite an external profile."); + m_valid_type = NoValid; + } + if (m_valid_type == Valid && existing && m_preset_name != m_presets->get_selected_preset_name()) + { + info_line = from_u8((boost::format(_u8L("Preset with name \"%1%\" already exists.")) % m_preset_name).str()) + "\n" + + _L("Note: This preset will be replaced after saving"); + m_valid_type = Warning; + } + + m_valid_label->SetLabel(info_line); + m_valid_label->Show(!info_line.IsEmpty()); + + std::string bmp_name = m_valid_type == Warning ? "exclamation" : + m_valid_type == NoValid ? "cross" : "tick_mark" ; + m_valid_bmp->SetBitmap(create_scaled_bitmap(bmp_name, m_parent)); + + if (m_type == Preset::TYPE_PRINTER) + m_parent->update_info_for_edit_ph_printer(m_preset_name); + + m_parent->layout(); +} + +void SavePresetDialog::Item::accept() +{ + if (m_valid_type == Warning) + m_presets->delete_preset(m_preset_name); +} + + +//----------------------------------------------- +// SavePresetDialog +//----------------------------------------------- + +SavePresetDialog::SavePresetDialog(Preset::Type type, const std::string& suffix) + : DPIDialog(nullptr, wxID_ANY, _L("Save preset"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER) +{ + SetFont(wxGetApp().normal_font()); + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + m_presets_sizer = new wxBoxSizer(wxVERTICAL); + + // Add first item + m_items.emplace_back(type, suffix, m_presets_sizer, this); + + // Add dialog's buttons + wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); + wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); + btnOK->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); }); + btnOK->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(enable_ok_btn()); }); + + topSizer->Add(m_presets_sizer, 0, wxEXPAND | wxALL, BORDER_W); + topSizer->Add(btns, 0, wxEXPAND | wxALL, BORDER_W); + + SetSizer(topSizer); + topSizer->SetSizeHints(this); +} + +void SavePresetDialog::AddItem(Preset::Type type, const std::string& suffix) +{ + m_items.emplace_back(type, suffix, m_presets_sizer, this); +} + +std::string SavePresetDialog::get_name() +{ + return m_items.front().preset_name(); +} + +std::string SavePresetDialog::get_name(Preset::Type type) +{ + for (Item& item : m_items) + if (item.type() == type) + return item.preset_name(); + return ""; +} + +bool SavePresetDialog::enable_ok_btn() const +{ + for (Item item : m_items) + if (!item.is_valid()) + return false; + + return true; +} + +void SavePresetDialog::add_info_for_edit_ph_printer(wxBoxSizer* sizer) +{ PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; m_ph_printer_name = printers.get_selected_printer_name(); m_old_preset_name = printers.get_selected_printer_preset_name(); @@ -1102,6 +1214,7 @@ void SavePresetDialog::add_items_for_edit_ph_printer(wxBoxSizer* sizer) m_action_radio_box = new wxRadioBox(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, WXSIZEOF(choices), choices, 3, wxRA_SPECIFY_ROWS); m_action_radio_box->SetFont(wxGetApp().normal_font()); + m_action_radio_box->SetLabelFont(wxGetApp().normal_font()); m_action_radio_box->SetSelection(0); m_action_radio_box->Bind(wxEVT_RADIOBOX, [this](wxCommandEvent& e) { m_action = (ActionType)e.GetSelection(); }); @@ -1110,18 +1223,13 @@ void SavePresetDialog::add_items_for_edit_ph_printer(wxBoxSizer* sizer) m_radio_sizer = new wxBoxSizer(wxHORIZONTAL); m_radio_sizer->Add(m_action_radio_box, 1, wxALIGN_CENTER_VERTICAL); - sizer->Add(m_label, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); - sizer->Add(m_radio_sizer, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); - - update(m_preset_name); + sizer->Add(m_label, 0, wxEXPAND | wxALL, 2*BORDER_W); + sizer->Add(m_radio_sizer, 1, wxEXPAND | wxLEFT, 2*BORDER_W); } -void SavePresetDialog::update(const std::string& preset_name) +void SavePresetDialog::update_info_for_edit_ph_printer(const std::string& preset_name) { - if (m_preset_cb->type() != Preset::TYPE_PRINTER || !m_preset_cb->is_selected_physical_printer()) - return; - - bool show = m_old_preset_name != preset_name; + bool show = wxGetApp().preset_bundle->physical_printers.has_selection() && m_old_preset_name != preset_name; m_label->Show(show); m_radio_sizer->ShowItems(show); @@ -1142,6 +1250,12 @@ void SavePresetDialog::update(const std::string& preset_name) m_action_radio_box->SetString(n++, label); } +void SavePresetDialog::layout() +{ + this->Layout(); + this->Fit(); +} + void SavePresetDialog::on_dpi_changed(const wxRect& suggested_rect) { const int& em = em_unit(); @@ -1149,65 +1263,13 @@ void SavePresetDialog::on_dpi_changed(const wxRect& suggested_rect) msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); const wxSize& size = wxSize(45 * em, 35 * em); - SetMinSize(size); + SetMinSize(/*size*/wxSize(100, 50)); Fit(); Refresh(); } -bool SavePresetDialog::preset_name_is_accepted() -{ - const char* unusable_symbols = "<>[]:/\\|?*\""; - const std::string unusable_suffix = PresetCollection::get_suffix_modified();//"(modified)"; - for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { - if (m_preset_name.find_first_of(unusable_symbols[i]) != std::string::npos) { - show_error(this, _L("The supplied name is not valid;") + "\n" + - _L("the following characters are not allowed:") + " " + unusable_symbols); - return false; - } - } - - if (m_preset_name.find(unusable_suffix) != std::string::npos) { - show_error(this, _L("The supplied name is not valid;") + "\n" + - _L("the following suffix is not allowed:") + "\n\t" + - from_u8(PresetCollection::get_suffix_modified())); - return false; - } - - if (m_preset_name == "- default -") { - show_error(this, _L("The supplied name is not available.")); - return false; - } - return true; -} - -bool SavePresetDialog::preset_is_possible_to_save() -{ - const Preset* existing = m_preset_cb->presets()->find_preset(m_preset_name, false); - if (existing && (existing->is_default || existing->is_system)) { - show_error(this, _L("Cannot overwrite a system profile.")); - return false; - } - if (existing && (existing->is_external)) { - show_error(this, _(L("Cannot overwrite an external profile."))); - return false; - } - if (existing && m_preset_name != m_preset_cb->presets()->get_selected_preset_name()) - { - wxString msg_text = GUI::from_u8((boost::format(_utf8(L("Preset with name \"%1%\" already exists."))) % m_preset_name).str()); - msg_text += "\n" + _L("Replace?"); - wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); - - if (dialog.ShowModal() == wxID_NO) - return false; - - // Remove the preset from the list. - m_preset_cb->presets()->delete_preset(m_preset_name); - } - return true; -} - -void SavePresetDialog::update_physical_printers() +void SavePresetDialog::update_physical_printers(const std::string& preset_name) { if (m_action == UndefAction) return; @@ -1228,29 +1290,20 @@ void SavePresetDialog::update_physical_printers() if (m_action == ChangePreset) printer.delete_preset(printer_preset_name); - if (printer.add_preset(m_preset_name)) + if (printer.add_preset(preset_name)) physical_printers.save_printer(printer); - else { - wxMessageDialog dialog(nullptr, _L("This preset is already exist for this physical printer. Please, select another one."), _L("Information"), wxICON_INFORMATION | wxOK); - dialog.ShowModal(); - } - physical_printers.select_printer(printer.get_full_name(m_preset_name)); + physical_printers.select_printer(printer.get_full_name(preset_name)); } } void SavePresetDialog::accept() { - m_preset_name = normalize_utf8_nfc(m_combo->GetValue().ToUTF8()); - - if (m_preset_name.empty()) - return; - - if (!preset_name_is_accepted() || - !preset_is_possible_to_save()) - return; - - update_physical_printers(); + for (Item& item : m_items) { + item.accept(); + if (item.type() == Preset::TYPE_PRINTER) + update_physical_printers(item.preset_name()); + } EndModal(wxID_OK); } diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index a30d9f6e9..c0de645df 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -14,6 +14,7 @@ class wxStaticText; class ScalableButton; class wxBoxSizer; class wxComboBox; +class wxStaticBitmap; namespace Slic3r { @@ -200,35 +201,70 @@ class SavePresetDialog : public DPIDialog UndefAction }; - TabPresetComboBox* m_preset_cb {nullptr}; - std::string m_preset_name; - wxComboBox* m_combo {nullptr}; - wxStaticText* m_label {nullptr}; + struct Item + { + enum ValidationType + { + Valid, + NoValid, + Warning + }; + + Item(Preset::Type type, const std::string& suffix, wxBoxSizer* sizer, SavePresetDialog* parent); + + void accept(); + + bool is_valid() const { return m_valid_type != NoValid; } + Preset::Type type() const { return m_type; } + std::string preset_name() const { return m_preset_name; } + + private: + Preset::Type m_type; + ValidationType m_valid_type; + std::string m_preset_name; + + SavePresetDialog* m_parent {nullptr}; + wxStaticBitmap* m_valid_bmp {nullptr}; + wxComboBox* m_combo {nullptr}; + wxStaticText* m_valid_label {nullptr}; + + PresetCollection* m_presets {nullptr}; + + void update(); + }; + + std::vector m_items; + + wxBoxSizer* m_presets_sizer {nullptr}; + wxStaticText* m_label {nullptr}; wxRadioBox* m_action_radio_box {nullptr}; wxBoxSizer* m_radio_sizer {nullptr}; ActionType m_action {UndefAction}; - std::string m_ph_printer_name; - std::string m_old_preset_name; + std::string m_ph_printer_name; + std::string m_old_preset_name; public: - SavePresetDialog(TabPresetComboBox* preset_cb, const std::string& suffix); + SavePresetDialog(Preset::Type type, const std::string& suffix); ~SavePresetDialog() {} - std::string get_name() { return m_preset_name; } + void AddItem(Preset::Type type, const std::string& suffix); + + std::string get_name(); + std::string get_name(Preset::Type type); + + bool enable_ok_btn() const; + void add_info_for_edit_ph_printer(wxBoxSizer *sizer); + void update_info_for_edit_ph_printer(const std::string &preset_name); + void layout(); protected: void on_dpi_changed(const wxRect& suggested_rect) override; void on_sys_color_changed() override {} private: - void add_common_items(wxBoxSizer *sizer, const std::string &suffix); - void add_items_for_edit_ph_printer(wxBoxSizer *sizer); - void update(const std::string &preset_name); - bool preset_name_is_accepted(); - bool preset_is_possible_to_save(); - void update_physical_printers(); + void update_physical_printers(const std::string& preset_name); void accept(); }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 4dbe7cc74..9d37362ba 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3262,7 +3262,7 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) std::string suffix = detach ? _utf8(L("Detached")) : _CTX_utf8(L_CONTEXT("Copy", "PresetName"), "PresetName"); if (name.empty()) { - SavePresetDialog dlg(m_presets_choice, suffix); + SavePresetDialog dlg(m_type, suffix); if (dlg.ShowModal() != wxID_OK) return; name = dlg.get_name(); From d84e70f59afaf7c052462f6a7cadf1fa28876eb9 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 30 Jul 2020 09:43:13 +0200 Subject: [PATCH 45/70] SavePresetDialog: Fixed OSX and Linux build + Added scaling of the validation icons --- src/slic3r/GUI/PresetComboBoxes.cpp | 34 ++++++++++++++++------------- src/slic3r/GUI/PresetComboBoxes.hpp | 1 + 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 35acbfd3a..77bdb3812 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -1049,9 +1049,8 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string& suffix, wxBox m_valid_bmp = new wxStaticBitmap(m_parent, wxID_ANY, create_scaled_bitmap("tick_mark", m_parent)); - m_combo = new wxComboBox(m_parent, wxID_ANY, from_u8(preset_name)/*, - wxDefaultPosition, wxDefaultSize, 0, 0, wxTE_PROCESS_ENTER*/); - for (auto value : values) + m_combo = new wxComboBox(m_parent, wxID_ANY, from_u8(preset_name)); + for (const std::string& value : values) m_combo->Append(from_u8(value)); m_combo->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { update(); }); @@ -1060,12 +1059,12 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string& suffix, wxBox m_valid_label->SetFont(wxGetApp().bold_font()); wxBoxSizer* combo_sizer = new wxBoxSizer(wxHORIZONTAL); - combo_sizer->Add(m_valid_bmp, 0, wxEXPAND | wxRIGHT, BORDER_W); + combo_sizer->Add(m_valid_bmp, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, BORDER_W); combo_sizer->Add(m_combo, 1, wxEXPAND, BORDER_W); sizer->Add(label_top, 0, wxEXPAND | wxTOP| wxBOTTOM, BORDER_W); sizer->Add(combo_sizer, 0, wxEXPAND | wxBOTTOM, BORDER_W); - sizer->Add(m_valid_label, 0, wxEXPAND | wxLEFT, 3*BORDER_W/* + m_valid_bmp->GetBitmap().GetWidth()*/); + sizer->Add(m_valid_label, 0, wxEXPAND | wxLEFT, 3*BORDER_W); if (m_type == Preset::TYPE_PRINTER) m_parent->add_info_for_edit_ph_printer(sizer); @@ -1123,9 +1122,7 @@ void SavePresetDialog::Item::update() m_valid_label->SetLabel(info_line); m_valid_label->Show(!info_line.IsEmpty()); - std::string bmp_name = m_valid_type == Warning ? "exclamation" : - m_valid_type == NoValid ? "cross" : "tick_mark" ; - m_valid_bmp->SetBitmap(create_scaled_bitmap(bmp_name, m_parent)); + update_valid_bmp(); if (m_type == Preset::TYPE_PRINTER) m_parent->update_info_for_edit_ph_printer(m_preset_name); @@ -1133,6 +1130,13 @@ void SavePresetDialog::Item::update() m_parent->layout(); } +void SavePresetDialog::Item::update_valid_bmp() +{ + std::string bmp_name = m_valid_type == Warning ? "exclamation" : + m_valid_type == NoValid ? "cross" : "tick_mark" ; + m_valid_bmp->SetBitmap(create_scaled_bitmap(bmp_name, m_parent)); +} + void SavePresetDialog::Item::accept() { if (m_valid_type == Warning) @@ -1147,7 +1151,6 @@ void SavePresetDialog::Item::accept() SavePresetDialog::SavePresetDialog(Preset::Type type, const std::string& suffix) : DPIDialog(nullptr, wxID_ANY, _L("Save preset"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER) { - SetFont(wxGetApp().normal_font()); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); @@ -1213,18 +1216,16 @@ void SavePresetDialog::add_info_for_edit_ph_printer(wxBoxSizer* sizer) m_action_radio_box = new wxRadioBox(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, WXSIZEOF(choices), choices, 3, wxRA_SPECIFY_ROWS); - m_action_radio_box->SetFont(wxGetApp().normal_font()); - m_action_radio_box->SetLabelFont(wxGetApp().normal_font()); m_action_radio_box->SetSelection(0); m_action_radio_box->Bind(wxEVT_RADIOBOX, [this](wxCommandEvent& e) { m_action = (ActionType)e.GetSelection(); }); m_action = ChangePreset; m_radio_sizer = new wxBoxSizer(wxHORIZONTAL); - m_radio_sizer->Add(m_action_radio_box, 1, wxALIGN_CENTER_VERTICAL); + m_radio_sizer->Add(m_action_radio_box, 1, wxEXPAND | wxTOP, 2*BORDER_W); - sizer->Add(m_label, 0, wxEXPAND | wxALL, 2*BORDER_W); - sizer->Add(m_radio_sizer, 1, wxEXPAND | wxLEFT, 2*BORDER_W); + sizer->Add(m_label, 0, wxEXPAND | wxLEFT | wxTOP, 3*BORDER_W); + sizer->Add(m_radio_sizer, 1, wxEXPAND | wxLEFT, 3*BORDER_W); } void SavePresetDialog::update_info_for_edit_ph_printer(const std::string& preset_name) @@ -1262,7 +1263,10 @@ void SavePresetDialog::on_dpi_changed(const wxRect& suggested_rect) msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); - const wxSize& size = wxSize(45 * em, 35 * em); + for (Item& item : m_items) + item.update_valid_bmp(); + + //const wxSize& size = wxSize(45 * em, 35 * em); SetMinSize(/*size*/wxSize(100, 50)); Fit(); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index c0de645df..f31b67fbe 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -212,6 +212,7 @@ class SavePresetDialog : public DPIDialog Item(Preset::Type type, const std::string& suffix, wxBoxSizer* sizer, SavePresetDialog* parent); + void update_valid_bmp(); void accept(); bool is_valid() const { return m_valid_type != NoValid; } From 5eb3b21be791003fa575066c4d1096e9b879f509 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 30 Jul 2020 09:45:45 +0200 Subject: [PATCH 46/70] Added missed icons/tick_mark.svg --- resources/icons/tick_mark.svg | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 resources/icons/tick_mark.svg diff --git a/resources/icons/tick_mark.svg b/resources/icons/tick_mark.svg new file mode 100644 index 000000000..4ccab2192 --- /dev/null +++ b/resources/icons/tick_mark.svg @@ -0,0 +1,6 @@ + + + + + + From a29b00a0b46b390a1ae195534e094faab49824d2 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 3 Aug 2020 08:28:43 +0200 Subject: [PATCH 47/70] Use ImGui::TextColored() --- src/slic3r/GUI/GLCanvas3D.cpp | 73 ++++++++------------ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 4 +- src/slic3r/GUI/ImGuiWrapper.cpp | 16 +++++ src/slic3r/GUI/ImGuiWrapper.hpp | 3 + src/slic3r/GUI/Mouse3DController.cpp | 63 ++++++----------- 5 files changed, 70 insertions(+), 89 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 2d4527784..5109d2426 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -225,56 +225,44 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); const Size& cnv_size = canvas.get_canvas_size(); - float canvas_w = (float)cnv_size.get_width(); - float canvas_h = (float)cnv_size.get_height(); ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.set_next_window_pos(canvas_w - imgui.get_style_scaling() * THICKNESS_BAR_WIDTH, canvas_h, ImGuiCond_Always, 1.0f, 1.0f); + imgui.set_next_window_pos(static_cast(cnv_size.get_width()) - imgui.get_style_scaling() * THICKNESS_BAR_WIDTH, + static_cast(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f); - imgui.begin(_(L("Variable layer height")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); + imgui.begin(_L("Variable layer height"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(_(L("Left mouse button:"))); - ImGui::PopStyleColor(); + imgui.text_colored(ORANGE, _L("Left mouse button:")); ImGui::SameLine(); - imgui.text(_(L("Add detail"))); + imgui.text(_L("Add detail")); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(_(L("Right mouse button:"))); - ImGui::PopStyleColor(); + imgui.text_colored(ORANGE, _L("Right mouse button:")); ImGui::SameLine(); - imgui.text(_(L("Remove detail"))); + imgui.text(_L("Remove detail")); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(_(L("Shift + Left mouse button:"))); - ImGui::PopStyleColor(); + imgui.text_colored(ORANGE, _L("Shift + Left mouse button:")); ImGui::SameLine(); - imgui.text(_(L("Reset to base"))); + imgui.text(_L("Reset to base")); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(_(L("Shift + Right mouse button:"))); - ImGui::PopStyleColor(); + imgui.text_colored(ORANGE, _L("Shift + Right mouse button:")); ImGui::SameLine(); - imgui.text(_(L("Smoothing"))); + imgui.text(_L("Smoothing")); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(_(L("Mouse wheel:"))); - ImGui::PopStyleColor(); + imgui.text_colored(ORANGE, _L("Mouse wheel:")); ImGui::SameLine(); - imgui.text(_(L("Increase/decrease edit area"))); + imgui.text(_L("Increase/decrease edit area")); ImGui::Separator(); - if (imgui.button(_(L("Adaptive")))) + if (imgui.button(_L("Adaptive"))) wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), Event(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, m_adaptive_quality)); ImGui::SameLine(); float text_align = ImGui::GetCursorPosX(); ImGui::AlignTextToFramePadding(); - imgui.text(_(L("Quality / Speed"))); - if (ImGui::IsItemHovered()) - { + imgui.text(_L("Quality / Speed")); + if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); - ImGui::TextUnformatted(_(L("Higher print quality versus higher print speed.")).ToUTF8()); + ImGui::TextUnformatted(_L("Higher print quality versus higher print speed.").ToUTF8()); ImGui::EndTooltip(); } @@ -285,13 +273,13 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const ImGui::SliderFloat("", &m_adaptive_quality, 0.0f, 1.f, "%.2f"); ImGui::Separator(); - if (imgui.button(_(L("Smooth")))) + if (imgui.button(_L("Smooth"))) wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), HeightProfileSmoothEvent(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, m_smooth_params)); ImGui::SameLine(); ImGui::SetCursorPosX(text_align); ImGui::AlignTextToFramePadding(); - imgui.text(_(L("Radius"))); + imgui.text(_L("Radius")); ImGui::SameLine(); ImGui::SetCursorPosX(widget_align); ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); @@ -301,7 +289,7 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const ImGui::SetCursorPosX(text_align); ImGui::AlignTextToFramePadding(); - imgui.text(_(L("Keep min"))); + imgui.text(_L("Keep min")); ImGui::SameLine(); if (ImGui::GetCursorPosX() < widget_align) // because of line lenght after localization ImGui::SetCursorPosX(widget_align); @@ -310,7 +298,7 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const imgui.checkbox("##2", m_smooth_params.keep_min); ImGui::Separator(); - if (imgui.button(_(L("Reset")))) + if (imgui.button(_L("Reset"))) wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE)); imgui.end(); @@ -1430,8 +1418,7 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas #if ENABLE_SLOPE_RENDERING void GLCanvas3D::Slope::render() const { - if (m_dialog_shown) - { + if (m_dialog_shown) { const std::array& z_range = m_volumes.get_slope_z_range(); std::array angle_range = { Geometry::rad2deg(::acos(z_range[0])) - 90.0f, Geometry::rad2deg(::acos(z_range[1])) - 90.0f }; bool modified = false; @@ -1439,9 +1426,9 @@ void GLCanvas3D::Slope::render() const ImGuiWrapper& imgui = *wxGetApp().imgui(); const Size& cnv_size = m_canvas.get_canvas_size(); imgui.set_next_window_pos((float)cnv_size.get_width(), (float)cnv_size.get_height(), ImGuiCond_Always, 1.0f, 1.0f); - imgui.begin(_(L("Slope visualization")), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + imgui.begin(_L("Slope visualization"), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - imgui.text(_(L("Facets' slope range (degrees)")) + ":"); + imgui.text(_L("Facets' slope range (degrees)") + ":"); ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.75f, 0.0f, 0.0f, 0.5f)); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 0.0f, 0.0f, 0.5f)); @@ -1453,8 +1440,7 @@ void GLCanvas3D::Slope::render() const float slope_bound = 90.f - angle_range[1]; bool mod = ImGui::SliderFloat("##red", &slope_bound, 0.0f, 90.0f, "%.1f"); angle_range[1] = 90.f - slope_bound; - if (mod) - { + if (mod) { modified = true; if (angle_range[0] > angle_range[1]) angle_range[0] = angle_range[1]; @@ -1462,15 +1448,14 @@ void GLCanvas3D::Slope::render() const ImGui::PopStyleColor(4); ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.75f, 0.75f, 0.0f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 1.0f, 0.0f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.85f, 0.85f, 0.0f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.25f, 0.25f, 0.0f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 1.0f, 0.0f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.85f, 0.85f, 0.0f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.25f, 0.25f, 0.0f, 1.0f)); slope_bound = 90.f - angle_range[0]; mod = ImGui::SliderFloat("##yellow", &slope_bound, 0.0f, 90.0f, "%.1f"); angle_range[0] = 90.f - slope_bound; - if (mod) - { + if (mod) { modified = true; if (angle_range[1] < angle_range[0]) angle_range[1] = angle_range[0]; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 3769e9660..4bbd52c30 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -513,9 +513,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - m_imgui->text(caption); - ImGui::PopStyleColor(); + m_imgui->text_colored(ORANGE, caption); ImGui::SameLine(caption_max); m_imgui->text(text); }; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 88dd02ccb..a21194d94 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -354,6 +354,22 @@ void ImGuiWrapper::text(const wxString &label) this->text(label_utf8.c_str()); } +void ImGuiWrapper::text_colored(const ImVec4& color, const char* label) +{ + ImGui::TextColored(color, label); +} + +void ImGuiWrapper::text_colored(const ImVec4& color, const std::string& label) +{ + this->text_colored(color, label.c_str()); +} + +void ImGuiWrapper::text_colored(const ImVec4& color, const wxString& label) +{ + auto label_utf8 = into_u8(label); + this->text_colored(color, label_utf8.c_str()); +} + bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/) { return ImGui::SliderFloat(label, v, v_min, v_max, format, power); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index bf542e138..dc62e57a0 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -73,6 +73,9 @@ public: void text(const char *label); void text(const std::string &label); void text(const wxString &label); + void text_colored(const ImVec4& color, const char* label); + void text_colored(const ImVec4& color, const std::string& label); + void text_colored(const ImVec4& color, const wxString& label); bool slider_float(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f); bool slider_float(const std::string& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f); bool slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f); diff --git a/src/slic3r/GUI/Mouse3DController.cpp b/src/slic3r/GUI/Mouse3DController.cpp index baa9356b6..ec7cd8d45 100644 --- a/src/slic3r/GUI/Mouse3DController.cpp +++ b/src/slic3r/GUI/Mouse3DController.cpp @@ -239,8 +239,7 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const // when the user clicks on [X] or [Close] button we need to trigger // an extra frame to let the dialog disappear - if (m_settings_dialog_closed_by_user) - { + if (m_settings_dialog_closed_by_user) { m_show_settings_dialog = false; m_settings_dialog_closed_by_user = false; canvas.request_extra_frame(); @@ -261,13 +260,10 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const static ImVec2 last_win_size(0.0f, 0.0f); bool shown = true; - if (imgui.begin(_(L("3Dconnexion settings")), &shown, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse)) - { - if (shown) - { + if (imgui.begin(_L("3Dconnexion settings"), &shown, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse)) { + if (shown) { ImVec2 win_size = ImGui::GetWindowSize(); - if ((last_win_size.x != win_size.x) || (last_win_size.y != win_size.y)) - { + if (last_win_size.x != win_size.x || last_win_size.y != win_size.y) { // when the user clicks on [X] button, the next time the dialog is shown // has a dummy size, so we trigger an extra frame to let it have the correct size last_win_size = win_size; @@ -275,59 +271,51 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const } const ImVec4& color = ImGui::GetStyleColorVec4(ImGuiCol_Separator); - ImGui::PushStyleColor(ImGuiCol_Text, color); - imgui.text(_(L("Device:"))); - ImGui::PopStyleColor(); + imgui.text_colored(color, _L("Device:")); ImGui::SameLine(); imgui.text(m_device_str); ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, color); - imgui.text(_(L("Speed:"))); - ImGui::PopStyleColor(); + imgui.text_colored(color, _L("Speed:")); float translation_scale = (float)params_copy.translation.scale / Params::DefaultTranslationScale; - if (imgui.slider_float(_(L("Translation")) + "##1", &translation_scale, 0.1f, 10.0f, "%.1f")) { + if (imgui.slider_float(_L("Translation") + "##1", &translation_scale, 0.1f, 10.0f, "%.1f")) { params_copy.translation.scale = Params::DefaultTranslationScale * (double)translation_scale; params_changed = true; } float rotation_scale = params_copy.rotation.scale / Params::DefaultRotationScale; - if (imgui.slider_float(_(L("Rotation")) + "##1", &rotation_scale, 0.1f, 10.0f, "%.1f")) { + if (imgui.slider_float(_L("Rotation") + "##1", &rotation_scale, 0.1f, 10.0f, "%.1f")) { params_copy.rotation.scale = Params::DefaultRotationScale * rotation_scale; params_changed = true; } float zoom_scale = params_copy.zoom.scale / Params::DefaultZoomScale; - if (imgui.slider_float(_(L("Zoom")), &zoom_scale, 0.1f, 10.0f, "%.1f")) { + if (imgui.slider_float(_L("Zoom"), &zoom_scale, 0.1f, 10.0f, "%.1f")) { params_copy.zoom.scale = Params::DefaultZoomScale * zoom_scale; params_changed = true; } ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, color); - imgui.text(_(L("Deadzone:"))); - ImGui::PopStyleColor(); + imgui.text_colored(color, _L("Deadzone:")); float translation_deadzone = (float)params_copy.translation.deadzone; - if (imgui.slider_float(_(L("Translation")) + "/" + _(L("Zoom")), &translation_deadzone, 0.0f, (float)Params::MaxTranslationDeadzone, "%.2f")) { + if (imgui.slider_float(_L("Translation") + "/" + _L("Zoom"), &translation_deadzone, 0.0f, (float)Params::MaxTranslationDeadzone, "%.2f")) { params_copy.translation.deadzone = (double)translation_deadzone; params_changed = true; } float rotation_deadzone = params_copy.rotation.deadzone; - if (imgui.slider_float(_(L("Rotation")) + "##2", &rotation_deadzone, 0.0f, Params::MaxRotationDeadzone, "%.2f")) { + if (imgui.slider_float(_L("Rotation") + "##2", &rotation_deadzone, 0.0f, Params::MaxRotationDeadzone, "%.2f")) { params_copy.rotation.deadzone = rotation_deadzone; params_changed = true; } ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, color); - imgui.text(_(L("Options:"))); - ImGui::PopStyleColor(); + imgui.text_colored(color, _L("Options:")); bool swap_yz = params_copy.swap_yz; - if (imgui.checkbox("Swap Y/Z axes", swap_yz)) { + if (imgui.checkbox(_L("Swap Y/Z axes"), swap_yz)) { params_copy.swap_yz = swap_yz; params_changed = true; } @@ -335,25 +323,20 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT ImGui::Separator(); ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, color); - imgui.text("DEBUG:"); - imgui.text("Vectors:"); - ImGui::PopStyleColor(); + imgui.text_colored(color, "DEBUG:"); + imgui.text_colored(color, "Vectors:"); Vec3f translation = m_state.get_first_vector_of_type(State::QueueItem::TranslationType).cast(); Vec3f rotation = m_state.get_first_vector_of_type(State::QueueItem::RotationType).cast(); ImGui::InputFloat3("Translation##3", translation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat3("Rotation##3", rotation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); - ImGui::PushStyleColor(ImGuiCol_Text, color); - imgui.text("Queue size:"); - ImGui::PopStyleColor(); + imgui.text_colored(color, "Queue size:"); int input_queue_size_current[2] = { int(m_state.input_queue_size_current()), int(m_state.input_queue_max_size_achieved) }; ImGui::InputInt2("Current##4", input_queue_size_current, ImGuiInputTextFlags_ReadOnly); int input_queue_size_param = int(params_copy.input_queue_max_size); - if (ImGui::InputInt("Max size", &input_queue_size_param, 1, 1, ImGuiInputTextFlags_ReadOnly)) - { + if (ImGui::InputInt("Max size", &input_queue_size_param, 1, 1, ImGuiInputTextFlags_ReadOnly)) { if (input_queue_size_param > 0) { params_copy.input_queue_max_size = input_queue_size_param; params_changed = true; @@ -361,23 +344,19 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const } ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, color); - imgui.text("Camera:"); - ImGui::PopStyleColor(); + imgui.text_colored(color, "Camera:"); Vec3f target = wxGetApp().plater()->get_camera().get_target().cast(); ImGui::InputFloat3("Target", target.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT ImGui::Separator(); - if (imgui.button(_(L("Close")))) - { + if (imgui.button(_L("Close"))) { // the user clicked on the [Close] button m_settings_dialog_closed_by_user = true; canvas.set_as_dirty(); } } - else - { + else { // the user clicked on the [X] button m_settings_dialog_closed_by_user = true; canvas.set_as_dirty(); From 757572b760d4062f780bc9b2568f5f0a8c410763 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 3 Aug 2020 11:08:17 +0200 Subject: [PATCH 48/70] Tech ENABLE_LAYOUT_NO_RESTART set as default --- src/libslic3r/Technologies.hpp | 3 - src/slic3r/GUI/GUI_App.cpp | 13 --- src/slic3r/GUI/GUI_Utils.hpp | 22 +---- src/slic3r/GUI/MainFrame.cpp | 164 +-------------------------------- src/slic3r/GUI/MainFrame.hpp | 18 ---- src/slic3r/GUI/Preferences.cpp | 24 ----- 6 files changed, 5 insertions(+), 239 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index c6991c057..e4b71697d 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -54,8 +54,5 @@ // Enable built-in DPI changed event handler of wxWidgets 3.1.3 #define ENABLE_WX_3_1_3_DPI_CHANGED_EVENT (1 && ENABLE_2_3_0_ALPHA1) -// Enable changing application layout without the need to restart -#define ENABLE_LAYOUT_NO_RESTART (1 && ENABLE_2_3_0_ALPHA1) - #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index a7b562bd7..bfb158619 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1061,34 +1061,21 @@ void GUI_App::add_config_menu(wxMenuBar *menu) break; case ConfigMenuPreferences: { -#if ENABLE_LAYOUT_NO_RESTART bool app_layout_changed = false; -#else - bool recreate_app = false; -#endif // ENABLE_LAYOUT_NO_RESTART { // the dialog needs to be destroyed before the call to recreate_GUI() // or sometimes the application crashes into wxDialogBase() destructor // so we put it into an inner scope PreferencesDialog dlg(mainframe); dlg.ShowModal(); -#if ENABLE_LAYOUT_NO_RESTART app_layout_changed = dlg.settings_layout_changed(); -#else - recreate_app = dlg.settings_layout_changed(); -#endif // ENABLE_LAYOUT_NO_RESTART } -#if ENABLE_LAYOUT_NO_RESTART if (app_layout_changed) { mainframe->GetSizer()->Hide((size_t)0); mainframe->update_layout(); mainframe->select_tab(0); mainframe->GetSizer()->Show((size_t)0); } -#else - if (recreate_app) - recreate_GUI(_L("Changing of the settings layout") + dots); -#endif // ENABLE_LAYOUT_NO_RESTART break; } case ConfigMenuLanguage: diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 2737b3edb..6ce3f62a6 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -110,13 +110,8 @@ public: if (!m_can_rescale) return; -#if ENABLE_LAYOUT_NO_RESTART if (m_force_rescale || is_new_scale_factor()) rescale(wxRect()); -#else - if (is_new_scale_factor()) - rescale(wxRect()); -#endif // ENABLE_LAYOUT_NO_RESTART }); #else this->Bind(EVT_DPI_CHANGED_SLICER, [this](const DpiChangedEvent& evt) { @@ -127,13 +122,8 @@ public: if (!m_can_rescale) return; -#if ENABLE_LAYOUT_NO_RESTART if (m_force_rescale || is_new_scale_factor()) rescale(evt.rect); -#else - if (is_new_scale_factor()) - rescale(evt.rect); -#endif // ENABLE_LAYOUT_NO_RESTART }); #endif // wxVERSION_EQUAL_OR_GREATER_THAN @@ -175,9 +165,7 @@ public: int em_unit() const { return m_em_unit; } // int font_size() const { return m_font_size; } const wxFont& normal_font() const { return m_normal_font; } -#if ENABLE_LAYOUT_NO_RESTART void enable_force_rescale() { m_force_rescale = true; } -#endif // ENABLE_LAYOUT_NO_RESTART protected: virtual void on_dpi_changed(const wxRect &suggested_rect) = 0; @@ -191,9 +179,7 @@ private: wxFont m_normal_font; float m_prev_scale_factor; bool m_can_rescale{ true }; -#if ENABLE_LAYOUT_NO_RESTART bool m_force_rescale{ false }; -#endif // ENABLE_LAYOUT_NO_RESTART int m_new_font_point_size; @@ -233,17 +219,17 @@ private: { this->Freeze(); -#if ENABLE_LAYOUT_NO_RESTART && wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) +#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) if (m_force_rescale) { -#endif // ENABLE_LAYOUT_NO_RESTART +#endif // wxVERSION_EQUAL_OR_GREATER_THAN // rescale fonts of all controls scale_controls_fonts(this, m_new_font_point_size); // rescale current window font scale_win_font(this, m_new_font_point_size); -#if ENABLE_LAYOUT_NO_RESTART && wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) +#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) m_force_rescale = false; } -#endif // ENABLE_LAYOUT_NO_RESTART +#endif // wxVERSION_EQUAL_OR_GREATER_THAN // set normal application font as a current window font m_normal_font = this->GetFont(); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 521dcab80..628aee2aa 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -42,7 +42,6 @@ namespace Slic3r { namespace GUI { -#if ENABLE_LAYOUT_NO_RESTART enum class ERescaleTarget { Mainframe, @@ -71,15 +70,12 @@ static void rescale_dialog_after_dpi_change(MainFrame& mainframe, SettingsDialog } } } -#endif // ENABLE_LAYOUT_NO_RESTART MainFrame::MainFrame() : DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE, "mainframe"), m_printhost_queue_dlg(new PrintHostQueueDialog(this)) , m_recent_projects(9) -#if ENABLE_LAYOUT_NO_RESTART , m_settings_dialog(this) -#endif // ENABLE_LAYOUT_NO_RESTART { // Fonts were created by the DPIFrame constructor for the monitor, on which the window opened. wxGetApp().update_fonts(this); @@ -124,43 +120,15 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S m_loaded = true; -#if !ENABLE_LAYOUT_NO_RESTART -#ifdef __APPLE__ - // Using SetMinSize() on Mac messes up the window position in some cases - // cf. https://groups.google.com/forum/#!topic/wx-users/yUKPBBfXWO0 - // So, if we haven't possibility to set MinSize() for the MainFrame, - // set the MinSize() as a half of regular for the m_plater and m_tabpanel, when settings layout is in slNew mode - // Otherwise, MainFrame will be maximized by height - if (slNew) { - wxSize size = wxGetApp().get_min_size(); - size.SetHeight(int(0.5*size.GetHeight())); - m_plater->SetMinSize(size); - m_tabpanel->SetMinSize(size); - } -#endif -#endif // !ENABLE_LAYOUT_NO_RESTART - // initialize layout m_main_sizer = new wxBoxSizer(wxVERTICAL); wxSizer* sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(m_main_sizer, 1, wxEXPAND); -#if ENABLE_LAYOUT_NO_RESTART SetSizer(sizer); // initialize layout from config update_layout(); sizer->SetSizeHints(this); Fit(); -#else - if (m_plater && m_layout != slOld) - sizer->Add(m_plater, 1, wxEXPAND); - - if (m_tabpanel && m_layout != slDlg) - sizer->Add(m_tabpanel, 1, wxEXPAND); - - sizer->SetSizeHints(this); - SetSizer(sizer); - Fit(); -#endif // !ENABLE_LAYOUT_NO_RESTART const wxSize min_size = wxGetApp().get_min_size(); //wxSize(76*wxGetApp().em_unit(), 49*wxGetApp().em_unit()); #ifdef __APPLE__ @@ -252,12 +220,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S }); wxGetApp().persist_window_geometry(this, true); -#if ENABLE_LAYOUT_NO_RESTART wxGetApp().persist_window_geometry(&m_settings_dialog, true); -#else - if (m_settings_dialog != nullptr) - wxGetApp().persist_window_geometry(m_settings_dialog, true); -#endif // ENABLE_LAYOUT_NO_RESTART update_ui_from_settings(); // FIXME (?) @@ -265,7 +228,6 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S m_plater->show_action_buttons(true); } -#if ENABLE_LAYOUT_NO_RESTART void MainFrame::update_layout() { auto restore_to_creation = [this]() { @@ -380,7 +342,6 @@ void MainFrame::update_layout() Layout(); Thaw(); } -#endif // ENABLE_LAYOUT_NO_RESTART // Called when closing the application and when switching the application language. void MainFrame::shutdown() @@ -414,20 +375,9 @@ void MainFrame::shutdown() // In addition, there were some crashes due to the Paint events sent to already destructed windows. this->Show(false); -#if ENABLE_LAYOUT_NO_RESTART if (m_settings_dialog.IsShown()) // call Close() to trigger call to lambda defined into GUI_App::persist_window_geometry() m_settings_dialog.Close(); -#else - if (m_settings_dialog != nullptr) - { - if (m_settings_dialog->IsShown()) - // call Close() to trigger call to lambda defined into GUI_App::persist_window_geometry() - m_settings_dialog->Close(); - - m_settings_dialog->Destroy(); - } -#endif // ENABLE_LAYOUT_NO_RESTART // Stop the background thread (Windows and Linux). // Disconnect from a 3DConnextion driver (OSX). @@ -486,7 +436,6 @@ void MainFrame::update_title() void MainFrame::init_tabpanel() { -#if ENABLE_LAYOUT_NO_RESTART // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10 // with multiple high resolution displays connected. m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); @@ -495,27 +444,6 @@ void MainFrame::init_tabpanel() #endif m_tabpanel->Hide(); m_settings_dialog.set_tabpanel(m_tabpanel); -#else - m_layout = wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? slOld : - wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? slNew : - wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? slDlg : slOld; - - // From the very beginning the Print settings should be selected - m_last_selected_tab = m_layout == slDlg ? 0 : 1; - - if (m_layout == slDlg) { - m_settings_dialog = new SettingsDialog(this); - m_tabpanel = m_settings_dialog->get_tabpanel(); - } - else { - // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10 - // with multiple high resolution displays connected. - m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); -#ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList - m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font()); -#endif - } -#endif // ENABLE_LAYOUT_NO_RESTART m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxEvent&) { wxWindow* panel = m_tabpanel->GetCurrentPage(); @@ -536,20 +464,9 @@ void MainFrame::init_tabpanel() select_tab(0); // select Plater }); -#if ENABLE_LAYOUT_NO_RESTART m_plater = new Plater(this, this); m_plater->Hide(); -#else - if (m_layout == slOld) { - m_plater = new Plater(m_tabpanel, this); - m_tabpanel->AddPage(m_plater, _L("Plater")); - } - else { - m_plater = new Plater(this, this); - if (m_layout == slNew) - m_tabpanel->AddPage(new wxPanel(m_tabpanel), _L("Plater")); // empty panel just for Plater tab - } -#endif // ENABLE_LAYOUT_NO_RESTART + wxGetApp().plater_ = m_plater; wxGetApp().obj_list()->create_popup_menus(); @@ -691,7 +608,6 @@ bool MainFrame::can_slice() const bool MainFrame::can_change_view() const { -#if ENABLE_LAYOUT_NO_RESTART switch (m_layout) { default: { return false; } @@ -702,15 +618,6 @@ bool MainFrame::can_change_view() const return page_id != wxNOT_FOUND && dynamic_cast(m_tabpanel->GetPage((size_t)page_id)) != nullptr; } } -#else - if (m_layout == slNew) - return m_plater->IsShown(); - if (m_layout == slDlg) - return true; - // slOld layout mode - int page_id = m_tabpanel->GetSelection(); - return page_id != wxNOT_FOUND && dynamic_cast(m_tabpanel->GetPage((size_t)page_id)) != nullptr; -#endif // ENABLE_LAYOUT_NO_RESTART } bool MainFrame::can_select() const @@ -756,11 +663,7 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect) wxGetApp().plater()->msw_rescale(); // update Tabs -#if ENABLE_LAYOUT_NO_RESTART if (m_layout != ESettingsLayout::Dlg) // Do not update tabs if the Settings are in the separated dialog -#else - if (m_layout != slDlg) // Update tabs later, from the SettingsDialog, when the Settings are in the separated dialog -#endif // ENABLE_LAYOUT_NO_RESTART for (auto tab : wxGetApp().tabs_list) tab->msw_rescale(); @@ -789,10 +692,8 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect) this->Maximize(is_maximized); -#if ENABLE_LAYOUT_NO_RESTART if (m_layout == ESettingsLayout::Dlg) rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::SettingsDialog); -#endif // ENABLE_LAYOUT_NO_RESTART } void MainFrame::on_sys_color_changed() @@ -1528,25 +1429,15 @@ void MainFrame::load_config(const DynamicPrintConfig& config) void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) { bool tabpanel_was_hidden = false; -#if ENABLE_LAYOUT_NO_RESTART if (m_layout == ESettingsLayout::Dlg) { -#else - if (m_layout == slDlg) { -#endif // ENABLE_LAYOUT_NO_RESTART if (tab==0) { -#if ENABLE_LAYOUT_NO_RESTART if (m_settings_dialog.IsShown()) this->SetFocus(); -#else - if (m_settings_dialog->IsShown()) - this->SetFocus(); -#endif // ENABLE_LAYOUT_NO_RESTART // plater should be focused for correct navigation inside search window if (m_plater->canvas3D()->is_search_pressed()) m_plater->SetFocus(); return; } -#if ENABLE_LAYOUT_NO_RESTART // Show/Activate Settings Dialog #ifdef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList if (m_settings_dialog.IsShown()) @@ -1563,28 +1454,11 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) m_settings_dialog.Show(); } #endif -#else - // Show/Activate Settings Dialog - if (m_settings_dialog->IsShown()) -#ifdef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList - m_settings_dialog->Hide(); -#else - m_settings_dialog->SetFocus(); - else -#endif - m_settings_dialog->Show(); -#endif // ENABLE_LAYOUT_NO_RESTART } -#if ENABLE_LAYOUT_NO_RESTART else if (m_layout == ESettingsLayout::New) { m_main_sizer->Show(m_plater, tab == 0); tabpanel_was_hidden = !m_main_sizer->IsShown(m_tabpanel); m_main_sizer->Show(m_tabpanel, tab != 0); -#else - else if (m_layout == slNew) { - m_plater->Show(tab == 0); - m_tabpanel->Show(tab != 0); -#endif // ENABLE_LAYOUT_NO_RESTART // plater should be focused for correct navigation inside search window if (tab == 0 && m_plater->canvas3D()->is_search_pressed()) @@ -1601,11 +1475,7 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) tab->update_changed_tree_ui(); // when tab == -1, it means we should show the last selected tab -#if ENABLE_LAYOUT_NO_RESTART m_tabpanel->SetSelection(tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == ESettingsLayout::Dlg && tab != 0) ? tab - 1 : tab); -#else - m_tabpanel->SetSelection(tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == slDlg && tab != 0) ? tab-1 : tab); -#endif // ENABLE_LAYOUT_NO_RESTART } // Set a camera direction, zoom to all objects. @@ -1734,34 +1604,6 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) SetIcon(wxIcon(var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); #endif // _WIN32 -#if !ENABLE_LAYOUT_NO_RESTART - // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10 - // with multiple high resolution displays connected. - m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxGetApp().get_min_size(), wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); -#ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList - m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font()); -#endif - - m_tabpanel->Bind(wxEVT_KEY_UP, [this](wxKeyEvent& evt) { - if ((evt.GetModifiers() & wxMOD_CONTROL) != 0) { - switch (evt.GetKeyCode()) { - case '1': { m_main_frame->select_tab(0); break; } - case '2': { m_main_frame->select_tab(1); break; } - case '3': { m_main_frame->select_tab(2); break; } - case '4': { m_main_frame->select_tab(3); break; } -#ifdef __APPLE__ - case 'f': -#else /* __APPLE__ */ - case WXK_CONTROL_F: -#endif /* __APPLE__ */ - case 'F': { m_main_frame->plater()->search(false); break; } - default:break; - } - } - }); -#endif // !ENABLE_LAYOUT_NO_RESTART - -#if ENABLE_LAYOUT_NO_RESTART this->Bind(wxEVT_SHOW, [this](wxShowEvent& evt) { auto key_up_handker = [this](wxKeyEvent& evt) { @@ -1791,13 +1633,9 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) m_tabpanel->Unbind(wxEVT_KEY_UP, key_up_handker); } }); -#endif // ENABLE_LAYOUT_NO_RESTART // initialize layout auto sizer = new wxBoxSizer(wxVERTICAL); -#if !ENABLE_LAYOUT_NO_RESTART - sizer->Add(m_tabpanel, 1, wxEXPAND); -#endif // !ENABLE_LAYOUT_NO_RESTART sizer->SetSizeHints(this); SetSizer(sizer); Fit(); diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 4514b8f50..3c93f6b58 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -55,11 +55,7 @@ class SettingsDialog : public DPIDialog public: SettingsDialog(MainFrame* mainframe); ~SettingsDialog() {} -#if ENABLE_LAYOUT_NO_RESTART void set_tabpanel(wxNotebook* tabpanel) { m_tabpanel = tabpanel; } -#else - wxNotebook* get_tabpanel() { return m_tabpanel; } -#endif // ENABLE_LAYOUT_NO_RESTART protected: void on_dpi_changed(const wxRect& suggested_rect) override; @@ -119,7 +115,6 @@ class MainFrame : public DPIFrame wxFileHistory m_recent_projects; -#if ENABLE_LAYOUT_NO_RESTART enum class ESettingsLayout { Unknown, @@ -129,13 +124,6 @@ class MainFrame : public DPIFrame }; ESettingsLayout m_layout{ ESettingsLayout::Unknown }; -#else - enum SettingsLayout { - slOld = 0, - slNew, - slDlg, - } m_layout; -#endif // ENABLE_LAYOUT_NO_RESTART protected: virtual void on_dpi_changed(const wxRect &suggested_rect); @@ -145,9 +133,7 @@ public: MainFrame(); ~MainFrame() = default; -#if ENABLE_LAYOUT_NO_RESTART void update_layout(); -#endif // ENABLE_LAYOUT_NO_RESTART // Called when closing the application and when switching the application language. void shutdown(); @@ -190,12 +176,8 @@ public: Plater* m_plater { nullptr }; wxNotebook* m_tabpanel { nullptr }; -#if ENABLE_LAYOUT_NO_RESTART SettingsDialog m_settings_dialog; wxWindow* m_plater_page{ nullptr }; -#else - SettingsDialog* m_settings_dialog { nullptr }; -#endif // ENABLE_LAYOUT_NO_RESTART wxProgressDialog* m_progress_dialog { nullptr }; std::shared_ptr m_statusbar; diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 02e4a899d..4b5808e16 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -234,30 +234,6 @@ void PreferencesDialog::accept() } } -#if !ENABLE_LAYOUT_NO_RESTART - if (m_settings_layout_changed) { - // the dialog needs to be destroyed before the call to recreate_gui() - // or sometimes the application crashes into wxDialogBase() destructor - // so we put it into an inner scope - wxMessageDialog dialog(nullptr, - _L("Switching the settings layout mode will trigger application restart.\n" - "You will lose content of the plater.") + "\n\n" + - _L("Do you want to proceed?"), - wxString(SLIC3R_APP_NAME) + " - " + _L("Switching the settings layout mode"), - wxICON_QUESTION | wxOK | wxCANCEL); - - if (dialog.ShowModal() == wxID_CANCEL) - { - 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; - - m_layout_mode_box->SetSelection(selection); - return; - } - } -#endif // !ENABLE_LAYOUT_NO_RESTART - for (std::map::iterator it = m_values.begin(); it != m_values.end(); ++it) app_config->set(it->first, it->second); From 4bfb69eb1482238e6ee975e905ae49b4b3acb106 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 3 Aug 2020 12:20:46 +0200 Subject: [PATCH 49/70] Added an icon for 'ironing' category --- resources/icons/ironing.svg | 27 +++++++++++++++++++++++++++ src/slic3r/GUI/GUI_ObjectList.cpp | 4 ++-- 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 resources/icons/ironing.svg diff --git a/resources/icons/ironing.svg b/resources/icons/ironing.svg new file mode 100644 index 000000000..94917d6bf --- /dev/null +++ b/resources/icons/ironing.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index f3ff264ce..c10853f69 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -94,7 +94,7 @@ ObjectList::ObjectList(wxWindow* parent) : // ptFFF CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers"); CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill"); - CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("infill"); // FIXME when the ironing icon is available + CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("ironing"); CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support"); CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time"); CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel"); @@ -645,7 +645,7 @@ void ObjectList::msw_rescale_icons() // ptFFF CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers"); CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill"); - CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("infill"); // FIXME when the ironing icon is available + CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("ironing"); CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support"); CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time"); CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel"); From b3f8ae5ca75b52ef74e8da2be2392ea8339dcab2 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 3 Aug 2020 15:36:55 +0200 Subject: [PATCH 50/70] Notifications & warning dialog notifications dialog with warnings produced by slicing is shown before exporting --- resources/icons/cancel.svg | 10 + resources/icons/cross_focus_large.svg | 81 ++ resources/icons/timer_dot.svg | 72 ++ resources/icons/timer_dot_empty.svg | 73 ++ src/imgui/imconfig.h | 7 +- src/libslic3r/GCode.cpp | 1 + src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/BackgroundSlicingProcess.cpp | 10 +- src/slic3r/GUI/BackgroundSlicingProcess.hpp | 7 + src/slic3r/GUI/GLCanvas3D.cpp | 41 +- src/slic3r/GUI/GUI_App.cpp | 10 +- src/slic3r/GUI/GUI_App.hpp | 3 + src/slic3r/GUI/ImGuiWrapper.cpp | 32 +- src/slic3r/GUI/ImGuiWrapper.hpp | 3 + src/slic3r/GUI/Mouse3DController.cpp | 3 + src/slic3r/GUI/NotificationManager.cpp | 918 ++++++++++++++++++++ src/slic3r/GUI/NotificationManager.hpp | 268 ++++++ src/slic3r/GUI/Plater.cpp | 225 +++-- src/slic3r/GUI/Plater.hpp | 7 +- src/slic3r/Utils/PresetUpdater.cpp | 101 ++- src/slic3r/Utils/PresetUpdater.hpp | 6 +- 21 files changed, 1791 insertions(+), 89 deletions(-) create mode 100644 resources/icons/cancel.svg create mode 100644 resources/icons/cross_focus_large.svg create mode 100644 resources/icons/timer_dot.svg create mode 100644 resources/icons/timer_dot_empty.svg create mode 100644 src/slic3r/GUI/NotificationManager.cpp create mode 100644 src/slic3r/GUI/NotificationManager.hpp diff --git a/resources/icons/cancel.svg b/resources/icons/cancel.svg new file mode 100644 index 000000000..da44606a0 --- /dev/null +++ b/resources/icons/cancel.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/resources/icons/cross_focus_large.svg b/resources/icons/cross_focus_large.svg new file mode 100644 index 000000000..c246f2bd9 --- /dev/null +++ b/resources/icons/cross_focus_large.svg @@ -0,0 +1,81 @@ + +image/svg+xml + + + + + + + + + + + diff --git a/resources/icons/timer_dot.svg b/resources/icons/timer_dot.svg new file mode 100644 index 000000000..3a77962b6 --- /dev/null +++ b/resources/icons/timer_dot.svg @@ -0,0 +1,72 @@ + +image/svg+xml + + + + diff --git a/resources/icons/timer_dot_empty.svg b/resources/icons/timer_dot_empty.svg new file mode 100644 index 000000000..a8e776b49 --- /dev/null +++ b/resources/icons/timer_dot_empty.svg @@ -0,0 +1,73 @@ + + + +image/svg+xml + + + + \ No newline at end of file diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index d32f64aa4..feda857ae 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -113,7 +113,12 @@ namespace ImGui const char PrinterSlaIconMarker = 0x6; const char FilamentIconMarker = 0x7; const char MaterialIconMarker = 0x8; - + const char CloseIconMarker = 0xB; + const char CloseIconHoverMarker = 0xC; + const char TimerDotMarker = 0xE; + const char TimerDotEmptyMarker = 0xF; + const char WarningMarker = 0x10; + const char ErrorMarker = 0x11; // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 35dc5a53b..7d8067718 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -686,6 +686,7 @@ std::vector>> GCode::collec std::sort(ordering.begin(), ordering.end(), [](const OrderingItem &oi1, const OrderingItem &oi2) { return oi1.print_z < oi2.print_z; }); std::vector>> layers_to_print; + // Merge numerically very close Z values. for (size_t i = 0; i < ordering.size();) { // Find the last layer with roughly the same print_z. diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 7e02c0fdd..57e84f71e 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -163,6 +163,8 @@ set(SLIC3R_GUI_SOURCES GUI/InstanceCheck.hpp GUI/Search.cpp GUI/Search.hpp + GUI/NotificationManager.cpp + GUI/NotificationManager.hpp Utils/Http.cpp Utils/Http.hpp Utils/FixModelByWin10.cpp diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 8d50998c4..7309654a8 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -89,11 +89,13 @@ void BackgroundSlicingProcess::process_fff() { assert(m_print == m_fff_print); m_print->process(); - wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_slicing_completed_id)); + wxCommandEvent evt(m_event_slicing_completed_id); + evt.SetInt((int)(m_fff_print->step_state_with_timestamp(PrintStep::psBrim).timestamp)); + wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone()); m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb); - if (this->set_step_started(bspsGCodeFinalize)) { if (! m_export_path.empty()) { + wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); //FIXME localize the messages // Perform the final post-processing of the export path by applying the print statistics over the file name. std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path); @@ -124,6 +126,7 @@ void BackgroundSlicingProcess::process_fff() run_post_process_scripts(export_path, m_fff_print->config()); m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str()); } else if (! m_upload_job.empty()) { + wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); prepare_upload(); } else { m_print->set_status(100, _utf8(L("Slicing complete"))); @@ -149,6 +152,8 @@ void BackgroundSlicingProcess::process_sla() m_print->process(); if (this->set_step_started(bspsGCodeFinalize)) { if (! m_export_path.empty()) { + wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); + const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path); Zipper zipper(export_path); @@ -170,6 +175,7 @@ void BackgroundSlicingProcess::process_sla() m_print->set_status(100, (boost::format(_utf8(L("Masked SLA file exported to %1%"))) % export_path).str()); } else if (! m_upload_job.empty()) { + wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); prepare_upload(); } else { m_print->set_status(100, _utf8(L("Slicing complete"))); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index 38e9e1075..c4672f1b4 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -60,6 +60,10 @@ public: // The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code export is finished. // The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed. void set_finished_event(int event_id) { m_event_finished_id = event_id; } + // The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code is being exported to + // specified path or uploaded. + // The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed. + void set_export_began_event(int event_id) { m_event_export_began_id = event_id; } // Activate either m_fff_print or m_sla_print. // Return true if changed. @@ -190,6 +194,9 @@ private: int m_event_slicing_completed_id = 0; // wxWidgets command ID to be sent to the plater to inform that the task finished. int m_event_finished_id = 0; + // wxWidgets command ID to be sent to the plater to inform that the G-code is being exported. + int m_event_export_began_id = 0; + }; }; // namespace Slic3r diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 5109d2426..1edd7aa2b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -31,6 +31,7 @@ #include "GUI_ObjectManipulation.hpp" #include "Mouse3DController.hpp" #include "I18N.hpp" +#include "NotificationManager.hpp" #if ENABLE_RETINA_GL #include "slic3r/Utils/RetinaHelper.hpp" @@ -651,19 +652,45 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool m_warnings.emplace_back(warning); std::sort(m_warnings.begin(), m_warnings.end()); + + std::string text; + switch (warning) { + case ObjectOutside: text = L("An object outside the print area was detected."); break; + case ToolpathOutside: text = L("A toolpath outside the print area was detected."); break; + case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); break; + case SomethingNotShown: text = L("Some objects are not visible."); break; + case ObjectClashed: wxGetApp().plater()->get_notification_manager()->push_plater_error_notification(L("An object outside the print area was detected.\n" + "Resolve the current problem to continue slicing."), + *(wxGetApp().plater()->get_current_canvas3D())); + break; + } + if (!text.empty()) + wxGetApp().plater()->get_notification_manager()->push_plater_warning_notification(text, *(wxGetApp().plater()->get_current_canvas3D())); } else { if (it == m_warnings.end()) // deactivating something that is not active is an easy task return; m_warnings.erase(it); - if (m_warnings.empty()) { // nothing remains to be shown + + std::string text; + switch (warning) { + case ObjectOutside: text = L("An object outside the print area was detected."); break; + case ToolpathOutside: text = L("A toolpath outside the print area was detected."); break; + case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); break; + case SomethingNotShown: text = L("Some objects are not visibl.e"); break; + case ObjectClashed: wxGetApp().plater()->get_notification_manager()->close_plater_error_notification(); break; + } + if (!text.empty()) + wxGetApp().plater()->get_notification_manager()->close_plater_warning_notification(text); + + /*if (m_warnings.empty()) { // nothing remains to be shown reset(); m_msg_text = "";// save information for rescaling return; - } + }*/ } - + /* // Look at the end of our vector and generate proper texture. std::string text; bool red_colored = false; @@ -685,6 +712,7 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool // save information for rescaling m_msg_text = text; m_is_colored_red = red_colored; + */ } @@ -2074,6 +2102,8 @@ void GLCanvas3D::render() std::string tooltip; + + // Negative coordinate means out of the window, likely because the window was deactivated. // In that case the tooltip should be hidden. if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.) @@ -2103,6 +2133,8 @@ void GLCanvas3D::render() m_tooltip.render(m_mouse.position, *this); wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this); + + wxGetApp().plater()->get_notification_manager()->render_notifications(*this); wxGetApp().imgui()->render(); @@ -3418,6 +3450,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) #ifdef SLIC3R_DEBUG_MOUSE_EVENTS printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str()); #endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ + m_dirty = true; // do not return if dragging or tooltip not empty to allow for tooltip update if (!m_mouse.dragging && m_tooltip.is_empty()) return; @@ -3811,7 +3844,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_gizmos.reset_all_states(); // Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over. - if (m_picking_enabled) + //if (m_picking_enabled) m_dirty = true; } else diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index bfb158619..49d08565a 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -54,6 +54,7 @@ #include "Mouse3DController.hpp" #include "RemovableDriveManager.hpp" #include "InstanceCheck.hpp" +#include "NotificationManager.hpp" #ifdef __WXMSW__ #include @@ -384,7 +385,7 @@ bool GUI_App::on_init_inner() // supplied as argument to --datadir; in that case we should still run the wizard preset_bundle->setup_directories(); -#ifdef __WXMSW__ +#ifdef __WXMSW__ associate_3mf_files(); #endif // __WXMSW__ @@ -392,6 +393,11 @@ bool GUI_App::on_init_inner() Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent &evt) { app_config->set("version_online", into_u8(evt.GetString())); app_config->save(); + if(this->plater_ != nullptr) { + if (*Semver::parse(SLIC3R_VERSION) < * Semver::parse(into_u8(evt.GetString()))) { + this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAviable, *(this->plater_->get_current_canvas3D())); + } + } }); // initialize label colors and fonts @@ -1439,7 +1445,7 @@ void GUI_App::check_updates(const bool verbose) PresetUpdater::UpdateResult updater_result; try { - updater_result = preset_updater->config_update(app_config->orig_version()); + updater_result = preset_updater->config_update(app_config->orig_version(), verbose); if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) { mainframe->Close(); } diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index c2b257f45..922d6173d 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -194,12 +194,15 @@ public: Plater* plater(); Model& model(); + AppConfig* app_config{ nullptr }; PresetBundle* preset_bundle{ nullptr }; PresetUpdater* preset_updater{ nullptr }; MainFrame* mainframe{ nullptr }; Plater* plater_{ nullptr }; + PresetUpdater* get_preset_updater() { return preset_updater; } + wxNotebook* tab_panel() const ; int extruders_cnt() const; int extruders_edited_cnt() const; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index a21194d94..266472eca 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -37,11 +37,17 @@ namespace GUI { static const std::map font_icons = { - {ImGui::PrintIconMarker , "cog" }, - {ImGui::PrinterIconMarker , "printer" }, - {ImGui::PrinterSlaIconMarker, "sla_printer"}, - {ImGui::FilamentIconMarker , "spool" }, - {ImGui::MaterialIconMarker , "resin" } + {ImGui::PrintIconMarker , "cog" }, + {ImGui::PrinterIconMarker , "printer" }, + {ImGui::PrinterSlaIconMarker, "sla_printer" }, + {ImGui::FilamentIconMarker , "spool" }, + {ImGui::MaterialIconMarker , "resin" }, + {ImGui::CloseIconMarker , "cross" }, + {ImGui::CloseIconHoverMarker, "cross_focus_large" }, + {ImGui::TimerDotMarker , "timer_dot" }, + {ImGui::TimerDotEmptyMarker , "timer_dot_empty" }, + {ImGui::WarningMarker , "flag_green" }, + {ImGui::ErrorMarker , "flag_red" } }; ImGuiWrapper::ImGuiWrapper() @@ -265,6 +271,11 @@ void ImGuiWrapper::set_next_window_bg_alpha(float alpha) ImGui::SetNextWindowBgAlpha(alpha); } +void ImGuiWrapper::set_next_window_size(float x, float y, ImGuiCond cond) +{ + ImGui::SetNextWindowSize(ImVec2(x, y), cond); +} + bool ImGuiWrapper::begin(const std::string &name, int flags) { return ImGui::Begin(name.c_str(), nullptr, (ImGuiWindowFlags)flags); @@ -296,12 +307,23 @@ bool ImGuiWrapper::button(const wxString &label) return ImGui::Button(label_utf8.c_str()); } +bool ImGuiWrapper::button(const wxString& label, float width, float height) +{ + auto label_utf8 = into_u8(label); + return ImGui::Button(label_utf8.c_str(), ImVec2(width, height)); +} + bool ImGuiWrapper::radio_button(const wxString &label, bool active) { auto label_utf8 = into_u8(label); return ImGui::RadioButton(label_utf8.c_str(), active); } +bool ImGuiWrapper::image_button() +{ + return false; +} + bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format) { return ImGui::InputDouble(label.c_str(), const_cast(&value), 0.0f, 0.0f, format.c_str()); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index dc62e57a0..ee553c4b6 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -57,6 +57,7 @@ public: void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f); void set_next_window_bg_alpha(float alpha); + void set_next_window_size(float x, float y, ImGuiCond cond); bool begin(const std::string &name, int flags = 0); bool begin(const wxString &name, int flags = 0); @@ -65,7 +66,9 @@ public: void end(); bool button(const wxString &label); + bool button(const wxString& label, float width, float height); bool radio_button(const wxString &label, bool active); + bool image_button(); bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f"); bool input_double(const wxString &label, const double &value, const std::string &format = "%.3f"); bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f"); diff --git a/src/slic3r/GUI/Mouse3DController.cpp b/src/slic3r/GUI/Mouse3DController.cpp index ec7cd8d45..33f0d6379 100644 --- a/src/slic3r/GUI/Mouse3DController.cpp +++ b/src/slic3r/GUI/Mouse3DController.cpp @@ -7,6 +7,7 @@ #include "AppConfig.hpp" #include "GLCanvas3D.hpp" #include "Plater.hpp" +#include "NotificationManager.hpp" #include @@ -403,6 +404,8 @@ void Mouse3DController::disconnected() m_params_by_device[m_device_str] = m_params_ui; m_device_str.clear(); m_connected = false; + wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::Mouse3dDisconnected, *(wxGetApp().plater()->get_current_canvas3D())); + wxGetApp().plater()->CallAfter([]() { Plater *plater = wxGetApp().plater(); if (plater != nullptr) { diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp new file mode 100644 index 000000000..b7301f3d8 --- /dev/null +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -0,0 +1,918 @@ +#include "NotificationManager.hpp" + +#include "GUI_App.hpp" +#include "Plater.hpp" +#include "GLCanvas3D.hpp" +#include "ImGuiWrapper.hpp" + +#include "wxExtensions.hpp" + +#include +#include +#include +#include + + + + +#define NOTIFICATION_MAX_MOVE 3.0f + +#define GAP_WIDTH 10.0f +#define SPACE_RIGHT_PANEL 10.0f + +namespace Slic3r { +namespace GUI { + +wxDEFINE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClickedEvent); +wxDEFINE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent); +wxDEFINE_EVENT(EVT_PRESET_UPDATE_AVIABLE_CLICKED, PresetUpdateAviableClickedEvent); + +namespace Notifications_Internal{ + void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, float current_fade_opacity) + { + if (fading_out) + ImGui::PushStyleColor(idx, ImVec4(col.x, col.y, col.z, col.w * current_fade_opacity)); + else + ImGui::PushStyleColor(idx, col); + } +} +//ScalableBitmap bmp_icon; +//------PopNotification-------- +NotificationManager::PopNotification::PopNotification(const NotificationData &n, const int id, wxEvtHandler* evt_handler) : + m_data (n) + , m_id (id) + , m_remaining_time (n.duration) + , m_last_remaining_time (n.duration) + , m_counting_down (n.duration != 0) + , m_text1 (n.text1) + , m_hypertext (n.hypertext) + , m_text2 (n.text2) + , m_evt_handler (evt_handler) +{ + init(); +} +NotificationManager::PopNotification::~PopNotification() +{ +} +NotificationManager::PopNotification::RenderResult NotificationManager::PopNotification::render(GLCanvas3D& canvas, const float& initial_y) +{ + if (m_finished) + return RenderResult::Finished; + if (m_close_pending) { + // request of extra frame will be done in caller function by ret val ClosePending + m_finished = true; + return RenderResult::ClosePending; + } + if (m_hidden) { + m_top_y = initial_y - GAP_WIDTH; + return RenderResult::Static; + } + RenderResult ret_val = m_counting_down ? RenderResult::Countdown : RenderResult::Static; + Size cnv_size = canvas.get_canvas_size(); + ImGuiWrapper& imgui = *wxGetApp().imgui(); + bool shown = true; + std::string name; + ImVec2 mouse_pos = ImGui::GetMousePos(); + + if (m_line_height != ImGui::CalcTextSize("A").y) + init(); + + set_next_window_size(imgui); + + //top y of window + m_top_y = initial_y + m_window_height; + //top right position + ImVec2 win_pos(1.0f * (float)cnv_size.get_width() - SPACE_RIGHT_PANEL, 1.0f * (float)cnv_size.get_height() - m_top_y); + imgui.set_next_window_pos(win_pos.x, win_pos.y, ImGuiCond_Always, 1.0f, 0.0f); + imgui.set_next_window_size(m_window_width, m_window_height, ImGuiCond_Always); + + //find if hovered + if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y&& mouse_pos.y < win_pos.y + m_window_height) + { + ImGui::SetNextWindowFocus(); + ret_val = RenderResult::Hovered; + //reset fading + m_fading_out = false; + m_current_fade_opacity = 1.f; + m_remaining_time = m_data.duration; + m_countdown_frame = 0; + } + + if (m_counting_down && m_remaining_time < 0) + m_close_pending = true; + + if (m_close_pending) { + // request of extra frame will be done in caller function by ret val ClosePending + m_finished = true; + return RenderResult::ClosePending; + } + + // color change based on fading out + bool fading_pop = false; + if (m_fading_out) { + if (!m_paused) + m_current_fade_opacity -= 1.f / ((m_fading_time + 1.f) * 60.f); + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity); + fading_pop = true; + } + // background color + if (m_is_gray) { + ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f); + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + } else if (m_data.level == NotificationLevel::ErrorNotification) { + ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); + backcolor.x += 0.3f; + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + } else if (m_data.level == NotificationLevel::WarningNotification) { + ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); + backcolor.x += 0.3f; + backcolor.y += 0.15f; + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + } + + //name of window - probably indentifies window and is shown so last_end add whitespaces according to id + for (size_t i = 0; i < m_id; i++) + name += " "; + if (imgui.begin(name, &shown, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar )) { + if (shown) { + + ImVec2 win_size = ImGui::GetWindowSize(); + + + //FIXME: dont forget to us this for texts + //GUI::format(_utf8(L())); + + /* + //countdown numbers + ImGui::SetCursorPosX(15); + ImGui::SetCursorPosY(15); + imgui.text(std::to_string(m_remaining_time).c_str()); + */ + if(m_counting_down) + render_countdown(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); + render_left_sign(imgui); + render_text(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); + render_close_button(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); + if (m_multiline && m_lines_count > 3) + render_minimize_button(imgui, win_pos.x, win_pos.y); + } else { + // the user clicked on the [X] button ( ImGuiWindowFlags_NoTitleBar means theres no [X] button) + m_close_pending = true; + canvas.set_as_dirty(); + } + } + imgui.end(); + + if (fading_pop) { + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + } + if (m_is_gray) + ImGui::PopStyleColor(); + else if (m_data.level == NotificationLevel::ErrorNotification) + ImGui::PopStyleColor(); + else if (m_data.level == NotificationLevel::WarningNotification) + ImGui::PopStyleColor(); + return ret_val; +} +void NotificationManager::PopNotification::init() +{ + std::string text = m_text1 + " " + m_hypertext; + int last_end = 0; + m_lines_count = 0; + + //determine line width + m_line_height = ImGui::CalcTextSize("A").y; + + m_left_indentation = m_line_height; + if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) { + std::string text; + text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); + float picture_width = ImGui::CalcTextSize(text.c_str()).x; + m_left_indentation = picture_width + m_line_height / 2; + } + m_window_width_offset = m_left_indentation + m_line_height * 2; + m_window_width = m_line_height * 25; + + // count lines + m_endlines.clear(); + while (last_end < text.length() - 1) + { + int next_hard_end = text.find_first_of('\n', last_end); + if (next_hard_end > 0 && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset) { + //next line is ended by '/n' + m_endlines.push_back(next_hard_end); + last_end = next_hard_end + 1; + } + else { + // find next suitable endline + if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - 3.5f * m_line_height) {// m_window_width_offset) { + // more than one line till end + int next_space = text.find_first_of(' ', last_end); + if (next_space > 0) { + int next_space_candidate = text.find_first_of(' ', next_space + 1); + while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) { + next_space = next_space_candidate; + next_space_candidate = text.find_first_of(' ', next_space + 1); + } + m_endlines.push_back(next_space); + last_end = next_space + 1; + } + } + else { + m_endlines.push_back(text.length()); + last_end = text.length(); + } + + } + m_lines_count++; + } +} +void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui) +{ + if (m_multiline) { + m_window_height = m_lines_count * m_line_height; + }else + { + m_window_height = 2 * m_line_height; + } + m_window_height += 1 * m_line_height; // top and bottom +} + +void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + float x_offset = m_left_indentation; + std::string fulltext = m_text1 + m_hypertext; //+ m_text2; + ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str()); + // text posistions are calculated by lines count + // large texts has "more" button or are displayed whole + // smaller texts are divided as one liners and two liners + if (m_lines_count > 2) { + if (m_multiline) { + + int last_end = 0; + float starting_y = m_line_height/2;//10; + float shift_y = m_line_height;// -m_line_height / 20; + for (size_t i = 0; i < m_lines_count; i++) { + std::string line = m_text1.substr(last_end , m_endlines[i] - last_end); + last_end = m_endlines[i] + 1; + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(starting_y + i * shift_y); + imgui.text(line.c_str()); + } + //hyperlink text + if (!m_hypertext.empty()) + { + render_hypertext(imgui, x_offset + ImGui::CalcTextSize(m_text1.substr(m_endlines[m_lines_count - 2] + 1, m_endlines[m_lines_count - 1] - m_endlines[m_lines_count - 2] - 1).c_str()).x, starting_y + (m_lines_count - 1) * shift_y, m_hypertext); + } + + + } else { + // line1 + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); + imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + // line2 + std::string line = m_text1.substr(m_endlines[0] + 1, m_endlines[1] - m_endlines[0] - 1); + if (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x) + { + line = line.substr(0, line.length() - 6); + line += ".."; + }else + line += " "; + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(win_size.y / 2 + win_size.y / 6 - m_line_height / 2); + imgui.text(line.c_str()); + // "More" hypertext + render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x, win_size.y / 2 + win_size.y / 6 - m_line_height / 2, _u8L("More"), true); + } + } else { + //text 1 + float cursor_y = win_size.y / 2 - text_size.y / 2; + float cursor_x = x_offset; + if(m_lines_count > 1) { + // line1 + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); + imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + // line2 + std::string line = m_text1.substr(m_endlines[0] + 1); + cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2; + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(cursor_y); + imgui.text(line.c_str()); + cursor_x = x_offset + ImGui::CalcTextSize(line.c_str()).x; + } else { + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(cursor_y); + imgui.text(m_text1.c_str()); + cursor_x = x_offset + ImGui::CalcTextSize(m_text1.c_str()).x; + } + //hyperlink text + if (!m_hypertext.empty()) + { + render_hypertext(imgui, cursor_x + 4, cursor_y, m_hypertext); + } + + //notification text 2 + //text 2 is suposed to be after the hyperlink - currently it is not used + /* + if (!m_text2.empty()) + { + ImVec2 part_size = ImGui::CalcTextSize(m_hypertext.c_str()); + ImGui::SetCursorPosX(win_size.x / 2 + text_size.x / 2 - part_size.x + 8 - x_offset); + ImGui::SetCursorPosY(cursor_y); + imgui.text(m_text2.c_str()); + } + */ + } +} + +void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, const float text_x, const float text_y, const std::string text, bool more) +{ + //invisible button + ImVec2 part_size = ImGui::CalcTextSize(text.c_str()); + ImGui::SetCursorPosX(text_x -4); + ImGui::SetCursorPosY(text_y -5); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + if (imgui.button(" ", part_size.x + 6, part_size.y + 10)) + { + if (more) + { + m_multiline = true; + set_next_window_size(imgui); + } + else { + on_text_click(); + m_close_pending = true; + } + } + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + + //hover color + ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)) + orange_color.y += 0.2f; + + //text + Notifications_Internal::push_style_color(ImGuiCol_Text, orange_color, m_fading_out, m_current_fade_opacity); + ImGui::SetCursorPosX(text_x); + ImGui::SetCursorPosY(text_y); + imgui.text(text.c_str()); + ImGui::PopStyleColor(); + + //underline + ImVec2 lineEnd = ImGui::GetItemRectMax(); + lineEnd.y -= 2; + ImVec2 lineStart = lineEnd; + lineStart.x = ImGui::GetItemRectMin().x; + ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.w * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f)))); + +} + +void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button); + orange_color.w = 0.8f; + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + + + //button - if part if treggered + std::string button_text; + button_text = ImGui::CloseIconMarker; + + if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y), + ImVec2(win_pos.x, win_pos.y + win_size.y - (m_multiline? 2 * m_line_height : 0)), + true)) + { + button_text = ImGui::CloseIconHoverMarker; + } + ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + ImGui::SetCursorPosX(win_size.x - m_line_height * 2.25f); + ImGui::SetCursorPosY(win_size.y / 2 - button_size.y/2); + if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + { + m_close_pending = true; + } + + //invisible large button + ImGui::SetCursorPosX(win_size.x - win_size.x / 10.f); + ImGui::SetCursorPosY(0); + if (imgui.button(" ", win_size.x / 10.f, win_size.y - (m_multiline ? 2 * m_line_height : 0))) + { + m_close_pending = true; + } + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); +} +void NotificationManager::PopNotification::render_countdown(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + /* + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + + //countdown dots + std::string dot_text; + dot_text = m_remaining_time <= (float)m_data.duration / 4 * 3 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker; + ImGui::SetCursorPosX(win_size.x - m_line_height); + //ImGui::SetCursorPosY(win_size.y / 2 - 24); + ImGui::SetCursorPosY(0); + imgui.text(dot_text.c_str()); + + dot_text = m_remaining_time < m_data.duration / 2 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker; + ImGui::SetCursorPosX(win_size.x - m_line_height); + //ImGui::SetCursorPosY(win_size.y / 2 - 9); + ImGui::SetCursorPosY(win_size.y / 2 - m_line_height / 2); + imgui.text(dot_text.c_str()); + + dot_text = m_remaining_time <= m_data.duration / 4 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker; + ImGui::SetCursorPosX(win_size.x - m_line_height); + //ImGui::SetCursorPosY(win_size.y / 2 + 6); + ImGui::SetCursorPosY(win_size.y - m_line_height); + imgui.text(dot_text.c_str()); + */ + if (!m_fading_out && m_remaining_time <= m_data.duration / 4) { + m_fading_out = true; + m_fading_time = m_remaining_time; + } + + if (m_last_remaining_time != m_remaining_time) { + m_last_remaining_time = m_remaining_time; + m_countdown_frame = 0; + } + /* + //countdown line + ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button); + float invisible_length = ((float)(m_data.duration - m_remaining_time) / (float)m_data.duration * win_size_x); + invisible_length -= win_size_x / ((float)m_data.duration * 60.f) * (60 - m_countdown_frame); + ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length, win_pos_y + win_size_y - 5); + ImVec2 lineStart = ImVec2(win_pos_x - win_size_x, win_pos_y + win_size_y - 5); + ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.picture_width * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f))), 2.f); + if (!m_paused) + m_countdown_frame++; + */ +} +void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui) +{ + if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) { + std::string text; + text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); + ImGui::SetCursorPosX(m_line_height / 3); + ImGui::SetCursorPosY(m_window_height / 2 - m_line_height / 2); + imgui.text(text.c_str()); + } +} +void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) +{ + ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button); + orange_color.w = 0.8f; + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + Notifications_Internal::push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); + + + //button - if part if treggered + std::string button_text; + button_text = ImGui::CloseIconMarker; + if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 10.f, win_pos_y + m_window_height - 2 * m_line_height + 1), + ImVec2(win_pos_x, win_pos_y + m_window_height), + true)) + { + button_text = ImGui::CloseIconHoverMarker; + } + ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + ImGui::SetCursorPosX(m_window_width - m_line_height * 2.25f); + ImGui::SetCursorPosY(m_window_height - button_size.y - 5); + if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + { + m_multiline = false; + } + + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); +} +void NotificationManager::PopNotification::on_text_click() +{ + switch (m_data.type) { + case NotificationType::ExportToRemovableFinished : + assert(m_evt_handler != nullptr); + if (m_evt_handler != nullptr) + wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED)); + break; + case NotificationType::SlicingComplete : + //wxGetApp().plater()->export_gcode(false); + assert(m_evt_handler != nullptr); + if (m_evt_handler != nullptr) + wxPostEvent(m_evt_handler, ExportGcodeNotificationClickedEvent(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED)); + break; + case NotificationType::PresetUpdateAviable : + //wxGetApp().plater()->export_gcode(false); + assert(m_evt_handler != nullptr); + if (m_evt_handler != nullptr) + wxPostEvent(m_evt_handler, PresetUpdateAviableClickedEvent(EVT_PRESET_UPDATE_AVIABLE_CLICKED)); + break; + case NotificationType::NewAppAviable: + wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); + break; + default: + break; + } +} +void NotificationManager::PopNotification::update(const NotificationData& n) +{ + m_text1 = n.text1; + m_hypertext = n.hypertext; + m_text2 = n.text2; + init(); +} +bool NotificationManager::PopNotification::compare_text(const std::string& text) +{ + std::string t1(m_text1); + std::string t2(text); + t1.erase(std::remove_if(t1.begin(), t1.end(), ::isspace), t1.end()); + t2.erase(std::remove_if(t2.begin(), t2.end(), ::isspace), t2.end()); + if (t1.compare(t2) == 0) + return true; + return false; +} + +NotificationManager::SlicingCompleteLargeNotification::SlicingCompleteLargeNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler, bool large) : + NotificationManager::PopNotification(n, id, evt_handler) +{ + set_large(large); +} +void NotificationManager::SlicingCompleteLargeNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + if (!m_is_large) + PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + else { + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + + ImVec2 text1_size = ImGui::CalcTextSize(m_text1.c_str()); + float x_offset = m_left_indentation; + std::string fulltext = m_text1 + m_hypertext + m_text2; + ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str()); + float cursor_y = win_size.y / 2 - text_size.y / 2; + if (m_has_print_info) { + x_offset = 20; + cursor_y = win_size.y / 2 + win_size.y / 6 - text_size.y / 2; + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(cursor_y); + imgui.text(m_print_info.c_str()); + cursor_y = win_size.y / 2 - win_size.y / 6 - text_size.y / 2; + } + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(cursor_y); + imgui.text(m_text1.c_str()); + + render_hypertext(imgui, x_offset + text1_size.x + 4, cursor_y, m_hypertext); + + } +} +void NotificationManager::SlicingCompleteLargeNotification::set_print_info(std::string info) +{ + m_print_info = info; + m_has_print_info = true; + if(m_is_large) + m_lines_count = 2; +} +void NotificationManager::SlicingCompleteLargeNotification::set_large(bool l) +{ + m_is_large = l; + m_counting_down = !l; + m_hypertext = l ? _u8L("Export G-Code.") : std::string(); + m_hidden = !l; +} +//------NotificationManager-------- +NotificationManager::NotificationManager(wxEvtHandler* evt_handler) : + m_evt_handler(evt_handler) +{ +} +NotificationManager::~NotificationManager() +{ + for (PopNotification* notification : m_pop_notifications) + { + delete notification; + } +} +void NotificationManager::push_notification(const NotificationType type, GLCanvas3D& canvas, int timestamp) +{ + auto it = std::find_if(basic_notifications.begin(), basic_notifications.end(), + boost::bind(&NotificationData::type, _1) == type); + if (it != basic_notifications.end()) + push_notification_data( *it, canvas, timestamp); +} +void NotificationManager::push_notification(const std::string& text, GLCanvas3D& canvas, int timestamp) +{ + push_notification_data({ NotificationType::CustomNotification, NotificationLevel::RegularNotification, 10, text }, canvas, timestamp ); +} +void NotificationManager::push_notification(const std::string& text, NotificationManager::NotificationLevel level, GLCanvas3D& canvas, int timestamp) +{ + switch (level) + { + case Slic3r::GUI::NotificationManager::NotificationLevel::RegularNotification: + push_notification_data({ NotificationType::CustomNotification, level, 10, text }, canvas, timestamp); + break; + case Slic3r::GUI::NotificationManager::NotificationLevel::ErrorNotification: + push_notification_data({ NotificationType::CustomNotification, level, 0, text }, canvas, timestamp); + + break; + case Slic3r::GUI::NotificationManager::NotificationLevel::ImportantNotification: + push_notification_data({ NotificationType::CustomNotification, level, 0, text }, canvas, timestamp); + break; + default: + break; + } +} +void NotificationManager::push_slicing_error_notification(const std::string& text, GLCanvas3D& canvas) +{ + set_all_slicing_errors_gray(false); + push_notification_data({ NotificationType::SlicingError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, canvas, 0); + close_notification_of_type(NotificationType::SlicingComplete); +} +void NotificationManager::push_slicing_warning_notification(const std::string& text, bool gray, GLCanvas3D& canvas, size_t oid, int warning_step) +{ + NotificationData data { NotificationType::SlicingWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }; + + NotificationManager::SlicingWarningNotification* notification = new NotificationManager::SlicingWarningNotification(data, m_next_id++, m_evt_handler); + notification->set_object_id(oid); + notification->set_warning_step(warning_step); + if + (push_notification_data(notification, canvas, 0)) { + notification->set_gray(gray); + } + else { + delete notification; + } + +} +void NotificationManager::push_plater_error_notification(const std::string& text, GLCanvas3D& canvas) +{ + push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, canvas, 0); +} +void NotificationManager::push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas) +{ + push_notification_data({ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }, canvas, 0); +} +void NotificationManager::close_plater_error_notification() +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::PlaterError) { + notification->close(); + } + } +} +void NotificationManager::close_plater_warning_notification(const std::string& text) +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::PlaterWarning && notification->compare_text(_u8L("WARNING:") + "\n" + text)) { + notification->close(); + } + } +} +void NotificationManager::set_all_slicing_errors_gray(bool g) +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingError) { + notification->set_gray(g); + } + } +} +void NotificationManager::set_all_slicing_warnings_gray(bool g) +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingWarning) { + notification->set_gray(g); + } + } +} +void NotificationManager::set_slicing_warning_gray(const std::string& text, bool g) +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingWarning && notification->compare_text(text)) { + notification->set_gray(g); + } + } +} +void NotificationManager::close_slicing_errors_and_warnings() +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingError || notification->get_type() == NotificationType::SlicingWarning) { + notification->close(); + } + } +} +void NotificationManager::push_slicing_complete_notification(GLCanvas3D& canvas, int timestamp, bool large) +{ + std::string hypertext; + int time = 10; + if(large) + { + hypertext = _u8L("Export G-Code."); + time = 0; + } + NotificationData data{ NotificationType::SlicingComplete, NotificationLevel::RegularNotification, time, _u8L("Slicing finished."), hypertext }; + + NotificationManager::SlicingCompleteLargeNotification* notification = new NotificationManager::SlicingCompleteLargeNotification(data, m_next_id++, m_evt_handler, large); + if (push_notification_data(notification, canvas, timestamp)) { + } else { + delete notification; + } +} +void NotificationManager::set_slicing_complete_print_time(std::string info) +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingComplete) { + dynamic_cast(notification)->set_print_info(info); + break; + } + } +} +void NotificationManager::set_slicing_complete_large(bool large) +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingComplete) { + dynamic_cast(notification)->set_large(large); + break; + } + } +} +void NotificationManager::close_notification_of_type(const NotificationType type) +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == type) { + notification->close(); + } + } +} +void NotificationManager::compare_warning_oids(const std::vector& living_oids) +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingWarning) { + auto w = dynamic_cast(notification); + bool found = false; + for (size_t oid : living_oids) { + if (w->get_object_id() == oid) { + found = true; + break; + } + } + if (!found) + notification->close(); + } + } +} +bool NotificationManager::push_notification_data(const NotificationData ¬ification_data, GLCanvas3D& canvas, int timestamp) +{ + PopNotification* n = new PopNotification(notification_data, m_next_id++, m_evt_handler); + bool r = push_notification_data(n, canvas, timestamp); + if (!r) + delete n; + return r; +} +bool NotificationManager::push_notification_data(NotificationManager::PopNotification* notification, GLCanvas3D& canvas, int timestamp) +{ + // if timestamped notif, push only new one + if (timestamp != 0) { + if (m_used_timestamps.find(timestamp) == m_used_timestamps.end()) { + m_used_timestamps.insert(timestamp); + } else { + return false; + } + } + if (!this->find_older(notification)) { + m_pop_notifications.emplace_back(notification); + canvas.request_extra_frame(); + return true; + } else { + m_pop_notifications.back()->update(notification->get_data()); + canvas.request_extra_frame(); + return false; + } +} +void NotificationManager::render_notifications(GLCanvas3D& canvas) +{ + float last_x = 0.0f; + float current_height = 0.0f; + bool request_next_frame = false; + bool render_main = false; + bool hovered = false; + sort_notifications(); + // iterate thru notifications and render them / erease them + for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) { + if ((*it)->get_finished()) { + delete (*it); + it = m_pop_notifications.erase(it); + } else { + (*it)->set_paused(m_hovered); + PopNotification::RenderResult res = (*it)->render(canvas, last_x); + if (res != PopNotification::RenderResult::Finished) { + last_x = (*it)->get_top() + GAP_WIDTH; + current_height = std::max(current_height, (*it)->get_current_top()); + render_main = true; + } + if (res == PopNotification::RenderResult::Countdown || res == PopNotification::RenderResult::ClosePending || res == PopNotification::RenderResult::Finished) + request_next_frame = true; + if (res == PopNotification::RenderResult::Hovered) + hovered = true; + ++it; + } + } + m_hovered = hovered; + + //actualizate timers and request frame if needed + wxWindow* p = dynamic_cast (wxGetApp().plater()); + while (p->GetParent()) + p = p->GetParent(); + wxTopLevelWindow* top_level_wnd = dynamic_cast(p); + if (!top_level_wnd->IsActive()) + return; + + if (!m_hovered && m_last_time < wxGetLocalTime()) + { + if (wxGetLocalTime() - m_last_time == 1) + { + for(auto notification : m_pop_notifications) + { + notification->substract_remaining_time(); + } + } + m_last_time = wxGetLocalTime(); + } + + if (request_next_frame) + canvas.request_extra_frame(); +} + + +void NotificationManager::sort_notifications() +{ + std::sort(m_pop_notifications.begin(), m_pop_notifications.end(), [](PopNotification* n1, PopNotification* n2) { + int n1l = (int)n1->get_data().level; + int n2l = (int)n2->get_data().level; + if (n1l == n2l && n1->get_is_gray() && !n2->get_is_gray()) + return true; + return (n1l < n2l); + }); +} + +bool NotificationManager::find_older(NotificationManager::PopNotification* notification) +{ + NotificationType type = notification->get_type(); + std::string text = notification->get_data().text1; + for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end(); ++it) { + if((*it)->get_type() == type && !(*it)->get_finished()) { + if (type == NotificationType::CustomNotification || type == NotificationType::PlaterWarning) { + if (!(*it)->compare_text(text)) + continue; + }else if (type == NotificationType::SlicingWarning) { + auto w1 = dynamic_cast(notification); + auto w2 = dynamic_cast(*it); + if (w1 != nullptr && w2 != nullptr) { + if (!(*it)->compare_text(text) || w1->get_object_id() != w2->get_object_id()) { + continue; + } + } else { + continue; + } + } + + if (it != m_pop_notifications.end() - 1) + std::rotate(it, it + 1, m_pop_notifications.end()); + return true; + } + } + return false; +} + +void NotificationManager::dpi_changed() +{ + +} + +}//namespace GUI +}//namespace Slic3r diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp new file mode 100644 index 000000000..d7037c53e --- /dev/null +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -0,0 +1,268 @@ +#ifndef slic3r_GUI_NotificationManager_hpp_ +#define slic3r_GUI_NotificationManager_hpp_ + +#include "Event.hpp" +#include "I18N.hpp" + +#include +#include +#include +#include + +namespace Slic3r { +namespace GUI { + +using EjectDriveNotificationClickedEvent = SimpleEvent; +wxDECLARE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClickedEvent); +using ExportGcodeNotificationClickedEvent = SimpleEvent; +wxDECLARE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent); +using PresetUpdateAviableClickedEvent = SimpleEvent; +wxDECLARE_EVENT(EVT_PRESET_UPDATE_AVIABLE_CLICKED, PresetUpdateAviableClickedEvent); + +class GLCanvas3D; +class ImGuiWrapper; + +enum class NotificationType +{ + CustomNotification, + SlicingComplete, + SlicingNotPossible, + ExportToRemovableFinished, + Mouse3dDisconnected, + Mouse3dConnected, + NewPresetsAviable, + NewAppAviable, + PresetUpdateAviable, + LoadingFailed, + ValidateError, // currently not used - instead Slicing error is used for both slicing and validate errors + SlicingError, + SlicingWarning, + PlaterError, + PlaterWarning, + ApplyError + +}; +class NotificationManager +{ +public: + enum class NotificationLevel : int + { + ErrorNotification = 4, + WarningNotification = 3, + ImportantNotification = 2, + RegularNotification = 1, + }; + // duration 0 means not disapearing + struct NotificationData { + NotificationType type; + NotificationLevel level; + const int duration; + const std::string text1; + const std::string hypertext = std::string(); + const std::string text2 = std::string(); + }; + + //Pop notification - shows only once to user. + class PopNotification + { + public: + enum class RenderResult + { + Finished, + ClosePending, + Static, + Countdown, + Hovered + }; + PopNotification(const NotificationData &n, const int id, wxEvtHandler* evt_handler); + virtual ~PopNotification(); + RenderResult render(GLCanvas3D& canvas, const float& initial_y); + // close will dissapear notification on next render + void close() { m_close_pending = true; } + // data from newer notification of same type + void update(const NotificationData& n); + bool get_finished() const { return m_finished; } + // returns top after movement + float get_top() const { return m_top_y; } + //returns top in actual frame + float get_current_top() const { return m_top_y; } + const NotificationType get_type() const { return m_data.type; } + const NotificationData get_data() const { return m_data; } + const bool get_is_gray() const { return m_is_gray; } + // Call equals one second down + void substract_remaining_time() { m_remaining_time--; } + void set_gray(bool g) { m_is_gray = g; } + void set_paused(bool p) { m_paused = p; } + bool compare_text(const std::string& text); + protected: + // Call after every size change + void init(); + // Calculetes correct size but not se it in imgui! + virtual void set_next_window_size(ImGuiWrapper& imgui); + virtual void render_text(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x , const float win_pos_y); + void render_close_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x , const float win_pos_y); + void render_countdown(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x , const float win_pos_y); + void render_hypertext(ImGuiWrapper& imgui, + const float text_x, const float text_y, + const std::string text, + bool more = false); + void render_left_sign(ImGuiWrapper& imgui); + void render_minimize_button(ImGuiWrapper& imgui, + const float win_pos_x, const float win_pos_y); + void on_text_click(); + + const NotificationData m_data; + + int m_id; + // Main text + std::string m_text1; + // Clickable text + std::string m_hypertext; + // Aditional text after hypertext - currently not used + std::string m_text2; + // Countdown variables + long m_remaining_time; + bool m_counting_down; + long m_last_remaining_time; + bool m_paused{ false }; + int m_countdown_frame{ 0 }; + bool m_fading_out{ false }; + // total time left when fading beggins + float m_fading_time{ 0.0f }; + float m_current_fade_opacity{ 1.f }; + // If hidden the notif is alive but not visible to user + bool m_hidden { false }; + // m_finished = true - does not render, marked to delete + bool m_finished { false }; + // Will go to m_finished next render + bool m_close_pending { false }; + // variables to count positions correctly + float m_window_width_offset; + float m_left_indentation; + // Total size of notification window - varies based on monitor + float m_window_height { 56.0f }; + float m_window_width { 450.0f }; + //Distance from bottom of notifications to top of this notification + float m_top_y { 0.0f }; + + // Height of text + // Used as basic scaling unit! + float m_line_height; + std::vector m_endlines; + // Gray are f.e. eorrors when its uknown if they are still valid + bool m_is_gray { false }; + //if multiline = true, notification is showing all lines(>2) + bool m_multiline { false }; + int m_lines_count{ 1 }; + wxEvtHandler* m_evt_handler; + }; + + class SlicingCompleteLargeNotification : public PopNotification + { + public: + SlicingCompleteLargeNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler, bool largeds); + void set_large(bool l); + bool get_large() { return m_is_large; } + + void set_print_info(std::string info); + protected: + virtual void render_text(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) + override; + + bool m_is_large; + bool m_has_print_info { false }; + std::string m_print_info { std::string() }; + }; + + class SlicingWarningNotification : public PopNotification + { + public: + SlicingWarningNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler) : PopNotification(n, id, evt_handler) {} + void set_object_id(size_t id) { object_id = id; } + const size_t get_object_id() { return object_id; } + void set_warning_step(int ws) { warning_step = ws; } + const int get_warning_step() { return warning_step; } + protected: + size_t object_id; + int warning_step; + }; + + NotificationManager(wxEvtHandler* evt_handler); + ~NotificationManager(); + + + // only type means one of basic_notification (see below) + void push_notification(const NotificationType type, GLCanvas3D& canvas, int timestamp = 0); + // only text means Undefined type + void push_notification(const std::string& text, GLCanvas3D& canvas, int timestamp = 0); + void push_notification(const std::string& text, NotificationLevel level, GLCanvas3D& canvas, int timestamp = 0); + // creates Slicing Error notification with custom text + void push_slicing_error_notification(const std::string& text, GLCanvas3D& canvas); + // creates Slicing Warning notification with custom text + void push_slicing_warning_notification(const std::string& text, bool gray, GLCanvas3D& canvas, size_t oid, int warning_step); + // marks slicing errors as gray + void set_all_slicing_errors_gray(bool g); + // marks slicing warings as gray + void set_all_slicing_warnings_gray(bool g); + void set_slicing_warning_gray(const std::string& text, bool g); + // imidietly stops showing slicing errors + void close_slicing_errors_and_warnings(); + void compare_warning_oids(const std::vector& living_oids); + void push_plater_error_notification(const std::string& text, GLCanvas3D& canvas); + void push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas); + void close_plater_error_notification(); + void close_plater_warning_notification(const std::string& text); + // creates special notification slicing complete + // if large = true prints printing time and export button + void push_slicing_complete_notification(GLCanvas3D& canvas, int timestamp, bool large); + void set_slicing_complete_print_time(std::string info); + void set_slicing_complete_large(bool large); + // renders notifications in queue and deletes expired ones + void render_notifications(GLCanvas3D& canvas); + // finds and closes all notifications of given type + void close_notification_of_type(const NotificationType type); + void dpi_changed(); +private: + //pushes notification into the queue of notifications that are rendered + //can be used to create custom notification + bool push_notification_data(const NotificationData& notification_data, GLCanvas3D& canvas, int timestamp); + bool push_notification_data(NotificationManager::PopNotification* notification, GLCanvas3D& canvas, int timestamp); + //finds older notification of same type and moves it to the end of queue. returns true if found + bool find_older(NotificationManager::PopNotification* notification); + void sort_notifications(); + + wxEvtHandler* m_evt_handler; + std::deque m_pop_notifications; + int m_next_id { 1 }; + long m_last_time { 0 }; + bool m_hovered { false }; + //timestamps used for slining finished - notification could be gone so it needs to be stored here + std::unordered_set m_used_timestamps; + + //prepared (basic) notifications + const std::vector basic_notifications = { + {NotificationType::SlicingNotPossible, NotificationLevel::RegularNotification, 10, _u8L("Slicing is not possible.")}, + {NotificationType::ExportToRemovableFinished, NotificationLevel::ImportantNotification, 0, _u8L("Exporting finished."), _u8L("Eject drive.") }, + {NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") }, + {NotificationType::Mouse3dConnected, NotificationLevel::RegularNotification, 5, _u8L("3D Mouse connected.") }, + {NotificationType::NewPresetsAviable, NotificationLevel::ImportantNotification, 20, _u8L("New Presets are available."), _u8L("See here.") }, + {NotificationType::PresetUpdateAviable, NotificationLevel::ImportantNotification, 20, _u8L("Configuration update is available."), _u8L("See more.")}, + {NotificationType::NewAppAviable, NotificationLevel::ImportantNotification, 20, _u8L("New version is available."), _u8L("See Releases page.")}, + //{NotificationType::NewAppAviable, NotificationLevel::ImportantNotification, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") }, + //{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20, _u8L("Loading of model has Failed") }, + //{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification + }; +}; + +}//namespace GUI +}//namespace Slic3r + +#endif //slic3r_GUI_NotificationManager_hpp_ \ No newline at end of file diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 761f574e1..9cfc717db 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -75,8 +75,10 @@ #include "../Utils/PrintHost.hpp" #include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" +#include "../Utils/PresetUpdater.hpp" #include "RemovableDriveManager.hpp" #include "InstanceCheck.hpp" +#include "NotificationManager.hpp" #ifdef __APPLE__ #include "Gizmos/GLGizmosManager.hpp" @@ -102,6 +104,7 @@ wxDEFINE_EVENT(EVT_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent); wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent); wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, wxCommandEvent); +wxDEFINE_EVENT(EVT_EXPORT_BEGAN, wxCommandEvent); // Sidebar widgets @@ -716,7 +719,7 @@ struct Sidebar::priv wxButton *btn_export_gcode; wxButton *btn_reslice; ScalableButton *btn_send_gcode; - ScalableButton *btn_remove_device; + ScalableButton *btn_eject_device; ScalableButton* btn_export_gcode_removable; //exports to removable drives (appears only if removable drive is connected) bool is_collapsed {false}; @@ -889,12 +892,12 @@ Sidebar::Sidebar(Plater *parent) }; init_scalable_btn(&p->btn_send_gcode , "export_gcode", _L("Send to printer") + "\tCtrl+Shift+G"); - init_scalable_btn(&p->btn_remove_device, "eject_sd" , _L("Remove device") + "\tCtrl+T"); + init_scalable_btn(&p->btn_eject_device, "eject_sd" , _L("Remove device") + "\tCtrl+T"); init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _L("Export to SD card / Flash drive") + "\tCtrl+U"); // regular buttons "Slice now" and "Export G-code" - const int scaled_height = p->btn_remove_device->GetBitmapHeight() + 4; + const int scaled_height = p->btn_eject_device->GetBitmapHeight() + 4; auto init_btn = [this](wxButton **btn, wxString label, const int button_height) { *btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition, wxSize(-1, button_height), wxBU_EXACTFIT); @@ -912,7 +915,7 @@ Sidebar::Sidebar(Plater *parent) complect_btns_sizer->Add(p->btn_export_gcode, 1, wxEXPAND); complect_btns_sizer->Add(p->btn_send_gcode); complect_btns_sizer->Add(p->btn_export_gcode_removable); - complect_btns_sizer->Add(p->btn_remove_device); + complect_btns_sizer->Add(p->btn_eject_device); btns_sizer->Add(p->btn_reslice, 0, wxEXPAND | wxTOP, margin_5); @@ -935,7 +938,7 @@ Sidebar::Sidebar(Plater *parent) p->plater->select_view_3D("Preview"); }); p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); }); - p->btn_remove_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); }); + p->btn_eject_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); }); p->btn_export_gcode_removable->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(true); }); } @@ -1083,9 +1086,9 @@ void Sidebar::msw_rescale() p->object_info->msw_rescale(); p->btn_send_gcode->msw_rescale(); - p->btn_remove_device->msw_rescale(); + p->btn_eject_device->msw_rescale(); p->btn_export_gcode_removable->msw_rescale(); - const int scaled_height = p->btn_remove_device->GetBitmap().GetHeight() + 4; + const int scaled_height = p->btn_eject_device->GetBitmap().GetHeight() + 4; p->btn_export_gcode->SetMinSize(wxSize(-1, scaled_height)); p->btn_reslice ->SetMinSize(wxSize(-1, scaled_height)); @@ -1114,7 +1117,7 @@ void Sidebar::sys_color_changed() // btn...->msw_rescale() updates icon on button, so use it p->btn_send_gcode->msw_rescale(); - p->btn_remove_device->msw_rescale(); + p->btn_eject_device->msw_rescale(); p->btn_export_gcode_removable->msw_rescale(); p->scrolled->Layout(); @@ -1350,6 +1353,12 @@ void Sidebar::update_sliced_info_sizer() new_label += format_wxstr("\n - %1%", _L("normal mode")); info_text += format_wxstr("\n%1%", ps.estimated_normal_print_time); fill_labels(ps.estimated_normal_custom_gcode_print_times, new_label, info_text); + + // uncomment next line to not disappear slicing finished notif when colapsing sidebar before time estimate + //if (p->plater->is_sidebar_collapsed()) + p->plater->get_notification_manager()->set_slicing_complete_large(p->plater->is_sidebar_collapsed()); + p->plater->get_notification_manager()->set_slicing_complete_print_time("Estimated printing time: " + ps.estimated_normal_print_time); + } if (ps.estimated_silent_print_time != "N/A") { new_label += format_wxstr("\n - %1%", _L("stealth mode")); @@ -1385,15 +1394,16 @@ void Sidebar::enable_buttons(bool enable) p->btn_reslice->Enable(enable); p->btn_export_gcode->Enable(enable); p->btn_send_gcode->Enable(enable); - p->btn_remove_device->Enable(enable); + p->btn_eject_device->Enable(enable); p->btn_export_gcode_removable->Enable(enable); } -bool Sidebar::show_reslice(bool show) const { return p->btn_reslice->Show(show); } -bool Sidebar::show_export(bool show) const { return p->btn_export_gcode->Show(show); } -bool Sidebar::show_send(bool show) const { return p->btn_send_gcode->Show(show); } -bool Sidebar::show_disconnect(bool show) const { return p->btn_remove_device->Show(show); } -bool Sidebar::show_export_removable(bool show)const { return p->btn_export_gcode_removable->Show(show); } +bool Sidebar::show_reslice(bool show) const { return p->btn_reslice->Show(show); } +bool Sidebar::show_export(bool show) const { return p->btn_export_gcode->Show(show); } +bool Sidebar::show_send(bool show) const { return p->btn_send_gcode->Show(show); } +bool Sidebar::show_export_removable(bool show) const { return p->btn_export_gcode_removable->Show(show); } +bool Sidebar::show_eject(bool show) const { return p->btn_eject_device->Show(show); } +bool Sidebar::get_eject_shown() const { return p->btn_eject_device->IsShown(); } bool Sidebar::is_multifilament() { @@ -1591,6 +1601,7 @@ struct Plater::priv GLToolbar view_toolbar; GLToolbar collapse_toolbar; Preview *preview; + NotificationManager* notification_manager; BackgroundSlicingProcess background_process; bool suppressed_backround_processing_update { false }; @@ -1775,7 +1786,17 @@ struct Plater::priv void on_slicing_update(SlicingStatusEvent&); void on_slicing_completed(wxCommandEvent&); void on_process_completed(wxCommandEvent&); + void on_export_began(wxCommandEvent&); void on_layer_editing_toggled(bool enable); + void on_slicing_began(); + + void clear_warnings(); + void add_warning(const Slic3r::PrintStateBase::Warning &warning, size_t oid); + void actualizate_warnings(const Model& model, size_t print_oid); + // Displays dialog window with list of warnings. + // Returns true if user clicks OK. + // Returns true if current_warnings vector is empty without showning the dialog + bool warnings_dialog(); void on_action_add(SimpleEvent&); void on_action_split_objects(SimpleEvent&); @@ -1826,7 +1847,7 @@ struct Plater::priv // Flag indicating that the G-code export targets a removable device, therefore the show_action_buttons() needs to be called at any case when the background processing finishes. bool writing_to_removable_device = { false }; bool inside_snapshot_capture() { return m_prevent_snapshots != 0; } - + bool process_completed_with_error { false }; private: bool init_object_menu(); bool init_common_menu(wxMenu* menu, const bool is_part = false); @@ -1854,6 +1875,11 @@ private: * */ std::string m_last_fff_printer_profile_name; std::string m_last_sla_printer_profile_name; + + // vector of all warnings generated by last slicing + std::vector> current_warnings; + bool show_warning_dialog { false }; + }; const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase); @@ -1899,6 +1925,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) }); background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED); background_process.set_finished_event(EVT_PROCESS_COMPLETED); + background_process.set_export_began_event(EVT_EXPORT_BEGAN); // Default printer technology for default config. background_process.select_technology(this->printer_technology); // Register progress callback from the Print class to the Plater. @@ -2010,8 +2037,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_double_slider(evt); }); - q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this); + q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this); q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this); + q->Bind(EVT_EXPORT_BEGAN, &priv::on_export_began, this); q->Bind(EVT_GLVIEWTOOLBAR_3D, [q](SimpleEvent&) { q->select_view_3D("3D"); }); q->Bind(EVT_GLVIEWTOOLBAR_PREVIEW, [q](SimpleEvent&) { q->select_view_3D("Preview"); }); @@ -2038,16 +2066,27 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) }); #endif /* _WIN32 */ - this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this](RemovableDriveEjectEvent &evt) { + notification_manager = new NotificationManager(this->q); + this->q->Bind(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, [this](EjectDriveNotificationClickedEvent&) { this->q->eject_drive(); }); + this->q->Bind(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, [this](ExportGcodeNotificationClickedEvent&) { this->q->export_gcode(true); }); + this->q->Bind(EVT_PRESET_UPDATE_AVIABLE_CLICKED, [this](PresetUpdateAviableClickedEvent&) { wxGetApp().get_preset_updater()->on_update_notification_confirm(); }); + + this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this, q](RemovableDriveEjectEvent &evt) { if (evt.data.second) { this->show_action_buttons(this->ready_to_slice); - Slic3r::GUI::show_info(this->q, format_wxstr(_L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."), - evt.data.first.name, evt.data.first.path)); - } else - Slic3r::GUI::show_info(this->q, format_wxstr(_L("Ejecting of device %s(%s) has failed."), - evt.data.first.name, evt.data.first.path)); + notification_manager->push_notification(format(_L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."),evt.data.first.name, evt.data.first.path), + NotificationManager::NotificationLevel::RegularNotification, *q->get_current_canvas3D()); + } else { + notification_manager->push_notification(format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path), + NotificationManager::NotificationLevel::ErrorNotification, *q->get_current_canvas3D()); + } + }); + this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this, q](RemovableDrivesChangedEvent &) { + this->show_action_buttons(this->ready_to_slice); + if (!this->sidebar->get_eject_shown()) { + notification_manager->close_notification_of_type(NotificationType::ExportToRemovableFinished); + } }); - this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this](RemovableDrivesChangedEvent &) { this->show_action_buttons(this->ready_to_slice); }); // Start the background thread and register this window as a target for update events. wxGetApp().removable_drive_manager()->init(this->q); #ifdef _WIN32 @@ -2675,6 +2714,8 @@ void Plater::priv::reset() { Plater::TakeSnapshot snapshot(q, _L("Reset Project")); + clear_warnings(); + set_project_filename(wxEmptyString); // Prevent toolpaths preview from rendering while we modify the Print object @@ -2844,22 +2885,13 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool // The state of the Print changed, and it is non-zero. Let's validate it and give the user feedback on errors. std::string err = this->background_process.validate(); if (err.empty()) { + notification_manager->set_all_slicing_errors_gray(true); if (invalidated != Print::APPLY_STATUS_UNCHANGED && this->background_processing_enabled()) return_state |= UPDATE_BACKGROUND_PROCESS_RESTART; } else { - // The print is not valid. - // Only show the error message immediately, if the top level parent of this window is active. - auto p = dynamic_cast(this->q); - while (p->GetParent()) - p = p->GetParent(); - auto *top_level_wnd = dynamic_cast(p); - if (! postpone_error_messages && top_level_wnd && top_level_wnd->IsActive()) { - // The error returned from the Print needs to be translated into the local language. - GUI::show_error(this->q, err); - } else { - // Show the error message once the main window gets activated. - this->delayed_error_message = err; - } + // The print is not valid. + // Show error as notification. + notification_manager->push_slicing_error_notification(err, *q->get_current_canvas3D()); return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; } } else if (! this->delayed_error_message.empty()) { @@ -2867,6 +2899,14 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; } + //actualizate warnings + if (invalidated != Print::APPLY_STATUS_UNCHANGED) { + actualizate_warnings(this->q->model(), this->background_process.current_print()->id().id); + notification_manager->set_all_slicing_warnings_gray(true); + show_warning_dialog = false; + process_completed_with_error = false; + } + if (invalidated != Print::APPLY_STATUS_UNCHANGED && was_running && ! this->background_process.running() && (return_state & UPDATE_BACKGROUND_PROCESS_RESTART) == 0) { // The background processing was killed and it will not be restarted. @@ -2929,6 +2969,8 @@ bool Plater::priv::restart_background_process(unsigned int state) this->statusbar()->set_status_text(_L("Cancelling")); this->background_process.stop(); }); + if (!show_warning_dialog) + on_slicing_began(); return true; } } @@ -2955,6 +2997,7 @@ void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_remova if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0) return; + show_warning_dialog = true; if (! output_path.empty()) { background_process.schedule_export(output_path.string(), output_path_on_removable_media); } else { @@ -3433,11 +3476,20 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) state = print_object->step_state_with_warnings(static_cast(warning_step)); } // Now process state.warnings. + for (auto const& warning : state.warnings) { + if (warning.current) { + notification_manager->push_slicing_warning_notification(warning.message, false, *q->get_current_canvas3D(), object_id.id, warning_step); + add_warning(warning, object_id.id); + } + } } } -void Plater::priv::on_slicing_completed(wxCommandEvent &) +void Plater::priv::on_slicing_completed(wxCommandEvent & evt) { + //notification_manager->push_notification(NotificationType::SlicingComplete, *q->get_current_canvas3D(), evt.GetInt()); + notification_manager->push_slicing_complete_notification(*q->get_current_canvas3D(), evt.GetInt(), is_sidebar_collapsed()); + switch (this->printer_technology) { case ptFFF: this->update_fff_scene(); @@ -3450,8 +3502,63 @@ void Plater::priv::on_slicing_completed(wxCommandEvent &) break; default: break; } -} +} +void Plater::priv::on_export_began(wxCommandEvent& evt) +{ + if (show_warning_dialog) + warnings_dialog(); +} +void Plater::priv::on_slicing_began() +{ + clear_warnings(); + notification_manager->close_notification_of_type(NotificationType::SlicingComplete); +} +void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, size_t oid) +{ + for (auto const& it : current_warnings) { + if (warning.message_id == it.first.message_id) { + if (warning.message_id != 0 || (warning.message_id == 0 && warning.message == it.first.message)) + return; + } + } + current_warnings.emplace_back(std::pair(warning, oid)); +} +void Plater::priv::actualizate_warnings(const Model& model, size_t print_oid) +{ + std::vector living_oids; + living_oids.push_back(model.id().id); + living_oids.push_back(print_oid); + for (auto it = model.objects.begin(); it != model.objects.end(); ++it) { + living_oids.push_back((*it)->id().id); + } + notification_manager->compare_warning_oids(living_oids); +} +void Plater::priv::clear_warnings() +{ + notification_manager->close_slicing_errors_and_warnings(); + this->current_warnings.clear(); +} +bool Plater::priv::warnings_dialog() +{ + if (current_warnings.empty()) + return true; + std::string text = _u8L("There are active warnings concerning sliced models:\n"); + bool empt = true; + for (auto const& it : current_warnings) { + int next_n = it.first.message.find_first_of('\n', 0); + text += "\n"; + if (next_n != std::string::npos) + text += it.first.message.substr(0, next_n); + else + text += it.first.message; + } + //text += "\n\nDo you still wish to export?"; + wxMessageDialog msg_wingow(this->q, text, wxString(SLIC3R_APP_NAME " ") + _L("generated warnings"), wxOK); + const auto res = msg_wingow.ShowModal(); + return res == wxID_OK; + +} void Plater::priv::on_process_completed(wxCommandEvent &evt) { // Stop the background task, wait until the thread goes into the "Idle" state. @@ -3470,14 +3577,13 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) if (error) { wxString message = evt.GetString(); if (message.IsEmpty()) - message = _L("Export failed"); - if (q->m_tracking_popup_menu) - // We don't want to pop-up a message box when tracking a pop-up menu. - // We postpone the error message instead. - q->m_tracking_popup_menu_error_message = message; - else - show_error(q, message); + message = _L("Export failed."); + notification_manager->push_slicing_error_notification(boost::nowide::narrow(message), *q->get_current_canvas3D()); this->statusbar()->set_status_text(message); + const wxString invalid_str = _L("Invalid data"); + for (auto btn : { ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport }) + sidebar->set_btn_label(btn, invalid_str); + process_completed_with_error = true; } if (canceled) this->statusbar()->set_status_text(_L("Cancelled")); @@ -3503,18 +3609,21 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) default: break; } - if (canceled) { if (wxGetApp().get_mode() == comSimple) sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now"); show_action_buttons(true); } - else if (this->writing_to_removable_device || wxGetApp().get_mode() == comSimple) + else if (wxGetApp().get_mode() == comSimple) { - wxGetApp().removable_drive_manager()->set_exporting_finished(true); show_action_buttons(false); } - this->writing_to_removable_device = false; + else if (this->writing_to_removable_device) + { + show_action_buttons(false); + notification_manager->push_notification(NotificationType::ExportToRemovableFinished, *q->get_current_canvas3D()); + } + this->writing_to_removable_device = false; } void Plater::priv::on_layer_editing_toggled(bool enable) @@ -4156,7 +4265,7 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const sidebar->show_export(true) | sidebar->show_send(send_gcode_shown) | sidebar->show_export_removable(removable_media_status.has_removable_drives) | - sidebar->show_disconnect(removable_media_status.has_eject)) + sidebar->show_eject(removable_media_status.has_eject)) sidebar->Layout(); } else @@ -4168,7 +4277,7 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const sidebar->show_export(!ready_to_slice) | sidebar->show_send(send_gcode_shown && !ready_to_slice) | sidebar->show_export_removable(!ready_to_slice && removable_media_status.has_removable_drives) | - sidebar->show_disconnect(!ready_to_slice && removable_media_status.has_eject)) + sidebar->show_eject(!ready_to_slice && removable_media_status.has_eject)) sidebar->Layout(); } } @@ -4731,6 +4840,9 @@ void Plater::export_gcode(bool prefer_removable) if (p->model.objects.empty()) return; + if (p->process_completed_with_error)//here + return; + // If possible, remove accents from accented latin characters. // This function is useful for generating file names to be processed by legacy firmwares. fs::path default_output_file; @@ -4990,7 +5102,6 @@ void Plater::export_toolpaths_to_obj() const p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str()); } - void Plater::reslice() { // Stop arrange and (or) optimize rotation tasks. @@ -5676,6 +5787,16 @@ Mouse3DController& Plater::get_mouse3d_controller() return p->mouse3d_controller; } +const NotificationManager* Plater::get_notification_manager() const +{ + return p->notification_manager; +} + +NotificationManager* Plater::get_notification_manager() +{ + return p->notification_manager; +} + bool Plater::can_delete() const { return p->can_delete(); } bool Plater::can_delete_all() const { return p->can_delete_all(); } bool Plater::can_increase_instances() const { return p->can_increase_instances(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index a08b19fa3..24e93c80e 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -47,6 +47,7 @@ class ObjectLayers; class ObjectList; class GLCanvas3D; class Mouse3DController; +class NotificationManager; struct Camera; class Bed3D; class GLToolbar; @@ -130,8 +131,9 @@ public: bool show_reslice(bool show) const; bool show_export(bool show) const; bool show_send(bool show) const; - bool show_disconnect(bool show)const; + bool show_eject(bool show)const; bool show_export_removable(bool show) const; + bool get_eject_shown() const; bool is_multifilament(); void update_mode(); bool is_collapsed(); @@ -338,6 +340,9 @@ public: Mouse3DController& get_mouse3d_controller(); void set_bed_shape() const; + + const NotificationManager* get_notification_manager() const; + NotificationManager* get_notification_manager(); // ROII wrapper for suppressing the Undo / Redo snapshot to be taken. class SuppressSnapshots diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index c32613c46..7d316e77c 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -27,6 +27,7 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/format.hpp" +#include "slic3r/GUI/NotificationManager.hpp" #include "slic3r/Utils/Http.hpp" #include "slic3r/Config/Version.hpp" #include "slic3r/Config/Snapshot.hpp" @@ -154,6 +155,9 @@ struct PresetUpdater::priv bool cancel; std::thread thread; + bool has_waiting_updates { false }; + Updates waiting_updates; + priv(); void set_download_prefs(AppConfig *app_config); @@ -165,6 +169,7 @@ struct PresetUpdater::priv void check_install_indices() const; Updates get_config_updates(const Semver& old_slic3r_version) const; void perform_updates(Updates &&updates, bool snapshot = true) const; + void set_waiting_updates(Updates u); }; PresetUpdater::priv::priv() @@ -326,7 +331,15 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors) continue; } Slic3r::rename_file(idx_path_temp, idx_path); - index = std::move(new_index); + //if we rename path we need to change it in Index object too or create the object again + //index = std::move(new_index); + try { + index.load(idx_path); + } + catch (const std::exception& /* err */) { + BOOST_LOG_TRIVIAL(error) << format("Could not load downloaded index %1% for vendor %2%: invalid index?", idx_path, vendor.name); + continue; + } if (cancel) return; } @@ -632,6 +645,12 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons } } +void PresetUpdater::priv::set_waiting_updates(Updates u) +{ + waiting_updates = u; + has_waiting_updates = true; +} + PresetUpdater::PresetUpdater() : p(new priv()) {} @@ -690,9 +709,9 @@ void PresetUpdater::slic3r_update_notify() } } -PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver &old_slic3r_version) const +PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, bool no_notification) const { - if (! p->enabled_config_update) { return R_NOOP; } + if (! p->enabled_config_update) { return R_NOOP; } auto updates = p->get_config_updates(old_slic3r_version); if (updates.incompats.size() > 0) { @@ -779,30 +798,38 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver &old_slic3 } // regular update - BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", updates.updates.size()); + if (no_notification) { + BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", p->waiting_updates.updates.size()); - std::vector updates_msg; - for (const auto &update : updates.updates) { - std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string(); - updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url)); - } + std::vector updates_msg; + for (const auto& update : updates.updates) { + std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string(); + updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url)); + } - GUI::MsgUpdateConfig dlg(updates_msg); + GUI::MsgUpdateConfig dlg(updates_msg); - const auto res = dlg.ShowModal(); - if (res == wxID_OK) { - BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; - p->perform_updates(std::move(updates)); + const auto res = dlg.ShowModal(); + if (res == wxID_OK) { + BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; + p->perform_updates(std::move(updates)); - // Reload global configuration - auto *app_config = GUI::wxGetApp().app_config; - GUI::wxGetApp().preset_bundle->load_presets(*app_config); - GUI::wxGetApp().load_current_presets(); - return R_UPDATE_INSTALLED; + // Reload global configuration + auto* app_config = GUI::wxGetApp().app_config; + GUI::wxGetApp().preset_bundle->load_presets(*app_config); + GUI::wxGetApp().load_current_presets(); + return R_UPDATE_INSTALLED; + } + else { + BOOST_LOG_TRIVIAL(info) << "User refused the update"; + return R_UPDATE_REJECT; + } } else { - BOOST_LOG_TRIVIAL(info) << "User refused the update"; - return R_UPDATE_REJECT; + p->set_waiting_updates(updates); + GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::PresetUpdateAviable, *(GUI::wxGetApp().plater()->get_current_canvas3D())); } + + // MsgUpdateConfig will show after the notificaation is clicked } else { BOOST_LOG_TRIVIAL(info) << "No configuration updates available."; } @@ -825,5 +852,37 @@ void PresetUpdater::install_bundles_rsrc(std::vector bundles, bool p->perform_updates(std::move(updates), snapshot); } +void PresetUpdater::on_update_notification_confirm() +{ + if (!p->has_waiting_updates) + return; + BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", p->waiting_updates.updates.size()); + + std::vector updates_msg; + for (const auto& update : p->waiting_updates.updates) { + std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string(); + updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url)); + } + + GUI::MsgUpdateConfig dlg(updates_msg); + + const auto res = dlg.ShowModal(); + if (res == wxID_OK) { + BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; + p->perform_updates(std::move(p->waiting_updates)); + + // Reload global configuration + auto* app_config = GUI::wxGetApp().app_config; + GUI::wxGetApp().preset_bundle->load_presets(*app_config); + GUI::wxGetApp().load_current_presets(); + p->has_waiting_updates = false; + //return R_UPDATE_INSTALLED; + } + else { + BOOST_LOG_TRIVIAL(info) << "User refused the update"; + //return R_UPDATE_REJECT; + } + +} } diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp index e18695828..0ca363c61 100644 --- a/src/slic3r/Utils/PresetUpdater.hpp +++ b/src/slic3r/Utils/PresetUpdater.hpp @@ -35,16 +35,20 @@ public: R_INCOMPAT_CONFIGURED, R_UPDATE_INSTALLED, R_UPDATE_REJECT, + R_UPDATE_NOTIFICATION }; // If updating is enabled, check if updates are available in cache, if so, ask about installation. // A false return value implies Slic3r should exit due to incompatibility of configuration. // Providing old slic3r version upgrade profiles on upgrade of an application even in case // that the config index installed from the Internet is equal to the index contained in the installation package. - UpdateResult config_update(const Semver &old_slic3r_version) const; + // no_notification = force modal textbox, otherwise some cases only shows notification + UpdateResult config_update(const Semver &old_slic3r_version, bool no_notification) const; // "Update" a list of bundles from resources (behaves like an online update). void install_bundles_rsrc(std::vector bundles, bool snapshot = true) const; + + void on_update_notification_confirm(); private: struct priv; std::unique_ptr p; From 38239f09e3ea889aab14cc6c6bc2d6a27013981d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Jun 2020 17:28:46 +0200 Subject: [PATCH 51/70] Fix remove_bottom_points function --- src/libslic3r/SLA/SupportPoint.hpp | 4 ++-- src/libslic3r/SLA/SupportPointGenerator.cpp | 11 ++++------ src/libslic3r/SLA/SupportPointGenerator.hpp | 2 +- src/libslic3r/SLAPrintSteps.cpp | 23 ++++++++++----------- 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/SLA/SupportPoint.hpp b/src/libslic3r/SLA/SupportPoint.hpp index 202a614c3..455962cc4 100644 --- a/src/libslic3r/SLA/SupportPoint.hpp +++ b/src/libslic3r/SLA/SupportPoint.hpp @@ -29,13 +29,13 @@ struct SupportPoint float pos_y, float pos_z, float head_radius, - bool new_island) + bool new_island = false) : pos(pos_x, pos_y, pos_z) , head_front_radius(head_radius) , is_new_island(new_island) {} - SupportPoint(Vec3f position, float head_radius, bool new_island) + SupportPoint(Vec3f position, float head_radius, bool new_island = false) : pos(position) , head_front_radius(head_radius) , is_new_island(new_island) diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index 78c2ced35..b598439ca 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -523,15 +523,12 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure } } -void remove_bottom_points(std::vector &pts, double gnd_lvl, double tolerance) +void remove_bottom_points(std::vector &pts, float lvl) { // get iterator to the reorganized vector end - auto endit = - std::remove_if(pts.begin(), pts.end(), - [tolerance, gnd_lvl](const sla::SupportPoint &sp) { - double diff = std::abs(gnd_lvl - - double(sp.pos(Z))); - return diff <= tolerance; + auto endit = std::remove_if(pts.begin(), pts.end(), [lvl] + (const sla::SupportPoint &sp) { + return sp.pos.z() <= lvl; }); // erase all elements after the new end diff --git a/src/libslic3r/SLA/SupportPointGenerator.hpp b/src/libslic3r/SLA/SupportPointGenerator.hpp index 2fe8e11fc..172923056 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.hpp +++ b/src/libslic3r/SLA/SupportPointGenerator.hpp @@ -214,7 +214,7 @@ private: std::mt19937 m_rng; }; -void remove_bottom_points(std::vector &pts, double gnd_lvl, double tolerance); +void remove_bottom_points(std::vector &pts, float lvl); }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index e421e9c1d..ea016d5bb 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -360,18 +360,6 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) // removed them on purpose. No calculation will be done. po.m_supportdata->pts = po.transformed_support_points(); } - - // If the zero elevation mode is engaged, we have to filter out all the - // points that are on the bottom of the object - if (is_zero_elevation(po.config())) { - double tolerance = po.config().pad_enable.getBool() ? - po.m_config.pad_wall_thickness.getFloat() : - po.m_config.support_base_height.getFloat(); - - remove_bottom_points(po.m_supportdata->pts, - po.m_supportdata->emesh.ground_level(), - tolerance); - } } void SLAPrint::Steps::support_tree(SLAPrintObject &po) @@ -382,6 +370,17 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po) if (pcfg.embed_object) po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm); + + // If the zero elevation mode is engaged, we have to filter out all the + // points that are on the bottom of the object + if (is_zero_elevation(po.config())) { + double discard = po.config().pad_enable.getBool() ? + po.m_config.pad_wall_height.getFloat() : + po.m_config.support_base_height.getFloat() ; + + remove_bottom_points(po.m_supportdata->pts, + float(po.m_supportdata->emesh.ground_level() + discard)); + } po.m_supportdata->cfg = make_support_cfg(po.m_config); // po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points()); From 06223221466508358ee210161b5872dae2f883e0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Jun 2020 17:31:52 +0200 Subject: [PATCH 52/70] Create smaller supports in problematic areas with established strategies Completely remove the concept of CompactBridge. Replace it with Heads having the same back radius as front radius. Try to apply the same rules for mini supports as in the route_to_model step. Increased accuracy of bridge_mesh_intersect shot from support points Refining mini support integration --- src/libslic3r/SLA/SupportTree.cpp | 5 +- src/libslic3r/SLA/SupportTreeBuilder.cpp | 280 ++++++++----- src/libslic3r/SLA/SupportTreeBuilder.hpp | 102 +++-- src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 426 ++++++++++---------- src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 11 +- tests/sla_print/CMakeLists.txt | 2 +- tests/sla_print/sla_test_utils.cpp | 6 +- tests/sla_print/sla_treebuilder_tests.cpp | 96 +++++ 8 files changed, 571 insertions(+), 357 deletions(-) create mode 100644 tests/sla_print/sla_treebuilder_tests.cpp diff --git a/src/libslic3r/SLA/SupportTree.cpp b/src/libslic3r/SLA/SupportTree.cpp index 528778b68..2edc4d21b 100644 --- a/src/libslic3r/SLA/SupportTree.cpp +++ b/src/libslic3r/SLA/SupportTree.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -103,9 +104,11 @@ SupportTree::UPtr SupportTree::create(const SupportableMesh &sm, builder->m_ctl = ctl; if (sm.cfg.enabled) { - builder->build(sm); + // Execute takes care about the ground_level + SupportTreeBuildsteps::execute(*builder, sm); builder->merge_and_cleanup(); // clean metadata, leave only the meshes. } else { + // If a pad gets added later, it will be in the right Z level builder->ground_level = sm.emesh.ground_level(); } diff --git a/src/libslic3r/SLA/SupportTreeBuilder.cpp b/src/libslic3r/SLA/SupportTreeBuilder.cpp index cf6e7e020..8c9b54bb7 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.cpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.cpp @@ -155,6 +155,65 @@ Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp) return ret; } +Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) +{ + assert(length > 0.); + assert(r_back > 0.); + assert(r_pin > 0.); + + Contour3D mesh; + + // We create two spheres which will be connected with a robe that fits + // both circles perfectly. + + // Set up the model detail level + const double detail = 2*PI/steps; + + // We don't generate whole circles. Instead, we generate only the + // portions which are visible (not covered by the robe) To know the + // exact portion of the bottom and top circles we need to use some + // rules of tangent circles from which we can derive (using simple + // triangles the following relations: + + // The height of the whole mesh + const double h = r_back + r_pin + length; + double phi = PI / 2. - std::acos((r_back - r_pin) / h); + + // To generate a whole circle we would pass a portion of (0, Pi) + // To generate only a half horizontal circle we can pass (0, Pi/2) + // The calculated phi is an offset to the half circles needed to smooth + // the transition from the circle to the robe geometry + + auto&& s1 = sphere(r_back, make_portion(0, PI/2 + phi), detail); + auto&& s2 = sphere(r_pin, make_portion(PI/2 + phi, PI), detail); + + for(auto& p : s2.points) p.z() += h; + + mesh.merge(s1); + mesh.merge(s2); + + for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); + idx1 < s1.points.size() - 1; + idx1++, idx2++) + { + coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2); + coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1; + + mesh.faces3.emplace_back(i1s1, i2s1, i2s2); + mesh.faces3.emplace_back(i1s1, i2s2, i1s2); + } + + auto i1s1 = coord_t(s1.points.size()) - coord_t(steps); + auto i2s1 = coord_t(s1.points.size()) - 1; + auto i1s2 = coord_t(s1.points.size()); + auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1; + + mesh.faces3.emplace_back(i2s2, i2s1, i1s1); + mesh.faces3.emplace_back(i1s2, i2s2, i1s1); + + return mesh; +} + Head::Head(double r_big_mm, double r_small_mm, double length_mm, @@ -164,67 +223,17 @@ Head::Head(double r_big_mm, const size_t circlesteps) : steps(circlesteps) , dir(direction) - , tr(offset) + , pos(offset) , r_back_mm(r_big_mm) , r_pin_mm(r_small_mm) , width_mm(length_mm) , penetration_mm(penetration) { - assert(width_mm > 0.); - assert(r_back_mm > 0.); - assert(r_pin_mm > 0.); - - // We create two spheres which will be connected with a robe that fits - // both circles perfectly. - - // Set up the model detail level - const double detail = 2*PI/steps; - - // We don't generate whole circles. Instead, we generate only the - // portions which are visible (not covered by the robe) To know the - // exact portion of the bottom and top circles we need to use some - // rules of tangent circles from which we can derive (using simple - // triangles the following relations: - - // The height of the whole mesh - const double h = r_big_mm + r_small_mm + width_mm; - double phi = PI/2 - std::acos( (r_big_mm - r_small_mm) / h ); - - // To generate a whole circle we would pass a portion of (0, Pi) - // To generate only a half horizontal circle we can pass (0, Pi/2) - // The calculated phi is an offset to the half circles needed to smooth - // the transition from the circle to the robe geometry - - auto&& s1 = sphere(r_big_mm, make_portion(0, PI/2 + phi), detail); - auto&& s2 = sphere(r_small_mm, make_portion(PI/2 + phi, PI), detail); - - for(auto& p : s2.points) p.z() += h; - - mesh.merge(s1); - mesh.merge(s2); - - for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); - idx1 < s1.points.size() - 1; - idx1++, idx2++) - { - coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2); - coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1; - - mesh.faces3.emplace_back(i1s1, i2s1, i2s2); - mesh.faces3.emplace_back(i1s1, i2s2, i1s2); - } - - auto i1s1 = coord_t(s1.points.size()) - coord_t(steps); - auto i2s1 = coord_t(s1.points.size()) - 1; - auto i1s2 = coord_t(s1.points.size()); - auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1; - - mesh.faces3.emplace_back(i2s2, i2s1, i1s1); - mesh.faces3.emplace_back(i1s2, i2s2, i1s1); + mesh = pinhead(r_pin_mm, r_back_mm, width_mm, steps); // To simplify further processing, we translate the mesh so that the // last vertex of the pointing sphere (the pinpoint) will be at (0,0,0) - for(auto& p : mesh.points) p.z() -= (h + r_small_mm - penetration_mm); + for(auto& p : mesh.points) p.z() -= (fullwidth() - r_back_mm); } Pillar::Pillar(const Vec3d &jp, const Vec3d &endp, double radius, size_t st): @@ -305,34 +314,6 @@ Bridge::Bridge(const Vec3d &j1, const Vec3d &j2, double r_mm, size_t steps): for(auto& p : mesh.points) p = quater * p + j1; } -CompactBridge::CompactBridge(const Vec3d &sp, - const Vec3d &ep, - const Vec3d &n, - double r, - bool endball, - size_t steps) -{ - Vec3d startp = sp + r * n; - Vec3d dir = (ep - startp).normalized(); - Vec3d endp = ep - r * dir; - - Bridge br(startp, endp, r, steps); - mesh.merge(br.mesh); - - // now add the pins - double fa = 2*PI/steps; - auto upperball = sphere(r, Portion{PI / 2 - fa, PI}, fa); - for(auto& p : upperball.points) p += startp; - - if(endball) { - auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); - for(auto& p : lowerball.points) p += endp; - mesh.merge(lowerball); - } - - mesh.merge(upperball); -} - Pad::Pad(const TriangleMesh &support_mesh, const ExPolygons & model_contours, double ground_level, @@ -368,7 +349,6 @@ SupportTreeBuilder::SupportTreeBuilder(SupportTreeBuilder &&o) , m_pillars{std::move(o.m_pillars)} , m_bridges{std::move(o.m_bridges)} , m_crossbridges{std::move(o.m_crossbridges)} - , m_compact_bridges{std::move(o.m_compact_bridges)} , m_pad{std::move(o.m_pad)} , m_meshcache{std::move(o.m_meshcache)} , m_meshcache_valid{o.m_meshcache_valid} @@ -382,7 +362,6 @@ SupportTreeBuilder::SupportTreeBuilder(const SupportTreeBuilder &o) , m_pillars{o.m_pillars} , m_bridges{o.m_bridges} , m_crossbridges{o.m_crossbridges} - , m_compact_bridges{o.m_compact_bridges} , m_pad{o.m_pad} , m_meshcache{o.m_meshcache} , m_meshcache_valid{o.m_meshcache_valid} @@ -397,7 +376,6 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(SupportTreeBuilder &&o) m_pillars = std::move(o.m_pillars); m_bridges = std::move(o.m_bridges); m_crossbridges = std::move(o.m_crossbridges); - m_compact_bridges = std::move(o.m_compact_bridges); m_pad = std::move(o.m_pad); m_meshcache = std::move(o.m_meshcache); m_meshcache_valid = o.m_meshcache_valid; @@ -413,7 +391,6 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o) m_pillars = o.m_pillars; m_bridges = o.m_bridges; m_crossbridges = o.m_crossbridges; - m_compact_bridges = o.m_compact_bridges; m_pad = o.m_pad; m_meshcache = o.m_meshcache; m_meshcache_valid = o.m_meshcache_valid; @@ -443,12 +420,7 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh() const if (ctl().stopcondition()) break; merged.merge(j.mesh); } - - for (auto &cb : m_compact_bridges) { - if (ctl().stopcondition()) break; - merged.merge(cb.mesh); - } - + for (auto &bs : m_bridges) { if (ctl().stopcondition()) break; merged.merge(bs.mesh); @@ -499,7 +471,6 @@ const TriangleMesh &SupportTreeBuilder::merge_and_cleanup() m_pillars = {}; m_junctions = {}; m_bridges = {}; - m_compact_bridges = {}; return ret; } @@ -514,11 +485,130 @@ const TriangleMesh &SupportTreeBuilder::retrieve_mesh(MeshType meshtype) const return m_meshcache; } -bool SupportTreeBuilder::build(const SupportableMesh &sm) +template +static Hit min_hit(const C &hits) { - ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm; - return SupportTreeBuildsteps::execute(*this, sm); + auto mit = std::min_element(hits.begin(), hits.end(), + [](const Hit &h1, const Hit &h2) { + return h1.distance() < h2.distance(); + }); + + return *mit; } +EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &h) +{ + static const size_t SAMPLES = 8; + + // Move away slightly from the touching point to avoid raycasting on the + // inner surface of the mesh. + + const double& sd = msh.cfg.safety_distance_mm; + + auto& m = msh.emesh; + using HitResult = EigenMesh3D::hit_result; + + // Hit results + std::array hits; + + Vec3d s1 = h.pos, s2 = h.junction_point(); + + struct Rings { + double rpin; + double rback; + Vec3d spin; + Vec3d sback; + PointRing ring; + + Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); } + Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); } + } rings {h.r_pin_mm + sd, h.r_back_mm + sd, s1, s2, h.dir}; + + // We will shoot multiple rays from the head pinpoint in the direction + // of the pinhead robe (side) surface. The result will be the smallest + // hit distance. + + auto hitfn = [&m, &rings, sd](HitResult &hit, size_t i) { + // Point on the circle on the pin sphere + Vec3d ps = rings.pinring(i); + // This is the point on the circle on the back sphere + Vec3d p = rings.backring(i); + + // Point ps is not on mesh but can be inside or + // outside as well. This would cause many problems + // with ray-casting. To detect the position we will + // use the ray-casting result (which has an is_inside + // predicate). + + Vec3d n = (p - ps).normalized(); + auto q = m.query_ray_hit(ps + sd * n, n); + + if (q.is_inside()) { // the hit is inside the model + if (q.distance() > rings.rpin) { + // If we are inside the model and the hit + // distance is bigger than our pin circle + // diameter, it probably indicates that the + // support point was already inside the + // model, or there is really no space + // around the point. We will assign a zero + // hit distance to these cases which will + // enforce the function return value to be + // an invalid ray with zero hit distance. + // (see min_element at the end) + hit = HitResult(0.0); + } else { + // re-cast the ray from the outside of the + // object. The starting point has an offset + // of 2*safety_distance because the + // original ray has also had an offset + auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n); + hit = q2; + } + } else + hit = q; + }; + + ccr::enumerate(hits.begin(), hits.end(), hitfn); + + return min_hit(hits); } + +EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d) +{ + static const size_t SAMPLES = 8; + + Vec3d dir = (br.endp - br.startp).normalized(); + PointRing ring{dir}; + + using Hit = EigenMesh3D::hit_result; + + // Hit results + std::array hits; + + const double sd = std::isnan(safety_d) ? msh.cfg.safety_distance_mm : safety_d; + bool ins_check = sd < msh.cfg.safety_distance_mm; + + auto hitfn = [&br, &ring, &msh, dir, sd, ins_check](Hit & hit, size_t i) { + // Point on the circle on the pin sphere + Vec3d p = ring.get(i, br.startp, br.r + sd); + + auto hr = msh.emesh.query_ray_hit(p + sd * dir, dir); + + if (ins_check && hr.is_inside()) { + if (hr.distance() > 2 * br.r + sd) + hit = Hit(0.0); + else { + // re-cast the ray from the outside of the object + hit = msh.emesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, + dir); + } + } else + hit = hr; + }; + + ccr::enumerate(hits.begin(), hits.end(), hitfn); + + return min_hit(hits); } + +}} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index 90cf417c8..aec2a7a58 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -76,6 +76,8 @@ Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI), // sp: starting point Contour3D cylinder(double r, double h, size_t ssteps = 45, const Vec3d &sp = {0,0,0}); +Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45); + const constexpr long ID_UNSET = -1; struct Head { @@ -83,7 +85,7 @@ struct Head { size_t steps = 45; Vec3d dir = {0, 0, -1}; - Vec3d tr = {0, 0, 0}; + Vec3d pos = {0, 0, 0}; double r_back_mm = 1; double r_pin_mm = 0.5; @@ -120,17 +122,22 @@ struct Head { // the -1 z coordinate auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, dir); - for(auto& p : mesh.points) p = quatern * p + tr; + for(auto& p : mesh.points) p = quatern * p + pos; } + inline double real_width() const + { + return 2 * r_pin_mm + width_mm + 2 * r_back_mm ; + } + inline double fullwidth() const { - return 2 * r_pin_mm + width_mm + 2*r_back_mm - penetration_mm; + return real_width() - penetration_mm; } inline Vec3d junction_point() const { - return tr + ( 2 * r_pin_mm + width_mm + r_back_mm - penetration_mm)*dir; + return pos + (fullwidth() - r_back_mm) * dir; } inline double request_pillar_radius(double radius) const @@ -211,20 +218,6 @@ struct Bridge { size_t steps = 45); }; -// A bridge that spans from model surface to model surface with small connecting -// edges on the endpoints. Used for headless support points. -struct CompactBridge { - Contour3D mesh; - long id = ID_UNSET; - - CompactBridge(const Vec3d& sp, - const Vec3d& ep, - const Vec3d& n, - double r, - bool endball = true, - size_t steps = 45); -}; - // A wrapper struct around the pad struct Pad { TriangleMesh tmesh; @@ -242,6 +235,67 @@ struct Pad { bool empty() const { return tmesh.facets_count() == 0; } }; +// Give points on a 3D ring with given center, radius and orientation +// method based on: +// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space +template +class PointRing { + std::array m_phis; + + // Two vectors that will be perpendicular to each other and to the + // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a + // placeholder. + // a and b vectors are perpendicular to the ring direction and to each other. + // Together they define the plane where we have to iterate with the + // given angles in the 'm_phis' vector + Vec3d a = {0, 1, 0}, b; + double m_radius = 0.; + + static inline bool constexpr is_one(double val) + { + return std::abs(std::abs(val) - 1) < 1e-20; + } + +public: + + PointRing(const Vec3d &n) + { + m_phis = linspace_array(0., 2 * PI); + + // We have to address the case when the direction vector v (same as + // dir) is coincident with one of the world axes. In this case two of + // its components will be completely zero and one is 1.0. Our method + // becomes dangerous here due to division with zero. Instead, vector + // 'a' can be an element-wise rotated version of 'v' + if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) { + a = {n(Z), n(X), n(Y)}; + b = {n(Y), n(Z), n(X)}; + } + else { + a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize(); + b = a.cross(n); + } + } + + Vec3d get(size_t idx, const Vec3d src, double r) const + { + double phi = m_phis[idx]; + double sinphi = std::sin(phi); + double cosphi = std::cos(phi); + + double rpscos = r * cosphi; + double rpssin = r * sinphi; + + // Point on the sphere + return {src(X) + rpscos * a(X) + rpssin * b(X), + src(Y) + rpscos * a(Y) + rpssin * b(Y), + src(Z) + rpscos * a(Z) + rpssin * b(Z)}; + } +}; + +EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan("")); +EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan("")); + // This class will hold the support tree meshes with some additional // bookkeeping as well. Various parts of the support geometry are stored // separately and are merged when the caller queries the merged mesh. The @@ -264,7 +318,6 @@ class SupportTreeBuilder: public SupportTree { std::vector m_junctions; std::vector m_bridges; std::vector m_crossbridges; - std::vector m_compact_bridges; Pad m_pad; using Mutex = ccr::SpinningMutex; @@ -415,15 +468,6 @@ public: return _add_bridge(m_crossbridges, std::forward(args)...); } - template const CompactBridge& add_compact_bridge(Args&&...args) - { - std::lock_guard lk(m_mutex); - m_compact_bridges.emplace_back(std::forward(args)...); - m_compact_bridges.back().id = long(m_compact_bridges.size() - 1); - m_meshcache_valid = false; - return m_compact_bridges.back(); - } - Head &head(unsigned id) { std::lock_guard lk(m_mutex); @@ -488,8 +532,6 @@ public: virtual const TriangleMesh &retrieve_mesh( MeshType meshtype = MeshType::Support) const override; - - bool build(const SupportableMesh &supportable_mesh); }; }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 29ad6057f..df9de3555 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -42,6 +42,8 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, { if(sm.pts.empty()) return false; + builder.ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm; + SupportTreeBuildsteps alg(builder, sm); // Let's define the individual steps of the processing. We can experiment @@ -166,64 +168,6 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, return pc == ABORT; } -// Give points on a 3D ring with given center, radius and orientation -// method based on: -// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space -template -class PointRing { - std::array m_phis; - - // Two vectors that will be perpendicular to each other and to the - // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a - // placeholder. - // a and b vectors are perpendicular to the ring direction and to each other. - // Together they define the plane where we have to iterate with the - // given angles in the 'm_phis' vector - Vec3d a = {0, 1, 0}, b; - double m_radius = 0.; - - static inline bool constexpr is_one(double val) - { - return std::abs(std::abs(val) - 1) < 1e-20; - } - -public: - - PointRing(const Vec3d &n) - { - m_phis = linspace_array(0., 2 * PI); - - // We have to address the case when the direction vector v (same as - // dir) is coincident with one of the world axes. In this case two of - // its components will be completely zero and one is 1.0. Our method - // becomes dangerous here due to division with zero. Instead, vector - // 'a' can be an element-wise rotated version of 'v' - if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) { - a = {n(Z), n(X), n(Y)}; - b = {n(Y), n(Z), n(X)}; - } - else { - a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize(); - b = a.cross(n); - } - } - - Vec3d get(size_t idx, const Vec3d src, double r) const - { - double phi = m_phis[idx]; - double sinphi = std::sin(phi); - double cosphi = std::cos(phi); - - double rpscos = r * cosphi; - double rpssin = r * sinphi; - - // Point on the sphere - return {src(X) + rpscos * a(X) + rpssin * b(X), - src(Y) + rpscos * a(Y) + rpssin * b(Y), - src(Z) + rpscos * a(Z) + rpssin * b(Z)}; - } -}; - template static Hit min_hit(const C &hits) { @@ -312,7 +256,7 @@ EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( } EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( - const Vec3d &src, const Vec3d &dir, double r, bool ins_check) + const Vec3d &src, const Vec3d &dir, double r, double safety_d) { static const size_t SAMPLES = 8; PointRing ring{dir}; @@ -321,16 +265,19 @@ EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( // Hit results std::array hits; + + double sd = std::isnan(safety_d) ? m_cfg.safety_distance_mm : safety_d; + sd = sd * r / m_cfg.head_back_radius_mm; + + bool ins_check = sd < m_cfg.safety_distance_mm; ccr::enumerate(hits.begin(), hits.end(), - [this, r, src, ins_check, &ring, dir] (Hit &hit, size_t i) { - - const double sd = m_cfg.safety_distance_mm; - + [this, r, src, ins_check, &ring, dir, sd] (Hit &hit, size_t i) { + // Point on the circle on the pin sphere Vec3d p = ring.get(i, src, r + sd); - auto hr = m_mesh.query_ray_hit(p + sd * dir, dir); + auto hr = m_mesh.query_ray_hit(p + r * dir, dir); if(ins_check && hr.is_inside()) { if(hr.distance() > 2 * r + sd) hit = Hit(0.0); @@ -460,7 +407,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, Vec3d bridgestart = headjp; Vec3d bridgeend = nearjp_u; - double max_len = m_cfg.max_bridge_length_mm; + double max_len = r * m_cfg.max_bridge_length_mm / m_cfg.head_back_radius_mm; double max_slope = m_cfg.bridge_slope; double zdiff = 0.0; @@ -494,7 +441,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, // There will be a minimum distance from the ground where the // bridge is allowed to connect. This is an empiric value. - double minz = m_builder.ground_level + 2 * m_cfg.head_width_mm; + double minz = m_builder.ground_level + 4 * head.r_back_mm; if(bridgeend(Z) < minz) return false; double t = bridge_mesh_distance(bridgestart, dirv(bridgestart, bridgeend), r); @@ -509,7 +456,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, if(zdiff > 0) { m_builder.add_pillar(head.id, bridgestart, r); m_builder.add_junction(bridgestart, r); - m_builder.add_bridge(bridgestart, bridgeend, head.r_back_mm); + m_builder.add_bridge(bridgestart, bridgeend, r); } else { m_builder.add_bridge(head.id, bridgeend); } @@ -520,40 +467,6 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, return true; } -bool SupportTreeBuildsteps::search_pillar_and_connect(const Head &head) -{ - PointIndex spindex = m_pillar_index.guarded_clone(); - - long nearest_id = ID_UNSET; - - Vec3d querypoint = head.junction_point(); - - while(nearest_id < 0 && !spindex.empty()) { m_thr(); - // loop until a suitable head is not found - // if there is a pillar closer than the cluster center - // (this may happen as the clustering is not perfect) - // than we will bridge to this closer pillar - - Vec3d qp(querypoint(X), querypoint(Y), m_builder.ground_level); - auto qres = spindex.nearest(qp, 1); - if(qres.empty()) break; - - auto ne = qres.front(); - nearest_id = ne.second; - - if(nearest_id >= 0) { - if(size_t(nearest_id) < m_builder.pillarcount()) { - if(!connect_to_nearpillar(head, nearest_id)) { - nearest_id = ID_UNSET; // continue searching - spindex.remove(ne); // without the current pillar - } - } - } - } - - return nearest_id >= 0; -} - void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, const Vec3d &sourcedir, double radius, @@ -565,9 +478,10 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, Vec3d endp = {jp(X), jp(Y), gndlvl}; double sd = m_cfg.pillar_base_safety_distance_mm; long pillar_id = ID_UNSET; - double min_dist = sd + m_cfg.base_radius_mm + EPSILON; + bool can_add_base = radius >= m_cfg.head_back_radius_mm; + double base_r = can_add_base ? m_cfg.base_radius_mm : 0.; + double min_dist = sd + base_r + EPSILON; double dist = 0; - bool can_add_base = true; bool normal_mode = true; // If in zero elevation mode and the pillar is too close to the model body, @@ -612,7 +526,7 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, endp = jp + std::get<0>(result.optimum) * dir; Vec3d pgnd = {endp(X), endp(Y), gndlvl}; - can_add_base = result.score > min_dist; + can_add_base = can_add_base && result.score > min_dist; double gnd_offs = m_mesh.ground_level_offset(); auto abort_in_shame = @@ -712,84 +626,85 @@ void SupportTreeBuildsteps::filter() auto [polar, azimuth] = dir_to_spheric(n); // skip if the tilt is not sane - if(polar >= PI - m_cfg.normal_cutoff_angle) { + if(polar < PI - m_cfg.normal_cutoff_angle) return; - // We saturate the polar angle to 3pi/4 - polar = std::max(polar, 3*PI / 4); - - // save the head (pinpoint) position - Vec3d hp = m_points.row(fidx); - - double w = m_cfg.head_width_mm + - m_cfg.head_back_radius_mm + - 2*m_cfg.head_front_radius_mm; - - double pin_r = double(m_support_pts[fidx].head_front_radius); - - // Reassemble the now corrected normal - auto nn = spheric_to_dir(polar, azimuth).normalized(); - - // check available distance - EigenMesh3D::hit_result t - = pinhead_mesh_intersect(hp, // touching point - nn, // normal - pin_r, - m_cfg.head_back_radius_mm, - w); - - if(t.distance() <= w) { - - // Let's try to optimize this angle, there might be a - // viable normal that doesn't collide with the model - // geometry and its very close to the default. - - StopCriteria stc; - stc.max_iterations = m_cfg.optimizer_max_iterations; - stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; - stc.stop_score = w; // space greater than w is enough - GeneticOptimizer solver(stc); - solver.seed(0); // we want deterministic behavior - - auto oresult = solver.optimize_max( - [this, pin_r, w, hp](double plr, double azm) - { - auto dir = spheric_to_dir(plr, azm).normalized(); - - double score = pinhead_mesh_distance( - hp, dir, pin_r, m_cfg.head_back_radius_mm, w); - - return score; - }, - initvals(polar, azimuth), // start with what we have - bound(3 * PI / 4, PI), // Must not exceed the tilt limit - bound(-PI, PI) // azimuth can be a full search - ); - - if(oresult.score > w) { - polar = std::get<0>(oresult.optimum); - azimuth = std::get<1>(oresult.optimum); - nn = spheric_to_dir(polar, azimuth).normalized(); - t = EigenMesh3D::hit_result(oresult.score); - } - } - - // save the verified and corrected normal - m_support_nmls.row(fidx) = nn; - - if (t.distance() > w) { - // Check distance from ground, we might have zero elevation. - if (hp(Z) + w * nn(Z) < m_builder.ground_level) { - addfn(m_iheadless, fidx); - } else { - // mark the point for needing a head. - addfn(m_iheads, fidx); - } - } else if (polar >= 3 * PI / 4) { - // Headless supports do not tilt like the headed ones - // so the normal should point almost to the ground. - addfn(m_iheadless, fidx); + // We saturate the polar angle to 3pi/4 + polar = std::max(polar, 3*PI / 4); + + // save the head (pinpoint) position + Vec3d hp = m_points.row(fidx); + + // The distance needed for a pinhead to not collide with model. + double w = m_cfg.head_width_mm + + m_cfg.head_back_radius_mm + + 2*m_cfg.head_front_radius_mm; + + double pin_r = double(m_support_pts[fidx].head_front_radius); + + // Reassemble the now corrected normal + auto nn = spheric_to_dir(polar, azimuth).normalized(); + + // check available distance + EigenMesh3D::hit_result t + = pinhead_mesh_intersect(hp, // touching point + nn, // normal + pin_r, + m_cfg.head_back_radius_mm, + w); + + if(t.distance() <= w) { + + // Let's try to optimize this angle, there might be a + // viable normal that doesn't collide with the model + // geometry and its very close to the default. + + StopCriteria stc; + stc.max_iterations = m_cfg.optimizer_max_iterations; + stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; + stc.stop_score = w; // space greater than w is enough + GeneticOptimizer solver(stc); + solver.seed(0); // we want deterministic behavior + + auto oresult = solver.optimize_max( + [this, pin_r, w, hp](double plr, double azm) + { + auto dir = spheric_to_dir(plr, azm).normalized(); + + double score = pinhead_mesh_intersect( + hp, dir, pin_r, m_cfg.head_back_radius_mm, w).distance(); + + return score; + }, + initvals(polar, azimuth), // start with what we have + bound(3 * PI / 4, PI), // Must not exceed the tilt limit + bound(-PI, PI) // azimuth can be a full search + ); + + if(oresult.score > w) { + polar = std::get<0>(oresult.optimum); + azimuth = std::get<1>(oresult.optimum); + nn = spheric_to_dir(polar, azimuth).normalized(); + t = EigenMesh3D::hit_result(oresult.score); } } + + // save the verified and corrected normal + m_support_nmls.row(fidx) = nn; + + if (t.distance() > w) { + // Check distance from ground, we might have zero elevation. + if (hp(Z) + w * nn(Z) < m_builder.ground_level) { + addfn(m_iheadless, fidx); + } else { + // mark the point for needing a head. + addfn(m_iheads, fidx); + } + } else if (polar >= 3 * PI / 4) { + // Headless supports do not tilt like the headed ones + // so the normal should point almost to the ground. + addfn(m_iheadless, fidx); + } + }; ccr::enumerate(filtered_indices.begin(), filtered_indices.end(), filterfn); @@ -811,6 +726,27 @@ void SupportTreeBuildsteps::add_pinheads() m_support_pts[i].pos.cast() // displacement ); } + + for (unsigned i : m_iheadless) { + const auto R = double(m_support_pts[i].head_front_radius); + + // The support point position on the mesh + Vec3d sph = m_support_pts[i].pos.cast(); + + // Get an initial normal from the filtering step + Vec3d n = m_support_nmls.row(i); + + // First we need to determine the available space for a mini pinhead. + // The goal is the move away from the model a little bit to make the + // contact point small as possible and avoid pearcing the model body. + double pin_space = std::min(2 * R, bridge_mesh_distance(sph, n, R, 0.)); + + if (pin_space <= 0) continue; + + m_iheads.emplace_back(i); + m_builder.add_head(i, R, R, pin_space, + m_cfg.head_penetration_mm, n, sph); + } } void SupportTreeBuildsteps::classify() @@ -864,8 +800,6 @@ void SupportTreeBuildsteps::classify() void SupportTreeBuildsteps::routing_to_ground() { - const double pradius = m_cfg.head_back_radius_mm; - ClusterEl cl_centroids; cl_centroids.reserve(m_pillar_clusters.size()); @@ -931,7 +865,7 @@ void SupportTreeBuildsteps::routing_to_ground() Vec3d pstart = sidehead.junction_point(); // Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; // Could not find a pillar, create one - create_ground_pillar(pstart, sidehead.dir, pradius, sidehead.id); + create_ground_pillar(pstart, sidehead.dir, sidehead.r_back_mm, sidehead.id); } } } @@ -943,7 +877,7 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir) double r = head.r_back_mm; double t = bridge_mesh_distance(hjp, dir, head.r_back_mm); double d = 0, tdown = 0; - t = std::min(t, m_cfg.max_bridge_length_mm); + t = std::min(t, m_cfg.max_bridge_length_mm * r / m_cfg.head_back_radius_mm); while (d < t && !std::isinf(tdown = bridge_mesh_distance(hjp + d * dir, DOWN, r))) d += r; @@ -1041,6 +975,42 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) return true; } +bool SupportTreeBuildsteps::search_pillar_and_connect(const Head &source) +{ + // Hope that a local copy takes less time than the whole search loop. + // We also need to remove elements progressively from the copied index. + PointIndex spindex = m_pillar_index.guarded_clone(); + + long nearest_id = ID_UNSET; + + Vec3d querypt = source.junction_point(); + + while(nearest_id < 0 && !spindex.empty()) { m_thr(); + // loop until a suitable head is not found + // if there is a pillar closer than the cluster center + // (this may happen as the clustering is not perfect) + // than we will bridge to this closer pillar + + Vec3d qp(querypt(X), querypt(Y), m_builder.ground_level); + auto qres = spindex.nearest(qp, 1); + if(qres.empty()) break; + + auto ne = qres.front(); + nearest_id = ne.second; + + if(nearest_id >= 0) { + if(size_t(nearest_id) < m_builder.pillarcount()) { + if(!connect_to_nearpillar(source, nearest_id)) { + nearest_id = ID_UNSET; // continue searching + spindex.remove(ne); // without the current pillar + } + } + } + } + + return nearest_id >= 0; +} + void SupportTreeBuildsteps::routing_to_model() { // We need to check if there is an easy way out to the bed surface. @@ -1054,18 +1024,18 @@ void SupportTreeBuildsteps::routing_to_model() auto& head = m_builder.head(idx); // Search nearby pillar - if(search_pillar_and_connect(head)) { head.transform(); return; } + if (search_pillar_and_connect(head)) { head.transform(); return; } // Cannot connect to nearby pillar. We will try to search for // a route to the ground. - if(connect_to_ground(head)) { head.transform(); return; } + if (connect_to_ground(head)) { head.transform(); return; } // No route to the ground, so connect to the model body as a last resort if (connect_to_model_body(head)) { return; } // We have failed to route this head. BOOST_LOG_TRIVIAL(warning) - << "Failed to route model facing support point. ID: " << idx; + << "Failed to route model facing support point. ID: " << idx; head.invalidate(); }); @@ -1107,9 +1077,10 @@ void SupportTreeBuildsteps::interconnect_pillars() // connections are already enough for the pillar if(pillar.links >= neighbors) return; + double max_d = d * pillar.r / m_cfg.head_back_radius_mm; // Query all remaining points within reach - auto qres = m_pillar_index.query([qp, d](const PointIndexEl& e){ - return distance(e.first, qp) < d; + auto qres = m_pillar_index.query([qp, max_d](const PointIndexEl& e){ + return distance(e.first, qp) < max_d; }); // sort the result by distance (have to check if this is needed) @@ -1288,37 +1259,54 @@ void SupportTreeBuildsteps::routing_headless() // We will sink the pins into the model surface for a distance of 1/3 of // the pin radius - for(unsigned i : m_iheadless) { - m_thr(); - - const auto R = double(m_support_pts[i].head_front_radius); - const double HWIDTH_MM = std::min(R, m_cfg.head_penetration_mm); - - // Exact support position - Vec3d sph = m_support_pts[i].pos.cast(); - Vec3d n = m_support_nmls.row(i); // mesh outward normal - Vec3d sp = sph - n * HWIDTH_MM; // stick head start point - - Vec3d sj = sp + R * n; // stick start point - - // This is only for checking - double idist = bridge_mesh_distance(sph, DOWN, R, true); - double realdist = ray_mesh_intersect(sj, DOWN).distance(); - double dist = realdist; - - if (std::isinf(dist)) dist = sph(Z) - m_builder.ground_level; - - if(std::isnan(idist) || idist < 2*R || std::isnan(dist) || dist < 2*R) { - BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless" - << " support stick at: " - << sj.transpose(); - continue; - } - - bool use_endball = !std::isinf(realdist); - Vec3d ej = sj + (dist + HWIDTH_MM) * DOWN ; - m_builder.add_compact_bridge(sp, ej, n, R, use_endball); - } +// for(unsigned i : m_iheadless) { +// m_thr(); + +// const auto R = double(m_support_pts[i].head_front_radius); + +// // The support point position on the mesh +// Vec3d sph = m_support_pts[i].pos.cast(); + +// // Get an initial normal from the filtering step +// Vec3d n = m_support_nmls.row(i); + +// // First we need to determine the available space for a mini pinhead. +// // The goal is the move away from the model a little bit to make the +// // contact point small as possible and avoid pearcing the model body. +// double pin_space = std::min(2 * R, bridge_mesh_distance(sph, n, R, 0.)); + +// if (pin_space <= 0) continue; + +// auto &head = m_builder.add_head(i, R, R, pin_space, +// m_cfg.head_penetration_mm, n, sph); + +// // collision check + +// m_head_to_ground_scans[i] = +// bridge_mesh_intersect(head.junction_point(), DOWN, R); + +// // Here the steps will be similar as in route_to_model step: +// // 1. Search for a nearby pillar, include other mini pillars + +// // Search nearby pillar +// if (search_pillar_and_connect(head)) { head.transform(); continue; } + +// if (std::isinf(m_head_to_ground_scans[i].distance())) { +// create_ground_pillar(head.junction_point(), head.dir, m_cfg.head_back_radius_mm, head.id); +// } + +// // Cannot connect to nearby pillar. We will try to search for +// // a route to the ground. +// if (connect_to_ground(head)) { head.transform(); continue; } + +// // No route to the ground, so connect to the model body as a last resort +// if (connect_to_model_body(head)) { continue; } + +// BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless" +// << " support stick at: " +// << sph.transpose(); +// head.invalidate(); +// } } } diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index cfe78fe97..1962f802b 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -229,11 +229,6 @@ class SupportTreeBuildsteps { double r_pin, double r_back, double width); - - template - inline double pinhead_mesh_distance(Args&&...args) { - return pinhead_mesh_intersect(std::forward(args)...).distance(); - } // Checking bridge (pillar and stick as well) intersection with the model. // If the function is used for headless sticks, the ins_check parameter @@ -247,7 +242,7 @@ class SupportTreeBuildsteps { const Vec3d& s, const Vec3d& dir, double r, - bool ins_check = false); + double safety_d = std::nan("")); template inline double bridge_mesh_distance(Args&&...args) { @@ -268,8 +263,8 @@ class SupportTreeBuildsteps { inline bool connect_to_ground(Head& head); bool connect_to_model_body(Head &head); - - bool search_pillar_and_connect(const Head& head); + + bool search_pillar_and_connect(const Head& source); // This is a proxy function for pillar creation which will mind the gap // between the pad and the model bottom in zero elevation mode. diff --git a/tests/sla_print/CMakeLists.txt b/tests/sla_print/CMakeLists.txt index 9d47f3ae4..f6b261fda 100644 --- a/tests/sla_print/CMakeLists.txt +++ b/tests/sla_print/CMakeLists.txt @@ -1,7 +1,7 @@ get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp sla_print_tests.cpp - sla_test_utils.hpp sla_test_utils.cpp + sla_test_utils.hpp sla_test_utils.cpp sla_treebuilder_tests.cpp sla_raycast_tests.cpp) target_link_libraries(${_TEST_NAME}_tests test_common libslic3r) set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 1eaf796c0..5a3bd82a0 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -129,8 +129,7 @@ void test_supports(const std::string &obj_filename, // If there is no elevation, support points shall be removed from the // bottom of the object. if (std::abs(supportcfg.object_elevation_mm) < EPSILON) { - sla::remove_bottom_points(support_points, zmin, - supportcfg.base_height_mm); + sla::remove_bottom_points(support_points, zmin + supportcfg.base_height_mm); } else { // Should be support points at least on the bottom of the model REQUIRE_FALSE(support_points.empty()); @@ -141,7 +140,8 @@ void test_supports(const std::string &obj_filename, // Generate the actual support tree sla::SupportTreeBuilder treebuilder; - treebuilder.build(sla::SupportableMesh{emesh, support_points, supportcfg}); + sla::SupportableMesh sm{emesh, support_points, supportcfg}; + sla::SupportTreeBuildsteps::execute(treebuilder, sm); check_support_tree_integrity(treebuilder, supportcfg); diff --git a/tests/sla_print/sla_treebuilder_tests.cpp b/tests/sla_print/sla_treebuilder_tests.cpp new file mode 100644 index 000000000..c785e4ba5 --- /dev/null +++ b/tests/sla_print/sla_treebuilder_tests.cpp @@ -0,0 +1,96 @@ +#include +#include + +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/SLA/SupportTreeBuilder.hpp" + +TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]") { + using namespace Slic3r; + + TriangleMesh cube = make_cube(10., 10., 10.); + + sla::SupportConfig cfg = {}; // use default config + sla::SupportPoints pts = {{10.f, 5.f, 5.f, float(cfg.head_front_radius_mm), false}}; + sla::SupportableMesh sm{cube, pts, cfg}; + + SECTION("Bridge is straight horizontal and pointing away from the cube") { + + sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 5.}, + pts[0].head_front_radius); + + auto hit = sla::query_hit(sm, bridge); + + REQUIRE(std::isinf(hit.distance())); + + cube.merge(sla::to_triangle_mesh(bridge.mesh)); + cube.require_shared_vertices(); + cube.WriteOBJFile("cube1.obj"); + } + + SECTION("Bridge is tilted down in 45 degrees, pointing away from the cube") { + sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 0.}, + pts[0].head_front_radius); + + auto hit = sla::query_hit(sm, bridge); + + REQUIRE(std::isinf(hit.distance())); + + cube.merge(sla::to_triangle_mesh(bridge.mesh)); + cube.require_shared_vertices(); + cube.WriteOBJFile("cube2.obj"); + } +} + + +TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") { + using namespace Slic3r; + + TriangleMesh sphere = make_sphere(1.); + + sla::SupportConfig cfg = {}; // use default config + cfg.head_back_radius_mm = cfg.head_front_radius_mm; + sla::SupportPoints pts = {{1.f, 0.f, 0.f, float(cfg.head_front_radius_mm), false}}; + sla::SupportableMesh sm{sphere, pts, cfg}; + + SECTION("Bridge is straight horizontal and pointing away from the sphere") { + + sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., 0.}, + pts[0].head_front_radius); + + auto hit = sla::query_hit(sm, bridge); + + sphere.merge(sla::to_triangle_mesh(bridge.mesh)); + sphere.require_shared_vertices(); + sphere.WriteOBJFile("sphere1.obj"); + + REQUIRE(std::isinf(hit.distance())); + } + + SECTION("Bridge is tilted down 45 deg and pointing away from the sphere") { + + sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., -2.}, + pts[0].head_front_radius); + + auto hit = sla::query_hit(sm, bridge); + + sphere.merge(sla::to_triangle_mesh(bridge.mesh)); + sphere.require_shared_vertices(); + sphere.WriteOBJFile("sphere2.obj"); + + REQUIRE(std::isinf(hit.distance())); + } + + SECTION("Bridge is tilted down 90 deg and pointing away from the sphere") { + + sla::Bridge bridge(pts[0].pos.cast(), Vec3d{1., 0., -2.}, + pts[0].head_front_radius); + + auto hit = sla::query_hit(sm, bridge); + + sphere.merge(sla::to_triangle_mesh(bridge.mesh)); + sphere.require_shared_vertices(); + sphere.WriteOBJFile("sphere3.obj"); + + REQUIRE(std::isinf(hit.distance())); + } +} From 67b61c23f7cbc9061834b08d358ea9f53418ef46 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 3 Jun 2020 17:42:29 +0200 Subject: [PATCH 53/70] Remove the discard region for bottom points removal. This was a workaround for small supports not to end up in the middle of the gap between the pad and the object. The issue needs to be solved at the support generation. --- src/libslic3r/SLAPrintSteps.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index ea016d5bb..defc5246c 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -374,12 +374,11 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po) // If the zero elevation mode is engaged, we have to filter out all the // points that are on the bottom of the object if (is_zero_elevation(po.config())) { - double discard = po.config().pad_enable.getBool() ? - po.m_config.pad_wall_height.getFloat() : - po.m_config.support_base_height.getFloat() ; +// double discard = pcfg.embed_object.object_gap_mm / +// std::cos(po.m_supportdata->cfg.bridge_slope) ; remove_bottom_points(po.m_supportdata->pts, - float(po.m_supportdata->emesh.ground_level() + discard)); + float(po.m_supportdata->emesh.ground_level() + EPSILON)); } po.m_supportdata->cfg = make_support_cfg(po.m_config); From 7b6565abeb0f9456b6bc17e0d9ef98f2a706c06c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 5 Jun 2020 20:19:19 +0200 Subject: [PATCH 54/70] Improvements on mini pillars --- src/libslic3r/Point.hpp | 11 +- src/libslic3r/SLA/EigenMesh3D.hpp | 2 + src/libslic3r/SLA/SupportTreeBuilder.cpp | 16 ++ src/libslic3r/SLA/SupportTreeBuilder.hpp | 6 + src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 178 ++++++++++---------- src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 2 +- 6 files changed, 124 insertions(+), 91 deletions(-) diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index b818cd8be..8c1c69fde 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -60,10 +60,13 @@ inline int64_t cross2(const Vec2i64 &v1, const Vec2i64 &v2) { return v1(0) * v2( inline float cross2(const Vec2f &v1, const Vec2f &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } inline double cross2(const Vec2d &v1, const Vec2d &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } -inline Vec2i32 to_2d(const Vec2i32 &pt3) { return Vec2i32(pt3(0), pt3(1)); } -inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); } -inline Vec2f to_2d(const Vec3f &pt3) { return Vec2f (pt3(0), pt3(1)); } -inline Vec2d to_2d(const Vec3d &pt3) { return Vec2d (pt3(0), pt3(1)); } +template Eigen::Matrix +to_2d(const Eigen::Matrix &ptN) { return {ptN(0), ptN(1)}; } + +//inline Vec2i32 to_2d(const Vec3i32 &pt3) { return Vec2i32(pt3(0), pt3(1)); } +//inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); } +//inline Vec2f to_2d(const Vec3f &pt3) { return Vec2f (pt3(0), pt3(1)); } +//inline Vec2d to_2d(const Vec3d &pt3) { return Vec2d (pt3(0), pt3(1)); } inline Vec3d to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); } inline Vec3f to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); } diff --git a/src/libslic3r/SLA/EigenMesh3D.hpp b/src/libslic3r/SLA/EigenMesh3D.hpp index b932c0c18..7b7562d47 100644 --- a/src/libslic3r/SLA/EigenMesh3D.hpp +++ b/src/libslic3r/SLA/EigenMesh3D.hpp @@ -125,6 +125,8 @@ public: } Vec3d normal_by_face_id(int face_id) const; + + const TriangleMesh * get_triangle_mesh() const { return m_tm; } }; // Calculate the normals for the selected points (from 'points' set) on the diff --git a/src/libslic3r/SLA/SupportTreeBuilder.cpp b/src/libslic3r/SLA/SupportTreeBuilder.cpp index 8c9b54bb7..121a00145 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.cpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.cpp @@ -314,6 +314,22 @@ Bridge::Bridge(const Vec3d &j1, const Vec3d &j2, double r_mm, size_t steps): for(auto& p : mesh.points) p = quater * p + j1; } +Bridge::Bridge(const Vec3d &j1, + const Vec3d &j2, + double r1_mm, + double r2_mm, + size_t steps) +{ + Vec3d dir = (j2 - j1); + mesh = pinhead(r1_mm, r2_mm, dir.norm(), steps); + dir.normalize(); + + using Quaternion = Eigen::Quaternion; + auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir); + + for(auto& p : mesh.points) p = quater * p + j1; +} + Pad::Pad(const TriangleMesh &support_mesh, const ExPolygons & model_contours, double ground_level, diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index aec2a7a58..66462ebbd 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -216,6 +216,12 @@ struct Bridge { const Vec3d &j2, double r_mm = 0.8, size_t steps = 45); + + Bridge(const Vec3d &j1, + const Vec3d &j2, + double r1_mm, + double r2_mm, + size_t steps = 45); }; // A wrapper struct around the pad diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index df9de3555..e94e3c402 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -467,107 +467,86 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, return true; } -void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, +bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, const Vec3d &sourcedir, double radius, long head_id) { - const double SLOPE = 1. / std::cos(m_cfg.bridge_slope); - - double gndlvl = m_builder.ground_level; - Vec3d endp = {jp(X), jp(Y), gndlvl}; double sd = m_cfg.pillar_base_safety_distance_mm; long pillar_id = ID_UNSET; bool can_add_base = radius >= m_cfg.head_back_radius_mm; double base_r = can_add_base ? m_cfg.base_radius_mm : 0.; + double gndlvl = m_builder.ground_level; + if (!can_add_base) gndlvl -= m_mesh.ground_level_offset(); + Vec3d endp = {jp(X), jp(Y), gndlvl}; double min_dist = sd + base_r + EPSILON; - double dist = 0; bool normal_mode = true; - - // If in zero elevation mode and the pillar is too close to the model body, - // the support pillar can not be placed in the gap between the model and - // the pad, and the pillar bases must not touch the model body either. - // To solve this, a corrector bridge is inserted between the starting point - // (jp) and the new pillar. - if (m_cfg.object_elevation_mm < EPSILON - && (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) { - // Get the distance from the mesh. This can be later optimized - // to get the distance in 2D plane because we are dealing with - // the ground level only. + Vec3d dir = sourcedir; - normal_mode = false; - - // The min distance needed to move away from the model in XY plane. - double current_d = min_dist - dist; - double current_bride_d = SLOPE * current_d; + auto to_floor = [gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; + if (m_cfg.object_elevation_mm < EPSILON) + { // get a suitable direction for the corrector bridge. It is the // original sourcedir's azimuth but the polar angle is saturated to the // configured bridge slope. - auto [polar, azimuth] = dir_to_spheric(sourcedir); + auto [polar, azimuth] = dir_to_spheric(dir); polar = PI - m_cfg.bridge_slope; - auto dir = spheric_to_dir(polar, azimuth).normalized(); - - StopCriteria scr; - scr.stop_score = min_dist; - SubplexOptimizer solver(scr); - - // Search for a distance along the corrector bridge to move the endpoint - // sufficiently away form the model body. The first few optimization - // cycles should succeed here. - auto result = solver.optimize_max( - [this, dir, jp, gndlvl](double mv) { - Vec3d endpt = jp + mv * dir; - endpt(Z) = gndlvl; - return std::sqrt(m_mesh.squared_distance(endpt)); - }, - initvals(current_bride_d), - bound(0.0, m_cfg.max_bridge_length_mm - current_bride_d)); - - endp = jp + std::get<0>(result.optimum) * dir; - Vec3d pgnd = {endp(X), endp(Y), gndlvl}; - can_add_base = can_add_base && result.score > min_dist; - - double gnd_offs = m_mesh.ground_level_offset(); - auto abort_in_shame = - [gnd_offs, &normal_mode, &can_add_base, &endp, jp, gndlvl]() - { - normal_mode = true; - can_add_base = false; // Nothing left to do, hope for the best - endp = {jp(X), jp(Y), gndlvl - gnd_offs }; - }; - - // We have to check if the bridge is feasible. - if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm()) - abort_in_shame(); - else { - // If the new endpoint is below ground, do not make a pillar - if (endp(Z) < gndlvl) - endp = endp - SLOPE * (gndlvl - endp(Z)) * dir; // back off - else { - - auto hit = bridge_mesh_intersect(endp, DOWN, radius); - if (!std::isinf(hit.distance())) abort_in_shame(); - - pillar_id = m_builder.add_pillar(endp, pgnd, radius); - - if (can_add_base) - m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm, - m_cfg.base_radius_mm); - } - - m_builder.add_bridge(jp, endp, radius); - m_builder.add_junction(endp, radius); - - // Add a degenerated pillar and the bridge. - // The degenerate pillar will have zero length and it will - // prevent from queries of head_pillar() to have non-existing - // pillar when the head should have one. - if (head_id >= 0) + Vec3d dir = spheric_to_dir(polar, azimuth).normalized(); + + // Check the distance of the endpoint and the closest point on model + // body. It should be greater than the min_dist which is + // the safety distance from the model. It includes the pad gap if in + // zero elevation mode. + // + // Try to move along the established bridge direction to dodge the + // forbidden region for the endpoint. + double t = -radius; + while (std::sqrt(m_mesh.squared_distance(to_floor(endp))) < min_dist || + !std::isinf(bridge_mesh_distance(endp, DOWN, radius))) { + t += radius; + endp = jp + t * dir; + normal_mode = false; + + if (t > m_cfg.max_bridge_length_mm || endp(Z) < gndlvl) { m_builder.add_pillar(head_id, jp, radius); + return false; + } + } + } + + // Check if the deduced route is sane and exit with error if not. + if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm()) { + m_builder.add_pillar(head_id, jp, radius); + return false; + } + + // If this is a mini pillar, do not let it grow too long, but change the + // radius to the normal pillar as soon as it is possible. + if (radius < m_cfg.head_back_radius_mm) { + double t = 0.; + double new_radius = m_cfg.head_back_radius_mm; + Vec3d new_endp = endp; + double d = 0.; + while (!std::isinf(d = bridge_mesh_distance(new_endp, DOWN, new_radius)) + && new_endp.z() > gndlvl) + { + t += m_cfg.head_fullwidth(); + new_endp = endp + t * DOWN; + } + + if (std::isinf(d) && new_endp.z() > gndlvl) { + if (t > 0.) { + m_builder.add_bridge(endp, new_endp, radius, new_radius); + endp = new_endp; + } else { + m_builder.add_junction(endp, new_radius); + } + radius = new_radius; } } + // Straigh path down, no area to dodge if (normal_mode) { pillar_id = head_id >= 0 ? m_builder.add_pillar(head_id, endp, radius) : m_builder.add_pillar(jp, endp, radius); @@ -575,10 +554,31 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, if (can_add_base) m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm, m_cfg.base_radius_mm); + } else { + + // Insert the bridge to get around the forbidden area + Vec3d pgnd{endp.x(), endp.y(), gndlvl}; + pillar_id = m_builder.add_pillar(endp, pgnd, radius); + + if (can_add_base) + m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm, + m_cfg.base_radius_mm); + + m_builder.add_bridge(jp, endp, radius); + m_builder.add_junction(endp, radius); + + // Add a degenerated pillar and the bridge. + // The degenerate pillar will have zero length and it will + // prevent from queries of head_pillar() to have non-existing + // pillar when the head should have one. + if (head_id >= 0) + m_builder.add_pillar(head_id, jp, radius); } - + if(pillar_id >= 0) // Save the pillar endpoint in the spatial index m_pillar_index.guarded_insert(endp, unsigned(pillar_id)); + + return true; } void SupportTreeBuildsteps::filter() @@ -835,7 +835,11 @@ void SupportTreeBuildsteps::routing_to_ground() Head &h = m_builder.head(hid); h.transform(); - create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id); + if (!create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id)) { + BOOST_LOG_TRIVIAL(warning) + << "Pillar cannot be created for support point id: " << hid; + h.invalidate(); + } } // now we will go through the clusters ones again and connect the @@ -999,8 +1003,9 @@ bool SupportTreeBuildsteps::search_pillar_and_connect(const Head &source) nearest_id = ne.second; if(nearest_id >= 0) { - if(size_t(nearest_id) < m_builder.pillarcount()) { - if(!connect_to_nearpillar(source, nearest_id)) { + if (size_t(nearest_id) < m_builder.pillarcount()) { + if(!connect_to_nearpillar(source, nearest_id) || + m_builder.pillar(nearest_id).r < source.r_back_mm) { nearest_id = ID_UNSET; // continue searching spindex.remove(ne); // without the current pillar } @@ -1104,7 +1109,8 @@ void SupportTreeBuildsteps::interconnect_pillars() const Pillar& neighborpillar = m_builder.pillar(re.second); // this neighbor is occupied, skip - if(neighborpillar.links >= neighbors) continue; + if (neighborpillar.links >= neighbors) continue; + if (neighborpillar.r < pillar.r) continue; if(interconnect(pillar, neighborpillar)) { pairs.insert(hashval); diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index 1962f802b..bd6a9613c 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -271,7 +271,7 @@ class SupportTreeBuildsteps { // jp is the starting junction point which needs to be routed down. // sourcedir is the allowed direction of an optional bridge between the // jp junction and the final pillar. - void create_ground_pillar(const Vec3d &jp, + bool create_ground_pillar(const Vec3d &jp, const Vec3d &sourcedir, double radius, long head_id = ID_UNSET); From ed460a3e7e14ccfbf8ee0345f03ce3fd40750dde Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 10 Jun 2020 15:34:06 +0200 Subject: [PATCH 55/70] Remove the `headless` step of support support tree gen --- src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 97 +-------------------- src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 5 -- 2 files changed, 2 insertions(+), 100 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index e94e3c402..334c88fb9 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -56,7 +56,6 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, ROUTING_GROUND, ROUTING_NONGROUND, CASCADE_PILLARS, - HEADLESS, MERGE_RESULT, DONE, ABORT, @@ -83,8 +82,6 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, std::bind(&SupportTreeBuildsteps::interconnect_pillars, &alg), - std::bind(&SupportTreeBuildsteps::routing_headless, &alg), - std::bind(&SupportTreeBuildsteps::merge_result, &alg), [] () { @@ -103,10 +100,6 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, BOOST_LOG_TRIVIAL(info) << "Skipping model-facing supports as requested."; }; - program[HEADLESS] = []() { - BOOST_LOG_TRIVIAL(info) << "Skipping headless stick generation as" - " requested."; - }; } // Let's define a simple automaton that will run our program. @@ -119,7 +112,6 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, "Routing to ground", "Routing supports to model surface", "Interconnecting pillars", - "Processing small holes", "Merging support mesh", "Done", "Abort" @@ -133,7 +125,6 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, 60, 70, 80, - 85, 99, 100, 0 @@ -148,8 +139,7 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, case CLASSIFY: pc = ROUTING_GROUND; break; case ROUTING_GROUND: pc = ROUTING_NONGROUND; break; case ROUTING_NONGROUND: pc = CASCADE_PILLARS; break; - case CASCADE_PILLARS: pc = HEADLESS; break; - case HEADLESS: pc = MERGE_RESULT; break; + case CASCADE_PILLARS: pc = MERGE_RESULT; break; case MERGE_RESULT: pc = DONE; break; case DONE: case ABORT: break; @@ -521,31 +511,6 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, return false; } - // If this is a mini pillar, do not let it grow too long, but change the - // radius to the normal pillar as soon as it is possible. - if (radius < m_cfg.head_back_radius_mm) { - double t = 0.; - double new_radius = m_cfg.head_back_radius_mm; - Vec3d new_endp = endp; - double d = 0.; - while (!std::isinf(d = bridge_mesh_distance(new_endp, DOWN, new_radius)) - && new_endp.z() > gndlvl) - { - t += m_cfg.head_fullwidth(); - new_endp = endp + t * DOWN; - } - - if (std::isinf(d) && new_endp.z() > gndlvl) { - if (t > 0.) { - m_builder.add_bridge(endp, new_endp, radius, new_radius); - endp = new_endp; - } else { - m_builder.add_junction(endp, new_radius); - } - radius = new_radius; - } - } - // Straigh path down, no area to dodge if (normal_mode) { pillar_id = head_id >= 0 ? m_builder.add_pillar(head_id, endp, radius) : @@ -1258,62 +1223,4 @@ void SupportTreeBuildsteps::interconnect_pillars() } } -void SupportTreeBuildsteps::routing_headless() -{ - // For now we will just generate smaller headless sticks with a sharp - // ending point that connects to the mesh surface. - - // We will sink the pins into the model surface for a distance of 1/3 of - // the pin radius -// for(unsigned i : m_iheadless) { -// m_thr(); - -// const auto R = double(m_support_pts[i].head_front_radius); - -// // The support point position on the mesh -// Vec3d sph = m_support_pts[i].pos.cast(); - -// // Get an initial normal from the filtering step -// Vec3d n = m_support_nmls.row(i); - -// // First we need to determine the available space for a mini pinhead. -// // The goal is the move away from the model a little bit to make the -// // contact point small as possible and avoid pearcing the model body. -// double pin_space = std::min(2 * R, bridge_mesh_distance(sph, n, R, 0.)); - -// if (pin_space <= 0) continue; - -// auto &head = m_builder.add_head(i, R, R, pin_space, -// m_cfg.head_penetration_mm, n, sph); - -// // collision check - -// m_head_to_ground_scans[i] = -// bridge_mesh_intersect(head.junction_point(), DOWN, R); - -// // Here the steps will be similar as in route_to_model step: -// // 1. Search for a nearby pillar, include other mini pillars - -// // Search nearby pillar -// if (search_pillar_and_connect(head)) { head.transform(); continue; } - -// if (std::isinf(m_head_to_ground_scans[i].distance())) { -// create_ground_pillar(head.junction_point(), head.dir, m_cfg.head_back_radius_mm, head.id); -// } - -// // Cannot connect to nearby pillar. We will try to search for -// // a route to the ground. -// if (connect_to_ground(head)) { head.transform(); continue; } - -// // No route to the ground, so connect to the model body as a last resort -// if (connect_to_model_body(head)) { continue; } - -// BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless" -// << " support stick at: " -// << sph.transpose(); -// head.invalidate(); -// } -} - -} -} +}} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index bd6a9613c..ae872f98b 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -319,11 +319,6 @@ public: void interconnect_pillars(); - // Step: process the support points where there is not enough space for a - // full pinhead. In this case we will use a rounded sphere as a touching - // point and use a thinner bridge (let's call it a stick). - void routing_headless (); - inline void merge_result() { m_builder.merged_mesh(); } static bool execute(SupportTreeBuilder & builder, const SupportableMesh &sm); From 2ff04e6f682a3925d44e72a0179a139f58ecc9f3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 15 Jun 2020 16:02:47 +0200 Subject: [PATCH 56/70] Bugfixes for support generator * Fix support heads floating in air * Fix failing tests for the bridge mesh intersection * Fix failing assertions WIP refactoring support tree gen, as its a mess. --- src/libslic3r/SLA/SupportTreeBuilder.cpp | 277 ++++++++------------ src/libslic3r/SLA/SupportTreeBuilder.hpp | 144 +++++----- src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 149 +++++++++-- src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 72 ++++- 4 files changed, 371 insertions(+), 271 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeBuilder.cpp b/src/libslic3r/SLA/SupportTreeBuilder.cpp index 121a00145..ebeca78a7 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.cpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.cpp @@ -214,6 +214,56 @@ Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) return mesh; } + +Contour3D pedestal(const Vec3d &endpt, double baseheight, double radius, size_t steps) +{ + if(baseheight <= 0) return {}; + + assert(steps >= 0); + auto last = int(steps - 1); + + Contour3D base; + + double a = 2*PI/steps; + double z = endpt(Z) + baseheight; + + for(size_t i = 0; i < steps; ++i) { + double phi = i*a; + double x = endpt(X) + radius * std::cos(phi); + double y = endpt(Y) + radius * std::sin(phi); + base.points.emplace_back(x, y, z); + } + + for(size_t i = 0; i < steps; ++i) { + double phi = i*a; + double x = endpt(X) + radius*std::cos(phi); + double y = endpt(Y) + radius*std::sin(phi); + base.points.emplace_back(x, y, z - baseheight); + } + + auto ep = endpt; ep(Z) += baseheight; + base.points.emplace_back(endpt); + base.points.emplace_back(ep); + + auto& indices = base.faces3; + auto hcenter = int(base.points.size() - 1); + auto lcenter = int(base.points.size() - 2); + auto offs = int(steps); + for(int i = 0; i < last; ++i) { + indices.emplace_back(i, i + offs, offs + i + 1); + indices.emplace_back(i, offs + i + 1, i + 1); + indices.emplace_back(i, i + 1, hcenter); + indices.emplace_back(lcenter, offs + i + 1, offs + i); + } + + indices.emplace_back(0, last, offs); + indices.emplace_back(last, offs + last, offs); + indices.emplace_back(hcenter, last, 0); + indices.emplace_back(offs, offs + last, lcenter); + + return base; +} + Head::Head(double r_big_mm, double r_small_mm, double length_mm, @@ -229,77 +279,76 @@ Head::Head(double r_big_mm, , width_mm(length_mm) , penetration_mm(penetration) { - mesh = pinhead(r_pin_mm, r_back_mm, width_mm, steps); +// mesh = pinhead(r_pin_mm, r_back_mm, width_mm, steps); // To simplify further processing, we translate the mesh so that the // last vertex of the pointing sphere (the pinpoint) will be at (0,0,0) - for(auto& p : mesh.points) p.z() -= (fullwidth() - r_back_mm); +// for(auto& p : mesh.points) p.z() -= (fullwidth() - r_back_mm); } -Pillar::Pillar(const Vec3d &jp, const Vec3d &endp, double radius, size_t st): - r(radius), steps(st), endpt(endp), starts_from_head(false) -{ - assert(steps > 0); - - height = jp(Z) - endp(Z); - if(height > EPSILON) { // Endpoint is below the starting point +//Pillar::Pillar(const Vec3d &endp, double h, double radius, size_t st): +// height{h}, r(radius), steps(st), endpt(endp), starts_from_head(false) +//{ +// assert(steps > 0); + +// if(height > EPSILON) { // Endpoint is below the starting point - // We just create a bridge geometry with the pillar parameters and - // move the data. - Contour3D body = cylinder(radius, height, st, endp); - mesh.points.swap(body.points); - mesh.faces3.swap(body.faces3); - } -} +// // We just create a bridge geometry with the pillar parameters and +// // move the data. +// Contour3D body = cylinder(radius, height, st, endp); +// mesh.points.swap(body.points); +// mesh.faces3.swap(body.faces3); +// } +//} -Pillar &Pillar::add_base(double baseheight, double radius) -{ - if(baseheight <= 0) return *this; - if(baseheight > height) baseheight = height; +//Pillar &Pillar::add_base(double baseheight, double radius) +//{ +// if(baseheight <= 0) return *this; +// if(baseheight > height) baseheight = height; - assert(steps >= 0); - auto last = int(steps - 1); +// assert(steps >= 0); +// auto last = int(steps - 1); - if(radius < r ) radius = r; +// if(radius < r ) radius = r; - double a = 2*PI/steps; - double z = endpt(Z) + baseheight; +// double a = 2*PI/steps; +// double z = endpt(Z) + baseheight; - for(size_t i = 0; i < steps; ++i) { - double phi = i*a; - double x = endpt(X) + r*std::cos(phi); - double y = endpt(Y) + r*std::sin(phi); - base.points.emplace_back(x, y, z); - } +// for(size_t i = 0; i < steps; ++i) { +// double phi = i*a; +// double x = endpt(X) + r*std::cos(phi); +// double y = endpt(Y) + r*std::sin(phi); +// base.points.emplace_back(x, y, z); +// } - for(size_t i = 0; i < steps; ++i) { - double phi = i*a; - double x = endpt(X) + radius*std::cos(phi); - double y = endpt(Y) + radius*std::sin(phi); - base.points.emplace_back(x, y, z - baseheight); - } +// for(size_t i = 0; i < steps; ++i) { +// double phi = i*a; +// double x = endpt(X) + radius*std::cos(phi); +// double y = endpt(Y) + radius*std::sin(phi); +// base.points.emplace_back(x, y, z - baseheight); +// } - auto ep = endpt; ep(Z) += baseheight; - base.points.emplace_back(endpt); - base.points.emplace_back(ep); +// auto ep = endpt; ep(Z) += baseheight; +// base.points.emplace_back(endpt); +// base.points.emplace_back(ep); - auto& indices = base.faces3; - auto hcenter = int(base.points.size() - 1); - auto lcenter = int(base.points.size() - 2); - auto offs = int(steps); - for(int i = 0; i < last; ++i) { - indices.emplace_back(i, i + offs, offs + i + 1); - indices.emplace_back(i, offs + i + 1, i + 1); - indices.emplace_back(i, i + 1, hcenter); - indices.emplace_back(lcenter, offs + i + 1, offs + i); - } +// auto& indices = base.faces3; +// auto hcenter = int(base.points.size() - 1); +// auto lcenter = int(base.points.size() - 2); +// auto offs = int(steps); +// for(int i = 0; i < last; ++i) { +// indices.emplace_back(i, i + offs, offs + i + 1); +// indices.emplace_back(i, offs + i + 1, i + 1); +// indices.emplace_back(i, i + 1, hcenter); +// indices.emplace_back(lcenter, offs + i + 1, offs + i); +// } - indices.emplace_back(0, last, offs); - indices.emplace_back(last, offs + last, offs); - indices.emplace_back(hcenter, last, 0); - indices.emplace_back(offs, offs + last, lcenter); - return *this; -} +// indices.emplace_back(0, last, offs); +// indices.emplace_back(last, offs + last, offs); +// indices.emplace_back(hcenter, last, 0); +// indices.emplace_back(offs, offs + last, lcenter); +// return *this; +//} Bridge::Bridge(const Vec3d &j1, const Vec3d &j2, double r_mm, size_t steps): r(r_mm), startp(j1), endp(j2) @@ -423,7 +472,7 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh() const for (auto &head : m_heads) { if (ctl().stopcondition()) break; - if (head.is_valid()) merged.merge(head.mesh); + if (head.is_valid()) merged.merge(get_mesh(head)); } for (auto &stick : m_pillars) { @@ -512,119 +561,5 @@ static Hit min_hit(const C &hits) return *mit; } -EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &h) -{ - static const size_t SAMPLES = 8; - - // Move away slightly from the touching point to avoid raycasting on the - // inner surface of the mesh. - - const double& sd = msh.cfg.safety_distance_mm; - - auto& m = msh.emesh; - using HitResult = EigenMesh3D::hit_result; - - // Hit results - std::array hits; - - Vec3d s1 = h.pos, s2 = h.junction_point(); - - struct Rings { - double rpin; - double rback; - Vec3d spin; - Vec3d sback; - PointRing ring; - - Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); } - Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); } - } rings {h.r_pin_mm + sd, h.r_back_mm + sd, s1, s2, h.dir}; - - // We will shoot multiple rays from the head pinpoint in the direction - // of the pinhead robe (side) surface. The result will be the smallest - // hit distance. - - auto hitfn = [&m, &rings, sd](HitResult &hit, size_t i) { - // Point on the circle on the pin sphere - Vec3d ps = rings.pinring(i); - // This is the point on the circle on the back sphere - Vec3d p = rings.backring(i); - - // Point ps is not on mesh but can be inside or - // outside as well. This would cause many problems - // with ray-casting. To detect the position we will - // use the ray-casting result (which has an is_inside - // predicate). - - Vec3d n = (p - ps).normalized(); - auto q = m.query_ray_hit(ps + sd * n, n); - - if (q.is_inside()) { // the hit is inside the model - if (q.distance() > rings.rpin) { - // If we are inside the model and the hit - // distance is bigger than our pin circle - // diameter, it probably indicates that the - // support point was already inside the - // model, or there is really no space - // around the point. We will assign a zero - // hit distance to these cases which will - // enforce the function return value to be - // an invalid ray with zero hit distance. - // (see min_element at the end) - hit = HitResult(0.0); - } else { - // re-cast the ray from the outside of the - // object. The starting point has an offset - // of 2*safety_distance because the - // original ray has also had an offset - auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n); - hit = q2; - } - } else - hit = q; - }; - - ccr::enumerate(hits.begin(), hits.end(), hitfn); - - return min_hit(hits); -} - -EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d) -{ - static const size_t SAMPLES = 8; - - Vec3d dir = (br.endp - br.startp).normalized(); - PointRing ring{dir}; - - using Hit = EigenMesh3D::hit_result; - - // Hit results - std::array hits; - - const double sd = std::isnan(safety_d) ? msh.cfg.safety_distance_mm : safety_d; - bool ins_check = sd < msh.cfg.safety_distance_mm; - - auto hitfn = [&br, &ring, &msh, dir, sd, ins_check](Hit & hit, size_t i) { - // Point on the circle on the pin sphere - Vec3d p = ring.get(i, br.startp, br.r + sd); - - auto hr = msh.emesh.query_ray_hit(p + sd * dir, dir); - - if (ins_check && hr.is_inside()) { - if (hr.distance() > 2 * br.r + sd) - hit = Hit(0.0); - else { - // re-cast the ray from the outside of the object - hit = msh.emesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, - dir); - } - } else - hit = hr; - }; - - ccr::enumerate(hits.begin(), hits.end(), hitfn); - - return min_hit(hits); -} }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index 66462ebbd..087173e55 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -74,10 +74,12 @@ Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI), // h: Height // ssteps: how many edges will create the base circle // sp: starting point -Contour3D cylinder(double r, double h, size_t ssteps = 45, const Vec3d &sp = {0,0,0}); +Contour3D cylinder(double r, double h, size_t steps = 45, const Vec3d &sp = Vec3d::Zero()); Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45); +Contour3D pedestal(const Vec3d &pt, double baseheight, double radius, size_t steps = 45); + const constexpr long ID_UNSET = -1; struct Head { @@ -114,15 +116,7 @@ struct Head { void transform() { - using Quaternion = Eigen::Quaternion; - - // We rotate the head to the specified direction The head's pointing - // side is facing upwards so this means that it would hold a support - // point with a normal pointing straight down. This is the reason of - // the -1 z coordinate - auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, dir); - - for(auto& p : mesh.points) p = quatern * p + pos; + // TODO: remove occurences } inline double real_width() const @@ -164,8 +158,8 @@ struct Junction { }; struct Pillar { - Contour3D mesh; - Contour3D base; +// Contour3D mesh; +// Contour3D base; double r = 1; size_t steps = 0; Vec3d endpt; @@ -182,27 +176,42 @@ struct Pillar { // How many pillars are cascaded with this one unsigned links = 0; - - Pillar(const Vec3d& jp, const Vec3d& endp, - double radius = 1, size_t st = 45); - - Pillar(const Junction &junc, const Vec3d &endp) - : Pillar(junc.pos, endp, junc.r, junc.steps) - {} - - Pillar(const Head &head, const Vec3d &endp, double radius = 1) - : Pillar(head.junction_point(), endp, - head.request_pillar_radius(radius), head.steps) - {} - + + Pillar(const Vec3d &endp, double h, double radius = 1, size_t st = 45): + height{h}, r(radius), steps(st), endpt(endp), starts_from_head(false) {} + + +// Pillar(const Junction &junc, const Vec3d &endp) +// : Pillar(junc.pos, endp, junc.r, junc.steps) +// {} + inline Vec3d startpoint() const { - return {endpt(X), endpt(Y), endpt(Z) + height}; + return {endpt.x(), endpt.y(), endpt.z() + height}; } inline const Vec3d& endpoint() const { return endpt; } - Pillar& add_base(double baseheight = 3, double radius = 2); +// Pillar& add_base(double baseheight = 3, double radius = 2); +}; + +struct Pedestal { + Vec3d pos; + double height, radius; + size_t steps = 45; + + Pedestal() = default; + Pedestal(const Vec3d &p, double h = 3., double r = 2., size_t stps = 45) + : pos{p}, height{h}, radius{r}, steps{stps} + {} + + Pedestal(const Pillar &p, double h = 3., double r = 2.) + : Pedestal{p.endpt, std::min(h, p.height), std::max(r, p.r), p.steps} + {} +}; + +struct PinJoin { + }; // A Bridge between two pillars (with junction endpoints) @@ -241,66 +250,39 @@ struct Pad { bool empty() const { return tmesh.facets_count() == 0; } }; -// Give points on a 3D ring with given center, radius and orientation -// method based on: -// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space -template -class PointRing { - std::array m_phis; +inline Contour3D get_mesh(const Head &h) +{ + Contour3D mesh = pinhead(h.r_pin_mm, h.r_back_mm, h.width_mm, h.steps); - // Two vectors that will be perpendicular to each other and to the - // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a - // placeholder. - // a and b vectors are perpendicular to the ring direction and to each other. - // Together they define the plane where we have to iterate with the - // given angles in the 'm_phis' vector - Vec3d a = {0, 1, 0}, b; - double m_radius = 0.; + using Quaternion = Eigen::Quaternion; - static inline bool constexpr is_one(double val) - { - return std::abs(std::abs(val) - 1) < 1e-20; + // We rotate the head to the specified direction The head's pointing + // side is facing upwards so this means that it would hold a support + // point with a normal pointing straight down. This is the reason of + // the -1 z coordinate + auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, h.dir); + + for(auto& p : mesh.points) p = quatern * p + h.pos; +} + +inline Contour3D get_mesh(const Pillar &p) +{ + assert(p.steps > 0); + + if(p.height > EPSILON) { // Endpoint is below the starting point + // We just create a bridge geometry with the pillar parameters and + // move the data. + return cylinder(p.r, p.height, p.steps, p.endpoint()); } -public: + return {}; +} - PointRing(const Vec3d &n) - { - m_phis = linspace_array(0., 2 * PI); +inline Contour3D get_mesh(const Pedestal &p, double h, double r) +{ + return pedestal(p.pos, p.height, p.radius, p.steps); +} - // We have to address the case when the direction vector v (same as - // dir) is coincident with one of the world axes. In this case two of - // its components will be completely zero and one is 1.0. Our method - // becomes dangerous here due to division with zero. Instead, vector - // 'a' can be an element-wise rotated version of 'v' - if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) { - a = {n(Z), n(X), n(Y)}; - b = {n(Y), n(Z), n(X)}; - } - else { - a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize(); - b = a.cross(n); - } - } - - Vec3d get(size_t idx, const Vec3d src, double r) const - { - double phi = m_phis[idx]; - double sinphi = std::sin(phi); - double cosphi = std::cos(phi); - - double rpscos = r * cosphi; - double rpssin = r * sinphi; - - // Point on the sphere - return {src(X) + rpscos * a(X) + rpssin * b(X), - src(Y) + rpscos * a(Y) + rpssin * b(Y), - src(Z) + rpscos * a(Z) + rpssin * b(Z)}; - } -}; - -EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan("")); -EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan("")); // This class will hold the support tree meshes with some additional // bookkeeping as well. Various parts of the support geometry are stored diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 334c88fb9..a8e79dc17 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -15,6 +15,119 @@ using libnest2d::opt::StopCriteria; using libnest2d::opt::GeneticOptimizer; using libnest2d::opt::SubplexOptimizer; +EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &h) +{ + static const size_t SAMPLES = 8; + + // Move away slightly from the touching point to avoid raycasting on the + // inner surface of the mesh. + + const double& sd = msh.cfg.safety_distance_mm; + + auto& m = msh.emesh; + using HitResult = EigenMesh3D::hit_result; + + // Hit results + std::array hits; + + Vec3d s1 = h.pos, s2 = h.junction_point(); + + struct Rings { + double rpin; + double rback; + Vec3d spin; + Vec3d sback; + PointRing ring; + + Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); } + Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); } + } rings {h.r_pin_mm + sd, h.r_back_mm + sd, s1, s2, h.dir}; + + // We will shoot multiple rays from the head pinpoint in the direction + // of the pinhead robe (side) surface. The result will be the smallest + // hit distance. + + auto hitfn = [&m, &rings, sd](HitResult &hit, size_t i) { + // Point on the circle on the pin sphere + Vec3d ps = rings.pinring(i); + // This is the point on the circle on the back sphere + Vec3d p = rings.backring(i); + + // Point ps is not on mesh but can be inside or + // outside as well. This would cause many problems + // with ray-casting. To detect the position we will + // use the ray-casting result (which has an is_inside + // predicate). + + Vec3d n = (p - ps).normalized(); + auto q = m.query_ray_hit(ps + sd * n, n); + + if (q.is_inside()) { // the hit is inside the model + if (q.distance() > rings.rpin) { + // If we are inside the model and the hit + // distance is bigger than our pin circle + // diameter, it probably indicates that the + // support point was already inside the + // model, or there is really no space + // around the point. We will assign a zero + // hit distance to these cases which will + // enforce the function return value to be + // an invalid ray with zero hit distance. + // (see min_element at the end) + hit = HitResult(0.0); + } else { + // re-cast the ray from the outside of the + // object. The starting point has an offset + // of 2*safety_distance because the + // original ray has also had an offset + auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n); + hit = q2; + } + } else + hit = q; + }; + + ccr::enumerate(hits.begin(), hits.end(), hitfn); + + return min_hit(hits); +} + +EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d) +{ + + static const size_t SAMPLES = 8; + + Vec3d dir = (br.endp - br.startp).normalized(); + PointRing ring{dir}; + + using Hit = EigenMesh3D::hit_result; + + // Hit results + std::array hits; + + double sd = std::isnan(safety_d) ? msh.cfg.safety_distance_mm : safety_d; + + auto hitfn = [&msh, &br, &ring, dir, sd] (Hit &hit, size_t i) { + + // Point on the circle on the pin sphere + Vec3d p = ring.get(i, br.startp, br.r + sd); + + auto hr = msh.emesh.query_ray_hit(p + br.r * dir, dir); + + if(hr.is_inside()) { + if(hr.distance() > 2 * br.r + sd) hit = Hit(0.0); + else { + // re-cast the ray from the outside of the object + hit = msh.emesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, dir); + } + } else hit = hr; + }; + + ccr::enumerate(hits.begin(), hits.end(), hitfn); + + return min_hit(hits); +} + SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm) : m_cfg(sm.cfg) @@ -246,7 +359,7 @@ EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( } EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( - const Vec3d &src, const Vec3d &dir, double r, double safety_d) + const Vec3d &src, const Vec3d &dir, double r, double sd) { static const size_t SAMPLES = 8; PointRing ring{dir}; @@ -255,25 +368,20 @@ EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( // Hit results std::array hits; - - double sd = std::isnan(safety_d) ? m_cfg.safety_distance_mm : safety_d; - sd = sd * r / m_cfg.head_back_radius_mm; - - bool ins_check = sd < m_cfg.safety_distance_mm; ccr::enumerate(hits.begin(), hits.end(), - [this, r, src, ins_check, &ring, dir, sd] (Hit &hit, size_t i) { + [this, r, src, /*ins_check,*/ &ring, dir, sd] (Hit &hit, size_t i) { // Point on the circle on the pin sphere Vec3d p = ring.get(i, src, r + sd); auto hr = m_mesh.query_ray_hit(p + r * dir, dir); - if(ins_check && hr.is_inside()) { + if(/*ins_check && */hr.is_inside()) { if(hr.distance() > 2 * r + sd) hit = Hit(0.0); else { // re-cast the ray from the outside of the object - hit = m_mesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, dir); + hit = m_mesh.query_ray_hit(p + (hr.distance() + EPSILON) * dir, dir); } } else hit = hr; }); @@ -499,7 +607,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, normal_mode = false; if (t > m_cfg.max_bridge_length_mm || endp(Z) < gndlvl) { - m_builder.add_pillar(head_id, jp, radius); + if (head_id >= 0) m_builder.add_pillar(head_id, jp, radius); return false; } } @@ -507,7 +615,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, // Check if the deduced route is sane and exit with error if not. if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm()) { - m_builder.add_pillar(head_id, jp, radius); + if (head_id >= 0) m_builder.add_pillar(head_id, jp, radius); return false; } @@ -798,13 +906,16 @@ void SupportTreeBuildsteps::routing_to_ground() cl_centroids.emplace_back(hid); Head &h = m_builder.head(hid); - h.transform(); if (!create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id)) { BOOST_LOG_TRIVIAL(warning) << "Pillar cannot be created for support point id: " << hid; - h.invalidate(); + m_iheads_onmodel.emplace_back(h.id); +// h.invalidate(); + continue; } + + h.transform(); } // now we will go through the clusters ones again and connect the @@ -854,12 +965,14 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir) if(!std::isinf(tdown)) return false; Vec3d endp = hjp + d * dir; - m_builder.add_bridge(head.id, endp); - m_builder.add_junction(endp, head.r_back_mm); + bool ret = false; + + if ((ret = create_ground_pillar(endp, dir, head.r_back_mm))) { + m_builder.add_bridge(head.id, endp); + m_builder.add_junction(endp, head.r_back_mm); + } - this->create_ground_pillar(endp, dir, head.r_back_mm); - - return true; + return ret; } bool SupportTreeBuildsteps::connect_to_ground(Head &head) diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index ae872f98b..bfa38505b 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -46,6 +46,68 @@ inline Vec3d spheric_to_dir(const std::pair &v) return spheric_to_dir(v.first, v.second); } + +// Give points on a 3D ring with given center, radius and orientation +// method based on: +// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space +template +class PointRing { + std::array m_phis; + + // Two vectors that will be perpendicular to each other and to the + // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a + // placeholder. + // a and b vectors are perpendicular to the ring direction and to each other. + // Together they define the plane where we have to iterate with the + // given angles in the 'm_phis' vector + Vec3d a = {0, 1, 0}, b; + double m_radius = 0.; + + static inline bool constexpr is_one(double val) + { + return std::abs(std::abs(val) - 1) < 1e-20; + } + +public: + + PointRing(const Vec3d &n) + { + m_phis = linspace_array(0., 2 * PI); + + // We have to address the case when the direction vector v (same as + // dir) is coincident with one of the world axes. In this case two of + // its components will be completely zero and one is 1.0. Our method + // becomes dangerous here due to division with zero. Instead, vector + // 'a' can be an element-wise rotated version of 'v' + if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) { + a = {n(Z), n(X), n(Y)}; + b = {n(Y), n(Z), n(X)}; + } + else { + a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize(); + b = a.cross(n); + } + } + + Vec3d get(size_t idx, const Vec3d src, double r) const + { + double phi = m_phis[idx]; + double sinphi = std::sin(phi); + double cosphi = std::cos(phi); + + double rpscos = r * cosphi; + double rpssin = r * sinphi; + + // Point on the sphere + return {src(X) + rpscos * a(X) + rpssin * b(X), + src(Y) + rpscos * a(Y) + rpssin * b(Y), + src(Z) + rpscos * a(Z) + rpssin * b(Z)}; + } +}; + +EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan("")); +EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan("")); + // This function returns the position of the centroid in the input 'clust' // vector of point indices. template @@ -242,7 +304,15 @@ class SupportTreeBuildsteps { const Vec3d& s, const Vec3d& dir, double r, - double safety_d = std::nan("")); + double safety_d); + + EigenMesh3D::hit_result bridge_mesh_intersect( + const Vec3d& s, + const Vec3d& dir, + double r) + { + return bridge_mesh_intersect(s, dir, r, m_cfg.safety_distance_mm); + } template inline double bridge_mesh_distance(Args&&...args) { From 184f64f8281229c0ffb36d273eda24fb12e14ebb Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 16 Jun 2020 13:17:06 +0200 Subject: [PATCH 57/70] Separate support tree routing and meshing, remove Common.hpp/.cpp . * Remove Common.hpp and Common.cpp, move things into their respective modules in sla. --- src/libslic3r/CMakeLists.txt | 7 +- src/libslic3r/OpenVDBUtils.hpp | 1 - src/libslic3r/SLA/BoostAdapter.hpp | 4 +- src/libslic3r/SLA/Clustering.cpp | 152 +++++++ src/libslic3r/SLA/Clustering.hpp | 58 ++- src/libslic3r/SLA/Common.hpp | 27 -- src/libslic3r/SLA/Contour3D.hpp | 9 +- .../SLA/{Common.cpp => EigenMesh3D.cpp} | 372 ++---------------- src/libslic3r/SLA/EigenMesh3D.hpp | 6 +- src/libslic3r/SLA/Hollowing.cpp | 3 +- src/libslic3r/SLA/Hollowing.hpp | 1 - src/libslic3r/SLA/JobController.hpp | 1 + src/libslic3r/SLA/Pad.cpp | 1 - src/libslic3r/SLA/Rotfinder.cpp | 1 - src/libslic3r/SLA/SpatIndex.cpp | 161 ++++++++ src/libslic3r/SLA/SpatIndex.hpp | 2 +- src/libslic3r/SLA/SupportPoint.hpp | 1 - src/libslic3r/SLA/SupportPointGenerator.hpp | 1 - src/libslic3r/SLA/SupportTree.cpp | 1 - src/libslic3r/SLA/SupportTree.hpp | 1 - src/libslic3r/SLA/SupportTreeBuilder.cpp | 326 +-------------- src/libslic3r/SLA/SupportTreeBuilder.hpp | 184 +++------ src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 62 +-- src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 50 +-- src/libslic3r/SLA/SupportTreeMesher.cpp | 268 +++++++++++++ src/libslic3r/SLA/SupportTreeMesher.hpp | 94 +++++ tests/sla_print/sla_treebuilder_tests.cpp | 15 +- 27 files changed, 897 insertions(+), 912 deletions(-) create mode 100644 src/libslic3r/SLA/Clustering.cpp delete mode 100644 src/libslic3r/SLA/Common.hpp rename src/libslic3r/SLA/{Common.cpp => EigenMesh3D.cpp} (58%) create mode 100644 src/libslic3r/SLA/SpatIndex.cpp create mode 100644 src/libslic3r/SLA/SupportTreeMesher.cpp create mode 100644 src/libslic3r/SLA/SupportTreeMesher.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 9f566b405..20f3c6b4b 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -204,11 +204,11 @@ add_library(libslic3r STATIC SimplifyMesh.cpp MarchingSquares.hpp ${OpenVDBUtils_SOURCES} - SLA/Common.hpp - SLA/Common.cpp SLA/Pad.hpp SLA/Pad.cpp SLA/SupportTreeBuilder.hpp + SLA/SupportTreeMesher.hpp + SLA/SupportTreeMesher.cpp SLA/SupportTreeBuildsteps.hpp SLA/SupportTreeBuildsteps.cpp SLA/SupportTreeBuilder.cpp @@ -220,6 +220,7 @@ add_library(libslic3r STATIC SLA/Rotfinder.cpp SLA/BoostAdapter.hpp SLA/SpatIndex.hpp + SLA/SpatIndex.cpp SLA/RasterBase.hpp SLA/RasterBase.cpp SLA/AGGRaster.hpp @@ -236,7 +237,9 @@ add_library(libslic3r STATIC SLA/Contour3D.hpp SLA/Contour3D.cpp SLA/EigenMesh3D.hpp + SLA/EigenMesh3D.cpp SLA/Clustering.hpp + SLA/Clustering.cpp SLA/ReprojectPointsOnMesh.hpp ) diff --git a/src/libslic3r/OpenVDBUtils.hpp b/src/libslic3r/OpenVDBUtils.hpp index c493845a1..e35231d35 100644 --- a/src/libslic3r/OpenVDBUtils.hpp +++ b/src/libslic3r/OpenVDBUtils.hpp @@ -2,7 +2,6 @@ #define OPENVDBUTILS_HPP #include -#include #include #include diff --git a/src/libslic3r/SLA/BoostAdapter.hpp b/src/libslic3r/SLA/BoostAdapter.hpp index b7b3c63a6..13e0465b1 100644 --- a/src/libslic3r/SLA/BoostAdapter.hpp +++ b/src/libslic3r/SLA/BoostAdapter.hpp @@ -1,7 +1,9 @@ #ifndef SLA_BOOSTADAPTER_HPP #define SLA_BOOSTADAPTER_HPP -#include +#include +#include + #include namespace boost { diff --git a/src/libslic3r/SLA/Clustering.cpp b/src/libslic3r/SLA/Clustering.cpp new file mode 100644 index 000000000..41ff1d4f0 --- /dev/null +++ b/src/libslic3r/SLA/Clustering.cpp @@ -0,0 +1,152 @@ +#include "Clustering.hpp" +#include "boost/geometry/index/rtree.hpp" + +#include +#include + +namespace Slic3r { namespace sla { + +namespace bgi = boost::geometry::index; +using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >; + +namespace { + +bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2) +{ + return e1.second < e2.second; +}; + +ClusteredPoints cluster(Index3D &sindex, + unsigned max_points, + std::function( + const Index3D &, const PointIndexEl &)> qfn) +{ + using Elems = std::vector; + + // Recursive function for visiting all the points in a given distance to + // each other + std::function group = + [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster) + { + for(auto& p : pts) { + std::vector tmp = qfn(sindex, p); + + std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements); + + Elems newpts; + std::set_difference(tmp.begin(), tmp.end(), + cluster.begin(), cluster.end(), + std::back_inserter(newpts), cmp_ptidx_elements); + + int c = max_points && newpts.size() + cluster.size() > max_points? + int(max_points - cluster.size()) : int(newpts.size()); + + cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c); + std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements); + + if(!newpts.empty() && (!max_points || cluster.size() < max_points)) + group(newpts, cluster); + } + }; + + std::vector clusters; + for(auto it = sindex.begin(); it != sindex.end();) { + Elems cluster = {}; + Elems pts = {*it}; + group(pts, cluster); + + for(auto& c : cluster) sindex.remove(c); + it = sindex.begin(); + + clusters.emplace_back(cluster); + } + + ClusteredPoints result; + for(auto& cluster : clusters) { + result.emplace_back(); + for(auto c : cluster) result.back().emplace_back(c.second); + } + + return result; +} + +std::vector distance_queryfn(const Index3D& sindex, + const PointIndexEl& p, + double dist, + unsigned max_points) +{ + std::vector tmp; tmp.reserve(max_points); + sindex.query( + bgi::nearest(p.first, max_points), + std::back_inserter(tmp) + ); + + for(auto it = tmp.begin(); it < tmp.end(); ++it) + if((p.first - it->first).norm() > dist) it = tmp.erase(it); + + return tmp; +} + +} // namespace + +// Clustering a set of points by the given criteria +ClusteredPoints cluster( + const std::vector& indices, + std::function pointfn, + double dist, + unsigned max_points) +{ + // A spatial index for querying the nearest points + Index3D sindex; + + // Build the index + for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); + + return cluster(sindex, max_points, + [dist, max_points](const Index3D& sidx, const PointIndexEl& p) + { + return distance_queryfn(sidx, p, dist, max_points); + }); +} + +// Clustering a set of points by the given criteria +ClusteredPoints cluster( + const std::vector& indices, + std::function pointfn, + std::function predicate, + unsigned max_points) +{ + // A spatial index for querying the nearest points + Index3D sindex; + + // Build the index + for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); + + return cluster(sindex, max_points, + [max_points, predicate](const Index3D& sidx, const PointIndexEl& p) + { + std::vector tmp; tmp.reserve(max_points); + sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){ + return predicate(p, e); + }), std::back_inserter(tmp)); + return tmp; + }); +} + +ClusteredPoints cluster(const Eigen::MatrixXd& pts, double dist, unsigned max_points) +{ + // A spatial index for querying the nearest points + Index3D sindex; + + // Build the index + for(Eigen::Index i = 0; i < pts.rows(); i++) + sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i))); + + return cluster(sindex, max_points, + [dist, max_points](const Index3D& sidx, const PointIndexEl& p) + { + return distance_queryfn(sidx, p, dist, max_points); + }); +} + +}} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/Clustering.hpp b/src/libslic3r/SLA/Clustering.hpp index 1b0d47d95..269ec2882 100644 --- a/src/libslic3r/SLA/Clustering.hpp +++ b/src/libslic3r/SLA/Clustering.hpp @@ -2,7 +2,8 @@ #define SLA_CLUSTERING_HPP #include -#include + +#include #include namespace Slic3r { namespace sla { @@ -16,7 +17,7 @@ ClusteredPoints cluster(const std::vector& indices, double dist, unsigned max_points); -ClusteredPoints cluster(const PointSet& points, +ClusteredPoints cluster(const Eigen::MatrixXd& points, double dist, unsigned max_points); @@ -26,5 +27,56 @@ ClusteredPoints cluster( std::function predicate, unsigned max_points); -}} +// This function returns the position of the centroid in the input 'clust' +// vector of point indices. +template +long cluster_centroid(const ClusterEl &clust, PointFn pointfn, DistFn df) +{ + switch(clust.size()) { + case 0: /* empty cluster */ return -1; + case 1: /* only one element */ return 0; + case 2: /* if two elements, there is no center */ return 0; + default: ; + } + + // The function works by calculating for each point the average distance + // from all the other points in the cluster. We create a selector bitmask of + // the same size as the cluster. The bitmask will have two true bits and + // false bits for the rest of items and we will loop through all the + // permutations of the bitmask (combinations of two points). Get the + // distance for the two points and add the distance to the averages. + // The point with the smallest average than wins. + + // The complexity should be O(n^2) but we will mostly apply this function + // for small clusters only (cca 3 elements) + + std::vector sel(clust.size(), false); // create full zero bitmask + std::fill(sel.end() - 2, sel.end(), true); // insert the two ones + std::vector avgs(clust.size(), 0.0); // store the average distances + + do { + std::array idx; + for(size_t i = 0, j = 0; i < clust.size(); i++) + if(sel[i]) idx[j++] = i; + + double d = df(pointfn(clust[idx[0]]), + pointfn(clust[idx[1]])); + + // add the distance to the sums for both associated points + for(auto i : idx) avgs[i] += d; + + // now continue with the next permutation of the bitmask with two 1s + } while(std::next_permutation(sel.begin(), sel.end())); + + // Divide by point size in the cluster to get the average (may be redundant) + for(auto& a : avgs) a /= clust.size(); + + // get the lowest average distance and return the index + auto minit = std::min_element(avgs.begin(), avgs.end()); + return long(minit - avgs.begin()); +} + + +}} // namespace Slic3r::sla + #endif // CLUSTERING_HPP diff --git a/src/libslic3r/SLA/Common.hpp b/src/libslic3r/SLA/Common.hpp deleted file mode 100644 index ca616cabc..000000000 --- a/src/libslic3r/SLA/Common.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef SLA_COMMON_HPP -#define SLA_COMMON_HPP - -#include -#include -#include -#include -#include - - -namespace Slic3r { - -// Typedefs from Point.hpp -typedef Eigen::Matrix Vec3f; -typedef Eigen::Matrix Vec3d; -typedef Eigen::Matrix Vec3i; -typedef Eigen::Matrix Vec4i; - -namespace sla { - -using PointSet = Eigen::MatrixXd; - -} // namespace sla -} // namespace Slic3r - - -#endif // SLASUPPORTTREE_HPP diff --git a/src/libslic3r/SLA/Contour3D.hpp b/src/libslic3r/SLA/Contour3D.hpp index 295612f19..1a4fa9a29 100644 --- a/src/libslic3r/SLA/Contour3D.hpp +++ b/src/libslic3r/SLA/Contour3D.hpp @@ -1,11 +1,14 @@ #ifndef SLA_CONTOUR3D_HPP #define SLA_CONTOUR3D_HPP -#include - #include -namespace Slic3r { namespace sla { +namespace Slic3r { + +// Used for quads (TODO: remove this, and convert quads to triangles in OpenVDBUtils) +using Vec4i = Eigen::Matrix; + +namespace sla { class EigenMesh3D; diff --git a/src/libslic3r/SLA/Common.cpp b/src/libslic3r/SLA/EigenMesh3D.cpp similarity index 58% rename from src/libslic3r/SLA/Common.cpp rename to src/libslic3r/SLA/EigenMesh3D.cpp index a7420a7fb..be44e324c 100644 --- a/src/libslic3r/SLA/Common.cpp +++ b/src/libslic3r/SLA/EigenMesh3D.cpp @@ -1,185 +1,16 @@ -#include -#include -#include -#include -#include -#include -#include +#include "EigenMesh3D.hpp" +#include "Concurrency.hpp" + #include +#include -// for concave hull merging decisions -#include -#include "boost/geometry/index/rtree.hpp" - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4244) -#pragma warning(disable: 4267) -#endif - - -#include +#include #ifdef SLIC3R_HOLE_RAYCASTER - #include +#include #endif - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - - -namespace Slic3r { -namespace sla { - - -/* ************************************************************************** - * PointIndex implementation - * ************************************************************************** */ - -class PointIndex::Impl { -public: - using BoostIndex = boost::geometry::index::rtree< PointIndexEl, - boost::geometry::index::rstar<16, 4> /* ? */ >; - - BoostIndex m_store; -}; - -PointIndex::PointIndex(): m_impl(new Impl()) {} -PointIndex::~PointIndex() {} - -PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} -PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} - -PointIndex& PointIndex::operator=(const PointIndex &cpy) -{ - m_impl.reset(new Impl(*cpy.m_impl)); - return *this; -} - -PointIndex& PointIndex::operator=(PointIndex &&cpy) -{ - m_impl.swap(cpy.m_impl); - return *this; -} - -void PointIndex::insert(const PointIndexEl &el) -{ - m_impl->m_store.insert(el); -} - -bool PointIndex::remove(const PointIndexEl& el) -{ - return m_impl->m_store.remove(el) == 1; -} - -std::vector -PointIndex::query(std::function fn) const -{ - namespace bgi = boost::geometry::index; - - std::vector ret; - m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret)); - return ret; -} - -std::vector PointIndex::nearest(const Vec3d &el, unsigned k = 1) const -{ - namespace bgi = boost::geometry::index; - std::vector ret; ret.reserve(k); - m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret)); - return ret; -} - -size_t PointIndex::size() const -{ - return m_impl->m_store.size(); -} - -void PointIndex::foreach(std::function fn) -{ - for(auto& el : m_impl->m_store) fn(el); -} - -void PointIndex::foreach(std::function fn) const -{ - for(const auto &el : m_impl->m_store) fn(el); -} - -/* ************************************************************************** - * BoxIndex implementation - * ************************************************************************** */ - -class BoxIndex::Impl { -public: - using BoostIndex = boost::geometry::index:: - rtree /* ? */>; - - BoostIndex m_store; -}; - -BoxIndex::BoxIndex(): m_impl(new Impl()) {} -BoxIndex::~BoxIndex() {} - -BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} -BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} - -BoxIndex& BoxIndex::operator=(const BoxIndex &cpy) -{ - m_impl.reset(new Impl(*cpy.m_impl)); - return *this; -} - -BoxIndex& BoxIndex::operator=(BoxIndex &&cpy) -{ - m_impl.swap(cpy.m_impl); - return *this; -} - -void BoxIndex::insert(const BoxIndexEl &el) -{ - m_impl->m_store.insert(el); -} - -bool BoxIndex::remove(const BoxIndexEl& el) -{ - return m_impl->m_store.remove(el) == 1; -} - -std::vector BoxIndex::query(const BoundingBox &qrbb, - BoxIndex::QueryType qt) -{ - namespace bgi = boost::geometry::index; - - std::vector ret; ret.reserve(m_impl->m_store.size()); - - switch (qt) { - case qtIntersects: - m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret)); - break; - case qtWithin: - m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret)); - } - - return ret; -} - -size_t BoxIndex::size() const -{ - return m_impl->m_store.size(); -} - -void BoxIndex::foreach(std::function fn) -{ - for(auto& el : m_impl->m_store) fn(el); -} - - -/* **************************************************************************** - * EigenMesh3D implementation - * ****************************************************************************/ - +namespace Slic3r { namespace sla { class EigenMesh3D::AABBImpl { private: @@ -189,7 +20,7 @@ public: void init(const TriangleMesh& tm) { m_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( - tm.its.vertices, tm.its.indices); + tm.its.vertices, tm.its.indices); } void intersect_ray(const TriangleMesh& tm, @@ -215,9 +46,9 @@ public: size_t idx_unsigned = 0; Vec3d closest_vec3d(closest); double dist = AABBTreeIndirect::squared_distance_to_indexed_triangle_set( - tm.its.vertices, - tm.its.indices, - m_tree, point, idx_unsigned, closest_vec3d); + tm.its.vertices, + tm.its.indices, + m_tree, point, idx_unsigned, closest_vec3d); i = int(idx_unsigned); closest = closest_vec3d; return dist; @@ -231,7 +62,7 @@ EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh) { auto&& bb = tmesh.bounding_box(); m_ground_level += bb.min(Z); - + // Build the AABB accelaration tree m_aabb->init(tmesh); } @@ -289,7 +120,6 @@ Vec3d EigenMesh3D::normal_by_face_id(int face_id) const { } - EigenMesh3D::hit_result EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const { @@ -325,7 +155,7 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const std::vector outs; std::vector hits; m_aabb->intersect_ray(*m_tm, s, dir, hits); - + // The sort is necessary, the hits are not always sorted. std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; }); @@ -334,7 +164,7 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const // along an axis of a cube due to floating-point approximations in igl (?) hits.erase(std::unique(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) - { return a.t == b.t; }), + { return a.t == b.t; }), hits.end()); // Convert the igl::Hit into hit_result @@ -356,7 +186,7 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const #ifdef SLIC3R_HOLE_RAYCASTER EigenMesh3D::hit_result EigenMesh3D::filter_hits( - const std::vector& object_hits) const + const std::vector& object_hits) const { assert(! m_holes.empty()); hit_result out(*this); @@ -377,7 +207,7 @@ EigenMesh3D::hit_result EigenMesh3D::filter_hits( }; std::vector hole_isects; hole_isects.reserve(m_holes.size()); - + auto sf = s.cast(); auto dirf = dir.cast(); @@ -461,29 +291,17 @@ double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const { return sqdst; } -/* **************************************************************************** - * Misc functions - * ****************************************************************************/ -namespace { - -bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2, - double eps = 0.05) +static bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2, + double eps = 0.05) { using Line3D = Eigen::ParametrizedLine; - + auto line = Line3D::Through(e1, e2); double d = line.distance(p); return std::abs(d) < eps; } -template double distance(const Vec& pp1, const Vec& pp2) { - auto p = pp2 - pp1; - return std::sqrt(p.transpose() * p); -} - -} - PointSet normals(const PointSet& points, const EigenMesh3D& mesh, double eps, @@ -531,11 +349,11 @@ PointSet normals(const PointSet& points, // ic will mark a single vertex. int ia = -1, ib = -1, ic = -1; - if (std::abs(distance(p, p1)) < eps) { + if (std::abs((p - p1).norm()) < eps) { ic = trindex(0); - } else if (std::abs(distance(p, p2)) < eps) { + } else if (std::abs((p - p2).norm()) < eps) { ic = trindex(1); - } else if (std::abs(distance(p, p3)) < eps) { + } else if (std::abs((p - p3).norm()) < eps) { ic = trindex(2); } else if (point_on_edge(p, p1, p2, eps)) { ia = trindex(0); @@ -612,148 +430,4 @@ PointSet normals(const PointSet& points, return ret; } -namespace bgi = boost::geometry::index; -using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >; - -namespace { - -bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2) -{ - return e1.second < e2.second; -}; - -ClusteredPoints cluster(Index3D &sindex, - unsigned max_points, - std::function( - const Index3D &, const PointIndexEl &)> qfn) -{ - using Elems = std::vector; - - // Recursive function for visiting all the points in a given distance to - // each other - std::function group = - [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster) - { - for(auto& p : pts) { - std::vector tmp = qfn(sindex, p); - - std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements); - - Elems newpts; - std::set_difference(tmp.begin(), tmp.end(), - cluster.begin(), cluster.end(), - std::back_inserter(newpts), cmp_ptidx_elements); - - int c = max_points && newpts.size() + cluster.size() > max_points? - int(max_points - cluster.size()) : int(newpts.size()); - - cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c); - std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements); - - if(!newpts.empty() && (!max_points || cluster.size() < max_points)) - group(newpts, cluster); - } - }; - - std::vector clusters; - for(auto it = sindex.begin(); it != sindex.end();) { - Elems cluster = {}; - Elems pts = {*it}; - group(pts, cluster); - - for(auto& c : cluster) sindex.remove(c); - it = sindex.begin(); - - clusters.emplace_back(cluster); - } - - ClusteredPoints result; - for(auto& cluster : clusters) { - result.emplace_back(); - for(auto c : cluster) result.back().emplace_back(c.second); - } - - return result; -} - -std::vector distance_queryfn(const Index3D& sindex, - const PointIndexEl& p, - double dist, - unsigned max_points) -{ - std::vector tmp; tmp.reserve(max_points); - sindex.query( - bgi::nearest(p.first, max_points), - std::back_inserter(tmp) - ); - - for(auto it = tmp.begin(); it < tmp.end(); ++it) - if(distance(p.first, it->first) > dist) it = tmp.erase(it); - - return tmp; -} - -} // namespace - -// Clustering a set of points by the given criteria -ClusteredPoints cluster( - const std::vector& indices, - std::function pointfn, - double dist, - unsigned max_points) -{ - // A spatial index for querying the nearest points - Index3D sindex; - - // Build the index - for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); - - return cluster(sindex, max_points, - [dist, max_points](const Index3D& sidx, const PointIndexEl& p) - { - return distance_queryfn(sidx, p, dist, max_points); - }); -} - -// Clustering a set of points by the given criteria -ClusteredPoints cluster( - const std::vector& indices, - std::function pointfn, - std::function predicate, - unsigned max_points) -{ - // A spatial index for querying the nearest points - Index3D sindex; - - // Build the index - for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); - - return cluster(sindex, max_points, - [max_points, predicate](const Index3D& sidx, const PointIndexEl& p) - { - std::vector tmp; tmp.reserve(max_points); - sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){ - return predicate(p, e); - }), std::back_inserter(tmp)); - return tmp; - }); -} - -ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points) -{ - // A spatial index for querying the nearest points - Index3D sindex; - - // Build the index - for(Eigen::Index i = 0; i < pts.rows(); i++) - sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i))); - - return cluster(sindex, max_points, - [dist, max_points](const Index3D& sidx, const PointIndexEl& p) - { - return distance_queryfn(sidx, p, dist, max_points); - }); -} - -} // namespace sla -} // namespace Slic3r +}} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/EigenMesh3D.hpp b/src/libslic3r/SLA/EigenMesh3D.hpp index 7b7562d47..c9196bb43 100644 --- a/src/libslic3r/SLA/EigenMesh3D.hpp +++ b/src/libslic3r/SLA/EigenMesh3D.hpp @@ -1,8 +1,10 @@ #ifndef SLA_EIGENMESH3D_H #define SLA_EIGENMESH3D_H -#include +#include +#include +#include // There is an implementation of a hole-aware raycaster that was eventually // not used in production version. It is now hidden under following define @@ -19,6 +21,8 @@ class TriangleMesh; namespace sla { +using PointSet = Eigen::MatrixXd; + /// An index-triangle structure for libIGL functions. Also serves as an /// alternative (raw) input format for the SLASupportTree. // Implemented in libslic3r/SLA/Common.cpp diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 0dd9436a1..44e4dd839 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -3,11 +3,10 @@ #include #include #include -#include #include -#include #include #include +#include #include diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index cc7d310ea..1f65fa8b7 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -2,7 +2,6 @@ #define SLA_HOLLOWING_HPP #include -#include #include #include diff --git a/src/libslic3r/SLA/JobController.hpp b/src/libslic3r/SLA/JobController.hpp index 3baa3d12d..b815e4d6f 100644 --- a/src/libslic3r/SLA/JobController.hpp +++ b/src/libslic3r/SLA/JobController.hpp @@ -2,6 +2,7 @@ #define SLA_JOBCONTROLLER_HPP #include +#include namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/Pad.cpp b/src/libslic3r/SLA/Pad.cpp index d933ef5ed..f2b189cd1 100644 --- a/src/libslic3r/SLA/Pad.cpp +++ b/src/libslic3r/SLA/Pad.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index fda8383b1..81ef00e6b 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include "Model.hpp" diff --git a/src/libslic3r/SLA/SpatIndex.cpp b/src/libslic3r/SLA/SpatIndex.cpp new file mode 100644 index 000000000..d95ba55be --- /dev/null +++ b/src/libslic3r/SLA/SpatIndex.cpp @@ -0,0 +1,161 @@ +#include "SpatIndex.hpp" + +// for concave hull merging decisions +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#pragma warning(disable: 4267) +#endif + +#include "boost/geometry/index/rtree.hpp" + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +namespace Slic3r { namespace sla { + +/* ************************************************************************** + * PointIndex implementation + * ************************************************************************** */ + +class PointIndex::Impl { +public: + using BoostIndex = boost::geometry::index::rtree< PointIndexEl, + boost::geometry::index::rstar<16, 4> /* ? */ >; + + BoostIndex m_store; +}; + +PointIndex::PointIndex(): m_impl(new Impl()) {} +PointIndex::~PointIndex() {} + +PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} +PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} + +PointIndex& PointIndex::operator=(const PointIndex &cpy) +{ + m_impl.reset(new Impl(*cpy.m_impl)); + return *this; +} + +PointIndex& PointIndex::operator=(PointIndex &&cpy) +{ + m_impl.swap(cpy.m_impl); + return *this; +} + +void PointIndex::insert(const PointIndexEl &el) +{ + m_impl->m_store.insert(el); +} + +bool PointIndex::remove(const PointIndexEl& el) +{ + return m_impl->m_store.remove(el) == 1; +} + +std::vector +PointIndex::query(std::function fn) const +{ + namespace bgi = boost::geometry::index; + + std::vector ret; + m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret)); + return ret; +} + +std::vector PointIndex::nearest(const Vec3d &el, unsigned k = 1) const +{ + namespace bgi = boost::geometry::index; + std::vector ret; ret.reserve(k); + m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret)); + return ret; +} + +size_t PointIndex::size() const +{ + return m_impl->m_store.size(); +} + +void PointIndex::foreach(std::function fn) +{ + for(auto& el : m_impl->m_store) fn(el); +} + +void PointIndex::foreach(std::function fn) const +{ + for(const auto &el : m_impl->m_store) fn(el); +} + +/* ************************************************************************** + * BoxIndex implementation + * ************************************************************************** */ + +class BoxIndex::Impl { +public: + using BoostIndex = boost::geometry::index:: + rtree /* ? */>; + + BoostIndex m_store; +}; + +BoxIndex::BoxIndex(): m_impl(new Impl()) {} +BoxIndex::~BoxIndex() {} + +BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} +BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} + +BoxIndex& BoxIndex::operator=(const BoxIndex &cpy) +{ + m_impl.reset(new Impl(*cpy.m_impl)); + return *this; +} + +BoxIndex& BoxIndex::operator=(BoxIndex &&cpy) +{ + m_impl.swap(cpy.m_impl); + return *this; +} + +void BoxIndex::insert(const BoxIndexEl &el) +{ + m_impl->m_store.insert(el); +} + +bool BoxIndex::remove(const BoxIndexEl& el) +{ + return m_impl->m_store.remove(el) == 1; +} + +std::vector BoxIndex::query(const BoundingBox &qrbb, + BoxIndex::QueryType qt) +{ + namespace bgi = boost::geometry::index; + + std::vector ret; ret.reserve(m_impl->m_store.size()); + + switch (qt) { + case qtIntersects: + m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret)); + break; + case qtWithin: + m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret)); + } + + return ret; +} + +size_t BoxIndex::size() const +{ + return m_impl->m_store.size(); +} + +void BoxIndex::foreach(std::function fn) +{ + for(auto& el : m_impl->m_store) fn(el); +} + +}} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/SpatIndex.hpp b/src/libslic3r/SLA/SpatIndex.hpp index 2955cdcdf..ef059d3ae 100644 --- a/src/libslic3r/SLA/SpatIndex.hpp +++ b/src/libslic3r/SLA/SpatIndex.hpp @@ -73,7 +73,7 @@ public: BoxIndex& operator=(BoxIndex&&); void insert(const BoxIndexEl&); - inline void insert(const BoundingBox& bb, unsigned idx) + void insert(const BoundingBox& bb, unsigned idx) { insert(std::make_pair(bb, unsigned(idx))); } diff --git a/src/libslic3r/SLA/SupportPoint.hpp b/src/libslic3r/SLA/SupportPoint.hpp index 455962cc4..2b973697b 100644 --- a/src/libslic3r/SLA/SupportPoint.hpp +++ b/src/libslic3r/SLA/SupportPoint.hpp @@ -2,7 +2,6 @@ #define SLA_SUPPORTPOINT_HPP #include -#include #include namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/SupportPointGenerator.hpp b/src/libslic3r/SLA/SupportPointGenerator.hpp index 172923056..3f07e9674 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.hpp +++ b/src/libslic3r/SLA/SupportPointGenerator.hpp @@ -3,7 +3,6 @@ #include -#include #include #include diff --git a/src/libslic3r/SLA/SupportTree.cpp b/src/libslic3r/SLA/SupportTree.cpp index 2edc4d21b..eec819e22 100644 --- a/src/libslic3r/SLA/SupportTree.cpp +++ b/src/libslic3r/SLA/SupportTree.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include #include diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index c6255aa2f..3b9f603fd 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -5,7 +5,6 @@ #include #include -#include #include #include #include diff --git a/src/libslic3r/SLA/SupportTreeBuilder.cpp b/src/libslic3r/SLA/SupportTreeBuilder.cpp index ebeca78a7..d4a9d00c9 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.cpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.cpp @@ -1,278 +1,18 @@ #include #include +#include #include namespace Slic3r { namespace sla { -Contour3D sphere(double rho, Portion portion, double fa) { - - Contour3D ret; - - // prohibit close to zero radius - if(rho <= 1e-6 && rho >= -1e-6) return ret; - - auto& vertices = ret.points; - auto& facets = ret.faces3; - - // Algorithm: - // Add points one-by-one to the sphere grid and form facets using relative - // coordinates. Sphere is composed effectively of a mesh of stacked circles. - - // adjust via rounding to get an even multiple for any provided angle. - double angle = (2*PI / floor(2*PI / fa)); - - // Ring to be scaled to generate the steps of the sphere - std::vector ring; - - for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i); - - const auto sbegin = size_t(2*std::get<0>(portion)/angle); - const auto send = size_t(2*std::get<1>(portion)/angle); - - const size_t steps = ring.size(); - const double increment = 1.0 / double(steps); - - // special case: first ring connects to 0,0,0 - // insert and form facets. - if(sbegin == 0) - vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho)); - - auto id = coord_t(vertices.size()); - for (size_t i = 0; i < ring.size(); i++) { - // Fixed scaling - const double z = -rho + increment*rho*2.0 * (sbegin + 1.0); - // radius of the circle for this step. - const double r = std::sqrt(std::abs(rho*rho - z*z)); - Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); - vertices.emplace_back(Vec3d(b(0), b(1), z)); - - if (sbegin == 0) - (i == 0) ? facets.emplace_back(coord_t(ring.size()), 0, 1) : - facets.emplace_back(id - 1, 0, id); - ++id; - } - - // General case: insert and form facets for each step, - // joining it to the ring below it. - for (size_t s = sbegin + 2; s < send - 1; s++) { - const double z = -rho + increment*double(s*2.0*rho); - const double r = std::sqrt(std::abs(rho*rho - z*z)); - - for (size_t i = 0; i < ring.size(); i++) { - Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); - vertices.emplace_back(Vec3d(b(0), b(1), z)); - auto id_ringsize = coord_t(id - int(ring.size())); - if (i == 0) { - // wrap around - facets.emplace_back(id - 1, id, id + coord_t(ring.size() - 1) ); - facets.emplace_back(id - 1, id_ringsize, id); - } else { - facets.emplace_back(id_ringsize - 1, id_ringsize, id); - facets.emplace_back(id - 1, id_ringsize - 1, id); - } - id++; - } - } - - // special case: last ring connects to 0,0,rho*2.0 - // only form facets. - if(send >= size_t(2*PI / angle)) { - vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho)); - for (size_t i = 0; i < ring.size(); i++) { - auto id_ringsize = coord_t(id - int(ring.size())); - if (i == 0) { - // third vertex is on the other side of the ring. - facets.emplace_back(id - 1, id_ringsize, id); - } else { - auto ci = coord_t(id_ringsize + coord_t(i)); - facets.emplace_back(ci - 1, ci, id); - } - } - } - id++; - - return ret; -} - -Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp) -{ - Contour3D ret; - - auto steps = int(ssteps); - auto& points = ret.points; - auto& indices = ret.faces3; - points.reserve(2*ssteps); - double a = 2*PI/steps; - - Vec3d jp = sp; - Vec3d endp = {sp(X), sp(Y), sp(Z) + h}; - - // Upper circle points - for(int i = 0; i < steps; ++i) { - double phi = i*a; - double ex = endp(X) + r*std::cos(phi); - double ey = endp(Y) + r*std::sin(phi); - points.emplace_back(ex, ey, endp(Z)); - } - - // Lower circle points - for(int i = 0; i < steps; ++i) { - double phi = i*a; - double x = jp(X) + r*std::cos(phi); - double y = jp(Y) + r*std::sin(phi); - points.emplace_back(x, y, jp(Z)); - } - - // Now create long triangles connecting upper and lower circles - indices.reserve(2*ssteps); - auto offs = steps; - for(int i = 0; i < steps - 1; ++i) { - indices.emplace_back(i, i + offs, offs + i + 1); - indices.emplace_back(i, offs + i + 1, i + 1); - } - - // Last triangle connecting the first and last vertices - auto last = steps - 1; - indices.emplace_back(0, last, offs); - indices.emplace_back(last, offs + last, offs); - - // According to the slicing algorithms, we need to aid them with generating - // a watertight body. So we create a triangle fan for the upper and lower - // ending of the cylinder to close the geometry. - points.emplace_back(jp); int ci = int(points.size() - 1); - for(int i = 0; i < steps - 1; ++i) - indices.emplace_back(i + offs + 1, i + offs, ci); - - indices.emplace_back(offs, steps + offs - 1, ci); - - points.emplace_back(endp); ci = int(points.size() - 1); - for(int i = 0; i < steps - 1; ++i) - indices.emplace_back(ci, i, i + 1); - - indices.emplace_back(steps - 1, 0, ci); - - return ret; -} - -Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) -{ - assert(length > 0.); - assert(r_back > 0.); - assert(r_pin > 0.); - - Contour3D mesh; - - // We create two spheres which will be connected with a robe that fits - // both circles perfectly. - - // Set up the model detail level - const double detail = 2*PI/steps; - - // We don't generate whole circles. Instead, we generate only the - // portions which are visible (not covered by the robe) To know the - // exact portion of the bottom and top circles we need to use some - // rules of tangent circles from which we can derive (using simple - // triangles the following relations: - - // The height of the whole mesh - const double h = r_back + r_pin + length; - double phi = PI / 2. - std::acos((r_back - r_pin) / h); - - // To generate a whole circle we would pass a portion of (0, Pi) - // To generate only a half horizontal circle we can pass (0, Pi/2) - // The calculated phi is an offset to the half circles needed to smooth - // the transition from the circle to the robe geometry - - auto&& s1 = sphere(r_back, make_portion(0, PI/2 + phi), detail); - auto&& s2 = sphere(r_pin, make_portion(PI/2 + phi, PI), detail); - - for(auto& p : s2.points) p.z() += h; - - mesh.merge(s1); - mesh.merge(s2); - - for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); - idx1 < s1.points.size() - 1; - idx1++, idx2++) - { - coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2); - coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1; - - mesh.faces3.emplace_back(i1s1, i2s1, i2s2); - mesh.faces3.emplace_back(i1s1, i2s2, i1s2); - } - - auto i1s1 = coord_t(s1.points.size()) - coord_t(steps); - auto i2s1 = coord_t(s1.points.size()) - 1; - auto i1s2 = coord_t(s1.points.size()); - auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1; - - mesh.faces3.emplace_back(i2s2, i2s1, i1s1); - mesh.faces3.emplace_back(i1s2, i2s2, i1s1); - - return mesh; -} - - -Contour3D pedestal(const Vec3d &endpt, double baseheight, double radius, size_t steps) -{ - if(baseheight <= 0) return {}; - - assert(steps >= 0); - auto last = int(steps - 1); - - Contour3D base; - - double a = 2*PI/steps; - double z = endpt(Z) + baseheight; - - for(size_t i = 0; i < steps; ++i) { - double phi = i*a; - double x = endpt(X) + radius * std::cos(phi); - double y = endpt(Y) + radius * std::sin(phi); - base.points.emplace_back(x, y, z); - } - - for(size_t i = 0; i < steps; ++i) { - double phi = i*a; - double x = endpt(X) + radius*std::cos(phi); - double y = endpt(Y) + radius*std::sin(phi); - base.points.emplace_back(x, y, z - baseheight); - } - - auto ep = endpt; ep(Z) += baseheight; - base.points.emplace_back(endpt); - base.points.emplace_back(ep); - - auto& indices = base.faces3; - auto hcenter = int(base.points.size() - 1); - auto lcenter = int(base.points.size() - 2); - auto offs = int(steps); - for(int i = 0; i < last; ++i) { - indices.emplace_back(i, i + offs, offs + i + 1); - indices.emplace_back(i, offs + i + 1, i + 1); - indices.emplace_back(i, i + 1, hcenter); - indices.emplace_back(lcenter, offs + i + 1, offs + i); - } - - indices.emplace_back(0, last, offs); - indices.emplace_back(last, offs + last, offs); - indices.emplace_back(hcenter, last, 0); - indices.emplace_back(offs, offs + last, lcenter); - - return base; -} - Head::Head(double r_big_mm, double r_small_mm, double length_mm, double penetration, const Vec3d &direction, - const Vec3d &offset, - const size_t circlesteps) - : steps(circlesteps) - , dir(direction) + const Vec3d &offset) + : dir(direction) , pos(offset) , r_back_mm(r_big_mm) , r_pin_mm(r_small_mm) @@ -350,35 +90,6 @@ Head::Head(double r_big_mm, // return *this; //} -Bridge::Bridge(const Vec3d &j1, const Vec3d &j2, double r_mm, size_t steps): - r(r_mm), startp(j1), endp(j2) -{ - using Quaternion = Eigen::Quaternion; - Vec3d dir = (j2 - j1).normalized(); - double d = distance(j2, j1); - - mesh = cylinder(r, d, steps); - - auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir); - for(auto& p : mesh.points) p = quater * p + j1; -} - -Bridge::Bridge(const Vec3d &j1, - const Vec3d &j2, - double r1_mm, - double r2_mm, - size_t steps) -{ - Vec3d dir = (j2 - j1); - mesh = pinhead(r1_mm, r2_mm, dir.norm(), steps); - dir.normalize(); - - using Quaternion = Eigen::Quaternion; - auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir); - - for(auto& p : mesh.points) p = quater * p + j1; -} - Pad::Pad(const TriangleMesh &support_mesh, const ExPolygons & model_contours, double ground_level, @@ -464,7 +175,7 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o) return *this; } -const TriangleMesh &SupportTreeBuilder::merged_mesh() const +const TriangleMesh &SupportTreeBuilder::merged_mesh(size_t steps) const { if (m_meshcache_valid) return m_meshcache; @@ -472,28 +183,31 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh() const for (auto &head : m_heads) { if (ctl().stopcondition()) break; - if (head.is_valid()) merged.merge(get_mesh(head)); + if (head.is_valid()) merged.merge(get_mesh(head, steps)); } - for (auto &stick : m_pillars) { + for (auto &pill : m_pillars) { if (ctl().stopcondition()) break; - merged.merge(stick.mesh); - merged.merge(stick.base); + merged.merge(get_mesh(pill, steps)); + } + + for (auto &pedest : m_pedestals) { + merged.merge(get_mesh(pedest, steps)); } for (auto &j : m_junctions) { if (ctl().stopcondition()) break; - merged.merge(j.mesh); + merged.merge(get_mesh(j, steps)); } for (auto &bs : m_bridges) { if (ctl().stopcondition()) break; - merged.merge(bs.mesh); + merged.merge(get_mesh(bs, steps)); } for (auto &bs : m_crossbridges) { if (ctl().stopcondition()) break; - merged.merge(bs.mesh); + merged.merge(get_mesh(bs, steps)); } if (ctl().stopcondition()) { @@ -550,16 +264,4 @@ const TriangleMesh &SupportTreeBuilder::retrieve_mesh(MeshType meshtype) const return m_meshcache; } -template -static Hit min_hit(const C &hits) -{ - auto mit = std::min_element(hits.begin(), hits.end(), - [](const Hit &h1, const Hit &h2) { - return h1.distance() < h2.distance(); - }); - - return *mit; -} - - }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index 087173e55..cc039de6f 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -2,7 +2,6 @@ #define SLA_SUPPORTTREEBUILDER_HPP #include -#include #include #include #include @@ -50,13 +49,6 @@ namespace sla { * nearby pillar. */ -using Coordf = double; -using Portion = std::tuple; - -inline Portion make_portion(double a, double b) { - return std::make_tuple(a, b); -} - template double distance(const Vec& p) { return std::sqrt(p.transpose() * p); } @@ -66,27 +58,13 @@ template double distance(const Vec& pp1, const Vec& pp2) { return distance(p); } -Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI), - double fa=(2*PI/360)); - -// Down facing cylinder in Z direction with arguments: -// r: radius -// h: Height -// ssteps: how many edges will create the base circle -// sp: starting point -Contour3D cylinder(double r, double h, size_t steps = 45, const Vec3d &sp = Vec3d::Zero()); - -Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45); - -Contour3D pedestal(const Vec3d &pt, double baseheight, double radius, size_t steps = 45); - const constexpr long ID_UNSET = -1; +const Vec3d DOWN = {0.0, 0.0, -1.0}; + +// A pinhead originating from a support point struct Head { - Contour3D mesh; - - size_t steps = 45; - Vec3d dir = {0, 0, -1}; + Vec3d dir = DOWN; Vec3d pos = {0, 0, 0}; double r_back_mm = 1; @@ -110,9 +88,9 @@ struct Head { double r_small_mm, double length_mm, double penetration, - const Vec3d &direction = {0, 0, -1}, // direction (normal to the dull end) - const Vec3d &offset = {0, 0, 0}, // displacement - const size_t circlesteps = 45); + const Vec3d &direction = DOWN, // direction (normal to the dull end) + const Vec3d &offset = {0, 0, 0} // displacement + ); void transform() { @@ -141,29 +119,27 @@ struct Head { } }; +struct Join { + enum Types { + jtPillarBrigde, jtHeadPillar, jtPillarPedestal, jtBridgePedestal, + jtPillarAnchor, jtBridgeAnchor + }; +}; + +// A junction connecting bridges and pillars struct Junction { - Contour3D mesh; double r = 1; - size_t steps = 45; Vec3d pos; long id = ID_UNSET; - - Junction(const Vec3d& tr, double r_mm, size_t stepnum = 45): - r(r_mm), steps(stepnum), pos(tr) - { - mesh = sphere(r_mm, make_portion(0, PI), 2*PI/steps); - for(auto& p : mesh.points) p += tr; - } + + Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {} }; + struct Pillar { -// Contour3D mesh; -// Contour3D base; - double r = 1; - size_t steps = 0; + double height, r; Vec3d endpt; - double height = 0; long id = ID_UNSET; @@ -177,60 +153,47 @@ struct Pillar { // How many pillars are cascaded with this one unsigned links = 0; - Pillar(const Vec3d &endp, double h, double radius = 1, size_t st = 45): - height{h}, r(radius), steps(st), endpt(endp), starts_from_head(false) {} + Pillar(const Vec3d &endp, double h, double radius = 1.): + height{h}, r(radius), endpt(endp), starts_from_head(false) {} - -// Pillar(const Junction &junc, const Vec3d &endp) -// : Pillar(junc.pos, endp, junc.r, junc.steps) -// {} - - inline Vec3d startpoint() const + Vec3d startpoint() const { return {endpt.x(), endpt.y(), endpt.z() + height}; } - inline const Vec3d& endpoint() const { return endpt; } + const Vec3d& endpoint() const { return endpt; } // Pillar& add_base(double baseheight = 3, double radius = 2); }; +// A base for pillars or bridges that end on the ground struct Pedestal { Vec3d pos; double height, radius; - size_t steps = 45; + long id = ID_UNSET; - Pedestal() = default; - Pedestal(const Vec3d &p, double h = 3., double r = 2., size_t stps = 45) - : pos{p}, height{h}, radius{r}, steps{stps} - {} - - Pedestal(const Pillar &p, double h = 3., double r = 2.) - : Pedestal{p.endpt, std::min(h, p.height), std::max(r, p.r), p.steps} + Pedestal(const Vec3d &p, double h = 3., double r = 2.) + : pos{p}, height{h}, radius{r} {} }; -struct PinJoin { - -}; +// This is the thing that anchors a pillar or bridge to the model body. +// It is actually a reverse pinhead. +struct Anchor: public Head { using Head::Head; }; // A Bridge between two pillars (with junction endpoints) struct Bridge { - Contour3D mesh; double r = 0.8; long id = ID_UNSET; Vec3d startp = Vec3d::Zero(), endp = Vec3d::Zero(); Bridge(const Vec3d &j1, const Vec3d &j2, - double r_mm = 0.8, - size_t steps = 45); + double r_mm = 0.8): r{r_mm}, startp{j1}, endp{j2} + {} - Bridge(const Vec3d &j1, - const Vec3d &j2, - double r1_mm, - double r2_mm, - size_t steps = 45); + double get_length() const { return (endp - startp).norm(); } + Vec3d get_dir() const { return (endp - startp).normalized(); } }; // A wrapper struct around the pad @@ -250,40 +213,6 @@ struct Pad { bool empty() const { return tmesh.facets_count() == 0; } }; -inline Contour3D get_mesh(const Head &h) -{ - Contour3D mesh = pinhead(h.r_pin_mm, h.r_back_mm, h.width_mm, h.steps); - - using Quaternion = Eigen::Quaternion; - - // We rotate the head to the specified direction The head's pointing - // side is facing upwards so this means that it would hold a support - // point with a normal pointing straight down. This is the reason of - // the -1 z coordinate - auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, h.dir); - - for(auto& p : mesh.points) p = quatern * p + h.pos; -} - -inline Contour3D get_mesh(const Pillar &p) -{ - assert(p.steps > 0); - - if(p.height > EPSILON) { // Endpoint is below the starting point - // We just create a bridge geometry with the pillar parameters and - // move the data. - return cylinder(p.r, p.height, p.steps, p.endpoint()); - } - - return {}; -} - -inline Contour3D get_mesh(const Pedestal &p, double h, double r) -{ - return pedestal(p.pos, p.height, p.radius, p.steps); -} - - // This class will hold the support tree meshes with some additional // bookkeeping as well. Various parts of the support geometry are stored // separately and are merged when the caller queries the merged mesh. The @@ -300,12 +229,15 @@ inline Contour3D get_mesh(const Pedestal &p, double h, double r) // merged mesh. It can be retrieved using a dedicated method (pad()) class SupportTreeBuilder: public SupportTree { // For heads it is beneficial to use the same IDs as for the support points. - std::vector m_heads; - std::vector m_head_indices; - std::vector m_pillars; + std::vector m_heads; + std::vector m_head_indices; + std::vector m_pillars; std::vector m_junctions; - std::vector m_bridges; - std::vector m_crossbridges; + std::vector m_bridges; + std::vector m_crossbridges; + std::vector m_pedestals; + std::vector m_anchors; + Pad m_pad; using Mutex = ccr::SpinningMutex; @@ -347,7 +279,7 @@ public: return m_heads.back(); } - template long add_pillar(long headid, Args&&... args) + template long add_pillar(long headid, double length) { std::lock_guard lk(m_mutex); if (m_pillars.capacity() < m_heads.size()) @@ -356,7 +288,9 @@ public: assert(headid >= 0 && size_t(headid) < m_head_indices.size()); Head &head = m_heads[m_head_indices[size_t(headid)]]; - m_pillars.emplace_back(head, std::forward(args)...); + Vec3d hjp = head.junction_point() - Vec3d{0, 0, length}; + m_pillars.emplace_back(hjp, length, head.r_back_mm); + Pillar& pillar = m_pillars.back(); pillar.id = long(m_pillars.size() - 1); head.pillar_id = pillar.id; @@ -371,7 +305,19 @@ public: { std::lock_guard lk(m_mutex); assert(pid >= 0 && size_t(pid) < m_pillars.size()); - m_pillars[size_t(pid)].add_base(baseheight, radius); + m_pedestals.emplace_back(m_pillars[size_t(pid)].endpt, baseheight, radius); + m_pedestals.back().id = m_pedestals.size() - 1; + m_meshcache_valid = false; +// m_pillars[size_t(pid)].add_base(baseheight, radius); + } + + template const Anchor& add_anchor(Args&&...args) + { + std::lock_guard lk(m_mutex); + m_anchors.emplace_back(std::forward(args)...); + m_anchors.back().id = long(m_junctions.size() - 1); + m_meshcache_valid = false; + return m_anchors.back(); } void increment_bridges(const Pillar& pillar) @@ -432,18 +378,18 @@ public: return m_junctions.back(); } - const Bridge& add_bridge(const Vec3d &s, const Vec3d &e, double r, size_t n = 45) + const Bridge& add_bridge(const Vec3d &s, const Vec3d &e, double r) { - return _add_bridge(m_bridges, s, e, r, n); + return _add_bridge(m_bridges, s, e, r); } - const Bridge& add_bridge(long headid, const Vec3d &endp, size_t s = 45) + const Bridge& add_bridge(long headid, const Vec3d &endp) { std::lock_guard lk(m_mutex); assert(headid >= 0 && size_t(headid) < m_head_indices.size()); Head &h = m_heads[m_head_indices[size_t(headid)]]; - m_bridges.emplace_back(h.junction_point(), endp, h.r_back_mm, s); + m_bridges.emplace_back(h.junction_point(), endp, h.r_back_mm); m_bridges.back().id = long(m_bridges.size() - 1); h.bridge_id = m_bridges.back().id; @@ -471,7 +417,7 @@ public: } inline const std::vector &pillars() const { return m_pillars; } - inline const std::vector &heads() const { return m_heads; } + inline const std::vector &heads() const { return m_heads; } inline const std::vector &bridges() const { return m_bridges; } inline const std::vector &crossbridges() const { return m_crossbridges; } @@ -496,7 +442,7 @@ public: const Pad& pad() const { return m_pad; } // WITHOUT THE PAD!!! - const TriangleMesh &merged_mesh() const; + const TriangleMesh &merged_mesh(size_t steps = 45) const; // WITH THE PAD double full_height() const; diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index a8e79dc17..4b8366ee4 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -7,14 +8,23 @@ namespace Slic3r { namespace sla { -static const Vec3d DOWN = {0.0, 0.0, -1.0}; - using libnest2d::opt::initvals; using libnest2d::opt::bound; using libnest2d::opt::StopCriteria; using libnest2d::opt::GeneticOptimizer; using libnest2d::opt::SubplexOptimizer; +template +static Hit min_hit(const C &hits) +{ + auto mit = std::min_element(hits.begin(), hits.end(), + [](const Hit &h1, const Hit &h2) { + return h1.distance() < h2.distance(); + }); + + return *mit; +} + EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &h) { static const size_t SAMPLES = 8; @@ -158,7 +168,7 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, builder.ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm; SupportTreeBuildsteps alg(builder, sm); - + // Let's define the individual steps of the processing. We can experiment // later with the ordering and the dependencies between them. enum Steps { @@ -271,17 +281,6 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, return pc == ABORT; } -template -static Hit min_hit(const C &hits) -{ - auto mit = std::min_element(hits.begin(), hits.end(), - [](const Hit &h1, const Hit &h2) { - return h1.distance() < h2.distance(); - }); - - return *mit; -} - EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width) { @@ -552,7 +551,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, if (m_builder.bridgecount(nearpillar()) < m_cfg.max_bridges_on_pillar) { // A partial pillar is needed under the starting head. if(zdiff > 0) { - m_builder.add_pillar(head.id, bridgestart, r); + m_builder.add_pillar(head.id, headjp.z() - bridgestart.z()); m_builder.add_junction(bridgestart, r); m_builder.add_bridge(bridgestart, bridgeend, r); } else { @@ -607,7 +606,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, normal_mode = false; if (t > m_cfg.max_bridge_length_mm || endp(Z) < gndlvl) { - if (head_id >= 0) m_builder.add_pillar(head_id, jp, radius); + if (head_id >= 0) m_builder.add_pillar(head_id, 0.); return false; } } @@ -615,14 +614,15 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, // Check if the deduced route is sane and exit with error if not. if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm()) { - if (head_id >= 0) m_builder.add_pillar(head_id, jp, radius); + if (head_id >= 0) m_builder.add_pillar(head_id, 0.); return false; } // Straigh path down, no area to dodge if (normal_mode) { - pillar_id = head_id >= 0 ? m_builder.add_pillar(head_id, endp, radius) : - m_builder.add_pillar(jp, endp, radius); + double h = jp.z() - endp.z(); + pillar_id = head_id >= 0 ? m_builder.add_pillar(head_id, h) : + m_builder.add_pillar(jp, h, radius); if (can_add_base) m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm, @@ -630,8 +630,9 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, } else { // Insert the bridge to get around the forbidden area - Vec3d pgnd{endp.x(), endp.y(), gndlvl}; - pillar_id = m_builder.add_pillar(endp, pgnd, radius); +// Vec3d pgnd{endp.x(), endp.y(), gndlvl}; + double h = endp.z() - gndlvl; + pillar_id = m_builder.add_pillar(endp, h, radius); if (can_add_base) m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm, @@ -645,7 +646,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, // prevent from queries of head_pillar() to have non-existing // pillar when the head should have one. if (head_id >= 0) - m_builder.add_pillar(head_id, jp, radius); + m_builder.add_pillar(head_id, 0.); } if(pillar_id >= 0) // Save the pillar endpoint in the spatial index @@ -1034,7 +1035,7 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) head.transform(); - long pillar_id = m_builder.add_pillar(head.id, endp, head.r_back_mm); + long pillar_id = m_builder.add_pillar(head.id, hit.distance() + h); Pillar &pill = m_builder.pillar(pillar_id); Vec3d taildir = endp - hitp; @@ -1046,11 +1047,14 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) w = 0.; } - Head tailhead(head.r_back_mm, head.r_pin_mm, w, - m_cfg.head_penetration_mm, taildir, hitp); + m_builder.add_anchor(head.r_back_mm, head.r_pin_mm, w, + m_cfg.head_penetration_mm, taildir, hitp); - tailhead.transform(); - pill.base = tailhead.mesh; +// Head tailhead(head.r_back_mm, head.r_pin_mm, w, +// m_cfg.head_penetration_mm, taildir, hitp); + +// tailhead.transform(); +// pill.base = tailhead.mesh; m_pillar_index.guarded_insert(pill.endpoint(), pill.id); @@ -1297,8 +1301,8 @@ void SupportTreeBuildsteps::interconnect_pillars() if (found) for (unsigned n = 0; n < needpillars; n++) { Vec3d s = spts[n]; - Pillar p(s, Vec3d(s(X), s(Y), gnd), pillar().r); - p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); + Pillar p(s, s.z() - gnd, pillar().r); +// p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); if (interconnect(pillar(), p)) { Pillar &pp = m_builder.pillar(m_builder.add_pillar(p)); diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index bfa38505b..fc5670b16 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace Slic3r { namespace sla { @@ -108,55 +109,6 @@ public: EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan("")); EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan("")); -// This function returns the position of the centroid in the input 'clust' -// vector of point indices. -template -long cluster_centroid(const ClusterEl& clust, - const std::function &pointfn, - DistFn df) -{ - switch(clust.size()) { - case 0: /* empty cluster */ return ID_UNSET; - case 1: /* only one element */ return 0; - case 2: /* if two elements, there is no center */ return 0; - default: ; - } - - // The function works by calculating for each point the average distance - // from all the other points in the cluster. We create a selector bitmask of - // the same size as the cluster. The bitmask will have two true bits and - // false bits for the rest of items and we will loop through all the - // permutations of the bitmask (combinations of two points). Get the - // distance for the two points and add the distance to the averages. - // The point with the smallest average than wins. - - // The complexity should be O(n^2) but we will mostly apply this function - // for small clusters only (cca 3 elements) - - std::vector sel(clust.size(), false); // create full zero bitmask - std::fill(sel.end() - 2, sel.end(), true); // insert the two ones - std::vector avgs(clust.size(), 0.0); // store the average distances - - do { - std::array idx; - for(size_t i = 0, j = 0; i < clust.size(); i++) if(sel[i]) idx[j++] = i; - - double d = df(pointfn(clust[idx[0]]), - pointfn(clust[idx[1]])); - - // add the distance to the sums for both associated points - for(auto i : idx) avgs[i] += d; - - // now continue with the next permutation of the bitmask with two 1s - } while(std::next_permutation(sel.begin(), sel.end())); - - // Divide by point size in the cluster to get the average (may be redundant) - for(auto& a : avgs) a /= clust.size(); - - // get the lowest average distance and return the index - auto minit = std::min_element(avgs.begin(), avgs.end()); - return long(minit - avgs.begin()); -} inline Vec3d dirv(const Vec3d& startp, const Vec3d& endp) { return (endp - startp).normalized(); diff --git a/src/libslic3r/SLA/SupportTreeMesher.cpp b/src/libslic3r/SLA/SupportTreeMesher.cpp new file mode 100644 index 000000000..1d9be6c34 --- /dev/null +++ b/src/libslic3r/SLA/SupportTreeMesher.cpp @@ -0,0 +1,268 @@ +#include "SupportTreeMesher.hpp" + +namespace Slic3r { namespace sla { + +Contour3D sphere(double rho, Portion portion, double fa) { + + Contour3D ret; + + // prohibit close to zero radius + if(rho <= 1e-6 && rho >= -1e-6) return ret; + + auto& vertices = ret.points; + auto& facets = ret.faces3; + + // Algorithm: + // Add points one-by-one to the sphere grid and form facets using relative + // coordinates. Sphere is composed effectively of a mesh of stacked circles. + + // adjust via rounding to get an even multiple for any provided angle. + double angle = (2*PI / floor(2*PI / fa)); + + // Ring to be scaled to generate the steps of the sphere + std::vector ring; + + for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i); + + const auto sbegin = size_t(2*std::get<0>(portion)/angle); + const auto send = size_t(2*std::get<1>(portion)/angle); + + const size_t steps = ring.size(); + const double increment = 1.0 / double(steps); + + // special case: first ring connects to 0,0,0 + // insert and form facets. + if(sbegin == 0) + vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho)); + + auto id = coord_t(vertices.size()); + for (size_t i = 0; i < ring.size(); i++) { + // Fixed scaling + const double z = -rho + increment*rho*2.0 * (sbegin + 1.0); + // radius of the circle for this step. + const double r = std::sqrt(std::abs(rho*rho - z*z)); + Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); + vertices.emplace_back(Vec3d(b(0), b(1), z)); + + if (sbegin == 0) + (i == 0) ? facets.emplace_back(coord_t(ring.size()), 0, 1) : + facets.emplace_back(id - 1, 0, id); + ++id; + } + + // General case: insert and form facets for each step, + // joining it to the ring below it. + for (size_t s = sbegin + 2; s < send - 1; s++) { + const double z = -rho + increment*double(s*2.0*rho); + const double r = std::sqrt(std::abs(rho*rho - z*z)); + + for (size_t i = 0; i < ring.size(); i++) { + Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); + vertices.emplace_back(Vec3d(b(0), b(1), z)); + auto id_ringsize = coord_t(id - int(ring.size())); + if (i == 0) { + // wrap around + facets.emplace_back(id - 1, id, id + coord_t(ring.size() - 1) ); + facets.emplace_back(id - 1, id_ringsize, id); + } else { + facets.emplace_back(id_ringsize - 1, id_ringsize, id); + facets.emplace_back(id - 1, id_ringsize - 1, id); + } + id++; + } + } + + // special case: last ring connects to 0,0,rho*2.0 + // only form facets. + if(send >= size_t(2*PI / angle)) { + vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho)); + for (size_t i = 0; i < ring.size(); i++) { + auto id_ringsize = coord_t(id - int(ring.size())); + if (i == 0) { + // third vertex is on the other side of the ring. + facets.emplace_back(id - 1, id_ringsize, id); + } else { + auto ci = coord_t(id_ringsize + coord_t(i)); + facets.emplace_back(ci - 1, ci, id); + } + } + } + id++; + + return ret; +} + +Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp) +{ + assert(steps > 0); + + Contour3D ret; + + auto steps = int(ssteps); + auto& points = ret.points; + auto& indices = ret.faces3; + points.reserve(2*ssteps); + double a = 2*PI/steps; + + Vec3d jp = sp; + Vec3d endp = {sp(X), sp(Y), sp(Z) + h}; + + // Upper circle points + for(int i = 0; i < steps; ++i) { + double phi = i*a; + double ex = endp(X) + r*std::cos(phi); + double ey = endp(Y) + r*std::sin(phi); + points.emplace_back(ex, ey, endp(Z)); + } + + // Lower circle points + for(int i = 0; i < steps; ++i) { + double phi = i*a; + double x = jp(X) + r*std::cos(phi); + double y = jp(Y) + r*std::sin(phi); + points.emplace_back(x, y, jp(Z)); + } + + // Now create long triangles connecting upper and lower circles + indices.reserve(2*ssteps); + auto offs = steps; + for(int i = 0; i < steps - 1; ++i) { + indices.emplace_back(i, i + offs, offs + i + 1); + indices.emplace_back(i, offs + i + 1, i + 1); + } + + // Last triangle connecting the first and last vertices + auto last = steps - 1; + indices.emplace_back(0, last, offs); + indices.emplace_back(last, offs + last, offs); + + // According to the slicing algorithms, we need to aid them with generating + // a watertight body. So we create a triangle fan for the upper and lower + // ending of the cylinder to close the geometry. + points.emplace_back(jp); int ci = int(points.size() - 1); + for(int i = 0; i < steps - 1; ++i) + indices.emplace_back(i + offs + 1, i + offs, ci); + + indices.emplace_back(offs, steps + offs - 1, ci); + + points.emplace_back(endp); ci = int(points.size() - 1); + for(int i = 0; i < steps - 1; ++i) + indices.emplace_back(ci, i, i + 1); + + indices.emplace_back(steps - 1, 0, ci); + + return ret; +} + +Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) +{ + assert(steps > 0); + assert(length > 0.); + assert(r_back > 0.); + assert(r_pin > 0.); + + Contour3D mesh; + + // We create two spheres which will be connected with a robe that fits + // both circles perfectly. + + // Set up the model detail level + const double detail = 2*PI/steps; + + // We don't generate whole circles. Instead, we generate only the + // portions which are visible (not covered by the robe) To know the + // exact portion of the bottom and top circles we need to use some + // rules of tangent circles from which we can derive (using simple + // triangles the following relations: + + // The height of the whole mesh + const double h = r_back + r_pin + length; + double phi = PI / 2. - std::acos((r_back - r_pin) / h); + + // To generate a whole circle we would pass a portion of (0, Pi) + // To generate only a half horizontal circle we can pass (0, Pi/2) + // The calculated phi is an offset to the half circles needed to smooth + // the transition from the circle to the robe geometry + + auto&& s1 = sphere(r_back, make_portion(0, PI/2 + phi), detail); + auto&& s2 = sphere(r_pin, make_portion(PI/2 + phi, PI), detail); + + for(auto& p : s2.points) p.z() += h; + + mesh.merge(s1); + mesh.merge(s2); + + for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); + idx1 < s1.points.size() - 1; + idx1++, idx2++) + { + coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2); + coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1; + + mesh.faces3.emplace_back(i1s1, i2s1, i2s2); + mesh.faces3.emplace_back(i1s1, i2s2, i1s2); + } + + auto i1s1 = coord_t(s1.points.size()) - coord_t(steps); + auto i2s1 = coord_t(s1.points.size()) - 1; + auto i1s2 = coord_t(s1.points.size()); + auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1; + + mesh.faces3.emplace_back(i2s2, i2s1, i1s1); + mesh.faces3.emplace_back(i1s2, i2s2, i1s1); + + return mesh; +} + +Contour3D pedestal(const Vec3d &endpt, double baseheight, double radius, size_t steps) +{ + assert(steps > 0); + + if(baseheight <= 0) return {}; + + assert(steps >= 0); + auto last = int(steps - 1); + + Contour3D base; + + double a = 2*PI/steps; + double z = endpt(Z) + baseheight; + + for(size_t i = 0; i < steps; ++i) { + double phi = i*a; + double x = endpt(X) + radius * std::cos(phi); + double y = endpt(Y) + radius * std::sin(phi); + base.points.emplace_back(x, y, z); + } + + for(size_t i = 0; i < steps; ++i) { + double phi = i*a; + double x = endpt(X) + radius*std::cos(phi); + double y = endpt(Y) + radius*std::sin(phi); + base.points.emplace_back(x, y, z - baseheight); + } + + auto ep = endpt; ep(Z) += baseheight; + base.points.emplace_back(endpt); + base.points.emplace_back(ep); + + auto& indices = base.faces3; + auto hcenter = int(base.points.size() - 1); + auto lcenter = int(base.points.size() - 2); + auto offs = int(steps); + for(int i = 0; i < last; ++i) { + indices.emplace_back(i, i + offs, offs + i + 1); + indices.emplace_back(i, offs + i + 1, i + 1); + indices.emplace_back(i, i + 1, hcenter); + indices.emplace_back(lcenter, offs + i + 1, offs + i); + } + + indices.emplace_back(0, last, offs); + indices.emplace_back(last, offs + last, offs); + indices.emplace_back(hcenter, last, 0); + indices.emplace_back(offs, offs + last, lcenter); + + return base; +} + +}} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/SupportTreeMesher.hpp b/src/libslic3r/SLA/SupportTreeMesher.hpp new file mode 100644 index 000000000..677cab3b8 --- /dev/null +++ b/src/libslic3r/SLA/SupportTreeMesher.hpp @@ -0,0 +1,94 @@ +#ifndef SUPPORTTREEMESHER_HPP +#define SUPPORTTREEMESHER_HPP + +#include "libslic3r/Point.hpp" + +#include "libslic3r/SLA/SupportTreeBuilder.hpp" +#include "libslic3r/SLA/Contour3D.hpp" + +namespace Slic3r { namespace sla { + +using Portion = std::tuple; + +inline Portion make_portion(double a, double b) +{ + return std::make_tuple(a, b); +} + +Contour3D sphere(double rho, + Portion portion = make_portion(0., 2. * PI), + double fa = (2. * PI / 360.)); + +// Down facing cylinder in Z direction with arguments: +// r: radius +// h: Height +// ssteps: how many edges will create the base circle +// sp: starting point +Contour3D cylinder(double r, double h, size_t steps = 45, const Vec3d &sp = Vec3d::Zero()); + +Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45); + +Contour3D pedestal(const Vec3d &pt, double baseheight, double radius, size_t steps = 45); + +inline Contour3D get_mesh(const Head &h, size_t steps) +{ + Contour3D mesh = pinhead(h.r_pin_mm, h.r_back_mm, h.width_mm, steps); + + // To simplify further processing, we translate the mesh so that the + // last vertex of the pointing sphere (the pinpoint) will be at (0,0,0) + for(auto& p : mesh.points) p.z() -= (h.fullwidth() - h.r_back_mm); + + using Quaternion = Eigen::Quaternion; + + // We rotate the head to the specified direction The head's pointing + // side is facing upwards so this means that it would hold a support + // point with a normal pointing straight down. This is the reason of + // the -1 z coordinate + auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, h.dir); + + for(auto& p : mesh.points) p = quatern * p + h.pos; + + return mesh; +} + +inline Contour3D get_mesh(const Pillar &p, size_t steps) +{ + if(p.height > EPSILON) { // Endpoint is below the starting point + // We just create a bridge geometry with the pillar parameters and + // move the data. + return cylinder(p.r, p.height, steps, p.endpoint()); + } + + return {}; +} + +inline Contour3D get_mesh(const Pedestal &p, size_t steps) +{ + return pedestal(p.pos, p.height, p.radius, steps); +} + +inline Contour3D get_mesh(const Junction &j, size_t steps) +{ + Contour3D mesh = sphere(j.r, make_portion(0, PI), 2 *PI / steps); + for(auto& p : mesh.points) p += j.pos; + return mesh; +} + +inline Contour3D get_mesh(const Bridge &br, size_t steps) +{ + using Quaternion = Eigen::Quaternion; + Vec3d v = (br.endp - br.startp); + Vec3d dir = v.normalized(); + double d = v.norm(); + + Contour3D mesh = cylinder(br.r, d, steps); + + auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir); + for(auto& p : mesh.points) p = quater * p + br.startp; + + return mesh; +} + +}} + +#endif // SUPPORTTREEMESHER_HPP diff --git a/tests/sla_print/sla_treebuilder_tests.cpp b/tests/sla_print/sla_treebuilder_tests.cpp index c785e4ba5..05aca963e 100644 --- a/tests/sla_print/sla_treebuilder_tests.cpp +++ b/tests/sla_print/sla_treebuilder_tests.cpp @@ -2,7 +2,8 @@ #include #include "libslic3r/TriangleMesh.hpp" -#include "libslic3r/SLA/SupportTreeBuilder.hpp" +#include "libslic3r/SLA/SupportTreeBuildsteps.hpp" +#include "libslic3r/SLA/SupportTreeMesher.hpp" TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]") { using namespace Slic3r; @@ -13,6 +14,7 @@ TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]" sla::SupportPoints pts = {{10.f, 5.f, 5.f, float(cfg.head_front_radius_mm), false}}; sla::SupportableMesh sm{cube, pts, cfg}; + size_t steps = 45; SECTION("Bridge is straight horizontal and pointing away from the cube") { sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 5.}, @@ -22,7 +24,7 @@ TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]" REQUIRE(std::isinf(hit.distance())); - cube.merge(sla::to_triangle_mesh(bridge.mesh)); + cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); cube.require_shared_vertices(); cube.WriteOBJFile("cube1.obj"); } @@ -35,7 +37,7 @@ TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]" REQUIRE(std::isinf(hit.distance())); - cube.merge(sla::to_triangle_mesh(bridge.mesh)); + cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); cube.require_shared_vertices(); cube.WriteOBJFile("cube2.obj"); } @@ -52,6 +54,7 @@ TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") { sla::SupportPoints pts = {{1.f, 0.f, 0.f, float(cfg.head_front_radius_mm), false}}; sla::SupportableMesh sm{sphere, pts, cfg}; + size_t steps = 45; SECTION("Bridge is straight horizontal and pointing away from the sphere") { sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., 0.}, @@ -59,7 +62,7 @@ TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") { auto hit = sla::query_hit(sm, bridge); - sphere.merge(sla::to_triangle_mesh(bridge.mesh)); + sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); sphere.require_shared_vertices(); sphere.WriteOBJFile("sphere1.obj"); @@ -73,7 +76,7 @@ TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") { auto hit = sla::query_hit(sm, bridge); - sphere.merge(sla::to_triangle_mesh(bridge.mesh)); + sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); sphere.require_shared_vertices(); sphere.WriteOBJFile("sphere2.obj"); @@ -87,7 +90,7 @@ TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") { auto hit = sla::query_hit(sm, bridge); - sphere.merge(sla::to_triangle_mesh(bridge.mesh)); + sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); sphere.require_shared_vertices(); sphere.WriteOBJFile("sphere3.obj"); From 301a168b8998d6ffd8389af9729357cd771fc9e4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 18 Jun 2020 13:49:35 +0200 Subject: [PATCH 58/70] Fix bugs and non working tests Fix failing tests Try to fix build on windows Try to fix failng tests on Mac --- src/libslic3r/SLA/SupportTreeBuilder.cpp | 91 +++++---------------- src/libslic3r/SLA/SupportTreeBuilder.hpp | 33 ++------ src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 81 +++++++++++------- src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 13 +-- src/libslic3r/SLA/SupportTreeMesher.cpp | 72 ++++++++-------- src/libslic3r/SLA/SupportTreeMesher.hpp | 19 +++-- tests/sla_print/sla_test_utils.cpp | 4 +- 7 files changed, 132 insertions(+), 181 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeBuilder.cpp b/src/libslic3r/SLA/SupportTreeBuilder.cpp index d4a9d00c9..959093623 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.cpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.cpp @@ -1,3 +1,5 @@ +#define NOMINMAX + #include #include #include @@ -19,77 +21,8 @@ Head::Head(double r_big_mm, , width_mm(length_mm) , penetration_mm(penetration) { -// mesh = pinhead(r_pin_mm, r_back_mm, width_mm, steps); - - // To simplify further processing, we translate the mesh so that the - // last vertex of the pointing sphere (the pinpoint) will be at (0,0,0) -// for(auto& p : mesh.points) p.z() -= (fullwidth() - r_back_mm); } -//Pillar::Pillar(const Vec3d &endp, double h, double radius, size_t st): -// height{h}, r(radius), steps(st), endpt(endp), starts_from_head(false) -//{ -// assert(steps > 0); - -// if(height > EPSILON) { // Endpoint is below the starting point - -// // We just create a bridge geometry with the pillar parameters and -// // move the data. -// Contour3D body = cylinder(radius, height, st, endp); -// mesh.points.swap(body.points); -// mesh.faces3.swap(body.faces3); -// } -//} - -//Pillar &Pillar::add_base(double baseheight, double radius) -//{ -// if(baseheight <= 0) return *this; -// if(baseheight > height) baseheight = height; - -// assert(steps >= 0); -// auto last = int(steps - 1); - -// if(radius < r ) radius = r; - -// double a = 2*PI/steps; -// double z = endpt(Z) + baseheight; - -// for(size_t i = 0; i < steps; ++i) { -// double phi = i*a; -// double x = endpt(X) + r*std::cos(phi); -// double y = endpt(Y) + r*std::sin(phi); -// base.points.emplace_back(x, y, z); -// } - -// for(size_t i = 0; i < steps; ++i) { -// double phi = i*a; -// double x = endpt(X) + radius*std::cos(phi); -// double y = endpt(Y) + radius*std::sin(phi); -// base.points.emplace_back(x, y, z - baseheight); -// } - -// auto ep = endpt; ep(Z) += baseheight; -// base.points.emplace_back(endpt); -// base.points.emplace_back(ep); - -// auto& indices = base.faces3; -// auto hcenter = int(base.points.size() - 1); -// auto lcenter = int(base.points.size() - 2); -// auto offs = int(steps); -// for(int i = 0; i < last; ++i) { -// indices.emplace_back(i, i + offs, offs + i + 1); -// indices.emplace_back(i, offs + i + 1, i + 1); -// indices.emplace_back(i, i + 1, hcenter); -// indices.emplace_back(lcenter, offs + i + 1, offs + i); -// } - -// indices.emplace_back(0, last, offs); -// indices.emplace_back(last, offs + last, offs); -// indices.emplace_back(hcenter, last, 0); -// indices.emplace_back(offs, offs + last, lcenter); -// return *this; -//} - Pad::Pad(const TriangleMesh &support_mesh, const ExPolygons & model_contours, double ground_level, @@ -175,6 +108,18 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o) return *this; } +void SupportTreeBuilder::add_pillar_base(long pid, double baseheight, double radius) +{ + std::lock_guard lk(m_mutex); + assert(pid >= 0 && size_t(pid) < m_pillars.size()); + Pillar& pll = m_pillars[size_t(pid)]; + m_pedestals.emplace_back(pll.endpt, std::min(baseheight, pll.height), + std::max(radius, pll.r), pll.r); + + m_pedestals.back().id = m_pedestals.size() - 1; + m_meshcache_valid = false; +} + const TriangleMesh &SupportTreeBuilder::merged_mesh(size_t steps) const { if (m_meshcache_valid) return m_meshcache; @@ -192,6 +137,7 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh(size_t steps) const } for (auto &pedest : m_pedestals) { + if (ctl().stopcondition()) break; merged.merge(get_mesh(pedest, steps)); } @@ -209,7 +155,12 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh(size_t steps) const if (ctl().stopcondition()) break; merged.merge(get_mesh(bs, steps)); } - + + for (auto &anch : m_anchors) { + if (ctl().stopcondition()) break; + merged.merge(get_mesh(anch, steps)); + } + if (ctl().stopcondition()) { // In case of failure we have to return an empty mesh m_meshcache = TriangleMesh(); diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index cc039de6f..2b3ff91a0 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -91,12 +91,7 @@ struct Head { const Vec3d &direction = DOWN, // direction (normal to the dull end) const Vec3d &offset = {0, 0, 0} // displacement ); - - void transform() - { - // TODO: remove occurences - } - + inline double real_width() const { return 2 * r_pin_mm + width_mm + 2 * r_back_mm ; @@ -119,13 +114,6 @@ struct Head { } }; -struct Join { - enum Types { - jtPillarBrigde, jtHeadPillar, jtPillarPedestal, jtBridgePedestal, - jtPillarAnchor, jtBridgeAnchor - }; -}; - // A junction connecting bridges and pillars struct Junction { double r = 1; @@ -136,7 +124,6 @@ struct Junction { Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {} }; - struct Pillar { double height, r; Vec3d endpt; @@ -162,18 +149,16 @@ struct Pillar { } const Vec3d& endpoint() const { return endpt; } - -// Pillar& add_base(double baseheight = 3, double radius = 2); }; // A base for pillars or bridges that end on the ground struct Pedestal { Vec3d pos; - double height, radius; + double height, r_bottom, r_top; long id = ID_UNSET; - Pedestal(const Vec3d &p, double h = 3., double r = 2.) - : pos{p}, height{h}, radius{r} + Pedestal(const Vec3d &p, double h, double rbottom, double rtop) + : pos{p}, height{h}, r_bottom{rbottom}, r_top{rtop} {} }; @@ -301,15 +286,7 @@ public: return pillar.id; } - void add_pillar_base(long pid, double baseheight = 3, double radius = 2) - { - std::lock_guard lk(m_mutex); - assert(pid >= 0 && size_t(pid) < m_pillars.size()); - m_pedestals.emplace_back(m_pillars[size_t(pid)].endpt, baseheight, radius); - m_pedestals.back().id = m_pedestals.size() - 1; - m_meshcache_valid = false; -// m_pillars[size_t(pid)].add_base(baseheight, radius); - } + void add_pillar_base(long pid, double baseheight = 3, double radius = 2); template const Anchor& add_anchor(Args&&...args) { diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 4b8366ee4..c6b2884d2 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -580,7 +580,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, bool normal_mode = true; Vec3d dir = sourcedir; - auto to_floor = [gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; + auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; if (m_cfg.object_elevation_mm < EPSILON) { @@ -599,6 +599,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, // Try to move along the established bridge direction to dodge the // forbidden region for the endpoint. double t = -radius; + bool succ = true; while (std::sqrt(m_mesh.squared_distance(to_floor(endp))) < min_dist || !std::isinf(bridge_mesh_distance(endp, DOWN, radius))) { t += radius; @@ -607,36 +608,58 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, if (t > m_cfg.max_bridge_length_mm || endp(Z) < gndlvl) { if (head_id >= 0) m_builder.add_pillar(head_id, 0.); - return false; + succ = false; + break; } } + + if (!succ) { + if (can_add_base) { + can_add_base = false; + base_r = 0.; + gndlvl -= m_mesh.ground_level_offset(); + min_dist = sd + base_r + EPSILON; + endp = {jp(X), jp(Y), gndlvl + radius}; + + t = -radius; + while (std::sqrt(m_mesh.squared_distance(to_floor(endp))) < min_dist || + !std::isinf(bridge_mesh_distance(endp, DOWN, radius))) { + t += radius; + endp = jp + t * dir; + normal_mode = false; + + if (t > m_cfg.max_bridge_length_mm || endp(Z) < (gndlvl + radius)) { + if (head_id >= 0) m_builder.add_pillar(head_id, 0.); + return false; + } + } + } else return false; + } } + double h = (jp - endp).norm(); + // Check if the deduced route is sane and exit with error if not. - if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm()) { + if (bridge_mesh_distance(jp, dir, radius) < h) { if (head_id >= 0) m_builder.add_pillar(head_id, 0.); return false; } // Straigh path down, no area to dodge if (normal_mode) { - double h = jp.z() - endp.z(); pillar_id = head_id >= 0 ? m_builder.add_pillar(head_id, h) : - m_builder.add_pillar(jp, h, radius); + m_builder.add_pillar(endp, h, radius); if (can_add_base) - m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm, - m_cfg.base_radius_mm); + add_pillar_base(pillar_id); } else { // Insert the bridge to get around the forbidden area -// Vec3d pgnd{endp.x(), endp.y(), gndlvl}; - double h = endp.z() - gndlvl; - pillar_id = m_builder.add_pillar(endp, h, radius); + Vec3d pgnd{endp.x(), endp.y(), gndlvl}; + pillar_id = m_builder.add_pillar(pgnd, endp.z() - gndlvl, radius); if (can_add_base) - m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm, - m_cfg.base_radius_mm); + add_pillar_base(pillar_id); m_builder.add_bridge(jp, endp, radius); m_builder.add_junction(endp, radius); @@ -912,11 +935,8 @@ void SupportTreeBuildsteps::routing_to_ground() BOOST_LOG_TRIVIAL(warning) << "Pillar cannot be created for support point id: " << hid; m_iheads_onmodel.emplace_back(h.id); -// h.invalidate(); continue; } - - h.transform(); } // now we will go through the clusters ones again and connect the @@ -939,7 +959,6 @@ void SupportTreeBuildsteps::routing_to_ground() if (c == cidx) continue; auto &sidehead = m_builder.head(c); - sidehead.transform(); if (!connect_to_nearpillar(sidehead, centerpillarID) && !search_pillar_and_connect(sidehead)) { @@ -1016,6 +1035,12 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) if (it == m_head_to_ground_scans.end()) return false; auto &hit = it->second; + + if (!hit.is_hit()) { + // TODO scan for potential anchor points on model surface + return false; + } + Vec3d hjp = head.junction_point(); double zangle = std::asin(hit.direction()(Z)); zangle = std::max(zangle, PI/4); @@ -1033,13 +1058,11 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm? center_hit.position() : hit.position(); - head.transform(); - - long pillar_id = m_builder.add_pillar(head.id, hit.distance() + h); + long pillar_id = m_builder.add_pillar(head.id, hjp.z() - endp.z()); Pillar &pill = m_builder.pillar(pillar_id); Vec3d taildir = endp - hitp; - double dist = distance(endp, hitp) + m_cfg.head_penetration_mm; + double dist = (hitp - endp).norm() + m_cfg.head_penetration_mm; double w = dist - 2 * head.r_pin_mm - head.r_back_mm; if (w < 0.) { @@ -1050,12 +1073,6 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) m_builder.add_anchor(head.r_back_mm, head.r_pin_mm, w, m_cfg.head_penetration_mm, taildir, hitp); -// Head tailhead(head.r_back_mm, head.r_pin_mm, w, -// m_cfg.head_penetration_mm, taildir, hitp); - -// tailhead.transform(); -// pill.base = tailhead.mesh; - m_pillar_index.guarded_insert(pill.endpoint(), pill.id); return true; @@ -1111,11 +1128,11 @@ void SupportTreeBuildsteps::routing_to_model() auto& head = m_builder.head(idx); // Search nearby pillar - if (search_pillar_and_connect(head)) { head.transform(); return; } + if (search_pillar_and_connect(head)) { return; } // Cannot connect to nearby pillar. We will try to search for // a route to the ground. - if (connect_to_ground(head)) { head.transform(); return; } + if (connect_to_ground(head)) { return; } // No route to the ground, so connect to the model body as a last resort if (connect_to_model_body(head)) { return; } @@ -1300,12 +1317,14 @@ void SupportTreeBuildsteps::interconnect_pillars() if (found) for (unsigned n = 0; n < needpillars; n++) { - Vec3d s = spts[n]; - Pillar p(s, s.z() - gnd, pillar().r); -// p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); + Vec3d s = spts[n]; + Pillar p(Vec3d{s.x(), s.y(), gnd}, s.z() - gnd, pillar().r); if (interconnect(pillar(), p)) { Pillar &pp = m_builder.pillar(m_builder.add_pillar(p)); + + add_pillar_base(pp.id); + m_pillar_index.insert(pp.endpoint(), unsigned(pp.id)); m_builder.add_junction(s, pillar().r); diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index fc5670b16..51d834448 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -17,9 +17,7 @@ enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers X, Y, Z }; -inline Vec2d to_vec2(const Vec3d& v3) { - return {v3(X), v3(Y)}; -} +inline Vec2d to_vec2(const Vec3d &v3) { return {v3(X), v3(Y)}; } inline std::pair dir_to_spheric(const Vec3d &n, double norm = 1.) { @@ -47,7 +45,6 @@ inline Vec3d spheric_to_dir(const std::pair &v) return spheric_to_dir(v.first, v.second); } - // Give points on a 3D ring with given center, radius and orientation // method based on: // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space @@ -297,8 +294,12 @@ class SupportTreeBuildsteps { const Vec3d &sourcedir, double radius, long head_id = ID_UNSET); - - + + void add_pillar_base(long pid) + { + m_builder.add_pillar_base(pid, m_cfg.base_height_mm, m_cfg.base_radius_mm); + } + public: SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm); diff --git a/src/libslic3r/SLA/SupportTreeMesher.cpp b/src/libslic3r/SLA/SupportTreeMesher.cpp index 1d9be6c34..15491775b 100644 --- a/src/libslic3r/SLA/SupportTreeMesher.cpp +++ b/src/libslic3r/SLA/SupportTreeMesher.cpp @@ -94,7 +94,7 @@ Contour3D sphere(double rho, Portion portion, double fa) { Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp) { - assert(steps > 0); + assert(ssteps > 0); Contour3D ret; @@ -157,7 +157,7 @@ Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp) Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) { assert(steps > 0); - assert(length > 0.); + assert(length >= 0.); assert(r_back > 0.); assert(r_pin > 0.); @@ -167,7 +167,7 @@ Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) // both circles perfectly. // Set up the model detail level - const double detail = 2*PI/steps; + const double detail = 2 * PI / steps; // We don't generate whole circles. Instead, we generate only the // portions which are visible (not covered by the robe) To know the @@ -176,26 +176,24 @@ Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) // triangles the following relations: // The height of the whole mesh - const double h = r_back + r_pin + length; - double phi = PI / 2. - std::acos((r_back - r_pin) / h); + const double h = r_back + r_pin + length; + double phi = PI / 2. - std::acos((r_back - r_pin) / h); // To generate a whole circle we would pass a portion of (0, Pi) // To generate only a half horizontal circle we can pass (0, Pi/2) // The calculated phi is an offset to the half circles needed to smooth // the transition from the circle to the robe geometry - auto&& s1 = sphere(r_back, make_portion(0, PI/2 + phi), detail); - auto&& s2 = sphere(r_pin, make_portion(PI/2 + phi, PI), detail); + auto &&s1 = sphere(r_back, make_portion(0, PI / 2 + phi), detail); + auto &&s2 = sphere(r_pin, make_portion(PI / 2 + phi, PI), detail); - for(auto& p : s2.points) p.z() += h; + for (auto &p : s2.points) p.z() += h; mesh.merge(s1); mesh.merge(s2); - for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); - idx1 < s1.points.size() - 1; - idx1++, idx2++) - { + for (size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); + idx1 < s1.points.size() - 1; idx1++, idx2++) { coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2); coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1; @@ -214,43 +212,43 @@ Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) return mesh; } -Contour3D pedestal(const Vec3d &endpt, double baseheight, double radius, size_t steps) +Contour3D halfcone(double baseheight, + double r_bottom, + double r_top, + const Vec3d &pos, + size_t steps) { assert(steps > 0); - if(baseheight <= 0) return {}; - - assert(steps >= 0); - auto last = int(steps - 1); + if (baseheight <= 0 || steps <= 0) return {}; Contour3D base; - double a = 2*PI/steps; - double z = endpt(Z) + baseheight; - - for(size_t i = 0; i < steps; ++i) { - double phi = i*a; - double x = endpt(X) + radius * std::cos(phi); - double y = endpt(Y) + radius * std::sin(phi); - base.points.emplace_back(x, y, z); + double a = 2 * PI / steps; + auto last = int(steps - 1); + Vec3d ep{pos.x(), pos.y(), pos.z() + baseheight}; + for (size_t i = 0; i < steps; ++i) { + double phi = i * a; + double x = pos.x() + r_top * std::cos(phi); + double y = pos.y() + r_top * std::sin(phi); + base.points.emplace_back(x, y, ep.z()); } - for(size_t i = 0; i < steps; ++i) { - double phi = i*a; - double x = endpt(X) + radius*std::cos(phi); - double y = endpt(Y) + radius*std::sin(phi); - base.points.emplace_back(x, y, z - baseheight); + for (size_t i = 0; i < steps; ++i) { + double phi = i * a; + double x = pos.x() + r_bottom * std::cos(phi); + double y = pos.y() + r_bottom * std::sin(phi); + base.points.emplace_back(x, y, pos.z()); } - auto ep = endpt; ep(Z) += baseheight; - base.points.emplace_back(endpt); + base.points.emplace_back(pos); base.points.emplace_back(ep); - auto& indices = base.faces3; - auto hcenter = int(base.points.size() - 1); - auto lcenter = int(base.points.size() - 2); - auto offs = int(steps); - for(int i = 0; i < last; ++i) { + auto &indices = base.faces3; + auto hcenter = int(base.points.size() - 1); + auto lcenter = int(base.points.size() - 2); + auto offs = int(steps); + for (int i = 0; i < last; ++i) { indices.emplace_back(i, i + offs, offs + i + 1); indices.emplace_back(i, offs + i + 1, i + 1); indices.emplace_back(i, i + 1, hcenter); diff --git a/src/libslic3r/SLA/SupportTreeMesher.hpp b/src/libslic3r/SLA/SupportTreeMesher.hpp index 677cab3b8..a086680c3 100644 --- a/src/libslic3r/SLA/SupportTreeMesher.hpp +++ b/src/libslic3r/SLA/SupportTreeMesher.hpp @@ -24,23 +24,28 @@ Contour3D sphere(double rho, // h: Height // ssteps: how many edges will create the base circle // sp: starting point -Contour3D cylinder(double r, double h, size_t steps = 45, const Vec3d &sp = Vec3d::Zero()); +Contour3D cylinder(double r, + double h, + size_t steps = 45, + const Vec3d &sp = Vec3d::Zero()); Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45); -Contour3D pedestal(const Vec3d &pt, double baseheight, double radius, size_t steps = 45); +Contour3D halfcone(double baseheight, + double r_bottom, + double r_top, + const Vec3d &pt = Vec3d::Zero(), + size_t steps = 45); inline Contour3D get_mesh(const Head &h, size_t steps) { Contour3D mesh = pinhead(h.r_pin_mm, h.r_back_mm, h.width_mm, steps); - // To simplify further processing, we translate the mesh so that the - // last vertex of the pointing sphere (the pinpoint) will be at (0,0,0) for(auto& p : mesh.points) p.z() -= (h.fullwidth() - h.r_back_mm); using Quaternion = Eigen::Quaternion; - // We rotate the head to the specified direction The head's pointing + // We rotate the head to the specified direction. The head's pointing // side is facing upwards so this means that it would hold a support // point with a normal pointing straight down. This is the reason of // the -1 z coordinate @@ -64,7 +69,7 @@ inline Contour3D get_mesh(const Pillar &p, size_t steps) inline Contour3D get_mesh(const Pedestal &p, size_t steps) { - return pedestal(p.pos, p.height, p.radius, steps); + return halfcone(p.height, p.r_bottom, p.r_top, p.pos, steps); } inline Contour3D get_mesh(const Junction &j, size_t steps) @@ -89,6 +94,6 @@ inline Contour3D get_mesh(const Bridge &br, size_t steps) return mesh; } -}} +}} // namespace Slic3r::sla #endif // SUPPORTTREEMESHER_HPP diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 5a3bd82a0..4cd94b7ed 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -157,8 +157,8 @@ void test_supports(const std::string &obj_filename, if (std::abs(supportcfg.object_elevation_mm) < EPSILON) allowed_zmin = zmin - 2 * supportcfg.head_back_radius_mm; - REQUIRE(obb.min.z() >= allowed_zmin); - REQUIRE(obb.max.z() <= zmax); + REQUIRE(obb.min.z() >= Approx(allowed_zmin)); + REQUIRE(obb.max.z() <= Approx(zmax)); // Move out the support tree into the byproducts, we can examine it further // in various tests. From f19b3a2344cb499d962b9665a97028b053d98cbc Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 19 Jun 2020 09:25:17 +0200 Subject: [PATCH 59/70] Id-s put in a base class for support tree primitives --- src/libslic3r/SLA/SupportTreeBuilder.hpp | 30 +++++++++------------ src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 8 +++--- src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 2 +- tests/sla_print/sla_test_utils.cpp | 4 +-- 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index 2b3ff91a0..aa8a4ea83 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -58,12 +58,17 @@ template double distance(const Vec& pp1, const Vec& pp2) { return distance(p); } -const constexpr long ID_UNSET = -1; - const Vec3d DOWN = {0.0, 0.0, -1.0}; +struct SupportTreeNode +{ + static const constexpr long ID_UNSET = -1; + + long id = ID_UNSET; // For identification withing a tree. +}; + // A pinhead originating from a support point -struct Head { +struct Head: public SupportTreeNode { Vec3d dir = DOWN; Vec3d pos = {0, 0, 0}; @@ -71,10 +76,7 @@ struct Head { double r_pin_mm = 0.5; double width_mm = 2; double penetration_mm = 0.5; - - // For identification purposes. This will be used as the index into the - // container holding the head structures. See SLASupportTree::Impl - long id = ID_UNSET; + // If there is a pillar connecting to this head, then the id will be set. long pillar_id = ID_UNSET; @@ -115,21 +117,17 @@ struct Head { }; // A junction connecting bridges and pillars -struct Junction { +struct Junction: public SupportTreeNode { double r = 1; Vec3d pos; - - long id = ID_UNSET; Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {} }; -struct Pillar { +struct Pillar: public SupportTreeNode { double height, r; Vec3d endpt; - long id = ID_UNSET; - // If the pillar connects to a head, this is the id of that head bool starts_from_head = true; // Could start from a junction as well long start_junction_id = ID_UNSET; @@ -152,10 +150,9 @@ struct Pillar { }; // A base for pillars or bridges that end on the ground -struct Pedestal { +struct Pedestal: public SupportTreeNode { Vec3d pos; double height, r_bottom, r_top; - long id = ID_UNSET; Pedestal(const Vec3d &p, double h, double rbottom, double rtop) : pos{p}, height{h}, r_bottom{rbottom}, r_top{rtop} @@ -167,9 +164,8 @@ struct Pedestal { struct Anchor: public Head { using Head::Head; }; // A Bridge between two pillars (with junction endpoints) -struct Bridge { +struct Bridge: public SupportTreeNode { double r = 0.8; - long id = ID_UNSET; Vec3d startp = Vec3d::Zero(), endp = Vec3d::Zero(); Bridge(const Vec3d &j1, diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index c6b2884d2..00f09b812 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -570,7 +570,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, long head_id) { double sd = m_cfg.pillar_base_safety_distance_mm; - long pillar_id = ID_UNSET; + long pillar_id = SupportTreeNode::ID_UNSET; bool can_add_base = radius >= m_cfg.head_back_radius_mm; double base_r = can_add_base ? m_cfg.base_radius_mm : 0.; double gndlvl = m_builder.ground_level; @@ -1029,7 +1029,7 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head) bool SupportTreeBuildsteps::connect_to_model_body(Head &head) { - if (head.id <= ID_UNSET) return false; + if (head.id <= SupportTreeNode::ID_UNSET) return false; auto it = m_head_to_ground_scans.find(unsigned(head.id)); if (it == m_head_to_ground_scans.end()) return false; @@ -1084,7 +1084,7 @@ bool SupportTreeBuildsteps::search_pillar_and_connect(const Head &source) // We also need to remove elements progressively from the copied index. PointIndex spindex = m_pillar_index.guarded_clone(); - long nearest_id = ID_UNSET; + long nearest_id = SupportTreeNode::ID_UNSET; Vec3d querypt = source.junction_point(); @@ -1105,7 +1105,7 @@ bool SupportTreeBuildsteps::search_pillar_and_connect(const Head &source) if (size_t(nearest_id) < m_builder.pillarcount()) { if(!connect_to_nearpillar(source, nearest_id) || m_builder.pillar(nearest_id).r < source.r_back_mm) { - nearest_id = ID_UNSET; // continue searching + nearest_id = SupportTreeNode::ID_UNSET; // continue searching spindex.remove(ne); // without the current pillar } } diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index 51d834448..e8f73149e 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -293,7 +293,7 @@ class SupportTreeBuildsteps { bool create_ground_pillar(const Vec3d &jp, const Vec3d &sourcedir, double radius, - long head_id = ID_UNSET); + long head_id = SupportTreeNode::ID_UNSET); void add_pillar_base(long pid) { diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 4cd94b7ed..bc0cfb0cd 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -175,8 +175,8 @@ void check_support_tree_integrity(const sla::SupportTreeBuilder &stree, double H2 = cfg.max_dual_pillar_height_mm; for (const sla::Head &head : stree.heads()) { - REQUIRE((!head.is_valid() || head.pillar_id != sla::ID_UNSET || - head.bridge_id != sla::ID_UNSET)); + REQUIRE((!head.is_valid() || head.pillar_id != sla::SupportTreeNode::ID_UNSET || + head.bridge_id != sla::SupportTreeNode::ID_UNSET)); } for (const sla::Pillar &pillar : stree.pillars()) { From 645fbed88bb94d3addf32691e08f0e9453978120 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 19 Jun 2020 09:49:50 +0200 Subject: [PATCH 60/70] Make compile time support tree conf params constexpr --- src/libslic3r/SLA/SupportTree.cpp | 14 -------------- src/libslic3r/SLA/SupportTree.hpp | 14 +++++++------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/SLA/SupportTree.cpp b/src/libslic3r/SLA/SupportTree.cpp index eec819e22..1bb4cfab7 100644 --- a/src/libslic3r/SLA/SupportTree.cpp +++ b/src/libslic3r/SLA/SupportTree.cpp @@ -28,20 +28,6 @@ namespace Slic3r { namespace sla { -// Compile time configuration value definitions: - -// The max Z angle for a normal at which it will get completely ignored. -const double SupportConfig::normal_cutoff_angle = 150.0 * M_PI / 180.0; - -// The shortest distance of any support structure from the model surface -const double SupportConfig::safety_distance_mm = 0.5; - -const double SupportConfig::max_solo_pillar_height_mm = 15.0; -const double SupportConfig::max_dual_pillar_height_mm = 35.0; -const double SupportConfig::optimizer_rel_score_diff = 1e-6; -const unsigned SupportConfig::optimizer_max_iterations = 1000; -const unsigned SupportConfig::pillar_cascade_neighbors = 3; - void SupportTree::retrieve_full_mesh(TriangleMesh &outmesh) const { outmesh.merge(retrieve_mesh(MeshType::Support)); outmesh.merge(retrieve_mesh(MeshType::Pad)); diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 3b9f603fd..1415ab8fe 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -94,16 +94,16 @@ struct SupportConfig // ///////////////////////////////////////////////////////////////////////// // The max Z angle for a normal at which it will get completely ignored. - static const double normal_cutoff_angle; + static const double constexpr normal_cutoff_angle = 150.0 * M_PI / 180.0; // The shortest distance of any support structure from the model surface - static const double safety_distance_mm; + static const double constexpr safety_distance_mm = 0.5; - static const double max_solo_pillar_height_mm; - static const double max_dual_pillar_height_mm; - static const double optimizer_rel_score_diff; - static const unsigned optimizer_max_iterations; - static const unsigned pillar_cascade_neighbors; + static const double constexpr max_solo_pillar_height_mm = 15.0; + static const double constexpr max_dual_pillar_height_mm = 35.0; + static const double constexpr optimizer_rel_score_diff = 1e-6; + static const unsigned constexpr optimizer_max_iterations = 1000; + static const unsigned constexpr pillar_cascade_neighbors = 3; }; From 1eec6c473c660196dbe7ca421d0abefbf4ea8739 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 25 Jun 2020 13:58:51 +0200 Subject: [PATCH 61/70] Rename EigenMesh3D to IndexedMesh and SupportConfig to SupportTreeConfig --- src/libslic3r/CMakeLists.txt | 4 +- src/libslic3r/SLA/Contour3D.cpp | 4 +- src/libslic3r/SLA/Contour3D.hpp | 4 +- src/libslic3r/SLA/Hollowing.cpp | 4 +- .../SLA/{EigenMesh3D.cpp => IndexedMesh.cpp} | 46 ++--- .../SLA/{EigenMesh3D.hpp => IndexedMesh.hpp} | 32 +-- src/libslic3r/SLA/ReprojectPointsOnMesh.hpp | 6 +- src/libslic3r/SLA/SupportPointGenerator.cpp | 10 +- src/libslic3r/SLA/SupportPointGenerator.hpp | 8 +- src/libslic3r/SLA/SupportTree.hpp | 21 +- src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 186 +++++++++--------- src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 19 +- src/libslic3r/SLAPrint.cpp | 6 +- src/libslic3r/SLAPrint.hpp | 2 +- src/slic3r/GUI/MeshUtils.cpp | 4 +- src/slic3r/GUI/MeshUtils.hpp | 4 +- tests/sla_print/sla_print_tests.cpp | 12 +- tests/sla_print/sla_raycast_tests.cpp | 4 +- tests/sla_print/sla_test_utils.cpp | 10 +- tests/sla_print/sla_test_utils.hpp | 12 +- tests/sla_print/sla_treebuilder_tests.cpp | 134 ++++++------- 21 files changed, 269 insertions(+), 263 deletions(-) rename src/libslic3r/SLA/{EigenMesh3D.cpp => IndexedMesh.cpp} (92%) rename src/libslic3r/SLA/{EigenMesh3D.hpp => IndexedMesh.hpp} (86%) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 20f3c6b4b..91da5df5d 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -236,8 +236,8 @@ add_library(libslic3r STATIC SLA/SupportPointGenerator.cpp SLA/Contour3D.hpp SLA/Contour3D.cpp - SLA/EigenMesh3D.hpp - SLA/EigenMesh3D.cpp + SLA/IndexedMesh.hpp + SLA/IndexedMesh.cpp SLA/Clustering.hpp SLA/Clustering.cpp SLA/ReprojectPointsOnMesh.hpp diff --git a/src/libslic3r/SLA/Contour3D.cpp b/src/libslic3r/SLA/Contour3D.cpp index 408465d43..96d10af20 100644 --- a/src/libslic3r/SLA/Contour3D.cpp +++ b/src/libslic3r/SLA/Contour3D.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include @@ -27,7 +27,7 @@ Contour3D::Contour3D(TriangleMesh &&trmesh) faces3.swap(trmesh.its.indices); } -Contour3D::Contour3D(const EigenMesh3D &emesh) { +Contour3D::Contour3D(const IndexedMesh &emesh) { points.reserve(emesh.vertices().size()); faces3.reserve(emesh.indices().size()); diff --git a/src/libslic3r/SLA/Contour3D.hpp b/src/libslic3r/SLA/Contour3D.hpp index 1a4fa9a29..3380cd6ab 100644 --- a/src/libslic3r/SLA/Contour3D.hpp +++ b/src/libslic3r/SLA/Contour3D.hpp @@ -10,7 +10,7 @@ using Vec4i = Eigen::Matrix; namespace sla { -class EigenMesh3D; +class IndexedMesh; /// Dumb vertex mesh consisting of triangles (or) quads. Capable of merging with /// other meshes of this type and converting to and from other mesh formats. @@ -22,7 +22,7 @@ struct Contour3D { Contour3D() = default; Contour3D(const TriangleMesh &trmesh); Contour3D(TriangleMesh &&trmesh); - Contour3D(const EigenMesh3D &emesh); + Contour3D(const IndexedMesh &emesh); Contour3D& merge(const Contour3D& ctr); Contour3D& merge(const Pointf3s& triangles); diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 44e4dd839..5334054a0 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include #include @@ -159,7 +159,7 @@ bool DrainHole::get_intersections(const Vec3f& s, const Vec3f& dir, const Eigen::ParametrizedLine ray(s, dir.normalized()); for (size_t i=0; i<2; ++i) - out[i] = std::make_pair(sla::EigenMesh3D::hit_result::infty(), Vec3d::Zero()); + out[i] = std::make_pair(sla::IndexedMesh::hit_result::infty(), Vec3d::Zero()); const float sqr_radius = pow(radius, 2.f); diff --git a/src/libslic3r/SLA/EigenMesh3D.cpp b/src/libslic3r/SLA/IndexedMesh.cpp similarity index 92% rename from src/libslic3r/SLA/EigenMesh3D.cpp rename to src/libslic3r/SLA/IndexedMesh.cpp index be44e324c..573b62b6d 100644 --- a/src/libslic3r/SLA/EigenMesh3D.cpp +++ b/src/libslic3r/SLA/IndexedMesh.cpp @@ -1,4 +1,4 @@ -#include "EigenMesh3D.hpp" +#include "IndexedMesh.hpp" #include "Concurrency.hpp" #include @@ -12,7 +12,7 @@ namespace Slic3r { namespace sla { -class EigenMesh3D::AABBImpl { +class IndexedMesh::AABBImpl { private: AABBTreeIndirect::Tree3f m_tree; @@ -57,7 +57,7 @@ public: static const constexpr double MESH_EPS = 1e-6; -EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh) +IndexedMesh::IndexedMesh(const TriangleMesh& tmesh) : m_aabb(new AABBImpl()), m_tm(&tmesh) { auto&& bb = tmesh.bounding_box(); @@ -67,61 +67,61 @@ EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh) m_aabb->init(tmesh); } -EigenMesh3D::~EigenMesh3D() {} +IndexedMesh::~IndexedMesh() {} -EigenMesh3D::EigenMesh3D(const EigenMesh3D &other): +IndexedMesh::IndexedMesh(const IndexedMesh &other): m_tm(other.m_tm), m_ground_level(other.m_ground_level), m_aabb( new AABBImpl(*other.m_aabb) ) {} -EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other) +IndexedMesh &IndexedMesh::operator=(const IndexedMesh &other) { m_tm = other.m_tm; m_ground_level = other.m_ground_level; m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this; } -EigenMesh3D &EigenMesh3D::operator=(EigenMesh3D &&other) = default; +IndexedMesh &IndexedMesh::operator=(IndexedMesh &&other) = default; -EigenMesh3D::EigenMesh3D(EigenMesh3D &&other) = default; +IndexedMesh::IndexedMesh(IndexedMesh &&other) = default; -const std::vector& EigenMesh3D::vertices() const +const std::vector& IndexedMesh::vertices() const { return m_tm->its.vertices; } -const std::vector& EigenMesh3D::indices() const +const std::vector& IndexedMesh::indices() const { return m_tm->its.indices; } -const Vec3f& EigenMesh3D::vertices(size_t idx) const +const Vec3f& IndexedMesh::vertices(size_t idx) const { return m_tm->its.vertices[idx]; } -const Vec3i& EigenMesh3D::indices(size_t idx) const +const Vec3i& IndexedMesh::indices(size_t idx) const { return m_tm->its.indices[idx]; } -Vec3d EigenMesh3D::normal_by_face_id(int face_id) const { +Vec3d IndexedMesh::normal_by_face_id(int face_id) const { return m_tm->stl.facet_start[face_id].normal.cast(); } -EigenMesh3D::hit_result -EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const +IndexedMesh::hit_result +IndexedMesh::query_ray_hit(const Vec3d &s, const Vec3d &dir) const { assert(is_approx(dir.norm(), 1.)); igl::Hit hit; @@ -149,10 +149,10 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const return ret; } -std::vector -EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const +std::vector +IndexedMesh::query_ray_hits(const Vec3d &s, const Vec3d &dir) const { - std::vector outs; + std::vector outs; std::vector hits; m_aabb->intersect_ray(*m_tm, s, dir, hits); @@ -170,7 +170,7 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const // Convert the igl::Hit into hit_result outs.reserve(hits.size()); for (const igl::Hit& hit : hits) { - outs.emplace_back(EigenMesh3D::hit_result(*this)); + outs.emplace_back(IndexedMesh::hit_result(*this)); outs.back().m_t = double(hit.t); outs.back().m_dir = dir; outs.back().m_source = s; @@ -185,8 +185,8 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const #ifdef SLIC3R_HOLE_RAYCASTER -EigenMesh3D::hit_result EigenMesh3D::filter_hits( - const std::vector& object_hits) const +IndexedMesh::hit_result IndexedMesh::filter_hits( + const std::vector& object_hits) const { assert(! m_holes.empty()); hit_result out(*this); @@ -282,7 +282,7 @@ EigenMesh3D::hit_result EigenMesh3D::filter_hits( #endif -double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const { +double IndexedMesh::squared_distance(const Vec3d &p, int& i, Vec3d& c) const { double sqdst = 0; Eigen::Matrix pp = p; Eigen::Matrix cc; @@ -303,7 +303,7 @@ static bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2, } PointSet normals(const PointSet& points, - const EigenMesh3D& mesh, + const IndexedMesh& mesh, double eps, std::function thr, // throw on cancel const std::vector& pt_indices) diff --git a/src/libslic3r/SLA/EigenMesh3D.hpp b/src/libslic3r/SLA/IndexedMesh.hpp similarity index 86% rename from src/libslic3r/SLA/EigenMesh3D.hpp rename to src/libslic3r/SLA/IndexedMesh.hpp index c9196bb43..b0970608e 100644 --- a/src/libslic3r/SLA/EigenMesh3D.hpp +++ b/src/libslic3r/SLA/IndexedMesh.hpp @@ -1,5 +1,5 @@ -#ifndef SLA_EIGENMESH3D_H -#define SLA_EIGENMESH3D_H +#ifndef SLA_INDEXEDMESH_H +#define SLA_INDEXEDMESH_H #include #include @@ -26,7 +26,7 @@ using PointSet = Eigen::MatrixXd; /// An index-triangle structure for libIGL functions. Also serves as an /// alternative (raw) input format for the SLASupportTree. // Implemented in libslic3r/SLA/Common.cpp -class EigenMesh3D { +class IndexedMesh { class AABBImpl; const TriangleMesh* m_tm; @@ -42,15 +42,15 @@ class EigenMesh3D { public: - explicit EigenMesh3D(const TriangleMesh&); + explicit IndexedMesh(const TriangleMesh&); - EigenMesh3D(const EigenMesh3D& other); - EigenMesh3D& operator=(const EigenMesh3D&); + IndexedMesh(const IndexedMesh& other); + IndexedMesh& operator=(const IndexedMesh&); - EigenMesh3D(EigenMesh3D &&other); - EigenMesh3D& operator=(EigenMesh3D &&other); + IndexedMesh(IndexedMesh &&other); + IndexedMesh& operator=(IndexedMesh &&other); - ~EigenMesh3D(); + ~IndexedMesh(); inline double ground_level() const { return m_ground_level + m_gnd_offset; } inline void ground_level_offset(double o) { m_gnd_offset = o; } @@ -66,15 +66,15 @@ public: // m_t holds a distance from m_source to the intersection. double m_t = infty(); int m_face_id = -1; - const EigenMesh3D *m_mesh = nullptr; + const IndexedMesh *m_mesh = nullptr; Vec3d m_dir; Vec3d m_source; Vec3d m_normal; - friend class EigenMesh3D; + friend class IndexedMesh; // A valid object of this class can only be obtained from - // EigenMesh3D::query_ray_hit method. - explicit inline hit_result(const EigenMesh3D& em): m_mesh(&em) {} + // IndexedMesh::query_ray_hit method. + explicit inline hit_result(const IndexedMesh& em): m_mesh(&em) {} public: // This denotes no hit on the mesh. static inline constexpr double infty() { return std::numeric_limits::infinity(); } @@ -111,7 +111,7 @@ public: // This function is currently not used anywhere, it was written when the // holes were subtracted on slices, that is, before we started using CGAL // to actually cut the holes into the mesh. - hit_result filter_hits(const std::vector& obj_hits) const; + hit_result filter_hits(const std::vector& obj_hits) const; #endif // Casting a ray on the mesh, returns the distance where the hit occures. @@ -136,11 +136,11 @@ public: // Calculate the normals for the selected points (from 'points' set) on the // mesh. This will call squared distance for each point. PointSet normals(const PointSet& points, - const EigenMesh3D& convert_mesh, + const IndexedMesh& convert_mesh, double eps = 0.05, // min distance from edges std::function throw_on_cancel = [](){}, const std::vector& selected_points = {}); }} // namespace Slic3r::sla -#endif // EIGENMESH3D_H +#endif // INDEXEDMESH_H diff --git a/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp b/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp index 702d1bce1..4737a6c21 100644 --- a/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp +++ b/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp @@ -4,7 +4,7 @@ #include "libslic3r/Point.hpp" #include "SupportPoint.hpp" #include "Hollowing.hpp" -#include "EigenMesh3D.hpp" +#include "IndexedMesh.hpp" #include "libslic3r/Model.hpp" #include @@ -15,7 +15,7 @@ template Vec3d pos(const Pt &p) { return p.pos.template cast() template void pos(Pt &p, const Vec3d &pp) { p.pos = pp.cast(); } template -void reproject_support_points(const EigenMesh3D &mesh, std::vector &pts) +void reproject_support_points(const IndexedMesh &mesh, std::vector &pts) { tbb::parallel_for(size_t(0), pts.size(), [&mesh, &pts](size_t idx) { int junk; @@ -40,7 +40,7 @@ inline void reproject_points_and_holes(ModelObject *object) TriangleMesh rmsh = object->raw_mesh(); rmsh.require_shared_vertices(); - EigenMesh3D emesh{rmsh}; + IndexedMesh emesh{rmsh}; if (has_sppoints) reproject_support_points(emesh, object->sla_support_points); diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index b598439ca..3cd075ae6 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -50,7 +50,7 @@ float SupportPointGenerator::distance_limit(float angle) const }*/ SupportPointGenerator::SupportPointGenerator( - const sla::EigenMesh3D &emesh, + const sla::IndexedMesh &emesh, const std::vector &slices, const std::vector & heights, const Config & config, @@ -64,7 +64,7 @@ SupportPointGenerator::SupportPointGenerator( } SupportPointGenerator::SupportPointGenerator( - const EigenMesh3D &emesh, + const IndexedMesh &emesh, const SupportPointGenerator::Config &config, std::function throw_on_cancel, std::function statusfn) @@ -95,8 +95,8 @@ void SupportPointGenerator::project_onto_mesh(std::vector& po m_throw_on_cancel(); Vec3f& p = points[point_id].pos; // Project the point upward and downward and choose the closer intersection with the mesh. - sla::EigenMesh3D::hit_result hit_up = m_emesh.query_ray_hit(p.cast(), Vec3d(0., 0., 1.)); - sla::EigenMesh3D::hit_result hit_down = m_emesh.query_ray_hit(p.cast(), Vec3d(0., 0., -1.)); + sla::IndexedMesh::hit_result hit_up = m_emesh.query_ray_hit(p.cast(), Vec3d(0., 0., 1.)); + sla::IndexedMesh::hit_result hit_down = m_emesh.query_ray_hit(p.cast(), Vec3d(0., 0., -1.)); bool up = hit_up.is_hit(); bool down = hit_down.is_hit(); @@ -104,7 +104,7 @@ void SupportPointGenerator::project_onto_mesh(std::vector& po if (!up && !down) continue; - sla::EigenMesh3D::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down; + sla::IndexedMesh::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down; p = p + (hit.distance() * hit.direction()).cast(); } }); diff --git a/src/libslic3r/SLA/SupportPointGenerator.hpp b/src/libslic3r/SLA/SupportPointGenerator.hpp index 3f07e9674..f1b377025 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.hpp +++ b/src/libslic3r/SLA/SupportPointGenerator.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include @@ -27,10 +27,10 @@ public: inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2) }; - SupportPointGenerator(const EigenMesh3D& emesh, const std::vector& slices, + SupportPointGenerator(const IndexedMesh& emesh, const std::vector& slices, const std::vector& heights, const Config& config, std::function throw_on_cancel, std::function statusfn); - SupportPointGenerator(const EigenMesh3D& emesh, const Config& config, std::function throw_on_cancel, std::function statusfn); + SupportPointGenerator(const IndexedMesh& emesh, const Config& config, std::function throw_on_cancel, std::function statusfn); const std::vector& output() const { return m_output; } std::vector& output() { return m_output; } @@ -206,7 +206,7 @@ private: static void output_structures(const std::vector &structures); #endif // SLA_SUPPORTPOINTGEN_DEBUG - const EigenMesh3D& m_emesh; + const IndexedMesh& m_emesh; std::function m_throw_on_cancel; std::function m_statusfn; diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 1415ab8fe..7d54b76a4 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include @@ -31,7 +31,7 @@ enum class PillarConnectionMode dynamic }; -struct SupportConfig +struct SupportTreeConfig { bool enabled = true; @@ -107,23 +107,30 @@ struct SupportConfig }; +// TODO: Part of future refactor +//class SupportConfig { +// std::optional tree_cfg {std::in_place_t{}}; // fill up +// std::optional pad_cfg; +//}; + enum class MeshType { Support, Pad }; struct SupportableMesh { - EigenMesh3D emesh; + IndexedMesh emesh; SupportPoints pts; - SupportConfig cfg; + SupportTreeConfig cfg; + PadConfig pad_cfg; explicit SupportableMesh(const TriangleMesh & trmsh, const SupportPoints &sp, - const SupportConfig &c) + const SupportTreeConfig &c) : emesh{trmsh}, pts{sp}, cfg{c} {} - explicit SupportableMesh(const EigenMesh3D &em, + explicit SupportableMesh(const IndexedMesh &em, const SupportPoints &sp, - const SupportConfig &c) + const SupportTreeConfig &c) : emesh{em}, pts{sp}, cfg{c} {} }; diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 00f09b812..b29ad0b9c 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -14,7 +14,7 @@ using libnest2d::opt::StopCriteria; using libnest2d::opt::GeneticOptimizer; using libnest2d::opt::SubplexOptimizer; -template +template static Hit min_hit(const C &hits) { auto mit = std::min_element(hits.begin(), hits.end(), @@ -25,118 +25,118 @@ static Hit min_hit(const C &hits) return *mit; } -EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &h) -{ - static const size_t SAMPLES = 8; +//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Head &h) +//{ +// static const size_t SAMPLES = 8; - // Move away slightly from the touching point to avoid raycasting on the - // inner surface of the mesh. +// // Move away slightly from the touching point to avoid raycasting on the +// // inner surface of the mesh. - const double& sd = msh.cfg.safety_distance_mm; +// const double& sd = msh.cfg.safety_distance_mm; - auto& m = msh.emesh; - using HitResult = EigenMesh3D::hit_result; +// auto& m = msh.emesh; +// using HitResult = IndexedMesh::hit_result; - // Hit results - std::array hits; +// // Hit results +// std::array hits; - Vec3d s1 = h.pos, s2 = h.junction_point(); +// Vec3d s1 = h.pos, s2 = h.junction_point(); - struct Rings { - double rpin; - double rback; - Vec3d spin; - Vec3d sback; - PointRing ring; +// struct Rings { +// double rpin; +// double rback; +// Vec3d spin; +// Vec3d sback; +// PointRing ring; - Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); } - Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); } - } rings {h.r_pin_mm + sd, h.r_back_mm + sd, s1, s2, h.dir}; +// Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); } +// Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); } +// } rings {h.r_pin_mm + sd, h.r_back_mm + sd, s1, s2, h.dir}; - // We will shoot multiple rays from the head pinpoint in the direction - // of the pinhead robe (side) surface. The result will be the smallest - // hit distance. +// // We will shoot multiple rays from the head pinpoint in the direction +// // of the pinhead robe (side) surface. The result will be the smallest +// // hit distance. - auto hitfn = [&m, &rings, sd](HitResult &hit, size_t i) { - // Point on the circle on the pin sphere - Vec3d ps = rings.pinring(i); - // This is the point on the circle on the back sphere - Vec3d p = rings.backring(i); +// auto hitfn = [&m, &rings, sd](HitResult &hit, size_t i) { +// // Point on the circle on the pin sphere +// Vec3d ps = rings.pinring(i); +// // This is the point on the circle on the back sphere +// Vec3d p = rings.backring(i); - // Point ps is not on mesh but can be inside or - // outside as well. This would cause many problems - // with ray-casting. To detect the position we will - // use the ray-casting result (which has an is_inside - // predicate). +// // Point ps is not on mesh but can be inside or +// // outside as well. This would cause many problems +// // with ray-casting. To detect the position we will +// // use the ray-casting result (which has an is_inside +// // predicate). - Vec3d n = (p - ps).normalized(); - auto q = m.query_ray_hit(ps + sd * n, n); +// Vec3d n = (p - ps).normalized(); +// auto q = m.query_ray_hit(ps + sd * n, n); - if (q.is_inside()) { // the hit is inside the model - if (q.distance() > rings.rpin) { - // If we are inside the model and the hit - // distance is bigger than our pin circle - // diameter, it probably indicates that the - // support point was already inside the - // model, or there is really no space - // around the point. We will assign a zero - // hit distance to these cases which will - // enforce the function return value to be - // an invalid ray with zero hit distance. - // (see min_element at the end) - hit = HitResult(0.0); - } else { - // re-cast the ray from the outside of the - // object. The starting point has an offset - // of 2*safety_distance because the - // original ray has also had an offset - auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n); - hit = q2; - } - } else - hit = q; - }; +// if (q.is_inside()) { // the hit is inside the model +// if (q.distance() > rings.rpin) { +// // If we are inside the model and the hit +// // distance is bigger than our pin circle +// // diameter, it probably indicates that the +// // support point was already inside the +// // model, or there is really no space +// // around the point. We will assign a zero +// // hit distance to these cases which will +// // enforce the function return value to be +// // an invalid ray with zero hit distance. +// // (see min_element at the end) +// hit = HitResult(0.0); +// } else { +// // re-cast the ray from the outside of the +// // object. The starting point has an offset +// // of 2*safety_distance because the +// // original ray has also had an offset +// auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n); +// hit = q2; +// } +// } else +// hit = q; +// }; - ccr::enumerate(hits.begin(), hits.end(), hitfn); +// ccr::enumerate(hits.begin(), hits.end(), hitfn); - return min_hit(hits); -} +// return min_hit(hits); +//} -EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d) -{ +//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d) +//{ - static const size_t SAMPLES = 8; +// static const size_t SAMPLES = 8; - Vec3d dir = (br.endp - br.startp).normalized(); - PointRing ring{dir}; +// Vec3d dir = (br.endp - br.startp).normalized(); +// PointRing ring{dir}; - using Hit = EigenMesh3D::hit_result; +// using Hit = IndexedMesh::hit_result; - // Hit results - std::array hits; +// // Hit results +// std::array hits; - double sd = std::isnan(safety_d) ? msh.cfg.safety_distance_mm : safety_d; +// double sd = std::isnan(safety_d) ? msh.cfg.safety_distance_mm : safety_d; - auto hitfn = [&msh, &br, &ring, dir, sd] (Hit &hit, size_t i) { +// auto hitfn = [&msh, &br, &ring, dir, sd] (Hit &hit, size_t i) { - // Point on the circle on the pin sphere - Vec3d p = ring.get(i, br.startp, br.r + sd); +// // Point on the circle on the pin sphere +// Vec3d p = ring.get(i, br.startp, br.r + sd); - auto hr = msh.emesh.query_ray_hit(p + br.r * dir, dir); +// auto hr = msh.emesh.query_ray_hit(p + br.r * dir, dir); - if(hr.is_inside()) { - if(hr.distance() > 2 * br.r + sd) hit = Hit(0.0); - else { - // re-cast the ray from the outside of the object - hit = msh.emesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, dir); - } - } else hit = hr; - }; +// if(hr.is_inside()) { +// if(hr.distance() > 2 * br.r + sd) hit = Hit(0.0); +// else { +// // re-cast the ray from the outside of the object +// hit = msh.emesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, dir); +// } +// } else hit = hr; +// }; - ccr::enumerate(hits.begin(), hits.end(), hitfn); +// ccr::enumerate(hits.begin(), hits.end(), hitfn); - return min_hit(hits); -} +// return min_hit(hits); +//} SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm) @@ -281,7 +281,7 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, return pc == ABORT; } -EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( +IndexedMesh::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width) { static const size_t SAMPLES = 8; @@ -292,7 +292,7 @@ EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( const double& sd = m_cfg.safety_distance_mm; auto& m = m_mesh; - using HitResult = EigenMesh3D::hit_result; + using HitResult = IndexedMesh::hit_result; // Hit results std::array hits; @@ -357,13 +357,13 @@ EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( return min_hit(hits); } -EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( +IndexedMesh::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( const Vec3d &src, const Vec3d &dir, double r, double sd) { static const size_t SAMPLES = 8; PointRing ring{dir}; - using Hit = EigenMesh3D::hit_result; + using Hit = IndexedMesh::hit_result; // Hit results std::array hits; @@ -742,7 +742,7 @@ void SupportTreeBuildsteps::filter() auto nn = spheric_to_dir(polar, azimuth).normalized(); // check available distance - EigenMesh3D::hit_result t + IndexedMesh::hit_result t = pinhead_mesh_intersect(hp, // touching point nn, // normal pin_r, @@ -781,7 +781,7 @@ void SupportTreeBuildsteps::filter() polar = std::get<0>(oresult.optimum); azimuth = std::get<1>(oresult.optimum); nn = spheric_to_dir(polar, azimuth).normalized(); - t = EigenMesh3D::hit_result(oresult.score); + t = IndexedMesh::hit_result(oresult.score); } } diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index e8f73149e..a98586789 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -103,9 +103,8 @@ public: } }; -EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan("")); -EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan("")); - +//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan("")); +//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan("")); inline Vec3d dirv(const Vec3d& startp, const Vec3d& endp) { return (endp - startp).normalized(); @@ -181,8 +180,8 @@ IntegerOnly pairhash(I a, I b) } class SupportTreeBuildsteps { - const SupportConfig& m_cfg; - const EigenMesh3D& m_mesh; + const SupportTreeConfig& m_cfg; + const IndexedMesh& m_mesh; const std::vector& m_support_pts; using PtIndices = std::vector; @@ -191,7 +190,7 @@ class SupportTreeBuildsteps { PtIndices m_iheads_onmodel; PtIndices m_iheadless; // headless support points - std::map m_head_to_ground_scans; + std::map m_head_to_ground_scans; // normals for support points from model faces. PointSet m_support_nmls; @@ -217,7 +216,7 @@ class SupportTreeBuildsteps { // When bridging heads to pillars... TODO: find a cleaner solution ccr::BlockingMutex m_bridge_mutex; - inline EigenMesh3D::hit_result ray_mesh_intersect(const Vec3d& s, + inline IndexedMesh::hit_result ray_mesh_intersect(const Vec3d& s, const Vec3d& dir) { return m_mesh.query_ray_hit(s, dir); @@ -234,7 +233,7 @@ class SupportTreeBuildsteps { // point was inside the model, an "invalid" hit_result will be returned // with a zero distance value instead of a NAN. This way the result can // be used safely for comparison with other distances. - EigenMesh3D::hit_result pinhead_mesh_intersect( + IndexedMesh::hit_result pinhead_mesh_intersect( const Vec3d& s, const Vec3d& dir, double r_pin, @@ -249,13 +248,13 @@ class SupportTreeBuildsteps { // point was inside the model, an "invalid" hit_result will be returned // with a zero distance value instead of a NAN. This way the result can // be used safely for comparison with other distances. - EigenMesh3D::hit_result bridge_mesh_intersect( + IndexedMesh::hit_result bridge_mesh_intersect( const Vec3d& s, const Vec3d& dir, double r, double safety_d); - EigenMesh3D::hit_result bridge_mesh_intersect( + IndexedMesh::hit_result bridge_mesh_intersect( const Vec3d& s, const Vec3d& dir, double r) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 2402207a8..eee3bbc9f 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -35,9 +35,9 @@ bool is_zero_elevation(const SLAPrintObjectConfig &c) } // Compile the argument for support creation from the static print config. -sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) +sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c) { - sla::SupportConfig scfg; + sla::SupportTreeConfig scfg; scfg.enabled = c.supports_enable.getBool(); scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat(); @@ -616,7 +616,7 @@ std::string SLAPrint::validate() const return L("Cannot proceed without support points! " "Add support points or disable support generation."); - sla::SupportConfig cfg = make_support_cfg(po->config()); + sla::SupportTreeConfig cfg = make_support_cfg(po->config()); double elv = cfg.object_elevation_mm; diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 9d41586ee..f4b220c58 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -544,7 +544,7 @@ private: bool is_zero_elevation(const SLAPrintObjectConfig &c); -sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c); +sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c); sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c); diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 581f50a88..ee0abe76f 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -134,7 +134,7 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& Vec3d direction; line_from_mouse_pos(mouse_pos, trafo, camera, point, direction); - std::vector hits = m_emesh.query_ray_hits(point, direction); + std::vector hits = m_emesh.query_ray_hits(point, direction); if (hits.empty()) return false; // no intersection found @@ -184,7 +184,7 @@ std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo bool is_obscured = false; // Cast a ray in the direction of the camera and look for intersection with the mesh: - std::vector hits; + std::vector hits; // Offset the start of the ray by EPSILON to account for numerical inaccuracies. hits = m_emesh.query_ray_hits((inverse_trafo * pt + direction_to_camera_mesh * EPSILON).cast(), direction_to_camera.cast()); diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 2758577a2..60dcb30c8 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -3,7 +3,7 @@ #include "libslic3r/Point.hpp" #include "libslic3r/Geometry.hpp" -#include "libslic3r/SLA/EigenMesh3D.hpp" +#include "libslic3r/SLA/IndexedMesh.hpp" #include "admesh/stl.h" #include "slic3r/GUI/3DScene.hpp" @@ -147,7 +147,7 @@ public: Vec3f get_triangle_normal(size_t facet_idx) const; private: - sla::EigenMesh3D m_emesh; + sla::IndexedMesh m_emesh; std::vector m_normals; }; diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index 82df2c1a6..9a9c762e3 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -37,9 +37,9 @@ TEST_CASE("Support point generator should be deterministic if seeded", "[SLASupportGeneration], [SLAPointGen]") { TriangleMesh mesh = load_model("A_upsidedown.obj"); - sla::EigenMesh3D emesh{mesh}; + sla::IndexedMesh emesh{mesh}; - sla::SupportConfig supportcfg; + sla::SupportTreeConfig supportcfg; sla::SupportPointGenerator::Config autogencfg; autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm); sla::SupportPointGenerator point_gen{emesh, autogencfg, [] {}, [](int) {}}; @@ -124,14 +124,14 @@ TEST_CASE("WingedPadAroundObjectIsValid", "[SLASupportGeneration]") { } TEST_CASE("ElevatedSupportGeometryIsValid", "[SLASupportGeneration]") { - sla::SupportConfig supportcfg; + sla::SupportTreeConfig supportcfg; supportcfg.object_elevation_mm = 5.; for (auto fname : SUPPORT_TEST_MODELS) test_supports(fname); } TEST_CASE("FloorSupportGeometryIsValid", "[SLASupportGeneration]") { - sla::SupportConfig supportcfg; + sla::SupportTreeConfig supportcfg; supportcfg.object_elevation_mm = 0; for (auto &fname: SUPPORT_TEST_MODELS) test_supports(fname, supportcfg); @@ -139,7 +139,7 @@ TEST_CASE("FloorSupportGeometryIsValid", "[SLASupportGeneration]") { TEST_CASE("ElevatedSupportsDoNotPierceModel", "[SLASupportGeneration]") { - sla::SupportConfig supportcfg; + sla::SupportTreeConfig supportcfg; for (auto fname : SUPPORT_TEST_MODELS) test_support_model_collision(fname, supportcfg); @@ -147,7 +147,7 @@ TEST_CASE("ElevatedSupportsDoNotPierceModel", "[SLASupportGeneration]") { TEST_CASE("FloorSupportsDoNotPierceModel", "[SLASupportGeneration]") { - sla::SupportConfig supportcfg; + sla::SupportTreeConfig supportcfg; supportcfg.object_elevation_mm = 0; for (auto fname : SUPPORT_TEST_MODELS) diff --git a/tests/sla_print/sla_raycast_tests.cpp b/tests/sla_print/sla_raycast_tests.cpp index c82e4569a..b56909280 100644 --- a/tests/sla_print/sla_raycast_tests.cpp +++ b/tests/sla_print/sla_raycast_tests.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include #include #include "sla_test_utils.hpp" @@ -65,7 +65,7 @@ TEST_CASE("Raycaster with loaded drillholes", "[sla_raycast]") cube.merge(*cube_inside); cube.require_shared_vertices(); - sla::EigenMesh3D emesh{cube}; + sla::IndexedMesh emesh{cube}; emesh.load_holes(holes); Vec3d s = center.cast(); diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index bc0cfb0cd..c46cf675c 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -2,13 +2,13 @@ #include "libslic3r/SLA/AGGRaster.hpp" void test_support_model_collision(const std::string &obj_filename, - const sla::SupportConfig &input_supportcfg, + const sla::SupportTreeConfig &input_supportcfg, const sla::HollowingConfig &hollowingcfg, const sla::DrainHoles &drainholes) { SupportByproducts byproducts; - sla::SupportConfig supportcfg = input_supportcfg; + sla::SupportTreeConfig supportcfg = input_supportcfg; // Set head penetration to a small negative value which should ensure that // the supports will not touch the model body. @@ -73,7 +73,7 @@ void export_failed_case(const std::vector &support_slices, const Sup } void test_supports(const std::string &obj_filename, - const sla::SupportConfig &supportcfg, + const sla::SupportTreeConfig &supportcfg, const sla::HollowingConfig &hollowingcfg, const sla::DrainHoles &drainholes, SupportByproducts &out) @@ -104,7 +104,7 @@ void test_supports(const std::string &obj_filename, // Create the special index-triangle mesh with spatial indexing which // is the input of the support point and support mesh generators - sla::EigenMesh3D emesh{mesh}; + sla::IndexedMesh emesh{mesh}; #ifdef SLIC3R_HOLE_RAYCASTER if (hollowingcfg.enabled) @@ -168,7 +168,7 @@ void test_supports(const std::string &obj_filename, } void check_support_tree_integrity(const sla::SupportTreeBuilder &stree, - const sla::SupportConfig &cfg) + const sla::SupportTreeConfig &cfg) { double gnd = stree.ground_level; double H1 = cfg.max_solo_pillar_height_mm; diff --git a/tests/sla_print/sla_test_utils.hpp b/tests/sla_print/sla_test_utils.hpp index 3652b1f81..fdd883ed8 100644 --- a/tests/sla_print/sla_test_utils.hpp +++ b/tests/sla_print/sla_test_utils.hpp @@ -67,16 +67,16 @@ struct SupportByproducts const constexpr float CLOSING_RADIUS = 0.005f; void check_support_tree_integrity(const sla::SupportTreeBuilder &stree, - const sla::SupportConfig &cfg); + const sla::SupportTreeConfig &cfg); void test_supports(const std::string &obj_filename, - const sla::SupportConfig &supportcfg, + const sla::SupportTreeConfig &supportcfg, const sla::HollowingConfig &hollowingcfg, const sla::DrainHoles &drainholes, SupportByproducts &out); inline void test_supports(const std::string &obj_filename, - const sla::SupportConfig &supportcfg, + const sla::SupportTreeConfig &supportcfg, SupportByproducts &out) { sla::HollowingConfig hcfg; @@ -85,7 +85,7 @@ inline void test_supports(const std::string &obj_filename, } inline void test_supports(const std::string &obj_filename, - const sla::SupportConfig &supportcfg = {}) + const sla::SupportTreeConfig &supportcfg = {}) { SupportByproducts byproducts; test_supports(obj_filename, supportcfg, byproducts); @@ -97,13 +97,13 @@ void export_failed_case(const std::vector &support_slices, void test_support_model_collision( const std::string &obj_filename, - const sla::SupportConfig &input_supportcfg, + const sla::SupportTreeConfig &input_supportcfg, const sla::HollowingConfig &hollowingcfg, const sla::DrainHoles &drainholes); inline void test_support_model_collision( const std::string &obj_filename, - const sla::SupportConfig &input_supportcfg = {}) + const sla::SupportTreeConfig &input_supportcfg = {}) { sla::HollowingConfig hcfg; hcfg.enabled = false; diff --git a/tests/sla_print/sla_treebuilder_tests.cpp b/tests/sla_print/sla_treebuilder_tests.cpp index 05aca963e..91c2ea6f8 100644 --- a/tests/sla_print/sla_treebuilder_tests.cpp +++ b/tests/sla_print/sla_treebuilder_tests.cpp @@ -1,99 +1,99 @@ -#include -#include +//#include +//#include -#include "libslic3r/TriangleMesh.hpp" -#include "libslic3r/SLA/SupportTreeBuildsteps.hpp" -#include "libslic3r/SLA/SupportTreeMesher.hpp" +//#include "libslic3r/TriangleMesh.hpp" +//#include "libslic3r/SLA/SupportTreeBuildsteps.hpp" +//#include "libslic3r/SLA/SupportTreeMesher.hpp" -TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]") { - using namespace Slic3r; +//TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]") { +// using namespace Slic3r; - TriangleMesh cube = make_cube(10., 10., 10.); +// TriangleMesh cube = make_cube(10., 10., 10.); - sla::SupportConfig cfg = {}; // use default config - sla::SupportPoints pts = {{10.f, 5.f, 5.f, float(cfg.head_front_radius_mm), false}}; - sla::SupportableMesh sm{cube, pts, cfg}; +// sla::SupportConfig cfg = {}; // use default config +// sla::SupportPoints pts = {{10.f, 5.f, 5.f, float(cfg.head_front_radius_mm), false}}; +// sla::SupportableMesh sm{cube, pts, cfg}; - size_t steps = 45; - SECTION("Bridge is straight horizontal and pointing away from the cube") { +// size_t steps = 45; +// SECTION("Bridge is straight horizontal and pointing away from the cube") { - sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 5.}, - pts[0].head_front_radius); +// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 5.}, +// pts[0].head_front_radius); - auto hit = sla::query_hit(sm, bridge); +// auto hit = sla::query_hit(sm, bridge); - REQUIRE(std::isinf(hit.distance())); +// REQUIRE(std::isinf(hit.distance())); - cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); - cube.require_shared_vertices(); - cube.WriteOBJFile("cube1.obj"); - } +// cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); +// cube.require_shared_vertices(); +// cube.WriteOBJFile("cube1.obj"); +// } - SECTION("Bridge is tilted down in 45 degrees, pointing away from the cube") { - sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 0.}, - pts[0].head_front_radius); +// SECTION("Bridge is tilted down in 45 degrees, pointing away from the cube") { +// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 0.}, +// pts[0].head_front_radius); - auto hit = sla::query_hit(sm, bridge); +// auto hit = sla::query_hit(sm, bridge); - REQUIRE(std::isinf(hit.distance())); +// REQUIRE(std::isinf(hit.distance())); - cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); - cube.require_shared_vertices(); - cube.WriteOBJFile("cube2.obj"); - } -} +// cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); +// cube.require_shared_vertices(); +// cube.WriteOBJFile("cube2.obj"); +// } +//} -TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") { - using namespace Slic3r; +//TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") { +// using namespace Slic3r; - TriangleMesh sphere = make_sphere(1.); +// TriangleMesh sphere = make_sphere(1.); - sla::SupportConfig cfg = {}; // use default config - cfg.head_back_radius_mm = cfg.head_front_radius_mm; - sla::SupportPoints pts = {{1.f, 0.f, 0.f, float(cfg.head_front_radius_mm), false}}; - sla::SupportableMesh sm{sphere, pts, cfg}; +// sla::SupportConfig cfg = {}; // use default config +// cfg.head_back_radius_mm = cfg.head_front_radius_mm; +// sla::SupportPoints pts = {{1.f, 0.f, 0.f, float(cfg.head_front_radius_mm), false}}; +// sla::SupportableMesh sm{sphere, pts, cfg}; - size_t steps = 45; - SECTION("Bridge is straight horizontal and pointing away from the sphere") { +// size_t steps = 45; +// SECTION("Bridge is straight horizontal and pointing away from the sphere") { - sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., 0.}, - pts[0].head_front_radius); +// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., 0.}, +// pts[0].head_front_radius); - auto hit = sla::query_hit(sm, bridge); +// auto hit = sla::query_hit(sm, bridge); - sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); - sphere.require_shared_vertices(); - sphere.WriteOBJFile("sphere1.obj"); +// sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); +// sphere.require_shared_vertices(); +// sphere.WriteOBJFile("sphere1.obj"); - REQUIRE(std::isinf(hit.distance())); - } +// REQUIRE(std::isinf(hit.distance())); +// } - SECTION("Bridge is tilted down 45 deg and pointing away from the sphere") { +// SECTION("Bridge is tilted down 45 deg and pointing away from the sphere") { - sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., -2.}, - pts[0].head_front_radius); +// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., -2.}, +// pts[0].head_front_radius); - auto hit = sla::query_hit(sm, bridge); +// auto hit = sla::query_hit(sm, bridge); - sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); - sphere.require_shared_vertices(); - sphere.WriteOBJFile("sphere2.obj"); +// sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); +// sphere.require_shared_vertices(); +// sphere.WriteOBJFile("sphere2.obj"); - REQUIRE(std::isinf(hit.distance())); - } +// REQUIRE(std::isinf(hit.distance())); +// } - SECTION("Bridge is tilted down 90 deg and pointing away from the sphere") { +// SECTION("Bridge is tilted down 90 deg and pointing away from the sphere") { - sla::Bridge bridge(pts[0].pos.cast(), Vec3d{1., 0., -2.}, - pts[0].head_front_radius); +// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{1., 0., -2.}, +// pts[0].head_front_radius); - auto hit = sla::query_hit(sm, bridge); +// auto hit = sla::query_hit(sm, bridge); - sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); - sphere.require_shared_vertices(); - sphere.WriteOBJFile("sphere3.obj"); +// sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); +// sphere.require_shared_vertices(); +// sphere.WriteOBJFile("sphere3.obj"); - REQUIRE(std::isinf(hit.distance())); - } -} +// REQUIRE(std::isinf(hit.distance())); +// } +//} From a68564e2d01994b13c1d675ec3d08ec0434d1b84 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 26 Jun 2020 15:12:37 +0200 Subject: [PATCH 62/70] Include test name with output obj files for sla_print_tests --- tests/sla_print/sla_test_utils.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index c46cf675c..8978281d8 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -69,7 +69,8 @@ void export_failed_case(const std::vector &support_slices, const Sup m.merge(byproducts.input_mesh); m.repair(); m.require_shared_vertices(); - m.WriteOBJFile(byproducts.obj_fname.c_str()); + m.WriteOBJFile((Catch::getResultCapture().getCurrentTestName() + "_" + + byproducts.obj_fname).c_str()); } void test_supports(const std::string &obj_filename, From 7c655b5d7e1732448b8a37fd335b8e9535372a38 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 29 Jun 2020 20:09:37 +0200 Subject: [PATCH 63/70] Fix junction made below ground level. --- src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index b29ad0b9c..2116568e8 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -1333,9 +1333,8 @@ void SupportTreeBuildsteps::interconnect_pillars() if (distance(pillarsp, s) < t) m_builder.add_bridge(pillarsp, s, pillar().r); - if (pillar().endpoint()(Z) > m_builder.ground_level) - m_builder.add_junction(pillar().endpoint(), - pillar().r); + if (pillar().endpoint()(Z) > m_builder.ground_level + pillar().r) + m_builder.add_junction(pillar().endpoint(), pillar().r); newpills.emplace_back(pp.id); m_builder.increment_links(pillar()); From 8cb115a03543f0d09c9e113ab8e42cf9eb39a694 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 8 Jul 2020 11:31:01 +0200 Subject: [PATCH 64/70] Add possible manipulation of small support diameter. --- src/libslic3r/PrintConfig.cpp | 21 +++- src/libslic3r/PrintConfig.hpp | 5 + src/libslic3r/SLA/SupportTree.hpp | 2 + src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 131 ++------------------ src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 14 ++- src/libslic3r/SLAPrint.cpp | 6 +- src/slic3r/GUI/ConfigManipulation.cpp | 1 + src/slic3r/GUI/Preset.cpp | 1 + src/slic3r/GUI/Tab.cpp | 1 + 9 files changed, 57 insertions(+), 125 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index a25292298..5c1ce4b7f 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2715,7 +2715,7 @@ void PrintConfigDef::init_sla_params() def->set_default_value(new ConfigOptionBool(true)); def = this->add("support_head_front_diameter", coFloat); - def->label = L("Support head front diameter"); + def->label = L("Pinhead front diameter"); def->category = L("Supports"); def->tooltip = L("Diameter of the pointing side of the head"); def->sidetext = L("mm"); @@ -2724,7 +2724,7 @@ void PrintConfigDef::init_sla_params() def->set_default_value(new ConfigOptionFloat(0.4)); def = this->add("support_head_penetration", coFloat); - def->label = L("Support head penetration"); + def->label = L("Head penetration"); def->category = L("Supports"); def->tooltip = L("How much the pinhead has to penetrate the model surface"); def->sidetext = L("mm"); @@ -2733,7 +2733,7 @@ void PrintConfigDef::init_sla_params() def->set_default_value(new ConfigOptionFloat(0.2)); def = this->add("support_head_width", coFloat); - def->label = L("Support head width"); + def->label = L("Pinhead width"); def->category = L("Supports"); def->tooltip = L("Width from the back sphere center to the front sphere center"); def->sidetext = L("mm"); @@ -2743,7 +2743,7 @@ void PrintConfigDef::init_sla_params() def->set_default_value(new ConfigOptionFloat(1.0)); def = this->add("support_pillar_diameter", coFloat); - def->label = L("Support pillar diameter"); + def->label = L("Pillar diameter"); def->category = L("Supports"); def->tooltip = L("Diameter in mm of the support pillars"); def->sidetext = L("mm"); @@ -2751,6 +2751,17 @@ void PrintConfigDef::init_sla_params() def->max = 15; def->mode = comSimple; def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add("support_small_pillar_diameter_percent", coPercent); + def->label = L("Small pillar diameter percent"); + def->category = L("Supports"); + def->tooltip = L("The percentage of smaller pillars compared to the normal pillar diameter " + "which are used in problematic areas where a normal pilla cannot fit."); + def->sidetext = L("%"); + def->min = 1; + def->max = 100; + def->mode = comExpert; + def->set_default_value(new ConfigOptionPercent(50)); def = this->add("support_max_bridges_on_pillar", coInt); def->label = L("Max bridges on a pillar"); @@ -2763,7 +2774,7 @@ void PrintConfigDef::init_sla_params() def->set_default_value(new ConfigOptionInt(3)); def = this->add("support_pillar_connection_mode", coEnum); - def->label = L("Support pillar connection mode"); + def->label = L("Pillar connection mode"); def->tooltip = L("Controls the bridge type between two neighboring pillars." " Can be zig-zag, cross (double zig-zag) or dynamic which" " will automatically switch between the first two depending" diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index f28ef2a22..0213a6d6b 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1018,6 +1018,10 @@ public: // Radius in mm of the support pillars. ConfigOptionFloat support_pillar_diameter /*= 0.8*/; + + // The percentage of smaller pillars compared to the normal pillar diameter + // which are used in problematic areas where a normal pilla cannot fit. + ConfigOptionPercent support_small_pillar_diameter_percent; // How much bridge (supporting another pinhead) can be placed on a pillar. ConfigOptionInt support_max_bridges_on_pillar; @@ -1142,6 +1146,7 @@ protected: OPT_PTR(support_head_penetration); OPT_PTR(support_head_width); OPT_PTR(support_pillar_diameter); + OPT_PTR(support_small_pillar_diameter_percent); OPT_PTR(support_max_bridges_on_pillar); OPT_PTR(support_pillar_connection_mode); OPT_PTR(support_buildplate_only); diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 7d54b76a4..4be90161d 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -44,6 +44,8 @@ struct SupportTreeConfig // Radius of the back side of the 3d arrow. double head_back_radius_mm = 0.5; + double head_fallback_radius_mm = 0.25; + // Width in mm from the back sphere center to the front sphere center. double head_width_mm = 1.0; diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 2116568e8..7f6c034dd 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -25,119 +25,6 @@ static Hit min_hit(const C &hits) return *mit; } -//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Head &h) -//{ -// static const size_t SAMPLES = 8; - -// // Move away slightly from the touching point to avoid raycasting on the -// // inner surface of the mesh. - -// const double& sd = msh.cfg.safety_distance_mm; - -// auto& m = msh.emesh; -// using HitResult = IndexedMesh::hit_result; - -// // Hit results -// std::array hits; - -// Vec3d s1 = h.pos, s2 = h.junction_point(); - -// struct Rings { -// double rpin; -// double rback; -// Vec3d spin; -// Vec3d sback; -// PointRing ring; - -// Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); } -// Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); } -// } rings {h.r_pin_mm + sd, h.r_back_mm + sd, s1, s2, h.dir}; - -// // We will shoot multiple rays from the head pinpoint in the direction -// // of the pinhead robe (side) surface. The result will be the smallest -// // hit distance. - -// auto hitfn = [&m, &rings, sd](HitResult &hit, size_t i) { -// // Point on the circle on the pin sphere -// Vec3d ps = rings.pinring(i); -// // This is the point on the circle on the back sphere -// Vec3d p = rings.backring(i); - -// // Point ps is not on mesh but can be inside or -// // outside as well. This would cause many problems -// // with ray-casting. To detect the position we will -// // use the ray-casting result (which has an is_inside -// // predicate). - -// Vec3d n = (p - ps).normalized(); -// auto q = m.query_ray_hit(ps + sd * n, n); - -// if (q.is_inside()) { // the hit is inside the model -// if (q.distance() > rings.rpin) { -// // If we are inside the model and the hit -// // distance is bigger than our pin circle -// // diameter, it probably indicates that the -// // support point was already inside the -// // model, or there is really no space -// // around the point. We will assign a zero -// // hit distance to these cases which will -// // enforce the function return value to be -// // an invalid ray with zero hit distance. -// // (see min_element at the end) -// hit = HitResult(0.0); -// } else { -// // re-cast the ray from the outside of the -// // object. The starting point has an offset -// // of 2*safety_distance because the -// // original ray has also had an offset -// auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n); -// hit = q2; -// } -// } else -// hit = q; -// }; - -// ccr::enumerate(hits.begin(), hits.end(), hitfn); - -// return min_hit(hits); -//} - -//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d) -//{ - -// static const size_t SAMPLES = 8; - -// Vec3d dir = (br.endp - br.startp).normalized(); -// PointRing ring{dir}; - -// using Hit = IndexedMesh::hit_result; - -// // Hit results -// std::array hits; - -// double sd = std::isnan(safety_d) ? msh.cfg.safety_distance_mm : safety_d; - -// auto hitfn = [&msh, &br, &ring, dir, sd] (Hit &hit, size_t i) { - -// // Point on the circle on the pin sphere -// Vec3d p = ring.get(i, br.startp, br.r + sd); - -// auto hr = msh.emesh.query_ray_hit(p + br.r * dir, dir); - -// if(hr.is_inside()) { -// if(hr.distance() > 2 * br.r + sd) hit = Hit(0.0); -// else { -// // re-cast the ray from the outside of the object -// hit = msh.emesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, dir); -// } -// } else hit = hr; -// }; - -// ccr::enumerate(hits.begin(), hits.end(), hitfn); - -// return min_hit(hits); -//} - SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm) : m_cfg(sm.cfg) @@ -282,15 +169,18 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, } IndexedMesh::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( - const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width) + const Vec3d &s, + const Vec3d &dir, + double r_pin, + double r_back, + double width, + double sd) { static const size_t SAMPLES = 8; // Move away slightly from the touching point to avoid raycasting on the // inner surface of the mesh. - const double& sd = m_cfg.safety_distance_mm; - auto& m = m_mesh; using HitResult = IndexedMesh::hit_result; @@ -836,12 +726,17 @@ void SupportTreeBuildsteps::add_pinheads() // First we need to determine the available space for a mini pinhead. // The goal is the move away from the model a little bit to make the // contact point small as possible and avoid pearcing the model body. - double pin_space = std::min(2 * R, bridge_mesh_distance(sph, n, R, 0.)); + double back_r = m_cfg.head_fallback_radius_mm; + double max_w = 2 * R; + double pin_space = std::min(max_w, + pinhead_mesh_intersect(sph, n, R, back_r, + max_w, 0.) + .distance()); if (pin_space <= 0) continue; m_iheads.emplace_back(i); - m_builder.add_head(i, R, R, pin_space, + m_builder.add_head(i, back_r, R, pin_space, m_cfg.head_penetration_mm, n, sph); } } diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index a98586789..d19194a87 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -238,7 +238,19 @@ class SupportTreeBuildsteps { const Vec3d& dir, double r_pin, double r_back, - double width); + double width, + double safety_d); + + IndexedMesh::hit_result pinhead_mesh_intersect( + const Vec3d& s, + const Vec3d& dir, + double r_pin, + double r_back, + double width) + { + return pinhead_mesh_intersect(s, dir, r_pin, r_back, width, + m_cfg.safety_distance_mm); + } // Checking bridge (pillar and stick as well) intersection with the model. // If the function is used for headless sticks, the ins_check parameter diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index eee3bbc9f..4395bea46 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -41,7 +41,10 @@ sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c) scfg.enabled = c.supports_enable.getBool(); scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat(); - scfg.head_back_radius_mm = 0.5*c.support_pillar_diameter.getFloat(); + double pillar_r = 0.5 * c.support_pillar_diameter.getFloat(); + scfg.head_back_radius_mm = pillar_r; + scfg.head_fallback_radius_mm = + 0.01 * c.support_small_pillar_diameter_percent.getFloat() * pillar_r; scfg.head_penetration_mm = c.support_head_penetration.getFloat(); scfg.head_width_mm = c.support_head_width.getFloat(); scfg.object_elevation_mm = is_zero_elevation(c) ? @@ -925,6 +928,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector& Preset::sla_print_options() "support_head_penetration", "support_head_width", "support_pillar_diameter", + "support_small_pillar_diameter_percent", "support_max_bridges_on_pillar", "support_pillar_connection_mode", "support_buildplate_only", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 84bc5a572..86b483a8d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3919,6 +3919,7 @@ void TabSLAPrint::build() optgroup = page->new_optgroup(L("Support pillar")); optgroup->append_single_option_line("support_pillar_diameter"); + optgroup->append_single_option_line("support_small_pillar_diameter_percent"); optgroup->append_single_option_line("support_max_bridges_on_pillar"); optgroup->append_single_option_line("support_pillar_connection_mode"); From 927b81ea9710266e2effb050ca27cb8762239870 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 16 Jul 2020 11:49:30 +0200 Subject: [PATCH 65/70] Working small-to-normal support merging Fixed fatal bug with anchors for mini supports Make the optimization cleaner in support generatior Much better widening behaviour Add an optimizer interface and the NLopt implementation into libslic3r New optimizer based only on nlopt C interfase Fix build and tests --- src/libslic3r/CMakeLists.txt | 1 + src/libslic3r/Optimizer.hpp | 369 ++++++++++ src/libslic3r/SLA/IndexedMesh.hpp | 2 +- src/libslic3r/SLA/SupportTreeBuilder.cpp | 5 + src/libslic3r/SLA/SupportTreeBuilder.hpp | 45 +- src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 758 ++++++++++---------- src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 17 +- src/libslic3r/SLA/SupportTreeMesher.hpp | 18 + tests/sla_print/sla_print_tests.cpp | 11 + 9 files changed, 831 insertions(+), 395 deletions(-) create mode 100644 src/libslic3r/Optimizer.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 91da5df5d..58b74402e 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -203,6 +203,7 @@ add_library(libslic3r STATIC SimplifyMeshImpl.hpp SimplifyMesh.cpp MarchingSquares.hpp + Optimizer.hpp ${OpenVDBUtils_SOURCES} SLA/Pad.hpp SLA/Pad.cpp diff --git a/src/libslic3r/Optimizer.hpp b/src/libslic3r/Optimizer.hpp new file mode 100644 index 000000000..dc70abe33 --- /dev/null +++ b/src/libslic3r/Optimizer.hpp @@ -0,0 +1,369 @@ +#ifndef NLOPTOPTIMIZER_HPP +#define NLOPTOPTIMIZER_HPP + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#pragma warning(disable: 4267) +#endif +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include +#include +#include +#include +#include +#include +#include + +namespace Slic3r { namespace opt { + +// A type to hold the complete result of the optimization. +template struct Result { + int resultcode; + std::array optimum; + double score; +}; + +// An interval of possible input values for optimization +class Bound { + double m_min, m_max; + +public: + Bound(double min = std::numeric_limits::min(), + double max = std::numeric_limits::max()) + : m_min(min), m_max(max) + {} + + double min() const noexcept { return m_min; } + double max() const noexcept { return m_max; } +}; + +// Helper types for optimization function input and bounds +template using Input = std::array; +template using Bounds = std::array; + +// A type for specifying the stop criteria. Setter methods can be concatenated +class StopCriteria { + + // If the absolute value difference between two scores. + double m_abs_score_diff = std::nan(""); + + // If the relative value difference between two scores. + double m_rel_score_diff = std::nan(""); + + // Stop if this value or better is found. + double m_stop_score = std::nan(""); + + // A predicate that if evaluates to true, the optimization should terminate + // and the best result found prior to termination should be returned. + std::function m_stop_condition = [] { return false; }; + + // The max allowed number of iterations. + unsigned m_max_iterations = 0; + +public: + + StopCriteria & abs_score_diff(double val) + { + m_abs_score_diff = val; return *this; + } + + double abs_score_diff() const { return m_abs_score_diff; } + + StopCriteria & rel_score_diff(double val) + { + m_rel_score_diff = val; return *this; + } + + double rel_score_diff() const { return m_rel_score_diff; } + + StopCriteria & stop_score(double val) + { + m_stop_score = val; return *this; + } + + double stop_score() const { return m_stop_score; } + + StopCriteria & max_iterations(double val) + { + m_max_iterations = val; return *this; + } + + double max_iterations() const { return m_max_iterations; } + + template StopCriteria & stop_condition(Fn &&cond) + { + m_stop_condition = cond; return *this; + } + + bool stop_condition() { return m_stop_condition(); } +}; + +// Helper to be used in static_assert. +template struct always_false { enum { value = false }; }; + +// Basic interface to optimizer object +template class Optimizer { +public: + + Optimizer(const StopCriteria &) + { + static_assert(always_false::value, + "Optimizer unimplemented for given method!"); + } + + Optimizer &to_min() { return *this; } + Optimizer &to_max() { return *this; } + Optimizer &set_criteria(const StopCriteria &) { return *this; } + StopCriteria get_criteria() const { return {}; }; + + template + Result optimize(Func&& func, + const Input &initvals, + const Bounds& bounds) { return {}; } + + // optional for randomized methods: + void seed(long /*s*/) {} +}; + +namespace detail { + +// Helper types for NLopt algorithm selection in template contexts +template struct NLoptAlg {}; + +// NLopt can combine multiple algorithms if one is global an other is a local +// method. This is how template specializations can be informed about this fact. +template +struct NLoptAlgComb {}; + +template struct IsNLoptAlg { + static const constexpr bool value = false; +}; + +template struct IsNLoptAlg> { + static const constexpr bool value = true; +}; + +template +struct IsNLoptAlg> { + static const constexpr bool value = true; +}; + +template +using NLoptOnly = std::enable_if_t::value, T>; + +// Convert any collection to tuple. This is useful for object functions taking +// an argument list of doubles. Make things cleaner on the call site of +// optimize(). +template struct to_tuple_ { + static auto call(const C &c) + { + return std::tuple_cat(std::tuple(c[N-I]), + to_tuple_::call(c)); + } +}; + +template struct to_tuple_<0, N, T, C> { + static auto call(const C &c) { return std::tuple<>(); } +}; + +// C array to tuple +template auto carray_tuple(const T *v) +{ + return to_tuple_::call(v); +} + +// Helper to convert C style array to std::array +template auto to_arr(const T (&a) [N]) +{ + std::array r; + std::copy(std::begin(a), std::end(a), std::begin(r)); + return r; +} + +enum class OptDir { MIN, MAX }; // Where to optimize + +struct NLopt { // Helper RAII class for nlopt_opt + nlopt_opt ptr = nullptr; + + template explicit NLopt(A&&...a) + { + ptr = nlopt_create(std::forward(a)...); + } + + NLopt(const NLopt&) = delete; + NLopt(NLopt&&) = delete; + NLopt& operator=(const NLopt&) = delete; + NLopt& operator=(NLopt&&) = delete; + + ~NLopt() { nlopt_destroy(ptr); } +}; + +template class NLoptOpt {}; + +// Optimizers based on NLopt. +template class NLoptOpt> { +protected: + StopCriteria m_stopcr; + OptDir m_dir; + + template using TOptData = + std::tuple*, NLoptOpt*, nlopt_opt>; + + template + static double optfunc(unsigned n, const double *params, + double *gradient, + void *data) + { + assert(n >= N); + + auto tdata = static_cast*>(data); + + if (std::get<1>(*tdata)->m_stopcr.stop_condition()) + nlopt_force_stop(std::get<2>(*tdata)); + + auto fnptr = std::get<0>(*tdata); + auto funval = carray_tuple(params); + + return std::apply(*fnptr, funval); + } + + template + void set_up(NLopt &nl, const Bounds& bounds) + { + std::array lb, ub; + + for (size_t i = 0; i < N; ++i) { + lb[i] = bounds[i].min(); + ub[i] = bounds[i].max(); + } + + nlopt_set_lower_bounds(nl.ptr, lb.data()); + nlopt_set_upper_bounds(nl.ptr, ub.data()); + + double abs_diff = m_stopcr.abs_score_diff(); + double rel_diff = m_stopcr.rel_score_diff(); + double stopval = m_stopcr.stop_score(); + if(!std::isnan(abs_diff)) nlopt_set_ftol_abs(nl.ptr, abs_diff); + if(!std::isnan(rel_diff)) nlopt_set_ftol_rel(nl.ptr, rel_diff); + if(!std::isnan(stopval)) nlopt_set_stopval(nl.ptr, stopval); + + if(this->m_stopcr.max_iterations() > 0) + nlopt_set_maxeval(nl.ptr, this->m_stopcr.max_iterations()); + } + + template + Result optimize(NLopt &nl, Fn &&fn, const Input &initvals) + { + Result r; + + TOptData data = std::make_tuple(&fn, this, nl.ptr); + + switch(m_dir) { + case OptDir::MIN: + nlopt_set_min_objective(nl.ptr, optfunc, &data); break; + case OptDir::MAX: + nlopt_set_max_objective(nl.ptr, optfunc, &data); break; + } + + r.optimum = initvals; + r.resultcode = nlopt_optimize(nl.ptr, r.optimum.data(), &r.score); + + return r; + } + +public: + + template + Result optimize(Func&& func, + const Input &initvals, + const Bounds& bounds) + { + NLopt nl{alg, N}; + set_up(nl, bounds); + + return optimize(nl, std::forward(func), initvals); + } + + explicit NLoptOpt(StopCriteria stopcr = {}) : m_stopcr(stopcr) {} + + void set_criteria(const StopCriteria &cr) { m_stopcr = cr; } + const StopCriteria &get_criteria() const noexcept { return m_stopcr; } + void set_dir(OptDir dir) noexcept { m_dir = dir; } + + void seed(long s) { nlopt_srand(s); } +}; + +template +class NLoptOpt>: public NLoptOpt> +{ + using Base = NLoptOpt>; +public: + + template + Result optimize(Fn&& f, + const Input &initvals, + const Bounds& bounds) + { + NLopt nl_glob{glob, N}, nl_loc{loc, N}; + + Base::set_up(nl_glob, bounds); + Base::set_up(nl_loc, bounds); + nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); + + return Base::optimize(nl_glob, std::forward(f), initvals); + } + + explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {} +}; + +} // namespace detail; + +// Optimizers based on NLopt. +template class Optimizer> { + detail::NLoptOpt m_opt; + +public: + + Optimizer& to_max() { m_opt.set_dir(detail::OptDir::MAX); return *this; } + Optimizer& to_min() { m_opt.set_dir(detail::OptDir::MIN); return *this; } + + template + Result optimize(Func&& func, + const Input &initvals, + const Bounds& bounds) + { + return m_opt.optimize(std::forward(func), initvals, bounds); + } + + explicit Optimizer(StopCriteria stopcr = {}) : m_opt(stopcr) {} + + Optimizer &set_criteria(const StopCriteria &cr) + { + m_opt.set_criteria(cr); return *this; + } + + const StopCriteria &get_criteria() const { return m_opt.get_criteria(); } + + void seed(long s) { m_opt.seed(s); } +}; + +template Bounds bounds(const Bound (&b) [N]) { return detail::to_arr(b); } +template Input initvals(const double (&a) [N]) { return detail::to_arr(a); } + +// Predefinded NLopt algorithms that are used in the codebase +using AlgNLoptGenetic = detail::NLoptAlgComb; +using AlgNLoptSubplex = detail::NLoptAlg; +using AlgNLoptSimplex = detail::NLoptAlg; + +// Helper defs for pre-crafted global and local optimizers that work well. +using DefaultGlobalOptimizer = Optimizer; +using DefaultLocalOptimizer = Optimizer; + +}} // namespace Slic3r::opt + +#endif // NLOPTOPTIMIZER_HPP diff --git a/src/libslic3r/SLA/IndexedMesh.hpp b/src/libslic3r/SLA/IndexedMesh.hpp index b0970608e..a72492b34 100644 --- a/src/libslic3r/SLA/IndexedMesh.hpp +++ b/src/libslic3r/SLA/IndexedMesh.hpp @@ -87,7 +87,7 @@ public: inline Vec3d position() const { return m_source + m_dir * m_t; } inline int face() const { return m_face_id; } inline bool is_valid() const { return m_mesh != nullptr; } - inline bool is_hit() const { return !std::isinf(m_t); } + inline bool is_hit() const { return m_face_id >= 0 && !std::isinf(m_t); } inline const Vec3d& normal() const { assert(is_valid()); diff --git a/src/libslic3r/SLA/SupportTreeBuilder.cpp b/src/libslic3r/SLA/SupportTreeBuilder.cpp index 959093623..daa01ef24 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.cpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.cpp @@ -156,6 +156,11 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh(size_t steps) const merged.merge(get_mesh(bs, steps)); } + for (auto &bs : m_diffbridges) { + if (ctl().stopcondition()) break; + merged.merge(get_mesh(bs, steps)); + } + for (auto &anch : m_anchors) { if (ctl().stopcondition()) break; merged.merge(get_mesh(anch, steps)); diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index aa8a4ea83..f29263ca3 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -177,6 +177,14 @@ struct Bridge: public SupportTreeNode { Vec3d get_dir() const { return (endp - startp).normalized(); } }; +struct DiffBridge: public Bridge { + double end_r; + + DiffBridge(const Vec3d &p_s, const Vec3d &p_e, double r_s, double r_e) + : Bridge{p_s, p_e, r_s}, end_r{r_e} + {} +}; + // A wrapper struct around the pad struct Pad { TriangleMesh tmesh; @@ -210,14 +218,15 @@ struct Pad { // merged mesh. It can be retrieved using a dedicated method (pad()) class SupportTreeBuilder: public SupportTree { // For heads it is beneficial to use the same IDs as for the support points. - std::vector m_heads; - std::vector m_head_indices; - std::vector m_pillars; - std::vector m_junctions; - std::vector m_bridges; - std::vector m_crossbridges; - std::vector m_pedestals; - std::vector m_anchors; + std::vector m_heads; + std::vector m_head_indices; + std::vector m_pillars; + std::vector m_junctions; + std::vector m_bridges; + std::vector m_crossbridges; + std::vector m_diffbridges; + std::vector m_pedestals; + std::vector m_anchors; Pad m_pad; @@ -228,8 +237,8 @@ class SupportTreeBuilder: public SupportTree { mutable bool m_meshcache_valid = false; mutable double m_model_height = 0; // the full height of the model - template - const Bridge& _add_bridge(std::vector &br, Args&&... args) + template + const BridgeT& _add_bridge(std::vector &br, Args&&... args) { std::lock_guard lk(m_mutex); br.emplace_back(std::forward(args)...); @@ -331,17 +340,6 @@ public: return pillar.id; } - const Pillar& head_pillar(unsigned headid) const - { - std::lock_guard lk(m_mutex); - assert(headid < m_head_indices.size()); - - const Head& h = m_heads[m_head_indices[headid]]; - assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size())); - - return m_pillars[size_t(h.pillar_id)]; - } - template const Junction& add_junction(Args&&... args) { std::lock_guard lk(m_mutex); @@ -374,6 +372,11 @@ public: { return _add_bridge(m_crossbridges, std::forward(args)...); } + + template const DiffBridge& add_diffbridge(Args&&... args) + { + return _add_bridge(m_diffbridges, std::forward(args)...); + } Head &head(unsigned id) { diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 7f6c034dd..7ed410802 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -1,18 +1,25 @@ #include #include -#include -#include +#include #include namespace Slic3r { namespace sla { -using libnest2d::opt::initvals; -using libnest2d::opt::bound; -using libnest2d::opt::StopCriteria; -using libnest2d::opt::GeneticOptimizer; -using libnest2d::opt::SubplexOptimizer; +using Slic3r::opt::initvals; +using Slic3r::opt::bounds; +using Slic3r::opt::StopCriteria; +using Slic3r::opt::Optimizer; +using Slic3r::opt::AlgNLoptSubplex; +using Slic3r::opt::AlgNLoptGenetic; + +StopCriteria get_criteria(const SupportTreeConfig &cfg) +{ + return StopCriteria{} + .rel_score_diff(cfg.optimizer_rel_score_diff) + .max_iterations(cfg.optimizer_max_iterations); +} template static Hit min_hit(const C &hits) @@ -37,7 +44,7 @@ SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder, { // Prepare the support points in Eigen/IGL format as well, we will use // it mostly in this form. - + long i = 0; for (const SupportPoint &sp : m_support_pts) { m_points.row(i)(X) = double(sp.pos(X)); @@ -51,7 +58,7 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, const SupportableMesh &sm) { if(sm.pts.empty()) return false; - + builder.ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm; SupportTreeBuildsteps alg(builder, sm); @@ -72,46 +79,46 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, NUM_STEPS //... }; - + // Collect the algorithm steps into a nice sequence std::array, NUM_STEPS> program = { [] () { // Begin... // Potentially clear up the shared data (not needed for now) }, - + std::bind(&SupportTreeBuildsteps::filter, &alg), - + std::bind(&SupportTreeBuildsteps::add_pinheads, &alg), - + std::bind(&SupportTreeBuildsteps::classify, &alg), - + std::bind(&SupportTreeBuildsteps::routing_to_ground, &alg), - + std::bind(&SupportTreeBuildsteps::routing_to_model, &alg), - + std::bind(&SupportTreeBuildsteps::interconnect_pillars, &alg), - + std::bind(&SupportTreeBuildsteps::merge_result, &alg), - + [] () { // Done }, - + [] () { // Abort } }; - + Steps pc = BEGIN; - + if(sm.cfg.ground_facing_only) { program[ROUTING_NONGROUND] = []() { BOOST_LOG_TRIVIAL(info) << "Skipping model-facing supports as requested."; }; } - + // Let's define a simple automaton that will run our program. auto progress = [&builder, &pc] () { static const std::array stepstr { @@ -126,7 +133,7 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, "Done", "Abort" }; - + static const std::array stepstate { 0, 10, @@ -139,9 +146,9 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, 100, 0 }; - + if(builder.ctl().stopcondition()) pc = ABORT; - + switch(pc) { case BEGIN: pc = FILTER; break; case FILTER: pc = PINHEADS; break; @@ -155,16 +162,16 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, case ABORT: break; default: ; } - + builder.ctl().statuscb(stepstate[pc], stepstr[pc]); }; - + // Just here we run the computation... while(pc < DONE) { progress(); program[pc](); } - + return pc == ABORT; } @@ -177,48 +184,48 @@ IndexedMesh::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( double sd) { static const size_t SAMPLES = 8; - + // Move away slightly from the touching point to avoid raycasting on the // inner surface of the mesh. - + auto& m = m_mesh; using HitResult = IndexedMesh::hit_result; - + // Hit results std::array hits; - + struct Rings { double rpin; double rback; Vec3d spin; Vec3d sback; PointRing ring; - + Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); } Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); } } rings {r_pin + sd, r_back + sd, s, s + width * dir, dir}; - + // We will shoot multiple rays from the head pinpoint in the direction // of the pinhead robe (side) surface. The result will be the smallest // hit distance. - - ccr::enumerate(hits.begin(), hits.end(), + + ccr::enumerate(hits.begin(), hits.end(), [&m, &rings, sd](HitResult &hit, size_t i) { - + // Point on the circle on the pin sphere Vec3d ps = rings.pinring(i); // This is the point on the circle on the back sphere Vec3d p = rings.backring(i); - + // Point ps is not on mesh but can be inside or // outside as well. This would cause many problems // with ray-casting. To detect the position we will // use the ray-casting result (which has an is_inside - // predicate). - + // predicate). + Vec3d n = (p - ps).normalized(); auto q = m.query_ray_hit(ps + sd * n, n); - + if (q.is_inside()) { // the hit is inside the model if (q.distance() > rings.rpin) { // If we are inside the model and the hit @@ -243,7 +250,7 @@ IndexedMesh::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( } else hit = q; }); - + return min_hit(hits); } @@ -252,20 +259,20 @@ IndexedMesh::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( { static const size_t SAMPLES = 8; PointRing ring{dir}; - + using Hit = IndexedMesh::hit_result; - + // Hit results std::array hits; - - ccr::enumerate(hits.begin(), hits.end(), + + ccr::enumerate(hits.begin(), hits.end(), [this, r, src, /*ins_check,*/ &ring, dir, sd] (Hit &hit, size_t i) { // Point on the circle on the pin sphere Vec3d p = ring.get(i, src, r + sd); - + auto hr = m_mesh.query_ray_hit(p + r * dir, dir); - + if(/*ins_check && */hr.is_inside()) { if(hr.distance() > 2 * r + sd) hit = Hit(0.0); else { @@ -274,7 +281,7 @@ IndexedMesh::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( } } else hit = hr; }); - + return min_hit(hits); } @@ -288,61 +295,61 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar, // shorter pillar is too short to start a new bridge but the taller // pillar could still be bridged with the shorter one. bool was_connected = false; - + Vec3d supper = pillar.startpoint(); Vec3d slower = nextpillar.startpoint(); Vec3d eupper = pillar.endpoint(); Vec3d elower = nextpillar.endpoint(); - + double zmin = m_builder.ground_level + m_cfg.base_height_mm; eupper(Z) = std::max(eupper(Z), zmin); elower(Z) = std::max(elower(Z), zmin); - + // The usable length of both pillars should be positive if(slower(Z) - elower(Z) < 0) return false; if(supper(Z) - eupper(Z) < 0) return false; - + double pillar_dist = distance(Vec2d{slower(X), slower(Y)}, Vec2d{supper(X), supper(Y)}); double bridge_distance = pillar_dist / std::cos(-m_cfg.bridge_slope); double zstep = pillar_dist * std::tan(-m_cfg.bridge_slope); - + if(pillar_dist < 2 * m_cfg.head_back_radius_mm || pillar_dist > m_cfg.max_pillar_link_distance_mm) return false; - + if(supper(Z) < slower(Z)) supper.swap(slower); if(eupper(Z) < elower(Z)) eupper.swap(elower); - + double startz = 0, endz = 0; - + startz = slower(Z) - zstep < supper(Z) ? slower(Z) - zstep : slower(Z); endz = eupper(Z) + zstep > elower(Z) ? eupper(Z) + zstep : eupper(Z); - + if(slower(Z) - eupper(Z) < std::abs(zstep)) { // no space for even one cross - + // Get max available space startz = std::min(supper(Z), slower(Z) - zstep); endz = std::max(eupper(Z) + zstep, elower(Z)); - + // Align to center double available_dist = (startz - endz); double rounds = std::floor(available_dist / std::abs(zstep)); startz -= 0.5 * (available_dist - rounds * std::abs(zstep)); } - + auto pcm = m_cfg.pillar_connection_mode; bool docrosses = pcm == PillarConnectionMode::cross || (pcm == PillarConnectionMode::dynamic && pillar_dist > 2*m_cfg.base_radius_mm); - + // 'sj' means starting junction, 'ej' is the end junction of a bridge. // They will be swapped in every iteration thus the zig-zag pattern. // According to a config parameter, a second bridge may be added which // results in a cross connection between the pillars. Vec3d sj = supper, ej = slower; sj(Z) = startz; ej(Z) = sj(Z) + zstep; - + // TODO: This is a workaround to not have a faulty last bridge while(ej(Z) >= eupper(Z) /*endz*/) { if(bridge_mesh_distance(sj, dirv(sj, ej), pillar.r) >= bridge_distance) @@ -350,7 +357,7 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar, m_builder.add_crossbridge(sj, ej, pillar.r); was_connected = true; } - + // double bridging: (crosses) if(docrosses) { Vec3d sjback(ej(X), ej(Y), sj(Z)); @@ -363,11 +370,11 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar, was_connected = true; } } - + sj.swap(ej); ej(Z) = sj(Z) + zstep; } - + return was_connected; } @@ -377,67 +384,67 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, auto nearpillar = [this, nearpillar_id]() -> const Pillar& { return m_builder.pillar(nearpillar_id); }; - - if (m_builder.bridgecount(nearpillar()) > m_cfg.max_bridges_on_pillar) + + if (m_builder.bridgecount(nearpillar()) > m_cfg.max_bridges_on_pillar) return false; - + Vec3d headjp = head.junction_point(); Vec3d nearjp_u = nearpillar().startpoint(); Vec3d nearjp_l = nearpillar().endpoint(); - + double r = head.r_back_mm; double d2d = distance(to_2d(headjp), to_2d(nearjp_u)); double d3d = distance(headjp, nearjp_u); - + double hdiff = nearjp_u(Z) - headjp(Z); double slope = std::atan2(hdiff, d2d); - + Vec3d bridgestart = headjp; Vec3d bridgeend = nearjp_u; double max_len = r * m_cfg.max_bridge_length_mm / m_cfg.head_back_radius_mm; double max_slope = m_cfg.bridge_slope; double zdiff = 0.0; - + // check the default situation if feasible for a bridge if(d3d > max_len || slope > -max_slope) { // not feasible to connect the two head junctions. We have to search // for a suitable touch point. - + double Zdown = headjp(Z) + d2d * std::tan(-max_slope); Vec3d touchjp = bridgeend; touchjp(Z) = Zdown; double D = distance(headjp, touchjp); zdiff = Zdown - nearjp_u(Z); - + if(zdiff > 0) { Zdown -= zdiff; bridgestart(Z) -= zdiff; touchjp(Z) = Zdown; - + double t = bridge_mesh_distance(headjp, DOWN, r); - + // We can't insert a pillar under the source head to connect // with the nearby pillar's starting junction if(t < zdiff) return false; } - + if(Zdown <= nearjp_u(Z) && Zdown >= nearjp_l(Z) && D < max_len) bridgeend(Z) = Zdown; else return false; } - + // There will be a minimum distance from the ground where the // bridge is allowed to connect. This is an empiric value. double minz = m_builder.ground_level + 4 * head.r_back_mm; if(bridgeend(Z) < minz) return false; - + double t = bridge_mesh_distance(bridgestart, dirv(bridgestart, bridgeend), r); - + // Cannot insert the bridge. (further search might not worth the hassle) if(t < distance(bridgestart, bridgeend)) return false; - + std::lock_guard lk(m_bridge_mutex); - + if (m_builder.bridgecount(nearpillar()) < m_cfg.max_bridges_on_pillar) { // A partial pillar is needed under the starting head. if(zdiff > 0) { @@ -447,31 +454,59 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, } else { m_builder.add_bridge(head.id, bridgeend); } - + m_builder.increment_bridges(nearpillar()); } else return false; - + return true; } -bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, +bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &hjp, const Vec3d &sourcedir, double radius, long head_id) { - double sd = m_cfg.pillar_base_safety_distance_mm; + Vec3d jp = hjp, endp = jp, dir = sourcedir; long pillar_id = SupportTreeNode::ID_UNSET; - bool can_add_base = radius >= m_cfg.head_back_radius_mm; - double base_r = can_add_base ? m_cfg.base_radius_mm : 0.; - double gndlvl = m_builder.ground_level; - if (!can_add_base) gndlvl -= m_mesh.ground_level_offset(); - Vec3d endp = {jp(X), jp(Y), gndlvl}; - double min_dist = sd + base_r + EPSILON; - bool normal_mode = true; - Vec3d dir = sourcedir; + bool can_add_base = false, non_head = false; + + double gndlvl = 0.; // The Z level where pedestals should be + double jp_gnd = 0.; // The lowest Z where a junction center can be + double gap_dist = 0.; // The gap distance between the model and the pad auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; + auto eval_limits = [this, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd] + (bool base_en = true) + { + can_add_base = base_en && radius >= m_cfg.head_back_radius_mm; + double base_r = can_add_base ? m_cfg.base_radius_mm : 0.; + gndlvl = m_builder.ground_level; + if (!can_add_base) gndlvl -= m_mesh.ground_level_offset(); + jp_gnd = gndlvl + (can_add_base ? 0. : m_cfg.head_back_radius_mm); + gap_dist = m_cfg.pillar_base_safety_distance_mm + base_r + EPSILON; + }; + + eval_limits(); + + // We are dealing with a mini pillar that's potentially too long + if (radius < m_cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius) + { + std::optional diffbr = + search_widening_path(jp, dir, radius, m_cfg.head_back_radius_mm); + + if (diffbr && diffbr->endp.z() > jp_gnd) { + auto &br = m_builder.add_diffbridge(diffbr.value()); + if (head_id >= 0) m_builder.head(head_id).bridge_id = br.id; + endp = diffbr->endp; + radius = diffbr->end_r; + m_builder.add_junction(endp, radius); + non_head = true; + dir = diffbr->get_dir(); + eval_limits(); + } else return false; + } + if (m_cfg.object_elevation_mm < EPSILON) { // get a suitable direction for the corrector bridge. It is the @@ -479,101 +514,118 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, // configured bridge slope. auto [polar, azimuth] = dir_to_spheric(dir); polar = PI - m_cfg.bridge_slope; - Vec3d dir = spheric_to_dir(polar, azimuth).normalized(); + Vec3d d = spheric_to_dir(polar, azimuth).normalized(); + double t = bridge_mesh_distance(endp, dir, radius); + double tmax = std::min(m_cfg.max_bridge_length_mm, t); + t = 0.; - // Check the distance of the endpoint and the closest point on model - // body. It should be greater than the min_dist which is - // the safety distance from the model. It includes the pad gap if in - // zero elevation mode. - // - // Try to move along the established bridge direction to dodge the - // forbidden region for the endpoint. - double t = -radius; - bool succ = true; - while (std::sqrt(m_mesh.squared_distance(to_floor(endp))) < min_dist || - !std::isinf(bridge_mesh_distance(endp, DOWN, radius))) { + double zd = endp.z() - jp_gnd; + double tmax2 = zd / std::sqrt(1 - m_cfg.bridge_slope * m_cfg.bridge_slope); + tmax = std::min(tmax, tmax2); + + Vec3d nexp = endp; + double dlast = 0.; + while (((dlast = std::sqrt(m_mesh.squared_distance(to_floor(nexp)))) < gap_dist || + !std::isinf(bridge_mesh_distance(nexp, DOWN, radius))) && t < tmax) { t += radius; - endp = jp + t * dir; - normal_mode = false; + nexp = endp + t * d; + } - if (t > m_cfg.max_bridge_length_mm || endp(Z) < gndlvl) { - if (head_id >= 0) m_builder.add_pillar(head_id, 0.); - succ = false; - break; + if (dlast < gap_dist && can_add_base) { + nexp = endp; + t = 0.; + can_add_base = false; + eval_limits(can_add_base); + + zd = endp.z() - jp_gnd; + tmax2 = zd / std::sqrt(1 - m_cfg.bridge_slope * m_cfg.bridge_slope); + tmax = std::min(tmax, tmax2); + + while (((dlast = std::sqrt(m_mesh.squared_distance(to_floor(nexp)))) < gap_dist || + !std::isinf(bridge_mesh_distance(nexp, DOWN, radius))) && t < tmax) { + t += radius; + nexp = endp + t * d; } } - if (!succ) { - if (can_add_base) { - can_add_base = false; - base_r = 0.; - gndlvl -= m_mesh.ground_level_offset(); - min_dist = sd + base_r + EPSILON; - endp = {jp(X), jp(Y), gndlvl + radius}; + // Could not find a path to avoid the pad gap + if (dlast < gap_dist) return false; - t = -radius; - while (std::sqrt(m_mesh.squared_distance(to_floor(endp))) < min_dist || - !std::isinf(bridge_mesh_distance(endp, DOWN, radius))) { - t += radius; - endp = jp + t * dir; - normal_mode = false; + if (t > 0.) { // Need to make additional bridge + const Bridge& br = m_builder.add_bridge(endp, nexp, radius); + if (head_id >= 0) m_builder.head(head_id).bridge_id = br.id; - if (t > m_cfg.max_bridge_length_mm || endp(Z) < (gndlvl + radius)) { - if (head_id >= 0) m_builder.add_pillar(head_id, 0.); - return false; - } - } - } else return false; + m_builder.add_junction(nexp, radius); + endp = nexp; + non_head = true; } } - double h = (jp - endp).norm(); + Vec3d gp = to_floor(endp); + double h = endp.z() - gp.z(); - // Check if the deduced route is sane and exit with error if not. - if (bridge_mesh_distance(jp, dir, radius) < h) { - if (head_id >= 0) m_builder.add_pillar(head_id, 0.); - return false; - } + pillar_id = head_id >= 0 && !non_head ? m_builder.add_pillar(head_id, h) : + m_builder.add_pillar(gp, h, radius); - // Straigh path down, no area to dodge - if (normal_mode) { - pillar_id = head_id >= 0 ? m_builder.add_pillar(head_id, h) : - m_builder.add_pillar(endp, h, radius); - - if (can_add_base) - add_pillar_base(pillar_id); - } else { - - // Insert the bridge to get around the forbidden area - Vec3d pgnd{endp.x(), endp.y(), gndlvl}; - pillar_id = m_builder.add_pillar(pgnd, endp.z() - gndlvl, radius); - - if (can_add_base) - add_pillar_base(pillar_id); - - m_builder.add_bridge(jp, endp, radius); - m_builder.add_junction(endp, radius); - - // Add a degenerated pillar and the bridge. - // The degenerate pillar will have zero length and it will - // prevent from queries of head_pillar() to have non-existing - // pillar when the head should have one. - if (head_id >= 0) - m_builder.add_pillar(head_id, 0.); - } + if (can_add_base) + add_pillar_base(pillar_id); if(pillar_id >= 0) // Save the pillar endpoint in the spatial index - m_pillar_index.guarded_insert(endp, unsigned(pillar_id)); + m_pillar_index.guarded_insert(m_builder.pillar(pillar_id).endpt, + unsigned(pillar_id)); return true; } +std::optional SupportTreeBuildsteps::search_widening_path( + const Vec3d &jp, const Vec3d &dir, double radius, double new_radius) +{ + double w = radius + 2 * m_cfg.head_back_radius_mm; + double stopval = w + jp.z() - m_builder.ground_level; + Optimizer solver(get_criteria(m_cfg).stop_score(stopval)); + + auto [polar, azimuth] = dir_to_spheric(dir); + + double fallback_ratio = radius / m_cfg.head_back_radius_mm; + + auto oresult = solver.to_max().optimize( + [this, jp, radius, new_radius](double plr, double azm, double t) { + auto d = spheric_to_dir(plr, azm).normalized(); + double ret = pinhead_mesh_intersect(jp, d, radius, new_radius, t) + .distance(); + double down = bridge_mesh_distance(jp + t * d, d, new_radius); + + if (ret > t && std::isinf(down)) + ret += jp.z() - m_builder.ground_level; + + return ret; + }, + initvals({polar, azimuth, w}), // start with what we have + bounds({ + {PI - m_cfg.bridge_slope, PI}, // Must not exceed the slope limit + {-PI, PI}, // azimuth can be a full search + {radius + m_cfg.head_back_radius_mm, + fallback_ratio * m_cfg.max_bridge_length_mm} + })); + + if (oresult.score >= stopval) { + polar = std::get<0>(oresult.optimum); + azimuth = std::get<1>(oresult.optimum); + double t = std::get<2>(oresult.optimum); + Vec3d endp = jp + t * spheric_to_dir(polar, azimuth); + + return DiffBridge(jp, endp, radius, m_cfg.head_back_radius_mm); + } + + return {}; +} + void SupportTreeBuildsteps::filter() { // Get the points that are too close to each other and keep only the // first one auto aliases = cluster(m_points, D_SP, 2); - + PtIndices filtered_indices; filtered_indices.reserve(aliases.size()); m_iheads.reserve(aliases.size()); @@ -582,49 +634,62 @@ void SupportTreeBuildsteps::filter() // Here we keep only the front point of the cluster. filtered_indices.emplace_back(a.front()); } - + // calculate the normals to the triangles for filtered points auto nmls = sla::normals(m_points, m_mesh, m_cfg.head_front_radius_mm, m_thr, filtered_indices); - + // Not all of the support points have to be a valid position for // support creation. The angle may be inappropriate or there may // not be enough space for the pinhead. Filtering is applied for // these reasons. - - ccr::SpinningMutex mutex; - auto addfn = [&mutex](PtIndices &container, unsigned val) { - std::lock_guard lk(mutex); - container.emplace_back(val); - }; - - auto filterfn = [this, &nmls, addfn](unsigned fidx, size_t i) { + + std::vector heads; heads.reserve(m_support_pts.size()); + for (const SupportPoint &sp : m_support_pts) { m_thr(); - + heads.emplace_back( + std::nan(""), + sp.head_front_radius, + 0., + m_cfg.head_penetration_mm, + Vec3d::Zero(), // dir + sp.pos.cast() // displacement + ); + } + + std::function filterfn; + filterfn = [this, &nmls, &heads, &filterfn](unsigned fidx, size_t i, double back_r) { + m_thr(); + auto n = nmls.row(Eigen::Index(i)); - + // for all normals we generate the spherical coordinates and // saturate the polar angle to 45 degrees from the bottom then // convert back to standard coordinates to get the new normal. // Then we just create a quaternion from the two normals // (Quaternion::FromTwoVectors) and apply the rotation to the // arrow head. - + auto [polar, azimuth] = dir_to_spheric(n); - + // skip if the tilt is not sane - if(polar < PI - m_cfg.normal_cutoff_angle) return; - + if (polar < PI - m_cfg.normal_cutoff_angle) return; + // We saturate the polar angle to 3pi/4 - polar = std::max(polar, 3*PI / 4); + polar = std::max(polar, PI - m_cfg.bridge_slope); // save the head (pinpoint) position Vec3d hp = m_points.row(fidx); + double lmin = m_cfg.head_width_mm, lmax = lmin; + + if (back_r < m_cfg.head_back_radius_mm) { + lmin = 0., lmax = m_cfg.head_penetration_mm; + } + // The distance needed for a pinhead to not collide with model. - double w = m_cfg.head_width_mm + - m_cfg.head_back_radius_mm + - 2*m_cfg.head_front_radius_mm; + double w = lmin + 2 * back_r + 2 * m_cfg.head_front_radius_mm - + m_cfg.head_penetration_mm; double pin_r = double(m_support_pts[fidx].head_front_radius); @@ -632,113 +697,69 @@ void SupportTreeBuildsteps::filter() auto nn = spheric_to_dir(polar, azimuth).normalized(); // check available distance - IndexedMesh::hit_result t - = pinhead_mesh_intersect(hp, // touching point - nn, // normal - pin_r, - m_cfg.head_back_radius_mm, - w); - - if(t.distance() <= w) { + IndexedMesh::hit_result t = pinhead_mesh_intersect(hp, nn, pin_r, + back_r, w); + if (t.distance() < w) { // Let's try to optimize this angle, there might be a // viable normal that doesn't collide with the model // geometry and its very close to the default. - StopCriteria stc; - stc.max_iterations = m_cfg.optimizer_max_iterations; - stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; - stc.stop_score = w; // space greater than w is enough - GeneticOptimizer solver(stc); - solver.seed(0); // we want deterministic behavior + // stc.stop_score = w; // space greater than w is enough + Optimizer solver(get_criteria(m_cfg)); + solver.seed(0); + //solver.seed(0); // we want deterministic behavior - auto oresult = solver.optimize_max( - [this, pin_r, w, hp](double plr, double azm) + auto oresult = solver.to_max().optimize( + [this, pin_r, back_r, hp](double plr, double azm, double l) { auto dir = spheric_to_dir(plr, azm).normalized(); double score = pinhead_mesh_intersect( - hp, dir, pin_r, m_cfg.head_back_radius_mm, w).distance(); + hp, dir, pin_r, back_r, l).distance(); return score; }, - initvals(polar, azimuth), // start with what we have - bound(3 * PI / 4, PI), // Must not exceed the tilt limit - bound(-PI, PI) // azimuth can be a full search - ); + initvals({polar, azimuth, (lmin + lmax) / 2.}), // start with what we have + bounds({ + {PI - m_cfg.bridge_slope, PI}, // Must not exceed the tilt limit + {-PI, PI}, // azimuth can be a full search + {lmin, lmax} + })); if(oresult.score > w) { polar = std::get<0>(oresult.optimum); azimuth = std::get<1>(oresult.optimum); nn = spheric_to_dir(polar, azimuth).normalized(); + lmin = std::get<2>(oresult.optimum); t = IndexedMesh::hit_result(oresult.score); } } - // save the verified and corrected normal - m_support_nmls.row(fidx) = nn; + if (t.distance() > w && hp(Z) + w * nn(Z) >= m_builder.ground_level) { + Head &h = heads[fidx]; + h.id = fidx; h.dir = nn; h.width_mm = lmin; h.r_back_mm = back_r; + } else if (back_r > m_cfg.head_fallback_radius_mm) { + filterfn(fidx, i, m_cfg.head_fallback_radius_mm); + } + }; - if (t.distance() > w) { - // Check distance from ground, we might have zero elevation. - if (hp(Z) + w * nn(Z) < m_builder.ground_level) { - addfn(m_iheadless, fidx); - } else { - // mark the point for needing a head. - addfn(m_iheads, fidx); - } - } else if (polar >= 3 * PI / 4) { - // Headless supports do not tilt like the headed ones - // so the normal should point almost to the ground. - addfn(m_iheadless, fidx); + ccr::enumerate(filtered_indices.begin(), filtered_indices.end(), + [this, &filterfn](unsigned fidx, size_t i) { + filterfn(fidx, i, m_cfg.head_back_radius_mm); + }); + + for (size_t i = 0; i < heads.size(); ++i) + if (heads[i].is_valid()) { + m_builder.add_head(i, heads[i]); + m_iheads.emplace_back(i); } - }; - - ccr::enumerate(filtered_indices.begin(), filtered_indices.end(), filterfn); - m_thr(); } void SupportTreeBuildsteps::add_pinheads() { - for (unsigned i : m_iheads) { - m_thr(); - m_builder.add_head( - i, - m_cfg.head_back_radius_mm, - m_support_pts[i].head_front_radius, - m_cfg.head_width_mm, - m_cfg.head_penetration_mm, - m_support_nmls.row(i), // dir - m_support_pts[i].pos.cast() // displacement - ); - } - - for (unsigned i : m_iheadless) { - const auto R = double(m_support_pts[i].head_front_radius); - - // The support point position on the mesh - Vec3d sph = m_support_pts[i].pos.cast(); - - // Get an initial normal from the filtering step - Vec3d n = m_support_nmls.row(i); - - // First we need to determine the available space for a mini pinhead. - // The goal is the move away from the model a little bit to make the - // contact point small as possible and avoid pearcing the model body. - double back_r = m_cfg.head_fallback_radius_mm; - double max_w = 2 * R; - double pin_space = std::min(max_w, - pinhead_mesh_intersect(sph, n, R, back_r, - max_w, 0.) - .distance()); - - if (pin_space <= 0) continue; - - m_iheads.emplace_back(i); - m_builder.add_head(i, back_r, R, pin_space, - m_cfg.head_penetration_mm, n, sph); - } } void SupportTreeBuildsteps::classify() @@ -747,37 +768,37 @@ void SupportTreeBuildsteps::classify() PtIndices ground_head_indices; ground_head_indices.reserve(m_iheads.size()); m_iheads_onmodel.reserve(m_iheads.size()); - + // First we decide which heads reach the ground and can be full // pillars and which shall be connected to the model surface (or // search a suitable path around the surface that leads to the // ground -- TODO) for(unsigned i : m_iheads) { m_thr(); - - auto& head = m_builder.head(i); + + Head &head = m_builder.head(i); double r = head.r_back_mm; Vec3d headjp = head.junction_point(); - + // collision check auto hit = bridge_mesh_intersect(headjp, DOWN, r); - + if(std::isinf(hit.distance())) ground_head_indices.emplace_back(i); else if(m_cfg.ground_facing_only) head.invalidate(); else m_iheads_onmodel.emplace_back(i); - + m_head_to_ground_scans[i] = hit; } - + // We want to search for clusters of points that are far enough // from each other in the XY plane to not cross their pillar bases // These clusters of support points will join in one pillar, // possibly in their centroid support point. - + auto pointfn = [this](unsigned i) { return m_builder.head(i).junction_point(); }; - + auto predicate = [this](const PointIndexEl &e1, const PointIndexEl &e2) { double d2d = distance(to_2d(e1.first), to_2d(e2.first)); @@ -794,10 +815,10 @@ void SupportTreeBuildsteps::routing_to_ground() { ClusterEl cl_centroids; cl_centroids.reserve(m_pillar_clusters.size()); - + for (auto &cl : m_pillar_clusters) { m_thr(); - + // place all the centroid head positions into the index. We // will query for alternative pillar positions. If a sidehead // cannot connect to the cluster centroid, we have to search @@ -805,9 +826,9 @@ void SupportTreeBuildsteps::routing_to_ground() // elements in the cluster, the centroid is arbitrary and the // sidehead is allowed to connect to a nearby pillar to // increase structural stability. - + if (cl.empty()) continue; - + // get the current cluster centroid auto & thr = m_thr; const auto &points = m_points; @@ -821,11 +842,11 @@ void SupportTreeBuildsteps::routing_to_ground() assert(lcid >= 0); unsigned hid = cl[size_t(lcid)]; // Head ID - + cl_centroids.emplace_back(hid); - + Head &h = m_builder.head(hid); - + if (!create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id)) { BOOST_LOG_TRIVIAL(warning) << "Pillar cannot be created for support point id: " << hid; @@ -833,34 +854,32 @@ void SupportTreeBuildsteps::routing_to_ground() continue; } } - + // now we will go through the clusters ones again and connect the // sidepoints with the cluster centroid (which is a ground pillar) // or a nearby pillar if the centroid is unreachable. size_t ci = 0; for (auto cl : m_pillar_clusters) { m_thr(); - + auto cidx = cl_centroids[ci++]; - - // TODO: don't consider the cluster centroid but calculate a - // central position where the pillar can be placed. this way - // the weight is distributed more effectively on the pillar. - - auto centerpillarID = m_builder.head_pillar(cidx).id; - - for (auto c : cl) { - m_thr(); - if (c == cidx) continue; - - auto &sidehead = m_builder.head(c); - - if (!connect_to_nearpillar(sidehead, centerpillarID) && - !search_pillar_and_connect(sidehead)) { - Vec3d pstart = sidehead.junction_point(); - // Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; - // Could not find a pillar, create one - create_ground_pillar(pstart, sidehead.dir, sidehead.r_back_mm, sidehead.id); + + auto q = m_pillar_index.query(m_builder.head(cidx).junction_point(), 1); + if (!q.empty()) { + long centerpillarID = q.front().second; + for (auto c : cl) { + m_thr(); + if (c == cidx) continue; + + auto &sidehead = m_builder.head(c); + + if (!connect_to_nearpillar(sidehead, centerpillarID) && + !search_pillar_and_connect(sidehead)) { + Vec3d pstart = sidehead.junction_point(); + // Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; + // Could not find a pillar, create one + create_ground_pillar(pstart, sidehead.dir, sidehead.r_back_mm, sidehead.id); + } } } } @@ -876,9 +895,9 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir) while (d < t && !std::isinf(tdown = bridge_mesh_distance(hjp + d * dir, DOWN, r))) d += r; - + if(!std::isinf(tdown)) return false; - + Vec3d endp = hjp + d * dir; bool ret = false; @@ -886,38 +905,33 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir) m_builder.add_bridge(head.id, endp); m_builder.add_junction(endp, head.r_back_mm); } - + return ret; } bool SupportTreeBuildsteps::connect_to_ground(Head &head) { if (connect_to_ground(head, head.dir)) return true; - + // Optimize bridge direction: // Straight path failed so we will try to search for a suitable // direction out of the cavity. auto [polar, azimuth] = dir_to_spheric(head.dir); - - StopCriteria stc; - stc.max_iterations = m_cfg.optimizer_max_iterations; - stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; - stc.stop_score = 1e6; - GeneticOptimizer solver(stc); + + Optimizer solver(get_criteria(m_cfg).stop_score(1e6)); solver.seed(0); // we want deterministic behavior - + double r_back = head.r_back_mm; - Vec3d hjp = head.junction_point(); - auto oresult = solver.optimize_max( + Vec3d hjp = head.junction_point(); + auto oresult = solver.to_max().optimize( [this, hjp, r_back](double plr, double azm) { Vec3d n = spheric_to_dir(plr, azm).normalized(); return bridge_mesh_distance(hjp, n, r_back); }, - initvals(polar, azimuth), // let's start with what we have - bound(3*PI/4, PI), // Must not exceed the slope limit - bound(-PI, PI) // azimuth can be a full range search - ); - + initvals({polar, azimuth}), // let's start with what we have + bounds({ {PI - m_cfg.bridge_slope, PI}, {-PI, PI} }) + ); + Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized(); return connect_to_ground(head, bridgedir); } @@ -925,10 +939,10 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head) bool SupportTreeBuildsteps::connect_to_model_body(Head &head) { if (head.id <= SupportTreeNode::ID_UNSET) return false; - + auto it = m_head_to_ground_scans.find(unsigned(head.id)); if (it == m_head_to_ground_scans.end()) return false; - + auto &hit = it->second; if (!hit.is_hit()) { @@ -943,9 +957,11 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) // The width of the tail head that we would like to have... h = std::min(hit.distance() - head.r_back_mm, h); - - if(h <= 0.) return false; - + + // If this is a mini pillar dont bother with the tail width, can be 0. + if (head.r_back_mm < m_cfg.head_back_radius_mm) h = std::max(h, 0.); + else if (h <= 0.) return false; + Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h}; auto center_hit = m_mesh.query_ray_hit(hjp, DOWN); @@ -969,7 +985,7 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) m_cfg.head_penetration_mm, taildir, hitp); m_pillar_index.guarded_insert(pill.endpoint(), pill.id); - + return true; } @@ -1011,7 +1027,7 @@ bool SupportTreeBuildsteps::search_pillar_and_connect(const Head &source) } void SupportTreeBuildsteps::routing_to_model() -{ +{ // We need to check if there is an easy way out to the bed surface. // If it can be routed there with a bridge shorter than // min_bridge_distance. @@ -1019,23 +1035,23 @@ void SupportTreeBuildsteps::routing_to_model() ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(), [this] (const unsigned idx, size_t) { m_thr(); - + auto& head = m_builder.head(idx); - + // Search nearby pillar if (search_pillar_and_connect(head)) { return; } - + // Cannot connect to nearby pillar. We will try to search for // a route to the ground. if (connect_to_ground(head)) { return; } - + // No route to the ground, so connect to the model body as a last resort if (connect_to_model_body(head)) { return; } - + // We have failed to route this head. BOOST_LOG_TRIVIAL(warning) << "Failed to route model facing support point. ID: " << idx; - + head.invalidate(); }); } @@ -1045,19 +1061,19 @@ void SupportTreeBuildsteps::interconnect_pillars() // Now comes the algorithm that connects pillars with each other. // Ideally every pillar should be connected with at least one of its // neighbors if that neighbor is within max_pillar_link_distance - + // Pillars with height exceeding H1 will require at least one neighbor // to connect with. Height exceeding H2 require two neighbors. double H1 = m_cfg.max_solo_pillar_height_mm; double H2 = m_cfg.max_dual_pillar_height_mm; double d = m_cfg.max_pillar_link_distance_mm; - + //A connection between two pillars only counts if the height ratio is // bigger than 50% double min_height_ratio = 0.5; - + std::set pairs; - + // A function to connect one pillar with its neighbors. THe number of // neighbors is given in the configuration. This function if called // for every pillar in the pillar index. A pair of pillar will not @@ -1067,68 +1083,68 @@ void SupportTreeBuildsteps::interconnect_pillars() [this, d, &pairs, min_height_ratio, H1] (const PointIndexEl& el) { Vec3d qp = el.first; // endpoint of the pillar - + const Pillar& pillar = m_builder.pillar(el.second); // actual pillar - + // Get the max number of neighbors a pillar should connect to unsigned neighbors = m_cfg.pillar_cascade_neighbors; - + // connections are already enough for the pillar if(pillar.links >= neighbors) return; - + double max_d = d * pillar.r / m_cfg.head_back_radius_mm; // Query all remaining points within reach auto qres = m_pillar_index.query([qp, max_d](const PointIndexEl& e){ return distance(e.first, qp) < max_d; }); - + // sort the result by distance (have to check if this is needed) std::sort(qres.begin(), qres.end(), [qp](const PointIndexEl& e1, const PointIndexEl& e2){ return distance(e1.first, qp) < distance(e2.first, qp); }); - + for(auto& re : qres) { // process the queried neighbors - + if(re.second == el.second) continue; // Skip self - + auto a = el.second, b = re.second; - + // Get unique hash for the given pair (order doesn't matter) auto hashval = pairhash(a, b); - + // Search for the pair amongst the remembered pairs if(pairs.find(hashval) != pairs.end()) continue; - + const Pillar& neighborpillar = m_builder.pillar(re.second); - + // this neighbor is occupied, skip if (neighborpillar.links >= neighbors) continue; if (neighborpillar.r < pillar.r) continue; - + if(interconnect(pillar, neighborpillar)) { pairs.insert(hashval); - + // If the interconnection length between the two pillars is // less than 50% of the longer pillar's height, don't count if(pillar.height < H1 || neighborpillar.height / pillar.height > min_height_ratio) m_builder.increment_links(pillar); - + if(neighborpillar.height < H1 || pillar.height / neighborpillar.height > min_height_ratio) m_builder.increment_links(neighborpillar); - + } - + // connections are enough for one pillar if(pillar.links >= neighbors) break; } }; - + // Run the cascade for the pillars in the index m_pillar_index.foreach(cascadefn); - + // We would be done here if we could allow some pillars to not be // connected with any neighbors. But this might leave the support tree // unprintable. @@ -1136,16 +1152,16 @@ void SupportTreeBuildsteps::interconnect_pillars() // The current solution is to insert additional pillars next to these // lonely pillars. One or even two additional pillar might get inserted // depending on the length of the lonely pillar. - + size_t pillarcount = m_builder.pillarcount(); - + // Again, go through all pillars, this time in the whole support tree // not just the index. for(size_t pid = 0; pid < pillarcount; pid++) { auto pillar = [this, pid]() { return m_builder.pillar(pid); }; - + // Decide how many additional pillars will be needed: - + unsigned needpillars = 0; if (pillar().bridges > m_cfg.max_bridges_on_pillar) needpillars = 3; @@ -1156,28 +1172,28 @@ void SupportTreeBuildsteps::interconnect_pillars() // No neighbors could be found and the pillar is too long. needpillars = 1; } - + needpillars = std::max(pillar().links, needpillars) - pillar().links; if (needpillars == 0) continue; - + // Search for new pillar locations: - + bool found = false; double alpha = 0; // goes to 2Pi double r = 2 * m_cfg.base_radius_mm; Vec3d pillarsp = pillar().startpoint(); - + // temp value for starting point detection Vec3d sp(pillarsp(X), pillarsp(Y), pillarsp(Z) - r); - + // A vector of bool for placement feasbility std::vector canplace(needpillars, false); std::vector spts(needpillars); // vector of starting points - + double gnd = m_builder.ground_level; double min_dist = m_cfg.pillar_base_safety_distance_mm + m_cfg.base_radius_mm + EPSILON; - + while(!found && alpha < 2*PI) { for (unsigned n = 0; n < needpillars && (!n || canplace[n - 1]); @@ -1188,25 +1204,25 @@ void SupportTreeBuildsteps::interconnect_pillars() s(X) += std::cos(a) * r; s(Y) += std::sin(a) * r; spts[n] = s; - + // Check the path vertically down Vec3d check_from = s + Vec3d{0., 0., pillar().r}; auto hr = bridge_mesh_intersect(check_from, DOWN, pillar().r); Vec3d gndsp{s(X), s(Y), gnd}; - + // If the path is clear, check for pillar base collisions canplace[n] = std::isinf(hr.distance()) && std::sqrt(m_mesh.squared_distance(gndsp)) > min_dist; } - + found = std::all_of(canplace.begin(), canplace.end(), [](bool v) { return v; }); - + // 20 angles will be tried... alpha += 0.1 * PI; } - + std::vector newpills; newpills.reserve(needpillars); @@ -1247,7 +1263,7 @@ void SupportTreeBuildsteps::interconnect_pillars() m_builder.increment_links(nxpll); } } - + m_pillar_index.foreach(cascadefn); } } diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index d19194a87..013666f07 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -45,6 +45,11 @@ inline Vec3d spheric_to_dir(const std::pair &v) return spheric_to_dir(v.first, v.second); } +inline Vec3d spheric_to_dir(const std::array &v) +{ + return spheric_to_dir(v[0], v[1]); +} + // Give points on a 3D ring with given center, radius and orientation // method based on: // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space @@ -249,7 +254,8 @@ class SupportTreeBuildsteps { double width) { return pinhead_mesh_intersect(s, dir, r_pin, r_back, width, - m_cfg.safety_distance_mm); + r_back * m_cfg.safety_distance_mm / + m_cfg.head_back_radius_mm); } // Checking bridge (pillar and stick as well) intersection with the model. @@ -271,7 +277,9 @@ class SupportTreeBuildsteps { const Vec3d& dir, double r) { - return bridge_mesh_intersect(s, dir, r, m_cfg.safety_distance_mm); + return bridge_mesh_intersect(s, dir, r, + r * m_cfg.safety_distance_mm / + m_cfg.head_back_radius_mm); } template @@ -311,6 +319,11 @@ class SupportTreeBuildsteps { m_builder.add_pillar_base(pid, m_cfg.base_height_mm, m_cfg.base_radius_mm); } + std::optional search_widening_path(const Vec3d &jp, + const Vec3d &dir, + double radius, + double new_radius); + public: SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm); diff --git a/src/libslic3r/SLA/SupportTreeMesher.hpp b/src/libslic3r/SLA/SupportTreeMesher.hpp index a086680c3..63182745d 100644 --- a/src/libslic3r/SLA/SupportTreeMesher.hpp +++ b/src/libslic3r/SLA/SupportTreeMesher.hpp @@ -94,6 +94,24 @@ inline Contour3D get_mesh(const Bridge &br, size_t steps) return mesh; } +inline Contour3D get_mesh(const DiffBridge &br, size_t steps) +{ + double h = br.get_length(); + Contour3D mesh = halfcone(h, br.r, br.end_r, Vec3d::Zero(), steps); + + using Quaternion = Eigen::Quaternion; + + // We rotate the head to the specified direction. The head's pointing + // side is facing upwards so this means that it would hold a support + // point with a normal pointing straight down. This is the reason of + // the -1 z coordinate + auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, 1}, br.get_dir()); + + for(auto& p : mesh.points) p = quatern * p + br.startp; + + return mesh; +} + }} // namespace Slic3r::sla #endif // SUPPORTTREEMESHER_HPP diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index 9a9c762e3..dad2b9097 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -4,6 +4,8 @@ #include "sla_test_utils.hpp" +#include + namespace { const char *const BELOW_PAD_TEST_OBJECTS[] = { @@ -228,3 +230,12 @@ TEST_CASE("Triangle mesh conversions should be correct", "[SLAConversions]") cntr.from_obj(infile); } } + +TEST_CASE("halfcone test", "[halfcone]") { + sla::DiffBridge br{Vec3d{1., 1., 1.}, Vec3d{10., 10., 10.}, 0.25, 0.5}; + + TriangleMesh m = sla::to_triangle_mesh(sla::get_mesh(br, 45)); + + m.require_shared_vertices(); + m.WriteOBJFile("Halfcone.obj"); +} From f3c0bf46d4573bc8592a8dab70695eb1fe8aaef1 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 17 Jul 2020 14:09:28 +0200 Subject: [PATCH 66/70] finish optimizer interface and remove commented code --- src/libslic3r/Optimizer.hpp | 73 ++++++++++++--------- src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 23 +++---- 2 files changed, 54 insertions(+), 42 deletions(-) diff --git a/src/libslic3r/Optimizer.hpp b/src/libslic3r/Optimizer.hpp index dc70abe33..6495ae7ff 100644 --- a/src/libslic3r/Optimizer.hpp +++ b/src/libslic3r/Optimizer.hpp @@ -103,6 +103,16 @@ public: bool stop_condition() { return m_stop_condition(); } }; +// Helper class to use optimization methods involving gradient. +template struct ScoreGradient { + double score; + std::optional> gradient; + + ScoreGradient(double s, const std::array &grad) + : score{s}, gradient{grad} + {} +}; + // Helper to be used in static_assert. template struct always_false { enum { value = false }; }; @@ -112,13 +122,13 @@ public: Optimizer(const StopCriteria &) { - static_assert(always_false::value, - "Optimizer unimplemented for given method!"); + static_assert (always_false::value, + "Optimizer unimplemented for given method!"); } - Optimizer &to_min() { return *this; } - Optimizer &to_max() { return *this; } - Optimizer &set_criteria(const StopCriteria &) { return *this; } + Optimizer &to_min() { return *this; } + Optimizer &to_max() { return *this; } + Optimizer &set_criteria(const StopCriteria &) { return *this; } StopCriteria get_criteria() const { return {}; }; template @@ -156,35 +166,20 @@ struct IsNLoptAlg> { template using NLoptOnly = std::enable_if_t::value, T>; -// Convert any collection to tuple. This is useful for object functions taking -// an argument list of doubles. Make things cleaner on the call site of -// optimize(). -template struct to_tuple_ { - static auto call(const C &c) - { - return std::tuple_cat(std::tuple(c[N-I]), - to_tuple_::call(c)); - } -}; - -template struct to_tuple_<0, N, T, C> { - static auto call(const C &c) { return std::tuple<>(); } -}; - -// C array to tuple -template auto carray_tuple(const T *v) -{ - return to_tuple_::call(v); -} - -// Helper to convert C style array to std::array -template auto to_arr(const T (&a) [N]) +// Helper to convert C style array to std::array. The copy should be optimized +// away with modern compilers. +template auto to_arr(const T *a) { std::array r; - std::copy(std::begin(a), std::end(a), std::begin(r)); + std::copy(a, a + N, std::begin(r)); return r; } +template auto to_arr(const T (&a) [N]) +{ + return to_arr(static_cast(a)); +} + enum class OptDir { MIN, MAX }; // Where to optimize struct NLopt { // Helper RAII class for nlopt_opt @@ -227,9 +222,19 @@ protected: nlopt_force_stop(std::get<2>(*tdata)); auto fnptr = std::get<0>(*tdata); - auto funval = carray_tuple(params); + auto funval = to_arr(params); - return std::apply(*fnptr, funval); + double scoreval = 0.; + using RetT = decltype((*fnptr)(funval)); + if constexpr (std::is_convertible_v>) { + ScoreGradient score = (*fnptr)(funval); + for (size_t i = 0; i < n; ++i) gradient[i] = (*score.gradient)[i]; + scoreval = score.score; + } else { + scoreval = (*fnptr)(funval); + } + + return scoreval; } template @@ -354,12 +359,18 @@ public: template Bounds bounds(const Bound (&b) [N]) { return detail::to_arr(b); } template Input initvals(const double (&a) [N]) { return detail::to_arr(a); } +template auto score_gradient(double s, const double (&grad)[N]) +{ + return ScoreGradient(s, detail::to_arr(grad)); +} // Predefinded NLopt algorithms that are used in the codebase using AlgNLoptGenetic = detail::NLoptAlgComb; using AlgNLoptSubplex = detail::NLoptAlg; using AlgNLoptSimplex = detail::NLoptAlg; +// TODO: define others if needed... + // Helper defs for pre-crafted global and local optimizers that work well. using DefaultGlobalOptimizer = Optimizer; using DefaultLocalOptimizer = Optimizer; diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 7ed410802..2b40f0082 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -496,7 +496,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &hjp, search_widening_path(jp, dir, radius, m_cfg.head_back_radius_mm); if (diffbr && diffbr->endp.z() > jp_gnd) { - auto &br = m_builder.add_diffbridge(diffbr.value()); + auto &br = m_builder.add_diffbridge(*diffbr); if (head_id >= 0) m_builder.head(head_id).bridge_id = br.id; endp = diffbr->endp; radius = diffbr->end_r; @@ -589,7 +589,9 @@ std::optional SupportTreeBuildsteps::search_widening_path( double fallback_ratio = radius / m_cfg.head_back_radius_mm; auto oresult = solver.to_max().optimize( - [this, jp, radius, new_radius](double plr, double azm, double t) { + [this, jp, radius, new_radius](const opt::Input<3> &input) { + auto &[plr, azm, t] = input; + auto d = spheric_to_dir(plr, azm).normalized(); double ret = pinhead_mesh_intersect(jp, d, radius, new_radius, t) .distance(); @@ -705,24 +707,22 @@ void SupportTreeBuildsteps::filter() // viable normal that doesn't collide with the model // geometry and its very close to the default. - // stc.stop_score = w; // space greater than w is enough Optimizer solver(get_criteria(m_cfg)); - solver.seed(0); - //solver.seed(0); // we want deterministic behavior + solver.seed(0); // we want deterministic behavior auto oresult = solver.to_max().optimize( - [this, pin_r, back_r, hp](double plr, double azm, double l) + [this, pin_r, back_r, hp](const opt::Input<3> &input) { + auto &[plr, azm, l] = input; + auto dir = spheric_to_dir(plr, azm).normalized(); - double score = pinhead_mesh_intersect( + return pinhead_mesh_intersect( hp, dir, pin_r, back_r, l).distance(); - - return score; }, initvals({polar, azimuth, (lmin + lmax) / 2.}), // start with what we have bounds({ - {PI - m_cfg.bridge_slope, PI}, // Must not exceed the tilt limit + {PI - m_cfg.bridge_slope, PI}, // Must not exceed the slope limit {-PI, PI}, // azimuth can be a full search {lmin, lmax} })); @@ -924,7 +924,8 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head) double r_back = head.r_back_mm; Vec3d hjp = head.junction_point(); auto oresult = solver.to_max().optimize( - [this, hjp, r_back](double plr, double azm) { + [this, hjp, r_back](const opt::Input<2> &input) { + auto &[plr, azm] = input; Vec3d n = spheric_to_dir(plr, azm).normalized(); return bridge_mesh_distance(hjp, n, r_back); }, From 0614d6d4a8ce959085488b8f6690c48f13442c99 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 3 Aug 2020 19:07:30 +0200 Subject: [PATCH 67/70] Remove leftover junk comments --- src/libslic3r/SLAPrintSteps.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index defc5246c..76bbf498d 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -374,9 +374,6 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po) // If the zero elevation mode is engaged, we have to filter out all the // points that are on the bottom of the object if (is_zero_elevation(po.config())) { -// double discard = pcfg.embed_object.object_gap_mm / -// std::cos(po.m_supportdata->cfg.bridge_slope) ; - remove_bottom_points(po.m_supportdata->pts, float(po.m_supportdata->emesh.ground_level() + EPSILON)); } From 171acf094c3b3a7320ab665a041a4bf2ba5b62fb Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 5 Aug 2020 16:34:01 +0200 Subject: [PATCH 68/70] Change license of libnest2d to LGPLv3 --- src/libnest2d/LICENSE.txt | 816 ++++++++------------------------------ 1 file changed, 160 insertions(+), 656 deletions(-) diff --git a/src/libnest2d/LICENSE.txt b/src/libnest2d/LICENSE.txt index dba13ed2d..07b1d92c0 100644 --- a/src/libnest2d/LICENSE.txt +++ b/src/libnest2d/LICENSE.txt @@ -1,661 +1,165 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file From 1079d4644c04d075e783d3fb3ad59f13077fae32 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 6 Aug 2020 10:40:04 +0200 Subject: [PATCH 69/70] PhysicalPrinterDialog improvements : Printer device default name is changed to force the user to change it SavePresetDialog : Fixed OSX bug, when wxEVT_TEXT wasn't invoked after change selection in ComboBox --- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 12 +++++++++--- src/slic3r/GUI/PresetComboBoxes.cpp | 5 +++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index f14f49801..12d1cd287 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -161,13 +161,15 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) SetFont(wxGetApp().normal_font()); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - m_default_name = _L("My Printer Device"); + m_default_name = _L("Type here the name of your printer device"); + bool new_printer = true; if (printer_name.IsEmpty()) printer_name = m_default_name; else { std::string full_name = into_u8(printer_name); printer_name = from_u8(PhysicalPrinter::get_short_name(full_name)); + new_printer = false; } wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _L("Descriptive name for the printer device") + ":"); @@ -206,7 +208,6 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) m_optgroup = new ConfigOptionsGroup(this, _L("Print Host upload"), m_config); build_printhost_settings(m_optgroup); - //m_optgroup->reload_config(); wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); @@ -230,6 +231,11 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) SetSizer(topSizer); topSizer->SetSizeHints(this); + + if (new_printer) { + m_printer_name->SetFocus(); + m_printer_name->SelectAll(); + } } PhysicalPrinterDialog::~PhysicalPrinterDialog() @@ -494,7 +500,7 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) std::string renamed_from; // temporary save previous printer name if it was edited - if (m_printer.name != _u8L("My Printer Device") && + if (m_printer.name != into_u8(m_default_name) && m_printer.name != into_u8(printer_name)) renamed_from = m_printer.name; diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 77bdb3812..da33ee51a 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -1054,6 +1054,11 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string& suffix, wxBox m_combo->Append(from_u8(value)); m_combo->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { update(); }); +#ifdef __WXOSX__ + // Under OSX wxEVT_TEXT wasn't invoked after change selection in combobox, + // So process wxEVT_COMBOBOX too + m_combo->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent&) { update(); }); +#endif //__WXOSX__ m_valid_label = new wxStaticText(m_parent, wxID_ANY, ""); m_valid_label->SetFont(wxGetApp().bold_font()); From 41b1dc3d80d43f4a2df141edd3d4b5bd1ee24ce1 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 6 Aug 2020 14:05:42 +0200 Subject: [PATCH 70/70] Fix of custom supports 3MF loading Multiple-part objects were not handled correctly --- src/libslic3r/Format/3mf.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 3612e6898..59dc85a0a 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -1878,10 +1878,11 @@ namespace Slic3r { volume->calculate_convex_hull(); // recreate custom supports from previously loaded attribute - assert(geometry.custom_supports.size() == triangles_count); for (unsigned i=0; im_supported_facets.set_triangle_from_string(i, geometry.custom_supports[i]); + size_t index = src_start_id/3 + i; + assert(index < geometry.custom_supports.size()); + if (! geometry.custom_supports[index].empty()) + volume->m_supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]); } // apply the remaining volume's metadata