diff --git a/doc/deps-build/unix-static/Makefile b/doc/deps-build/unix-static/Makefile index 21740bc53..90d5083bb 100644 --- a/doc/deps-build/unix-static/Makefile +++ b/doc/deps-build/unix-static/Makefile @@ -26,10 +26,11 @@ TBB_SHA = a0dc9bf76d0120f917b641ed095360448cabc85b TBB = tbb-$(TBB_SHA) OPENSSL = openssl-OpenSSL_1_1_0g CURL = curl-7.58.0 +WXWIDGETS = wxWidgets-3.1.1 .PHONY: all destdir boost libcurl libopenssl libtbb -all: destdir boost libtbb libcurl +all: destdir boost libtbb libcurl wxwidgets @echo @echo "All done!" @echo @@ -131,5 +132,33 @@ $(CURL).tar.gz: curl -L -o $@ https://curl.haxx.se/download/$@ + +wxwidgets: $(WXWIDGETS).tar.bz2 + # tar -jxvf $(WXWIDGETS).tar.bz2 + cd $(WXWIDGETS) && ./configure \ + --prefix=$(DESTDIR)/usr/local \ + --disable-shared \ + --with-gtk=2 \ + --with-opengl \ + --enable-unicode \ + --enable-graphics_ctx \ + --with-regex=builtin \ + --with-libpng=builtin \ + --with-libxpm=builtin \ + --with-libjpeg=builtin \ + --with-libtiff=builtin \ + --with-zlib=builtin \ + --with-expat=builtin \ + --disable-precomp-headers \ + --enable-debug_info \ + --enable-debug_gdb + $(MAKE) -C $(WXWIDGETS) -j$(NPROC) + $(MAKE) -C $(WXWIDGETS)/locale allmo # XXX: ? + $(MAKE) -C $(WXWIDGETS) install #DESTDIR=$(DESTDIR) + +$(WXWIDGETS).tar.bz2: + curl -L -o $@ https://github.com/wxWidgets/wxWidgets/releases/download/v3.1.1/$@ + + clean: rm -rf $(BOOST) $(BOOST).tar.gz $(TBB) $(TBB).tar.gz $(OPENSSL) $(OPENSSL).tar.gz $(CURL) $(CURL).tar.gz diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index c7d4260d2..b834bc202 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -593,6 +593,7 @@ sub new { $self->on_process_completed($event->GetInt ? undef : $event->GetString); }); +# XXX: ??? { my $timer_id = Wx::NewId(); $self->{apply_config_timer} = Wx::Timer->new($self, $timer_id); @@ -872,7 +873,7 @@ sub on_layer_editing_toggled { $self->{canvas3D}->Update; } -sub GetFrame { +sub GetFrame { # XXX: main_frame in C++ Plater my ($self) = @_; return &Wx::GetTopLevelParent($self); } @@ -960,7 +961,7 @@ sub load_files { # Object indices for the UI. my @obj_idx = (); # Collected file names to display the final message on the status bar. - my @loaded_files = (); + my @loaded_files = (); # XXX: used??? # For all input files. for (my $i = 0; $i < @$input_files; $i += 1) { my $input_file = $input_files->[$i]; @@ -996,7 +997,7 @@ sub load_files { # objects imported from 3mf require a call to center_around_origin to have gizmos working properly and this call # need to be done after looks_like_multipart_object detection - if ($input_file =~ /.3[mM][fF]$/) + if ($input_file =~ /[.]3[mM][fF]$/) { foreach my $model_object (@{$model->objects}) { $model_object->center_around_origin; # also aligns object to Z = 0 @@ -1241,7 +1242,7 @@ sub set_number_of_copies { } } -sub _get_number_from_user { +sub _get_number_from_user { # XXX: Enrico my ($self, $title, $prompt_message, $error_message, $default, $only_positive) = @_; for (;;) { my $value = Wx::GetTextFromUser($prompt_message, $title, $default, $self); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index dc7de94b1..e2405fc83 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -61,7 +61,7 @@ class ModelObject friend class Model; public: std::string name; - std::string input_file; + std::string input_file; // XXX: consider fs::path // Instances of this ModelObject. Each instance defines a shift on the print bed, rotation around the Z axis and a uniform scaling. // Instances are owned by this ModelObject. ModelInstancePtrs instances; @@ -336,6 +336,7 @@ public: void swap(Model &other); ~Model() { this->clear_objects(); this->clear_materials(); } + // XXX: use fs::path ? static Model read_from_file(const std::string &input_file, DynamicPrintConfig *config = nullptr, bool add_default_instances = true); static Model read_from_archive(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances = true); diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 6d9d82d25..87cdc3777 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -56,6 +56,11 @@ inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); } inline Vec2f to_2d(const Vec3f &pt3) { return Vec2f (pt3(0), pt3(1)); } inline Vec2d to_2d(const Vec3d &pt3) { return Vec2d (pt3(0), pt3(1)); } +// inline Vec3crd to_3d(const Vec2crd &pt2, coord_t z) { return Vec3crd(pt2(0), pt2(1), z); } +// inline Vec3i64 to_3d(const Vec2i64 &pt2, int64_t z) { return Vec3i64(pt2(0), pt2(1), z); } +// inline Vec3f to_3d(const Vec2f &pt2, float z) { return Vec3f (pt2(0), pt2(1), z); } +// inline Vec3d to_3d(const Vec2d &pt2, double z) { return Vec3d (pt2(0), pt2(1), z); } + inline Vec2d unscale(coord_t x, coord_t y) { return Vec2d(unscale(x), unscale(y)); } inline Vec2d unscale(const Vec2crd &pt) { return Vec2d(unscale(pt(0)), unscale(pt(1))); } inline Vec2d unscale(const Vec2d &pt) { return Vec2d(unscale(pt(0)), unscale(pt(1))); } diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index a8888701c..06fb79a50 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -43,6 +43,8 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/GUI_App.hpp ${LIBDIR}/slic3r/GUI/MainFrame.cpp ${LIBDIR}/slic3r/GUI/MainFrame.hpp + ${LIBDIR}/slic3r/GUI/Plater.cpp + ${LIBDIR}/slic3r/GUI/Plater.hpp ${LIBDIR}/slic3r/GUI/LambdaObjectDialog.cpp ${LIBDIR}/slic3r/GUI/LambdaObjectDialog.hpp ${LIBDIR}/slic3r/GUI/Tab.cpp diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 16477a884..5e4e71881 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2876,10 +2876,13 @@ void GLCanvas3D::register_on_update_geometry_info_callback(void* callback) } #endif // ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM +// void GLCanvas3D::register_action_add_callback(GLToolbarItem::Callback callback) void GLCanvas3D::register_action_add_callback(void* callback) { if (callback != nullptr) m_action_add_callback.register_callback(callback); + // if (callback) + // m_action_add_callback = std::move(callback); } void GLCanvas3D::register_action_delete_callback(void* callback) @@ -3729,7 +3732,8 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = GUI::L_str("Add..."); item.sprite_id = 0; item.is_toggable = false; - item.action_callback = &m_action_add_callback; + // item.action_callback = &m_action_add_callback; + item.action_event = EVT_GLTOOLBAR_ADD; if (!m_toolbar.add_item(item)) return false; @@ -3737,7 +3741,8 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = GUI::L_str("Delete"); item.sprite_id = 1; item.is_toggable = false; - item.action_callback = &m_action_delete_callback; + // item.action_callback = &m_action_delete_callback; + item.action_event = EVT_GLTOOLBAR_DELETE; if (!m_toolbar.add_item(item)) return false; @@ -3745,7 +3750,8 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = GUI::L_str("Delete all"); item.sprite_id = 2; item.is_toggable = false; - item.action_callback = &m_action_deleteall_callback; + // item.action_callback = &m_action_deleteall_callback; + item.action_event = EVT_GLTOOLBAR_TODO_MORE; if (!m_toolbar.add_item(item)) return false; @@ -3753,7 +3759,8 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = GUI::L_str("Arrange"); item.sprite_id = 3; item.is_toggable = false; - item.action_callback = &m_action_arrange_callback; + // item.action_callback = &m_action_arrange_callback; + item.action_event = EVT_GLTOOLBAR_TODO_MORE; if (!m_toolbar.add_item(item)) return false; @@ -3764,7 +3771,8 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = GUI::L_str("Add instance"); item.sprite_id = 4; item.is_toggable = false; - item.action_callback = &m_action_more_callback; + // item.action_callback = &m_action_more_callback; + item.action_event = EVT_GLTOOLBAR_TODO_MORE; if (!m_toolbar.add_item(item)) return false; @@ -3772,7 +3780,8 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = GUI::L_str("Remove instance"); item.sprite_id = 5; item.is_toggable = false; - item.action_callback = &m_action_fewer_callback; + // item.action_callback = &m_action_fewer_callback; + item.action_event = EVT_GLTOOLBAR_TODO_MORE; if (!m_toolbar.add_item(item)) return false; @@ -3783,7 +3792,8 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = GUI::L_str("Split"); item.sprite_id = 6; item.is_toggable = false; - item.action_callback = &m_action_split_callback; + // item.action_callback = &m_action_split_callback; + item.action_event = EVT_GLTOOLBAR_TODO_MORE; if (!m_toolbar.add_item(item)) return false; @@ -3791,7 +3801,8 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = GUI::L_str("Cut..."); item.sprite_id = 7; item.is_toggable = false; - item.action_callback = &m_action_cut_callback; + // item.action_callback = &m_action_cut_callback; + item.action_event = EVT_GLTOOLBAR_TODO_MORE; if (!m_toolbar.add_item(item)) return false; @@ -3802,7 +3813,8 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = GUI::L_str("Settings..."); item.sprite_id = 8; item.is_toggable = false; - item.action_callback = &m_action_settings_callback; + // item.action_callback = &m_action_settings_callback; + item.action_event = EVT_GLTOOLBAR_TODO_MORE; if (!m_toolbar.add_item(item)) return false; @@ -3810,7 +3822,8 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = GUI::L_str("Layers editing"); item.sprite_id = 9; item.is_toggable = true; - item.action_callback = &m_action_layersediting_callback; + // item.action_callback = &m_action_layersediting_callback; + item.action_event = EVT_GLTOOLBAR_TODO_MORE; if (!m_toolbar.add_item(item)) return false; @@ -3821,7 +3834,8 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = GUI::L_str("Select by parts"); item.sprite_id = 10; item.is_toggable = true; - item.action_callback = &m_action_selectbyparts_callback; + // item.action_callback = &m_action_selectbyparts_callback; + item.action_event = EVT_GLTOOLBAR_TODO_MORE; if (!m_toolbar.add_item(item)) return false; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 451190161..7c0a7fe2e 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1,8 +1,11 @@ #ifndef slic3r_GLCanvas3D_hpp_ #define slic3r_GLCanvas3D_hpp_ -#include "../../slic3r/GUI/3DScene.hpp" -#include "../../slic3r/GUI/GLToolbar.hpp" +#include + +#include "3DScene.hpp" +#include "GLToolbar.hpp" +#include "callback.hpp" class wxTimer; class wxSizeEvent; @@ -529,6 +532,7 @@ class GLCanvas3D PerlCallback m_on_update_geometry_info_callback; #endif // ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM + // TODO: Remove these PerlCallback m_action_add_callback; PerlCallback m_action_delete_callback; PerlCallback m_action_deleteall_callback; @@ -545,6 +549,8 @@ public: GLCanvas3D(wxGLCanvas* canvas); ~GLCanvas3D(); + wxGLCanvas* widget() { return m_canvas; } + bool init(bool useVBOs, bool use_legacy_opengl); bool set_current(); @@ -661,6 +667,7 @@ public: void register_on_update_geometry_info_callback(void* callback); #endif // ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM + // void register_action_add_callback(GLToolbarItem::Callback); void register_action_add_callback(void* callback); void register_action_delete_callback(void* callback); void register_action_deleteall_callback(void* callback); diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 388868b12..196dc663f 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -6,19 +6,25 @@ #include +#include #include #include #include +#include namespace Slic3r { namespace GUI { + +wxDEFINE_EVENT(EVT_GLTOOLBAR_ADD, wxCommandEvent); +wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE, wxCommandEvent); +wxDEFINE_EVENT(EVT_GLTOOLBAR_TODO_MORE, wxCommandEvent); + GLToolbarItem::Data::Data() : name("") , tooltip("") , sprite_id(-1) , is_toggable(false) - , action_callback(nullptr) { } @@ -49,10 +55,12 @@ const std::string& GLToolbarItem::get_tooltip() const return m_data.tooltip; } -void GLToolbarItem::do_action() +void GLToolbarItem::do_action(wxEvtHandler *target) { - if (m_data.action_callback != nullptr) - m_data.action_callback->call(); + // if (m_data.action_callback != nullptr) + // m_data.action_callback->call(); + + wxPostEvent(target, wxCommandEvent(m_data.action_event)); } bool GLToolbarItem::is_enabled() const @@ -325,13 +333,13 @@ void GLToolbar::do_action(unsigned int item_id) item->set_state(GLToolbarItem::Hover); m_parent.render(); - item->do_action(); + item->do_action(m_parent.widget()); } else { item->set_state(GLToolbarItem::HoverPressed); m_parent.render(); - item->do_action(); + item->do_action(m_parent.widget()); if (item->get_state() != GLToolbarItem::Disabled) { // the item may get disabled during the action, if not, set it back to hover state diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index c67e78f58..45d22e785 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -1,17 +1,26 @@ #ifndef slic3r_GLToolbar_hpp_ #define slic3r_GLToolbar_hpp_ -#include "../../slic3r/GUI/GLTexture.hpp" -#include "callback.hpp" - +#include #include #include +#include + +#include "../../slic3r/GUI/GLTexture.hpp" +// #include "callback.hpp" + +class wxEvtHandler; + namespace Slic3r { namespace GUI { class GLCanvas3D; +wxDECLARE_EVENT(EVT_GLTOOLBAR_ADD, wxCommandEvent); +wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE, wxCommandEvent); +wxDECLARE_EVENT(EVT_GLTOOLBAR_TODO_MORE, wxCommandEvent); + class GLToolbarItem { public: @@ -32,13 +41,17 @@ public: Num_States }; + // typedef std::function Callback; + // typedef PerlCallback Callback; + struct Data { std::string name; std::string tooltip; unsigned int sprite_id; bool is_toggable; - PerlCallback* action_callback; + // Callback *action_callback; + wxEventType action_event; Data(); }; @@ -57,7 +70,7 @@ public: const std::string& get_name() const; const std::string& get_tooltip() const; - void do_action(); + void do_action(wxEvtHandler *target); bool is_enabled() const; bool is_hovered() const; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 17f3b53b2..6aeea4a74 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -48,7 +48,7 @@ bool GUI_App::OnInit() app_config = new AppConfig(); // set_app_config(app_config);// #ys_FIXME preset_bundle = new PresetBundle(); - // set_preset_bundle(preset_bundle);// #ys_FIXME + set_preset_bundle(preset_bundle); // #ys_FIXME // just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory // supplied as argument to --datadir; in that case we should still run the wizard diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 91a7b2742..99c3d2c1e 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -42,7 +42,7 @@ class Tab; class GUI_App : public wxApp { - bool no_plater{ true }; + bool no_plater{ false }; bool app_conf_exists{ false }; // Lock to guard the callback stack diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 6aefb9385..ee90cf297 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -118,6 +118,10 @@ void MainFrame::init_tabpanel() auto panel = m_tabpanel->GetCurrentPage(); // panel->OnActivate(); if panel->can('OnActivate'); + if (panel == nullptr) { + return; + } + for (auto& tab_name : { "print", "filament", "printer" }) { if (tab_name == panel->GetName()) { // On GTK, the wxEVT_NOTEBOOK_PAGE_CHANGED event is triggered @@ -131,13 +135,14 @@ void MainFrame::init_tabpanel() }); if (!m_no_plater) { -// m_plater = new Slic3r::GUI::Plater(m_tabpanel, -// event_object_selection_changed = > $OBJECT_SELECTION_CHANGED_EVENT, -// event_object_settings_changed = > $OBJECT_SETTINGS_CHANGED_EVENT, -// event_remove_object = > $OBJECT_REMOVE_EVENT, -// event_update_scene = > $UPDATE_SCENE_EVENT, -// ), L("Plater") -// m_tabpanel->AddPage(plater); + m_plater = new Slic3r::GUI::Plater(m_tabpanel, this); + // m_plater = new Slic3r::GUI::Plater(m_tabpanel, + // event_object_selection_changed = > $OBJECT_SELECTION_CHANGED_EVENT, + // event_object_settings_changed = > $OBJECT_SETTINGS_CHANGED_EVENT, + // event_remove_object = > $OBJECT_REMOVE_EVENT, + // event_update_scene = > $UPDATE_SCENE_EVENT, + // ), L("Plater") + m_tabpanel->AddPage(m_plater, _(L("Plater"))); } // The following event is emited by the C++ Tab implementation on config value change. diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index aff8a5985..f4c75de65 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -8,6 +8,8 @@ #include #include +#include "Plater.hpp" + class wxMenuBar; class wxNotebook; class wxPanel; @@ -45,7 +47,6 @@ class MainFrame : public wxFrame wxString m_qs_last_output_file = wxEmptyString; wxString m_last_config = wxEmptyString; - ProgressStatusBar* m_statusbar { nullptr }; AppController* m_appController { nullptr }; std::map m_options_tabs; @@ -70,6 +71,7 @@ public: void init_tabpanel(); + const std::map& options_tabs() const { return m_options_tabs; } Tab* get_preset_tab(const std::string& name); void create_preset_tabs(); void add_created_tab(Tab* panel); @@ -93,9 +95,10 @@ public: void select_view(const std::string& direction); - wxPanel* m_plater {nullptr}; + Plater* m_plater { nullptr }; wxNotebook* m_tabpanel { nullptr }; wxProgressDialog* m_progress_dialog { nullptr }; + ProgressStatusBar* m_statusbar { nullptr }; }; } // GUI diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp new file mode 100644 index 000000000..e48ae031b --- /dev/null +++ b/src/slic3r/GUI/Plater.cpp @@ -0,0 +1,908 @@ +#include "Plater.hpp" + +#include // XXX +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/GCode/PreviewData.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/Polygon.hpp" +// #include "libslic3r/BoundingBox.hpp" +#include "GUI.hpp" +#include "GUI_App.hpp" +#include "MainFrame.hpp" +#include "3DScene.hpp" +#include "GLToolbar.hpp" +#include "GUI_Preview.hpp" +#include "Tab.hpp" +#include "PresetBundle.hpp" +#include "BackgroundSlicingProcess.hpp" +#include "ProgressStatusBar.hpp" +#include "slic3r/Utils/ASCIIFolding.hpp" + +#include // Needs to be last because reasons :-/ + +namespace fs = boost::filesystem; +using Slic3r::_3DScene; +using Slic3r::Preset; + + +namespace Slic3r { +namespace GUI { + + +wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent); +wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, wxCommandEvent); + + +// Sidebar widgets + +// struct InfoBox : public wxStaticBox +// { +// InfoBox(wxWindow *parent, const wxString &label) : +// wxStaticBox(parent, wxID_ANY, label) +// { +// SetFont(GUI::small_font().Bold()); +// } +// }; + +class ObjectInfo : public wxStaticBoxSizer +{ +public: + ObjectInfo(wxWindow *parent); + +private: + wxStaticText *info_size; + wxStaticText *info_volume; + wxStaticText *info_facets; + wxStaticText *info_materials; + wxStaticText *info_manifold; + wxStaticBitmap *manifold_warning_icon; +}; + +ObjectInfo::ObjectInfo(wxWindow *parent) : + wxStaticBoxSizer(new wxStaticBox(parent, wxID_ANY, _(L("Info"))), wxVERTICAL) +{ + // GetStaticBox()->SetFont(GUI::bold_font()); // XXX: ? + + auto *grid_sizer = new wxFlexGridSizer(4, 5, 5); + grid_sizer->SetFlexibleDirection(wxHORIZONTAL); + grid_sizer->AddGrowableCol(1, 1); + grid_sizer->AddGrowableCol(3, 1); + + auto init_info_label = [parent, grid_sizer](wxStaticText **info_label, wxString text_label) { + auto *text = new wxStaticText(parent, wxID_ANY, text_label); + text->SetFont(GUI::small_font()); + *info_label = new wxStaticText(parent, wxID_ANY, ""); + (*info_label)->SetFont(GUI::small_font()); + grid_sizer->Add(text, 0); + grid_sizer->Add(*info_label, 0); + }; + + init_info_label(&info_size, _(L("Size"))); + init_info_label(&info_volume, _(L("Volume"))); + init_info_label(&info_facets, _(L("Facets"))); + init_info_label(&info_materials, _(L("Materials"))); + + auto *info_manifold_text = new wxStaticText(parent, wxID_ANY, _(L("Manifold"))); + info_manifold_text->SetFont(GUI::small_font()); + info_manifold = new wxStaticText(parent, wxID_ANY, ""); + info_manifold->SetFont(GUI::small_font()); + wxBitmap bitmap(GUI::from_u8(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG); + manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, bitmap); + auto *sizer_manifold = new wxBoxSizer(wxHORIZONTAL); + sizer_manifold->Add(info_manifold_text, 0); + sizer_manifold->Add(manifold_warning_icon, 0, wxLEFT, 2); + sizer_manifold->Add(info_manifold, 0, wxLEFT, 2); + grid_sizer->Add(sizer_manifold, 0, wxEXPAND | wxTOP, 4); + + Add(grid_sizer, 0, wxEXPAND); +} + +class SlicedInfo : public wxStaticBoxSizer +{ +public: + SlicedInfo(wxWindow *parent); + +private: + wxStaticText *info_filament_m; + wxStaticText *info_filament_mm3; + wxStaticText *info_filament_g; + wxStaticText *info_cost; + wxStaticText *info_time_normal; + wxStaticText *info_time_silent; +}; + +SlicedInfo::SlicedInfo(wxWindow *parent) : + wxStaticBoxSizer(new wxStaticBox(parent, wxID_ANY, _(L("Sliced Info"))), wxVERTICAL) +{ + // GetStaticBox()->SetFont(GUI::bold_font()); // XXX: ? + + auto *grid_sizer = new wxFlexGridSizer(2, 5, 5); + grid_sizer->SetFlexibleDirection(wxHORIZONTAL); + grid_sizer->AddGrowableCol(1, 1); + grid_sizer->AddGrowableCol(3, 1); + + auto init_info_label = [parent, grid_sizer](wxStaticText *&info_label, wxString text_label) { + auto *text = new wxStaticText(parent, wxID_ANY, text_label); + text->SetFont(GUI::small_font()); + info_label = new wxStaticText(parent, wxID_ANY, "N/A"); + info_label->SetFont(GUI::small_font()); + grid_sizer->Add(text, 0); + grid_sizer->Add(info_label, 0); + }; + + init_info_label(info_filament_m, _(L("Used Filament (m)"))); + init_info_label(info_filament_mm3, _(L("Used Filament (mm³)"))); + init_info_label(info_filament_g, _(L("Used Filament (g)"))); + init_info_label(info_cost, _(L("Cost"))); + init_info_label(info_time_normal, _(L("Estimated printing time (normal mode)"))); + init_info_label(info_time_silent, _(L("Estimated printing time (silent mode)"))); + + Add(grid_sizer, 0, wxEXPAND); +} + + +class PresetComboBox : public wxBitmapComboBox +{ +public: + PresetComboBox(wxWindow *parent, Preset::Type preset_type); + ~PresetComboBox(); + +private: + typedef std::size_t Marker; + enum { LABEL_ITEM_MARKER = 0x4d }; + + Preset::Type preset_type; + int last_selected; +}; + +PresetComboBox::PresetComboBox(wxWindow *parent, Preset::Type preset_type) : + wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY), + preset_type(preset_type), + last_selected(wxNOT_FOUND) +{ + Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &evt) { + auto selected_item = this->GetSelection(); + + auto marker = reinterpret_cast(this->GetClientData(selected_item)); + if (marker == LABEL_ITEM_MARKER) { + this->SetSelection(this->last_selected); + evt.StopPropagation(); + } else if (this->last_selected != selected_item) { + this->last_selected = selected_item; + evt.SetInt(this->preset_type); + } else { + evt.StopPropagation(); + } + }); +} + +PresetComboBox::~PresetComboBox() {} + + +// Sidebar / private + +struct Sidebar::priv +{ + // Sidebar *q; // PIMPL back pointer ("Q-Pointer") + + wxScrolledWindow *scrolled; + + wxFlexGridSizer *sizer_presets; + PresetComboBox *combo_print; + std::vector combos_filament; + wxBoxSizer *sizer_filaments; + PresetComboBox *combo_sla_material; + PresetComboBox *combo_printer; + + wxBoxSizer *sizer_params; + ObjectInfo *object_info; + SlicedInfo *sliced_info; + + wxButton *btn_export_gcode; + wxButton *btn_reslice; + // wxButton *btn_print; // XXX: remove + wxButton *btn_send_gcode; +}; + + +// Sidebar / public + +Sidebar::Sidebar(wxWindow *parent) + : wxPanel(parent), p(new priv) +{ + p->scrolled = new wxScrolledWindow(this); + p->scrolled->SetScrollbars(0, 1, 1, 1); // XXX + + // The preset chooser + p->sizer_presets = new wxFlexGridSizer(4, 2, 1, 2); + p->sizer_presets->AddGrowableCol(1, 1); + p->sizer_presets->SetFlexibleDirection(wxHORIZONTAL); + p->sizer_filaments = new wxBoxSizer(wxVERTICAL); + + auto init_combo = [this](PresetComboBox **combo, wxString label, Preset::Type preset_type, bool filament) { + auto *text = new wxStaticText(this, wxID_ANY, label); + text->SetFont(GUI::small_font()); + // combo = new wxBitmapComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY); + *combo = new PresetComboBox(this, preset_type); + + auto *sizer_presets = this->p->sizer_presets; + auto *sizer_filaments = this->p->sizer_filaments; + sizer_presets->Add(text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); + if (! filament) { + sizer_presets->Add(*combo, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 1); + } else { + sizer_filaments->Add(*combo, 1, wxEXPAND | wxBOTTOM, 1); + sizer_presets->Add(sizer_filaments, 1, wxEXPAND); + } + }; + + p->combos_filament.push_back(nullptr); + init_combo(&p->combo_print, _(L("Print settings")), Preset::TYPE_PRINT, false); + init_combo(&p->combos_filament[0], _(L("Filament")), Preset::TYPE_FILAMENT, true); + init_combo(&p->combo_sla_material, _(L("SLA material")), Preset::TYPE_SLA_MATERIAL, false); + init_combo(&p->combo_printer, _(L("Printer")), Preset::TYPE_PRINTER, false); + + // Frequently changed parameters + p->sizer_params = new wxBoxSizer(wxVERTICAL); + // GUI::add_frequently_changed_parameters(this, p->sizer_params, p->sizer_presets); + + // Buttons in the scrolled area + wxBitmap arrow_up(GUI::from_u8(Slic3r::var("brick_go.png")), wxBITMAP_TYPE_PNG); + p->btn_send_gcode = new wxButton(this, wxID_ANY, _(L("Send to printer"))); + p->btn_send_gcode->SetBitmap(arrow_up); + p->btn_send_gcode->Hide(); + auto *btns_sizer_scrolled = new wxBoxSizer(wxHORIZONTAL); + std::cerr << "btns_sizer_scrolled: " << btns_sizer_scrolled << std::endl; + btns_sizer_scrolled->Add(p->btn_send_gcode); + + // Info boxes + p->object_info = new ObjectInfo(this); + p->sliced_info = new SlicedInfo(this); + + // Sizer in the scrolled area + auto *scrolled_sizer = new wxBoxSizer(wxVERTICAL); + std::cerr << "scrolled_sizer: " << scrolled_sizer << std::endl; + scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, 2); + scrolled_sizer->Add(p->object_info, 1, wxEXPAND); + scrolled_sizer->Add(btns_sizer_scrolled, 0, wxEXPAND, 0); + scrolled_sizer->Add(p->sliced_info, 0, wxEXPAND); + + // Buttons underneath the scrolled area + p->btn_export_gcode = new wxButton(this, wxID_ANY, _(L("Export G-code…"))); + p->btn_reslice = new wxButton(this, wxID_ANY, _(L("Slice now"))); + + auto *btns_sizer = new wxBoxSizer(wxVERTICAL); + std::cerr << "btns_sizer: " << btns_sizer << std::endl; + btns_sizer->Add(p->btn_reslice); + btns_sizer->Add(p->btn_export_gcode); + + auto *sizer = new wxBoxSizer(wxVERTICAL); + std::cerr << "sizer: " << sizer << std::endl; + sizer->Add(scrolled_sizer); + sizer->Add(btns_sizer); + SetSizer(sizer); + SetMinSize(wxSize(320, -1)); +} + +Sidebar::~Sidebar() {} + +void Sidebar::update_presets(Preset::Type preset_type) +{ + // TODO: wxApp access + + switch (preset_type) { + case Preset::TYPE_FILAMENT: + // my $choice_idx = 0; + if (p->combos_filament.size() == 1) { + // Single filament printer, synchronize the filament presets. + // wxTheApp->{preset_bundle}->set_filament_preset(0, wxTheApp->{preset_bundle}->filament->get_selected_preset->name); + } + + for (size_t i = 0; i < p->combos_filament.size(); i++) { + // wxTheApp->{preset_bundle}->update_platter_filament_ui($choice_idx, $choice); + } + break; + + case Preset::TYPE_PRINT: + // wxTheApp->{preset_bundle}->print->update_platter_ui($choosers[0]); + break; + + case Preset::TYPE_SLA_MATERIAL: + // wxTheApp->{preset_bundle}->sla_material->update_platter_ui($choosers[0]); + break; + + case Preset::TYPE_PRINTER: + // Update the print choosers to only contain the compatible presets, update the dirty flags. + // wxTheApp->{preset_bundle}->print->update_platter_ui($self->{preset_choosers}{print}->[0]); + // Update the printer choosers, update the dirty flags. + // wxTheApp->{preset_bundle}->printer->update_platter_ui($choosers[0]); + // Update the filament choosers to only contain the compatible presets, update the color preview, + // update the dirty flags. + for (size_t i = 0; i < p->combos_filament.size(); i++) { + // wxTheApp->{preset_bundle}->update_platter_filament_ui($choice_idx, $choice); + } + break; + + default: break; + } + + // Synchronize config.ini with the current selections. + // wxTheApp->{preset_bundle}->export_selections(wxTheApp->{app_config}); +} + + +// Plater::Object + +struct PlaterObject +{ + std::string name; + bool selected; + + PlaterObject(std::string name) : name(std::move(name)), selected(false) {} +}; + +// Plater::DropTarget + +class PlaterDropTarget : public wxFileDropTarget +{ +public: + PlaterDropTarget(Plater *plater) : plater(plater) {} + + virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames); + +private: + Plater *plater; +}; + +bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames) +{ + // TODO + // return false; + throw 0; +} + +// Plater / private + +struct Plater::priv +{ + // PIMPL back pointer ("Q-Pointer") + Plater *q; + MainFrame *main_frame; + + // Data + Slic3r::DynamicPrintConfig *config; + Slic3r::Print print; + Slic3r::Model model; + Slic3r::GCodePreviewData gcode_preview_data; + std::vector objects; + + std::string export_gcode_output_file; + std::string send_gcode_file; + + // GUI elements + wxNotebook *notebook; + Sidebar *sidebar; + wxGLCanvas *canvas3D; // TODO: Use GLCanvas3D when we can + GUI::Preview *preview; + BackgroundSlicingProcess background_process; + + static const int gl_attrs[]; + static const std::regex pattern_bundle; + static const std::regex pattern_3mf; + static const std::regex pattern_zip_amf; + + priv(Plater *q, MainFrame *main_frame); + + std::vector collect_selections(); + void update(bool force_autocenter = false); + void update_ui_from_settings(); + ProgressStatusBar* statusbar(); + std::vector load_files(const std::vector &input_files); + size_t load_model_objects(const ModelObjectPtrs &model_objects); + + void on_notebook_changed(wxBookCtrlEvent &); + void on_select_preset(wxCommandEvent &); + void on_update_print_preview(wxCommandEvent &); + void on_process_completed(wxCommandEvent &); + void on_layer_editing_toggled(bool enable); + void on_action_add(const wxCommandEvent&); +}; + +// TODO: multisample, see 3DScene.pm +const int Plater::priv::gl_attrs[] = {WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 24, 0}; +const std::regex Plater::priv::pattern_bundle("[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)$", std::regex::icase); +const std::regex Plater::priv::pattern_3mf("[.]3mf$", std::regex::icase); +const std::regex Plater::priv::pattern_zip_amf("[.]zip[.]amf$", std::regex::icase); + +Plater::priv::priv(Plater *q, MainFrame *main_frame) : + q(q), + main_frame(main_frame), + config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({ + "bed_shape", "complete_objects", "extruder_clearance_radius", "skirts", "skirt_distance", + "brim_width", "variable_layer_height", "serial_port", "serial_speed", "host_type", "print_host", + "printhost_apikey", "printhost_cafile", "nozzle_diameter", "single_extruder_multi_material", + "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", + "extruder_colour", "filament_colour", "max_print_height", "printer_model" + })), + notebook(new wxNotebook(q, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_BOTTOM)), + sidebar(new Sidebar(q)), + canvas3D(new wxGLCanvas(notebook, wxID_ANY, gl_attrs)) +{ + background_process.set_print(&print); + background_process.set_gcode_preview_data(&gcode_preview_data); + background_process.set_sliced_event(EVT_SLICING_COMPLETED); + background_process.set_finished_event(EVT_PROCESS_COMPLETED); + + _3DScene::add_canvas(canvas3D); + notebook->AddPage(canvas3D, _(L("3D"))); + preview = new GUI::Preview(notebook, config, &print, &gcode_preview_data); + + // XXX: If have OpenGL + _3DScene::enable_picking(canvas3D, true); + _3DScene::enable_moving(canvas3D, true); + // XXX: more config from 3D.pm + // Slic3r::GUI::_3DScene::set_select_by($self, 'object'); + // Slic3r::GUI::_3DScene::set_drag_by($self, 'instance'); + // Slic3r::GUI::_3DScene::set_model($self, $model); + // Slic3r::GUI::_3DScene::set_print($self, $print); + // Slic3r::GUI::_3DScene::set_config($self, $config); + _3DScene::enable_gizmos(canvas3D, true); + _3DScene::enable_toolbar(canvas3D, true); + _3DScene::enable_shader(canvas3D, true); + _3DScene::enable_force_zoom_to_bed(canvas3D, true); + + // XXX: apply_config_timer + // { + // my $timer_id = Wx::NewId(); + // $self->{apply_config_timer} = Wx::Timer->new($self, $timer_id); + // EVT_TIMER($self, $timer_id, sub { + // my ($self, $event) = @_; + // $self->async_apply_config; + // }); + // } + + auto *bed_shape = config->opt("bed_shape"); + _3DScene::set_bed_shape(canvas3D, bed_shape->values); + _3DScene::zoom_to_bed(canvas3D); + preview->set_bed_shape(bed_shape->values); + + update(); + + // TODO: Preview::register_on_viewport_changed_callback is Perl-only + + auto *hsizer = new wxBoxSizer(wxHORIZONTAL); + hsizer->Add(notebook, 1, wxEXPAND | wxTOP, 1); + hsizer->Add(sidebar, 0, wxEXPAND | wxLEFT | wxRIGHT, 0); + q->SetSizer(hsizer); + + // Events: + + // Notebook page change event + notebook->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &priv::on_notebook_changed, this); + + // Preset change event + sidebar->Bind(wxEVT_COMBOBOX, &priv::on_select_preset, this); + + // Sidebar button events + sidebar->p->btn_export_gcode->Bind(wxEVT_BUTTON, [q](wxCommandEvent&) { q->export_gcode(""); }); + sidebar->p->btn_reslice->Bind(wxEVT_BUTTON, [q](wxCommandEvent&) { q->reslice(); }); + sidebar->p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + this->send_gcode_file = this->q->export_gcode(""); + }); + + // 3DScene events: + // TODO: (?) + // on_layer_editing_toggled + // on_action_add + canvas3D->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); + + q->Bind(EVT_SLICING_COMPLETED, &priv::on_update_print_preview, this); + q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this); + + // Drop target: + q->SetDropTarget(new PlaterDropTarget(q)); // if my understanding is right, wxWindow takes the owenership + + // TODO: ? + // # Send sizers/buttons to C++ + // Slic3r::GUI::set_objects_from_perl( $self->{scrolled_window_panel}, + // $frequently_changed_parameters_sizer, + // $info_sizer, + // $self->{btn_export_gcode}, + // # $self->{btn_export_stl}, + // $self->{btn_reslice}, + // $self->{btn_print}, + // $self->{btn_send_gcode}, + // $self->{object_info_manifold_warning_icon} ); + + // Slic3r::GUI::set_model_events_from_perl( $self->{model}, + // $self->{event_object_selection_changed}, + // $self->{event_object_settings_changed}, + // $self->{event_remove_object}, + // $self->{event_update_scene}); + + update_ui_from_settings(); + q->Layout(); +} + +std::vector Plater::priv::collect_selections() +{ + std::vector res; + for (const auto &obj : objects) { + res.push_back(obj.selected); + } + return res; +} + +void Plater::priv::update(bool force_autocenter) +{ + // TODO +} + +void Plater::priv::update_ui_from_settings() +{ + // TODO: (?) + // my ($self) = @_; + // if (defined($self->{btn_reslice}) && $self->{buttons_sizer}->IsShown($self->{btn_reslice}) != (! wxTheApp->{app_config}->get("background_processing"))) { + // $self->{buttons_sizer}->Show($self->{btn_reslice}, ! wxTheApp->{app_config}->get("background_processing")); + // $self->{buttons_sizer}->Layout; + // } +} + +ProgressStatusBar* Plater::priv::statusbar() +{ + return main_frame->m_statusbar; +} + +std::vector Plater::priv::load_files(const std::vector &input_files) +{ + if (input_files.size() < 1) { return std::vector(); } + + auto *nozzle_dmrs = config->opt("nozzle_diameter"); + + bool one_by_one = input_files.size() == 1 || nozzle_dmrs->values.size() <= 1; + if (! one_by_one) { + for (const auto &path : input_files) { + if (std::regex_match(path.string(), pattern_bundle)) { + one_by_one = true; + break; + } + } + } + + const auto loading = _(L("Loading…")); + wxProgressDialog dlg(loading, loading); + dlg.Pulse(); + + auto *new_model = one_by_one ? nullptr : new Slic3r::Model(); + std::vector obj_idx; // XXX: ? + + 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 = wxString::Format(_(L("Processing input file %s\n")), filename.string()); + dlg.Update(100 * i / 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); + + Slic3r::Model model; + try { + if (type_3mf || type_zip_amf) { + DynamicPrintConfig config; + config.apply(FullPrintConfig::defaults()); + model = Slic3r::Model::read_from_archive(path.string(), &config, false); + Preset::normalize(config); + wxGetApp().preset_bundle->load_config_model(filename.string(), std::move(config)); + for (const auto &kv : main_frame->options_tabs()) { kv.second->load_current_preset(); } + wxGetApp().app_config->update_config_dir(path.parent_path().string()); + // forces the update of the config here, or it will invalidate the imported layer heights profile if done using the timer + // and if the config contains a "layer_height" different from the current defined one + // TODO: + // $self->async_apply_config; + } else { + model = Slic3r::Model::read_from_file(path.string(), nullptr, false); + } + } + catch (const std::runtime_error &e) { + GUI::show_error(q, e.what()); + continue; + } + + // The model should now be initialized + + if (model.looks_like_multipart_object()) { + wxMessageDialog dlg(q, _(L( + "This file contains several objects positioned at multiple heights. " + "Instead of considering them as multiple objects, should I consider\n" + "this file as a single object having multiple parts?\n" + )), _(L("Multi-part object detected")), wxICON_WARNING | wxYES | wxNO); + if (dlg.ShowModal() == wxID_YES) { + model.convert_multipart_object(nozzle_dmrs->values.size()); + } + } + + if (type_3mf) { + for (ModelObject* model_object : model.objects) { + model_object->center_around_origin(); + } + } + + if (one_by_one) { + // TODO: + // push @obj_idx, $self->load_model_objects(@{$model->objects}); + // obj_idx.push_back(load_model_objects(model.objects); + } else { + // This must be an .stl or .obj file, which may contain a maximum of one volume. + for (const ModelObject* model_object : model.objects) { + new_model->add_object(*model_object); + } + } + } + + if (new_model != nullptr) { + wxMessageDialog dlg(q, _(L( + "Multiple objects were loaded for a multi-material printer.\n" + "Instead of considering them as multiple objects, should I consider\n" + "these files to represent a single object having multiple parts?\n" + )), _(L("Multi-part object detected")), wxICON_WARNING | wxYES | wxNO); + if (dlg.ShowModal() == wxID_YES) { + new_model->convert_multipart_object(nozzle_dmrs->values.size()); + } + + // TODO: + // push @obj_idx, $self->load_model_objects(@{$new_model->objects}); + // obj_idx.push_back(load_model_objects(new_model->objects); + } + + wxGetApp().app_config->update_skein_dir(input_files[input_files.size() - 1].parent_path().string()); + // XXX: Plater.pm had @loaded_files, but didn't seem to fill them with the filenames... + statusbar()->set_status_text(_(L("Loaded"))); + return obj_idx; +} + + +// TODO: move to Point.hpp +// inline Vec3crd to_3d(const Vec2crd &pt2, coord_t z) { return Vec3crd(pt2(0), pt2(1), z); } +// inline Vec3i64 to_3d(const Vec2i64 &pt2, int64_t z) { return Vec3i64(pt2(0), pt2(1), z); } +// inline Vec3f to_3d(const Vec2f &pt2, float z) { return Vec3f (pt2(0), pt2(1), z); } +// inline Vec3d to_3d(const Vec2d &pt2, double z) { return Vec3d (pt2(0), pt2(1), z); } +// typedef Eigen::Matrix Vec2crd; +template Eigen::Matrix to_3d(const Eigen::Matrix &m, T z) +{ + return Eigen::Matrix(m(0), m(1), z); +} + +Vec3crd to_3d(const Point &p, coord_t z) +{ + return Vec3crd(p(0), p(1), z); +} + +size_t Plater::priv::load_model_objects(const ModelObjectPtrs &model_objects) +{ + // TODO + + auto *bed_shape_opt = config->opt("bed_shape"); + const auto bed_shape = Slic3r::Polygon::new_scale(bed_shape_opt->values); + const BoundingBox bed_shape_bb = bed_shape.bounding_box(); + // const Point bed_center_2 = bed_shape_bb.center(); + // const Vec3d bed_center(bed_center_2(0), bed_center_2(1), 0.0); + // const Vec3d bed_center = to_3d(bed_shape_bb.center(), 0).cast(); + const Vec3d bed_center = to_3d(bed_shape_bb.center().cast(), 0.0); + const Vec2d bed_size = bed_shape_bb.size().cast(); + + bool need_arrange = false; + bool scaled_down = false; + // my @obj_idx = (); // XXX + std::vector obj_idx; + + for (const ModelObject *model_object : model_objects) { + auto *object = model.add_object(*model_object); + std::string object_name = object->name.empty() ? fs::path(object->input_file).filename().string() : object->name; + objects.emplace_back(std::move(object_name)); + obj_idx.push_back(objects.size() - 1); + + if (model_object->instances.size() == 0) { + // if object has no defined position(s) we need to rearrange everything after loading + need_arrange = true; + + // add a default instance and center object around origin + object->center_around_origin(); // also aligns object to Z = 0 + auto *instance = object->add_instance(); + instance->set_offset(bed_center); + } + + // my $size = $o->bounding_box->size; + // const auto size = object->bounding_box().size(); + // my $ratio = max($size->x / unscale($bed_size->x), $size->y / unscale($bed_size->y)); + // size.cwiseQuotient(bed_size); + // const auto = size / bed_size; + // const auto ratio = std::max(size.x / Slic3r::unscale(bed_size(0)), size.y / Slic3r::unscale(bed_size(1))); + // if ($ratio > 10000) { + // # the size of the object is too big -> this could lead to overflow when moving to clipper coordinates, + // # so scale down the mesh + // $o->scale_xyz(Slic3r::Pointf3->new(1/$ratio, 1/$ratio, 1/$ratio)); + // $scaled_down = 1; + // } + // elsif ($ratio > 5) { + // $_->set_scaling_factor(1/$ratio) for @{$o->instances}; + // $scaled_down = 1; + // } + } + + throw 0; + return 0; +} + +void Plater::priv::on_notebook_changed(wxBookCtrlEvent&) +{ + const auto current_id = notebook->GetCurrentPage()->GetId(); + if (current_id == canvas3D->GetId()) { + if (_3DScene::is_reload_delayed(canvas3D)) { + _3DScene::set_objects_selections(canvas3D, collect_selections()); + _3DScene::reload_scene(canvas3D, true); + } + // sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably) + _3DScene::set_as_dirty(canvas3D); + } else if (current_id == preview->GetId()) { + preview->reload_print(); + preview->set_canvas_as_dirty(); + } +} + +void Plater::priv::on_select_preset(wxCommandEvent &evt) +{ + auto preset_type = static_cast(evt.GetInt()); + auto *combo = static_cast(evt.GetEventObject()); + + if (preset_type == Preset::TYPE_FILAMENT) { + // FIXME: + // wxTheApp->{preset_bundle}->set_filament_preset($idx, $choice->GetStringSelection); + } + + // TODO: ? + if (false) { + // if ($group eq 'filament' && @{$self->{preset_choosers}{filament}} > 1) { + // # Only update the platter UI for the 2nd and other filaments. + // wxTheApp->{preset_bundle}->update_platter_filament_ui($idx, $choice); + // } + } else { + auto selected_item = combo->GetSelection(); + + // TODO: Handle by an event handler in MainFrame, if needed + } + + // TODO: + // # Synchronize config.ini with the current selections. + // wxTheApp->{preset_bundle}->export_selections(wxTheApp->{app_config}); + // # get new config and generate on_config_change() event for updating plater and other things + // $self->on_config_change(wxTheApp->{preset_bundle}->full_config); +} + +void Plater::priv::on_update_print_preview(wxCommandEvent &) +{ + // TODO +} + +void Plater::priv::on_process_completed(wxCommandEvent &) +{ + // TODO +} + +void Plater::priv::on_layer_editing_toggled(bool enable) +{ + _3DScene::enable_layers_editing(canvas3D, enable); + if (enable && !_3DScene::is_layers_editing_enabled(canvas3D)) { + // Initialization of the OpenGL shaders failed. Disable the tool. + _3DScene::enable_toolbar_item(canvas3D, "layersediting", false); + } + canvas3D->Refresh(); + canvas3D->Update(); +} + +void Plater::priv::on_action_add(const wxCommandEvent&) +{ + wxArrayString input_files; + wxGetApp().open_model(q, input_files); + + std::vector input_paths; + for (const auto &file : input_files) { + input_paths.push_back(file.wx_str()); + } + load_files(input_paths); +} + +// Plater / Public + +Plater::Plater(wxWindow *parent, MainFrame *main_frame) + : wxPanel(parent), p(new priv(this, main_frame)) +{ + // Initialization performed in the private c-tor +} + +Plater::~Plater() +{ + _3DScene::remove_canvas(p->canvas3D); +} + +Sidebar& Plater::sidebar() { return *p->sidebar; } + +std::string Plater::export_gcode(const std::string &output_path) +{ + if (p->objects.size() == 0) { return ""; } + + if (! p->export_gcode_output_file.empty()) { + GUI::show_error(this, _(L("Another export job is currently running."))); + return ""; + } + + // wxTheApp->{preset_bundle}->full_config->validate; // FIXME + const std::string err = p->print.validate(); + if (! err.empty()) { + // The config is not valid + GUI::show_error(this, _(err)); + return ""; + } + + // Copy the names of active presets into the placeholder parser. + // wxTheApp->{preset_bundle}->export_selections_pp($self->{print}->placeholder_parser); // FIXME + + // select output file + if (! output_path.empty()) { + p->export_gcode_output_file = p->print.output_filepath(output_path); + // FIXME: ^ errors to handle? + } else { + // FIXME: + std::string default_output_file; // FIXME: tmp + // my $default_output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') }; + // Slic3r::GUI::catch_error($self) and return; + + // If possible, remove accents from accented latin characters. + // This function is useful for generating file names to be processed by legacy firmwares. + default_output_file = Slic3r::fold_utf8_to_ascii(default_output_file); + wxFileDialog dlg(this, _(L("Save G-code file as:")), + wxEmptyString, + wxEmptyString, + Slic3r::GUI::FILE_WILDCARDS.at("gcode"), + wxFD_SAVE | wxFD_OVERWRITE_PROMPT + ); + // FIXME: ^ defaultDir: + // wxTheApp->{app_config}->get_last_output_dir(dirname($default_output_file)), + // FIXME: ^ defaultFile: + // basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + + if (dlg.ShowModal() != wxID_OK) { return ""; } + auto path = dlg.GetPath(); + // wxTheApp->{app_config}->update_last_output_dir(dirname($path)); // FIXME + p->export_gcode_output_file = path; + } + + return p->export_gcode_output_file; +} + +void Plater::reslice() +{ + // TODO +} + + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp new file mode 100644 index 000000000..415e9faab --- /dev/null +++ b/src/slic3r/GUI/Plater.hpp @@ -0,0 +1,60 @@ +#ifndef slic3r_Plater_hpp_ +#define slic3r_Plater_hpp_ + +#include + +#include +#include + +#include "Preset.hpp" + + +namespace Slic3r { +namespace GUI { + +class MainFrame; + +class Sidebar : public wxPanel +{ +public: + Sidebar(wxWindow *parent); + Sidebar(Sidebar &&) = delete; + Sidebar(const Sidebar &) = delete; + Sidebar &operator=(Sidebar &&) = delete; + Sidebar &operator=(const Sidebar &) = delete; + ~Sidebar(); + + void update_presets(Slic3r::Preset::Type preset_type); +private: + struct priv; + std::unique_ptr p; + + friend class Plater; // XXX: better encapsulation? +}; + + +class Plater: public wxPanel +{ +public: + Plater(wxWindow *parent, MainFrame *main_frame); + Plater(Plater &&) = delete; + Plater(const Plater &) = delete; + Plater &operator=(Plater &&) = delete; + Plater &operator=(const Plater &) = delete; + ~Plater(); + + Sidebar& sidebar(); + + // TODO: use fs::path + // Note: empty string means request default path + std::string export_gcode(const std::string &output_path); + void reslice(); +private: + struct priv; + std::unique_ptr p; +}; + + +}} + +#endif diff --git a/src/slic3r/GUI/Preset.hpp b/src/slic3r/GUI/Preset.hpp index 821d7dc54..bed936411 100644 --- a/src/slic3r/GUI/Preset.hpp +++ b/src/slic3r/GUI/Preset.hpp @@ -177,11 +177,11 @@ public: static const std::vector& sla_material_options(); static void update_suffix_modified(); + static void normalize(DynamicPrintConfig &config); protected: friend class PresetCollection; friend class PresetBundle; - static void normalize(DynamicPrintConfig &config); // Resize the extruder specific vectors () static void set_num_extruders(DynamicPrintConfig &config, unsigned int n); static const std::string& suffix_modified(); diff --git a/src/slic3r/GUI/PresetBundle.hpp b/src/slic3r/GUI/PresetBundle.hpp index 68ec534da..3cd8df54b 100644 --- a/src/slic3r/GUI/PresetBundle.hpp +++ b/src/slic3r/GUI/PresetBundle.hpp @@ -73,6 +73,11 @@ public: void load_config(const std::string &name, DynamicPrintConfig config) { this->load_config_file_config(name, false, std::move(config)); } + // Load configuration that comes from a model file containing configuration, such as 3MF et al. + // This method is called by the Plater. + void load_config_model(const std::string &name, DynamicPrintConfig config) + { this->load_config_file_config(name, true, std::move(config)); } + // Load an external config file containing the print, filament and printer presets. // Instead of a config file, a G-code may be loaded containing the full set of parameters. // In the future the configuration will likely be read from an AMF file as well. diff --git a/src/slic3r/GUI/callback.hpp b/src/slic3r/GUI/callback.hpp index e031e3260..53f750e16 100644 --- a/src/slic3r/GUI/callback.hpp +++ b/src/slic3r/GUI/callback.hpp @@ -10,7 +10,7 @@ namespace Slic3r { class PerlCallback { public: - PerlCallback(void *) {} + // PerlCallback(void *) {} PerlCallback() {} void register_callback(void *) {} void deregister_callback() {}