From 72db3392c833b2f6b6b4acd5857efefc80edd2c8 Mon Sep 17 00:00:00 2001 From: Oekn5w <38046255+Oekn5w@users.noreply.github.com> Date: Mon, 23 Apr 2018 19:04:47 +0200 Subject: [PATCH] "Reload from disk" - UI function overhaul (#4388) * C++ backend work to support reloading modifier files * UI update preserving configs and volumes of modifiers (those are not reloaded) * clarifying variable names * Setting up variables in the GUI enviroment * Implementation of added variables in (new ModelVolume(*)) funcion * Implementation of new reload function * Overhaul of the reload function, also renaming of some variables * Rewriting the main loop of the reload function, explicitly differentiating between the original file and later added parts and modifiers pointing to other files * Whitespace cleanup * Added dialog to choose from different reload behaviors, added hide and default option in preferences, copied volumes are matched the new object's origin translation --- lib/Slic3r/GUI.pm | 5 +- lib/Slic3r/GUI/Plater.pm | 171 +++++++++++++++++++--- lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm | 12 +- lib/Slic3r/GUI/Preferences.pm | 19 ++- lib/Slic3r/GUI/ReloadDialog.pm | 60 ++++++++ xs/src/libslic3r/Model.cpp | 16 +- xs/src/libslic3r/Model.hpp | 7 +- xs/xsp/Model.xsp | 14 ++ 8 files changed, 278 insertions(+), 26 deletions(-) create mode 100644 lib/Slic3r/GUI/ReloadDialog.pm diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index c8cd20c5cf..6d2e5e3a84 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -52,6 +52,7 @@ use Slic3r::GUI::Preset; use Slic3r::GUI::PresetEditor; use Slic3r::GUI::PresetEditorDialog; use Slic3r::GUI::SLAPrintOptions; +use Slic3r::GUI::ReloadDialog; our $have_OpenGL = eval "use Slic3r::GUI::3DScene; 1"; our $have_LWP = eval "use LWP::UserAgent; 1"; @@ -91,7 +92,9 @@ our $Settings = { color_toolpaths_by => 'role', tabbed_preset_editors => 1, show_host => 0, - nudge_val => 1 + nudge_val => 1, + reload_hide_dialog => 0, + reload_behavior => 0 }, }; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index ef364b08c1..8b2c26d0d1 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1177,7 +1177,7 @@ sub add_tin { sub load_file { my $self = shift; - my ($input_file, $obj_idx) = @_; + my ($input_file, $obj_idx_to_load) = @_; $Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file); wxTheApp->save_settings; @@ -1203,14 +1203,27 @@ sub load_file { } } - if (defined $obj_idx) { - return () if $obj_idx >= $model->objects_count; - @obj_idx = $self->load_model_objects($model->get_object($obj_idx)); + for my $obj_idx (0..($model->objects_count-1)) { + my $object = $model->objects->[$obj_idx]; + $object->set_input_file($input_file); + for my $vol_idx (0..($object->volumes_count-1)) { + my $volume = $object->get_volume($vol_idx); + $volume->set_input_file($input_file); + $volume->set_input_file_obj_idx($obj_idx); + $volume->set_input_file_obj_idx($vol_idx); + } + } + + my $i = 0; + + if (defined $obj_idx_to_load) { + return () if $obj_idx_to_load >= $model->objects_count; + @obj_idx = $self->load_model_objects($model->get_object($obj_idx_to_load)); + $i = $obj_idx_to_load; } else { @obj_idx = $self->load_model_objects(@{$model->objects}); } - my $i = 0; foreach my $obj_idx (@obj_idx) { $self->{objects}[$obj_idx]->input_file($input_file); $self->{objects}[$obj_idx]->input_file_obj_idx($i++); @@ -2306,26 +2319,141 @@ sub reload_from_disk { my ($obj_idx, $object) = $self->selected_object; return if !defined $obj_idx; - return if !$object->input_file - || !-e $object->input_file; + if (!$object->input_file) { + Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be reloaded because it isn't referenced to its input file any more. This is the case after performing operations like cut or split."); + return; + } + if (!-e $object->input_file) { + Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be reloaded because the file doesn't exist anymore on the disk."); + return; + } # Only reload the selected object and not all objects from the input file. my @new_obj_idx = $self->load_file($object->input_file, $object->input_file_obj_idx); - return if !@new_obj_idx; - - my $model_object = $self->{model}->objects->[$obj_idx]; + if (!@new_obj_idx) { + Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be reloaded because the new file doesn't contain the object."); + return; + } + + my $org_obj = $self->{model}->objects->[$obj_idx]; + + # check if the object is dependant of more than one file + my $org_obj_has_modifiers=0; + for my $i (0..($org_obj->volumes_count-1)) { + if ($org_obj->input_file ne $org_obj->get_volume($i)->input_file) { + $org_obj_has_modifiers=1; + last; + } + } + + my $reload_behavior = $Slic3r::GUI::Settings->{_}{reload_behavior}; + + # ask the user how to proceed, if option is selected in preferences + if ($org_obj_has_modifiers && !$Slic3r::GUI::Settings->{_}{reload_hide_dialog}) { + my $dlg = Slic3r::GUI::ReloadDialog->new(undef,$reload_behavior); + my $res = $dlg->ShowModal; + if ($res==wxID_CANCEL) { + $self->remove($_) for @new_obj_idx; + $dlg->Destroy; + return; + } + $reload_behavior = $dlg->GetSelection; + my $save = 0; + if ($reload_behavior != $Slic3r::GUI::Settings->{_}{reload_behavior}) { + $Slic3r::GUI::Settings->{_}{reload_behavior} = $reload_behavior; + $save = 1; + } + if ($dlg->GetHideOnNext) { + $Slic3r::GUI::Settings->{_}{reload_hide_dialog} = 1; + $save = 1; + } + Slic3r::GUI->save_settings if $save; + $dlg->Destroy; + } + + my $volume_unmatched=0; + foreach my $new_obj_idx (@new_obj_idx) { - my $o = $self->{model}->objects->[$new_obj_idx]; - $o->clear_instances; - $o->add_instance($_) for @{$model_object->instances}; + my $new_obj = $self->{model}->objects->[$new_obj_idx]; + $new_obj->clear_instances; + $new_obj->add_instance($_) for @{$org_obj->instances}; + $new_obj->config->apply($org_obj->config); - if ($o->volumes_count == $model_object->volumes_count) { - for my $i (0..($o->volumes_count-1)) { - $o->get_volume($i)->config->apply($model_object->get_volume($i)->config); + my $new_vol_idx = 0; + my $org_vol_idx = 0; + my $new_vol_count=$new_obj->volumes_count; + my $org_vol_count=$org_obj->volumes_count; + + while ($new_vol_idx<=$new_vol_count-1) { + if (($org_vol_idx<=$org_vol_count-1) && ($org_obj->get_volume($org_vol_idx)->input_file eq $new_obj->input_file)) { + # apply config from the matching volumes + $new_obj->get_volume($new_vol_idx++)->config->apply($org_obj->get_volume($org_vol_idx++)->config); + } else { + # reload has more volumes than original (first file), apply config from the first volume + $new_obj->get_volume($new_vol_idx++)->config->apply($org_obj->get_volume(0)->config); + $volume_unmatched=1; } } + $org_vol_idx=$org_vol_count if $reload_behavior==2; # Reload behavior: discard + while (($org_vol_idx<=$org_vol_count-1) && ($org_obj->get_volume($org_vol_idx)->input_file eq $new_obj->input_file)) { + # original has more volumes (first file), skip those + $org_vol_idx++; + $volume_unmatched=1; + } + while ($org_vol_idx<=$org_vol_count-1) { + if ($reload_behavior==1) { # Reload behavior: copy + my $new_volume = $new_obj->add_volume($org_obj->get_volume($org_vol_idx)); + $new_volume->mesh->translate(@{$org_obj->origin_translation->negative}); + $new_volume->mesh->translate(@{$new_obj->origin_translation}); + if ($new_volume->name =~ m/link to path\z/) { + my $new_name = $new_volume->name; + $new_name =~ s/ - no link to path$/ - copied/; + $new_volume->set_name($new_name); + }elsif(!($new_volume->name =~ m/copied\z/)) { + $new_volume->set_name($new_volume->name . " - copied"); + } + }else{ # Reload behavior: Reload all, also fallback solution if ini was manually edited to a wrong value + if ($org_obj->get_volume($org_vol_idx)->input_file) { + my $model = eval { Slic3r::Model->read_from_file($org_obj->get_volume($org_vol_idx)->input_file) }; + if ($@) { + $org_obj->get_volume($org_vol_idx)->set_input_file(""); + }elsif ($org_obj->get_volume($org_vol_idx)->input_file_obj_idx > ($model->objects_count-1)) { + # Object Index for that part / modifier not found in current version of the file + $org_obj->get_volume($org_vol_idx)->set_input_file(""); + }else{ + my $prt_mod_obj = $model->objects->[$org_obj->get_volume($org_vol_idx)->input_file_obj_idx]; + if ($org_obj->get_volume($org_vol_idx)->input_file_vol_idx > ($prt_mod_obj->volumes_count-1)) { + # Volume Index for that part / modifier not found in current version of the file + $org_obj->get_volume($org_vol_idx)->set_input_file(""); + }else{ + # all checks passed, load new mesh and copy metadata + my $new_volume = $new_obj->add_volume($prt_mod_obj->get_volume($org_obj->get_volume($org_vol_idx)->input_file_vol_idx)); + $new_volume->set_input_file($org_obj->get_volume($org_vol_idx)->input_file); + $new_volume->set_input_file_obj_idx($org_obj->get_volume($org_vol_idx)->input_file_obj_idx); + $new_volume->set_input_file_vol_idx($org_obj->get_volume($org_vol_idx)->input_file_vol_idx); + $new_volume->config->apply($org_obj->get_volume($org_vol_idx)->config); + $new_volume->set_modifier($org_obj->get_volume($org_vol_idx)->modifier); + $new_volume->mesh->translate(@{$new_obj->origin_translation}); + } + } + } + if (!$org_obj->get_volume($org_vol_idx)->input_file) { + my $new_volume = $new_obj->add_volume($org_obj->get_volume($org_vol_idx)); # error -> copy old mesh + $new_volume->mesh->translate(@{$org_obj->origin_translation->negative}); + $new_volume->mesh->translate(@{$new_obj->origin_translation}); + if ($new_volume->name =~ m/copied\z/) { + my $new_name = $new_volume->name; + $new_name =~ s/ - copied$/ - no link to path/; + $new_volume->set_name($new_name); + }elsif(!($new_volume->name =~ m/link to path\z/)) { + $new_volume->set_name($new_volume->name . " - no link to path"); + } + $volume_unmatched=1; + } + } + $org_vol_idx++; + } } - $self->remove($obj_idx); # TODO: refresh object list which contains wrong count and scale @@ -2336,8 +2464,17 @@ sub reload_from_disk { # When porting to C++ we'll probably have cleaner ways to do this. $self->make_thumbnail($_-1) for @new_obj_idx; + # update print + $self->stop_background_process; + $self->{print}->reload_object($_-1) for @new_obj_idx; + $self->on_model_change; + # Empty the redo stack $self->{redo_stack} = []; + + if ($volume_unmatched) { + Slic3r::GUI::warning_catcher($self)->("At least 1 volume couldn't be matched between the original object and the reloaded one."); + } } sub export_object_stl { diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index bfd858f5c5..35c1af28be 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -353,11 +353,17 @@ sub on_btn_load { next; } - foreach my $object (@{$model->objects}) { - foreach my $volume (@{$object->volumes}) { - my $new_volume = $self->{model_object}->add_volume($volume); + for my $obj_idx (0..($model->objects_count-1)) { + my $object = $model->objects->[$obj_idx]; + for my $vol_idx (0..($object->volumes_count-1)) { + my $new_volume = $self->{model_object}->add_volume($object->get_volume($vol_idx)); $new_volume->set_modifier($is_modifier); $new_volume->set_name(basename($input_file)); + + # input_file needed to reload / update modifiers' volumes + $new_volume->set_input_file($input_file); + $new_volume->set_input_file_obj_idx($obj_idx); + $new_volume->set_input_file_vol_idx($vol_idx); # apply the same translation we applied to the object $new_volume->mesh->translate(@{$self->{model_object}->origin_translation}); diff --git a/lib/Slic3r/GUI/Preferences.pm b/lib/Slic3r/GUI/Preferences.pm index 23da73d8d2..e63ca8cbc8 100644 --- a/lib/Slic3r/GUI/Preferences.pm +++ b/lib/Slic3r/GUI/Preferences.pm @@ -92,6 +92,23 @@ sub new { tooltip => 'In 2D plater, Move objects using keyboard by nudge value of', default => $Slic3r::GUI::Settings->{_}{nudge_val}, )); + $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( # reload hide dialog + opt_id => 'reload_hide_dialog', + type => 'bool', + label => 'Hide Dialog on Reload', + tooltip => 'When checked, the dialog on reloading files with added parts & modifiers is suppressed. The reload is performed according to the option given in \'Default Reload Behavior\'', + default => $Slic3r::GUI::Settings->{_}{reload_hide_dialog}, + )); + $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( # default reload behavior + opt_id => 'reload_behavior', + type => 'select', + label => 'Default Reload Behavior', + tooltip => 'Choose the default behavior of the \'Reload from disk\' function regarding additional parts and modifiers.', + labels => ['Reload all','Reload main, copy added','Reload main, discard added'], + values => [0, 1, 2], + default => $Slic3r::GUI::Settings->{_}{reload_behavior}, + width => 180, + )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( # colorscheme opt_id => 'colorscheme', type => 'select', @@ -100,7 +117,7 @@ sub new { labels => ['Default','Solarized'], # add more schemes, if you want in ColorScheme.pm. values => ['getDefault','getSolarized'], # add more schemes, if you want - those are the names of the corresponding function in ColorScheme.pm. default => $Slic3r::GUI::Settings->{_}{colorscheme} // 'getDefault', - width => 130, + width => 180, )); my $sizer = Wx::BoxSizer->new(wxVERTICAL); diff --git a/lib/Slic3r/GUI/ReloadDialog.pm b/lib/Slic3r/GUI/ReloadDialog.pm new file mode 100644 index 0000000000..bb11bf0898 --- /dev/null +++ b/lib/Slic3r/GUI/ReloadDialog.pm @@ -0,0 +1,60 @@ +# A tiny dialog to select how to reload an object that has additional parts or modifiers. + +package Slic3r::GUI::ReloadDialog; +use strict; +use warnings; +use utf8; + +use Wx qw(:button :dialog :id :misc :sizer :choicebook wxTAB_TRAVERSAL); +use Wx::Event qw(EVT_CLOSE); +use base 'Wx::Dialog'; + +sub new { + my $class = shift; + my ($parent,$default_selection) = @_; + my $self = $class->SUPER::new($parent, -1, "Additional parts and modifiers detected", wxDefaultPosition, [350,100], wxDEFAULT_DIALOG_STYLE); + + # label + my $text = Wx::StaticText->new($self, -1, "Additional parts and modifiers are loaded in the current model. \n\nHow do you want to proceed?", wxDefaultPosition, wxDefaultSize); + + # selector + $self->{choice} = my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, wxDefaultSize, []); + $choice->Append("Reload all linked files"); + $choice->Append("Reload main file, copy added parts & modifiers"); + $choice->Append("Reload main file, discard added parts & modifiers"); + $choice->SetSelection($default_selection); + + # checkbox + $self->{checkbox} = my $checkbox = Wx::CheckBox->new($self, -1, "Don't ask again"); + + my $vsizer = Wx::BoxSizer->new(wxVERTICAL); + my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); + $vsizer->Add($text, 0, wxEXPAND | wxALL, 10); + $vsizer->Add($choice, 0, wxEXPAND | wxALL, 10); + $hsizer->Add($checkbox, 1, wxEXPAND | wxALL, 10); + $hsizer->Add($self->CreateButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND | wxALL, 10); + $vsizer->Add($hsizer, 0, wxEXPAND | wxALL, 0); + + $self->SetSizer($vsizer); + $self->SetMinSize($self->GetSize); + $vsizer->SetSizeHints($self); + + # needed to actually free memory + EVT_CLOSE($self, sub { + $self->EndModal(wxID_CANCEL); + $self->Destroy; + }); + + return $self; +} + +sub GetSelection { + my ($self) = @_; + return $self->{choice}->GetSelection; +} +sub GetHideOnNext { + my ($self) = @_; + return $self->{checkbox}->GetValue; +} + +1; diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index aa29858afe..2d809db654 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -936,12 +936,18 @@ ModelObject::print_info() const ModelVolume::ModelVolume(ModelObject* object, const TriangleMesh &mesh) -: mesh(mesh), modifier(false), object(object) +: mesh(mesh), modifier(false), input_file(""), object(object) {} ModelVolume::ModelVolume(ModelObject* object, const ModelVolume &other) -: name(other.name), mesh(other.mesh), config(other.config), - modifier(other.modifier), object(object) +: name(other.name), + mesh(other.mesh), + config(other.config), + modifier(other.modifier), + input_file(other.input_file), + input_file_obj_idx(other.input_file_obj_idx), + input_file_vol_idx(other.input_file_vol_idx), + object(object) { this->material_id(other.material_id()); } @@ -959,6 +965,10 @@ ModelVolume::swap(ModelVolume &other) std::swap(this->mesh, other.mesh); std::swap(this->config, other.config); std::swap(this->modifier, other.modifier); + + std::swap(this->input_file, other.input_file); + std::swap(this->input_file_obj_idx, other.input_file_obj_idx); + std::swap(this->input_file_vol_idx, other.input_file_vol_idx); } t_model_material_id diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index db6299ee46..30cbf5e0d7 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -457,7 +457,12 @@ class ModelVolume DynamicPrintConfig config; ///< Configuration parameters specific to an object model geometry or a modifier volume, ///< overriding the global Slic3r settings and the ModelObject settings. - + + /// Input file path needed for reloading the volume from disk + std::string input_file; ///< Input file path + int input_file_obj_idx; ///< Input file object index + int input_file_vol_idx; ///< Input file volume index + bool modifier; ///< Is it an object to be printed, or a modifier volume? /// Get the parent object owning this modifier volume. diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 6772aee075..67bca3fd3b 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -258,6 +258,20 @@ ModelMaterial::attributes() %code%{ RETVAL = THIS->name; %}; void set_name(std::string value) %code%{ THIS->name = value; %}; + + std::string input_file() + %code%{ RETVAL = THIS->input_file; %}; + void set_input_file(std::string value) + %code%{ THIS->input_file = value; %}; + int input_file_obj_idx() + %code%{ RETVAL = THIS->input_file_obj_idx; %}; + void set_input_file_obj_idx(int obj_idx) + %code%{ THIS->input_file_obj_idx = obj_idx; %}; + int input_file_vol_idx() + %code%{ RETVAL = THIS->input_file_vol_idx; %}; + void set_input_file_vol_idx(int vol_idx) + %code%{ THIS->input_file_vol_idx = vol_idx; %}; + t_model_material_id material_id(); void set_material_id(t_model_material_id material_id) %code%{ THIS->material_id(material_id); %};