From ada744d7182a48dbf70e0f770d00c7e9a8e2cda2 Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Sun, 13 May 2018 21:18:50 -0500 Subject: [PATCH] Implement generic object rotation in all 3 axes with a slider and box. --- src/GUI/Dialogs/AnglePicker.hpp | 89 ++++++++++++++++++ src/GUI/MainFrame.cpp | 6 +- src/GUI/Plater.cpp | 154 ++++++++++++++++++++++++++++++-- src/GUI/Plater.hpp | 12 ++- src/GUI/misc_ui.cpp | 18 ++++ src/GUI/misc_ui.hpp | 12 ++- 6 files changed, 270 insertions(+), 21 deletions(-) create mode 100644 src/GUI/Dialogs/AnglePicker.hpp diff --git a/src/GUI/Dialogs/AnglePicker.hpp b/src/GUI/Dialogs/AnglePicker.hpp new file mode 100644 index 000000000..7b6bc6d3e --- /dev/null +++ b/src/GUI/Dialogs/AnglePicker.hpp @@ -0,0 +1,89 @@ +#ifndef ANGLEPICKER_HPP +#define ANGLEPICKER_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace Slic3r { namespace GUI { + +/// Dialog to produce a special dialog with both a slider for +/- 360 degree rotation and a text box. +/// Supports decimal numbers via integer scaling. +template +class AnglePicker : public wxDialog { +public: + AnglePicker(wxWindow* parent, const wxString& title, double initial_angle) : + wxDialog(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE, wxString("AnglePicker")), _angle(scaling * initial_angle) { + + auto* lbl_min = new wxStaticText(this, wxID_ANY, wxString(L"-360°"), wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); + auto* lbl_max = new wxStaticText(this, wxID_ANY, wxString(L"360°"), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + auto* lbl_txt = new wxStaticText(this, wxID_ANY, wxString("Angle "), wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); + + auto btn_sizer = new wxBoxSizer(wxHORIZONTAL); + btn_sizer->Add( + new wxButton(this, wxID_OK, _("OK"), wxDefaultPosition, wxDefaultSize), + 0, + wxALL, + 10); + btn_sizer->Add( + new wxButton(this, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxDefaultSize), + 0, + wxALL, + 10); + + + this->slider = new wxSlider(this, wxID_ANY, _angle, -360*scaling, 360*scaling, wxDefaultPosition, wxSize(255, wxDefaultSize.y)); + this->manual_entry = new wxTextCtrl(this, wxID_ANY, wxString("") << angle(), wxDefaultPosition, wxDefaultSize); + + + this->hsizer = new wxBoxSizer(wxHORIZONTAL); + this->hsizer->Add(lbl_min, wxALIGN_LEFT); + this->hsizer->Add(this->slider, wxALIGN_CENTER); + this->hsizer->Add(lbl_max, wxALIGN_RIGHT); + + auto text_sizer {new wxBoxSizer(wxHORIZONTAL)}; + text_sizer->Add(lbl_txt, 0); + text_sizer->Add(this->manual_entry, 0); + + this->vsizer = new wxBoxSizer(wxVERTICAL); + + this->vsizer->Add(this->hsizer, 0); + this->vsizer->Add(text_sizer, 0); + this->vsizer->Add(btn_sizer, 0, wxALIGN_CENTER); + + this->SetSizerAndFit(vsizer); + + this->Bind(wxEVT_SLIDER, [this](wxCommandEvent &e){ + wxString str; + _angle = this->slider->GetValue(); + str.Printf("%f", this->angle()); + this->manual_entry->SetValue(str); + }); + this->Bind(wxEVT_TEXT, [this](wxCommandEvent &e){ + wxString str {this->manual_entry->GetValue()}; + double value {0.0}; + if (str.ToDouble(&value)) + if (value <= 360.0 && value >= -360.0) + this->slider->SetValue(value * scaling); + }); + } + double angle() { return double(_angle) / double(scaling) ; } + +private: + /// Scaled integer + int _angle {0}; + + wxSlider* slider {nullptr}; + wxTextCtrl* manual_entry {nullptr}; + wxSizer* hsizer {nullptr}; + wxSizer* vsizer {nullptr}; + +}; + +}} + +#endif// ANGLEPICKER_HPP diff --git a/src/GUI/MainFrame.cpp b/src/GUI/MainFrame.cpp index 2eb0d2ec3..e1facce0f 100644 --- a/src/GUI/MainFrame.cpp +++ b/src/GUI/MainFrame.cpp @@ -120,11 +120,9 @@ void MainFrame::init_menubar() wxMenu* menuPlater = new wxMenu(); { - append_menu_item(menuPlater, _(L"Arrange…"), _("Arrange models on plater"), [=](wxCommandEvent& e) { if (this->plater != nullptr) this->plater->arrange();}, wxID_ANY, "bricks.png", "Ctrl+G"); - } - wxMenu* menuObject = new wxMenu(); - { + append_menu_item(menuPlater, _(L"Arrange…"), _("Arrange models on plater"), [this](wxCommandEvent& e) { if (this->plater != nullptr) this->plater->arrange();}, wxID_ANY, "bricks.png", "Ctrl+G"); } + wxMenu* menuObject = this->plater->object_menu(); wxMenu* menuSettings = new wxMenu(); { } diff --git a/src/GUI/Plater.cpp b/src/GUI/Plater.cpp index 7532f6160..898bb945c 100644 --- a/src/GUI/Plater.cpp +++ b/src/GUI/Plater.cpp @@ -9,6 +9,8 @@ #include "MainFrame.hpp" #include "BoundingBox.hpp" #include "Geometry.hpp" +#include "Dialogs/AnglePicker.hpp" + namespace Slic3r { namespace GUI { @@ -450,11 +452,6 @@ void Plater::object_settings_dialog(ObjRef obj) { } -wxMenu* Plater::object_menu() { - return new wxMenu(); -} - - void Plater::select_object(ObjRef obj) { for (auto& o : this->objects) { o.selected = false; @@ -733,8 +730,26 @@ void Plater::decrease(size_t copies, bool dont_push) { this->on_model_change(); } +void Plater::rotate(Axis axis, bool dont_push) { + ObjRef obj {this->selected_object()}; + if (obj == this->objects.end()) return; + double angle {0.0}; + + auto* model_object {this->model->objects.at(obj->identifier)}; + auto model_instance {model_object->instances.begin()}; + + // pop a menu to get the angle + auto* pick = new AnglePicker<1000>(this, "Set Angle", angle); + if (pick->ShowModal() == wxID_OK) { + angle = pick->angle(); + pick->Destroy(); // cleanup afterwards. + this->rotate(angle, axis, dont_push); + } else { + pick->Destroy(); // cleanup afterwards. + } +} + void Plater::rotate(double angle, Axis axis, bool dont_push) { - //TODO ObjRef obj {this->selected_object()}; if (obj == this->objects.end()) return; @@ -761,7 +776,7 @@ void Plater::rotate(double angle, Axis axis, bool dont_push) { this->print->add_model_object(model_object, obj->identifier); if (!dont_push) { - // TODO + add_undo_operation(UndoCmd::Rotate, obj->identifier, angle, axis); } this->selection_changed(); @@ -797,6 +812,9 @@ void Plater::add_undo_operation(UndoCmd cmd, int obj_id, Slic3r::Model& model) { void Plater::add_undo_operation(UndoCmd cmd, int obj_id, size_t copies) { } +void Plater::add_undo_operation(UndoCmd cmd, int obj_id, double angle, Axis axis) { +} + void Plater::object_list_changed() { //TODO } @@ -816,5 +834,127 @@ void Plater::resume_background_process() { //TODO } +wxMenu* Plater::object_menu() { + auto* frame {this->GetFrame()}; + auto* menu {new wxMenu()}; + + append_menu_item(menu, _("Delete"), _("Remove the selected object."), [=](wxCommandEvent& e) { this->remove();}, wxID_ANY, "brick_delete.png", "Ctrl+Del"); +/* + wxTheApp->append_menu_item($menu, "Delete\tCtrl+Del", 'Remove the selected object', sub { + $self->remove; + }, undef, 'brick_delete.png'); + wxTheApp->append_menu_item($menu, "Increase copies\tCtrl++", 'Place one more copy of the selected object', sub { + $self->increase; + }, undef, 'add.png'); + wxTheApp->append_menu_item($menu, "Decrease copies\tCtrl+-", 'Remove one copy of the selected object', sub { + $self->decrease; + }, undef, 'delete.png'); + wxTheApp->append_menu_item($menu, "Set number of copies…", 'Change the number of copies of the selected object', sub { + $self->set_number_of_copies; + }, undef, 'textfield.png'); + $menu->AppendSeparator(); + wxTheApp->append_menu_item($menu, "Move to bed center", 'Center object around bed center', sub { + $self->center_selected_object_on_bed; + }, undef, 'arrow_in.png'); + wxTheApp->append_menu_item($menu, "Rotate 45° clockwise", 'Rotate the selected object by 45° clockwise', sub { + $self->rotate(-45); + }, undef, 'arrow_rotate_clockwise.png'); + wxTheApp->append_menu_item($menu, "Rotate 45° counter-clockwise", 'Rotate the selected object by 45° counter-clockwise', sub { + $self->rotate(+45); + }, undef, 'arrow_rotate_anticlockwise.png'); + */ + { + auto* rotateMenu {new wxMenu}; + append_menu_item(rotateMenu, _(L"Around X axis…"), _("Rotate the selected object by an arbitrary angle around X axis."), [this](wxCommandEvent& e) { this->rotate(X); }, wxID_ANY, "bullet_red.png"); + + append_menu_item(rotateMenu, _(L"Around Y axis…"), _("Rotate the selected object by an arbitrary angle around Y axis."), [this](wxCommandEvent& e) { this->rotate(Y); }, wxID_ANY, "bullet_green.png"); + + append_menu_item(rotateMenu, _(L"Around Z axis…"), _("Rotate the selected object by an arbitrary angle around Z axis."), [this](wxCommandEvent& e) { this->rotate(Z); }, wxID_ANY, "bullet_blue.png"); + + append_submenu(menu, _("Rotate"), _("Rotate the selected object by an arbitrary angle"), rotateMenu, wxID_ANY, "textfield.png"); + } + /* + + { + my $mirrorMenu = Wx::Menu->new; + wxTheApp->append_menu_item($mirrorMenu, "Along X axis…", 'Mirror the selected object along the X axis', sub { + $self->mirror(X); + }, undef, 'bullet_red.png'); + wxTheApp->append_menu_item($mirrorMenu, "Along Y axis…", 'Mirror the selected object along the Y axis', sub { + $self->mirror(Y); + }, undef, 'bullet_green.png'); + wxTheApp->append_menu_item($mirrorMenu, "Along Z axis…", 'Mirror the selected object along the Z axis', sub { + $self->mirror(Z); + }, undef, 'bullet_blue.png'); + wxTheApp->append_submenu($menu, "Mirror", 'Mirror the selected object', $mirrorMenu, undef, 'shape_flip_horizontal.png'); + } + + { + my $scaleMenu = Wx::Menu->new; + wxTheApp->append_menu_item($scaleMenu, "Uniformly…", 'Scale the selected object along the XYZ axes', sub { + $self->changescale(undef); + }); + wxTheApp->append_menu_item($scaleMenu, "Along X axis…", 'Scale the selected object along the X axis', sub { + $self->changescale(X); + }, undef, 'bullet_red.png'); + wxTheApp->append_menu_item($scaleMenu, "Along Y axis…", 'Scale the selected object along the Y axis', sub { + $self->changescale(Y); + }, undef, 'bullet_green.png'); + wxTheApp->append_menu_item($scaleMenu, "Along Z axis…", 'Scale the selected object along the Z axis', sub { + $self->changescale(Z); + }, undef, 'bullet_blue.png'); + wxTheApp->append_submenu($menu, "Scale", 'Scale the selected object by a given factor', $scaleMenu, undef, 'arrow_out.png'); + } + + { + my $scaleToSizeMenu = Wx::Menu->new; + wxTheApp->append_menu_item($scaleToSizeMenu, "Uniformly…", 'Scale the selected object along the XYZ axes', sub { + $self->changescale(undef, 1); + }); + wxTheApp->append_menu_item($scaleToSizeMenu, "Along X axis…", 'Scale the selected object along the X axis', sub { + $self->changescale(X, 1); + }, undef, 'bullet_red.png'); + wxTheApp->append_menu_item($scaleToSizeMenu, "Along Y axis…", 'Scale the selected object along the Y axis', sub { + $self->changescale(Y, 1); + }, undef, 'bullet_green.png'); + wxTheApp->append_menu_item($scaleToSizeMenu, "Along Z axis…", 'Scale the selected object along the Z axis', sub { + $self->changescale(Z, 1); + }, undef, 'bullet_blue.png'); + wxTheApp->append_submenu($menu, "Scale to size", 'Scale the selected object to match a given size', $scaleToSizeMenu, undef, 'arrow_out.png'); + } + + wxTheApp->append_menu_item($menu, "Split", 'Split the selected object into individual parts', sub { + $self->split_object; + }, undef, 'shape_ungroup.png'); + wxTheApp->append_menu_item($menu, "Cut…", 'Open the 3D cutting tool', sub { + $self->object_cut_dialog; + }, undef, 'package.png'); + wxTheApp->append_menu_item($menu, "Layer heights…", 'Open the dynamic layer height control', sub { + $self->object_layers_dialog; + }, undef, 'variable_layer_height.png'); + $menu->AppendSeparator(); + wxTheApp->append_menu_item($menu, "Settings…", 'Open the object editor dialog', sub { + $self->object_settings_dialog; + }, undef, 'cog.png'); + $menu->AppendSeparator(); + wxTheApp->append_menu_item($menu, "Reload from Disk", 'Reload the selected file from Disk', sub { + $self->reload_from_disk; + }, undef, 'arrow_refresh.png'); + wxTheApp->append_menu_item($menu, "Export object as STL…", 'Export this single object as STL file', sub { + $self->export_object_stl; + }, undef, 'brick_go.png'); + wxTheApp->append_menu_item($menu, "Export object and modifiers as AMF…", 'Export this single object and all associated modifiers as AMF file', sub { + $self->export_object_amf; + }, undef, 'brick_go.png'); + wxTheApp->append_menu_item($menu, "Export object and modifiers as 3MF…", 'Export this single object and all associated modifiers as 3MF file', sub { + $self->export_object_tmf; + }, undef, 'brick_go.png'); + + return $menu; +} +*/ + return menu; +} + }} // Namespace Slic3r::GUI diff --git a/src/GUI/Plater.hpp b/src/GUI/Plater.hpp index 518931834..a1c81e60b 100644 --- a/src/GUI/Plater.hpp +++ b/src/GUI/Plater.hpp @@ -31,7 +31,7 @@ namespace Slic3r { namespace GUI { using UndoOperation = int; enum class UndoCmd { - Remove, Add, Reset, Increase, Decrease + Remove, Add, Reset, Increase, Decrease, Rotate }; using ObjIdx = unsigned int; @@ -70,6 +70,12 @@ public: /// Undo for increase/decrease void add_undo_operation(UndoCmd cmd, int obj_id, size_t copies); + /// Undo for increase/decrease + void add_undo_operation(UndoCmd cmd, int obj_id, double angle, Axis axis); + + /// Create menu for object. + wxMenu* object_menu(); + private: std::shared_ptr print {std::make_shared(Slic3r::Print())}; std::shared_ptr model {std::make_shared(Slic3r::Model())}; @@ -158,8 +164,6 @@ private: void object_settings_dialog(ObjIdx obj_idx); void object_settings_dialog(ObjRef obj); - /// Create and launch menu for object. - wxMenu* object_menu(); /// Instantiate the toolbar void build_toolbar(); @@ -173,6 +177,8 @@ private: /// Remove instances of the currently selected model. void decrease(size_t copies = 1, bool dont_push = false); + /// Rotate the currently selected model, triggering a user prompt. + void rotate(Axis axis = Z, bool dont_push = false); /// Rotate the currently selected model. void rotate(double angle, Axis axis = Z, bool dont_push = false); diff --git a/src/GUI/misc_ui.cpp b/src/GUI/misc_ui.cpp index f04adc8b7..41c1f17f0 100644 --- a/src/GUI/misc_ui.cpp +++ b/src/GUI/misc_ui.cpp @@ -64,6 +64,24 @@ void fatal_error(wxWindow* parent, const wxString& message) { throw std::runtime_error(message.ToStdString()); } +wxMenuItem* append_submenu(wxMenu* menu, const wxString& name, const wxString& help, wxMenu* submenu, int id, const wxString& icon) { + auto* item {new wxMenuItem(menu, id, name, help)}; + + set_menu_item_icon(item,icon); + item->SetSubMenu(submenu); + menu->Append(item); + return item; +} + +void set_menu_item_icon(wxMenuItem* item, const wxString& icon) { + if (!icon.IsEmpty()) { + wxBitmap ico; + if(ico.LoadFile(var(icon), wxBITMAP_TYPE_PNG)) + item->SetBitmap(ico); + else + std::cerr<< var(icon) << " failed to load \n"; + } +} /* sub append_submenu { diff --git a/src/GUI/misc_ui.hpp b/src/GUI/misc_ui.hpp index fb3930325..e592618ea 100644 --- a/src/GUI/misc_ui.hpp +++ b/src/GUI/misc_ui.hpp @@ -107,6 +107,8 @@ void show_info(wxWindow* parent, const wxString& message, const wxString& title) /// Show an error messagebox and then throw an exception. void fatal_error(wxWindow* parent, const wxString& message); +/// Assign a menu item icon +void set_menu_item_icon(wxMenuItem* item, const wxString& icon); template void append_menu_item(wxMenu* menu, const wxString& name,const wxString& help, T lambda, int id = wxID_ANY, const wxString& icon = "", const wxString& accel = "") { @@ -117,18 +119,14 @@ void append_menu_item(wxMenu* menu, const wxString& name,const wxString& help, T tmp->SetAccel(a); // set the accelerator if and only if the accelerator is fine } tmp->SetHelp(help); - if (!icon.IsEmpty()) { - wxBitmap ico; - if(ico.LoadFile(var(icon), wxBITMAP_TYPE_PNG)) - tmp->SetBitmap(ico); - else - std::cerr<< var(icon) << " failed to load \n"; - } + set_menu_item_icon(tmp, icon); if (typeid(lambda) != typeid(nullptr)) menu->Bind(wxEVT_MENU, lambda, tmp->GetId(), tmp->GetId()); } +wxMenuItem* append_submenu(wxMenu* menu, const wxString& name, const wxString& help, wxMenu* submenu, int id = wxID_ANY, const wxString& icon = ""); + /* sub CallAfter { my ($self, $cb) = @_;