From 9ca0fed4b5c8d1c7dbb9b0e5a35435503dcb539a Mon Sep 17 00:00:00 2001 From: Benjamin Landers Date: Mon, 2 Jul 2018 15:16:34 -0700 Subject: [PATCH] Added the rough form of ObjectCutDialog OptionsGroup needs to be more complete before moving ahead with this --- src/CMakeLists.txt | 1 + src/GUI/Dialogs/ObjectCutDialog.cpp | 351 ++++++++++++++++++++++++++++ src/GUI/Dialogs/ObjectCutDialog.hpp | 51 ++++ src/GUI/Plater.cpp | 8 + 4 files changed, 411 insertions(+) create mode 100644 src/GUI/Dialogs/ObjectCutDialog.cpp create mode 100644 src/GUI/Dialogs/ObjectCutDialog.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bdd272f3e..6767d1a25 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -246,6 +246,7 @@ IF(wxWidgets_FOUND) ${GUI_LIBDIR}/Dialogs/PrintEditor.cpp ${GUI_LIBDIR}/Dialogs/PrinterEditor.cpp ${GUI_LIBDIR}/Dialogs/MaterialEditor.cpp + ${GUI_LIBDIR}/Dialogs/ObjectCutDialog.cpp ${GUI_LIBDIR}/GUI.cpp ${GUI_LIBDIR}/MainFrame.cpp ${GUI_LIBDIR}/Plater.cpp diff --git a/src/GUI/Dialogs/ObjectCutDialog.cpp b/src/GUI/Dialogs/ObjectCutDialog.cpp new file mode 100644 index 000000000..75f412b08 --- /dev/null +++ b/src/GUI/Dialogs/ObjectCutDialog.cpp @@ -0,0 +1,351 @@ +#include "ObjectCutDialog.hpp" + + +// Cut an object at a Z position, keep either the top or the bottom of the object. +// This dialog gets opened with the "Cut..." button above the platter. + +namespace Slic3r { namespace GUI { + +ObjectCutDialog::ObjectCutDialog(wxWindow* parent, ModelObject* _model_object/*, ...*/): wxDialog(parent, -1, _(_model_object->name), wxDefaultPosition, wxSize(500, 500), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), model_object(_model_object) { + +/* + $self->{model_object}->transform_by_instance($self->{model_object}->get_instance(0), 1); + + // cut options + my $size_z = $self->{model_object}->instance_bounding_box(0)->size->z; + $self->{cut_options} = { + axis => Z, + z => $size_z/2, + keep_upper => 0, + keep_lower => 1, + rotate_lower => 0, + preview => 1, + }; + + my $optgroup; + $optgroup = $self->{optgroup} = Slic3r::GUI::OptionsGroup->new( + parent => $self, + title => 'Cut', + on_change => sub { + my ($opt_id) = @_; + # There seems to be an issue with wxWidgets 3.0.2/3.0.3, where the slider + # genates tens of events for a single value change. + # Only trigger the recalculation if the value changes + # or a live preview was activated and the mesh cut is not valid yet. + if ($self->{cut_options}{$opt_id} != $optgroup->get_value($opt_id) || + ! $self->{mesh_cut_valid} && $self->_life_preview_active()) { + $self->{cut_options}{$opt_id} = $optgroup->get_value($opt_id); + $self->{mesh_cut_valid} = 0; + wxTheApp->CallAfter(sub { + $self->_update; + }); + } + }, + label_width => 120, + ); + $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => 'axis', + type => 'select', + label => 'Axis', + labels => ['X','Y','Z'], + values => [X,Y,Z], + default => $self->{cut_options}{axis}, + )); + $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => 'z', + type => 'slider', + label => 'Z', + default => $self->{cut_options}{z}, + min => 0, + max => $size_z, + full_width => 1, + )); + { + my $line = Slic3r::GUI::OptionsGroup::Line->new( + label => 'Keep', + ); + $line->append_option(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => 'keep_upper', + type => 'bool', + label => 'Upper part', + default => $self->{cut_options}{keep_upper}, + )); + $line->append_option(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => 'keep_lower', + type => 'bool', + label => 'Lower part', + default => $self->{cut_options}{keep_lower}, + )); + $optgroup->append_line($line); + } + $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => 'rotate_lower', + label => 'Rotate lower part upwards', + type => 'bool', + tooltip => 'If enabled, the lower part will be rotated by 180° so that the flat cut surface lies on the print bed.', + default => $self->{cut_options}{rotate_lower}, + )); + $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => 'preview', + label => 'Show preview', + type => 'bool', + tooltip => 'If enabled, object will be cut in real time.', + default => $self->{cut_options}{preview}, + )); + { + my $cut_button_sizer = Wx::BoxSizer->new(wxVERTICAL); + + $self->{btn_cut} = Wx::Button->new($self, -1, "Perform cut", wxDefaultPosition, wxDefaultSize); + $self->{btn_cut}->SetDefault; + $cut_button_sizer->Add($self->{btn_cut}, 0, wxALIGN_RIGHT | wxALL, 10); + + $self->{btn_cut_grid} = Wx::Button->new($self, -1, "Cut by grid…", wxDefaultPosition, wxDefaultSize); + $cut_button_sizer->Add($self->{btn_cut_grid}, 0, wxALIGN_RIGHT | wxALL, 10); + + $optgroup->append_line(Slic3r::GUI::OptionsGroup::Line->new( + sizer => $cut_button_sizer, + )); + } + + # left pane with tree + my $left_sizer = Wx::BoxSizer->new(wxVERTICAL); + $left_sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); + + # right pane with preview canvas + my $canvas; + if ($Slic3r::GUI::have_OpenGL) { + $canvas = $self->{canvas} = Slic3r::GUI::3DScene->new($self); + $canvas->load_object($self->{model_object}, undef, [0]); + $canvas->set_auto_bed_shape; + $canvas->SetSize([500,500]); + $canvas->SetMinSize($canvas->GetSize); + $canvas->zoom_to_volumes; + } + + $self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL); + $self->{sizer}->Add($left_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); + $self->{sizer}->Add($canvas, 1, wxEXPAND | wxALL, 0) if $canvas; + + $self->SetSizer($self->{sizer}); + $self->SetMinSize($self->GetSize); + $self->{sizer}->SetSizeHints($self); + + EVT_BUTTON($self, $self->{btn_cut}, sub { + // Recalculate the cut if the preview was not active. + $self->_perform_cut() unless $self->{mesh_cut_valid}; + + // Adjust position / orientation of the split object halves. + if (my $lower = $self->{new_model_objects}[0]) { + if ($self->{cut_options}{rotate_lower} && $self->{cut_options}{axis} == Z) { + $lower->rotate(PI, X); + } + $lower->center_around_origin; # align to Z = 0 + } + if (my $upper = $self->{new_model_objects}[1]) { + $upper->center_around_origin; # align to Z = 0 + } + + // Note that the window was already closed, so a pending update will not be executed. + already_closed = true; + EndModal(wxID_OK); + Destroy(); + }); + + EVT_BUTTON($self, $self->{btn_cut_grid}, sub { + my $grid_x = Wx::GetTextFromUser("Enter the width of the desired tiles along the X axis:", + "Cut by Grid", 100, $self); + return if !looks_like_number($grid_x) || $grid_x <= 0; + + my $grid_y = Wx::GetTextFromUser("Enter the width of the desired tiles along the Y axis:", + "Cut by Grid", 100, $self); + return if !looks_like_number($grid_y) || $grid_y <= 0; + + my $process_dialog = Wx::ProgressDialog->new('Cutting…', "Cutting model by grid…", 100, $self, 0); + $process_dialog->Pulse; + + my $meshes = $self->{model_object}->mesh->cut_by_grid(Slic3r::Pointf->new($grid_x, $grid_y)); + $self->{new_model_objects} = []; + + my $bb = $self->{model_object}->bounding_box; + $self->{new_model} = my $model = Slic3r::Model->new; + for my $i (0..$#$meshes) { + push @{$self->{new_model_objects}}, my $o = $model->add_object( + name => sprintf('%s (%d)', $self->{model_object}->name, $i+1), + ); + my $v = $o->add_volume( + mesh => $meshes->[$i], + name => $o->name, + ); + $o->center_around_origin; + my $i = $o->add_instance( + offset => Slic3r::Pointf->new(@{$o->origin_translation->negative}[X,Y]), + ); + $i->offset->translate( + 5 * ceil(($i->offset->x - $bb->center->x) / $grid_x), + 5 * ceil(($i->offset->y - $bb->center->y) / $grid_y), + ); + } + + $process_dialog->Destroy; + + # Note that the window was already closed, so a pending update will not be executed. + $self->{already_closed} = 1; + $self->EndModal(wxID_OK); + $self->Destroy(); + }); + + EVT_CLOSE($self, sub { + # Note that the window was already closed, so a pending update will not be executed. + already_closed = true; + EndModal(wxID_CANCEL); + Destroy(); + }); +*/ + _update(); + +} + +// scale Z down to original size since we're using the transformed mesh for 3D preview +// and cut dialog but ModelObject::cut() needs Z without any instance transformation +void ObjectCutDialog::_mesh_slice_z_pos(){ +/* + my $bb = $self->{model_object}->instance_bounding_box(0); + my $z = $self->{cut_options}{axis} == X ? $bb->x_min + : $self->{cut_options}{axis} == Y ? $bb->y_min + : $bb->z_min; + + $z += $self->{cut_options}{z} / $self->{model_object}->instances->[0]->scaling_factor; + + return $z; +*/ +} + +// Only perform live preview if just a single part of the object shall survive. +void ObjectCutDialog::_life_preview_active() { + /* + return $self->{cut_options}{preview} && ($self->{cut_options}{keep_upper} != $self->{cut_options}{keep_lower}); + */ +} + +// Slice the mesh, keep the top / bottom part. +void ObjectCutDialog::_perform_cut(){ + + + // Early exit. If the cut is valid, don't recalculate it. + if (mesh_cut_valid) return; + +/* + my $z = $self->_mesh_slice_z_pos(); + + my ($new_model) = $self->{model_object}->cut($self->{cut_options}{axis}, $z); + my ($upper_object, $lower_object) = @{$new_model->objects}; + $self->{new_model} = $new_model; + $self->{new_model_objects} = []; + if ($self->{cut_options}{keep_upper} && $upper_object->volumes_count > 0) { + $self->{new_model_objects}[1] = $upper_object; + } + if ($self->{cut_options}{keep_lower} && $lower_object->volumes_count > 0) { + $self->{new_model_objects}[0] = $lower_object; + } +*/ + mesh_cut_valid = true; +} + +void ObjectCutDialog::_update() { + + // Don't update if the window was already closed. + // We are not sure whether the action planned by wxTheApp->CallAfter() may be triggered after the window is closed. + // Probably not, but better be safe than sorry, which is espetially true on multiple platforms. + if (already_closed) return; +/* + # Only recalculate the cut, if the live cut preview is active. + my $life_preview_active = $self->_life_preview_active(); + $self->_perform_cut() if $life_preview_active; + + { + # scale Z down to original size since we're using the transformed mesh for 3D preview + # and cut dialog but ModelObject::cut() needs Z without any instance transformation + my $z = $self->_mesh_slice_z_pos(); + + # update canvas + if ($self->{canvas}) { + # get volumes to render + my @objects = (); + if ($life_preview_active) { + push @objects, grep defined, @{$self->{new_model_objects}}; + } else { + push @objects, $self->{model_object}; + } + + # get section contour + my @expolygons = (); + foreach my $volume (@{$self->{model_object}->volumes}) { + next if !$volume->mesh; + next if $volume->modifier; + my $expp = $volume->mesh->slice_at($self->{cut_options}{axis}, $z); + push @expolygons, @$expp; + } + + my $offset = $self->{model_object}->instances->[0]->offset; + foreach my $expolygon (@expolygons) { + $self->{model_object}->instances->[0]->transform_polygon($_) + for @$expolygon; + + if ($self->{cut_options}{axis} != X) { + $expolygon->translate(0, Slic3r::Geometry::scale($offset->y)); #) + } + if ($self->{cut_options}{axis} != Y) { + $expolygon->translate(Slic3r::Geometry::scale($offset->x), 0); + } + } + + $self->{canvas}->reset_objects; + $self->{canvas}->load_object($_, undef, [0]) for @objects; + + my $plane_z = $self->{cut_options}{z}; + $plane_z += 0.02 if !$self->{cut_options}{keep_upper}; + $plane_z -= 0.02 if !$self->{cut_options}{keep_lower}; + $self->{canvas}->SetCuttingPlane( + $self->{cut_options}{axis}, + $plane_z, + [@expolygons], + ); + $self->{canvas}->Render; + } + } + + # update controls + { + my $z = $self->{cut_options}{z}; + my $optgroup = $self->{optgroup}; + { + my $bb = $self->{model_object}->instance_bounding_box(0); + my $max = $self->{cut_options}{axis} == X ? $bb->size->x + : $self->{cut_options}{axis} == Y ? $bb->size->y ### + : $bb->size->z; + $optgroup->get_field('z')->set_range(0, $max); + } + $optgroup->get_field('keep_upper')->toggle(my $have_upper = abs($z - $optgroup->get_option('z')->max) > 0.1); + $optgroup->get_field('keep_lower')->toggle(my $have_lower = $z > 0.1); + $optgroup->get_field('rotate_lower')->toggle($z > 0 && $self->{cut_options}{keep_lower} && $self->{cut_options}{axis} == Z); + $optgroup->get_field('preview')->toggle($self->{cut_options}{keep_upper} != $self->{cut_options}{keep_lower}); + + # update cut button + if (($self->{cut_options}{keep_upper} && $have_upper) + || ($self->{cut_options}{keep_lower} && $have_lower)) { + $self->{btn_cut}->Enable; + } else { + $self->{btn_cut}->Disable; + } + } +*/ +} + +void ObjectCutDialog::NewModelObjects() { +/* + my ($self) = @_; + return grep defined, @{ $self->{new_model_objects} }; +*/ +} + +}} // namespace Slic3r::GUI diff --git a/src/GUI/Dialogs/ObjectCutDialog.hpp b/src/GUI/Dialogs/ObjectCutDialog.hpp new file mode 100644 index 000000000..f87cc66ef --- /dev/null +++ b/src/GUI/Dialogs/ObjectCutDialog.hpp @@ -0,0 +1,51 @@ +#ifndef OBJECTCUTDIALOG_HPP +#define OBJECTCUTDIALOG_HPP + + +#include + +#include "Scene3D.hpp" +#include "Model.hpp" + +namespace Slic3r { namespace GUI { + +class ObjectCutCanvas : public Scene3D { + // Not sure yet + +protected: + // Draws the cutting plane + void after_render(); +}; + +struct CutOptions { + float z; + enum {X,Y,Z} axis; + bool keep_upper, keep_lower, rotate_lower; + bool preview; +}; + +class ObjectCutDialog : public wxDialog { +public: + ObjectCutDialog(wxWindow* parent, ModelObject* _model); +private: + // Mark whether the mesh cut is valid. + // If not, it needs to be recalculated by _update() on wxTheApp->CallAfter() or on exit of the dialog. + bool mesh_cut_valid = false; + // Note whether the window was already closed, so a pending update is not executed. + bool already_closed = false; + +// ObjectCutCanvas canvas; + ModelObject* model_object; + + //std::shared_ptr model; + + void _mesh_slice_z_pos(); + void _life_preview_active(); + void _perform_cut(); + void _update(); + void NewModelObjects(); + +}; + +}} // namespace Slic3r::GUI +#endif // OBJECTCUTDIALOG_HPP diff --git a/src/GUI/Plater.cpp b/src/GUI/Plater.cpp index f72a70b96..cfad2149b 100644 --- a/src/GUI/Plater.cpp +++ b/src/GUI/Plater.cpp @@ -13,6 +13,7 @@ #include "BoundingBox.hpp" #include "Geometry.hpp" #include "Dialogs/AnglePicker.hpp" +#include "Dialogs/ObjectCutDialog.hpp" namespace Slic3r { namespace GUI { @@ -888,6 +889,13 @@ void Plater::changescale() { void Plater::object_cut_dialog() { //TODO + ObjRef obj {this->selected_object()}; + if (obj == this->objects.end()) return; + + auto* model_object {this->model->objects.at(obj->identifier)}; + auto cut_dialog = new ObjectCutDialog(nullptr, model_object); + cut_dialog->ShowModal(); + cut_dialog->Destroy(); } void Plater::object_layers_dialog() {