mirror of
https://git.mirrors.martin98.com/https://github.com/slic3r/Slic3r.git
synced 2025-08-11 17:49:00 +08:00
Undo/Redo to rotate, miror, split, cut, etc (#4100)
* * Add UndoOperations package in Plater.pm * Add undo/redo options to the plater menu with their icons. * Add the 2 undo/redo stacks to Slic3r::GUI::Plater package. * Add add_undo_operation, undo & redo sub routines to Slic3r::GUI::Plater. * Add objet identifier variable to each newly created Slic3r::GUI::Plater::Object to keep track of objects referenced in the stacks of undo/redo. * Add undo/redo to rotate, mirror, increase, remove. * * Add undo/redo to cut operation. * * Add undo/redo to split, decrease, change scale and reset. * Fix pop and push error.a * * Refactoring undo/redo functions. * * Some fixes to undo/redo for reset and decrease functions * Some Fixes to undo/redo split, reset and cut operations. * Improve undo/redo menu items sync. * * limit the undo stack operations to save RAM. * Remove debugging lines. * Add undo/redo to Add models. * Some Refactoring and some fixes. * Some Refactoring. Fixes #3265
This commit is contained in:
parent
7e018f1d38
commit
1a2df5e491
@ -191,6 +191,12 @@ sub _init_menubar {
|
|||||||
my $selectMenu = $self->{plater_select_menu} = Wx::Menu->new;
|
my $selectMenu = $self->{plater_select_menu} = Wx::Menu->new;
|
||||||
wxTheApp->append_submenu($self->{plater_menu}, "Select", 'Select an object in the plater', $selectMenu, undef, 'brick.png');
|
wxTheApp->append_submenu($self->{plater_menu}, "Select", 'Select an object in the plater', $selectMenu, undef, 'brick.png');
|
||||||
}
|
}
|
||||||
|
wxTheApp->append_menu_item($self->{plater_menu}, "Undo\tCtrl+Z", 'Undo', sub {
|
||||||
|
$plater->undo;
|
||||||
|
}, undef, 'arrow_undo.png');
|
||||||
|
wxTheApp->append_menu_item($self->{plater_menu}, "Redo\tCtrl+Shift+Z", 'Redo', sub {
|
||||||
|
$plater->redo;
|
||||||
|
}, undef, 'arrow_redo.png');
|
||||||
wxTheApp->append_menu_item($self->{plater_menu}, "Select Next Object\tCtrl+Right", 'Select Next Object in the plater', sub {
|
wxTheApp->append_menu_item($self->{plater_menu}, "Select Next Object\tCtrl+Right", 'Select Next Object in the plater', sub {
|
||||||
$plater->select_next;
|
$plater->select_next;
|
||||||
}, undef, 'arrow_right.png');
|
}, undef, 'arrow_right.png');
|
||||||
@ -331,12 +337,20 @@ sub is_loaded {
|
|||||||
return $self->{loaded};
|
return $self->{loaded};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub on_undo_redo_stacks_changed {
|
||||||
|
my $self = shift;
|
||||||
|
# Enable undo or redo if they have operations in their stack.
|
||||||
|
$self->{plater_menu}->Enable($self->{plater_menu}->FindItem("Undo\tCtrl+Z"), $#{$self->{plater}->{undo_stack}} < 0 ? 0 : 1);
|
||||||
|
$self->{plater_menu}->Enable( $self->{plater_menu}->FindItem("Redo\tCtrl+Shift+Z"), $#{$self->{plater}->{redo_stack}} < 0 ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
sub on_plater_object_list_changed {
|
sub on_plater_object_list_changed {
|
||||||
my ($self, $have_objects) = @_;
|
my ($self, $have_objects) = @_;
|
||||||
|
|
||||||
return if !defined $self->{plater_menu};
|
return if !defined $self->{plater_menu};
|
||||||
$self->{plater_menu}->Enable($_->GetId, $have_objects)
|
$self->{plater_menu}->Enable($_->GetId, $have_objects)
|
||||||
for $self->{plater_menu}->GetMenuItems;
|
for $self->{plater_menu}->GetMenuItems;
|
||||||
|
$self->on_undo_redo_stacks_changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub on_plater_selection_changed {
|
sub on_plater_selection_changed {
|
||||||
@ -345,6 +359,8 @@ sub on_plater_selection_changed {
|
|||||||
return if !defined $self->{object_menu};
|
return if !defined $self->{object_menu};
|
||||||
$self->{object_menu}->Enable($_->GetId, $have_selection)
|
$self->{object_menu}->Enable($_->GetId, $have_selection)
|
||||||
for $self->{object_menu}->GetMenuItems;
|
for $self->{object_menu}->GetMenuItems;
|
||||||
|
$self->on_undo_redo_stacks_changed;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub quick_slice {
|
sub quick_slice {
|
||||||
|
@ -1,5 +1,20 @@
|
|||||||
# The "Plater" tab. It contains the "3D", "2D", "Preview" and "Layers" subtabs.
|
# The "Plater" tab. It contains the "3D", "2D", "Preview" and "Layers" subtabs.
|
||||||
|
|
||||||
|
package Slic3r::GUI::Plater::UndoOperation;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
sub new{
|
||||||
|
my $class = shift;
|
||||||
|
my $self = {
|
||||||
|
type => shift,
|
||||||
|
object_identifier => shift,
|
||||||
|
attributes => shift,
|
||||||
|
};
|
||||||
|
bless ($self, $class);
|
||||||
|
return $self;
|
||||||
|
}
|
||||||
|
|
||||||
package Slic3r::GUI::Plater;
|
package Slic3r::GUI::Plater;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
@ -56,7 +71,16 @@ sub new {
|
|||||||
$self->{processed} = 0;
|
$self->{processed} = 0;
|
||||||
# List of Perl objects Slic3r::GUI::Plater::Object, representing a 2D preview of the platter.
|
# List of Perl objects Slic3r::GUI::Plater::Object, representing a 2D preview of the platter.
|
||||||
$self->{objects} = [];
|
$self->{objects} = [];
|
||||||
|
|
||||||
|
# Objects identifier used for undo/redo operations. It's a one time id assigned to each newly created object.
|
||||||
|
$self->{object_identifier} = 0;
|
||||||
|
|
||||||
|
# Stack of undo operations.
|
||||||
|
$self->{undo_stack} = [];
|
||||||
|
|
||||||
|
# Stack of redo operations.
|
||||||
|
$self->{redo_stack} = [];
|
||||||
|
|
||||||
$self->{print}->set_status_cb(sub {
|
$self->{print}->set_status_cb(sub {
|
||||||
my ($percent, $message) = @_;
|
my ($percent, $message) = @_;
|
||||||
|
|
||||||
@ -857,11 +881,262 @@ sub config {
|
|||||||
return $config;
|
return $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub get_object_index {
|
||||||
|
my $self = shift;
|
||||||
|
my ($object_indentifier) = @_;
|
||||||
|
return undef if !defined $object_indentifier;
|
||||||
|
|
||||||
|
for (my $i = 0; $i <= $#{$self->{objects}}; $i++){
|
||||||
|
if ($self->{objects}->[$i]->identifier eq $object_indentifier) {
|
||||||
|
return $i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub add_undo_operation {
|
||||||
|
my $self = shift;
|
||||||
|
my @parameters = @_;
|
||||||
|
|
||||||
|
my $type = $parameters[0];
|
||||||
|
my $object_identifier = $parameters[1];
|
||||||
|
my @attributes = @parameters[2..$#parameters]; # operation values.
|
||||||
|
|
||||||
|
my $new_undo_operation = new Slic3r::GUI::Plater::UndoOperation($type, $object_identifier, \@attributes);
|
||||||
|
|
||||||
|
push @{$self->{undo_stack}}, $new_undo_operation;
|
||||||
|
|
||||||
|
$self->{redo_stack} = [];
|
||||||
|
|
||||||
|
$self->limit_undo_operations(8); # Current limit of undo/redo operations.
|
||||||
|
$self->GetFrame->on_undo_redo_stacks_changed;
|
||||||
|
|
||||||
|
return $new_undo_operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub limit_undo_operations {
|
||||||
|
my ($self, $limit)= @_;
|
||||||
|
return if !defined $limit;
|
||||||
|
# Delete undo operations succeeded by 4 operations or more to save memory.
|
||||||
|
while ($#{$self->{undo_stack}} + 1 > $limit) {
|
||||||
|
print "Removing an old operation.\n";
|
||||||
|
splice @{$self->{undo_stack}}, 0, 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub undo {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
my $operation = pop @{$self->{undo_stack}};
|
||||||
|
return if !defined $operation;
|
||||||
|
|
||||||
|
push @{$self->{redo_stack}}, $operation;
|
||||||
|
|
||||||
|
my $type = $operation->{type};
|
||||||
|
|
||||||
|
if ($type eq "ROTATE") {
|
||||||
|
my $object_id = $operation->{object_identifier};
|
||||||
|
my $obj_idx = $self->get_object_index($object_id);
|
||||||
|
$self->select_object($obj_idx);
|
||||||
|
|
||||||
|
my $angle = $operation->{attributes}->[0];
|
||||||
|
my $axis = $operation->{attributes}->[1];
|
||||||
|
$self->rotate(-1 * $angle, $axis, 'true'); # Apply inverse transformation.
|
||||||
|
|
||||||
|
} elsif ($type eq "INCREASE") {
|
||||||
|
my $object_id = $operation->{object_identifier};
|
||||||
|
my $obj_idx = $self->get_object_index($object_id);
|
||||||
|
$self->select_object($obj_idx);
|
||||||
|
|
||||||
|
my $copies = $operation->{attributes}->[0];
|
||||||
|
$self->decrease($copies, 'true');
|
||||||
|
|
||||||
|
} elsif ($type eq "DECREASE") {
|
||||||
|
my $object_id = $operation->{object_identifier};
|
||||||
|
my $obj_idx = $self->get_object_index($object_id);
|
||||||
|
$self->select_object($obj_idx);
|
||||||
|
|
||||||
|
my $copies = $operation->{attributes}->[0];
|
||||||
|
$self->increase($copies, 'true');
|
||||||
|
|
||||||
|
} elsif ($type eq "MIRROR") {
|
||||||
|
my $object_id = $operation->{object_identifier};
|
||||||
|
my $obj_idx = $self->get_object_index($object_id);
|
||||||
|
$self->select_object($obj_idx);
|
||||||
|
|
||||||
|
my $axis = $operation->{attributes}->[0];
|
||||||
|
$self->mirror($axis, 'true');
|
||||||
|
|
||||||
|
} elsif ($type eq "REMOVE") {
|
||||||
|
my $_model = $operation->{attributes}->[0];
|
||||||
|
$self->load_model_objects(@{$_model->objects});
|
||||||
|
$self->{object_identifier}--; # Decrement the identifier as we will change the object identifier with the saved one.
|
||||||
|
$self->{objects}->[-1]->identifier($operation->{object_identifier});
|
||||||
|
|
||||||
|
} elsif ($type eq "CUT" || $type eq "SPLIT") {
|
||||||
|
# Delete the produced objects.
|
||||||
|
my $obj_identifiers_start = $operation->{attributes}->[2];
|
||||||
|
for (my $i_object = 0; $i_object < $#{$operation->{attributes}->[1]->objects} + 1; $i_object++) {
|
||||||
|
$self->remove($self->get_object_index($obj_identifiers_start++), 'true');
|
||||||
|
}
|
||||||
|
# Add the original object.
|
||||||
|
$self->load_model_objects(@{$operation->{attributes}->[0]->objects});
|
||||||
|
$self->{object_identifier}--;
|
||||||
|
$self->{objects}->[-1]->identifier($operation->{object_identifier}); # Add the original assigned identifier.
|
||||||
|
|
||||||
|
} elsif ($type eq "CHANGE_SCALE") {
|
||||||
|
my $object_id = $operation->{object_identifier};
|
||||||
|
my $obj_idx = $self->get_object_index($object_id);
|
||||||
|
$self->select_object($obj_idx);
|
||||||
|
|
||||||
|
my $axis = $operation->{attributes}->[0];
|
||||||
|
my $tosize = $operation->{attributes}->[1];
|
||||||
|
my $saved_scale = $operation->{attributes}->[3];
|
||||||
|
$self->changescale($axis, $tosize, $saved_scale, 'true');
|
||||||
|
|
||||||
|
} elsif ($type eq "RESET") {
|
||||||
|
# Revert changes to the plater object identifier. It's modified when adding new objects only not when undo/redo is executed.
|
||||||
|
my $current_objects_identifier = $self->{object_identifier};
|
||||||
|
my $_model = $operation->{attributes}->[0];
|
||||||
|
$self->load_model_objects(@{$_model->objects});
|
||||||
|
$self->{object_identifier} = $current_objects_identifier;
|
||||||
|
|
||||||
|
# don't forget the identifiers.
|
||||||
|
my $objects_count = $#{$operation->{attributes}->[0]->objects} + 1;
|
||||||
|
|
||||||
|
foreach my $identifier (@{$operation->{attributes}->[1]})
|
||||||
|
{
|
||||||
|
$self->{objects}->[-$objects_count]->identifier($identifier);
|
||||||
|
$objects_count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
} elsif ($type eq "ADD") {
|
||||||
|
my $objects_count = $#{$operation->{attributes}->[0]->objects} + 1;
|
||||||
|
my $identifier_start = $operation->{attributes}->[1];
|
||||||
|
for (my $identifier = $identifier_start; $identifier < $objects_count + $identifier_start; $identifier++) {
|
||||||
|
my $obj_idx = $self->get_object_index($identifier);
|
||||||
|
$self->remove($obj_idx, 'true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub redo {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
my $operation = pop @{$self->{redo_stack}};
|
||||||
|
return if !defined $operation;
|
||||||
|
|
||||||
|
push @{$self->{undo_stack}}, $operation;
|
||||||
|
|
||||||
|
my $type = $operation->{type};
|
||||||
|
|
||||||
|
if ($type eq "ROTATE") {
|
||||||
|
my $object_id = $operation->{object_identifier};
|
||||||
|
my $obj_idx = $self->get_object_index($object_id);
|
||||||
|
$self->select_object($obj_idx);
|
||||||
|
|
||||||
|
my $angle = $operation->{attributes}->[0];
|
||||||
|
my $axis = $operation->{attributes}->[1];
|
||||||
|
$self->rotate($angle, $axis, 'true');
|
||||||
|
|
||||||
|
} elsif ($type eq "INCREASE") {
|
||||||
|
my $object_id = $operation->{object_identifier};
|
||||||
|
my $obj_idx = $self->get_object_index($object_id);
|
||||||
|
$self->select_object($obj_idx);
|
||||||
|
|
||||||
|
my $copies = $operation->{attributes}->[0];
|
||||||
|
$self->increase($copies, 'true');
|
||||||
|
|
||||||
|
} elsif ($type eq "DECREASE") {
|
||||||
|
my $object_id = $operation->{object_identifier};
|
||||||
|
my $obj_idx = $self->get_object_index($object_id);
|
||||||
|
$self->select_object($obj_idx);
|
||||||
|
|
||||||
|
my $copies = $operation->{attributes}->[0];
|
||||||
|
$self->decrease($copies, 'true');
|
||||||
|
|
||||||
|
} elsif ($type eq "MIRROR") {
|
||||||
|
my $object_id = $operation->{object_identifier};
|
||||||
|
my $obj_idx = $self->get_object_index($object_id);
|
||||||
|
$self->select_object($obj_idx);
|
||||||
|
|
||||||
|
my $axis = $operation->{attributes}->[0];
|
||||||
|
$self->mirror($axis, 'true');
|
||||||
|
|
||||||
|
} elsif ($type eq "REMOVE") {
|
||||||
|
my $object_id = $operation->{object_identifier};
|
||||||
|
my $obj_idx = $self->get_object_index($object_id);
|
||||||
|
$self->select_object($obj_idx);
|
||||||
|
|
||||||
|
$self->remove(undef, 'true');
|
||||||
|
|
||||||
|
} elsif ($type eq "CUT" || $type eq "SPLIT") {
|
||||||
|
# Delete the org objects.
|
||||||
|
$self->remove($self->get_object_index($operation->{object_identifier}), 'true');
|
||||||
|
# Add the new objects and revert changes to the plater object identifier.
|
||||||
|
my $current_objects_identifier = $self->{object_identifier};
|
||||||
|
$self->load_model_objects(@{$operation->{attributes}->[1]->objects});
|
||||||
|
$self->{object_identifier} = $current_objects_identifier;
|
||||||
|
# Add their identifiers.
|
||||||
|
my $obj_identifiers_start = $operation->{attributes}->[2];
|
||||||
|
my $obj_count = $#{$operation->{attributes}->[1]->objects} + 1;
|
||||||
|
for (my $i_object = 0; $i_object <= $#{$operation->{attributes}->[1]->objects}; $i_object++){
|
||||||
|
$self->{objects}->[-$obj_count]->identifier($obj_identifiers_start++);
|
||||||
|
$obj_count--;
|
||||||
|
}
|
||||||
|
} elsif ($type eq "CHANGE_SCALE") {
|
||||||
|
my $object_id = $operation->{object_identifier};
|
||||||
|
my $obj_idx = $self->get_object_index($object_id);
|
||||||
|
$self->select_object($obj_idx);
|
||||||
|
|
||||||
|
my $axis = $operation->{attributes}->[0];
|
||||||
|
my $tosize = $operation->{attributes}->[1];
|
||||||
|
my $old_scale = $operation->{attributes}->[2];
|
||||||
|
$self->changescale($axis, $tosize, $old_scale, 'true');
|
||||||
|
|
||||||
|
} elsif ($type eq "RESET") {
|
||||||
|
$self->reset('true');
|
||||||
|
} elsif ($type eq "ADD") {
|
||||||
|
# Revert changes to the plater object identifier. It's modified when adding new objects only not when undo/redo is executed.
|
||||||
|
my $current_objects_identifier = $self->{object_identifier};
|
||||||
|
$self->load_model_objects(@{$operation->{attributes}->[0]->objects});
|
||||||
|
$self->{object_identifier} = $current_objects_identifier;
|
||||||
|
|
||||||
|
my $objects_count = $#{$operation->{attributes}->[0]->objects} + 1;
|
||||||
|
my $start_identifier = $operation->{attributes}->[1];
|
||||||
|
foreach my $object (@{$operation->{attributes}->[0]->objects})
|
||||||
|
{
|
||||||
|
$self->{objects}->[-$objects_count]->identifier($start_identifier++);
|
||||||
|
$objects_count--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sub add {
|
sub add {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
|
|
||||||
|
# Save the current object identifier to track added objects.
|
||||||
|
my $start_object_id = $self->{object_identifier};
|
||||||
|
|
||||||
my @input_files = wxTheApp->open_model($self);
|
my @input_files = wxTheApp->open_model($self);
|
||||||
$self->load_file($_) for @input_files;
|
$self->load_file($_) for @input_files;
|
||||||
|
|
||||||
|
# Check if no objects are added.
|
||||||
|
if ($start_object_id == $self->{object_identifier}) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Save the added objects.
|
||||||
|
my $new_model = $self->{model}->new;
|
||||||
|
|
||||||
|
# Get newly added objects count.
|
||||||
|
my $new_objects_count = $self->{object_identifier} - $start_object_id;
|
||||||
|
for (my $i_object = $start_object_id; $i_object < $new_objects_count + $start_object_id; $i_object++){
|
||||||
|
my $object_index = $self->get_object_index($i_object);
|
||||||
|
$new_model->add_object($self->{model}->get_object($object_index));
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->add_undo_operation("ADD", undef, $new_model, $start_object_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub add_tin {
|
sub add_tin {
|
||||||
@ -938,7 +1213,10 @@ sub load_file {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$process_dialog->Destroy;
|
$process_dialog->Destroy;
|
||||||
|
|
||||||
|
# Empty the redo stack
|
||||||
|
$self->{redo_stack} = [];
|
||||||
|
|
||||||
return @obj_idx;
|
return @obj_idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -960,10 +1238,12 @@ sub load_model_objects {
|
|||||||
foreach my $model_object (@model_objects) {
|
foreach my $model_object (@model_objects) {
|
||||||
my $o = $self->{model}->add_object($model_object);
|
my $o = $self->{model}->add_object($model_object);
|
||||||
$o->repair;
|
$o->repair;
|
||||||
|
|
||||||
push @{ $self->{objects} }, Slic3r::GUI::Plater::Object->new(
|
push @{ $self->{objects} }, Slic3r::GUI::Plater::Object->new(
|
||||||
name => $model_object->name || basename($model_object->input_file),
|
name => $model_object->name || basename($model_object->input_file), identifier =>
|
||||||
);
|
$self->{object_identifier}++
|
||||||
|
);
|
||||||
|
|
||||||
push @obj_idx, $#{ $self->{objects} };
|
push @obj_idx, $#{ $self->{objects} };
|
||||||
|
|
||||||
if ($model_object->instances_count == 0) {
|
if ($model_object->instances_count == 0) {
|
||||||
@ -1028,7 +1308,7 @@ sub bed_centerf {
|
|||||||
|
|
||||||
sub remove {
|
sub remove {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($obj_idx) = @_;
|
my ($obj_idx, $dont_push) = @_;
|
||||||
|
|
||||||
$self->stop_background_process;
|
$self->stop_background_process;
|
||||||
|
|
||||||
@ -1040,7 +1320,12 @@ sub remove {
|
|||||||
if (!defined $obj_idx) {
|
if (!defined $obj_idx) {
|
||||||
($obj_idx, undef) = $self->selected_object;
|
($obj_idx, undef) = $self->selected_object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Save the object identifier and copy the object for undo/redo operations.
|
||||||
|
my $object_id = $self->{objects}->[$obj_idx]->identifier;
|
||||||
|
my $new_model = Slic3r::Model->new; # store this before calling get_object()
|
||||||
|
$new_model->add_object($self->{model}->get_object($obj_idx));
|
||||||
|
|
||||||
splice @{$self->{objects}}, $obj_idx, 1;
|
splice @{$self->{objects}}, $obj_idx, 1;
|
||||||
$self->{model}->delete_object($obj_idx);
|
$self->{model}->delete_object($obj_idx);
|
||||||
$self->{print}->delete_object($obj_idx);
|
$self->{print}->delete_object($obj_idx);
|
||||||
@ -1048,28 +1333,44 @@ sub remove {
|
|||||||
|
|
||||||
$self->select_object(undef);
|
$self->select_object(undef);
|
||||||
$self->on_model_change;
|
$self->on_model_change;
|
||||||
|
|
||||||
|
if (!defined $dont_push) {
|
||||||
|
$self->add_undo_operation("REMOVE", $object_id, $new_model);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub reset {
|
sub reset {
|
||||||
my $self = shift;
|
my ($self, $dont_push) = @_;
|
||||||
|
|
||||||
$self->stop_background_process;
|
$self->stop_background_process;
|
||||||
|
|
||||||
# Prevent toolpaths preview from rendering while we modify the Print object
|
# Prevent toolpaths preview from rendering while we modify the Print object
|
||||||
$self->{toolpaths2D}->enabled(0) if $self->{toolpaths2D};
|
$self->{toolpaths2D}->enabled(0) if $self->{toolpaths2D};
|
||||||
$self->{preview3D}->enabled(0) if $self->{preview3D};
|
$self->{preview3D}->enabled(0) if $self->{preview3D};
|
||||||
|
|
||||||
|
# Save the current model.
|
||||||
|
my $current_model = $self->{model}->clone;
|
||||||
|
|
||||||
|
if (!defined $dont_push) {
|
||||||
|
# Get the identifiers of the curent model objects.
|
||||||
|
my $objects_identifiers = [];
|
||||||
|
for (my $i = 0; $i <= $#{$self->{objects}}; $i++){
|
||||||
|
push @{$objects_identifiers}, $self->{objects}->[$i]->identifier;
|
||||||
|
}
|
||||||
|
$self->add_undo_operation("RESET", undef, $current_model, $objects_identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
@{$self->{objects}} = ();
|
@{$self->{objects}} = ();
|
||||||
$self->{model}->clear_objects;
|
$self->{model}->clear_objects;
|
||||||
$self->{print}->clear_objects;
|
$self->{print}->clear_objects;
|
||||||
$self->object_list_changed;
|
$self->object_list_changed;
|
||||||
|
|
||||||
$self->select_object(undef);
|
$self->select_object(undef);
|
||||||
$self->on_model_change;
|
$self->on_model_change;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub increase {
|
sub increase {
|
||||||
my ($self, $copies) = @_;
|
my ($self, $copies, $dont_push) = @_;
|
||||||
|
|
||||||
$copies //= 1;
|
$copies //= 1;
|
||||||
my ($obj_idx, $object) = $self->selected_object;
|
my ($obj_idx, $object) = $self->selected_object;
|
||||||
@ -1087,7 +1388,11 @@ sub increase {
|
|||||||
);
|
);
|
||||||
$self->{print}->objects->[$obj_idx]->add_copy($instance->offset);
|
$self->{print}->objects->[$obj_idx]->add_copy($instance->offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!defined $dont_push) {
|
||||||
|
$self->add_undo_operation("INCREASE", $object->identifier , $copies);
|
||||||
|
}
|
||||||
|
|
||||||
# only autoarrange if user has autocentering enabled
|
# only autoarrange if user has autocentering enabled
|
||||||
$self->stop_background_process;
|
$self->stop_background_process;
|
||||||
if ($Slic3r::GUI::Settings->{_}{autocenter}) {
|
if ($Slic3r::GUI::Settings->{_}{autocenter}) {
|
||||||
@ -1098,7 +1403,7 @@ sub increase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub decrease {
|
sub decrease {
|
||||||
my ($self, $copies) = @_;
|
my ($self, $copies, $dont_push) = @_;
|
||||||
|
|
||||||
$copies //= 1;
|
$copies //= 1;
|
||||||
$self->stop_background_process;
|
$self->stop_background_process;
|
||||||
@ -1110,6 +1415,9 @@ sub decrease {
|
|||||||
$model_object->delete_last_instance;
|
$model_object->delete_last_instance;
|
||||||
$self->{print}->objects->[$obj_idx]->delete_last_copy;
|
$self->{print}->objects->[$obj_idx]->delete_last_copy;
|
||||||
}
|
}
|
||||||
|
if (!defined $dont_push) {
|
||||||
|
$self->add_undo_operation("DECREASE", $object->identifier, $copies);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$self->remove;
|
$self->remove;
|
||||||
}
|
}
|
||||||
@ -1159,7 +1467,7 @@ sub center_selected_object_on_bed {
|
|||||||
|
|
||||||
sub rotate {
|
sub rotate {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($angle, $axis) = @_;
|
my ($angle, $axis, $dont_push) = @_;
|
||||||
|
|
||||||
# angle is in degrees
|
# angle is in degrees
|
||||||
$axis //= Z;
|
$axis //= Z;
|
||||||
@ -1198,17 +1506,21 @@ sub rotate {
|
|||||||
$model_object->center_around_origin;
|
$model_object->center_around_origin;
|
||||||
$self->make_thumbnail($obj_idx);
|
$self->make_thumbnail($obj_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
$model_object->update_bounding_box;
|
$model_object->update_bounding_box;
|
||||||
# update print and start background processing
|
# update print and start background processing
|
||||||
$self->{print}->add_model_object($model_object, $obj_idx);
|
$self->{print}->add_model_object($model_object, $obj_idx);
|
||||||
|
|
||||||
|
if (!defined $dont_push) {
|
||||||
|
$self->add_undo_operation("ROTATE", $object->identifier, $angle, $axis);
|
||||||
|
}
|
||||||
|
|
||||||
$self->selection_changed; # refresh info (size etc.)
|
$self->selection_changed; # refresh info (size etc.)
|
||||||
$self->on_model_change;
|
$self->on_model_change;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub mirror {
|
sub mirror {
|
||||||
my ($self, $axis) = @_;
|
my ($self, $axis, $dont_push) = @_;
|
||||||
|
|
||||||
my ($obj_idx, $object) = $self->selected_object;
|
my ($obj_idx, $object) = $self->selected_object;
|
||||||
return if !defined $obj_idx;
|
return if !defined $obj_idx;
|
||||||
@ -1229,13 +1541,17 @@ sub mirror {
|
|||||||
# update print and start background processing
|
# update print and start background processing
|
||||||
$self->stop_background_process;
|
$self->stop_background_process;
|
||||||
$self->{print}->add_model_object($model_object, $obj_idx);
|
$self->{print}->add_model_object($model_object, $obj_idx);
|
||||||
|
|
||||||
|
if (!defined $dont_push) {
|
||||||
|
$self->add_undo_operation("MIRROR", $object->identifier, $axis);
|
||||||
|
}
|
||||||
|
|
||||||
$self->selection_changed; # refresh info (size etc.)
|
$self->selection_changed; # refresh info (size etc.)
|
||||||
$self->on_model_change;
|
$self->on_model_change;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub changescale {
|
sub changescale {
|
||||||
my ($self, $axis, $tosize) = @_;
|
my ($self, $axis, $tosize, $saved_scale, $dont_push) = @_;
|
||||||
|
|
||||||
my ($obj_idx, $object) = $self->selected_object;
|
my ($obj_idx, $object) = $self->selected_object;
|
||||||
return if !defined $obj_idx;
|
return if !defined $obj_idx;
|
||||||
@ -1248,27 +1564,36 @@ sub changescale {
|
|||||||
|
|
||||||
my $object_size = $model_object->bounding_box->size;
|
my $object_size = $model_object->bounding_box->size;
|
||||||
my $bed_size = Slic3r::Polygon->new_scale(@{$self->{config}->bed_shape})->bounding_box->size;
|
my $bed_size = Slic3r::Polygon->new_scale(@{$self->{config}->bed_shape})->bounding_box->size;
|
||||||
|
|
||||||
|
my $old_scale;
|
||||||
|
my $scale;
|
||||||
|
|
||||||
if (defined $axis) {
|
if (defined $axis) {
|
||||||
my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z';
|
my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z';
|
||||||
my $scale;
|
if (!defined $saved_scale) {
|
||||||
if ($tosize) {
|
if ($tosize) {
|
||||||
my $cursize = $object_size->[$axis];
|
my $cursize = $object_size->[$axis];
|
||||||
# Wx::GetNumberFromUser() does not support decimal numbers
|
# Wx::GetNumberFromUser() does not support decimal numbers
|
||||||
my $newsize = Wx::GetTextFromUser(
|
my $newsize = Wx::GetTextFromUser(
|
||||||
sprintf("Enter the new size for the selected object (print bed: %smm):", $bed_size->[$axis]),
|
sprintf("Enter the new size for the selected object (print bed: %smm):", $bed_size->[$axis]),
|
||||||
"Scale along $axis_name",
|
"Scale along $axis_name",
|
||||||
$cursize, $self);
|
$cursize, $self);
|
||||||
return if !$newsize || $newsize !~ /^\d*(?:\.\d*)?$/ || $newsize < 0;
|
return if !$newsize || $newsize !~ /^\d*(?:\.\d*)?$/ || $newsize < 0;
|
||||||
$scale = $newsize / $cursize * 100;
|
$scale = $newsize / $cursize * 100;
|
||||||
} else {
|
$old_scale = $cursize / $newsize * 100;
|
||||||
# Wx::GetNumberFromUser() does not support decimal numbers
|
} else {
|
||||||
$scale = Wx::GetTextFromUser("Enter the scale % for the selected object:",
|
# Wx::GetNumberFromUser() does not support decimal numbers
|
||||||
"Scale along $axis_name", 100, $self);
|
$scale = Wx::GetTextFromUser("Enter the scale % for the selected object:",
|
||||||
$scale =~ s/%$//;
|
"Scale along $axis_name", 100, $self);
|
||||||
return if !$scale || $scale !~ /^\d*(?:\.\d*)?$/ || $scale < 0;
|
$scale =~ s/%$//;
|
||||||
|
return if !$scale || $scale !~ /^\d*(?:\.\d*)?$/ || $scale < 0;
|
||||||
|
$old_scale = 100 * 100 / $scale;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
$scale = $saved_scale;
|
||||||
|
}
|
||||||
|
|
||||||
# apply Z rotation before scaling
|
# apply Z rotation before scaling
|
||||||
$model_object->transform_by_instance($model_instance, 1);
|
$model_object->transform_by_instance($model_instance, 1);
|
||||||
|
|
||||||
@ -1278,23 +1603,28 @@ sub changescale {
|
|||||||
# object was already aligned to Z = 0, so no need to realign it
|
# object was already aligned to Z = 0, so no need to realign it
|
||||||
$self->make_thumbnail($obj_idx);
|
$self->make_thumbnail($obj_idx);
|
||||||
} else {
|
} else {
|
||||||
my $scale;
|
if (!defined $saved_scale) {
|
||||||
if ($tosize) {
|
if ($tosize) {
|
||||||
my $cursize = max(@$object_size);
|
my $cursize = max(@$object_size);
|
||||||
# Wx::GetNumberFromUser() does not support decimal numbers
|
# Wx::GetNumberFromUser() does not support decimal numbers
|
||||||
my $newsize = Wx::GetTextFromUser("Enter the new max size for the selected object:",
|
my $newsize = Wx::GetTextFromUser("Enter the new max size for the selected object:",
|
||||||
"Scale", $cursize, $self);
|
"Scale", $cursize, $self);
|
||||||
return if !$newsize || $newsize !~ /^\d*(?:\.\d*)?$/ || $newsize < 0;
|
return if !$newsize || $newsize !~ /^\d*(?:\.\d*)?$/ || $newsize < 0;
|
||||||
$scale = $model_instance->scaling_factor * $newsize / $cursize * 100;
|
$scale = $model_instance->scaling_factor * $newsize / $cursize * 100;
|
||||||
|
$old_scale = $model_instance->scaling_factor * 100;
|
||||||
|
} else {
|
||||||
|
# max scale factor should be above 2540 to allow importing files exported in inches
|
||||||
|
# Wx::GetNumberFromUser() does not support decimal numbers
|
||||||
|
$scale = Wx::GetTextFromUser("Enter the scale % for the selected object:", 'Scale',
|
||||||
|
$model_instance->scaling_factor * 100, $self);
|
||||||
|
return if !$scale || $scale !~ /^\d*(?:\.\d*)?$/ || $scale < 0;
|
||||||
|
$old_scale = $model_instance->scaling_factor * 100;
|
||||||
|
}
|
||||||
|
return if !$scale || $scale < 0;
|
||||||
} else {
|
} else {
|
||||||
# max scale factor should be above 2540 to allow importing files exported in inches
|
$scale = $saved_scale;
|
||||||
# Wx::GetNumberFromUser() does not support decimal numbers
|
|
||||||
$scale = Wx::GetTextFromUser("Enter the scale % for the selected object:", 'Scale',
|
|
||||||
$model_instance->scaling_factor*100, $self);
|
|
||||||
return if !$scale || $scale !~ /^\d*(?:\.\d*)?$/ || $scale < 0;
|
|
||||||
}
|
}
|
||||||
return if !$scale || $scale < 0;
|
|
||||||
|
|
||||||
$scale /= 100; # turn percent into factor
|
$scale /= 100; # turn percent into factor
|
||||||
|
|
||||||
my $variation = $scale / $model_instance->scaling_factor;
|
my $variation = $scale / $model_instance->scaling_factor;
|
||||||
@ -1304,7 +1634,15 @@ sub changescale {
|
|||||||
}
|
}
|
||||||
$_->set_scaling_factor($scale) for @{ $model_object->instances };
|
$_->set_scaling_factor($scale) for @{ $model_object->instances };
|
||||||
$object->transform_thumbnail($self->{model}, $obj_idx);
|
$object->transform_thumbnail($self->{model}, $obj_idx);
|
||||||
|
|
||||||
|
$scale *= 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Add the new undo operation.
|
||||||
|
if (!defined $dont_push) {
|
||||||
|
$self->add_undo_operation("CHANGE_SCALE", $object->identifier, $axis, $tosize, $scale, $old_scale);
|
||||||
|
}
|
||||||
|
|
||||||
$model_object->update_bounding_box;
|
$model_object->update_bounding_box;
|
||||||
|
|
||||||
# update print and start background processing
|
# update print and start background processing
|
||||||
@ -1329,7 +1667,7 @@ sub arrange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub split_object {
|
sub split_object {
|
||||||
my $self = shift;
|
my ($self, $dont_push) = @_;
|
||||||
|
|
||||||
my ($obj_idx, $current_object) = $self->selected_object;
|
my ($obj_idx, $current_object) = $self->selected_object;
|
||||||
|
|
||||||
@ -1344,7 +1682,14 @@ sub split_object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$self->pause_background_process;
|
$self->pause_background_process;
|
||||||
|
|
||||||
|
# Save the curent model object for undo/redo operataions.
|
||||||
|
my $org_object_model = Slic3r::Model->new;
|
||||||
|
$org_object_model->add_object($current_model_object);
|
||||||
|
|
||||||
|
# Save the org object identifier.
|
||||||
|
my $object_id = $self->{objects}->[$obj_idx]->identifier;
|
||||||
|
|
||||||
my @model_objects = @{$current_model_object->split_object};
|
my @model_objects = @{$current_model_object->split_object};
|
||||||
if (@model_objects == 1) {
|
if (@model_objects == 1) {
|
||||||
$self->resume_background_process;
|
$self->resume_background_process;
|
||||||
@ -1364,12 +1709,24 @@ sub split_object {
|
|||||||
# remove the original object before spawning the object_loaded event, otherwise
|
# remove the original object before spawning the object_loaded event, otherwise
|
||||||
# we'll pass the wrong $obj_idx to it (which won't be recognized after the
|
# we'll pass the wrong $obj_idx to it (which won't be recognized after the
|
||||||
# thumbnail thread returns)
|
# thumbnail thread returns)
|
||||||
$self->remove($obj_idx);
|
$self->remove($obj_idx, 'true'); # Don't push to the undo stack it's considered a split opeation not a remove one.
|
||||||
$current_object = $obj_idx = undef;
|
$current_object = $obj_idx = undef;
|
||||||
|
|
||||||
|
# Save the object identifiers used in undo/redo operations.
|
||||||
|
my $new_objects_id_start = $self->{object_identifier};
|
||||||
|
print "The new object identifier start for split is " .$new_objects_id_start . "\n";
|
||||||
|
|
||||||
# load all model objects at once, otherwise the plate would be rearranged after each one
|
# load all model objects at once, otherwise the plate would be rearranged after each one
|
||||||
# causing original positions not to be kept
|
# causing original positions not to be kept
|
||||||
$self->load_model_objects(@model_objects);
|
$self->load_model_objects(@model_objects);
|
||||||
|
|
||||||
|
# Create two models to save the current object and the resulted objects.
|
||||||
|
my $new_objects_model = Slic3r::Model->new;
|
||||||
|
foreach my $new_object (@model_objects) {
|
||||||
|
$new_objects_model->add_object($new_object);
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->add_undo_operation("SPLIT", $object_id, $org_object_model, $new_objects_model, $new_objects_id_start);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub toggle_print_stats {
|
sub toggle_print_stats {
|
||||||
@ -1949,6 +2306,9 @@ sub reload_from_disk {
|
|||||||
# event, so the on_thumbnail_made callback is called with the wrong $obj_idx.
|
# event, so the on_thumbnail_made callback is called with the wrong $obj_idx.
|
||||||
# When porting to C++ we'll probably have cleaner ways to do this.
|
# When porting to C++ we'll probably have cleaner ways to do this.
|
||||||
$self->make_thumbnail($_-1) for @new_obj_idx;
|
$self->make_thumbnail($_-1) for @new_obj_idx;
|
||||||
|
|
||||||
|
# Empty the redo stack
|
||||||
|
$self->{redo_stack} = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
sub export_object_stl {
|
sub export_object_stl {
|
||||||
@ -2235,8 +2595,23 @@ sub object_cut_dialog {
|
|||||||
if (my @new_objects = $dlg->NewModelObjects) {
|
if (my @new_objects = $dlg->NewModelObjects) {
|
||||||
my $process_dialog = Wx::ProgressDialog->new('Loading…', "Loading new objects…", 100, $self, 0);
|
my $process_dialog = Wx::ProgressDialog->new('Loading…', "Loading new objects…", 100, $self, 0);
|
||||||
$process_dialog->Pulse;
|
$process_dialog->Pulse;
|
||||||
|
|
||||||
$self->remove($obj_idx);
|
# Create two models to save the current object and the resulted objects.
|
||||||
|
my $new_objects_model = Slic3r::Model->new;
|
||||||
|
foreach my $new_object (@new_objects) {
|
||||||
|
$new_objects_model->add_object($new_object);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $org_object_model = Slic3r::Model->new;
|
||||||
|
$org_object_model->add_object($self->{model}->get_object($obj_idx));
|
||||||
|
|
||||||
|
# Save the object identifiers used in undo/redo operations.
|
||||||
|
my $object_id = $self->{objects}->[$obj_idx]->identifier;
|
||||||
|
my $new_objects_id_start = $self->{object_identifier};
|
||||||
|
|
||||||
|
$self->add_undo_operation("CUT", $object_id, $org_object_model, $new_objects_model, $new_objects_id_start);
|
||||||
|
|
||||||
|
$self->remove($obj_idx, 'true');
|
||||||
$self->load_model_objects(grep defined($_), @new_objects);
|
$self->load_model_objects(grep defined($_), @new_objects);
|
||||||
$self->arrange if @new_objects <= 2; # don't arrange for grid cuts
|
$self->arrange if @new_objects <= 2; # don't arrange for grid cuts
|
||||||
|
|
||||||
@ -2626,6 +3001,7 @@ use List::Util qw(first);
|
|||||||
use Slic3r::Geometry qw(X Y Z MIN MAX deg2rad);
|
use Slic3r::Geometry qw(X Y Z MIN MAX deg2rad);
|
||||||
|
|
||||||
has 'name' => (is => 'rw', required => 1);
|
has 'name' => (is => 'rw', required => 1);
|
||||||
|
has 'identifier' => (is => 'rw', required => 1);
|
||||||
has 'input_file' => (is => 'rw');
|
has 'input_file' => (is => 'rw');
|
||||||
has 'input_file_obj_idx' => (is => 'rw');
|
has 'input_file_obj_idx' => (is => 'rw');
|
||||||
has 'thumbnail' => (is => 'rw'); # ExPolygon::Collection in scaled model units with no transforms
|
has 'thumbnail' => (is => 'rw'); # ExPolygon::Collection in scaled model units with no transforms
|
||||||
|
BIN
var/arrow_redo.png
Normal file
BIN
var/arrow_redo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 625 B |
BIN
var/arrow_undo.png
Normal file
BIN
var/arrow_undo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 631 B |
Loading…
x
Reference in New Issue
Block a user