From f53289ae2d967ecb465a0fe26b1f7f588f87c091 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 10 Jun 2020 14:16:11 +0200 Subject: [PATCH 01/35] Fix support and hole point reprojection after reload and netfabb --- src/libslic3r/SLA/Hollowing.cpp | 2 +- src/libslic3r/SLA/ReprojectPointsOnMesh.hpp | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index c4a616d93a..0dd9436a1d 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -97,7 +97,7 @@ std::unique_ptr generate_interior(const TriangleMesh & mesh, _generate_interior(mesh, ctl, hc.min_thickness, voxel_scale, hc.closing_distance)); - if (meshptr) { + if (meshptr && !meshptr->empty()) { // This flips the normals to be outward facing... meshptr->require_shared_vertices(); diff --git a/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp b/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp index fa919a1170..702d1bce18 100644 --- a/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp +++ b/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp @@ -28,17 +28,25 @@ void reproject_support_points(const EigenMesh3D &mesh, std::vector &p inline void reproject_points_and_holes(ModelObject *object) { bool has_sppoints = !object->sla_support_points.empty(); - bool has_holes = !object->sla_drain_holes.empty(); - if (!object || (!has_holes && !has_sppoints)) return; + // Disabling reprojection of holes as they have a significant offset away + // from the model body which tolerates minor geometrical changes. + // + // TODO: uncomment and ensure the right offset of the hole points if + // reprojection would still be necessary. + // bool has_holes = !object->sla_drain_holes.empty(); - EigenMesh3D emesh{object->raw_mesh()}; + if (!object || (/*!has_holes &&*/ !has_sppoints)) return; + + TriangleMesh rmsh = object->raw_mesh(); + rmsh.require_shared_vertices(); + EigenMesh3D emesh{rmsh}; if (has_sppoints) reproject_support_points(emesh, object->sla_support_points); - if (has_holes) - reproject_support_points(emesh, object->sla_drain_holes); +// if (has_holes) +// reproject_support_points(emesh, object->sla_drain_holes); } }} From 920d9677da02bee73e9957f606c4519ce755537c Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 11 Jun 2020 14:02:59 +0200 Subject: [PATCH 02/35] Added new tech ENABLE_LAYOUT_NO_RESTART -> Enable changing application layout without the need to restart it --- src/libslic3r/Technologies.hpp | 3 + src/slic3r/GUI/GUI_App.cpp | 16 +++ src/slic3r/GUI/MainFrame.cpp | 230 +++++++++++++++++++++++++++++++-- src/slic3r/GUI/MainFrame.hpp | 27 +++- src/slic3r/GUI/Preferences.cpp | 2 + 5 files changed, 269 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index e0d534e001..8233ba40a1 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -51,5 +51,8 @@ // Enable error logging for OpenGL calls when SLIC3R_LOGLEVEL >= 5 #define ENABLE_OPENGL_ERROR_LOGGING (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 3c000f62e5..3650468d0a 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1059,17 +1059,33 @@ 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->update_layout(); + mainframe->select_tab(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/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index ef837c200d..ab7efcf575 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -46,6 +46,9 @@ 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); @@ -90,6 +93,7 @@ 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 @@ -103,8 +107,17 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S m_tabpanel->SetMinSize(size); } #endif +#endif // !ENABLE_LAYOUT_NO_RESTART + // initialize layout auto sizer = new wxBoxSizer(wxVERTICAL); +#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); @@ -114,6 +127,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S 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__ @@ -205,8 +219,12 @@ 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 (?) @@ -214,6 +232,90 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S m_plater->show_action_buttons(true); } +#if ENABLE_LAYOUT_NO_RESTART +void MainFrame::update_layout() +{ + ESettingsLayout layout = wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old : + wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New : + wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? ESettingsLayout::Dlg : ESettingsLayout::Old; + + if (m_layout == layout) + return; + + wxBusyCursor busy; + + Freeze(); + + if (m_layout != ESettingsLayout::Unknown) { + // Restore from previous settings + if (m_layout == ESettingsLayout::Old) { + m_plater->Reparent(this); + m_tabpanel->RemovePage(m_tabpanel->FindPage(m_plater)); + GetSizer()->Detach(m_tabpanel); + } + else { + if (m_layout == ESettingsLayout::New) { + GetSizer()->Detach(m_plater); + GetSizer()->Detach(m_tabpanel); + m_tabpanel->DeletePage(m_tabpanel->FindPage(m_plater_page)); + } + else { + if (m_settings_dialog.IsShown()) + m_settings_dialog.Close(); + + GetSizer()->Detach(m_plater); + m_settings_dialog.GetSizer()->Detach(m_tabpanel); + m_tabpanel->Reparent(this); + } + } + } + + m_layout = layout; + + // From the very beginning the Print settings should be selected + m_last_selected_tab = m_layout == ESettingsLayout::Dlg ? 0 : 1; + + // Set new settings + if (m_layout == ESettingsLayout::Old) { + m_plater->Reparent(m_tabpanel); + m_tabpanel->InsertPage(0, m_plater, _L("Plater")); + GetSizer()->Add(m_tabpanel, 1, wxEXPAND); + m_tabpanel->Show(); + } + else { + if (m_layout == ESettingsLayout::New) { + GetSizer()->Add(m_plater, 1, wxEXPAND); + GetSizer()->Add(m_tabpanel, 1, wxEXPAND); + m_plater_page = new wxPanel(m_tabpanel); + m_tabpanel->InsertPage(0, m_plater_page, _L("Plater")); // empty panel just for Plater tab */ + } + else { + GetSizer()->Add(m_plater, 1, wxEXPAND); + m_plater->Show(); + m_tabpanel->Reparent(&m_settings_dialog); + m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND); + } + } + +#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 (m_layout == ESettingsLayout::New) { + wxSize size = wxGetApp().get_min_size(); + size.SetHeight(int(0.5 * size.GetHeight())); + m_plater->SetMinSize(size); + m_tabpanel->SetMinSize(size); + } +#endif + + Layout(); + Thaw(); +} +#endif // ENABLE_LAYOUT_NO_RESTART + // Called when closing the application and when switching the application language. void MainFrame::shutdown() { @@ -246,6 +348,11 @@ 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()) @@ -254,6 +361,7 @@ void MainFrame::shutdown() m_settings_dialog->Destroy(); } +#endif // ENABLE_LAYOUT_NO_RESTART // Stop the background thread (Windows and Linux). // Disconnect from a 3DConnextion driver (OSX). @@ -312,9 +420,18 @@ 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); +#ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList + m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font()); +#endif + 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; + 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; @@ -331,6 +448,7 @@ void MainFrame::init_tabpanel() 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(); @@ -351,6 +469,9 @@ void MainFrame::init_tabpanel() select_tab(0); // select Plater }); +#if ENABLE_LAYOUT_NO_RESTART + m_plater = new Plater(this, this); +#else if (m_layout == slOld) { m_plater = new Plater(m_tabpanel, this); m_tabpanel->AddPage(m_plater, _L("Plater")); @@ -360,6 +481,7 @@ void MainFrame::init_tabpanel() 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(); @@ -501,6 +623,18 @@ bool MainFrame::can_slice() const bool MainFrame::can_change_view() const { +#if ENABLE_LAYOUT_NO_RESTART + switch (m_layout) + { + default: { return false; } + case ESettingsLayout::New: { return m_plater->IsShown(); } + case ESettingsLayout::Dlg: { return true; } + case ESettingsLayout::Old: { + int page_id = m_tabpanel->GetSelection(); + 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) @@ -508,6 +642,7 @@ bool MainFrame::can_change_view() const // 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 @@ -549,7 +684,11 @@ 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) // Update tabs later, from the SettingsDialog, when 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(); @@ -1311,15 +1450,41 @@ void MainFrame::load_config(const DynamicPrintConfig& config) void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) { +#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()) + m_settings_dialog.Hide(); + + m_tabpanel->Show(); + m_settings_dialog.Show(); +#else + if (m_settings_dialog.IsShown()) + m_settings_dialog.SetFocus(); + else { + m_tabpanel->Show(); + 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 @@ -1329,8 +1494,13 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) else #endif m_settings_dialog->Show(); +#endif // ENABLE_LAYOUT_NO_RESTART } +#if ENABLE_LAYOUT_NO_RESTART + else if (m_layout == ESettingsLayout::New) { +#else else if (m_layout == slNew) { +#endif // ENABLE_LAYOUT_NO_RESTART m_plater->Show(tab == 0); m_tabpanel->Show(tab != 0); @@ -1340,8 +1510,12 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) Layout(); } - // when tab == -1, it means we should to show the last selected tab + // when tab == -1, it means we should to 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. @@ -1452,14 +1626,12 @@ std::string MainFrame::get_dir_name(const wxString &full_name) const // ---------------------------------------------------------------------------- SettingsDialog::SettingsDialog(MainFrame* mainframe) -: DPIDialog(nullptr, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _L("Settings"), wxDefaultPosition, wxDefaultSize, - wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxDIALOG_NO_PARENT, "settings_dialog"), +: DPIDialog(mainframe, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _L("Settings"), wxDefaultPosition, wxDefaultSize, + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX, "settings_dialog"), m_main_frame(mainframe) { this->SetFont(wxGetApp().normal_font()); - - wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - this->SetBackgroundColour(bgr_clr); + this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); // Load the icon either from the exe, or from the ico file. #if _WIN32 @@ -1472,6 +1644,7 @@ 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); @@ -1496,10 +1669,45 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) } } }); +#endif // !ENABLE_LAYOUT_NO_RESTART + +#if ENABLE_LAYOUT_NO_RESTART + this->Bind(wxEVT_SHOW, [this](wxShowEvent& evt) { + + auto key_up_handker = [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; + } + } + }; + + if (evt.IsShown()) { + if (m_tabpanel != nullptr) + m_tabpanel->Bind(wxEVT_KEY_UP, key_up_handker); + } + else { + if (m_tabpanel != nullptr) + 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(); @@ -1513,7 +1721,13 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) SetMinSize(min_size); SetSize(GetMinSize()); #endif +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//#if !ENABLE_LAYOUT_NO_RESTART +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Layout(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//#endif // !ENABLE_LAYOUT_NO_RESTART +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } void SettingsDialog::on_dpi_changed(const wxRect& suggested_rect) diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 3b64be9bcc..4d651f04ea 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -51,11 +51,15 @@ struct PresetTab { class SettingsDialog : public DPIDialog { wxNotebook* m_tabpanel { nullptr }; - MainFrame* m_main_frame {nullptr }; + MainFrame* m_main_frame { nullptr }; 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; @@ -114,11 +118,23 @@ class MainFrame : public DPIFrame wxFileHistory m_recent_projects; +#if ENABLE_LAYOUT_NO_RESTART + enum class ESettingsLayout + { + Unknown, + Old, + New, + Dlg, + }; + + 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); @@ -128,6 +144,10 @@ 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(); @@ -169,7 +189,12 @@ 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 50abfb7e68..84ad548799 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -234,6 +234,7 @@ 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 @@ -255,6 +256,7 @@ void PreferencesDialog::accept() 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 a1319cc8f708b316897bd586f936f6467037038f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 11 Jun 2020 16:07:28 +0200 Subject: [PATCH 03/35] Fixed restoring of application starting position --- src/slic3r/GUI/GUI_App.cpp | 4 +++- src/slic3r/GUI/GUI_Utils.hpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 9dfb4b422a..440efe0a5e 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1394,7 +1394,9 @@ void GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &na return; } - window->SetSize(metrics->get_rect()); + const wxRect& rect = metrics->get_rect(); + window->SetPosition(rect.GetPosition()); + window->SetSize(rect.GetSize()); window->Maximize(metrics->get_maximized()); } diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 934845fb37..a2f939facb 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -341,7 +341,7 @@ public: static WindowMetrics from_window(wxTopLevelWindow *window); static boost::optional deserialize(const std::string &str); - wxRect get_rect() const { return rect; } + const wxRect& get_rect() const { return rect; } bool get_maximized() const { return maximized; } void sanitize_for_display(const wxRect &screen_rect); From 1c95ceaeaa49d4815e4a60f3f94b94bcb397aef0 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 11 Jun 2020 16:09:51 +0200 Subject: [PATCH 04/35] Reworked algorithm for Voronoi Offset curve extraction. Now the algorithm is very different from the OpenVoronoi implementation and hopefully it is now correct (save numerical issues, which will be a big PITA). --- src/libslic3r/CMakeLists.txt | 1 + src/libslic3r/Line.cpp | 2 +- src/libslic3r/Line.hpp | 4 +- src/libslic3r/VoronoiOffset.cpp | 643 +++++++++++++++++++-------- src/libslic3r/VoronoiOffset.hpp | 13 +- src/libslic3r/VoronoiVisualUtils.hpp | 407 +++++++++++++++++ tests/libslic3r/test_voronoi.cpp | 421 +----------------- 7 files changed, 908 insertions(+), 583 deletions(-) create mode 100644 src/libslic3r/VoronoiVisualUtils.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 1a58bdbbd9..881466b399 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -190,6 +190,7 @@ add_library(libslic3r STATIC MTUtils.hpp VoronoiOffset.cpp VoronoiOffset.hpp + VoronoiVisualUtils.hpp Zipper.hpp Zipper.cpp MinAreaBoundingBox.hpp diff --git a/src/libslic3r/Line.cpp b/src/libslic3r/Line.cpp index 429cde3727..974f585dc2 100644 --- a/src/libslic3r/Line.cpp +++ b/src/libslic3r/Line.cpp @@ -135,4 +135,4 @@ BoundingBox get_extents(const Lines &lines) return bbox; } -} +} // namespace Slic3r diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index f255bee93e..caab809f5c 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -103,7 +103,7 @@ public: Vec3d b; }; -extern BoundingBox get_extents(const Lines &lines); +BoundingBox get_extents(const Lines &lines); } // namespace Slic3r @@ -125,4 +125,4 @@ namespace boost { namespace polygon { } } // end Boost -#endif +#endif // slic3r_Line_hpp_ diff --git a/src/libslic3r/VoronoiOffset.cpp b/src/libslic3r/VoronoiOffset.cpp index cd96e3cdc3..7c736f19d9 100644 --- a/src/libslic3r/VoronoiOffset.cpp +++ b/src/libslic3r/VoronoiOffset.cpp @@ -1,11 +1,15 @@ -// Polygon offsetting code inspired by OpenVoronoi by Anders Wallin -// https://github.com/aewallin/openvoronoi -// This offsetter uses results of boost::polygon Voronoi. +// Polygon offsetting using Voronoi diagram prodiced by boost::polygon. #include "VoronoiOffset.hpp" #include +// #define VORONOI_DEBUG_OUT + +#ifdef VORONOI_DEBUG_OUT +#include +#endif + namespace Slic3r { using VD = Geometry::VoronoiDiagram; @@ -48,6 +52,93 @@ namespace detail { } } + struct Intersections + { + int count; + Vec2d pts[2]; + }; + + // Return maximum two points, that are at distance "d" from both points + Intersections point_point_equal_distance_points(const Point &pt1, const Point &pt2, const double d) + { + // input points + const auto cx = double(pt1.x()); + const auto cy = double(pt1.y()); + const auto qx = double(pt2.x()); + const auto qy = double(pt2.y()); + + // Calculating determinant. + auto x0 = 2. * qy; + auto cx2 = cx * cx; + auto cy2 = cy * cy; + auto x5 = 2 * cx * qx; + auto x6 = cy * x0; + auto qx2 = qx * qx; + auto qy2 = qy * qy; + auto x9 = qx2 + qy2; + auto x10 = cx2 + cy2 - x5 - x6 + x9; + auto x11 = - cx2 - cy2; + auto discr = x10 * (4. * d + x11 + x5 + x6 - qx2 - qy2); + if (discr < 0.) + // No intersection point found, the two circles are too far away. + return Intersections { 0, { Vec2d(), Vec2d() } }; + + // Some intersections are found. + int npoints = (discr > 0) ? 2 : 1; + auto x1 = 2. * cy - x0; + auto x2 = cx - qx; + auto x12 = 0.5 * x2 * sqrt(discr) / x10; + auto x13 = 0.5 * (cy + qy); + auto x14 = - x12 + x13; + auto x15 = x11 + x9; + auto x16 = 0.5 / x2; + auto x17 = x12 + x13; + return Intersections { npoints, { Vec2d(- x16 * (x1 * x14 + x15), x14), + Vec2d(- x16 * (x1 * x17 + x15), x17) } }; + } + + // Return maximum two points, that are at distance "d" from both the line and point. + Intersections line_point_equal_distance_points(const Line &line, const Point &pt, const double d) + { + assert(line.a != pt && line.b != pt); + // Calculating two points of distance "d" to a ray and a point. + // Point. + auto x0 = double(pt.x()); + auto y0 = double(pt.y()); + // Ray equation. Vector (a, b) is perpendicular to line. + auto a = double(line.a.y() - line.b.y()); + auto b = double(line.b.x() - line.a.x()); + // pt shall not lie on line. + assert(std::abs((x0 - line.a.x()) * a + (y0 - line.a.y()) * b) < SCALED_EPSILON); + // Orient line so that the vector (a, b) points towards pt. + if (a * (x0 - line.a.x()) + b * (y0 - line.a.y()) < 0.) + std::swap(x0, y0); + double c = - a * double(line.a.x()) - b * double(line.a.y()); + // Calculate the two points. + double a2 = a * a; + double b2 = b * b; + double a2b2 = a2 + b2; + double d2 = d * d; + double s = a2*d2 - a2*sqr(x0) - 2*a*b*x0*y0 - 2*a*c*x0 + 2*a*d*x0 + b2*d2 - b2*sqr(y0) - 2*b*c*y0 + 2*b*d*y0 - sqr(c) + 2*c*d - d2; + if (s < 0.) + // Distance of pt from line is bigger than 2 * d. + return Intersections { 0 }; + double u; + int cnt; + if (s == 0.) { + // Distance of pt from line is 2 * d. + cnt = 1; + u = 0.; + } else { + // Distance of pt from line is smaller than 2 * d. + cnt = 2; + u = a*sqrt(s)/a2b2; + } + double v = (-a2*y0 + a*b*x0 + b*c - b*d)/a2b2; + return Intersections { cnt, { Vec2d((b * ( u + v) - c + d) / a, - u - v), + Vec2d((b * (- u + v) - c + d) / a, u - v) } }; + } + Vec2d voronoi_edge_offset_point( const VD &vd, const Lines &lines, @@ -131,174 +222,384 @@ namespace detail { } }; -Polygons voronoi_offset(const VD &vd, const Lines &lines, double offset_distance, double discretization_error) +static Vec2d foot_pt(const Line &iline, const Point &ipt) { - // Distance of a VD vertex to the closest site (input polygon edge or vertex). - std::vector vertex_dist(vd.num_vertices(), std::numeric_limits::max()); + Vec2d pt = iline.a.cast(); + Vec2d dir = (iline.b - iline.a).cast(); + Vec2d v = ipt.cast() - pt; + double l2 = dir.squaredNorm(); + double t = (l2 == 0.) ? 0. : v.dot(dir) / l2; + return pt + dir * t; +} - // Minium distance of a VD edge to the closest site (input polygon edge or vertex). - // For a parabolic segment the distance may be smaller than the distance of the two end points. - std::vector edge_dist(vd.num_edges(), std::numeric_limits::max()); - - // Calculate minimum distance of input polygons to voronoi vertices and voronoi edges. - for (const VD::edge_type &edge : vd.edges()) { - const VD::vertex_type *v0 = edge.vertex0(); - const VD::vertex_type *v1 = edge.vertex1(); - const VD::cell_type *cell = edge.cell(); - const VD::cell_type *cell2 = edge.twin()->cell(); - const Line &line0 = lines[cell->source_index()]; - const Line &line1 = lines[cell2->source_index()]; - double d0, d1, dmin; - if (v0 == nullptr || v1 == nullptr) { - assert(edge.is_infinite()); - if (cell->contains_point() && cell2->contains_point()) { - const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; - const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; - d0 = d1 = std::numeric_limits::max(); - if (v0 == nullptr && v1 == nullptr) { - dmin = (pt1.cast() - pt0.cast()).norm(); - } else { - Vec2d pt((pt0 + pt1).cast() * 0.5); - Vec2d dir(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x())); - Vec2d pt0d(pt0.x(), pt0.y()); - if (v0) { - Vec2d a(v0->x(), v0->y()); - d0 = (a - pt0d).norm(); - dmin = ((a - pt).dot(dir) < 0.) ? (a - pt0d).norm() : d0; - vertex_dist[v0 - &vd.vertices().front()] = d0; - } else { - Vec2d a(v1->x(), v1->y()); - d1 = (a - pt0d).norm(); - dmin = ((a - pt).dot(dir) < 0.) ? (a - pt0d).norm() : d1; - vertex_dist[v1 - &vd.vertices().front()] = d1; - } - } - } else { - // Infinite edges could not be created by two segment sites. - assert(cell->contains_point() != cell2->contains_point()); - // Linear edge goes through the endpoint of a segment. - assert(edge.is_linear()); - assert(edge.is_secondary()); +Polygons voronoi_offset( + const Geometry::VoronoiDiagram &vd, + const Lines &lines, + double offset_distance, + double discretization_error) +{ #ifndef NDEBUG - if (cell->contains_segment()) { - const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; - assert((pt1.x() == line0.a.x() && pt1.y() == line0.a.y()) || - (pt1.x() == line0.b.x() && pt1.y() == line0.b.y())); + // Verify that twin halfedges are stored next to the other in vd. + for (size_t i = 0; i < vd.num_edges(); i += 2) { + const VD::edge_type &e = vd.edges()[i]; + const VD::edge_type &e2 = vd.edges()[i + 1]; + assert(e.twin() == &e2); + assert(e2.twin() == &e); + assert(e.is_secondary() == e2.is_secondary()); + if (e.is_secondary()) { + assert(e.cell()->contains_point() != e2.cell()->contains_point()); + const VD::edge_type &ex = (e.cell()->contains_point() ? e : e2); + // Verify that the Point defining the cell left of ex is an end point of a segment + // defining the cell right of ex. + const Line &line0 = lines[ex.cell()->source_index()]; + const Line &line1 = lines[ex.twin()->cell()->source_index()]; + const Point &pt = (ex.cell()->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; + assert(pt == line1.a || pt == line1.b); + } + } +#endif // NDEBUG + + // Mark edges with outward vertex pointing outside the polygons, thus there is a chance + // that such an edge will have an intersection with our desired offset curve. + bool outside = offset_distance > 0.; + std::vector edge_candidate(vd.num_edges(), 2); // unknown state + const VD::edge_type *front_edge = &vd.edges().front(); + for (const VD::edge_type &edge : vd.edges()) + if (edge.vertex1() == nullptr) { + // Infinite Voronoi edge separating two Point sites. + // Infinite edge is always outside and it has at least one valid vertex. + assert(edge.vertex0() != nullptr); + edge_candidate[&edge - front_edge] = outside; + // Opposite edge of an infinite edge is certainly not active. + edge_candidate[edge.twin() - front_edge] = 0; + } else if (edge.vertex1() != nullptr) { + // Finite edge. + const VD::cell_type *cell = edge.cell(); + const Line *line = cell->contains_segment() ? &lines[cell->source_index()] : nullptr; + if (line == nullptr) { + cell = edge.twin()->cell(); + line = cell->contains_segment() ? &lines[cell->source_index()] : nullptr; + } + if (line) { + const VD::vertex_type *v1 = edge.vertex1(); + assert(v1); + Vec2d l0(line->a.cast()); + Vec2d lv((line->b - line->a).cast()); + double side = cross2(lv, Vec2d(v1->x(), v1->y()) - l0); + edge_candidate[&edge - front_edge] = outside ? (side < 0.) : (side > 0.); + } + } + for (const VD::edge_type &edge : vd.edges()) + if (edge_candidate[&edge - front_edge] == 2) { + assert(edge.cell()->contains_point() && edge.twin()->cell()->contains_point()); + // Edge separating two point sources, not yet classified as inside / outside. + const VD::edge_type *e = &edge; + char state; + do { + state = edge_candidate[e - front_edge]; + if (state != 2) + break; + e = e->next(); + } while (e != &edge); + e = &edge; + do { + char &s = edge_candidate[e - front_edge]; + if (s == 2) { + assert(e->cell()->contains_point() && e->twin()->cell()->contains_point()); + assert(edge_candidate[e->twin() - front_edge] == 2); + s = state; + edge_candidate[e->twin() - front_edge] = state; + } + e = e->next(); + } while (e != &edge); + } + if (! outside) + offset_distance = - offset_distance; + +#ifdef VORONOI_DEBUG_OUT + BoundingBox bbox; + { + bbox.merge(get_extents(lines)); + bbox.min -= (0.01 * bbox.size().cast()).cast(); + bbox.max += (0.01 * bbox.size().cast()).cast(); + } + { + Lines helper_lines; + for (const VD::edge_type &edge : vd.edges()) + if (edge_candidate[&edge - front_edge]) { + const VD::vertex_type *v0 = edge.vertex0(); + const VD::vertex_type *v1 = edge.vertex1(); + assert(v0 != nullptr); + Vec2d pt1(v0->x(), v0->y()); + Vec2d pt2; + if (v1 == nullptr) { + // Unconstrained edge. Calculate a trimmed position. + assert(edge.is_linear()); + const VD::cell_type *cell = edge.cell(); + const VD::cell_type *cell2 = edge.twin()->cell(); + const Line &line0 = lines[cell->source_index()]; + const Line &line1 = lines[cell2->source_index()]; + if (cell->contains_point() && cell2->contains_point()) { + const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; + const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; + // Direction vector of this unconstrained Voronoi edge. + Vec2d dir(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x())); + pt2 = Vec2d(v0->x(), v0->y()) + dir.normalized() * scale_(10.); + } else { + // Infinite edges could not be created by two segment sites. + assert(cell->contains_point() != cell2->contains_point()); + // Linear edge goes through the endpoint of a segment. + assert(edge.is_secondary()); + const Point &ipt = cell->contains_segment() ? + ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) : + ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b); + // Infinite edge starts at an input contour, therefore there is always an intersection with an offset curve. + const Line &line = cell->contains_segment() ? line0 : line1; + assert(line.a == ipt || line.b == ipt); + // dir is perpendicular to line. + Vec2d dir(line.a.y() - line.b.y(), line.b.x() - line.a.x()); + assert(dir.norm() > 0.); + if (((line.a == ipt) == cell->contains_point()) == (v0 == nullptr)) + dir = - dir; + pt2 = ipt.cast() + dir.normalized() * scale_(10.); + } } else { + pt2 = Vec2d(v1->x(), v1->y()); + // Clip the line by the bounding box, so that the coloring of the line will be visible. + Geometry::liang_barsky_line_clipping(pt1, pt2, BoundingBoxf(bbox.min.cast(), bbox.max.cast())); + } + helper_lines.emplace_back(Line(Point(pt1.cast()), Point(((pt1 + pt2) * 0.5).cast()))); + } + dump_voronoi_to_svg(debug_out_path("voronoi-offset-candidates1.svg").c_str(), vd, Points(), lines, Polygons(), helper_lines); + } +#endif // VORONOI_DEBUG_OUT + + std::vector edge_offset_point(vd.num_edges(), Vec2d()); + const double offset_distance2 = offset_distance * offset_distance; + for (const VD::edge_type &edge : vd.edges()) { + assert(edge_candidate[&edge - front_edge] != 2); + size_t edge_idx = &edge - front_edge; + if (edge_candidate[edge_idx] == 1) { + // Edge candidate, intersection points were not calculated yet. + const VD::vertex_type *v0 = edge.vertex0(); + const VD::vertex_type *v1 = edge.vertex1(); + assert(v0 != nullptr); + const VD::cell_type *cell = edge.cell(); + const VD::cell_type *cell2 = edge.twin()->cell(); + const Line &line0 = lines[cell->source_index()]; + const Line &line1 = lines[cell2->source_index()]; + size_t edge_idx2 = edge.twin() - front_edge; + if (v1 == nullptr) { + assert(edge.is_infinite()); + assert(edge_candidate[edge_idx2] == 0); + if (cell->contains_point() && cell2->contains_point()) { const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; - assert((pt0.x() == line1.a.x() && pt0.y() == line1.a.y()) || - (pt0.x() == line1.b.x() && pt0.y() == line1.b.y())); - } - const Point &pt = cell->contains_segment() ? - ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) : - ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b); -#endif /* NDEBUG */ - if (v0) { - assert((Point(v0->x(), v0->y()) - pt).cast().norm() < SCALED_EPSILON); - d0 = dmin = 0.; - vertex_dist[v0 - &vd.vertices().front()] = d0; + const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; + double dmin2 = (Vec2d(v0->x(), v0->y()) - pt0.cast()).squaredNorm(); + if (dmin2 <= offset_distance2) { + // There shall be an intersection of this unconstrained edge with the offset curve. + // Direction vector of this unconstrained Voronoi edge. + Vec2d dir(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x())); + Vec2d pt(v0->x(), v0->y()); + double t = detail::first_circle_segment_intersection_parameter(Vec2d(pt0.x(), pt0.y()), offset_distance, pt, dir); + edge_offset_point[edge_idx] = pt + t * dir; + edge_candidate[edge_idx] = 3; + } else + edge_candidate[edge_idx] = 0; } else { - assert((Point(v1->x(), v1->y()) - pt).cast().norm() < SCALED_EPSILON); - d1 = dmin = 0.; - vertex_dist[v1 - &vd.vertices().front()] = d1; + // Infinite edges could not be created by two segment sites. + assert(cell->contains_point() != cell2->contains_point()); + // Linear edge goes through the endpoint of a segment. + assert(edge.is_linear()); + assert(edge.is_secondary()); + const Point &ipt = cell->contains_segment() ? + ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) : + ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b); + #ifndef NDEBUG + if (cell->contains_segment()) { + const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; + assert((pt1.x() == line0.a.x() && pt1.y() == line0.a.y()) || + (pt1.x() == line0.b.x() && pt1.y() == line0.b.y())); + } else { + const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; + assert((pt0.x() == line1.a.x() && pt0.y() == line1.a.y()) || + (pt0.x() == line1.b.x() && pt0.y() == line1.b.y())); + } + assert((Vec2d(v0->x(), v0->y()) - ipt.cast()).norm() < SCALED_EPSILON); + #endif /* NDEBUG */ + // Infinite edge starts at an input contour, therefore there is always an intersection with an offset curve. + const Line &line = cell->contains_segment() ? line0 : line1; + assert(line.a == ipt || line.b == ipt); + Vec2d pt = ipt.cast(); + Vec2d dir(line.a.y() - line.b.y(), line.b.x() - line.a.x()); + assert(dir.norm() > 0.); + double t = offset_distance / dir.norm(); + if (((line.a == ipt) == cell->contains_point()) == (v0 == nullptr)) + t = - t; + edge_offset_point[edge_idx] = pt + t * dir; + edge_candidate[edge_idx] = 3; } - } - } else { - // Finite edge has valid points at both sides. - if (cell->contains_segment() && cell2->contains_segment()) { - // This edge is a bisector of two line segments. Project v0, v1 onto one of the line segments. - Vec2d pt(line0.a.cast()); - Vec2d dir(line0.b.cast() - pt); - Vec2d vec0 = Vec2d(v0->x(), v0->y()) - pt; - Vec2d vec1 = Vec2d(v1->x(), v1->y()) - pt; - double l2 = dir.squaredNorm(); - assert(l2 > 0.); - d0 = (dir * (vec0.dot(dir) / l2) - vec0).norm(); - d1 = (dir * (vec1.dot(dir) / l2) - vec1).norm(); - dmin = std::min(d0, d1); - } else { - assert(cell->contains_point() || cell2->contains_point()); - const Point &pt0 = cell->contains_point() ? + // The other edge of an unconstrained edge starting with null vertex shall never be intersected. + edge_candidate[edge_idx2] = 0; + } else if (edge.is_secondary()) { + assert(cell->contains_point() != cell2->contains_point()); + const Line &line0 = lines[edge.cell()->source_index()]; + const Line &line1 = lines[edge.twin()->cell()->source_index()]; + const Point &pt = cell->contains_point() ? ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) : ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b); - // Project p0 to line segment . - Vec2d p0(v0->x(), v0->y()); - Vec2d p1(v1->x(), v1->y()); - Vec2d px(pt0.x(), pt0.y()); - Vec2d v = p1 - p0; - d0 = (p0 - px).norm(); - d1 = (p1 - px).norm(); - double t = v.dot(px - p0); - double l2 = v.squaredNorm(); - if (t > 0. && t < l2) { - // Foot point on the line segment. - Vec2d foot = p0 + (t / l2) * v; - dmin = (foot - px).norm(); - } else - dmin = std::min(d0, d1); - } - vertex_dist[v0 - &vd.vertices().front()] = d0; - vertex_dist[v1 - &vd.vertices().front()] = d1; + const Line &line = cell->contains_segment() ? line0 : line1; + assert(pt == line.a || pt == line.b); + assert((pt.cast() - Vec2d(v0->x(), v0->y())).norm() < SCALED_EPSILON); + Vec2d dir(v1->x() - v0->x(), v1->y() - v0->y()); + double l2 = dir.squaredNorm(); + if (offset_distance2 <= l2) { + edge_offset_point[edge_idx] = pt.cast() + (offset_distance / sqrt(l2)) * dir; + edge_candidate[edge_idx] = 3; + } else { + edge_candidate[edge_idx] = 0; + } + edge_candidate[edge_idx2] = 0; + } else { + // Finite edge has valid points at both sides. + bool done = false; + if (cell->contains_segment() && cell2->contains_segment()) { + // This edge is a bisector of two line segments. Project v0, v1 onto one of the line segments. + Vec2d pt(line0.a.cast()); + Vec2d dir(line0.b.cast() - pt); + Vec2d vec0 = Vec2d(v0->x(), v0->y()) - pt; + Vec2d vec1 = Vec2d(v1->x(), v1->y()) - pt; + double l2 = dir.squaredNorm(); + assert(l2 > 0.); + double dmin = (dir * (vec0.dot(dir) / l2) - vec0).squaredNorm(); + double dmax = (dir * (vec1.dot(dir) / l2) - vec1).squaredNorm(); + bool flip = dmin > dmax; + if (flip) + std::swap(dmin, dmax); + if (offset_distance2 >= dmin && offset_distance2 <= dmax) { + // Intersect. Maximum one intersection will be found. + // This edge is a bisector of two line segments. Distance to the input polygon increases/decreases monotonically. + dmin = sqrt(dmin); + dmax = sqrt(dmax); + assert(offset_distance > dmin - EPSILON && offset_distance < dmax + EPSILON); + double ddif = dmax - dmin; + if (ddif == 0.) { + // line, line2 are exactly parallel. This is a singular case, the offset curve should miss it. + } else { + if (flip) { + std::swap(edge_idx, edge_idx2); + std::swap(v0, v1); + } + double t = clamp(0., 1., (offset_distance - dmin) / ddif); + edge_offset_point[edge_idx] = Vec2d(lerp(v0->x(), v1->x(), t), lerp(v0->y(), v1->y(), t)); + edge_candidate[edge_idx] = 3; + edge_candidate[edge_idx2] = 0; + done = true; + } + } + } else { + assert(cell->contains_point() || cell2->contains_point()); + bool point_vs_segment = cell->contains_point() != cell2->contains_point(); + const Point &pt0 = cell->contains_point() ? + ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) : + ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b); + // Project p0 to line segment . + Vec2d p0(v0->x(), v0->y()); + Vec2d p1(v1->x(), v1->y()); + Vec2d px(pt0.x(), pt0.y()); + double d0 = (p0 - px).squaredNorm(); + double d1 = (p1 - px).squaredNorm(); + double dmin = std::min(d0, d1); + double dmax = std::max(d0, d1); + bool has_intersection = false; + if (offset_distance2 <= dmax) { + if (offset_distance2 >= dmin) { + has_intersection = true; + } else { + double dmin_new; + if (point_vs_segment) { + Vec2d ft = foot_pt(cell->contains_segment() ? line0 : line1, pt0); + dmin_new = (ft - px).squaredNorm() * 0.25; + } else { + // point vs. point + const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; + dmin_new = (pt1.cast() - px).squaredNorm() * 0.25; + } + assert(dmin_new < dmax + SCALED_EPSILON); + assert(dmin_new < dmin + SCALED_EPSILON); + dmin = dmin_new; + has_intersection = offset_distance2 >= dmin; + } + } + if (has_intersection) { + detail::Intersections intersections; + if (point_vs_segment) { + assert(cell->contains_point() || cell2->contains_point()); + intersections = detail::line_point_equal_distance_points(cell->contains_segment() ? line0 : line1, pt0, offset_distance); + } else { + const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; + intersections = detail::point_point_equal_distance_points(pt0, pt1, offset_distance); + } + if (intersections.count == 2) { + // Now decide which points fall on this Voronoi edge. + // Tangential points (single intersection) are ignored. + Vec2d v = p1 - p0; + double l2 = v.squaredNorm(); + double t0 = v.dot(intersections.pts[0] - p0); + double t1 = v.dot(intersections.pts[1] - p0); + if (t0 > t1) { + std::swap(t0, t1); + std::swap(intersections.pts[0], intersections.pts[1]); + } + // Remove points outside of the line range. + if (t0 < 0. || t0 > l2) { + if (t1 < 0. || t1 > l2) + intersections.count = 0; + else { + -- intersections.count; + t0 = t1; + intersections.pts[0] = intersections.pts[1]; + } + } else if (t1 < 0. || t1 > l2) + -- intersections.count; + if (intersections.count == 2) { + edge_candidate[edge_idx] = edge_candidate[edge_idx2] = 3; + edge_offset_point[edge_idx] = intersections.pts[0]; + edge_offset_point[edge_idx2] = intersections.pts[1]; + done = true; + } else if (intersections.count == 1) { + if (d1 > d0) { + std::swap(edge_idx, edge_idx2); + edge_candidate[edge_idx] = 3; + edge_candidate[edge_idx2] = 0; + edge_offset_point[edge_idx] = intersections.pts[0]; + } + done = true; + } + } + if (! done) + edge_candidate[edge_idx] = edge_candidate[edge_idx2] = 0; + } + } + } } - edge_dist[&edge - &vd.edges().front()] = dmin; - } + } - // Mark cells intersected by the offset curve. - std::vector seed_cells(vd.num_cells(), false); - for (const VD::cell_type &cell : vd.cells()) { - const VD::edge_type *first_edge = cell.incident_edge(); - const VD::edge_type *edge = first_edge; - do { - double dmin = edge_dist[edge - &vd.edges().front()]; - double dmax = std::numeric_limits::max(); - const VD::vertex_type *v0 = edge->vertex0(); - const VD::vertex_type *v1 = edge->vertex1(); - if (v0 != nullptr) - dmax = vertex_dist[v0 - &vd.vertices().front()]; - if (v1 != nullptr) - dmax = std::max(dmax, vertex_dist[v1 - &vd.vertices().front()]); - if (offset_distance >= dmin && offset_distance <= dmax) { - // This cell is being intersected by the offset curve. - seed_cells[&cell - &vd.cells().front()] = true; - break; - } - edge = edge->next(); - } while (edge != first_edge); - } - auto edge_dir = [&vd, &vertex_dist, &edge_dist, offset_distance](const VD::edge_type *edge) { - const VD::vertex_type *v0 = edge->vertex0(); - const VD::vertex_type *v1 = edge->vertex1(); - double d0 = v0 ? vertex_dist[v0 - &vd.vertices().front()] : std::numeric_limits::max(); - double d1 = v1 ? vertex_dist[v1 - &vd.vertices().front()] : std::numeric_limits::max(); - if (d0 < offset_distance && offset_distance < d1) - return true; - else if (d1 < offset_distance && offset_distance < d0) - return false; - else { - assert(false); - return false; - } - }; +#ifdef VORONOI_DEBUG_OUT + { + Lines helper_lines; + for (const VD::edge_type &edge : vd.edges()) + if (edge_candidate[&edge - front_edge] == 3) + helper_lines.emplace_back(Line(Point(edge.vertex0()->x(), edge.vertex0()->y()), Point(edge_offset_point[&edge - front_edge].cast()))); + dump_voronoi_to_svg(debug_out_path("voronoi-offset-candidates2.svg").c_str(), vd, Points(), lines, Polygons(), helper_lines); + } +#endif // VORONOI_DEBUG_OUT - /// \brief starting at e, find the next edge on the face that brackets t - /// - /// we can be in one of two modes. - /// if direction==false then we are looking for an edge where src_t < t < trg_t - /// if direction==true we are looning for an edge where trg_t < t < src_t - auto next_offset_edge = - [&vd, &vertex_dist, &edge_dist, offset_distance] - (const VD::edge_type *start_edge, bool direction) -> const VD::edge_type* { - const VD::edge_type *edge = start_edge; - do { - const VD::vertex_type *v0 = edge->vertex0(); - const VD::vertex_type *v1 = edge->vertex1(); - double d0 = v0 ? vertex_dist[v0 - &vd.vertices().front()] : std::numeric_limits::max(); - double d1 = v1 ? vertex_dist[v1 - &vd.vertices().front()] : std::numeric_limits::max(); - if (direction ? (d1 < offset_distance && offset_distance < d0) : (d0 < offset_distance && offset_distance < d1)) - return edge; - edge = edge->next(); - } while (edge != start_edge); + auto next_offset_edge = [&edge_candidate, front_edge](const VD::edge_type *start_edge) -> const VD::edge_type* { + for (const VD::edge_type *edge = start_edge->next(); edge != start_edge; edge = edge->next()) + if (edge_candidate[edge->twin() - front_edge] == 3) + return edge->twin(); assert(false); return nullptr; }; @@ -316,28 +617,20 @@ Polygons voronoi_offset(const VD &vd, const Lines &lines, double offset_distance Polygons out; double angle_step = 2. * acos((offset_distance - discretization_error) / offset_distance); double sin_threshold = sin(angle_step) + EPSILON; - for (size_t seed_cell_idx = 0; seed_cell_idx < vd.num_cells(); ++ seed_cell_idx) - if (seed_cells[seed_cell_idx]) { - seed_cells[seed_cell_idx] = false; - // Initial direction should not matter, an offset curve shall intersect a cell at least at two points - // (if it is not just touching the cell at a single vertex), and such two intersection points shall have - // opposite direction. - bool direction = false; - // the first edge on the start-face - const VD::cell_type &cell = vd.cells()[seed_cell_idx]; - const VD::edge_type *start_edge = next_offset_edge(cell.incident_edge(), direction); - assert(start_edge->cell() == &cell); + for (size_t seed_edge_idx = 0; seed_edge_idx < vd.num_edges(); ++ seed_edge_idx) + if (edge_candidate[seed_edge_idx] == 3) { + const VD::edge_type *start_edge = &vd.edges()[seed_edge_idx]; const VD::edge_type *edge = start_edge; Polygon poly; do { - direction = edge_dir(edge); // find the next edge - const VD::edge_type *next_edge = next_offset_edge(edge->next(), direction); + const VD::edge_type *next_edge = next_offset_edge(edge); //std::cout << "offset-output: "; print_edge(edge); std::cout << " to "; print_edge(next_edge); std::cout << "\n"; // Interpolate a circular segment or insert a linear segment between edge and next_edge. const VD::cell_type *cell = edge->cell(); - Vec2d p1 = detail::voronoi_edge_offset_point(vd, lines, vertex_dist, edge_dist, *edge, offset_distance); - Vec2d p2 = detail::voronoi_edge_offset_point(vd, lines, vertex_dist, edge_dist, *next_edge, offset_distance); + edge_candidate[next_edge - front_edge] = 0; + Vec2d p1 = edge_offset_point[edge - front_edge]; + Vec2d p2 = edge_offset_point[next_edge - front_edge]; #ifndef NDEBUG { double err = dist_to_site(*cell, p1) - offset_distance; @@ -380,9 +673,7 @@ Polygons voronoi_offset(const VD &vd, const Lines &lines, double offset_distance } } poly.points.emplace_back(Point(coord_t(p2.x()), coord_t(p2.y()))); - // although we may revisit current_face (if it is non-convex), it seems safe to mark it "done" here. - seed_cells[cell - &vd.cells().front()] = false; - edge = next_edge->twin(); + edge = next_edge; } while (edge != start_edge); out.emplace_back(std::move(poly)); } diff --git a/src/libslic3r/VoronoiOffset.hpp b/src/libslic3r/VoronoiOffset.hpp index 9f5485c00d..a21b44f93e 100644 --- a/src/libslic3r/VoronoiOffset.hpp +++ b/src/libslic3r/VoronoiOffset.hpp @@ -1,3 +1,5 @@ +// Polygon offsetting using Voronoi diagram prodiced by boost::polygon. + #ifndef slic3r_VoronoiOffset_hpp_ #define slic3r_VoronoiOffset_hpp_ @@ -7,7 +9,16 @@ namespace Slic3r { -Polygons voronoi_offset(const Geometry::VoronoiDiagram &vd, const Lines &lines, double offset_distance, double discretization_error); +// Offset a polygon or a set of polygons possibly with holes by traversing a Voronoi diagram. +// The input polygons are stored in lines and lines are referenced by vd. +// Outer curve will be extracted for a positive offset_distance, +// inner curve will be extracted for a negative offset_distance. +// Circular arches will be discretized to achieve discretization_error. +Polygons voronoi_offset( + const Geometry::VoronoiDiagram &vd, + const Lines &lines, + double offset_distance, + double discretization_error); } // namespace Slic3r diff --git a/src/libslic3r/VoronoiVisualUtils.hpp b/src/libslic3r/VoronoiVisualUtils.hpp new file mode 100644 index 0000000000..186bfb7acf --- /dev/null +++ b/src/libslic3r/VoronoiVisualUtils.hpp @@ -0,0 +1,407 @@ +#include + +#include +#include +#include +#include + +namespace boost { namespace polygon { + +// The following code for the visualization of the boost Voronoi diagram is based on: +// +// Boost.Polygon library voronoi_graphic_utils.hpp header file +// Copyright Andrii Sydorchuk 2010-2012. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +template +class voronoi_visual_utils { + public: + // Discretize parabolic Voronoi edge. + // Parabolic Voronoi edges are always formed by one point and one segment + // from the initial input set. + // + // Args: + // point: input point. + // segment: input segment. + // max_dist: maximum discretization distance. + // discretization: point discretization of the given Voronoi edge. + // + // Template arguments: + // InCT: coordinate type of the input geometries (usually integer). + // Point: point type, should model point concept. + // Segment: segment type, should model segment concept. + // + // Important: + // discretization should contain both edge endpoints initially. + template class Point, + template class Segment> + static + typename enable_if< + typename gtl_and< + typename gtl_if< + typename is_point_concept< + typename geometry_concept< Point >::type + >::type + >::type, + typename gtl_if< + typename is_segment_concept< + typename geometry_concept< Segment >::type + >::type + >::type + >::type, + void + >::type discretize( + const Point& point, + const Segment& segment, + const CT max_dist, + std::vector< Point >* discretization) { + // Apply the linear transformation to move start point of the segment to + // the point with coordinates (0, 0) and the direction of the segment to + // coincide the positive direction of the x-axis. + CT segm_vec_x = cast(x(high(segment))) - cast(x(low(segment))); + CT segm_vec_y = cast(y(high(segment))) - cast(y(low(segment))); + CT sqr_segment_length = segm_vec_x * segm_vec_x + segm_vec_y * segm_vec_y; + + // Compute x-coordinates of the endpoints of the edge + // in the transformed space. + CT projection_start = sqr_segment_length * + get_point_projection((*discretization)[0], segment); + CT projection_end = sqr_segment_length * + get_point_projection((*discretization)[1], segment); + + // Compute parabola parameters in the transformed space. + // Parabola has next representation: + // f(x) = ((x-rot_x)^2 + rot_y^2) / (2.0*rot_y). + CT point_vec_x = cast(x(point)) - cast(x(low(segment))); + CT point_vec_y = cast(y(point)) - cast(y(low(segment))); + CT rot_x = segm_vec_x * point_vec_x + segm_vec_y * point_vec_y; + CT rot_y = segm_vec_x * point_vec_y - segm_vec_y * point_vec_x; + + // Save the last point. + Point last_point = (*discretization)[1]; + discretization->pop_back(); + + // Use stack to avoid recursion. + std::stack point_stack; + point_stack.push(projection_end); + CT cur_x = projection_start; + CT cur_y = parabola_y(cur_x, rot_x, rot_y); + + // Adjust max_dist parameter in the transformed space. + const CT max_dist_transformed = max_dist * max_dist * sqr_segment_length; + while (!point_stack.empty()) { + CT new_x = point_stack.top(); + CT new_y = parabola_y(new_x, rot_x, rot_y); + + // Compute coordinates of the point of the parabola that is + // furthest from the current line segment. + CT mid_x = (new_y - cur_y) / (new_x - cur_x) * rot_y + rot_x; + CT mid_y = parabola_y(mid_x, rot_x, rot_y); + + // Compute maximum distance between the given parabolic arc + // and line segment that discretize it. + CT dist = (new_y - cur_y) * (mid_x - cur_x) - + (new_x - cur_x) * (mid_y - cur_y); + dist = dist * dist / ((new_y - cur_y) * (new_y - cur_y) + + (new_x - cur_x) * (new_x - cur_x)); + if (dist <= max_dist_transformed) { + // Distance between parabola and line segment is less than max_dist. + point_stack.pop(); + CT inter_x = (segm_vec_x * new_x - segm_vec_y * new_y) / + sqr_segment_length + cast(x(low(segment))); + CT inter_y = (segm_vec_x * new_y + segm_vec_y * new_x) / + sqr_segment_length + cast(y(low(segment))); + discretization->push_back(Point(inter_x, inter_y)); + cur_x = new_x; + cur_y = new_y; + } else { + point_stack.push(mid_x); + } + } + + // Update last point. + discretization->back() = last_point; + } + + private: + // Compute y(x) = ((x - a) * (x - a) + b * b) / (2 * b). + static CT parabola_y(CT x, CT a, CT b) { + return ((x - a) * (x - a) + b * b) / (b + b); + } + + // Get normalized length of the distance between: + // 1) point projection onto the segment + // 2) start point of the segment + // Return this length divided by the segment length. This is made to avoid + // sqrt computation during transformation from the initial space to the + // transformed one and vice versa. The assumption is made that projection of + // the point lies between the start-point and endpoint of the segment. + template class Point, + template class Segment> + static + typename enable_if< + typename gtl_and< + typename gtl_if< + typename is_point_concept< + typename geometry_concept< Point >::type + >::type + >::type, + typename gtl_if< + typename is_segment_concept< + typename geometry_concept< Segment >::type + >::type + >::type + >::type, + CT + >::type get_point_projection( + const Point& point, const Segment& segment) { + CT segment_vec_x = cast(x(high(segment))) - cast(x(low(segment))); + CT segment_vec_y = cast(y(high(segment))) - cast(y(low(segment))); + CT point_vec_x = x(point) - cast(x(low(segment))); + CT point_vec_y = y(point) - cast(y(low(segment))); + CT sqr_segment_length = + segment_vec_x * segment_vec_x + segment_vec_y * segment_vec_y; + CT vec_dot = segment_vec_x * point_vec_x + segment_vec_y * point_vec_y; + return vec_dot / sqr_segment_length; + } + + template + static CT cast(const InCT& value) { + return static_cast(value); + } +}; + +} } // namespace boost::polygon + + +namespace Slic3r +{ + +// The following code for the visualization of the boost Voronoi diagram is based on: +// +// Boost.Polygon library voronoi_visualizer.cpp file +// Copyright Andrii Sydorchuk 2010-2012. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +namespace Voronoi { namespace Internal { + + using VD = Geometry::VoronoiDiagram; + typedef double coordinate_type; + typedef boost::polygon::point_data point_type; + typedef boost::polygon::segment_data segment_type; + typedef boost::polygon::rectangle_data rect_type; + typedef VD::cell_type cell_type; + typedef VD::cell_type::source_index_type source_index_type; + typedef VD::cell_type::source_category_type source_category_type; + typedef VD::edge_type edge_type; + typedef VD::cell_container_type cell_container_type; + typedef VD::cell_container_type vertex_container_type; + typedef VD::edge_container_type edge_container_type; + typedef VD::const_cell_iterator const_cell_iterator; + typedef VD::const_vertex_iterator const_vertex_iterator; + typedef VD::const_edge_iterator const_edge_iterator; + + static const std::size_t EXTERNAL_COLOR = 1; + + inline void color_exterior(const VD::edge_type* edge) + { + if (edge->color() == EXTERNAL_COLOR) + return; + edge->color(EXTERNAL_COLOR); + edge->twin()->color(EXTERNAL_COLOR); + const VD::vertex_type* v = edge->vertex1(); + if (v == NULL || !edge->is_primary()) + return; + v->color(EXTERNAL_COLOR); + const VD::edge_type* e = v->incident_edge(); + do { + color_exterior(e); + e = e->rot_next(); + } while (e != v->incident_edge()); + } + + inline point_type retrieve_point(const Points &points, const std::vector &segments, const cell_type& cell) + { + assert(cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT || cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT || + cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT); + return cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT ? + Voronoi::Internal::point_type(double(points[cell.source_index()].x()), double(points[cell.source_index()].y())) : + (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? + low(segments[cell.source_index()]) : high(segments[cell.source_index()]); + } + + inline void clip_infinite_edge(const Points &points, const std::vector &segments, const edge_type& edge, coordinate_type bbox_max_size, std::vector* clipped_edge) + { + const cell_type& cell1 = *edge.cell(); + const cell_type& cell2 = *edge.twin()->cell(); + point_type origin, direction; + // Infinite edges could not be created by two segment sites. + if (! cell1.contains_point() && ! cell2.contains_point()) { + printf("Error! clip_infinite_edge - infinite edge separates two segment cells\n"); + return; + } + if (cell1.contains_point() && cell2.contains_point()) { + point_type p1 = retrieve_point(points, segments, cell1); + point_type p2 = retrieve_point(points, segments, cell2); + origin.x((p1.x() + p2.x()) * 0.5); + origin.y((p1.y() + p2.y()) * 0.5); + direction.x(p1.y() - p2.y()); + direction.y(p2.x() - p1.x()); + } else { + origin = cell1.contains_segment() ? retrieve_point(points, segments, cell2) : retrieve_point(points, segments, cell1); + segment_type segment = cell1.contains_segment() ? segments[cell1.source_index()] : segments[cell2.source_index()]; + coordinate_type dx = high(segment).x() - low(segment).x(); + coordinate_type dy = high(segment).y() - low(segment).y(); + if ((low(segment) == origin) ^ cell1.contains_point()) { + direction.x(dy); + direction.y(-dx); + } else { + direction.x(-dy); + direction.y(dx); + } + } + coordinate_type koef = bbox_max_size / (std::max)(fabs(direction.x()), fabs(direction.y())); + if (edge.vertex0() == NULL) { + clipped_edge->push_back(point_type( + origin.x() - direction.x() * koef, + origin.y() - direction.y() * koef)); + } else { + clipped_edge->push_back( + point_type(edge.vertex0()->x(), edge.vertex0()->y())); + } + if (edge.vertex1() == NULL) { + clipped_edge->push_back(point_type( + origin.x() + direction.x() * koef, + origin.y() + direction.y() * koef)); + } else { + clipped_edge->push_back( + point_type(edge.vertex1()->x(), edge.vertex1()->y())); + } + } + + inline void sample_curved_edge(const Points &points, const std::vector &segments, const edge_type& edge, std::vector &sampled_edge, coordinate_type max_dist) + { + point_type point = edge.cell()->contains_point() ? + retrieve_point(points, segments, *edge.cell()) : + retrieve_point(points, segments, *edge.twin()->cell()); + segment_type segment = edge.cell()->contains_point() ? + segments[edge.twin()->cell()->source_index()] : + segments[edge.cell()->source_index()]; + ::boost::polygon::voronoi_visual_utils::discretize(point, segment, max_dist, &sampled_edge); + } + +} /* namespace Internal */ } // namespace Voronoi + +BoundingBox get_extents(const Lines &lines); + +static inline void dump_voronoi_to_svg( + const char *path, + const Geometry::VoronoiDiagram &vd, + const Points &points, + const Lines &lines, + const Polygons &offset_curves = Polygons(), + const Lines &helper_lines = Lines(), + const double scale = 0.7) // 0.2? +{ + const std::string inputSegmentPointColor = "lightseagreen"; + const coord_t inputSegmentPointRadius = coord_t(0.09 * scale / SCALING_FACTOR); + const std::string inputSegmentColor = "lightseagreen"; + const coord_t inputSegmentLineWidth = coord_t(0.03 * scale / SCALING_FACTOR); + + const std::string voronoiPointColor = "black"; + const coord_t voronoiPointRadius = coord_t(0.06 * scale / SCALING_FACTOR); + const std::string voronoiLineColorPrimary = "black"; + const std::string voronoiLineColorSecondary = "green"; + const std::string voronoiArcColor = "red"; + const coord_t voronoiLineWidth = coord_t(0.02 * scale / SCALING_FACTOR); + + const std::string offsetCurveColor = "magenta"; + const coord_t offsetCurveLineWidth = coord_t(0.09 * scale / SCALING_FACTOR); + + const std::string helperLineColor = "orange"; + const coord_t helperLineWidth = coord_t(0.09 * scale / SCALING_FACTOR); + + const bool internalEdgesOnly = false; + const bool primaryEdgesOnly = false; + + BoundingBox bbox; + bbox.merge(get_extents(points)); + bbox.merge(get_extents(lines)); + bbox.merge(get_extents(offset_curves)); + bbox.min -= (0.01 * bbox.size().cast()).cast(); + bbox.max += (0.01 * bbox.size().cast()).cast(); + + ::Slic3r::SVG svg(path, bbox); + +// bbox.scale(1.2); + // For clipping of half-lines to some reasonable value. + // The line will then be clipped by the SVG viewer anyway. + const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y())); + // For the discretization of the Voronoi parabolic segments. + const double discretization_step = 0.05 * bbox_dim_max; + + // Make a copy of the input segments with the double type. + std::vector segments; + for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++ it) + segments.push_back(Voronoi::Internal::segment_type( + Voronoi::Internal::point_type(double(it->a(0)), double(it->a(1))), + Voronoi::Internal::point_type(double(it->b(0)), double(it->b(1))))); + + // Color exterior edges. + for (boost::polygon::voronoi_diagram::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) + if (!it->is_finite()) + Voronoi::Internal::color_exterior(&(*it)); + + // Draw the end points of the input polygon. + for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) { + svg.draw(it->a, inputSegmentPointColor, inputSegmentPointRadius); + svg.draw(it->b, inputSegmentPointColor, inputSegmentPointRadius); + } + // Draw the input polygon. + for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) + svg.draw(Line(Point(coord_t(it->a(0)), coord_t(it->a(1))), Point(coord_t(it->b(0)), coord_t(it->b(1)))), inputSegmentColor, inputSegmentLineWidth); + +#if 1 + // Draw voronoi vertices. + for (boost::polygon::voronoi_diagram::const_vertex_iterator it = vd.vertices().begin(); it != vd.vertices().end(); ++it) + if (! internalEdgesOnly || it->color() != Voronoi::Internal::EXTERNAL_COLOR) + svg.draw(Point(coord_t(it->x()), coord_t(it->y())), voronoiPointColor, voronoiPointRadius); + + for (boost::polygon::voronoi_diagram::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) { + if (primaryEdgesOnly && !it->is_primary()) + continue; + if (internalEdgesOnly && (it->color() == Voronoi::Internal::EXTERNAL_COLOR)) + continue; + std::vector samples; + std::string color = voronoiLineColorPrimary; + if (!it->is_finite()) { + Voronoi::Internal::clip_infinite_edge(points, segments, *it, bbox_dim_max, &samples); + if (! it->is_primary()) + color = voronoiLineColorSecondary; + } else { + // Store both points of the segment into samples. sample_curved_edge will split the initial line + // until the discretization_step is reached. + samples.push_back(Voronoi::Internal::point_type(it->vertex0()->x(), it->vertex0()->y())); + samples.push_back(Voronoi::Internal::point_type(it->vertex1()->x(), it->vertex1()->y())); + if (it->is_curved()) { + Voronoi::Internal::sample_curved_edge(points, segments, *it, samples, discretization_step); + color = voronoiArcColor; + } else if (! it->is_primary()) + color = voronoiLineColorSecondary; + } + for (std::size_t i = 0; i + 1 < samples.size(); ++i) + svg.draw(Line(Point(coord_t(samples[i].x()), coord_t(samples[i].y())), Point(coord_t(samples[i+1].x()), coord_t(samples[i+1].y()))), color, voronoiLineWidth); + } +#endif + + svg.draw_outline(offset_curves, offsetCurveColor, offsetCurveLineWidth); + svg.draw(helper_lines, helperLineColor, helperLineWidth); + + svg.Close(); +} + +} // namespace Slic3r diff --git a/tests/libslic3r/test_voronoi.cpp b/tests/libslic3r/test_voronoi.cpp index ef05119ad9..6d7211f37d 100644 --- a/tests/libslic3r/test_voronoi.cpp +++ b/tests/libslic3r/test_voronoi.cpp @@ -1,16 +1,18 @@ #include #include -#include - #include #include #include #include + #include -#define BOOST_VORONOI_USE_GMP 1 -#include "boost/polygon/voronoi.hpp" +// #define VORONOI_DEBUG_OUT + +#ifdef VORONOI_DEBUG_OUT +#include +#endif using boost::polygon::voronoi_builder; using boost::polygon::voronoi_diagram; @@ -19,400 +21,6 @@ using namespace Slic3r; using VD = Geometry::VoronoiDiagram; -// #define VORONOI_DEBUG_OUT - -#ifdef VORONOI_DEBUG_OUT -#include -#endif - -#ifdef VORONOI_DEBUG_OUT -namespace boost { namespace polygon { - -// The following code for the visualization of the boost Voronoi diagram is based on: -// -// Boost.Polygon library voronoi_graphic_utils.hpp header file -// Copyright Andrii Sydorchuk 2010-2012. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -template -class voronoi_visual_utils { - public: - // Discretize parabolic Voronoi edge. - // Parabolic Voronoi edges are always formed by one point and one segment - // from the initial input set. - // - // Args: - // point: input point. - // segment: input segment. - // max_dist: maximum discretization distance. - // discretization: point discretization of the given Voronoi edge. - // - // Template arguments: - // InCT: coordinate type of the input geometries (usually integer). - // Point: point type, should model point concept. - // Segment: segment type, should model segment concept. - // - // Important: - // discretization should contain both edge endpoints initially. - template class Point, - template class Segment> - static - typename enable_if< - typename gtl_and< - typename gtl_if< - typename is_point_concept< - typename geometry_concept< Point >::type - >::type - >::type, - typename gtl_if< - typename is_segment_concept< - typename geometry_concept< Segment >::type - >::type - >::type - >::type, - void - >::type discretize( - const Point& point, - const Segment& segment, - const CT max_dist, - std::vector< Point >* discretization) { - // Apply the linear transformation to move start point of the segment to - // the point with coordinates (0, 0) and the direction of the segment to - // coincide the positive direction of the x-axis. - CT segm_vec_x = cast(x(high(segment))) - cast(x(low(segment))); - CT segm_vec_y = cast(y(high(segment))) - cast(y(low(segment))); - CT sqr_segment_length = segm_vec_x * segm_vec_x + segm_vec_y * segm_vec_y; - - // Compute x-coordinates of the endpoints of the edge - // in the transformed space. - CT projection_start = sqr_segment_length * - get_point_projection((*discretization)[0], segment); - CT projection_end = sqr_segment_length * - get_point_projection((*discretization)[1], segment); - - // Compute parabola parameters in the transformed space. - // Parabola has next representation: - // f(x) = ((x-rot_x)^2 + rot_y^2) / (2.0*rot_y). - CT point_vec_x = cast(x(point)) - cast(x(low(segment))); - CT point_vec_y = cast(y(point)) - cast(y(low(segment))); - CT rot_x = segm_vec_x * point_vec_x + segm_vec_y * point_vec_y; - CT rot_y = segm_vec_x * point_vec_y - segm_vec_y * point_vec_x; - - // Save the last point. - Point last_point = (*discretization)[1]; - discretization->pop_back(); - - // Use stack to avoid recursion. - std::stack point_stack; - point_stack.push(projection_end); - CT cur_x = projection_start; - CT cur_y = parabola_y(cur_x, rot_x, rot_y); - - // Adjust max_dist parameter in the transformed space. - const CT max_dist_transformed = max_dist * max_dist * sqr_segment_length; - while (!point_stack.empty()) { - CT new_x = point_stack.top(); - CT new_y = parabola_y(new_x, rot_x, rot_y); - - // Compute coordinates of the point of the parabola that is - // furthest from the current line segment. - CT mid_x = (new_y - cur_y) / (new_x - cur_x) * rot_y + rot_x; - CT mid_y = parabola_y(mid_x, rot_x, rot_y); - - // Compute maximum distance between the given parabolic arc - // and line segment that discretize it. - CT dist = (new_y - cur_y) * (mid_x - cur_x) - - (new_x - cur_x) * (mid_y - cur_y); - dist = dist * dist / ((new_y - cur_y) * (new_y - cur_y) + - (new_x - cur_x) * (new_x - cur_x)); - if (dist <= max_dist_transformed) { - // Distance between parabola and line segment is less than max_dist. - point_stack.pop(); - CT inter_x = (segm_vec_x * new_x - segm_vec_y * new_y) / - sqr_segment_length + cast(x(low(segment))); - CT inter_y = (segm_vec_x * new_y + segm_vec_y * new_x) / - sqr_segment_length + cast(y(low(segment))); - discretization->push_back(Point(inter_x, inter_y)); - cur_x = new_x; - cur_y = new_y; - } else { - point_stack.push(mid_x); - } - } - - // Update last point. - discretization->back() = last_point; - } - - private: - // Compute y(x) = ((x - a) * (x - a) + b * b) / (2 * b). - static CT parabola_y(CT x, CT a, CT b) { - return ((x - a) * (x - a) + b * b) / (b + b); - } - - // Get normalized length of the distance between: - // 1) point projection onto the segment - // 2) start point of the segment - // Return this length divided by the segment length. This is made to avoid - // sqrt computation during transformation from the initial space to the - // transformed one and vice versa. The assumption is made that projection of - // the point lies between the start-point and endpoint of the segment. - template class Point, - template class Segment> - static - typename enable_if< - typename gtl_and< - typename gtl_if< - typename is_point_concept< - typename geometry_concept< Point >::type - >::type - >::type, - typename gtl_if< - typename is_segment_concept< - typename geometry_concept< Segment >::type - >::type - >::type - >::type, - CT - >::type get_point_projection( - const Point& point, const Segment& segment) { - CT segment_vec_x = cast(x(high(segment))) - cast(x(low(segment))); - CT segment_vec_y = cast(y(high(segment))) - cast(y(low(segment))); - CT point_vec_x = x(point) - cast(x(low(segment))); - CT point_vec_y = y(point) - cast(y(low(segment))); - CT sqr_segment_length = - segment_vec_x * segment_vec_x + segment_vec_y * segment_vec_y; - CT vec_dot = segment_vec_x * point_vec_x + segment_vec_y * point_vec_y; - return vec_dot / sqr_segment_length; - } - - template - static CT cast(const InCT& value) { - return static_cast(value); - } -}; - -} } // namespace boost::polygon - -// The following code for the visualization of the boost Voronoi diagram is based on: -// -// Boost.Polygon library voronoi_visualizer.cpp file -// Copyright Andrii Sydorchuk 2010-2012. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -namespace Voronoi { namespace Internal { - - typedef double coordinate_type; - typedef boost::polygon::point_data point_type; - typedef boost::polygon::segment_data segment_type; - typedef boost::polygon::rectangle_data rect_type; - typedef boost::polygon::voronoi_diagram VD; - typedef VD::cell_type cell_type; - typedef VD::cell_type::source_index_type source_index_type; - typedef VD::cell_type::source_category_type source_category_type; - typedef VD::edge_type edge_type; - typedef VD::cell_container_type cell_container_type; - typedef VD::cell_container_type vertex_container_type; - typedef VD::edge_container_type edge_container_type; - typedef VD::const_cell_iterator const_cell_iterator; - typedef VD::const_vertex_iterator const_vertex_iterator; - typedef VD::const_edge_iterator const_edge_iterator; - - static const std::size_t EXTERNAL_COLOR = 1; - - inline void color_exterior(const VD::edge_type* edge) - { - if (edge->color() == EXTERNAL_COLOR) - return; - edge->color(EXTERNAL_COLOR); - edge->twin()->color(EXTERNAL_COLOR); - const VD::vertex_type* v = edge->vertex1(); - if (v == NULL || !edge->is_primary()) - return; - v->color(EXTERNAL_COLOR); - const VD::edge_type* e = v->incident_edge(); - do { - color_exterior(e); - e = e->rot_next(); - } while (e != v->incident_edge()); - } - - inline point_type retrieve_point(const Points &points, const std::vector &segments, const cell_type& cell) - { - assert(cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT || cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT || - cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT); - return cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT ? - Voronoi::Internal::point_type(double(points[cell.source_index()].x()), double(points[cell.source_index()].y())) : - (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? - low(segments[cell.source_index()]) : high(segments[cell.source_index()]); - } - - inline void clip_infinite_edge(const Points &points, const std::vector &segments, const edge_type& edge, coordinate_type bbox_max_size, std::vector* clipped_edge) - { - const cell_type& cell1 = *edge.cell(); - const cell_type& cell2 = *edge.twin()->cell(); - point_type origin, direction; - // Infinite edges could not be created by two segment sites. - if (! cell1.contains_point() && ! cell2.contains_point()) { - printf("Error! clip_infinite_edge - infinite edge separates two segment cells\n"); - return; - } - if (cell1.contains_point() && cell2.contains_point()) { - point_type p1 = retrieve_point(points, segments, cell1); - point_type p2 = retrieve_point(points, segments, cell2); - origin.x((p1.x() + p2.x()) * 0.5); - origin.y((p1.y() + p2.y()) * 0.5); - direction.x(p1.y() - p2.y()); - direction.y(p2.x() - p1.x()); - } else { - origin = cell1.contains_segment() ? retrieve_point(points, segments, cell2) : retrieve_point(points, segments, cell1); - segment_type segment = cell1.contains_segment() ? segments[cell1.source_index()] : segments[cell2.source_index()]; - coordinate_type dx = high(segment).x() - low(segment).x(); - coordinate_type dy = high(segment).y() - low(segment).y(); - if ((low(segment) == origin) ^ cell1.contains_point()) { - direction.x(dy); - direction.y(-dx); - } else { - direction.x(-dy); - direction.y(dx); - } - } - coordinate_type koef = bbox_max_size / (std::max)(fabs(direction.x()), fabs(direction.y())); - if (edge.vertex0() == NULL) { - clipped_edge->push_back(point_type( - origin.x() - direction.x() * koef, - origin.y() - direction.y() * koef)); - } else { - clipped_edge->push_back( - point_type(edge.vertex0()->x(), edge.vertex0()->y())); - } - if (edge.vertex1() == NULL) { - clipped_edge->push_back(point_type( - origin.x() + direction.x() * koef, - origin.y() + direction.y() * koef)); - } else { - clipped_edge->push_back( - point_type(edge.vertex1()->x(), edge.vertex1()->y())); - } - } - - inline void sample_curved_edge(const Points &points, const std::vector &segments, const edge_type& edge, std::vector &sampled_edge, coordinate_type max_dist) - { - point_type point = edge.cell()->contains_point() ? - retrieve_point(points, segments, *edge.cell()) : - retrieve_point(points, segments, *edge.twin()->cell()); - segment_type segment = edge.cell()->contains_point() ? - segments[edge.twin()->cell()->source_index()] : - segments[edge.cell()->source_index()]; - ::boost::polygon::voronoi_visual_utils::discretize(point, segment, max_dist, &sampled_edge); - } - -} /* namespace Internal */ } // namespace Voronoi - -static inline void dump_voronoi_to_svg( - const char *path, - /* const */ VD &vd, - const Points &points, - const Lines &lines, - const Polygons &offset_curves = Polygons(), - const double scale = 0.7) // 0.2? -{ - const std::string inputSegmentPointColor = "lightseagreen"; - const coord_t inputSegmentPointRadius = coord_t(0.09 * scale / SCALING_FACTOR); - const std::string inputSegmentColor = "lightseagreen"; - const coord_t inputSegmentLineWidth = coord_t(0.03 * scale / SCALING_FACTOR); - - const std::string voronoiPointColor = "black"; - const coord_t voronoiPointRadius = coord_t(0.06 * scale / SCALING_FACTOR); - const std::string voronoiLineColorPrimary = "black"; - const std::string voronoiLineColorSecondary = "green"; - const std::string voronoiArcColor = "red"; - const coord_t voronoiLineWidth = coord_t(0.02 * scale / SCALING_FACTOR); - - const std::string offsetCurveColor = "magenta"; - const coord_t offsetCurveLineWidth = coord_t(0.09 * scale / SCALING_FACTOR); - - const bool internalEdgesOnly = false; - const bool primaryEdgesOnly = false; - - BoundingBox bbox; - bbox.merge(get_extents(points)); - bbox.merge(get_extents(lines)); - bbox.min -= (0.01 * bbox.size().cast()).cast(); - bbox.max += (0.01 * bbox.size().cast()).cast(); - - ::Slic3r::SVG svg(path, bbox); - -// bbox.scale(1.2); - // For clipping of half-lines to some reasonable value. - // The line will then be clipped by the SVG viewer anyway. - const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y())); - // For the discretization of the Voronoi parabolic segments. - const double discretization_step = 0.05 * bbox_dim_max; - - // Make a copy of the input segments with the double type. - std::vector segments; - for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++ it) - segments.push_back(Voronoi::Internal::segment_type( - Voronoi::Internal::point_type(double(it->a(0)), double(it->a(1))), - Voronoi::Internal::point_type(double(it->b(0)), double(it->b(1))))); - - // Color exterior edges. - for (boost::polygon::voronoi_diagram::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) - if (!it->is_finite()) - Voronoi::Internal::color_exterior(&(*it)); - - // Draw the end points of the input polygon. - for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) { - svg.draw(it->a, inputSegmentPointColor, inputSegmentPointRadius); - svg.draw(it->b, inputSegmentPointColor, inputSegmentPointRadius); - } - // Draw the input polygon. - for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) - svg.draw(Line(Point(coord_t(it->a(0)), coord_t(it->a(1))), Point(coord_t(it->b(0)), coord_t(it->b(1)))), inputSegmentColor, inputSegmentLineWidth); - -#if 1 - // Draw voronoi vertices. - for (boost::polygon::voronoi_diagram::const_vertex_iterator it = vd.vertices().begin(); it != vd.vertices().end(); ++it) - if (! internalEdgesOnly || it->color() != Voronoi::Internal::EXTERNAL_COLOR) - svg.draw(Point(coord_t(it->x()), coord_t(it->y())), voronoiPointColor, voronoiPointRadius); - - for (boost::polygon::voronoi_diagram::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) { - if (primaryEdgesOnly && !it->is_primary()) - continue; - if (internalEdgesOnly && (it->color() == Voronoi::Internal::EXTERNAL_COLOR)) - continue; - std::vector samples; - std::string color = voronoiLineColorPrimary; - if (!it->is_finite()) { - Voronoi::Internal::clip_infinite_edge(points, segments, *it, bbox_dim_max, &samples); - if (! it->is_primary()) - color = voronoiLineColorSecondary; - } else { - // Store both points of the segment into samples. sample_curved_edge will split the initial line - // until the discretization_step is reached. - samples.push_back(Voronoi::Internal::point_type(it->vertex0()->x(), it->vertex0()->y())); - samples.push_back(Voronoi::Internal::point_type(it->vertex1()->x(), it->vertex1()->y())); - if (it->is_curved()) { - Voronoi::Internal::sample_curved_edge(points, segments, *it, samples, discretization_step); - color = voronoiArcColor; - } else if (! it->is_primary()) - color = voronoiLineColorSecondary; - } - for (std::size_t i = 0; i + 1 < samples.size(); ++i) - svg.draw(Line(Point(coord_t(samples[i].x()), coord_t(samples[i].y())), Point(coord_t(samples[i+1].x()), coord_t(samples[i+1].y()))), color, voronoiLineWidth); - } -#endif - - svg.draw_outline(offset_curves, offsetCurveColor, offsetCurveLineWidth); - svg.Close(); -} -#endif - // https://svn.boost.org/trac10/ticket/12067 // This bug seems to be confirmed. // Vojtech supposes that there may be no Voronoi edges produced for @@ -1586,7 +1194,7 @@ TEST_CASE("Voronoi NaN coordinates 12139", "[Voronoi][!hide][!mayfail]") #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-NaNs.svg").c_str(), - vd, Points(), lines, Polygons(), 0.015); + vd, Points(), lines, Polygons(), Lines(), 0.015); #endif } @@ -1606,12 +1214,19 @@ TEST_CASE("Voronoi offset", "[VoronoiOffset]") Lines lines = to_lines(poly_with_hole); construct_voronoi(lines.begin(), lines.end(), &vd); - Polygons offsetted_polygons = voronoi_offset(vd, lines, scale_(0.2), scale_(0.005)); + Polygons offsetted_polygons_out = voronoi_offset(vd, lines, scale_(0.2), scale_(0.005)); + REQUIRE(offsetted_polygons_out.size() == 1); #ifdef VORONOI_DEBUG_OUT - dump_voronoi_to_svg(debug_out_path("voronoi-offset.svg").c_str(), - vd, Points(), lines, offsetted_polygons); + dump_voronoi_to_svg(debug_out_path("voronoi-offset-out.svg").c_str(), + vd, Points(), lines, offsetted_polygons_out); #endif - REQUIRE(offsetted_polygons.size() == 2); + Polygons offsetted_polygons_in = voronoi_offset(vd, lines, - scale_(0.2), scale_(0.005)); + REQUIRE(offsetted_polygons_in.size() == 1); + +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-offset-in.svg").c_str(), + vd, Points(), lines, offsetted_polygons_in); +#endif } From 04514f009eb0ae3154a080809b8ffdfb7e553250 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 12 Jun 2020 12:35:17 +0200 Subject: [PATCH 05/35] ENABLE_LAYOUT_NO_RESTART -> Refactoring of MainFrame::update_layout() --- src/slic3r/GUI/MainFrame.cpp | 70 ++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index ab7efcf575..52d1f778d2 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -251,19 +251,21 @@ void MainFrame::update_layout() if (m_layout == ESettingsLayout::Old) { m_plater->Reparent(this); m_tabpanel->RemovePage(m_tabpanel->FindPage(m_plater)); + GetSizer()->Hide(m_tabpanel); GetSizer()->Detach(m_tabpanel); - } - else { + } else { + GetSizer()->Hide(m_plater); + GetSizer()->Detach(m_plater); if (m_layout == ESettingsLayout::New) { - GetSizer()->Detach(m_plater); + GetSizer()->Hide(m_tabpanel); GetSizer()->Detach(m_tabpanel); m_tabpanel->DeletePage(m_tabpanel->FindPage(m_plater_page)); - } - else { + m_plater_page = nullptr; + } else { if (m_settings_dialog.IsShown()) m_settings_dialog.Close(); - GetSizer()->Detach(m_plater); + m_settings_dialog.GetSizer()->Hide(m_tabpanel); m_settings_dialog.GetSizer()->Detach(m_tabpanel); m_tabpanel->Reparent(this); } @@ -280,36 +282,35 @@ void MainFrame::update_layout() m_plater->Reparent(m_tabpanel); m_tabpanel->InsertPage(0, m_plater, _L("Plater")); GetSizer()->Add(m_tabpanel, 1, wxEXPAND); - m_tabpanel->Show(); - } - else { + GetSizer()->Show(m_tabpanel); + } else { + GetSizer()->Add(m_plater, 1, wxEXPAND); + GetSizer()->Show(m_plater); if (m_layout == ESettingsLayout::New) { - GetSizer()->Add(m_plater, 1, wxEXPAND); GetSizer()->Add(m_tabpanel, 1, wxEXPAND); + GetSizer()->Hide(m_tabpanel); m_plater_page = new wxPanel(m_tabpanel); m_tabpanel->InsertPage(0, m_plater_page, _L("Plater")); // empty panel just for Plater tab */ - } - else { - GetSizer()->Add(m_plater, 1, wxEXPAND); - m_plater->Show(); + } else { m_tabpanel->Reparent(&m_settings_dialog); m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND); + m_settings_dialog.GetSizer()->Show(m_tabpanel); } } -#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 (m_layout == ESettingsLayout::New) { - wxSize size = wxGetApp().get_min_size(); - size.SetHeight(int(0.5 * size.GetHeight())); - m_plater->SetMinSize(size); - m_tabpanel->SetMinSize(size); - } -#endif +//#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 (m_layout == ESettingsLayout::New) { +// wxSize size = wxGetApp().get_min_size(); +// size.SetHeight(int(0.5 * size.GetHeight())); +// m_plater->SetMinSize(size); +// m_tabpanel->SetMinSize(size); +// } +//#endif Layout(); Thaw(); @@ -455,7 +456,7 @@ void MainFrame::init_tabpanel() Tab* tab = dynamic_cast(panel); // There shouldn't be a case, when we try to select a tab, which doesn't support a printer technology - if (panel == nullptr || (tab && ! tab->supports_printer_technology(m_plater->printer_technology()))) + if (panel == nullptr || (tab != nullptr && !tab->supports_printer_technology(m_plater->printer_technology()))) return; auto& tabs_list = wxGetApp().tabs_list; @@ -1501,8 +1502,13 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) #else else if (m_layout == slNew) { #endif // ENABLE_LAYOUT_NO_RESTART +#if ENABLE_LAYOUT_NO_RESTART + GetSizer()->Show(m_plater, tab == 0); + GetSizer()->Show(m_tabpanel, tab != 0); +#else 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()) @@ -1510,7 +1516,7 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) Layout(); } - // when tab == -1, it means we should to show the last selected tab + // 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 @@ -1721,13 +1727,7 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) SetMinSize(min_size); SetSize(GetMinSize()); #endif -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//#if !ENABLE_LAYOUT_NO_RESTART -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Layout(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//#endif // !ENABLE_LAYOUT_NO_RESTART -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } void SettingsDialog::on_dpi_changed(const wxRect& suggested_rect) From 76d9f0f5091250f8ca2002fdfeb94e4d03c615f0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 15 Jun 2020 09:04:21 +0200 Subject: [PATCH 06/35] Removed unnecessary line --- src/slic3r/GUI/GUI_App.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 0b7d6e1792..5e51cca3c4 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -126,8 +126,6 @@ static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x static void register_win32_device_notification_event() { - enum { WM_DPICHANGED_ = 0x02e0 }; - wxWindow::MSWRegisterMessageHandler(WM_DEVICECHANGE, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only. auto main_frame = dynamic_cast(win); From dcf68aefd75d904e9c2fa372fa4efb299f17a4d4 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 15 Jun 2020 16:20:34 +0200 Subject: [PATCH 07/35] Enable built-in DPI changed event handler when building against wxWidgets 3.1.3 --- src/libslic3r/Technologies.hpp | 3 +++ src/slic3r/GUI/GUI_Utils.hpp | 34 ++++++++++++++++++++++++++++++++-- src/slic3r/GUI/MainFrame.cpp | 4 ++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index e0d534e001..e4b71697d8 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -51,5 +51,8 @@ // Enable error logging for OpenGL calls when SLIC3R_LOGLEVEL >= 5 #define ENABLE_OPENGL_ERROR_LOGGING (1 && ENABLE_2_3_0_ALPHA1) +// 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) + #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index a2f939facb..3c2506b86d 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -24,6 +24,11 @@ class wxCheckBox; class wxTopLevelWindow; class wxRect; +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT +#define wxVERSION_EQUAL_OR_GREATER_THAN(major, minor, release) ((wxMAJOR_VERSION >= major) && (wxMINOR_VERSION >= minor) && (wxRELEASE_NUMBER >= release)) +#else +#define wxVERSION_EQUAL_OR_GREATER_THAN(major, minor, release) 0 +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT namespace Slic3r { namespace GUI { @@ -86,11 +91,29 @@ public: this->SetFont(m_normal_font); #endif // initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window. +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + m_em_unit = std::max(10, 10.0f * m_scale_factor); +#else m_em_unit = std::max(10, this->GetTextExtent("m").x - 1); +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT // recalc_font(); - this->Bind(EVT_DPI_CHANGED_SLICER, [this](const DpiChangedEvent &evt) { +#if wxVERSION_EQUAL_OR_GREATER_THAN(3, 1, 3) + this->Bind(wxEVT_DPI_CHANGED, [this](wxDPIChangedEvent& evt) { + m_scale_factor = (float)evt.GetNewDPI().x / (float)DPI_DEFAULT; + + m_new_font_point_size = get_default_font_for_dpi(evt.GetNewDPI().x).GetPointSize(); + + if (!m_can_rescale) + return; + + if (is_new_scale_factor()) + rescale(wxRect()); + + }); +#else + this->Bind(EVT_DPI_CHANGED_SLICER, [this](const DpiChangedEvent& evt) { m_scale_factor = (float)evt.dpi / (float)DPI_DEFAULT; m_new_font_point_size = get_default_font_for_dpi(evt.dpi).GetPointSize(); @@ -100,7 +123,8 @@ public: if (is_new_scale_factor()) rescale(evt.rect); - }); + }); +#endif // wxMAJOR_VERSION this->Bind(wxEVT_MOVE_START, [this](wxMoveEvent& event) { @@ -192,17 +216,23 @@ private: { this->Freeze(); +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3, 1, 3) // 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); +#endif // wxMAJOR_VERSION // set normal application font as a current window font m_normal_font = this->GetFont(); // update em_unit value for new window font +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + m_em_unit = std::max(10, 10.0f * m_scale_factor); +#else m_em_unit = std::max(10, this->GetTextExtent("m").x - 1); +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT // rescale missed controls sizes and images on_dpi_changed(suggested_rect); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index ef837c200d..d406dc8ad5 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -537,7 +537,11 @@ bool MainFrame::can_reslice() const void MainFrame::on_dpi_changed(const wxRect &suggested_rect) { +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + wxGetApp().update_fonts(this); +#else wxGetApp().update_fonts(); +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT this->SetFont(this->normal_font()); /* Load default preset bitmaps before a tabpanel initialization, From af5c3583e8b844ffa164ef5679bb9c696362fb00 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 16 Jun 2020 08:15:36 +0200 Subject: [PATCH 08/35] Follow-up of dcf68aefd75d904e9c2fa372fa4efb299f17a4d4 - disable old from builds against wxWidgets 3.1.3 --- src/slic3r/GUI/GUI_App.cpp | 4 ++++ src/slic3r/GUI/GUI_Utils.cpp | 2 ++ src/slic3r/GUI/GUI_Utils.hpp | 4 +++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 5e51cca3c4..6517f85a25 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -106,6 +106,7 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension) static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); } #ifdef WIN32 +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) static void register_win32_dpi_event() { enum { WM_DPICHANGED_ = 0x02e0 }; @@ -121,6 +122,7 @@ static void register_win32_dpi_event() return true; }); } +#endif // !ENABLE_WX_3_1_3_DPI_CHANGED_EVENT static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 }; @@ -408,7 +410,9 @@ bool GUI_App::on_init_inner() } #ifdef WIN32 +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) register_win32_dpi_event(); +#endif // !ENABLE_WX_3_1_3_DPI_CHANGED_EVENT register_win32_device_notification_event(); #endif // WIN32 diff --git a/src/slic3r/GUI/GUI_Utils.cpp b/src/slic3r/GUI/GUI_Utils.cpp index 5b23aeee04..7db3d57ffc 100644 --- a/src/slic3r/GUI/GUI_Utils.cpp +++ b/src/slic3r/GUI/GUI_Utils.cpp @@ -61,7 +61,9 @@ void on_window_geometry(wxTopLevelWindow *tlw, std::function callback) #endif } +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) wxDEFINE_EVENT(EVT_DPI_CHANGED_SLICER, DpiChangedEvent); +#endif // !wxVERSION_EQUAL_OR_GREATER_THAN #ifdef _WIN32 template typename F::FN winapi_get_function(const wchar_t *dll, const char *fn_name) { diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 3c2506b86d..55a0feecc0 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -25,7 +25,7 @@ class wxTopLevelWindow; class wxRect; #if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT -#define wxVERSION_EQUAL_OR_GREATER_THAN(major, minor, release) ((wxMAJOR_VERSION >= major) && (wxMINOR_VERSION >= minor) && (wxRELEASE_NUMBER >= release)) +#define wxVERSION_EQUAL_OR_GREATER_THAN(major, minor, release) ((wxMAJOR_VERSION > major) || ((wxMAJOR_VERSION == major) && (wxMINOR_VERSION > minor)) || ((wxMAJOR_VERSION == major) && (wxMINOR_VERSION == minor) && (wxRELEASE_NUMBER >= release))) #else #define wxVERSION_EQUAL_OR_GREATER_THAN(major, minor, release) 0 #endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT @@ -56,6 +56,7 @@ enum { DPI_DEFAULT = 96 }; int get_dpi_for_window(wxWindow *window); wxFont get_default_font_for_dpi(int dpi); +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) struct DpiChangedEvent : public wxEvent { int dpi; wxRect rect; @@ -71,6 +72,7 @@ struct DpiChangedEvent : public wxEvent { }; wxDECLARE_EVENT(EVT_DPI_CHANGED_SLICER, DpiChangedEvent); +#endif // !wxVERSION_EQUAL_OR_GREATER_THAN template class DPIAware : public P { From b101a8e26601c49bfa3a8c62baca9e6810f474b1 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 16 Jun 2020 13:13:51 +0200 Subject: [PATCH 09/35] Fixes of the offset curves from Voronoi diagram. The offset curve extractor is already quite usable, though singular cases are still not covered yet when the offset curve intersects or nearly intersects a Voronoi vertex. Removal of the PRINTF_ZU "%zu" Visual Studio printf compatibility macro. Fixes of a contours self intersection test for collinear segments. SVG exporter now exports white background, so that the GNOME Eye viewer is usable. --- src/libslic3r/BridgeDetector.cpp | 2 +- src/libslic3r/EdgeGrid.cpp | 17 +- src/libslic3r/ExPolygon.cpp | 4 +- src/libslic3r/Format/AMF.cpp | 2 +- src/libslic3r/GCodeSender.cpp | 2 +- src/libslic3r/Geometry.cpp | 2 +- src/libslic3r/Geometry.hpp | 98 +++- src/libslic3r/LayerRegion.cpp | 2 +- src/libslic3r/Polygon.hpp | 8 +- src/libslic3r/PrintObject.cpp | 2 +- src/libslic3r/SVG.cpp | 2 + src/libslic3r/TriangleMesh.cpp | 4 +- src/libslic3r/VoronoiOffset.cpp | 705 +++++++++++++++++---------- src/libslic3r/VoronoiVisualUtils.hpp | 56 ++- src/libslic3r/libslic3r.h | 7 - tests/libslic3r/test_voronoi.cpp | 179 ++++++- 16 files changed, 749 insertions(+), 343 deletions(-) diff --git a/src/libslic3r/BridgeDetector.cpp b/src/libslic3r/BridgeDetector.cpp index ce7c960fa3..bf8907c3fe 100644 --- a/src/libslic3r/BridgeDetector.cpp +++ b/src/libslic3r/BridgeDetector.cpp @@ -53,7 +53,7 @@ void BridgeDetector::initialize() this->_edges = intersection_pl(to_polylines(grown), contours); #ifdef SLIC3R_DEBUG - printf(" bridge has " PRINTF_ZU " support(s)\n", this->_edges.size()); + printf(" bridge has %zu support(s)\n", this->_edges.size()); #endif // detect anchors as intersection between our bridge expolygon and the lower slices diff --git a/src/libslic3r/EdgeGrid.cpp b/src/libslic3r/EdgeGrid.cpp index 2409510538..486a7b1aa8 100644 --- a/src/libslic3r/EdgeGrid.cpp +++ b/src/libslic3r/EdgeGrid.cpp @@ -1586,12 +1586,17 @@ std::vector> ++ cnt; } } - len /= double(cnt); - bbox.offset(20); - EdgeGrid::Grid grid; - grid.set_bbox(bbox); - grid.create(polygons, len); - return grid.intersecting_edges(); + + std::vector> out; + if (cnt > 0) { + len /= double(cnt); + bbox.offset(20); + EdgeGrid::Grid grid; + grid.set_bbox(bbox); + grid.create(polygons, len); + out = grid.intersecting_edges(); + } + return out; } // Find all pairs of intersectiong edges from the set of polygons, highlight them in an SVG. diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 4a89660447..daaab47555 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -404,7 +404,7 @@ void ExPolygon::triangulate_pp(Polygons* polygons) const { TPPLPoly p; p.Init(int(ex->contour.points.size())); - //printf(PRINTF_ZU "\n0\n", ex->contour.points.size()); + //printf("%zu\n0\n", ex->contour.points.size()); for (const Point &point : ex->contour.points) { size_t i = &point - &ex->contour.points.front(); p[i].x = point(0); @@ -419,7 +419,7 @@ void ExPolygon::triangulate_pp(Polygons* polygons) const for (Polygons::const_iterator hole = ex->holes.begin(); hole != ex->holes.end(); ++hole) { TPPLPoly p; p.Init(hole->points.size()); - //printf(PRINTF_ZU "\n1\n", hole->points.size()); + //printf("%zu\n1\n", hole->points.size()); for (const Point &point : hole->points) { size_t i = &point - &hole->points.front(); p[i].x = point(0); diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 41025f043b..af7b9b1b60 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -1218,7 +1218,7 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, for (ModelInstance *instance : object->instances) { char buf[512]; sprintf(buf, - " \n" + " \n" " %lf\n" " %lf\n" " %lf\n" diff --git a/src/libslic3r/GCodeSender.cpp b/src/libslic3r/GCodeSender.cpp index 0988091ce3..9567e07d28 100644 --- a/src/libslic3r/GCodeSender.cpp +++ b/src/libslic3r/GCodeSender.cpp @@ -393,7 +393,7 @@ GCodeSender::on_read(const boost::system::error_code& error, } this->send(); } else { - printf("Cannot resend " PRINTF_ZU " (oldest we have is " PRINTF_ZU ")\n", toresend, this->sent - this->last_sent.size()); + printf("Cannot resend %zu (oldest we have is %zu)\n", toresend, this->sent - this->last_sent.size()); } } else if (boost::starts_with(line, "wait")) { // ignore diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index b9e4d6e78c..00a4ad47c3 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -471,7 +471,7 @@ Pointfs arrange(size_t num_parts, const Vec2d &part_size, coordf_t gap, const Bo size_t cellw = size_t(floor((bed_bbox.size()(0) + gap) / cell_size(0))); size_t cellh = size_t(floor((bed_bbox.size()(1) + gap) / cell_size(1))); if (num_parts > cellw * cellh) - throw std::invalid_argument(PRINTF_ZU " parts won't fit in your print area!\n", num_parts); + throw std::invalid_argument("%zu parts won't fit in your print area!\n", num_parts); // Get a bounding box of cellw x cellh cells, centered at the center of the bed. Vec2d cells_size(cellw * cell_size(0) - gap, cellh * cell_size(1) - gap); diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 87fb0c9c72..75f3708d25 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -115,32 +115,94 @@ inline bool segment_segment_intersection(const Vec2d &p1, const Vec2d &v1, const return true; } - -inline int segments_could_intersect( - const Slic3r::Point &ip1, const Slic3r::Point &ip2, - const Slic3r::Point &jp1, const Slic3r::Point &jp2) -{ - Vec2i64 iv = (ip2 - ip1).cast(); - Vec2i64 vij1 = (jp1 - ip1).cast(); - Vec2i64 vij2 = (jp2 - ip1).cast(); - int64_t tij1 = cross2(iv, vij1); - int64_t tij2 = cross2(iv, vij2); - int sij1 = (tij1 > 0) ? 1 : ((tij1 < 0) ? -1 : 0); // signum - int sij2 = (tij2 > 0) ? 1 : ((tij2 < 0) ? -1 : 0); - return sij1 * sij2; -} - inline bool segments_intersect( const Slic3r::Point &ip1, const Slic3r::Point &ip2, const Slic3r::Point &jp1, const Slic3r::Point &jp2) +{ + assert(ip1 != ip2); + assert(jp1 != jp2); + + auto segments_could_intersect = []( + const Slic3r::Point &ip1, const Slic3r::Point &ip2, + const Slic3r::Point &jp1, const Slic3r::Point &jp2) -> std::pair + { + Vec2i64 iv = (ip2 - ip1).cast(); + Vec2i64 vij1 = (jp1 - ip1).cast(); + Vec2i64 vij2 = (jp2 - ip1).cast(); + int64_t tij1 = cross2(iv, vij1); + int64_t tij2 = cross2(iv, vij2); + return std::make_pair( + // signum + (tij1 > 0) ? 1 : ((tij1 < 0) ? -1 : 0), + (tij2 > 0) ? 1 : ((tij2 < 0) ? -1 : 0)); + }; + + std::pair sign1 = segments_could_intersect(ip1, ip2, jp1, jp2); + std::pair sign2 = segments_could_intersect(jp1, jp2, ip1, ip2); + int test1 = sign1.first * sign1.second; + int test2 = sign2.first * sign2.second; + if (test1 <= 0 && test2 <= 0) { + // The segments possibly intersect. They may also be collinear, but not intersect. + if (test1 != 0 || test2 != 0) + // Certainly not collinear, then the segments intersect. + return true; + // If the first segment is collinear with the other, the other is collinear with the first segment. + assert((sign1.first == 0 && sign1.second == 0) == (sign2.first == 0 && sign2.second == 0)); + if (sign1.first == 0 && sign1.second == 0) { + // The segments are certainly collinear. Now verify whether they overlap. + Slic3r::Point vi = ip2 - ip1; + // Project both on the longer coordinate of vi. + int axis = std::abs(vi.x()) > std::abs(vi.y()) ? 0 : 1; + coord_t i = ip1(axis); + coord_t j = ip2(axis); + coord_t k = jp1(axis); + coord_t l = jp2(axis); + if (i > j) + std::swap(i, j); + if (k > l) + std::swap(k, l); + return (k >= i && k <= j) || (i >= k && i <= l); + } + } + return false; +} + +template inline T foot_pt(const T &line_pt, const T &line_dir, const T &pt) { - return segments_could_intersect(ip1, ip2, jp1, jp2) <= 0 && - segments_could_intersect(jp1, jp2, ip1, ip2) <= 0; + T v = pt - line_pt; + auto l2 = line_dir.squaredNorm(); + auto t = (l2 == 0) ? 0 : v.dot(line_dir) / l2; + return line_pt + line_dir * t; +} + +inline Vec2d foot_pt(const Line &iline, const Point &ipt) +{ + return foot_pt(iline.a.cast(), (iline.b - iline.a).cast(), ipt.cast()); +} + +template inline auto ray_point_distance_squared(const T &ray_pt, const T &ray_dir, const T &pt) +{ + return (foot_pt(ray_pt, ray_dir, pt) - pt).squaredNorm(); +} + +template inline auto ray_point_distance(const T &ray_pt, const T &ray_dir, const T &pt) +{ + return (foot_pt(ray_pt, ray_dir, pt) - pt).norm(); +} + +inline double ray_point_distance_squared(const Line &iline, const Point &ipt) +{ + return (foot_pt(iline, ipt) - ipt.cast()).squaredNorm(); +} + +inline double ray_point_distance(const Line &iline, const Point &ipt) +{ + return (foot_pt(iline, ipt) - ipt.cast()).norm(); } // Based on Liang-Barsky function by Daniel White @ http://www.skytopia.com/project/articles/compsci/clipping.html template -bool liang_barsky_line_clipping( +inline bool liang_barsky_line_clipping( // Start and end points of the source line, result will be stored there as well. Eigen::Matrix &x0, Eigen::Matrix &x1, diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 19907d6de7..5fda69f779 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -264,7 +264,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly this->flow(frInfill, true).scaled_width() ); #ifdef SLIC3R_DEBUG - printf("Processing bridge at layer " PRINTF_ZU ":\n", this->layer()->id()); + printf("Processing bridge at layer %zu:\n", this->layer()->id()); #endif double custom_angle = Geometry::deg2rad(this->region()->config().bridge_angle.value); if (bd.detect_angle(custom_angle)) { diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index c6678e2d83..ab7c171e3c 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -153,9 +153,11 @@ inline Lines to_lines(const Polygon &poly) { Lines lines; lines.reserve(poly.points.size()); - for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it) - lines.push_back(Line(*it, *(it + 1))); - lines.push_back(Line(poly.points.back(), poly.points.front())); + if (poly.points.size() > 2) { + for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it) + lines.push_back(Line(*it, *(it + 1))); + lines.push_back(Line(poly.points.back(), poly.points.front())); + } return lines; } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index a5cb544858..cc39cbf0ae 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1431,7 +1431,7 @@ void PrintObject::bridge_over_infill() } #ifdef SLIC3R_DEBUG - printf("Bridging " PRINTF_ZU " internal areas at layer " PRINTF_ZU "\n", to_bridge.size(), layer->id()); + printf("Bridging %zu internal areas at layer %zu\n", to_bridge.size(), layer->id()); #endif // compute the remaning internal solid surfaces as difference diff --git a/src/libslic3r/SVG.cpp b/src/libslic3r/SVG.cpp index 6e4b973eac..1c1c906c9f 100644 --- a/src/libslic3r/SVG.cpp +++ b/src/libslic3r/SVG.cpp @@ -21,6 +21,7 @@ bool SVG::open(const char* afilename) " \n" " \n" ); + fprintf(this->f, "\n", 2000.f, 2000.f); return true; } @@ -42,6 +43,7 @@ bool SVG::open(const char* afilename, const BoundingBox &bbox, const coord_t bbo " \n" " \n", h, w); + fprintf(this->f, "\n", w, h); return true; } diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index f2deb5cbab..17edf1b5a8 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -952,7 +952,7 @@ void TriangleMeshSlicer::slice(const std::vector &z, SlicingMode mode, co [&layers_p, mode, closing_radius, layers, throw_on_cancel, this](const tbb::blocked_range& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { #ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf("Layer " PRINTF_ZU " (slice_z = %.2f):\n", layer_id, z[layer_id]); + printf("Layer %zu (slice_z = %.2f):\n", layer_id, z[layer_id]); #endif throw_on_cancel(); ExPolygons &expolygons = (*layers)[layer_id]; @@ -1779,7 +1779,7 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, const float clos size_t holes_count = 0; for (ExPolygons::const_iterator e = ex_slices.begin(); e != ex_slices.end(); ++ e) holes_count += e->holes.size(); - printf(PRINTF_ZU " surface(s) having " PRINTF_ZU " holes detected from " PRINTF_ZU " polylines\n", + printf("%zu surface(s) having %zu holes detected from %zu polylines\n", ex_slices.size(), holes_count, loops.size()); #endif diff --git a/src/libslic3r/VoronoiOffset.cpp b/src/libslic3r/VoronoiOffset.cpp index 7c736f19d9..c0541bd9f9 100644 --- a/src/libslic3r/VoronoiOffset.cpp +++ b/src/libslic3r/VoronoiOffset.cpp @@ -15,7 +15,8 @@ namespace Slic3r { using VD = Geometry::VoronoiDiagram; namespace detail { - // Intersect a circle with a ray, return the two parameters + // Intersect a circle with a ray, return the two parameters. + // Currently used for unbounded Voronoi edges only. double first_circle_segment_intersection_parameter( const Vec2d ¢er, const double r, const Vec2d &pt, const Vec2d &v) { @@ -61,70 +62,109 @@ namespace detail { // Return maximum two points, that are at distance "d" from both points Intersections point_point_equal_distance_points(const Point &pt1, const Point &pt2, const double d) { - // input points - const auto cx = double(pt1.x()); - const auto cy = double(pt1.y()); - const auto qx = double(pt2.x()); - const auto qy = double(pt2.y()); - - // Calculating determinant. - auto x0 = 2. * qy; - auto cx2 = cx * cx; - auto cy2 = cy * cy; - auto x5 = 2 * cx * qx; - auto x6 = cy * x0; - auto qx2 = qx * qx; - auto qy2 = qy * qy; - auto x9 = qx2 + qy2; - auto x10 = cx2 + cy2 - x5 - x6 + x9; - auto x11 = - cx2 - cy2; - auto discr = x10 * (4. * d + x11 + x5 + x6 - qx2 - qy2); - if (discr < 0.) + // Calculate the two intersection points. + // With the help of Python package sympy: + // res = solve([(x - cx)**2 + (y - cy)**2 - d**2, x**2 + y**2 - d**2], [x, y]) + // ccode(cse((res[0][0], res[0][1], res[1][0], res[1][1]))) + // where cx, cy is the center of pt1 relative to pt2, + // d is distance from the line and the point (0, 0). + // The result is then shifted to pt2. + auto cx = double(pt1.x() - pt2.x()); + auto cy = double(pt1.y() - pt2.y()); + double cl = cx * cx + cy * cy; + double discr = 4. * d * d - cl; + if (discr < 0.) { // No intersection point found, the two circles are too far away. return Intersections { 0, { Vec2d(), Vec2d() } }; + } + // Avoid division by zero if a gets too small. + bool xy_swapped = std::abs(cx) < std::abs(cy); + if (xy_swapped) + std::swap(cx, cy); + double u; + int cnt; + if (discr == 0.) { + cnt = 1; + u = 0; + } else { + cnt = 2; + u = 0.5 * cx * sqrt(cl * discr) / cl; + } + double v = 0.5 * cy - u; + double w = 2. * cy; + double e = 0.5 / cx; + double f = 0.5 * cy + u; + Intersections out { cnt, { Vec2d(-e * (v * w - cl), v), + Vec2d(-e * (w * f - cl), f) } }; + if (xy_swapped) { + std::swap(out.pts[0].x(), out.pts[0].y()); + std::swap(out.pts[1].x(), out.pts[1].y()); + } + out.pts[0] += pt2.cast(); + out.pts[1] += pt2.cast(); - // Some intersections are found. - int npoints = (discr > 0) ? 2 : 1; - auto x1 = 2. * cy - x0; - auto x2 = cx - qx; - auto x12 = 0.5 * x2 * sqrt(discr) / x10; - auto x13 = 0.5 * (cy + qy); - auto x14 = - x12 + x13; - auto x15 = x11 + x9; - auto x16 = 0.5 / x2; - auto x17 = x12 + x13; - return Intersections { npoints, { Vec2d(- x16 * (x1 * x14 + x15), x14), - Vec2d(- x16 * (x1 * x17 + x15), x17) } }; + assert(std::abs((out.pts[0] - pt1.cast()).norm() - d) < SCALED_EPSILON); + assert(std::abs((out.pts[1] - pt1.cast()).norm() - d) < SCALED_EPSILON); + assert(std::abs((out.pts[0] - pt2.cast()).norm() - d) < SCALED_EPSILON); + assert(std::abs((out.pts[1] - pt2.cast()).norm() - d) < SCALED_EPSILON); + return out; } // Return maximum two points, that are at distance "d" from both the line and point. - Intersections line_point_equal_distance_points(const Line &line, const Point &pt, const double d) + Intersections line_point_equal_distance_points(const Line &line, const Point &ipt, const double d) { - assert(line.a != pt && line.b != pt); + assert(line.a != ipt && line.b != ipt); // Calculating two points of distance "d" to a ray and a point. // Point. - auto x0 = double(pt.x()); - auto y0 = double(pt.y()); - // Ray equation. Vector (a, b) is perpendicular to line. - auto a = double(line.a.y() - line.b.y()); - auto b = double(line.b.x() - line.a.x()); - // pt shall not lie on line. - assert(std::abs((x0 - line.a.x()) * a + (y0 - line.a.y()) * b) < SCALED_EPSILON); - // Orient line so that the vector (a, b) points towards pt. - if (a * (x0 - line.a.x()) + b * (y0 - line.a.y()) < 0.) - std::swap(x0, y0); - double c = - a * double(line.a.x()) - b * double(line.a.y()); - // Calculate the two points. - double a2 = a * a; - double b2 = b * b; - double a2b2 = a2 + b2; - double d2 = d * d; - double s = a2*d2 - a2*sqr(x0) - 2*a*b*x0*y0 - 2*a*c*x0 + 2*a*d*x0 + b2*d2 - b2*sqr(y0) - 2*b*c*y0 + 2*b*d*y0 - sqr(c) + 2*c*d - d2; + Vec2d pt = ipt.cast(); + Vec2d lv = (line.b - line.a).cast(); + double l2 = lv.squaredNorm(); + Vec2d lpv = (line.a - ipt).cast(); + double c = cross2(lpv, lv); + if (c < 0) { + lv = - lv; + c = - c; + } + + // Line equation (ax + by + c - d * sqrt(l2)). + auto a = - lv.y(); + auto b = lv.x(); + // Line point shifted by -ipt is on the line. + assert(std::abs(lpv.x() * a + lpv.y() * b + c) < SCALED_EPSILON); + // Line vector (a, b) points towards ipt. + assert(a * lpv.x() + b * lpv.y() < - SCALED_EPSILON); + +#ifndef NDEBUG + { + // Foot point of ipt on line. + Vec2d ft = Geometry::foot_pt(line, ipt); + // Center point between ipt and line, its distance to both line and ipt is equal. + Vec2d centerpt = 0.5 * (ft + pt) - pt; + double dcenter = 0.5 * (ft - pt).norm(); + // Verify that the center point + assert(std::abs(centerpt.x() * a + centerpt.y() * b + c - dcenter * sqrt(l2)) < SCALED_EPSILON * sqrt(l2)); + } +#endif // NDEBUG + + // Calculate the two intersection points. + // With the help of Python package sympy: + // res = solve([a * x + b * y + c - d * sqrt(a**2 + b**2), x**2 + y**2 - d**2], [x, y]) + // ccode(cse((res[0][0], res[0][1], res[1][0], res[1][1]))) + // where (a, b, c, d) is the line equation, not normalized (vector a,b is not normalized), + // d is distance from the line and the point (0, 0). + // The result is then shifted to ipt. + + double dscaled = d * sqrt(l2); + double s = c * (2. * dscaled - c); if (s < 0.) // Distance of pt from line is bigger than 2 * d. return Intersections { 0 }; double u; int cnt; + // Avoid division by zero if a gets too small. + bool xy_swapped = std::abs(a) < std::abs(b); + if (xy_swapped) + std::swap(a, b); if (s == 0.) { // Distance of pt from line is 2 * d. cnt = 1; @@ -132,110 +172,34 @@ namespace detail { } else { // Distance of pt from line is smaller than 2 * d. cnt = 2; - u = a*sqrt(s)/a2b2; + u = a * sqrt(s) / l2; } - double v = (-a2*y0 + a*b*x0 + b*c - b*d)/a2b2; - return Intersections { cnt, { Vec2d((b * ( u + v) - c + d) / a, - u - v), - Vec2d((b * (- u + v) - c + d) / a, u - v) } }; + double e = dscaled - c; + double f = b * e / l2; + double g = f - u; + double h = f + u; + Intersections out { cnt, { Vec2d((- b * g + e) / a, g), + Vec2d((- b * h + e) / a, h) } }; + if (xy_swapped) { + std::swap(out.pts[0].x(), out.pts[0].y()); + std::swap(out.pts[1].x(), out.pts[1].y()); + } + out.pts[0] += pt; + out.pts[1] += pt; + + assert(std::abs(Geometry::ray_point_distance(line.a.cast(), (line.b - line.a).cast(), out.pts[0]) - d) < SCALED_EPSILON); + assert(std::abs(Geometry::ray_point_distance(line.a.cast(), (line.b - line.a).cast(), out.pts[1]) - d) < SCALED_EPSILON); + assert(std::abs((out.pts[0] - ipt.cast()).norm() - d) < SCALED_EPSILON); + assert(std::abs((out.pts[1] - ipt.cast()).norm() - d) < SCALED_EPSILON); + return out; } - Vec2d voronoi_edge_offset_point( - const VD &vd, - const Lines &lines, - // Distance of a VD vertex to the closest site (input polygon edge or vertex). - const std::vector &vertex_dist, - // Minium distance of a VD edge to the closest site (input polygon edge or vertex). - // For a parabolic segment the distance may be smaller than the distance of the two end points. - const std::vector &edge_dist, - // Edge for which to calculate the offset point. If the distance towards the input polygon - // is not monotonical, pick the offset point closer to edge.vertex0(). - const VD::edge_type &edge, - // Distance from the input polygon along the edge. - const double offset_distance) - { - const VD::vertex_type *v0 = edge.vertex0(); - const VD::vertex_type *v1 = edge.vertex1(); - const VD::cell_type *cell = edge.cell(); - const VD::cell_type *cell2 = edge.twin()->cell(); - const Line &line0 = lines[cell->source_index()]; - const Line &line1 = lines[cell2->source_index()]; - if (v0 == nullptr || v1 == nullptr) { - assert(edge.is_infinite()); - assert(v0 != nullptr || v1 != nullptr); - // Offsetting on an unconstrained edge. - assert(offset_distance > vertex_dist[(v0 ? v0 : v1) - &vd.vertices().front()] - EPSILON); - Vec2d pt, dir; - double t; - if (cell->contains_point() && cell2->contains_point()) { - const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; - const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; - // Direction vector of this unconstrained Voronoi edge. - dir = Vec2d(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x())); - if (v0 == nullptr) { - v0 = v1; - dir = - dir; - } - pt = Vec2d(v0->x(), v0->y()); - t = detail::first_circle_segment_intersection_parameter(Vec2d(pt0.x(), pt0.y()), offset_distance, pt, dir); - } else { - // Infinite edges could not be created by two segment sites. - assert(cell->contains_point() != cell2->contains_point()); - // Linear edge goes through the endpoint of a segment. - assert(edge.is_linear()); - assert(edge.is_secondary()); - const Line &line = cell->contains_segment() ? line0 : line1; - const Point &ipt = cell->contains_segment() ? - ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) : - ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b); - assert(line.a == ipt || line.b == ipt); - pt = Vec2d(ipt.x(), ipt.y()); - dir = Vec2d(line.a.y() - line.b.y(), line.b.x() - line.a.x()); - assert(dir.norm() > 0.); - t = offset_distance / dir.norm(); - if (((line.a == ipt) == cell->contains_point()) == (v0 == nullptr)) - t = - t; - } - return pt + t * dir; - } else { - // Constrained edge. - Vec2d p0(v0->x(), v0->y()); - Vec2d p1(v1->x(), v1->y()); - double d0 = vertex_dist[v0 - &vd.vertices().front()]; - double d1 = vertex_dist[v1 - &vd.vertices().front()]; - if (cell->contains_segment() && cell2->contains_segment()) { - // This edge is a bisector of two line segments. Distance to the input polygon increases/decreases monotonically. - double ddif = d1 - d0; - assert(offset_distance > std::min(d0, d1) - EPSILON && offset_distance < std::max(d0, d1) + EPSILON); - double t = (ddif == 0) ? 0. : clamp(0., 1., (offset_distance - d0) / ddif); - return Slic3r::lerp(p0, p1, t); - } else { - // One cell contains a point, the other contains an edge or a point. - assert(cell->contains_point() || cell2->contains_point()); - const Point &ipt = cell->contains_point() ? - ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) : - ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b); - double t = detail::first_circle_segment_intersection_parameter( - Vec2d(ipt.x(), ipt.y()), offset_distance, p0, p1 - p0); - return Slic3r::lerp(p0, p1, t); - } - } - } -}; - -static Vec2d foot_pt(const Line &iline, const Point &ipt) -{ - Vec2d pt = iline.a.cast(); - Vec2d dir = (iline.b - iline.a).cast(); - Vec2d v = ipt.cast() - pt; - double l2 = dir.squaredNorm(); - double t = (l2 == 0.) ? 0. : v.dot(dir) / l2; - return pt + dir * t; -} +} // namespace detail Polygons voronoi_offset( - const Geometry::VoronoiDiagram &vd, - const Lines &lines, - double offset_distance, + const Geometry::VoronoiDiagram &vd, + const Lines &lines, + double offset_distance, double discretization_error) { #ifndef NDEBUG @@ -259,20 +223,94 @@ Polygons voronoi_offset( } #endif // NDEBUG + enum class EdgeState : unsigned char { + // Initial state, don't know. + Unknown, + // This edge will certainly not be intersected by the offset curve. + Inactive, + // This edge will certainly be intersected by the offset curve. + Active, + // This edge will possibly be intersected by the offset curve. + Possible + }; + + enum class CellState : unsigned char { + // Initial state, don't know. + Unknown, + // Inactive cell is inside for outside curves and outside for inside curves. + Inactive, + // Active cell is outside for outside curves and inside for inside curves. + Active, + // Boundary cell is intersected by the input segment, part of it is active. + Boundary + }; + // Mark edges with outward vertex pointing outside the polygons, thus there is a chance // that such an edge will have an intersection with our desired offset curve. - bool outside = offset_distance > 0.; - std::vector edge_candidate(vd.num_edges(), 2); // unknown state - const VD::edge_type *front_edge = &vd.edges().front(); + bool outside = offset_distance > 0.; + std::vector edge_state(vd.num_edges(), EdgeState::Unknown); + std::vector cell_state(vd.num_cells(), CellState::Unknown); + const VD::edge_type *front_edge = &vd.edges().front(); + const VD::cell_type *front_cell = &vd.cells().front(); + auto set_edge_state_initial = [&edge_state, front_edge](const VD::edge_type *edge, EdgeState new_edge_type) { + EdgeState &edge_type = edge_state[edge - front_edge]; + assert(edge_type == EdgeState::Unknown || edge_type == new_edge_type); + assert(new_edge_type == EdgeState::Possible || new_edge_type == EdgeState::Inactive); + edge_type = new_edge_type; + }; + auto set_edge_state_final = [&edge_state, front_edge](const size_t edge_id, EdgeState new_edge_type) { + EdgeState &edge_type = edge_state[edge_id]; + assert(edge_type == EdgeState::Possible || edge_type == new_edge_type); + assert(new_edge_type == EdgeState::Active || new_edge_type == EdgeState::Inactive); + edge_type = new_edge_type; + }; + auto set_cell_state = [&cell_state, front_cell](const VD::cell_type *cell, CellState new_cell_type) -> bool { + CellState &cell_type = cell_state[cell - front_cell]; + assert(cell_type == CellState::Active || cell_type == CellState::Inactive || cell_type == CellState::Boundary || cell_type == CellState::Unknown); + assert(new_cell_type == CellState::Active || new_cell_type == CellState::Inactive || new_cell_type == CellState::Boundary); + switch (cell_type) { + case CellState::Unknown: + break; + case CellState::Active: + if (new_cell_type == CellState::Inactive) + new_cell_type = CellState::Boundary; + break; + case CellState::Inactive: + if (new_cell_type == CellState::Active) + new_cell_type = CellState::Boundary; + break; + case CellState::Boundary: + return false; + } + if (cell_type != new_cell_type) { + cell_type = new_cell_type; + return true; + } + return false; + }; + for (const VD::edge_type &edge : vd.edges()) if (edge.vertex1() == nullptr) { - // Infinite Voronoi edge separating two Point sites. + // Infinite Voronoi edge separating two Point sites or a Point site and a Segment site. // Infinite edge is always outside and it has at least one valid vertex. assert(edge.vertex0() != nullptr); - edge_candidate[&edge - front_edge] = outside; + set_edge_state_initial(&edge, outside ? EdgeState::Possible : EdgeState::Inactive); // Opposite edge of an infinite edge is certainly not active. - edge_candidate[edge.twin() - front_edge] = 0; - } else if (edge.vertex1() != nullptr) { + set_edge_state_initial(edge.twin(), EdgeState::Inactive); + if (edge.is_secondary()) { + // edge.vertex0() must lie on source contour. + const VD::cell_type *cell = edge.cell(); + const VD::cell_type *cell2 = edge.twin()->cell(); + if (cell->contains_segment()) + std::swap(cell, cell2); + // State of a cell containing a boundary point is known. + assert(cell->contains_point()); + set_cell_state(cell, outside ? CellState::Active : CellState::Inactive); + // State of a cell containing a boundary edge is Boundary. + assert(cell2->contains_segment()); + set_cell_state(cell2, CellState::Boundary); + } + } else if (edge.vertex0() != nullptr) { // Finite edge. const VD::cell_type *cell = edge.cell(); const Line *line = cell->contains_segment() ? &lines[cell->source_index()] : nullptr; @@ -281,38 +319,114 @@ Polygons voronoi_offset( line = cell->contains_segment() ? &lines[cell->source_index()] : nullptr; } if (line) { - const VD::vertex_type *v1 = edge.vertex1(); + const VD::vertex_type *v1 = edge.vertex1(); + const VD::cell_type *cell2 = (cell == edge.cell()) ? edge.twin()->cell() : edge.cell(); assert(v1); + const Point *pt_on_contour = nullptr; + if (cell == edge.cell() && edge.twin()->cell()->contains_segment()) { + // Constrained bisector of two segments. + // If the two segments share a point, then one end of the current Voronoi edge shares this point as well. + // Find pt_on_contour if it exists. + const Line &line2 = lines[cell2->source_index()]; + if (line->a == line2.b) + pt_on_contour = &line->a; + else if (line->b == line2.a) + pt_on_contour = &line->b; + } else if (edge.is_secondary()) { + assert(edge.is_linear()); + // One end of the current Voronoi edge shares a point of a contour. + assert(edge.cell()->contains_point() != edge.twin()->cell()->contains_point()); + const Line &line2 = lines[cell2->source_index()]; + pt_on_contour = &((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line2.a : line2.b); + } + if (pt_on_contour) { + // One end of the current Voronoi edge shares a point of a contour. + // Find out which one it is. + const VD::vertex_type *v0 = edge.vertex0(); + Vec2d vec0(v0->x() - pt_on_contour->x(), v0->y() - pt_on_contour->y()); + Vec2d vec1(v1->x() - pt_on_contour->x(), v1->y() - pt_on_contour->y()); + double d0 = vec0.squaredNorm(); + double d1 = vec1.squaredNorm(); + assert(std::min(d0, d1) < SCALED_EPSILON * SCALED_EPSILON); + if (d0 < d1) { + // v0 is equal to pt. + } else { + // Skip secondary edge pointing to a contour point. + set_edge_state_initial(&edge, EdgeState::Inactive); + continue; + } + } Vec2d l0(line->a.cast()); Vec2d lv((line->b - line->a).cast()); double side = cross2(lv, Vec2d(v1->x(), v1->y()) - l0); - edge_candidate[&edge - front_edge] = outside ? (side < 0.) : (side > 0.); + bool edge_active = outside ? (side < 0.) : (side > 0.); + set_edge_state_initial(&edge, edge_active ? EdgeState::Possible : EdgeState::Inactive); + assert(cell->contains_segment()); + set_cell_state(cell, + pt_on_contour ? CellState::Boundary : + edge_active ? CellState::Active : CellState::Inactive); + set_cell_state(cell2, + (pt_on_contour && cell2->contains_segment()) ? + CellState::Boundary : + edge_active ? CellState::Active : CellState::Inactive); } } - for (const VD::edge_type &edge : vd.edges()) - if (edge_candidate[&edge - front_edge] == 2) { - assert(edge.cell()->contains_point() && edge.twin()->cell()->contains_point()); - // Edge separating two point sources, not yet classified as inside / outside. - const VD::edge_type *e = &edge; - char state; - do { - state = edge_candidate[e - front_edge]; - if (state != 2) - break; - e = e->next(); - } while (e != &edge); - e = &edge; - do { - char &s = edge_candidate[e - front_edge]; - if (s == 2) { - assert(e->cell()->contains_point() && e->twin()->cell()->contains_point()); - assert(edge_candidate[e->twin() - front_edge] == 2); - s = state; - edge_candidate[e->twin() - front_edge] = state; + { + // Perform one round of expansion marking Voronoi edges and cells next to boundary cells as active / inactive. + std::vector cell_queue; + for (const VD::edge_type &edge : vd.edges()) + if (edge_state[&edge - front_edge] == EdgeState::Unknown) { + assert(edge.cell()->contains_point() && edge.twin()->cell()->contains_point()); + // Edge separating two point sources, not yet classified as inside / outside. + CellState cs = cell_state[edge.cell() - front_cell]; + CellState cs2 = cell_state[edge.twin()->cell() - front_cell]; + if (cs != CellState::Unknown || cs2 != CellState::Unknown) { + if (cs == CellState::Unknown) { + cs = cs2; + if (set_cell_state(edge.cell(), cs)) + cell_queue.emplace_back(edge.cell()); + } else if (set_cell_state(edge.twin()->cell(), cs)) + cell_queue.emplace_back(edge.twin()->cell()); + EdgeState es = (cs == CellState::Active) ? EdgeState::Possible : EdgeState::Inactive; + set_edge_state_initial(&edge, es); + set_edge_state_initial(edge.twin(), es); + } else { + const VD::edge_type *e = edge.twin()->rot_prev(); + do { + EdgeState es = edge_state[e->twin() - front_edge]; + if (es != EdgeState::Unknown) { + assert(es == EdgeState::Possible || es == EdgeState::Inactive); + set_edge_state_initial(&edge, es); + CellState cs = (es == EdgeState::Possible) ? CellState::Active : CellState::Inactive; + if (set_cell_state(edge.cell(), cs)) + cell_queue.emplace_back(edge.cell()); + if (set_cell_state(edge.twin()->cell(), cs)) + cell_queue.emplace_back(edge.twin()->cell()); + break; + } + e = e->rot_prev(); + } while (e != edge.twin()); } - e = e->next(); - } while (e != &edge); + } + // Do a final seed fill over Voronoi cells and unmarked Voronoi edges. + while (! cell_queue.empty()) { + const VD::cell_type *cell = cell_queue.back(); + const CellState cs = cell_state[cell - front_cell]; + cell_queue.pop_back(); + const VD::edge_type *first_edge = cell->incident_edge(); + const VD::edge_type *edge = cell->incident_edge(); + EdgeState es = (cs == CellState::Active) ? EdgeState::Possible : EdgeState::Inactive; + do { + if (set_cell_state(edge->twin()->cell(), cs)) { + set_edge_state_initial(edge, es); + set_edge_state_initial(edge->twin(), es); + cell_queue.emplace_back(edge->twin()->cell()); + } + edge = edge->next(); + } while (edge != first_edge); } + } + if (! outside) offset_distance = - offset_distance; @@ -323,10 +437,12 @@ Polygons voronoi_offset( bbox.min -= (0.01 * bbox.size().cast()).cast(); bbox.max += (0.01 * bbox.size().cast()).cast(); } + static int irun = 0; + ++ irun; { Lines helper_lines; for (const VD::edge_type &edge : vd.edges()) - if (edge_candidate[&edge - front_edge]) { + if (edge_state[&edge - front_edge] == EdgeState::Possible) { const VD::vertex_type *v0 = edge.vertex0(); const VD::vertex_type *v1 = edge.vertex1(); assert(v0 != nullptr); @@ -370,16 +486,16 @@ Polygons voronoi_offset( } helper_lines.emplace_back(Line(Point(pt1.cast()), Point(((pt1 + pt2) * 0.5).cast()))); } - dump_voronoi_to_svg(debug_out_path("voronoi-offset-candidates1.svg").c_str(), vd, Points(), lines, Polygons(), helper_lines); + dump_voronoi_to_svg(debug_out_path("voronoi-offset-candidates1-%d.svg", irun).c_str(), vd, Points(), lines, Polygons(), helper_lines); } #endif // VORONOI_DEBUG_OUT std::vector edge_offset_point(vd.num_edges(), Vec2d()); const double offset_distance2 = offset_distance * offset_distance; for (const VD::edge_type &edge : vd.edges()) { - assert(edge_candidate[&edge - front_edge] != 2); + assert(edge_state[&edge - front_edge] != EdgeState::Unknown); size_t edge_idx = &edge - front_edge; - if (edge_candidate[edge_idx] == 1) { + if (edge_state[edge_idx] == EdgeState::Possible) { // Edge candidate, intersection points were not calculated yet. const VD::vertex_type *v0 = edge.vertex0(); const VD::vertex_type *v1 = edge.vertex1(); @@ -391,11 +507,14 @@ Polygons voronoi_offset( size_t edge_idx2 = edge.twin() - front_edge; if (v1 == nullptr) { assert(edge.is_infinite()); - assert(edge_candidate[edge_idx2] == 0); + assert(edge.is_linear()); + assert(edge_state[edge_idx2] == EdgeState::Inactive); if (cell->contains_point() && cell2->contains_point()) { + assert(! edge.is_secondary()); const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; double dmin2 = (Vec2d(v0->x(), v0->y()) - pt0.cast()).squaredNorm(); + assert(dmin2 >= SCALED_EPSILON * SCALED_EPSILON); if (dmin2 <= offset_distance2) { // There shall be an intersection of this unconstrained edge with the offset curve. // Direction vector of this unconstrained Voronoi edge. @@ -403,14 +522,13 @@ Polygons voronoi_offset( Vec2d pt(v0->x(), v0->y()); double t = detail::first_circle_segment_intersection_parameter(Vec2d(pt0.x(), pt0.y()), offset_distance, pt, dir); edge_offset_point[edge_idx] = pt + t * dir; - edge_candidate[edge_idx] = 3; + set_edge_state_final(edge_idx, EdgeState::Active); } else - edge_candidate[edge_idx] = 0; + set_edge_state_final(edge_idx, EdgeState::Inactive); } else { // Infinite edges could not be created by two segment sites. assert(cell->contains_point() != cell2->contains_point()); // Linear edge goes through the endpoint of a segment. - assert(edge.is_linear()); assert(edge.is_secondary()); const Point &ipt = cell->contains_segment() ? ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) : @@ -428,20 +546,15 @@ Polygons voronoi_offset( assert((Vec2d(v0->x(), v0->y()) - ipt.cast()).norm() < SCALED_EPSILON); #endif /* NDEBUG */ // Infinite edge starts at an input contour, therefore there is always an intersection with an offset curve. - const Line &line = cell->contains_segment() ? line0 : line1; + const Line &line = cell->contains_segment() ? line0 : line1; assert(line.a == ipt || line.b == ipt); - Vec2d pt = ipt.cast(); - Vec2d dir(line.a.y() - line.b.y(), line.b.x() - line.a.x()); - assert(dir.norm() > 0.); - double t = offset_distance / dir.norm(); - if (((line.a == ipt) == cell->contains_point()) == (v0 == nullptr)) - t = - t; - edge_offset_point[edge_idx] = pt + t * dir; - edge_candidate[edge_idx] = 3; + edge_offset_point[edge_idx] = ipt.cast() + offset_distance * Vec2d(line.b.y() - line.a.y(), line.a.x() - line.b.x()).normalized(); + set_edge_state_final(edge_idx, EdgeState::Active); } // The other edge of an unconstrained edge starting with null vertex shall never be intersected. - edge_candidate[edge_idx2] = 0; + set_edge_state_final(edge_idx2, EdgeState::Inactive); } else if (edge.is_secondary()) { + assert(edge.is_linear()); assert(cell->contains_point() != cell2->contains_point()); const Line &line0 = lines[edge.cell()->source_index()]; const Line &line1 = lines[edge.twin()->cell()->source_index()]; @@ -455,11 +568,11 @@ Polygons voronoi_offset( double l2 = dir.squaredNorm(); if (offset_distance2 <= l2) { edge_offset_point[edge_idx] = pt.cast() + (offset_distance / sqrt(l2)) * dir; - edge_candidate[edge_idx] = 3; + set_edge_state_final(edge_idx, EdgeState::Active); } else { - edge_candidate[edge_idx] = 0; + set_edge_state_final(edge_idx, EdgeState::Inactive); } - edge_candidate[edge_idx2] = 0; + set_edge_state_final(edge_idx2, EdgeState::Inactive); } else { // Finite edge has valid points at both sides. bool done = false; @@ -492,8 +605,8 @@ Polygons voronoi_offset( } double t = clamp(0., 1., (offset_distance - dmin) / ddif); edge_offset_point[edge_idx] = Vec2d(lerp(v0->x(), v1->x(), t), lerp(v0->y(), v1->y(), t)); - edge_candidate[edge_idx] = 3; - edge_candidate[edge_idx2] = 0; + set_edge_state_final(edge_idx, EdgeState::Active); + set_edge_state_final(edge_idx2, EdgeState::Inactive); done = true; } } @@ -512,23 +625,44 @@ Polygons voronoi_offset( double dmin = std::min(d0, d1); double dmax = std::max(d0, d1); bool has_intersection = false; + bool possibly_two_points = false; if (offset_distance2 <= dmax) { if (offset_distance2 >= dmin) { has_intersection = true; } else { - double dmin_new; + double dmin_new = dmin; if (point_vs_segment) { - Vec2d ft = foot_pt(cell->contains_segment() ? line0 : line1, pt0); - dmin_new = (ft - px).squaredNorm() * 0.25; + // Project on the source segment. + const Line &line = cell->contains_segment() ? line0 : line1; + const Vec2d pt_line = line.a.cast(); + const Vec2d v_line = (line.b - line.a).cast(); + double t0 = (p0 - pt_line).dot(v_line); + double t1 = (p1 - pt_line).dot(v_line); + double tx = (px - pt_line).dot(v_line); + if ((tx >= t0 && tx <= t1) || (tx >= t1 && tx <= t0)) { + // Projection of the Point site falls between the projections of the Voronoi edge end points + // onto the Line site. + Vec2d ft = pt_line + (tx / v_line.squaredNorm()) * v_line; + dmin_new = (ft - px).squaredNorm() * 0.25; + } } else { - // point vs. point - const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; - dmin_new = (pt1.cast() - px).squaredNorm() * 0.25; + // Point-Point Voronoi sites. Project point site onto the current Voronoi edge. + Vec2d v = p1 - p0; + auto l2 = v.squaredNorm(); + assert(l2 > 0); + auto t = v.dot(px - p0); + if (t >= 0. && t <= l2) { + // Projection falls onto the Voronoi edge. Calculate foot point and distance. + Vec2d ft = p0 + (t / l2) * v; + dmin_new = (ft - px).squaredNorm(); + } } assert(dmin_new < dmax + SCALED_EPSILON); assert(dmin_new < dmin + SCALED_EPSILON); - dmin = dmin_new; - has_intersection = offset_distance2 >= dmin; + if (dmin_new < dmin) { + dmin = dmin_new; + has_intersection = possibly_two_points = offset_distance2 >= dmin; + } } } if (has_intersection) { @@ -540,67 +674,90 @@ Polygons voronoi_offset( const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; intersections = detail::point_point_equal_distance_points(pt0, pt1, offset_distance); } + // If the span of distances of start / end point / foot point to the point site indicate an intersection, + // we should find one. + assert(intersections.count > 0); if (intersections.count == 2) { // Now decide which points fall on this Voronoi edge. // Tangential points (single intersection) are ignored. - Vec2d v = p1 - p0; - double l2 = v.squaredNorm(); - double t0 = v.dot(intersections.pts[0] - p0); - double t1 = v.dot(intersections.pts[1] - p0); - if (t0 > t1) { - std::swap(t0, t1); - std::swap(intersections.pts[0], intersections.pts[1]); - } - // Remove points outside of the line range. - if (t0 < 0. || t0 > l2) { - if (t1 < 0. || t1 > l2) - intersections.count = 0; - else { - -- intersections.count; - t0 = t1; - intersections.pts[0] = intersections.pts[1]; + if (possibly_two_points) { + Vec2d v = p1 - p0; + double l2 = v.squaredNorm(); + double t0 = v.dot(intersections.pts[0] - p0); + double t1 = v.dot(intersections.pts[1] - p0); + if (t0 > t1) { + std::swap(t0, t1); + std::swap(intersections.pts[0], intersections.pts[1]); } - } else if (t1 < 0. || t1 > l2) + // Remove points outside of the line range. + if (t0 < 0. || t0 > l2) { + if (t1 < 0. || t1 > l2) + intersections.count = 0; + else { + -- intersections.count; + t0 = t1; + intersections.pts[0] = intersections.pts[1]; + } + } else if (t1 < 0. || t1 > l2) + -- intersections.count; + } else { + // Take the point furthest from the end points of the Voronoi edge or a Voronoi parabolic arc. + double d0 = std::max((intersections.pts[0] - p0).squaredNorm(), (intersections.pts[0] - p1).squaredNorm()); + double d1 = std::max((intersections.pts[1] - p0).squaredNorm(), (intersections.pts[1] - p1).squaredNorm()); + if (d0 > d1) + intersections.pts[0] = intersections.pts[1]; -- intersections.count; + } + assert(intersections.count > 0); if (intersections.count == 2) { - edge_candidate[edge_idx] = edge_candidate[edge_idx2] = 3; - edge_offset_point[edge_idx] = intersections.pts[0]; - edge_offset_point[edge_idx2] = intersections.pts[1]; + set_edge_state_final(edge_idx, EdgeState::Active); + set_edge_state_final(edge_idx2, EdgeState::Active); + edge_offset_point[edge_idx] = intersections.pts[1]; + edge_offset_point[edge_idx2] = intersections.pts[0]; done = true; } else if (intersections.count == 1) { - if (d1 > d0) { + if (d1 < d0) std::swap(edge_idx, edge_idx2); - edge_candidate[edge_idx] = 3; - edge_candidate[edge_idx2] = 0; - edge_offset_point[edge_idx] = intersections.pts[0]; - } + set_edge_state_final(edge_idx, EdgeState::Active); + set_edge_state_final(edge_idx2, EdgeState::Inactive); + edge_offset_point[edge_idx] = intersections.pts[0]; done = true; } } - if (! done) - edge_candidate[edge_idx] = edge_candidate[edge_idx2] = 0; } } + if (! done) { + set_edge_state_final(edge_idx, EdgeState::Inactive); + set_edge_state_final(edge_idx2, EdgeState::Inactive); + } } } } +#ifndef NDEBUG + for (const VD::edge_type &edge : vd.edges()) { + assert(edge_state[&edge - front_edge] == EdgeState::Inactive || edge_state[&edge - front_edge] == EdgeState::Active); + // None of a new edge candidate may start with null vertex. + assert(edge_state[&edge - front_edge] == EdgeState::Inactive || edge.vertex0() != nullptr); + assert(edge_state[edge.twin() - front_edge] == EdgeState::Inactive || edge.twin()->vertex0() != nullptr); + } +#endif // NDEBUG #ifdef VORONOI_DEBUG_OUT { Lines helper_lines; for (const VD::edge_type &edge : vd.edges()) - if (edge_candidate[&edge - front_edge] == 3) + if (edge_state[&edge - front_edge] == EdgeState::Active) helper_lines.emplace_back(Line(Point(edge.vertex0()->x(), edge.vertex0()->y()), Point(edge_offset_point[&edge - front_edge].cast()))); - dump_voronoi_to_svg(debug_out_path("voronoi-offset-candidates2.svg").c_str(), vd, Points(), lines, Polygons(), helper_lines); + dump_voronoi_to_svg(debug_out_path("voronoi-offset-candidates2-%d.svg", irun).c_str(), vd, Points(), lines, Polygons(), helper_lines); } #endif // VORONOI_DEBUG_OUT - auto next_offset_edge = [&edge_candidate, front_edge](const VD::edge_type *start_edge) -> const VD::edge_type* { + auto next_offset_edge = [&edge_state, front_edge](const VD::edge_type *start_edge) -> const VD::edge_type* { for (const VD::edge_type *edge = start_edge->next(); edge != start_edge; edge = edge->next()) - if (edge_candidate[edge->twin() - front_edge] == 3) + if (edge_state[edge->twin() - front_edge] == EdgeState::Active) return edge->twin(); - assert(false); + // assert(false); return nullptr; }; @@ -609,56 +766,66 @@ Polygons voronoi_offset( const Line &line = lines[cell.source_index()]; return cell.contains_point() ? (((cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line.a : line.b).cast() - point).norm() : - line.distance_to(point.cast()); + (Geometry::foot_pt(line.a.cast(), (line.b - line.a).cast(), point) - point).norm(); }; #endif /* NDEBUG */ // Track the offset curves. Polygons out; double angle_step = 2. * acos((offset_distance - discretization_error) / offset_distance); - double sin_threshold = sin(angle_step) + EPSILON; + double cos_threshold = cos(angle_step); for (size_t seed_edge_idx = 0; seed_edge_idx < vd.num_edges(); ++ seed_edge_idx) - if (edge_candidate[seed_edge_idx] == 3) { + if (edge_state[seed_edge_idx] == EdgeState::Active) { const VD::edge_type *start_edge = &vd.edges()[seed_edge_idx]; const VD::edge_type *edge = start_edge; Polygon poly; do { // find the next edge - const VD::edge_type *next_edge = next_offset_edge(edge); + const VD::edge_type *next_edge = next_offset_edge(edge); +#ifdef VORONOI_DEBUG_OUT + if (next_edge == nullptr) { + Lines helper_lines; + dump_voronoi_to_svg(debug_out_path("voronoi-offset-open-loop-%d.svg", irun).c_str(), vd, Points(), lines, Polygons(), to_lines(poly)); + } +#endif // VORONOI_DEBUG_OUT + assert(next_edge); //std::cout << "offset-output: "; print_edge(edge); std::cout << " to "; print_edge(next_edge); std::cout << "\n"; // Interpolate a circular segment or insert a linear segment between edge and next_edge. const VD::cell_type *cell = edge->cell(); - edge_candidate[next_edge - front_edge] = 0; + edge_state[next_edge - front_edge] = EdgeState::Inactive; Vec2d p1 = edge_offset_point[edge - front_edge]; Vec2d p2 = edge_offset_point[next_edge - front_edge]; #ifndef NDEBUG { - double err = dist_to_site(*cell, p1) - offset_distance; - assert(std::abs(err) < SCALED_EPSILON); - err = dist_to_site(*cell, p2) - offset_distance; + double err = dist_to_site(*cell, p1) - offset_distance; + double err2 = dist_to_site(*cell, p2) - offset_distance; +#ifdef VORONOI_DEBUG_OUT + if (std::max(err, err2) >= SCALED_EPSILON) { + Lines helper_lines; + dump_voronoi_to_svg(debug_out_path("voronoi-offset-incorrect_pt-%d.svg", irun).c_str(), vd, Points(), lines, Polygons(), to_lines(poly)); + } +#endif // VORONOI_DEBUG_OUT assert(std::abs(err) < SCALED_EPSILON); + assert(std::abs(err2) < SCALED_EPSILON); } #endif /* NDEBUG */ if (cell->contains_point()) { // Discretize an arc from p1 to p2 with radius = offset_distance and discretization_error. - // The arc should cover angle < PI. - //FIXME we should be able to produce correctly oriented output curves based on the first edge taken! + // The extracted contour is CCW oriented, extracted holes are CW oriented. + // The extracted arc will have the same orientation. As the Voronoi regions are convex, the angle covered by the arc will be convex as well. const Line &line0 = lines[cell->source_index()]; const Vec2d ¢er = ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b).cast(); const Vec2d v1 = p1 - center; const Vec2d v2 = p2 - center; - double orient = cross2(v1, v2); - double orient_norm = v1.norm() * v2.norm(); - bool ccw = orient > 0; - bool obtuse = v1.dot(v2) < 0.; - if (! ccw) - orient = - orient; - assert(orient != 0.); - if (obtuse || orient > orient_norm * sin_threshold) { + bool ccw = cross2(v1, v2) > 0; + double cos_a = v1.dot(v2); + double norm = v1.norm() * v2.norm(); + assert(norm > 0.); + if (cos_a < cos_threshold * norm) { // Angle is bigger than the threshold, therefore the arc will be discretized. - double angle = asin(orient / orient_norm); - if (obtuse) - angle = M_PI - angle; + cos_a /= norm; + assert(cos_a > -1. - EPSILON && cos_a < 1. + EPSILON); + double angle = acos(std::max(-1., std::min(1., cos_a))); size_t n_steps = size_t(ceil(angle / angle_step)); double astep = angle / n_steps; if (! ccw) @@ -670,9 +837,13 @@ Polygons voronoi_offset( Vec2d p = center + Vec2d(c * v1.x() - s * v1.y(), s * v1.x() + c * v1.y()); poly.points.emplace_back(Point(coord_t(p.x()), coord_t(p.y()))); } - } + } } - poly.points.emplace_back(Point(coord_t(p2.x()), coord_t(p2.y()))); + { + Point pt_last(coord_t(p2.x()), coord_t(p2.y())); + if (poly.empty() || poly.points.back() != pt_last) + poly.points.emplace_back(pt_last); + } edge = next_edge; } while (edge != start_edge); out.emplace_back(std::move(poly)); diff --git a/src/libslic3r/VoronoiVisualUtils.hpp b/src/libslic3r/VoronoiVisualUtils.hpp index 186bfb7acf..fa6a342418 100644 --- a/src/libslic3r/VoronoiVisualUtils.hpp +++ b/src/libslic3r/VoronoiVisualUtils.hpp @@ -305,44 +305,52 @@ static inline void dump_voronoi_to_svg( const Lines &lines, const Polygons &offset_curves = Polygons(), const Lines &helper_lines = Lines(), - const double scale = 0.7) // 0.2? + double scale = 0) { - const std::string inputSegmentPointColor = "lightseagreen"; - const coord_t inputSegmentPointRadius = coord_t(0.09 * scale / SCALING_FACTOR); - const std::string inputSegmentColor = "lightseagreen"; - const coord_t inputSegmentLineWidth = coord_t(0.03 * scale / SCALING_FACTOR); - - const std::string voronoiPointColor = "black"; - const coord_t voronoiPointRadius = coord_t(0.06 * scale / SCALING_FACTOR); - const std::string voronoiLineColorPrimary = "black"; - const std::string voronoiLineColorSecondary = "green"; - const std::string voronoiArcColor = "red"; - const coord_t voronoiLineWidth = coord_t(0.02 * scale / SCALING_FACTOR); - - const std::string offsetCurveColor = "magenta"; - const coord_t offsetCurveLineWidth = coord_t(0.09 * scale / SCALING_FACTOR); - - const std::string helperLineColor = "orange"; - const coord_t helperLineWidth = coord_t(0.09 * scale / SCALING_FACTOR); - - const bool internalEdgesOnly = false; - const bool primaryEdgesOnly = false; - BoundingBox bbox; bbox.merge(get_extents(points)); bbox.merge(get_extents(lines)); bbox.merge(get_extents(offset_curves)); + bbox.merge(get_extents(helper_lines)); bbox.min -= (0.01 * bbox.size().cast()).cast(); bbox.max += (0.01 * bbox.size().cast()).cast(); + if (scale == 0) + scale = +// 0.1 + 0.01 + * std::min(bbox.size().x(), bbox.size().y()); + else + scale /= SCALING_FACTOR; + + const std::string inputSegmentPointColor = "lightseagreen"; + const coord_t inputSegmentPointRadius = coord_t(0.09 * scale); + const std::string inputSegmentColor = "lightseagreen"; + const coord_t inputSegmentLineWidth = coord_t(0.03 * scale); + + const std::string voronoiPointColor = "black"; + const coord_t voronoiPointRadius = coord_t(0.06 * scale); + const std::string voronoiLineColorPrimary = "black"; + const std::string voronoiLineColorSecondary = "green"; + const std::string voronoiArcColor = "red"; + const coord_t voronoiLineWidth = coord_t(0.02 * scale); + + const std::string offsetCurveColor = "magenta"; + const coord_t offsetCurveLineWidth = coord_t(0.02 * scale); + + const std::string helperLineColor = "orange"; + const coord_t helperLineWidth = coord_t(0.04 * scale); + + const bool internalEdgesOnly = false; + const bool primaryEdgesOnly = false; + ::Slic3r::SVG svg(path, bbox); -// bbox.scale(1.2); // For clipping of half-lines to some reasonable value. // The line will then be clipped by the SVG viewer anyway. const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y())); // For the discretization of the Voronoi parabolic segments. - const double discretization_step = 0.05 * bbox_dim_max; + const double discretization_step = 0.0002 * bbox_dim_max; // Make a copy of the input segments with the double type. std::vector segments; diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index db375ec14f..2f7932a4e4 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -73,13 +73,6 @@ inline std::string debug_out_path(const char *name, ...) return std::string(SLIC3R_DEBUG_OUT_PATH_PREFIX) + std::string(buffer); } -#ifdef _MSC_VER - // Visual Studio older than 2015 does not support the prinf type specifier %zu. Use %Iu instead. - #define PRINTF_ZU "%Iu" -#else - #define PRINTF_ZU "%zu" -#endif - #ifndef UNUSED #define UNUSED(x) (void)(x) #endif /* UNUSED */ diff --git a/tests/libslic3r/test_voronoi.cpp b/tests/libslic3r/test_voronoi.cpp index 6d7211f37d..42d4a14bb1 100644 --- a/tests/libslic3r/test_voronoi.cpp +++ b/tests/libslic3r/test_voronoi.cpp @@ -1198,6 +1198,12 @@ TEST_CASE("Voronoi NaN coordinates 12139", "[Voronoi][!hide][!mayfail]") #endif } +struct OffsetTest { + double distance; + size_t num_outer; + size_t num_inner; +}; + TEST_CASE("Voronoi offset", "[VoronoiOffset]") { Polygons poly_with_hole = { Polygon { @@ -1210,23 +1216,180 @@ TEST_CASE("Voronoi offset", "[VoronoiOffset]") } }; + double area = std::accumulate(poly_with_hole.begin(), poly_with_hole.end(), 0., [](double a, auto &poly){ return a + poly.area(); }); + REQUIRE(area > 0.); + VD vd; Lines lines = to_lines(poly_with_hole); construct_voronoi(lines.begin(), lines.end(), &vd); - Polygons offsetted_polygons_out = voronoi_offset(vd, lines, scale_(0.2), scale_(0.005)); - REQUIRE(offsetted_polygons_out.size() == 1); + for (const OffsetTest &ot : { + OffsetTest { scale_(0.2), 1, 1 }, + OffsetTest { scale_(0.4), 1, 1 }, + OffsetTest { scale_(0.5), 1, 1 }, + OffsetTest { scale_(0.505), 1, 2 }, + OffsetTest { scale_(0.51), 1, 2 }, + OffsetTest { scale_(0.52), 1, 1 }, + OffsetTest { scale_(0.53), 1, 1 }, + OffsetTest { scale_(0.54), 1, 1 }, + OffsetTest { scale_(0.55), 1, 0 } + }) { + + Polygons offsetted_polygons_out = voronoi_offset(vd, lines, ot.distance, scale_(0.005)); + REQUIRE(offsetted_polygons_out.size() == ot.num_outer); #ifdef VORONOI_DEBUG_OUT - dump_voronoi_to_svg(debug_out_path("voronoi-offset-out.svg").c_str(), - vd, Points(), lines, offsetted_polygons_out); + dump_voronoi_to_svg(debug_out_path("voronoi-offset-out-%lf.svg", ot.distance).c_str(), + vd, Points(), lines, offsetted_polygons_out); #endif - Polygons offsetted_polygons_in = voronoi_offset(vd, lines, - scale_(0.2), scale_(0.005)); - REQUIRE(offsetted_polygons_in.size() == 1); + Polygons offsetted_polygons_in = voronoi_offset(vd, lines, - ot.distance, scale_(0.005)); + REQUIRE(offsetted_polygons_in.size() == ot.num_inner); #ifdef VORONOI_DEBUG_OUT - dump_voronoi_to_svg(debug_out_path("voronoi-offset-in.svg").c_str(), - vd, Points(), lines, offsetted_polygons_in); + dump_voronoi_to_svg(debug_out_path("voronoi-offset-in-%lf.svg", ot.distance).c_str(), + vd, Points(), lines, offsetted_polygons_in); #endif + } +} + +TEST_CASE("Voronoi offset 2", "[VoronoiOffset]") +{ + coord_t mm = coord_t(scale_(1.)); + Polygons poly = { + Polygon { + { 0, 0 }, + { 1, 0 }, + { 1, 1 }, + { 2, 1 }, + { 2, 0 }, + { 3, 0 }, + { 3, 2 }, + { 0, 2 } + }, + Polygon { + { 0, - 1 - 2 }, + { 3, - 1 - 2 }, + { 3, - 1 - 0 }, + { 2, - 1 - 0 }, + { 2, - 1 - 1 }, + { 1, - 1 - 1 }, + { 1, - 1 - 0 }, + { 0, - 1 - 0 } + }, + }; + for (Polygon &p : poly) + for (Point &pt : p.points) + pt *= mm; + + double area = std::accumulate(poly.begin(), poly.end(), 0., [](double a, auto &poly){ return a + poly.area(); }); + REQUIRE(area > 0.); + + VD vd; + Lines lines = to_lines(poly); + construct_voronoi(lines.begin(), lines.end(), &vd); + + for (const OffsetTest &ot : { + OffsetTest { scale_(0.2), 2, 2 }, + OffsetTest { scale_(0.4), 2, 2 }, + OffsetTest { scale_(0.45), 2, 2 }, + OffsetTest { scale_(0.48), 2, 2 }, +//FIXME Exact intersections of an Offset curve with any Voronoi vertex are not handled correctly yet. +// OffsetTest { scale_(0.5), 2, 2 }, + OffsetTest { scale_(0.505), 2, 4 }, + OffsetTest { scale_(0.7), 2, 0 }, + OffsetTest { scale_(0.8), 1, 0 } + }) { + + Polygons offsetted_polygons_out = voronoi_offset(vd, lines, ot.distance, scale_(0.005)); +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-offset2-out-%lf.svg", ot.distance).c_str(), + vd, Points(), lines, offsetted_polygons_out); +#endif + REQUIRE(offsetted_polygons_out.size() == ot.num_outer); + + Polygons offsetted_polygons_in = voronoi_offset(vd, lines, - ot.distance, scale_(0.005)); +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-offset2-in-%lf.svg", ot.distance).c_str(), + vd, Points(), lines, offsetted_polygons_in); +#endif + REQUIRE(offsetted_polygons_in.size() == ot.num_inner); + } +} + +TEST_CASE("Voronoi offset 3", "[VoronoiOffset]") +{ + coord_t mm = coord_t(scale_(1.)); + Polygons poly = { + Polygon { + { 0, 0 }, + { 2, 0 }, + { 2, 1 }, + { 3, 1 }, + { 3, 0 }, + { 5, 0 }, + { 5, 2 }, + { 4, 2 }, + { 4, 3 }, + { 1, 3 }, + { 1, 2 }, + { 0, 2 } + }, + Polygon { + { 0, -1 - 2 }, + { 1, -1 - 2 }, + { 1, -1 - 3 }, + { 4, -1 - 3 }, + { 4, -1 - 2 }, + { 5, -1 - 2 }, + { 5, -1 - 0 }, + { 3, -1 - 0 }, + { 3, -1 - 1 }, + { 2, -1 - 1 }, + { 2, -1 - 0 }, + { 0, -1 - 0 } + }, + }; + for (Polygon &p : poly) { + REQUIRE(p.area() > 0.); + for (Point &pt : p.points) + pt *= mm; + } + + VD vd; + Lines lines = to_lines(poly); + construct_voronoi(lines.begin(), lines.end(), &vd); + + for (const OffsetTest &ot : { + OffsetTest { scale_(0.2), 2, 2 }, + OffsetTest { scale_(0.4), 2, 2 }, + OffsetTest { scale_(0.49), 2, 2 }, +//FIXME this fails +// OffsetTest { scale_(0.5), 2, 2 }, + OffsetTest { scale_(0.51), 2, 2 }, + OffsetTest { scale_(0.56), 2, 2 }, + OffsetTest { scale_(0.6), 2, 2 }, + OffsetTest { scale_(0.7), 2, 2 }, + OffsetTest { scale_(0.8), 1, 6 }, + OffsetTest { scale_(0.9), 1, 6 }, + OffsetTest { scale_(0.99), 1, 6 }, +//FIXME this fails +// OffsetTest { scale_(1.0), 1, 6 }, + OffsetTest { scale_(1.01), 1, 0 }, + }) { + + Polygons offsetted_polygons_out = voronoi_offset(vd, lines, ot.distance, scale_(0.005)); +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-offset2-out-%lf.svg", ot.distance).c_str(), + vd, Points(), lines, offsetted_polygons_out); +#endif + REQUIRE(offsetted_polygons_out.size() == ot.num_outer); + + Polygons offsetted_polygons_in = voronoi_offset(vd, lines, - ot.distance, scale_(0.005)); +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-offset2-in-%lf.svg", ot.distance).c_str(), + vd, Points(), lines, offsetted_polygons_in); +#endif + REQUIRE(offsetted_polygons_in.size() == ot.num_inner); + } } From 851ebc7991d2fa85c159f522dad7367898de3e79 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 16 Jun 2020 14:00:25 +0200 Subject: [PATCH 10/35] fix of previous commit, missing include --- tests/libslic3r/test_voronoi.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/libslic3r/test_voronoi.cpp b/tests/libslic3r/test_voronoi.cpp index 42d4a14bb1..ba318e4fd6 100644 --- a/tests/libslic3r/test_voronoi.cpp +++ b/tests/libslic3r/test_voronoi.cpp @@ -8,6 +8,8 @@ #include +#include + // #define VORONOI_DEBUG_OUT #ifdef VORONOI_DEBUG_OUT From 81b809f2a178079ffc03a89a6f0e4e9ece5d9e56 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 16 Jun 2020 14:39:08 +0200 Subject: [PATCH 11/35] Fixed broken compilation of Perl bindings. --- src/libslic3r/libslic3r.h | 2 +- xs/xsp/Geometry.xsp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 2f7932a4e4..e3816b87f9 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -26,7 +26,7 @@ // Saves around 32% RAM after slicing step, 6.7% after G-code export (tested on PrusaSlicer 2.2.0 final). using coord_t = int32_t; #else -//FIXME At least FillRectilinear2 requires coord_t to be 32bit. +//FIXME At least FillRectilinear2 and std::boost Voronoi require coord_t to be 32bit. typedef int64_t coord_t; #endif diff --git a/xs/xsp/Geometry.xsp b/xs/xsp/Geometry.xsp index 5d6454e8a8..e44d169493 100644 --- a/xs/xsp/Geometry.xsp +++ b/xs/xsp/Geometry.xsp @@ -13,7 +13,7 @@ Pointfs arrange(size_t total_parts, Vec2d* part, coordf_t dist, BoundingBoxf* bb %code{% Pointfs points; if (! Slic3r::Geometry::arrange(total_parts, *part, dist, bb, points)) - CONFESS(PRINTF_ZU " parts won't fit in your print area!\n", total_parts); + CONFESS("%zu parts won't fit in your print area!\n", total_parts); RETVAL = points; %}; From cfb552d496ec9344833648214d83aaec6bb4d640 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 16 Jun 2020 15:16:28 +0200 Subject: [PATCH 12/35] ENABLE_LAYOUT_NO_RESTART -> Another refactoring of MainFrame::update_layout() --- src/slic3r/GUI/MainFrame.cpp | 105 ++++++++++++++++++++++------------- 1 file changed, 66 insertions(+), 39 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index d85d9feee1..23287eb699 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -235,6 +235,41 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S #if ENABLE_LAYOUT_NO_RESTART void MainFrame::update_layout() { + auto restore_to_creation = [this]() { + auto clean_sizer = [](wxSizer* sizer) { + while (!sizer->GetChildren().IsEmpty()) { + sizer->Detach(0); + } + }; + + if (m_plater->GetParent() != this) + m_plater->Reparent(this); + + if (m_tabpanel->GetParent() != this) + m_tabpanel->Reparent(this); + + int plater_page_id = m_tabpanel->FindPage(m_plater); + if (plater_page_id != wxNOT_FOUND) + m_tabpanel->RemovePage(plater_page_id); + + plater_page_id = (m_plater_page != nullptr) ? m_tabpanel->FindPage(m_plater_page) : wxNOT_FOUND; + if (plater_page_id != wxNOT_FOUND) { + m_tabpanel->DeletePage(plater_page_id); + m_plater_page = nullptr; + } + + clean_sizer(GetSizer()); + clean_sizer(m_settings_dialog.GetSizer()); + + if (m_settings_dialog.IsShown()) + m_settings_dialog.Close(); + + m_tabpanel->Hide(); + m_plater->Hide(); + + Layout(); + }; + ESettingsLayout layout = wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old : wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New : wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? ESettingsLayout::Dlg : ESettingsLayout::Old; @@ -246,31 +281,9 @@ void MainFrame::update_layout() Freeze(); - if (m_layout != ESettingsLayout::Unknown) { - // Restore from previous settings - if (m_layout == ESettingsLayout::Old) { - m_plater->Reparent(this); - m_tabpanel->RemovePage(m_tabpanel->FindPage(m_plater)); - GetSizer()->Hide(m_tabpanel); - GetSizer()->Detach(m_tabpanel); - } else { - GetSizer()->Hide(m_plater); - GetSizer()->Detach(m_plater); - if (m_layout == ESettingsLayout::New) { - GetSizer()->Hide(m_tabpanel); - GetSizer()->Detach(m_tabpanel); - m_tabpanel->DeletePage(m_tabpanel->FindPage(m_plater_page)); - m_plater_page = nullptr; - } else { - if (m_settings_dialog.IsShown()) - m_settings_dialog.Close(); - - m_settings_dialog.GetSizer()->Hide(m_tabpanel); - m_settings_dialog.GetSizer()->Detach(m_tabpanel); - m_tabpanel->Reparent(this); - } - } - } + // Remove old settings + if (m_layout != ESettingsLayout::Unknown) + restore_to_creation(); m_layout = layout; @@ -278,24 +291,36 @@ void MainFrame::update_layout() m_last_selected_tab = m_layout == ESettingsLayout::Dlg ? 0 : 1; // Set new settings - if (m_layout == ESettingsLayout::Old) { + switch (m_layout) + { + case ESettingsLayout::Old: + { m_plater->Reparent(m_tabpanel); m_tabpanel->InsertPage(0, m_plater, _L("Plater")); GetSizer()->Add(m_tabpanel, 1, wxEXPAND); - GetSizer()->Show(m_tabpanel); - } else { + m_plater->Show(); + m_tabpanel->Show(); + break; + } + case ESettingsLayout::New: + { GetSizer()->Add(m_plater, 1, wxEXPAND); - GetSizer()->Show(m_plater); - if (m_layout == ESettingsLayout::New) { - GetSizer()->Add(m_tabpanel, 1, wxEXPAND); - GetSizer()->Hide(m_tabpanel); - m_plater_page = new wxPanel(m_tabpanel); - m_tabpanel->InsertPage(0, m_plater_page, _L("Plater")); // empty panel just for Plater tab */ - } else { - m_tabpanel->Reparent(&m_settings_dialog); - m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND); - m_settings_dialog.GetSizer()->Show(m_tabpanel); - } + m_tabpanel->Hide(); + GetSizer()->Add(m_tabpanel, 1, wxEXPAND); + m_plater_page = new wxPanel(m_tabpanel); + m_tabpanel->InsertPage(0, m_plater_page, _L("Plater")); // empty panel just for Plater tab */ + m_plater->Show(); + break; + } + case ESettingsLayout::Dlg: + { + GetSizer()->Add(m_plater, 1, wxEXPAND); + m_tabpanel->Reparent(&m_settings_dialog); + m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND); + m_tabpanel->Show(); + m_plater->Show(); + break; + } } //#ifdef __APPLE__ @@ -428,6 +453,7 @@ void MainFrame::init_tabpanel() #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->Hide(); m_settings_dialog.set_tabpanel(m_tabpanel); #else m_layout = wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? slOld : @@ -472,6 +498,7 @@ void MainFrame::init_tabpanel() #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); From e8736c47ae341335c7c5ace8e45d2bb7a9d13bdb Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 16 Jun 2020 16:11:31 +0200 Subject: [PATCH 13/35] Bugfix: incorrect scaling of clipping plane cuts in SLA support gizmo --- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 051e9cf880..28f317c269 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -448,6 +448,7 @@ void SupportsClipper::render_cut() const // Get transformation of supports Geometry::Transformation supports_trafo = trafo; + supports_trafo.set_scaling_factor(Vec3d::Ones()); supports_trafo.set_offset(Vec3d(trafo.get_offset()(0), trafo.get_offset()(1), sel_info->get_sla_shift())); supports_trafo.set_rotation(Vec3d(0., 0., trafo.get_rotation()(2))); // I don't know why, but following seems to be correct. From 3383650c00e751e63dee52814e8bf47cd1e9c2e4 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 17 Jun 2020 09:34:33 +0200 Subject: [PATCH 14/35] ENABLE_LAYOUT_NO_RESTART -> Fixed MainFrame::update_layout() for Linux build --- src/slic3r/GUI/MainFrame.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 23287eb699..5f7f5a81fd 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -242,16 +242,17 @@ void MainFrame::update_layout() } }; + // On Linux m_plater needs to be removed from m_tabpanel before to reparent it + int plater_page_id = m_tabpanel->FindPage(m_plater); + if (plater_page_id != wxNOT_FOUND) + m_tabpanel->RemovePage(plater_page_id); + if (m_plater->GetParent() != this) m_plater->Reparent(this); if (m_tabpanel->GetParent() != this) m_tabpanel->Reparent(this); - int plater_page_id = m_tabpanel->FindPage(m_plater); - if (plater_page_id != wxNOT_FOUND) - m_tabpanel->RemovePage(plater_page_id); - plater_page_id = (m_plater_page != nullptr) ? m_tabpanel->FindPage(m_plater_page) : wxNOT_FOUND; if (plater_page_id != wxNOT_FOUND) { m_tabpanel->DeletePage(plater_page_id); From 9608103d58798b7b54b0e0cbd3d1a7af8c8872ec Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 19 Jun 2020 11:04:08 +0200 Subject: [PATCH 15/35] ENABLE_LAYOUT_NO_RESTART -> Fixed font scaling when switching to/from non modal setting dialog layout when building against wxWidgets prior to 3.1.3 --- src/slic3r/GUI/GUI_App.cpp | 9 ++++--- src/slic3r/GUI/GUI_Utils.hpp | 33 +++++++++++++++++++------ src/slic3r/GUI/MainFrame.cpp | 48 +++++++++++++++++++++++++++++++++--- 3 files changed, 75 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index b486312b20..6268bc27f1 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -122,7 +122,7 @@ static void register_win32_dpi_event() return true; }); } -#endif // !ENABLE_WX_3_1_3_DPI_CHANGED_EVENT +#endif // !wxVERSION_EQUAL_OR_GREATER_THAN static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 }; @@ -412,7 +412,7 @@ bool GUI_App::on_init_inner() #ifdef WIN32 #if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) register_win32_dpi_event(); -#endif // !ENABLE_WX_3_1_3_DPI_CHANGED_EVENT +#endif // !wxVERSION_EQUAL_OR_GREATER_THAN register_win32_device_notification_event(); #endif // WIN32 @@ -1079,10 +1079,11 @@ void GUI_App::add_config_menu(wxMenuBar *menu) #endif // ENABLE_LAYOUT_NO_RESTART } #if ENABLE_LAYOUT_NO_RESTART - if (app_layout_changed) - { + if (app_layout_changed) { + mainframe->Hide(); mainframe->update_layout(); mainframe->select_tab(0); + mainframe->Show(); } #else if (recreate_app) diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 55a0feecc0..7dc6e80627 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -101,18 +101,22 @@ public: // recalc_font(); -#if wxVERSION_EQUAL_OR_GREATER_THAN(3, 1, 3) +#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) this->Bind(wxEVT_DPI_CHANGED, [this](wxDPIChangedEvent& evt) { m_scale_factor = (float)evt.GetNewDPI().x / (float)DPI_DEFAULT; - m_new_font_point_size = get_default_font_for_dpi(evt.GetNewDPI().x).GetPointSize(); + m_new_font_point_size = get_default_font_for_dpi(evt.GetNewDPI().x).GetPointSize(); - if (!m_can_rescale) + 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) { @@ -123,10 +127,15 @@ 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 // wxMAJOR_VERSION +#endif // wxVERSION_EQUAL_OR_GREATER_THAN this->Bind(wxEVT_MOVE_START, [this](wxMoveEvent& event) { @@ -166,6 +175,9 @@ 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; @@ -179,6 +191,9 @@ 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; @@ -218,13 +233,15 @@ private: { this->Freeze(); -#if !wxVERSION_EQUAL_OR_GREATER_THAN(3, 1, 3) +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) // 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); -#endif // wxMAJOR_VERSION - +#if ENABLE_LAYOUT_NO_RESTART + 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 5f7f5a81fd..b47398905a 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -42,6 +42,37 @@ namespace Slic3r { namespace GUI { +#if ENABLE_LAYOUT_NO_RESTART +enum class ERescaleTarget +{ + Mainframe, + SettingsDialog +}; + +static void rescale_dialog_after_dpi_change(MainFrame& mainframe, SettingsDialog& dialog, ERescaleTarget target) +{ + int mainframe_dpi = get_dpi_for_window(&mainframe); + int dialog_dpi = get_dpi_for_window(&dialog); + if (mainframe_dpi != dialog_dpi) { + if (target == ERescaleTarget::SettingsDialog) { + dialog.enable_force_rescale(); +#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) + dialog.GetEventHandler()->AddPendingEvent(wxDPIChangedEvent(wxSize(mainframe_dpi, mainframe_dpi), wxSize(dialog_dpi, dialog_dpi))); +#else + dialog.GetEventHandler()->AddPendingEvent(DpiChangedEvent(EVT_DPI_CHANGED_SLICER, dialog_dpi, dialog.GetRect())); +#endif // wxVERSION_EQUAL_OR_GREATER_THAN + } else { +#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) + mainframe.GetEventHandler()->AddPendingEvent(wxDPIChangedEvent(wxSize(dialog_dpi, dialog_dpi), wxSize(mainframe_dpi, mainframe_dpi))); +#else + mainframe.enable_force_rescale(); + mainframe.GetEventHandler()->AddPendingEvent(DpiChangedEvent(EVT_DPI_CHANGED_SLICER, mainframe_dpi, mainframe.GetRect())); +#endif // wxVERSION_EQUAL_OR_GREATER_THAN + } + } +} +#endif // ENABLE_LAYOUT_NO_RESTART + MainFrame::MainFrame() : DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE, "mainframe"), m_printhost_queue_dlg(new PrintHostQueueDialog(this)) @@ -259,6 +290,9 @@ void MainFrame::update_layout() m_plater_page = nullptr; } + if (m_layout == ESettingsLayout::Dlg) + rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::Mainframe); + clean_sizer(GetSizer()); clean_sizer(m_settings_dialog.GetSizer()); @@ -318,6 +352,9 @@ void MainFrame::update_layout() GetSizer()->Add(m_plater, 1, wxEXPAND); m_tabpanel->Reparent(&m_settings_dialog); m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND); + + rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::SettingsDialog); + m_tabpanel->Show(); m_plater->Show(); break; @@ -699,7 +736,7 @@ bool MainFrame::can_reslice() const return (m_plater != nullptr) && !m_plater->model().objects.empty(); } -void MainFrame::on_dpi_changed(const wxRect &suggested_rect) +void MainFrame::on_dpi_changed(const wxRect& suggested_rect) { #if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT wxGetApp().update_fonts(this); @@ -718,7 +755,7 @@ void MainFrame::on_dpi_changed(const wxRect &suggested_rect) // update Tabs #if ENABLE_LAYOUT_NO_RESTART - if (m_layout != ESettingsLayout::Dlg) // Update tabs later, from the SettingsDialog, when the Settings are in the separated dialog + 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 @@ -731,7 +768,7 @@ void MainFrame::on_dpi_changed(const wxRect &suggested_rect) // Workarounds for correct Window rendering after rescale - /* Even if Window is maximized during moving, + /* Even if Window is maximized during moving, * first of all we should imitate Window resizing. So: * 1. cancel maximization, if it was set * 2. imitate resizing @@ -749,6 +786,11 @@ void MainFrame::on_dpi_changed(const wxRect &suggested_rect) this->SetSize(sz); 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() From e782d34ec81c3f45d1500b8fcc7b82e788047475 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 19 Jun 2020 11:18:48 +0200 Subject: [PATCH 16/35] ENABLE_LAYOUT_NO_RESTART -> Fixed font scaling when switching to non modal setting dialog layout when building against wxWidgets 3.1.3 --- src/slic3r/GUI/GUI_Utils.hpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 7dc6e80627..c51c0cf42d 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -233,15 +233,17 @@ private: { this->Freeze(); -#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) - // 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 - m_force_rescale = false; + if (m_force_rescale) { +#endif // ENABLE_LAYOUT_NO_RESTART + // 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 + 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(); From d6e040c282a9992d4cfebe85b3a931a43b2773d0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 19 Jun 2020 12:48:01 +0200 Subject: [PATCH 17/35] Follow-up of e782d34ec81c3f45d1500b8fcc7b82e788047475 -> Fix in DPIAware::rescale() --- src/slic3r/GUI/GUI_Utils.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index c51c0cf42d..2737b3edbf 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -233,14 +233,14 @@ private: { this->Freeze(); -#if ENABLE_LAYOUT_NO_RESTART +#if ENABLE_LAYOUT_NO_RESTART && wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) if (m_force_rescale) { #endif // ENABLE_LAYOUT_NO_RESTART // 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 +#if ENABLE_LAYOUT_NO_RESTART && wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) m_force_rescale = false; } #endif // ENABLE_LAYOUT_NO_RESTART From 92f0c01ee3858b4444d8c6304e26aa0432ffaf06 Mon Sep 17 00:00:00 2001 From: bgiot Date: Mon, 22 Jun 2020 16:47:57 +0200 Subject: [PATCH 18/35] Use the correct LIBPNG --- deps/wxWidgets/wxWidgets.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index ee8a22c4f3..34103c8ec6 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -23,7 +23,7 @@ prusaslicer_add_cmake_project(wxWidgets -DwxUSE_DETECT_SM=OFF -DwxUSE_UNICODE=ON -DwxUSE_OPENGL=ON - -DwxUSE_LIBPNG=sys + -DwxUSE_LIBPNG=builtin -DwxUSE_ZLIB=sys -DwxUSE_REGEX=builtin -DwxUSE_LIBXPM=builtin From f5c7034f4753a2b79c281f5fb67319c9bbd3f4bc Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 23 Jun 2020 13:38:25 +0200 Subject: [PATCH 19/35] Fixed 'Export plate as STL including supports' command --- src/slic3r/GUI/Plater.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 8c3a90370a..f71ee4a040 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4847,25 +4847,33 @@ void Plater::export_stl(bool extended, bool selection_only) ? Transform3d::Identity() : object->model_object()->instances[instance_idx]->get_transformation().get_matrix(); + TriangleMesh inst_mesh; + if (has_pad_mesh) { TriangleMesh inst_pad_mesh = pad_mesh; inst_pad_mesh.transform(inst_transform, is_left_handed); - mesh.merge(inst_pad_mesh); + inst_mesh.merge(inst_pad_mesh); } if (has_supports_mesh) { TriangleMesh inst_supports_mesh = supports_mesh; inst_supports_mesh.transform(inst_transform, is_left_handed); - mesh.merge(inst_supports_mesh); + inst_mesh.merge(inst_supports_mesh); } TriangleMesh inst_object_mesh = object->get_mesh_to_print(); inst_object_mesh.transform(mesh_trafo_inv); inst_object_mesh.transform(inst_transform, is_left_handed); - mesh.merge(inst_object_mesh); + inst_mesh.merge(inst_object_mesh); + + // ensure that the instance lays on the bed + inst_mesh.translate(0.0f, 0.0f, -inst_mesh.bounding_box().min[2]); + + // merge instance with global mesh + mesh.merge(inst_mesh); if (one_inst_only) break; From 990bfc0d760dbf396a2a1b8b50b35aae78f07f5f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 24 Jun 2020 11:29:09 +0200 Subject: [PATCH 20/35] Fix of #4428 Don't update a visibility of the collapse_toolbar: if sidebar is collapsed and "show_collapse_button" doesn't have "true" value + Fixed typos in the Preferences.cpp --- src/slic3r/GUI/Plater.cpp | 3 --- src/slic3r/GUI/Preferences.cpp | 8 ++++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f71ee4a040..ac6e7819ba 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2087,9 +2087,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) // collapse sidebar according to saved value bool is_collapsed = wxGetApp().app_config->get("collapsed_sidebar") == "1"; sidebar->collapse(is_collapsed); - // Update an enable of the collapse_toolbar: if sidebar is collapsed, then collapse_toolbar should be visible - if (is_collapsed) - wxGetApp().app_config->set("show_collapse_button", "1"); } Plater::priv::~priv() diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 84ad548799..02e4a899d2 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -161,7 +161,7 @@ void PreferencesDialog::build() } }; - def.label = L("Show the button for the collapse sidebar"); + def.label = L("Show sidebar collapse/expand button"); def.type = coBool; def.tooltip = L("If enabled, the button for the collapse sidebar will be appeared in top right corner of the 3D Scene"); def.set_default_value(new ConfigOptionBool{ app_config->get("show_collapse_button") == "1" }); @@ -353,9 +353,9 @@ void PreferencesDialog::create_icon_size_slider() void PreferencesDialog::create_settings_mode_widget() { - wxString choices[] = { _L("Old regular layout with tab bar"), - _L("New layout without the tab bar on the platter"), - _L("Settings will be shown in non-modal dialog") }; + wxString choices[] = { _L("Old regular layout with the tab bar"), + _L("New layout without the tab bar on the plater"), + _L("Settings will be shown in the non-modal dialog") }; auto app_config = get_app_config(); int selection = app_config->get("old_settings_layout_mode") == "1" ? 0 : From 53b6c93ceba03da1a15d0a7f5f25025bd406b588 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 26 Jun 2020 13:45:54 +0200 Subject: [PATCH 21/35] Fix of #4441 SearchDialog: Select first item in the lit, if search_list has at least one item --- src/slic3r/GUI/Search.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 613a39ccef..012af342a8 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -617,8 +617,9 @@ void SearchDialog::update_list() for (const FoundOption& item : filters) search_list_model->Prepend(item.label); - // select first item - search_list->Select(search_list_model->GetItem(0)); + // select first item, if search_list + if (search_list_model->GetCount() > 0) + search_list->Select(search_list_model->GetItem(0)); prevent_list_events = false; } From c10f6a622d0a55fe52d794327295132a794d3b6b Mon Sep 17 00:00:00 2001 From: bubnikv Date: Sat, 27 Jun 2020 08:44:13 +0200 Subject: [PATCH 22/35] Fixed unit tests on Windows after introduction of GMP to boost::polygon Voronoi diagram generator by Vojtech. Fixed Perl bindings on Windows after some "improvement" of the Windows 10 SDK headers, which fail if included from a C++ code using the extern "C" clause. Namely, the Windows 10 SDK include for sockets introduces C++ macros if a "compiled with C++" symbol is provided even if included through exetrn "C". --- deps/CMakeLists.txt | 94 +++++++++++++++------------------- tests/fff_print/CMakeLists.txt | 4 ++ xs/CMakeLists.txt | 10 ++++ xs/main.xs.in | 4 +- xs/src/xsinit.h | 4 +- 5 files changed, 58 insertions(+), 58 deletions(-) diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 4078af3df9..7189458286 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -129,19 +129,6 @@ else() include("deps-linux.cmake") endif() -# Patch the boost::polygon library with a custom one. -ExternalProject_Add(dep_boost_polygon - EXCLUDE_FROM_ALL ON - GIT_REPOSITORY "https://github.com/prusa3d/polygon" - GIT_TAG prusaslicer_gmp - DEPENDS dep_boost - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory - "${CMAKE_CURRENT_BINARY_DIR}/dep_boost_polygon-prefix/src/dep_boost_polygon/include/boost/polygon" - "${DESTDIR}/usr/local/include/boost/polygon" -) - set(ZLIB_PKG "") if (NOT ZLIB_FOUND) include(ZLIB/ZLIB.cmake) @@ -170,50 +157,49 @@ if (NOT "${ZLIB_PKG}" STREQUAL "") add_dependencies(dep_openexr ${ZLIB_PKG}) endif () +set(_dep_list + dep_boost + dep_tbb + dep_libcurl + dep_wxWidgets + dep_gtest + dep_cereal + dep_nlopt + dep_openvdb + dep_OpenCSG + dep_CGAL + ${PNG_PKG} + ${ZLIB_PKG} + ${EXPAT_PKG} + ) + +if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") + # Patch the boost::polygon library with a custom one. + ExternalProject_Add(dep_boost_polygon + EXCLUDE_FROM_ALL ON + GIT_REPOSITORY "https://github.com/prusa3d/polygon" + GIT_TAG prusaslicer_gmp + DEPENDS dep_boost + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_BINARY_DIR}/dep_boost_polygon-prefix/src/dep_boost_polygon/include/boost/polygon" + "${DESTDIR}/usr/local/include/boost/polygon" + ) + # Only override boost::Polygon Voronoi implementation with Vojtech's GMP hacks on 64bit platforms. + list(APPEND _dep_list "dep_boost_polygon") +endif () + if (MSVC) - - add_custom_target(deps ALL - DEPENDS - dep_boost - dep_boost_polygon - dep_tbb - dep_libcurl - dep_wxWidgets - dep_gtest - dep_cereal - dep_nlopt - # dep_qhull # Experimental - dep_openvdb - dep_OpenCSG - dep_CGAL - ${PNG_PKG} - ${ZLIB_PKG} - ${EXPAT_PKG} - ) - + # Experimental + #list(APPEND _dep_list "dep_qhull") else() - - add_custom_target(deps ALL - DEPENDS - dep_boost - dep_boost_polygon - dep_tbb - dep_libcurl - dep_wxWidgets - dep_gtest - dep_cereal - dep_nlopt - dep_qhull - dep_openvdb - dep_OpenCSG - dep_CGAL - ${PNG_PKG} - ${ZLIB_PKG} - ${EXPAT_PKG} - # dep_libigl # Not working, static build has different Eigen - ) - + list(APPEND _dep_list "dep_qhull") + # Not working, static build has different Eigen + #list(APPEND _dep_list "dep_libigl") endif() +add_custom_target(deps ALL DEPENDS ${_dep_list}) + # Note: I'm not using any of the LOG_xxx options in ExternalProject_Add() commands # because they seem to generate bogus build files (possibly a bug in ExternalProject). diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index 75a9c31372..c69e722af3 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -19,5 +19,9 @@ add_executable(${_TEST_NAME}_tests target_link_libraries(${_TEST_NAME}_tests test_common libslic3r) set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") +if (WIN32) + prusaslicer_copy_dlls(${_TEST_NAME}_tests) +endif() + # catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ") add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${CATCH_EXTRA_ARGS}) diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index a59a199368..75d236a54c 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -185,6 +185,16 @@ if (MSVC) string(REPLACE "/" "\\" PROPS_CMAKE_SOURCE_DIR "${CMAKE_SOURCE_DIR}") configure_file("../cmake/msvc/xs.wperl.props.in" "${CMAKE_BINARY_DIR}/xs.wperl.props" NEWLINE_STYLE CRLF) set_target_properties(XS PROPERTIES VS_USER_PROPS "${CMAKE_BINARY_DIR}/xs.wperl.props") + + if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") + set(_bits 64) + elseif ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") + set(_bits 32) + endif () + add_custom_command(TARGET XS POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${TOP_LEVEL_PROJECT_DIR}/deps/GMP/gmp/lib/win${_bits}/libgmp-10.dll "${PERL_LOCAL_LIB_ARCH_DIR}/auto/Slic3r/XS/" + COMMENT "Installing gmp runtime into the local-lib directory ..." + VERBATIM) endif() # Installation diff --git a/xs/main.xs.in b/xs/main.xs.in index 3523d569ea..c10f432d83 100644 --- a/xs/main.xs.in +++ b/xs/main.xs.in @@ -5,7 +5,7 @@ // #include #ifdef __cplusplus -extern "C" { +/* extern "C" { */ #endif #include "EXTERN.h" #include "perl.h" @@ -14,7 +14,7 @@ extern "C" { #undef do_open #undef do_close #ifdef __cplusplus -} +/* } */ #endif #ifdef _WIN32 diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h index f14e1262dc..2082dfb883 100644 --- a/xs/src/xsinit.h +++ b/xs/src/xsinit.h @@ -40,7 +40,7 @@ // #include #ifdef SLIC3RXS -extern "C" { +// extern "C" { #include "EXTERN.h" #include "perl.h" #include "XSUB.h" @@ -88,7 +88,7 @@ extern "C" { #undef Zero #undef Packet #undef _ -} +// } #endif #include From 6a7efbbf967edc47d647afb16fa1f4a51a184834 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 29 Jun 2020 15:23:24 +0200 Subject: [PATCH 23/35] ENABLE_LAYOUT_NO_RESTART -> Added an extra sizer to Mainframe to avoid hiding the entire application when switching the layout type --- src/slic3r/GUI/GUI_App.cpp | 4 ++-- src/slic3r/GUI/MainFrame.cpp | 21 ++++++++++----------- src/slic3r/GUI/MainFrame.hpp | 1 + 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 6268bc27f1..d67eb5ded4 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1080,10 +1080,10 @@ void GUI_App::add_config_menu(wxMenuBar *menu) } #if ENABLE_LAYOUT_NO_RESTART if (app_layout_changed) { - mainframe->Hide(); + mainframe->GetSizer()->Hide((size_t)0); mainframe->update_layout(); mainframe->select_tab(0); - mainframe->Show(); + mainframe->GetSizer()->Show((size_t)0); } #else if (recreate_app) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index b47398905a..0f6d08124e 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -141,7 +141,9 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S #endif // !ENABLE_LAYOUT_NO_RESTART // initialize layout - auto sizer = new wxBoxSizer(wxVERTICAL); + 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 @@ -293,7 +295,7 @@ void MainFrame::update_layout() if (m_layout == ESettingsLayout::Dlg) rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::Mainframe); - clean_sizer(GetSizer()); + clean_sizer(m_main_sizer); clean_sizer(m_settings_dialog.GetSizer()); if (m_settings_dialog.IsShown()) @@ -332,16 +334,16 @@ void MainFrame::update_layout() { m_plater->Reparent(m_tabpanel); m_tabpanel->InsertPage(0, m_plater, _L("Plater")); - GetSizer()->Add(m_tabpanel, 1, wxEXPAND); + m_main_sizer->Add(m_tabpanel, 1, wxEXPAND); m_plater->Show(); m_tabpanel->Show(); break; } case ESettingsLayout::New: { - GetSizer()->Add(m_plater, 1, wxEXPAND); + m_main_sizer->Add(m_plater, 1, wxEXPAND); m_tabpanel->Hide(); - GetSizer()->Add(m_tabpanel, 1, wxEXPAND); + m_main_sizer->Add(m_tabpanel, 1, wxEXPAND); m_plater_page = new wxPanel(m_tabpanel); m_tabpanel->InsertPage(0, m_plater_page, _L("Plater")); // empty panel just for Plater tab */ m_plater->Show(); @@ -349,7 +351,7 @@ void MainFrame::update_layout() } case ESettingsLayout::Dlg: { - GetSizer()->Add(m_plater, 1, wxEXPAND); + m_main_sizer->Add(m_plater, 1, wxEXPAND); m_tabpanel->Reparent(&m_settings_dialog); m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND); @@ -1573,13 +1575,10 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) } #if ENABLE_LAYOUT_NO_RESTART else if (m_layout == ESettingsLayout::New) { + m_main_sizer->Show(m_plater, tab == 0); + m_main_sizer->Show(m_tabpanel, tab != 0); #else else if (m_layout == slNew) { -#endif // ENABLE_LAYOUT_NO_RESTART -#if ENABLE_LAYOUT_NO_RESTART - GetSizer()->Show(m_plater, tab == 0); - GetSizer()->Show(m_tabpanel, tab != 0); -#else m_plater->Show(tab == 0); m_tabpanel->Show(tab != 0); #endif // ENABLE_LAYOUT_NO_RESTART diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 4d651f04ea..4514b8f50f 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -76,6 +76,7 @@ class MainFrame : public DPIFrame wxMenuItem* m_menu_item_repeat { nullptr }; // doesn't used now #endif wxMenuItem* m_menu_item_reslice_now { nullptr }; + wxSizer* m_main_sizer{ nullptr }; PrintHostQueueDialog *m_printhost_queue_dlg; From d8a81e978a8e95d16ec269a77be37dabda6cccff Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 30 Jun 2020 10:48:07 +0200 Subject: [PATCH 24/35] Fixed initial size of features type combo popup when building against wxWidgets 3.1.3 --- src/slic3r/GUI/GUI.cpp | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index b9516b12f2..c119112c2c 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -237,7 +237,7 @@ void show_error_id(int id, const std::string& message) void show_info(wxWindow* parent, const wxString& message, const wxString& title) { - wxMessageDialog msg_wingow(parent, message, wxString(SLIC3R_APP_NAME " - ") + (title.empty() ? _(L("Notice")) : title), wxOK | wxICON_INFORMATION); + wxMessageDialog msg_wingow(parent, message, wxString(SLIC3R_APP_NAME " - ") + (title.empty() ? _L("Notice") : title), wxOK | wxICON_INFORMATION); msg_wingow.ShowModal(); } @@ -249,7 +249,7 @@ void show_info(wxWindow* parent, const char* message, const char* title) void warning_catcher(wxWindow* parent, const wxString& message) { - wxMessageDialog msg(parent, message, _(L("Warning")), wxOK | wxICON_WARNING); + wxMessageDialog msg(parent, message, _L("Warning"), wxOK | wxICON_WARNING); msg.ShowModal(); } @@ -259,14 +259,14 @@ void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string return; wxCheckListBoxComboPopup* popup = new wxCheckListBoxComboPopup; - if (popup != nullptr) - { - // FIXME If the following line is removed, the combo box popup list will not react to mouse clicks. + if (popup != nullptr) { + // FIXME If the following line is removed, the combo box popup list will not react to mouse clicks. // On the other side, with this line the combo box popup cannot be closed by clicking on the combo button on Windows 10. comboCtrl->UseAltPopupWindow(); - comboCtrl->EnablePopupAnimation(false); - comboCtrl->SetPopupControl(popup); + // the following line messes up the popup size the first time it is shown on wxWidgets 3.1.3 +// comboCtrl->EnablePopupAnimation(false); + comboCtrl->SetPopupControl(popup); popup->SetStringValue(from_u8(text)); popup->Bind(wxEVT_CHECKLISTBOX, [popup](wxCommandEvent& evt) { popup->OnCheckListBox(evt); }); popup->Bind(wxEVT_LISTBOX, [popup](wxCommandEvent& evt) { popup->OnListBoxSelection(evt); }); @@ -276,13 +276,11 @@ void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string std::vector items_str; boost::split(items_str, items, boost::is_any_of("|"), boost::token_compress_off); - for (const std::string& item : items_str) - { + for (const std::string& item : items_str) { popup->Append(from_u8(item)); } - for (unsigned int i = 0; i < popup->GetCount(); ++i) - { + for (unsigned int i = 0; i < popup->GetCount(); ++i) { popup->Check(i, initial_value); } } @@ -293,10 +291,8 @@ int combochecklist_get_flags(wxComboCtrl* comboCtrl) int flags = 0; wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup); - if (popup != nullptr) - { - for (unsigned int i = 0; i < popup->GetCount(); ++i) - { + if (popup != nullptr) { + for (unsigned int i = 0; i < popup->GetCount(); ++i) { if (popup->IsChecked(i)) flags |= 1 << i; } From 21648c63565287dea03e0f5508ad23701e82bc0d Mon Sep 17 00:00:00 2001 From: bgiot Date: Mon, 6 Jul 2020 12:45:06 +0200 Subject: [PATCH 25/35] Fix macos deps --- deps/deps-macos.cmake | 2 ++ deps/wxWidgets/wxWidgets.cmake | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/deps/deps-macos.cmake b/deps/deps-macos.cmake index ef00b80d50..a71a0ebfc0 100644 --- a/deps/deps-macos.cmake +++ b/deps/deps-macos.cmake @@ -9,6 +9,8 @@ set(DEP_CMAKE_OPTS "-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEP_OSX_TARGET}" "-DCMAKE_CXX_FLAGS=${DEP_WERRORS_SDK}" "-DCMAKE_C_FLAGS=${DEP_WERRORS_SDK}" + "-DCMAKE_FIND_FRAMEWORK=LAST" + "-DCMAKE_FIND_APPBUNDLE=LAST" ) include("deps-unix-common.cmake") diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index 34103c8ec6..ee8a22c4f3 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -23,7 +23,7 @@ prusaslicer_add_cmake_project(wxWidgets -DwxUSE_DETECT_SM=OFF -DwxUSE_UNICODE=ON -DwxUSE_OPENGL=ON - -DwxUSE_LIBPNG=builtin + -DwxUSE_LIBPNG=sys -DwxUSE_ZLIB=sys -DwxUSE_REGEX=builtin -DwxUSE_LIBXPM=builtin From b9d34c312f1373f2391ef8916ebb9e88a8b17ae2 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 10 Jul 2020 10:20:57 +0200 Subject: [PATCH 26/35] #4492 - Fixed application loosing focus when importing models --- src/slic3r/GUI/GUI_App.cpp | 10 +++++----- src/slic3r/GUI/MainFrame.cpp | 8 ++++---- src/slic3r/GUI/Plater.cpp | 8 ++++---- src/slic3r/Utils/FixModelByWin10.cpp | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index d67eb5ded4..a7b562bd75 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -633,9 +633,9 @@ void GUI_App::recreate_GUI(const wxString& msg_name) { mainframe->shutdown(); - wxProgressDialog dlg(msg_name, msg_name); + wxProgressDialog dlg(msg_name, msg_name, 100, nullptr, wxPD_AUTO_HIDE); dlg.Pulse(); - dlg.Update(10, _(L("Recreating")) + dots); + dlg.Update(10, _L("Recreating") + dots); MainFrame *old_main_frame = mainframe; mainframe = new MainFrame(); @@ -645,17 +645,17 @@ void GUI_App::recreate_GUI(const wxString& msg_name) sidebar().obj_list()->init_objects(); SetTopWindow(mainframe); - dlg.Update(30, _(L("Recreating")) + dots); + dlg.Update(30, _L("Recreating") + dots); old_main_frame->Destroy(); // For this moment ConfigWizard is deleted, invalidate it. m_wizard = nullptr; - dlg.Update(80, _(L("Loading of current presets")) + dots); + dlg.Update(80, _L("Loading of current presets") + dots); m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); load_current_presets(); mainframe->Show(true); - dlg.Update(90, _(L("Loading of a mode view")) + dots); + dlg.Update(90, _L("Loading of a mode view") + dots); /* Temporary workaround for the correct behavior of the Scrolled sidebar panel: * change min hight of object list to the normal min value (15 * wxGetApp().em_unit()) * after first whole Mainframe updating/layouting diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 0f6d08124e..10d9d800f9 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1303,10 +1303,10 @@ void MainFrame::quick_slice(const int qs) } // show processbar dialog - m_progress_dialog = new wxProgressDialog(_(L("Slicing")) + dots, - // TRN "Processing input_file_basename" - from_u8((boost::format(_utf8(L("Processing %s"))) % (input_file_basename + dots)).str()), - 100, this, 4); + m_progress_dialog = new wxProgressDialog(_L("Slicing") + dots, + // TRN "Processing input_file_basename" + from_u8((boost::format(_utf8(L("Processing %s"))) % (input_file_basename + dots)).str()), + 100, nullptr, wxPD_AUTO_HIDE); m_progress_dialog->Pulse(); { // my @warnings = (); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ac6e7819ba..f1156271c8 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2210,17 +2210,17 @@ std::vector Plater::priv::load_files(const std::vector& input_ } const auto loading = _L("Loading") + dots; - wxProgressDialog dlg(loading, loading); + wxProgressDialog dlg(loading, "", 100, q, wxPD_AUTO_HIDE); dlg.Pulse(); auto *new_model = (!load_model || one_by_one) ? nullptr : new Slic3r::Model(); std::vector obj_idxs; - for (size_t i = 0; i < input_files.size(); i++) { + for (size_t i = 0; i < input_files.size(); ++i) { const auto &path = input_files[i]; const auto filename = path.filename(); - const auto dlg_info = format_wxstr(_L("Processing input file %s"), from_path(filename)) + "\n"; - dlg.Update(100 * i / input_files.size(), dlg_info); + const auto dlg_info = _L("Loading file") + ": " + from_path(filename); + dlg.Update(static_cast(100.0f * static_cast(i) / static_cast(input_files.size())), dlg_info); const bool type_3mf = std::regex_match(path.string(), pattern_3mf); const bool type_zip_amf = !type_3mf && std::regex_match(path.string(), pattern_zip_amf); diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 0de526432b..8de4991d85 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -337,8 +337,8 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) // Open a progress dialog. wxProgressDialog progress_dialog( - _(L("Model fixing")), - _(L("Exporting model...")), + _L("Model fixing"), + _L("Exporting model") + "...", 100, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT); // Executing the calculation in a background thread, so that the COM context could be created with its own threading model. // (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context). From f5215cac44b3283baeb86ce075447f8165123394 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 13 Jul 2020 13:16:18 +0200 Subject: [PATCH 27/35] Attempt to fix crash on Mac when rotating an object while layer editing is active --- src/libslic3r/PrintObject.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index cc39cbf0ae..d2bdb6d531 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1577,7 +1577,9 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c bool updated = false; if (layer_height_profile.empty()) { - layer_height_profile = model_object.layer_height_profile; + // use the constructor because the assignement is crashing on ASAN OsX + layer_height_profile = std::vector(model_object.layer_height_profile); +// layer_height_profile = model_object.layer_height_profile; updated = true; } From 746ece4c40dbbc889b9e029f6f39b6c52e7bcbdc Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 14 Jul 2020 11:52:34 +0200 Subject: [PATCH 28/35] Support for collecting warnings from the background processing. Multiple warnigns may be collected per Print / PrintObject milestone and a status update is sent to the UI immediately after a warning is issued. --- src/libslic3r/Print.hpp | 7 ++ src/libslic3r/PrintBase.hpp | 144 ++++++++++++++++++++++++++++++------ src/libslic3r/SLAPrint.hpp | 7 ++ src/slic3r/GUI/Plater.cpp | 21 ++++++ 4 files changed, 158 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 9da85bfba5..bf541e1222 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -395,6 +395,13 @@ public: const PrintObjectPtrs& objects() const { return m_objects; } PrintObject* get_object(size_t idx) { return m_objects[idx]; } const PrintObject* get_object(size_t idx) const { return m_objects[idx]; } + // PrintObject by its ObjectID, to be used to uniquely bind slicing warnings to their source PrintObjects + // in the notification center. + const PrintObject* get_object(ObjectID object_id) const { + auto it = std::find_if(m_objects.begin(), m_objects.end(), + [object_id](const PrintObject *obj) { return *static_cast(obj) == object_id; }); + return (it == m_objects.end()) ? nullptr : *it; + } const PrintRegionPtrs& regions() const { return m_regions; } // How many of PrintObject::copies() over all print objects are there? // If zero, then the print is empty and the print shall not be executed. diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index 05d884cc88..5e422ca701 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -13,6 +13,7 @@ #endif #include "tbb/mutex.h" +#include "ObjectID.hpp" #include "Model.hpp" #include "PlaceholderParser.hpp" #include "PrintConfig.hpp" @@ -32,6 +33,11 @@ public: DONE, }; + enum class WarningLevel { + NON_CRITICAL, + CRITICAL + }; + typedef size_t TimeStamp; // A new unique timestamp is being assigned to the step every time the step changes its state. @@ -42,6 +48,17 @@ public: TimeStamp timestamp; }; + struct Warning + { + WarningLevel level; + std::string message; + }; + + struct StateWithWarnings : public StateWithTimeStamp + { + std::vector warnings; + }; + protected: //FIXME last timestamp is shared between Print & SLAPrint, // and if multiple Print or SLAPrint instances are executed in parallel, modification of g_last_timestamp @@ -56,12 +73,18 @@ class PrintState : public PrintStateBase public: PrintState() {} - StateWithTimeStamp state_with_timestamp(StepType step, tbb::mutex &mtx) const { + StateWithTimeStamp state_with_timestamp(StepType step, tbb::mutex &mtx) const { tbb::mutex::scoped_lock lock(mtx); StateWithTimeStamp state = m_state[step]; return state; } + StateWithWarnings state_with_warnings(StepType step, tbb::mutex &mtx) const { + tbb::mutex::scoped_lock lock(mtx); + StateWithWarnings state = m_state[step]; + return state; + } + bool is_started(StepType step, tbb::mutex &mtx) const { return this->state_with_timestamp(step, mtx).state == STARTED; } @@ -91,10 +114,26 @@ public: tbb::mutex::scoped_lock lock(mtx); // If canceled, throw before changing the step state. throw_if_canceled(); +#ifndef NDEBUG +// The following test is not necessarily valid after the background processing thread +// is stopped with throw_if_canceled(), as the CanceledException is not being catched +// by the Print or PrintObject to update m_step_active or m_state[...].state. +// This should not be a problem as long as the caller calls set_started() / set_done() / +// active_step_add_warning() consistently. From the robustness point of view it would be +// be better to catch CanceledException and do the updates. From the performance point of view, +// the current implementation is optimal. +// +// assert(m_step_active == -1); +// for (int i = 0; i < int(COUNT); ++ i) +// assert(m_state[i].state != STARTED); +#endif // NDEBUG if (m_state[step].state == DONE) return false; - m_state[step].state = STARTED; - m_state[step].timestamp = ++ g_last_timestamp; + PrintStateBase::StateWithWarnings &state = m_state[step]; + state.state = STARTED; + state.timestamp = ++ g_last_timestamp; + state.warnings.clear(); + m_step_active = static_cast(step); return true; } @@ -105,10 +144,13 @@ public: tbb::mutex::scoped_lock lock(mtx); // If canceled, throw before changing the step state. throw_if_canceled(); - assert(m_state[step].state != DONE); - m_state[step].state = DONE; - m_state[step].timestamp = ++ g_last_timestamp; - return m_state[step].timestamp; + assert(m_state[step].state == STARTED); + assert(m_step_active == static_cast(step)); + PrintStateBase::StateWithWarnings &state = m_state[step]; + state.state = DONE; + state.timestamp = ++ g_last_timestamp; + m_step_active = -1; + return state.timestamp; } // Make the step invalid. @@ -124,13 +166,18 @@ public: printf("Not held!\n"); } #endif - m_state[step].state = INVALID; - m_state[step].timestamp = ++ g_last_timestamp; + PrintStateBase::StateWithWarnings &state = m_state[step]; + state.state = INVALID; + state.timestamp = ++ g_last_timestamp; // Raise the mutex, so that the following cancel() callback could cancel // the background processing. // Internally the cancel() callback shall unlock the PrintBase::m_status_mutex to let - // the working thread to proceed. + // the working thread proceed. cancel(); + // Now the worker thread should be stopped, therefore it cannot write into the warnings field. + // It is safe to clear it. + state.warnings.clear(); + m_step_active = -1; } return invalidated; } @@ -157,6 +204,11 @@ public: // Internally the cancel() callback shall unlock the PrintBase::m_status_mutex to let // the working thread to proceed. cancel(); + // Now the worker thread should be stopped, therefore it cannot write into the warnings field. + // It is safe to clear it. + for (StepTypeIterator it = step_begin; it != step_end; ++ it) + m_state[*it].warnings.clear(); + m_step_active = -1; } return invalidated; } @@ -176,18 +228,37 @@ public: state.timestamp = ++ g_last_timestamp; } } - if (invalidated) + if (invalidated) { cancel(); + // Now the worker thread should be stopped, therefore it cannot write into the warnings field. + // It is safe to clear it. + for (size_t i = 0; i < COUNT; ++ i) + m_state[i].warnings.clear(); + m_step_active = -1; + } return invalidated; } + StepType active_step_add_warning(PrintStateBase::WarningLevel warning_level, const std::string &message, tbb::mutex &mtx) + { + tbb::mutex::scoped_lock lock(mtx); + assert(m_step_active != -1); + assert(m_state[m_step_active].state == STARTED); + m_state[m_step_active].warnings.emplace_back(PrintStateBase::Warning{ warning_level, message}); + return static_cast(m_step_active); + } + private: - StateWithTimeStamp m_state[COUNT]; + StateWithWarnings m_state[COUNT]; + // Active class StepType or -1 if none is active. + // If the background processing is canceled, m_step_active may not be resetted + // to -1, see the comment in this->set_started(). + int m_step_active = -1; }; class PrintBase; -class PrintObjectBase +class PrintObjectBase : public ObjectID { public: const ModelObject* model_object() const { return m_model_object; } @@ -214,7 +285,7 @@ protected: * The PrintBase class will abstract this flow for different technologies. * */ -class PrintBase +class PrintBase : public ObjectID { public: PrintBase() : m_placeholder_parser(&m_full_print_config) { this->restart(); } @@ -264,17 +335,29 @@ public: struct SlicingStatus { SlicingStatus(int percent, const std::string &text, unsigned int flags = 0) : percent(percent), text(text), flags(flags) {} - int percent; + SlicingStatus(const PrintBase &print, int warning_step) : + flags(UPDATE_PRINT_STEP_WARNINGS), warning_object_id(print), warning_step(warning_step) {} + SlicingStatus(const PrintObjectBase &print_object, int warning_step) : + flags(UPDATE_PRINT_OBJECT_STEP_WARNINGS), warning_object_id(print_object), warning_step(warning_step) {} + int percent { -1 }; std::string text; // Bitmap of flags. enum FlagBits { - DEFAULT = 0, - RELOAD_SCENE = 1 << 1, - RELOAD_SLA_SUPPORT_POINTS = 1 << 2, - RELOAD_SLA_PREVIEW = 1 << 3, + DEFAULT = 0, + RELOAD_SCENE = 1 << 1, + RELOAD_SLA_SUPPORT_POINTS = 1 << 2, + RELOAD_SLA_PREVIEW = 1 << 3, + // UPDATE_PRINT_STEP_WARNINGS is mutually exclusive with UPDATE_PRINT_OBJECT_STEP_WARNINGS. + UPDATE_PRINT_STEP_WARNINGS = 1 << 4, + UPDATE_PRINT_OBJECT_STEP_WARNINGS = 1 << 5 }; // Bitmap of FlagBits unsigned int flags; + // set to an ObjectID of a Print or a PrintObject based on flags + // (whether UPDATE_PRINT_STEP_WARNINGS or UPDATE_PRINT_OBJECT_STEP_WARNINGS is set). + ObjectID warning_object_id; + // For which Print or PrintObject step a new warning is beeing issued? + int warning_step { -1 }; }; typedef std::function status_callback_type; // Default status console print out in the form of percent => message. @@ -343,11 +426,12 @@ protected: DynamicPrintConfig m_full_print_config; PlaceholderParser m_placeholder_parser; -private: - tbb::atomic m_cancel_status; // Callback to be evoked regularly to update state of the UI thread. status_callback_type m_status_callback; +private: + tbb::atomic m_cancel_status; + // Callback to be evoked to stop the background processing before a state is updated. cancel_callback_type m_cancel_callback = [](){}; @@ -363,6 +447,7 @@ class PrintBaseWithState : public PrintBase public: bool is_step_done(PrintStepEnum step) const { return m_state.is_done(step, this->state_mutex()); } PrintStateBase::StateWithTimeStamp step_state_with_timestamp(PrintStepEnum step) const { return m_state.state_with_timestamp(step, this->state_mutex()); } + PrintStateBase::StateWithWarnings step_state_with_warnings(PrintStepEnum step) const { return m_state.state_with_warnings(step, this->state_mutex()); } protected: bool set_started(PrintStepEnum step) { return m_state.set_started(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); } @@ -380,6 +465,14 @@ protected: bool is_step_started_unguarded(PrintStepEnum step) const { return m_state.is_started_unguarded(step); } bool is_step_done_unguarded(PrintStepEnum step) const { return m_state.is_done_unguarded(step); } + // Add a slicing warning to the active Print step and send a status notification. + // This method could be called multiple times between this->set_started() and this->set_done(). + void active_step_add_warning(PrintStateBase::WarningLevel warning_level, const std::string &message) { + PrintStepEnum active_step = m_state.active_step_add_warning(warning_level, message, this->state_mutex()); + if (m_status_callback) m_status_callback(SlicingStatus(*this, active_step)); + else printf("print warning: %s\n", message.c_str()); + } + private: PrintState m_state; }; @@ -394,6 +487,7 @@ public: typedef PrintState PrintObjectState; bool is_step_done(PrintObjectStepEnum step) const { return m_state.is_done(step, PrintObjectBase::state_mutex(m_print)); } PrintStateBase::StateWithTimeStamp step_state_with_timestamp(PrintObjectStepEnum step) const { return m_state.state_with_timestamp(step, PrintObjectBase::state_mutex(m_print)); } + PrintStateBase::StateWithWarnings step_state_with_warnings(PrintObjectStepEnum step) const { return m_state.state_with_warnings(step, PrintObjectBase::state_mutex(m_print)); } protected: PrintObjectBaseWithState(PrintType *print, ModelObject *model_object) : PrintObjectBase(model_object), m_print(print) {} @@ -416,6 +510,14 @@ protected: bool is_step_started_unguarded(PrintObjectStepEnum step) const { return m_state.is_started_unguarded(step); } bool is_step_done_unguarded(PrintObjectStepEnum step) const { return m_state.is_done_unguarded(step); } + // Add a slicing warning to the active PrintObject step and send a status notification. + // This method could be called multiple times between this->set_started() and this->set_done(). + void active_step_add_warning(PrintStateBase::WarningLevel warning_level, const std::string &message) { + PrintObjectStepEnum active_step = m_state.active_step_add_warning(warning_level, message, PrintObjectBase::state_mutex(m_print)); + if (m_print.m_status_callback) m_print.m_status_callback(SlicingStatus(*this, active_step)); + else printf("print object warning: %s\n", message.c_str()); + } + protected: // If the background processing stop was requested, throw CanceledException. // To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly. diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index a272075656..573fc2b0c8 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -429,6 +429,13 @@ public: bool finished() const override { return this->is_step_done(slaposSliceSupports) && this->Inherited::is_step_done(slapsRasterize); } const PrintObjects& objects() const { return m_objects; } + // PrintObject by its ObjectID, to be used to uniquely bind slicing warnings to their source PrintObjects + // in the notification center. + const SLAPrintObject* get_object(ObjectID object_id) const { + auto it = std::find_if(m_objects.begin(), m_objects.end(), + [object_id](const SLAPrintObject *obj) { return *static_cast(obj) == object_id; }); + return (it == m_objects.end()) ? nullptr : *it; + } const SLAPrintConfig& print_config() const { return m_print_config; } const SLAPrinterConfig& printer_config() const { return m_printer_config; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f1156271c8..ec13610b8c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3413,6 +3413,27 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) // Update the SLA preview. Only called if not RELOAD_SLA_SUPPORT_POINTS, as the block above will refresh the preview anyways. this->preview->reload_print(); } + + if (evt.status.flags & (PrintBase::SlicingStatus::UPDATE_PRINT_STEP_WARNINGS | PrintBase::SlicingStatus::UPDATE_PRINT_OBJECT_STEP_WARNINGS)) { + // Update notification center with warnings of object_id and its warning_step. + ObjectID object_id = evt.status.warning_object_id; + int warning_step = evt.status.warning_step; + PrintStateBase::StateWithWarnings state; + if (evt.status.flags & PrintBase::SlicingStatus::UPDATE_PRINT_STEP_WARNINGS) { + state = this->printer_technology == ptFFF ? + this->fff_print.step_state_with_warnings(static_cast(warning_step)) : + this->sla_print.step_state_with_warnings(static_cast(warning_step)); + } else if (this->printer_technology == ptFFF) { + const PrintObject *print_object = this->fff_print.get_object(object_id); + if (print_object) + state = print_object->step_state_with_warnings(static_cast(warning_step)); + } else { + const SLAPrintObject *print_object = this->sla_print.get_object(object_id); + if (print_object) + state = print_object->step_state_with_warnings(static_cast(warning_step)); + } + // Now process state.warnings. + } } void Plater::priv::on_slicing_completed(wxCommandEvent &) From f64da8e6ccde6e98f73f78aa3acd1557601328c6 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 15 Jul 2020 18:03:39 +0200 Subject: [PATCH 29/35] Update of back-end warnings: Back-end warnings contain two new members: "current" and "message_id". A warning is set to "not current" if its milestone is invalidated. --- src/libslic3r/PrintBase.cpp | 13 ++++ src/libslic3r/PrintBase.hpp | 116 ++++++++++++++++++++++++++++-------- 2 files changed, 103 insertions(+), 26 deletions(-) diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp index 81affe04d4..14339f3c62 100644 --- a/src/libslic3r/PrintBase.cpp +++ b/src/libslic3r/PrintBase.cpp @@ -88,6 +88,14 @@ std::string PrintBase::output_filepath(const std::string &path, const std::strin return path; } +void PrintBase::status_update_warnings(ObjectID object_id, int step, PrintStateBase::WarningLevel /* warning_level */, const std::string &message) +{ + if (this->m_status_callback) + m_status_callback(SlicingStatus(*this, step)); + else if (! message.empty()) + printf("%s warning: %s\n", (object_id == ObjectID(*this)) ? "print" : "print object", message.c_str()); +} + tbb::mutex& PrintObjectBase::state_mutex(PrintBase *print) { return print->state_mutex(); @@ -98,4 +106,9 @@ std::function PrintObjectBase::cancel_callback(PrintBase *print) return print->cancel_callback(); } +void PrintObjectBase::status_update_warnings(PrintBase *print, int step, PrintStateBase::WarningLevel warning_level, const std::string &message) +{ + print->status_update_warnings(*this, step, warning_level, message); +} + } // namespace Slic3r diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index 5e422ca701..65f1764e5b 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -50,12 +50,23 @@ public: struct Warning { + // Critical warnings will be displayed on G-code export in a modal dialog, so that the user cannot miss them. WarningLevel level; + // If the warning is not current, then it is in an unknown state. It may or may not be valid. + // A current warning will become non-current if its milestone gets invalidated. + // A non-current warning will either become current or it will be removed at the end of a milestone. + bool current; + // Message to be shown to the user, UTF8, localized. std::string message; + // If message_id == 0, then the message is expected to identify the warning uniquely. + // Otherwise message_id identifies the message. For example, if the message contains a varying number, then + // it cannot itself identify the message type. + int message_id; }; struct StateWithWarnings : public StateWithTimeStamp { + void mark_warnings_non_current() { for (auto &w : warnings) w.current = false; } std::vector warnings; }; @@ -132,15 +143,18 @@ public: PrintStateBase::StateWithWarnings &state = m_state[step]; state.state = STARTED; state.timestamp = ++ g_last_timestamp; - state.warnings.clear(); + state.mark_warnings_non_current(); m_step_active = static_cast(step); return true; } // Set the step as done. Block on mutex while the Print / PrintObject / PrintRegion objects are being // modified by the UI thread. + // Return value: + // Timestamp when this stepentered the DONE state. + // bool indicates whether the UI has to update the slicing warnings of this step or not. template - TimeStamp set_done(StepType step, tbb::mutex &mtx, ThrowIfCanceled throw_if_canceled) { + std::pair set_done(StepType step, tbb::mutex &mtx, ThrowIfCanceled throw_if_canceled) { tbb::mutex::scoped_lock lock(mtx); // If canceled, throw before changing the step state. throw_if_canceled(); @@ -150,7 +164,14 @@ public: state.state = DONE; state.timestamp = ++ g_last_timestamp; m_step_active = -1; - return state.timestamp; + // Remove all non-current warnings. + auto it = std::remove_if(state.warnings.begin(), state.warnings.end(), [](const auto &w) { return ! w.current; }); + bool update_warning_ui = false; + if (it != state.warnings.end()) { + state.warnings.erase(it, state.warnings.end()); + update_warning_ui = true; + } + return std::make_pair(state.timestamp, update_warning_ui); } // Make the step invalid. @@ -175,8 +196,8 @@ public: // the working thread proceed. cancel(); // Now the worker thread should be stopped, therefore it cannot write into the warnings field. - // It is safe to clear it. - state.warnings.clear(); + // It is safe to modify it. + state.mark_warnings_non_current(); m_step_active = -1; } return invalidated; @@ -205,9 +226,9 @@ public: // the working thread to proceed. cancel(); // Now the worker thread should be stopped, therefore it cannot write into the warnings field. - // It is safe to clear it. + // It is safe to modify the warnings. for (StepTypeIterator it = step_begin; it != step_end; ++ it) - m_state[*it].warnings.clear(); + m_state[*it].mark_warnings_non_current(); m_step_active = -1; } return invalidated; @@ -231,21 +252,46 @@ public: if (invalidated) { cancel(); // Now the worker thread should be stopped, therefore it cannot write into the warnings field. - // It is safe to clear it. + // It is safe to modify the warnings. for (size_t i = 0; i < COUNT; ++ i) - m_state[i].warnings.clear(); + m_state[i].mark_warnings_non_current(); m_step_active = -1; } return invalidated; } - StepType active_step_add_warning(PrintStateBase::WarningLevel warning_level, const std::string &message, tbb::mutex &mtx) + // Update list of warnings of the current milestone with a new warning. + // The warning may already exist in the list, marked as current or not current. + // If it already exists, mark it as current. + // Return value: + // Current milestone (StepType). + // bool indicates whether the UI has to be updated or not. + std::pair active_step_add_warning(PrintStateBase::WarningLevel warning_level, const std::string &message, int message_id, tbb::mutex &mtx) { tbb::mutex::scoped_lock lock(mtx); assert(m_step_active != -1); - assert(m_state[m_step_active].state == STARTED); - m_state[m_step_active].warnings.emplace_back(PrintStateBase::Warning{ warning_level, message}); - return static_cast(m_step_active); + StateWithWarnings &state = m_state[m_step_active]; + assert(state.state == STARTED); + std::pair retval(static_cast(m_step_active), true); + // Does a warning of the same level and message or message_id exist already? + auto it = (message_id == 0) ? + std::find_if(state.warnings.begin(), state.warnings.end(), [&message](const auto &w) { return w.message_id == 0 && w.message == message; }) : + std::find_if(state.warnings.begin(), state.warnings.end(), [message_id](const auto& w) { return w.message_id == message_id; }); + if (it == state.warnings.end()) + // No, create a new warning and update UI. + state.warnings.emplace_back(PrintStateBase::Warning{ warning_level, true, message, message_id }); + else if (it->message != message || it->level != warning_level) { + // Yes, however it needs an update. + it->message = message; + it->level = warning_level; + it->current = true; + } else if (it->current) + // Yes, and it is current. Don't update UI. + retval.second = false; + else + // Yes, but it is not current. Mark it as current. + it->current = true; + return retval; } private: @@ -268,8 +314,12 @@ protected: PrintObjectBase(ModelObject *model_object) : m_model_object(model_object) {} virtual ~PrintObjectBase() {} // Declared here to allow access from PrintBase through friendship. - static tbb::mutex& state_mutex(PrintBase *print); - static std::function cancel_callback(PrintBase *print); + static tbb::mutex& state_mutex(PrintBase *print); + static std::function cancel_callback(PrintBase *print); + // Notify UI about a new warning of a milestone "step" on this PrintObjectBase. + // The UI will be notified by calling a status callback registered on print. + // If no status callback is registered, the message is printed to console. + void status_update_warnings(PrintBase *print, int step, PrintStateBase::WarningLevel warning_level, const std::string &message); ModelObject *m_model_object; }; @@ -412,6 +462,10 @@ protected: tbb::mutex& state_mutex() const { return m_state_mutex; } std::function cancel_callback() { return m_cancel_callback; } void call_cancel_callback() { m_cancel_callback(); } + // Notify UI about a new warning of a milestone "step" on this PrintBase. + // The UI will be notified by calling a status callback. + // If no status callback is registered, the message is printed to console. + void status_update_warnings(ObjectID object_id, int step, PrintStateBase::WarningLevel warning_level, const std::string &message); // If the background processing stop was requested, throw CanceledException. // To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly. @@ -451,7 +505,12 @@ public: protected: bool set_started(PrintStepEnum step) { return m_state.set_started(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); } - PrintStateBase::TimeStamp set_done(PrintStepEnum step) { return m_state.set_done(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); } + PrintStateBase::TimeStamp set_done(PrintStepEnum step) { + std::pair status = m_state.set_done(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); + if (status.second) + this->status_update_warnings(*this, static_cast(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string()); + return status.first; + } bool invalidate_step(PrintStepEnum step) { return m_state.invalidate(step, this->cancel_callback()); } template @@ -467,10 +526,11 @@ protected: // Add a slicing warning to the active Print step and send a status notification. // This method could be called multiple times between this->set_started() and this->set_done(). - void active_step_add_warning(PrintStateBase::WarningLevel warning_level, const std::string &message) { - PrintStepEnum active_step = m_state.active_step_add_warning(warning_level, message, this->state_mutex()); - if (m_status_callback) m_status_callback(SlicingStatus(*this, active_step)); - else printf("print warning: %s\n", message.c_str()); + void active_step_add_warning(PrintStateBase::WarningLevel warning_level, const std::string &message, int message_id = 0) { + std::pair active_step = m_state.active_step_add_warning(warning_level, message, message_id, this->state_mutex()); + if (active_step.second) + // Update UI. + this->status_update_warnings(*this, static_cast(active_step.first), warning_level, message); } private: @@ -494,8 +554,12 @@ protected: bool set_started(PrintObjectStepEnum step) { return m_state.set_started(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); } - PrintStateBase::TimeStamp set_done(PrintObjectStepEnum step) - { return m_state.set_done(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); } + PrintStateBase::TimeStamp set_done(PrintObjectStepEnum step) { + std::pair status = m_state.set_done(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); + if (status.second) + this->status_update_warnings(m_print, static_cast(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string()); + return status.first; + } bool invalidate_step(PrintObjectStepEnum step) { return m_state.invalidate(step, PrintObjectBase::cancel_callback(m_print)); } @@ -512,10 +576,10 @@ protected: // Add a slicing warning to the active PrintObject step and send a status notification. // This method could be called multiple times between this->set_started() and this->set_done(). - void active_step_add_warning(PrintStateBase::WarningLevel warning_level, const std::string &message) { - PrintObjectStepEnum active_step = m_state.active_step_add_warning(warning_level, message, PrintObjectBase::state_mutex(m_print)); - if (m_print.m_status_callback) m_print.m_status_callback(SlicingStatus(*this, active_step)); - else printf("print object warning: %s\n", message.c_str()); + void active_step_add_warning(PrintStateBase::WarningLevel warning_level, const std::string &message, int message_id = 0) { + std::pair active_step = m_state.active_step_add_warning(warning_level, message, message_id, PrintObjectBase::state_mutex(m_print)); + if (active_step.second) + this->status_update_warnings(m_print, static_cast(active_step.first), warning_level, message); } protected: From ba0146746d88db8f4fff69a24c9f785c06c18005 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 15 Jul 2020 18:13:11 +0200 Subject: [PATCH 30/35] Fix of previous commit. --- src/libslic3r/PrintBase.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index 65f1764e5b..d7f3483e87 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -527,7 +527,7 @@ protected: // Add a slicing warning to the active Print step and send a status notification. // This method could be called multiple times between this->set_started() and this->set_done(). void active_step_add_warning(PrintStateBase::WarningLevel warning_level, const std::string &message, int message_id = 0) { - std::pair active_step = m_state.active_step_add_warning(warning_level, message, message_id, this->state_mutex()); + std::pair active_step = m_state.active_step_add_warning(warning_level, message, message_id, this->state_mutex()); if (active_step.second) // Update UI. this->status_update_warnings(*this, static_cast(active_step.first), warning_level, message); @@ -577,7 +577,7 @@ protected: // Add a slicing warning to the active PrintObject step and send a status notification. // This method could be called multiple times between this->set_started() and this->set_done(). void active_step_add_warning(PrintStateBase::WarningLevel warning_level, const std::string &message, int message_id = 0) { - std::pair active_step = m_state.active_step_add_warning(warning_level, message, message_id, PrintObjectBase::state_mutex(m_print)); + std::pair active_step = m_state.active_step_add_warning(warning_level, message, message_id, PrintObjectBase::state_mutex(m_print)); if (active_step.second) this->status_update_warnings(m_print, static_cast(active_step.first), warning_level, message); } From f326352cebec56c980572f0d093439def63961d2 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 16 Jul 2020 15:42:30 +0200 Subject: [PATCH 31/35] Empty layers check converted to a warning (except for the first layer) --- src/libslic3r/GCode.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 9a0c9eaa7d..7fc531b92f 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -617,6 +617,13 @@ std::vector GCode::collect_layers_to_print(const PrintObjec layers_to_print.emplace_back(layer_to_print); + // Check that there are extrusions on the very first layer. + if (layers_to_print.size() == 1u) { + if ((layer_to_print.object_layer && ! layer_to_print.object_layer->has_extrusions()) + || (layer_to_print.support_layer && ! layer_to_print.support_layer->has_extrusions())) + throw std::runtime_error(_(L("There is an object with no extrusions on the first layer."))); + } + // In case there are extrusions on this layer, check there is a layer to lay it on. if ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions. @@ -634,14 +641,18 @@ std::vector GCode::collect_layers_to_print(const PrintObjec bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions()); - if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) - throw std::runtime_error(_(L("Empty layers detected, the output would not be printable.")) + "\n\n" + + if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) { + const_cast(object.print())->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, + _(L("Empty layers detected, the output would not be printable.")) + "\n\n" + _(L("Object name")) + ": " + object.model_object()->name + "\n" + _(L("Print z")) + ": " + std::to_string(layers_to_print.back().print_z()) + "\n\n" + _(L("This is " "usually caused by negligibly small extrusions or by a faulty model. Try to repair " "the model or change its orientation on the bed."))); + } + // Remember last layer with extrusions. - last_extrusion_layer = &layers_to_print.back(); + if (has_extrusions) + last_extrusion_layer = &layers_to_print.back(); } } @@ -1939,7 +1950,6 @@ void GCode::process_layer( const size_t single_object_instance_idx) { assert(! layers.empty()); -// assert(! layer_tools.extruders.empty()); // Either printing all copies of all objects, or just a single copy of a single object. assert(single_object_instance_idx == size_t(-1) || layers.size() == 1); From 2de442b617a315437bf5c2a4aba1bbe435bc99aa Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 20 Jul 2020 13:31:39 +0200 Subject: [PATCH 32/35] Pull request #4235 - Fix tick/untick ironing feature in preview by rongith --- src/libslic3r/ExtrusionEntity.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 2 +- src/slic3r/GUI/GUI_Preview.cpp | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index c482f7edba..69b3a6455d 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -312,8 +312,8 @@ std::string ExtrusionEntity::role_to_string(ExtrusionRole role) case erOverhangPerimeter : return L("Overhang perimeter"); case erInternalInfill : return L("Internal infill"); case erSolidInfill : return L("Solid infill"); - case erIroning : return L("Ironing"); case erTopSolidInfill : return L("Top solid infill"); + case erIroning : return L("Ironing"); case erBridgeInfill : return L("Bridge infill"); case erGapFill : return L("Gap fill"); case erSkirt : return L("Skirt"); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index d6a23d75d3..0c1c828f7e 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1122,7 +1122,7 @@ void PrintConfigDef::init_fff_params() def = this->add("ironing_spacing", coFloat); def->label = L("Spacing between ironing passes"); - def->tooltip = L("Distance between ironing lins"); + def->tooltip = L("Distance between ironing lines"); def->sidetext = L("mm"); def->min = 0; def->mode = comExpert; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index c1e8b4c33c..b4606ab7f0 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -250,6 +250,7 @@ bool Preview::init(wxWindow* parent, Model* model) _(L("Internal infill")) + "|" + _(L("Solid infill")) + "|" + _(L("Top solid infill")) + "|" + + _(L("Ironing")) + "|" + _(L("Bridge infill")) + "|" + _(L("Gap fill")) + "|" + _(L("Skirt")) + "|" + From d910f7934b4b267b90a9766da4cbb3cc2c69b7af Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 21 Jul 2020 08:43:20 +0200 Subject: [PATCH 33/35] Empty layer check fix The test gave false positive in case there were supposed to be both object and support extrusions on the first layer --- src/libslic3r/GCode.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 7fc531b92f..35dc5a53bd 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -617,10 +617,12 @@ std::vector GCode::collect_layers_to_print(const PrintObjec layers_to_print.emplace_back(layer_to_print); + bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) + || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions()); + // Check that there are extrusions on the very first layer. if (layers_to_print.size() == 1u) { - if ((layer_to_print.object_layer && ! layer_to_print.object_layer->has_extrusions()) - || (layer_to_print.support_layer && ! layer_to_print.support_layer->has_extrusions())) + if (! has_extrusions) throw std::runtime_error(_(L("There is an object with no extrusions on the first layer."))); } @@ -637,10 +639,6 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // Negative support_contact_z is not taken into account, it can result in false positives in cases // where previous layer has object extrusions too (https://github.com/prusa3d/PrusaSlicer/issues/2752) - // Only check this layer in case it has some extrusions. - bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) - || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions()); - if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) { const_cast(object.print())->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, _(L("Empty layers detected, the output would not be printable.")) + "\n\n" + From 6e8006524009cd10322cc3418cf4500ceb833978 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 21 Jul 2020 09:08:48 +0200 Subject: [PATCH 34/35] Added well-known metadata to 3mf export --- src/libslic3r/Format/3mf.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 657e9ec983..edf55ba37e 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -4,6 +4,7 @@ #include "../GCode.hpp" #include "../Geometry.hpp" #include "../GCode/ThumbnailData.hpp" +#include "../Time.hpp" #include "../I18N.hpp" @@ -1991,7 +1992,7 @@ namespace Slic3r { bool _add_content_types_file_to_archive(mz_zip_archive& archive); bool _add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data); bool _add_relationships_file_to_archive(mz_zip_archive& archive); - bool _add_model_file_to_archive(mz_zip_archive& archive, const Model& model, IdToObjectDataMap &objects_data); + bool _add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, IdToObjectDataMap& objects_data); bool _add_object_to_model_stream(std::stringstream& stream, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets); bool _add_mesh_to_object_stream(std::stringstream& stream, ModelObject& object, VolumeToOffsetsMap& volumes_offsets); bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items); @@ -2054,7 +2055,7 @@ namespace Slic3r { // Adds model file ("3D/3dmodel.model"). // This is the one and only file that contains all the geometry (vertices and triangles) of all ModelVolumes. IdToObjectDataMap objects_data; - if (!_add_model_file_to_archive(archive, model, objects_data)) + if (!_add_model_file_to_archive(filename, archive, model, objects_data)) { close_zip_writer(&archive); boost::filesystem::remove(filename); @@ -2203,7 +2204,7 @@ namespace Slic3r { return true; } - bool _3MF_Exporter::_add_model_file_to_archive(mz_zip_archive& archive, const Model& model, IdToObjectDataMap &objects_data) + bool _3MF_Exporter::_add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, IdToObjectDataMap& objects_data) { std::stringstream stream; // https://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10 @@ -2214,6 +2215,19 @@ namespace Slic3r { stream << "\n"; stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3rpe=\"http://schemas.slic3r.org/3mf/2017/06\">\n"; stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_3MF_VERSION << "\">" << VERSION_3MF << "\n"; + std::string name = boost::filesystem::path(filename).stem().string(); + stream << " <" << METADATA_TAG << " name=\"Title\">" << name << "\n"; + stream << " <" << METADATA_TAG << " name=\"Designer\">" << "\n"; + stream << " <" << METADATA_TAG << " name=\"Description\">" << name << "\n"; + stream << " <" << METADATA_TAG << " name=\"Copyright\">" << "\n"; + stream << " <" << METADATA_TAG << " name=\"LicenseTerms\">" << "\n"; + stream << " <" << METADATA_TAG << " name=\"Rating\">" << "\n"; + std::string date = Slic3r::Utils::utc_timestamp(Slic3r::Utils::get_current_time_utc()); + // keep only the date part of the string + date = date.substr(0, 10); + stream << " <" << METADATA_TAG << " name=\"CreationDate\">" << date << "\n"; + stream << " <" << METADATA_TAG << " name=\"ModificationDate\">" << date << "\n"; + stream << " <" << METADATA_TAG << " name=\"Application\">" << SLIC3R_APP_KEY << "-" << SLIC3R_VERSION << "\n"; stream << " <" << RESOURCES_TAG << ">\n"; // Instance transformations, indexed by the 3MF object ID (which is a linear serialization of all instances of all ModelObjects). From 6057fb95958dd03dc8de8d0a92cd2017fbb40c6b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 21 Jul 2020 09:44:07 +0200 Subject: [PATCH 35/35] GUI_objectList: fixed typo in assert --- src/slic3r/GUI/GUI_ObjectList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index c11ed66ab8..d44db72b10 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -350,7 +350,7 @@ void ObjectList::get_selection_indexes(std::vector& obj_idxs, std::vectorGetItemType(item); - assert(type & itObject | itInstance | itInstanceRoot); + assert(type & (itObject | itInstance | itInstanceRoot)); obj_idxs.emplace_back(type & itObject ? m_objects_model->GetIdByItem(item) : m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)));