mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-08-06 05:24:01 -06:00
Merge branch 'master' into avoid-crossing-perimeters
Conflicts: lib/Slic3r/GCode.pm lib/Slic3r/GUI/Plater.pm lib/Slic3r/Print.pm lib/Slic3r/SVG.pm
This commit is contained in:
commit
48e00a4c40
52 changed files with 2388 additions and 821 deletions
|
@ -44,6 +44,7 @@ Slic3r::GUI::OptionsGroup - pre-filled Wx::StaticBoxSizer wrapper containing one
|
|||
has 'parent' => (is => 'ro', required => 1);
|
||||
has 'title' => (is => 'ro', required => 1);
|
||||
has 'options' => (is => 'ro', required => 1, trigger => 1);
|
||||
has 'lines' => (is => 'lazy');
|
||||
has 'on_change' => (is => 'ro', default => sub { sub {} });
|
||||
has 'no_labels' => (is => 'ro', default => sub { 0 });
|
||||
has 'label_width' => (is => 'ro', default => sub { 180 });
|
||||
|
@ -66,87 +67,149 @@ sub BUILD {
|
|||
$grid_sizer->SetFlexibleDirection(wxHORIZONTAL);
|
||||
$grid_sizer->AddGrowableCol($self->no_labels ? 0 : 1);
|
||||
|
||||
my $sidetext_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
|
||||
foreach my $opt (@{$self->options}) {
|
||||
my $opt_key = $opt->{opt_key};
|
||||
$self->_triggers->{$opt_key} = $opt->{on_change} || sub {};
|
||||
|
||||
my $label;
|
||||
if (!$self->no_labels) {
|
||||
$label = Wx::StaticText->new($self->parent, -1, "$opt->{label}:", wxDefaultPosition, [$self->label_width, -1]);
|
||||
$label->Wrap($self->label_width) ; # needed to avoid Linux/GTK bug
|
||||
$grid_sizer->Add($label, 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
}
|
||||
|
||||
my $field;
|
||||
if ($opt->{type} =~ /^(i|f|s|s@)$/) {
|
||||
my $style = 0;
|
||||
$style = wxTE_MULTILINE if $opt->{multiline};
|
||||
my $size = Wx::Size->new($opt->{width} || -1, $opt->{height} || -1);
|
||||
|
||||
$field = $opt->{type} eq 'i'
|
||||
? Wx::SpinCtrl->new($self->parent, -1, $opt->{default}, wxDefaultPosition, $size, $style, $opt->{min} || 0, $opt->{max} || 2147483647, $opt->{default})
|
||||
: Wx::TextCtrl->new($self->parent, -1, $opt->{default}, wxDefaultPosition, $size, $style);
|
||||
$field->Disable if $opt->{readonly};
|
||||
$self->_setters->{$opt_key} = sub { $field->SetValue($_[0]) };
|
||||
|
||||
my $on_change = sub { $self->_on_change($opt_key, $field->GetValue) };
|
||||
$opt->{type} eq 'i'
|
||||
? EVT_SPINCTRL ($self->parent, $field, $on_change)
|
||||
: EVT_TEXT ($self->parent, $field, $on_change);
|
||||
} elsif ($opt->{type} eq 'bool') {
|
||||
$field = Wx::CheckBox->new($self->parent, -1, "");
|
||||
$field->SetValue($opt->{default});
|
||||
EVT_CHECKBOX($self->parent, $field, sub { $self->_on_change($opt_key, $field->GetValue); });
|
||||
$self->_setters->{$opt_key} = sub { $field->SetValue($_[0]) };
|
||||
} elsif ($opt->{type} eq 'point') {
|
||||
$field = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
my $field_size = Wx::Size->new(40, -1);
|
||||
my @items = (
|
||||
Wx::StaticText->new($self->parent, -1, "x:"),
|
||||
my $x_field = Wx::TextCtrl->new($self->parent, -1, $opt->{default}->[0], wxDefaultPosition, $field_size),
|
||||
Wx::StaticText->new($self->parent, -1, " y:"),
|
||||
my $y_field = Wx::TextCtrl->new($self->parent, -1, $opt->{default}->[1], wxDefaultPosition, $field_size),
|
||||
);
|
||||
$field->Add($_, 0, wxALIGN_CENTER_VERTICAL, 0) for @items;
|
||||
if ($opt->{tooltip}) {
|
||||
$_->SetToolTipString($opt->{tooltip}) for @items;
|
||||
}
|
||||
EVT_TEXT($self->parent, $_, sub { $self->_on_change($opt_key, [ $x_field->GetValue, $y_field->GetValue ]) })
|
||||
for $x_field, $y_field;
|
||||
$self->_setters->{$opt_key} = sub {
|
||||
$x_field->SetValue($_[0][0]);
|
||||
$y_field->SetValue($_[0][1]);
|
||||
};
|
||||
} elsif ($opt->{type} eq 'select') {
|
||||
$field = Wx::ComboBox->new($self->parent, -1, "", wxDefaultPosition, wxDefaultSize, $opt->{labels} || $opt->{values}, wxCB_READONLY);
|
||||
EVT_COMBOBOX($self->parent, $field, sub {
|
||||
$self->_on_change($opt_key, $opt->{values}[$field->GetSelection]);
|
||||
});
|
||||
$self->_setters->{$opt_key} = sub {
|
||||
$field->SetSelection(grep $opt->{values}[$_] eq $_[0], 0..$#{$opt->{values}});
|
||||
};
|
||||
$self->_setters->{$opt_key}->($opt->{default});
|
||||
} else {
|
||||
die "Unsupported option type: " . $opt->{type};
|
||||
}
|
||||
$label->SetToolTipString($opt->{tooltip}) if $label && $opt->{tooltip};
|
||||
$field->SetToolTipString($opt->{tooltip}) if $opt->{tooltip} && $field->can('SetToolTipString');
|
||||
if ($opt->{sidetext}) {
|
||||
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$sizer->Add($field, 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
my $sidetext = Wx::StaticText->new($self->parent, -1, $opt->{sidetext}, wxDefaultPosition, wxDefaultSize);
|
||||
$sidetext->SetFont($sidetext_font);
|
||||
$sizer->Add($sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL , 4);
|
||||
$grid_sizer->Add($sizer);
|
||||
} else {
|
||||
$grid_sizer->Add($field, 0, ($opt->{full_width} ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
# TODO: border size may be related to wxWidgets 2.8.x vs. 2.9.x instead of wxMAC specific
|
||||
$self->sizer->Add($grid_sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 5);
|
||||
|
||||
$self->{sidetext_font} = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
|
||||
foreach my $line (@{$self->lines}) {
|
||||
if ($line->{widget}) {
|
||||
my $window = $line->{widget}->GetWindow($self->parent);
|
||||
$self->sizer->Add($window, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15);
|
||||
} else {
|
||||
$self->_build_line($line, $grid_sizer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# default behavior: one option per line
|
||||
sub _build_lines {
|
||||
my $self = shift;
|
||||
|
||||
my $lines = [];
|
||||
foreach my $opt (@{$self->options}) {
|
||||
push @$lines, {
|
||||
label => $opt->{label},
|
||||
sidetext => $opt->{sidetext},
|
||||
full_width => $opt->{full_width},
|
||||
options => [$opt->{opt_key}],
|
||||
};
|
||||
}
|
||||
return $lines;
|
||||
}
|
||||
|
||||
sub single_option_line {
|
||||
my $class = shift;
|
||||
my ($opt_key) = @_;
|
||||
|
||||
return {
|
||||
label => $Slic3r::Config::Options->{$opt_key}{label},
|
||||
sidetext => $Slic3r::Config::Options->{$opt_key}{sidetext},
|
||||
options => [$opt_key],
|
||||
};
|
||||
}
|
||||
|
||||
sub _build_line {
|
||||
my $self = shift;
|
||||
my ($line, $grid_sizer) = @_;
|
||||
|
||||
my $label;
|
||||
if (!$self->no_labels) {
|
||||
$label = Wx::StaticText->new($self->parent, -1, $line->{label} ? "$line->{label}:" : "", wxDefaultPosition, [$self->label_width, -1]);
|
||||
$label->Wrap($self->label_width) ; # needed to avoid Linux/GTK bug
|
||||
$grid_sizer->Add($label, 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
$label->SetToolTipString($line->{tooltip}) if $line->{tooltip};
|
||||
}
|
||||
|
||||
my @fields = ();
|
||||
my @field_labels = ();
|
||||
foreach my $opt_key (@{$line->{options}}) {
|
||||
my $opt = first { $_->{opt_key} eq $opt_key } @{$self->options};
|
||||
push @fields, $self->_build_field($opt);
|
||||
push @field_labels, $opt->{label};
|
||||
}
|
||||
if (@fields > 1 || $line->{sidetext}) {
|
||||
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
for my $i (0 .. $#fields) {
|
||||
if (@fields > 1 && $field_labels[$i]) {
|
||||
my $field_label = Wx::StaticText->new($self->parent, -1, "$field_labels[$i]:", wxDefaultPosition, wxDefaultSize);
|
||||
$field_label->SetFont($self->{sidetext_font});
|
||||
$sizer->Add($field_label, 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
}
|
||||
$sizer->Add($fields[$i], 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
}
|
||||
if ($line->{sidetext}) {
|
||||
my $sidetext = Wx::StaticText->new($self->parent, -1, $line->{sidetext}, wxDefaultPosition, wxDefaultSize);
|
||||
$sidetext->SetFont($self->{sidetext_font});
|
||||
$sizer->Add($sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL , 4);
|
||||
}
|
||||
$grid_sizer->Add($sizer);
|
||||
} else {
|
||||
$grid_sizer->Add($fields[0], 0, ($line->{full_width} ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
sub _build_field {
|
||||
my $self = shift;
|
||||
my ($opt) = @_;
|
||||
|
||||
my $opt_key = $opt->{opt_key};
|
||||
$self->_triggers->{$opt_key} = $opt->{on_change} || sub {};
|
||||
|
||||
my $field;
|
||||
if ($opt->{type} =~ /^(i|f|s|s@)$/) {
|
||||
my $style = 0;
|
||||
$style = wxTE_MULTILINE if $opt->{multiline};
|
||||
my $size = Wx::Size->new($opt->{width} || -1, $opt->{height} || -1);
|
||||
|
||||
$field = $opt->{type} eq 'i'
|
||||
? Wx::SpinCtrl->new($self->parent, -1, $opt->{default}, wxDefaultPosition, $size, $style, $opt->{min} || 0, $opt->{max} || 2147483647, $opt->{default})
|
||||
: Wx::TextCtrl->new($self->parent, -1, $opt->{default}, wxDefaultPosition, $size, $style);
|
||||
$field->Disable if $opt->{readonly};
|
||||
$self->_setters->{$opt_key} = sub { $field->SetValue($_[0]) };
|
||||
|
||||
my $on_change = sub { $self->_on_change($opt_key, $field->GetValue) };
|
||||
$opt->{type} eq 'i'
|
||||
? EVT_SPINCTRL ($self->parent, $field, $on_change)
|
||||
: EVT_TEXT ($self->parent, $field, $on_change);
|
||||
} elsif ($opt->{type} eq 'bool') {
|
||||
$field = Wx::CheckBox->new($self->parent, -1, "");
|
||||
$field->SetValue($opt->{default});
|
||||
EVT_CHECKBOX($self->parent, $field, sub { $self->_on_change($opt_key, $field->GetValue); });
|
||||
$self->_setters->{$opt_key} = sub { $field->SetValue($_[0]) };
|
||||
} elsif ($opt->{type} eq 'point') {
|
||||
$field = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
my $field_size = Wx::Size->new(40, -1);
|
||||
my @items = (
|
||||
Wx::StaticText->new($self->parent, -1, "x:"),
|
||||
my $x_field = Wx::TextCtrl->new($self->parent, -1, $opt->{default}->[0], wxDefaultPosition, $field_size),
|
||||
Wx::StaticText->new($self->parent, -1, " y:"),
|
||||
my $y_field = Wx::TextCtrl->new($self->parent, -1, $opt->{default}->[1], wxDefaultPosition, $field_size),
|
||||
);
|
||||
$field->Add($_, 0, wxALIGN_CENTER_VERTICAL, 0) for @items;
|
||||
if ($opt->{tooltip}) {
|
||||
$_->SetToolTipString($opt->{tooltip}) for @items;
|
||||
}
|
||||
EVT_TEXT($self->parent, $_, sub { $self->_on_change($opt_key, [ $x_field->GetValue, $y_field->GetValue ]) })
|
||||
for $x_field, $y_field;
|
||||
$self->_setters->{$opt_key} = sub {
|
||||
$x_field->SetValue($_[0][0]);
|
||||
$y_field->SetValue($_[0][1]);
|
||||
};
|
||||
} elsif ($opt->{type} eq 'select') {
|
||||
$field = Wx::ComboBox->new($self->parent, -1, "", wxDefaultPosition, wxDefaultSize, $opt->{labels} || $opt->{values}, wxCB_READONLY);
|
||||
EVT_COMBOBOX($self->parent, $field, sub {
|
||||
$self->_on_change($opt_key, $opt->{values}[$field->GetSelection]);
|
||||
});
|
||||
$self->_setters->{$opt_key} = sub {
|
||||
$field->SetSelection(grep $opt->{values}[$_] eq $_[0], 0..$#{$opt->{values}});
|
||||
};
|
||||
$self->_setters->{$opt_key}->($opt->{default});
|
||||
} else {
|
||||
die "Unsupported option type: " . $opt->{type};
|
||||
}
|
||||
$field->SetToolTipString($opt->{tooltip}) if $opt->{tooltip} && $field->can('SetToolTipString');
|
||||
return $field;
|
||||
}
|
||||
|
||||
sub _option {
|
||||
|
@ -308,4 +371,27 @@ sub _config_methods {
|
|||
: qw(get 0);
|
||||
}
|
||||
|
||||
package Slic3r::GUI::OptionsGroup::StaticTextLine;
|
||||
use Moo;
|
||||
use Wx qw(:misc :systemsettings);
|
||||
|
||||
sub GetWindow {
|
||||
my $self = shift;
|
||||
my ($parent) = @_;
|
||||
|
||||
$self->{statictext} = Wx::StaticText->new($parent, -1, "foo", wxDefaultPosition, wxDefaultSize);
|
||||
my $font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
$self->{statictext}->SetFont($font);
|
||||
return $self->{statictext};
|
||||
}
|
||||
|
||||
sub SetText {
|
||||
my $self = shift;
|
||||
my ($value) = @_;
|
||||
|
||||
$self->{statictext}->SetLabel($value);
|
||||
$self->{statictext}->Wrap(400);
|
||||
$self->{statictext}->GetParent->Layout;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -4,13 +4,13 @@ use warnings;
|
|||
use utf8;
|
||||
|
||||
use File::Basename qw(basename dirname);
|
||||
use List::Util qw(max sum);
|
||||
use List::Util qw(max sum first);
|
||||
use Math::ConvexHull::MonotoneChain qw(convex_hull);
|
||||
use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX);
|
||||
use Slic3r::Geometry::Clipper qw(JT_ROUND);
|
||||
use threads::shared qw(shared_clone);
|
||||
use Wx qw(:bitmap :brush :button :cursor :dialog :filedialog :font :keycode :icon :id :listctrl :misc :panel :pen :sizer :toolbar :window);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL EVT_CHOICE);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL EVT_CHOICE);
|
||||
use base 'Wx::Panel';
|
||||
|
||||
use constant TB_MORE => &Wx::NewId;
|
||||
|
@ -87,6 +87,7 @@ sub new {
|
|||
$self->{list}->InsertColumn(2, "Scale", wxLIST_FORMAT_CENTER, wxLIST_AUTOSIZE_USEHEADER);
|
||||
EVT_LIST_ITEM_SELECTED($self, $self->{list}, \&list_item_selected);
|
||||
EVT_LIST_ITEM_DESELECTED($self, $self->{list}, \&list_item_deselected);
|
||||
EVT_LIST_ITEM_ACTIVATED($self, $self->{list}, \&list_item_activated);
|
||||
EVT_KEY_DOWN($self->{list}, sub {
|
||||
my ($list, $event) = @_;
|
||||
if ($event->GetKeyCode == WXK_TAB) {
|
||||
|
@ -222,11 +223,7 @@ sub new {
|
|||
my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT);
|
||||
my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, [150, -1], []);
|
||||
$self->{preset_choosers}{$group} = [$choice];
|
||||
EVT_CHOICE($choice, $choice, sub {
|
||||
my $choice = shift; # avoid leaks
|
||||
return if $group eq 'filament' && @{$self->{preset_choosers}{filament}} > 1; #/
|
||||
$self->skeinpanel->{options_tabs}{$group}->select_preset($choice->GetSelection);
|
||||
});
|
||||
EVT_CHOICE($choice, $choice, sub { $self->on_select_preset($group, @_) });
|
||||
|
||||
$self->{preset_choosers_sizers}{$group} = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$self->{preset_choosers_sizers}{$group}->Add($choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING);
|
||||
|
@ -246,6 +243,21 @@ sub new {
|
|||
return $self;
|
||||
}
|
||||
|
||||
sub on_select_preset {
|
||||
my $self = shift;
|
||||
my ($group, $choice) = @_;
|
||||
|
||||
if ($group eq 'filament' && @{$self->{preset_choosers}{filament}} > 1) {
|
||||
my @filament_presets = $self->filament_presets;
|
||||
$Slic3r::GUI::Settings->{presets}{filament} = $choice->GetString($filament_presets[0]) . ".ini";
|
||||
$Slic3r::GUI::Settings->{presets}{"filament_${_}"} = $choice->GetString($filament_presets[$_])
|
||||
for 1 .. $#filament_presets;
|
||||
Slic3r::GUI->save_settings;
|
||||
return;
|
||||
}
|
||||
$self->skeinpanel->{options_tabs}{$group}->select_preset($choice->GetSelection);
|
||||
}
|
||||
|
||||
sub skeinpanel {
|
||||
my $self = shift;
|
||||
return $self->GetParent->GetParent;
|
||||
|
@ -308,7 +320,7 @@ sub load_file {
|
|||
: [0,0],
|
||||
],
|
||||
);
|
||||
$object->mesh->check_manifoldness;
|
||||
$object->check_manifoldness;
|
||||
|
||||
# we only consider the rotation of the first instance for now
|
||||
$object->set_rotation($model->objects->[$i]->instances->[0]->rotation)
|
||||
|
@ -404,6 +416,9 @@ sub rotate {
|
|||
|
||||
my ($obj_idx, $object) = $self->selected_object;
|
||||
|
||||
# we need thumbnail to be computed before allowing rotation
|
||||
return if !$object->thumbnail;
|
||||
|
||||
if (!defined $angle) {
|
||||
$angle = Wx::GetNumberFromUser("", "Enter the rotation angle:", "Rotate", $object->rotate, -364, 364, $self);
|
||||
return if !$angle || $angle == -1;
|
||||
|
@ -434,7 +449,7 @@ sub arrange {
|
|||
my $total_parts = sum(map $_->instances_count, @{$self->{objects}}) or return;
|
||||
my @size = ();
|
||||
for my $a (X,Y) {
|
||||
$size[$a] = max(map $_->rotated_size->[$a], @{$self->{objects}});
|
||||
$size[$a] = max(map $_->size->[$a], @{$self->{objects}});
|
||||
}
|
||||
|
||||
eval {
|
||||
|
@ -477,13 +492,18 @@ sub split_object {
|
|||
# thumbnail thread returns)
|
||||
$self->remove($obj_idx);
|
||||
|
||||
# create a bogus Model object, we only need to instantiate the new Model::Object objects
|
||||
my $new_model = Slic3r::Model->new;
|
||||
|
||||
foreach my $mesh (@new_meshes) {
|
||||
my @extents = $mesh->extents;
|
||||
my $model_object = $new_model->add_object(vertices => $mesh->vertices);
|
||||
$model_object->add_volume(facets => $mesh->facets);
|
||||
my $object = Slic3r::GUI::Plater::Object->new(
|
||||
name => basename($current_object->input_file),
|
||||
input_file => $current_object->input_file,
|
||||
input_file_object_id => undef,
|
||||
mesh => $mesh,
|
||||
model_object => $model_object,
|
||||
instances => [ map [$extents[X][MIN], $extents[Y][MIN]], 1..$current_copies_num ],
|
||||
);
|
||||
push @{ $self->{objects} }, $object;
|
||||
|
@ -688,7 +708,7 @@ sub make_model {
|
|||
material_id => $volume->material_id,
|
||||
facets => $volume->facets,
|
||||
);
|
||||
$model->materials->{$volume->material_id || 0} ||= {};
|
||||
$model->set_material($volume->material_id || 0, {});
|
||||
}
|
||||
$new_model_object->scale($plater_object->scale);
|
||||
$new_model_object->add_instance(
|
||||
|
@ -704,9 +724,11 @@ sub make_thumbnail {
|
|||
my $self = shift;
|
||||
my ($obj_idx) = @_;
|
||||
|
||||
my $object = $self->{objects}[$obj_idx];
|
||||
$object->thumbnail_scaling_factor($self->{scaling_factor});
|
||||
my $cb = sub {
|
||||
my $object = $self->{objects}[$obj_idx];
|
||||
my $thumbnail = $object->make_thumbnail(scaling_factor => $self->{scaling_factor});
|
||||
$Slic3r::Geometry::Clipper::clipper = Math::Clipper->new if $Slic3r::have_threads;
|
||||
my $thumbnail = $object->make_thumbnail;
|
||||
|
||||
if ($Slic3r::have_threads) {
|
||||
Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $THUMBNAIL_DONE_EVENT, shared_clone([ $obj_idx, $thumbnail ])));
|
||||
|
@ -739,7 +761,7 @@ sub recenter {
|
|||
my $obj = $_;
|
||||
map {
|
||||
my $instance = $_;
|
||||
$instance, [ map $instance->[$_] + $obj->rotated_size->[$_], X,Y ];
|
||||
$instance, [ map $instance->[$_] + $obj->size->[$_], X,Y ];
|
||||
} @{$obj->instances};
|
||||
} @{$self->{objects}},
|
||||
]);
|
||||
|
@ -755,8 +777,12 @@ sub on_config_change {
|
|||
if ($opt_key eq 'extruders_count' && defined $value) {
|
||||
my $choices = $self->{preset_choosers}{filament};
|
||||
while (@$choices < $value) {
|
||||
push @$choices, Wx::Choice->new($self, -1, wxDefaultPosition, [150, -1], [$choices->[0]->GetStrings]);
|
||||
my @presets = $choices->[0]->GetStrings;
|
||||
push @$choices, Wx::Choice->new($self, -1, wxDefaultPosition, [150, -1], [@presets]);
|
||||
$self->{preset_choosers_sizers}{filament}->Add($choices->[-1], 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING);
|
||||
EVT_CHOICE($choices->[-1], $choices->[-1], sub { $self->on_select_preset('filament', @_) });
|
||||
my $i = first { $choices->[-1]->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets;
|
||||
$choices->[-1]->SetSelection($i || 0);
|
||||
}
|
||||
while (@$choices > $value) {
|
||||
$self->{preset_choosers_sizers}{filament}->Remove(-1);
|
||||
|
@ -832,7 +858,8 @@ sub repaint {
|
|||
for my $instance_idx (0 .. $#{$object->instances}) {
|
||||
my $instance = $object->instances->[$instance_idx];
|
||||
push @{$parent->{object_previews}}, [ $obj_idx, $instance_idx, $object->thumbnail->clone ];
|
||||
$parent->{object_previews}->[-1][2]->translate(map $parent->to_pixel($instance->[$_]) + $parent->{shift}[$_], (X,Y));
|
||||
$_->translate(map $parent->to_pixel($instance->[$_]) + $parent->{shift}[$_], (X,Y))
|
||||
for @{$parent->{object_previews}->[-1][2]->expolygons};
|
||||
|
||||
my $drag_object = $self->{drag_object};
|
||||
if (defined $drag_object && $obj_idx == $drag_object->[0] && $instance_idx == $drag_object->[1]) {
|
||||
|
@ -842,11 +869,12 @@ sub repaint {
|
|||
} else {
|
||||
$dc->SetBrush($parent->{objects_brush});
|
||||
}
|
||||
$dc->DrawPolygon($parent->_y($parent->{object_previews}->[-1][2]), 0, 0);
|
||||
$dc->DrawPolygon($parent->_y($_), 0, 0) for map $_->contour, @{ $parent->{object_previews}->[-1][2]->expolygons };
|
||||
|
||||
# if sequential printing is enabled and we have more than one object
|
||||
if ($parent->{config}->complete_objects && (map @{$_->instances}, @{$parent->{objects}}) > 1) {
|
||||
my $clearance = +($parent->{object_previews}->[-1][2]->offset($parent->{config}->extruder_clearance_radius / 2 * $parent->{scaling_factor}, 1, JT_ROUND))[0];
|
||||
my $convex_hull = Slic3r::Polygon->new(convex_hull([ map @{$_->contour}, @{$parent->{object_previews}->[-1][2]->expolygons} ]));
|
||||
my $clearance = +($convex_hull->offset($parent->{config}->extruder_clearance_radius / 2 * $parent->{scaling_factor}, 1, JT_ROUND))[0];
|
||||
$dc->SetPen($parent->{clearance_pen});
|
||||
$dc->SetBrush($parent->{transparent_brush});
|
||||
$dc->DrawPolygon($parent->_y($clearance), 0, 0);
|
||||
|
@ -856,7 +884,7 @@ sub repaint {
|
|||
|
||||
# draw skirt
|
||||
if (@{$parent->{object_previews}} && $parent->{config}->skirts) {
|
||||
my $convex_hull = Slic3r::Polygon->new(convex_hull([ map @{$_->[2]}, @{$parent->{object_previews}} ]));
|
||||
my $convex_hull = Slic3r::Polygon->new(convex_hull([ map @{$_->contour}, map @{$_->[2]->expolygons}, @{$parent->{object_previews}} ]));
|
||||
$convex_hull = +($convex_hull->offset($parent->{config}->skirt_distance * $parent->{scaling_factor}, 1, JT_ROUND))[0];
|
||||
$dc->SetPen($parent->{skirt_pen});
|
||||
$dc->SetBrush($parent->{transparent_brush});
|
||||
|
@ -878,7 +906,7 @@ sub mouse_event {
|
|||
$parent->selection_changed(0);
|
||||
for my $preview (@{$parent->{object_previews}}) {
|
||||
my ($obj_idx, $instance_idx, $thumbnail) = @$preview;
|
||||
if ($thumbnail->encloses_point($pos)) {
|
||||
if (first { $_->contour->encloses_point($pos) } @{$thumbnail->expolygons}) {
|
||||
$parent->{selected_objects} = [ [$obj_idx, $instance_idx] ];
|
||||
$parent->{list}->Select($obj_idx, 1);
|
||||
$parent->selection_changed(1);
|
||||
|
@ -894,6 +922,9 @@ sub mouse_event {
|
|||
$self->{drag_start_pos} = undef;
|
||||
$self->{drag_object} = undef;
|
||||
$self->SetCursor(wxSTANDARD_CURSOR);
|
||||
} elsif ($event->ButtonDClick) {
|
||||
$parent->list_item_activated(undef, $parent->{selected_objects}->[0][0])
|
||||
if @{$parent->{selected_objects}};
|
||||
} elsif ($event->Dragging) {
|
||||
return if !$self->{drag_start_pos}; # concurrency problems
|
||||
for my $preview ($self->{drag_object}) {
|
||||
|
@ -906,7 +937,7 @@ sub mouse_event {
|
|||
} elsif ($event->Moving) {
|
||||
my $cursor = wxSTANDARD_CURSOR;
|
||||
for my $preview (@{$parent->{object_previews}}) {
|
||||
if ($preview->[2]->encloses_point($pos)) {
|
||||
if (first { $_->contour->encloses_point($pos) } @{ $preview->[2]->expolygons }) {
|
||||
$cursor = Wx::Cursor->new(wxCURSOR_HAND);
|
||||
last;
|
||||
}
|
||||
|
@ -934,6 +965,16 @@ sub list_item_selected {
|
|||
$self->selection_changed(1);
|
||||
}
|
||||
|
||||
sub list_item_activated {
|
||||
my ($self, $event, $obj_idx) = @_;
|
||||
|
||||
$obj_idx //= $event->GetIndex;
|
||||
my $dlg = Slic3r::GUI::Plater::ObjectInfoDialog->new($self,
|
||||
object => $self->{objects}[$obj_idx],
|
||||
);
|
||||
$dlg->ShowModal;
|
||||
}
|
||||
|
||||
sub object_list_changed {
|
||||
my $self = shift;
|
||||
|
||||
|
@ -1014,7 +1055,7 @@ package Slic3r::GUI::Plater::Object;
|
|||
use Moo;
|
||||
|
||||
use Math::ConvexHull::MonotoneChain qw(convex_hull);
|
||||
use Slic3r::Geometry qw(X Y);
|
||||
use Slic3r::Geometry qw(X Y Z);
|
||||
|
||||
has 'name' => (is => 'rw', required => 1);
|
||||
has 'input_file' => (is => 'rw', required => 1);
|
||||
|
@ -1025,10 +1066,30 @@ has 'scale' => (is => 'rw', default => sub { 1 });
|
|||
has 'rotate' => (is => 'rw', default => sub { 0 });
|
||||
has 'instances' => (is => 'rw', default => sub { [] }); # upward Y axis
|
||||
has 'thumbnail' => (is => 'rw');
|
||||
has 'thumbnail_scaling_factor' => (is => 'rw');
|
||||
|
||||
# statistics
|
||||
has 'facets' => (is => 'rw');
|
||||
has 'vertices' => (is => 'rw');
|
||||
has 'materials' => (is => 'rw');
|
||||
has 'is_manifold' => (is => 'rw');
|
||||
|
||||
sub _trigger_model_object {
|
||||
my $self = shift;
|
||||
$self->size([$self->model_object->mesh->size]) if $self->model_object;
|
||||
if ($self->model_object) {
|
||||
my $mesh = $self->model_object->mesh;
|
||||
$self->size([$mesh->size]);
|
||||
$self->facets(scalar @{$mesh->facets});
|
||||
$self->vertices(scalar @{$mesh->vertices});
|
||||
$self->materials($self->model_object->materials_count);
|
||||
}
|
||||
}
|
||||
|
||||
sub check_manifoldness {
|
||||
my $self = shift;
|
||||
|
||||
$self->is_manifold($self->get_model_object->mesh->check_manifoldness);
|
||||
return $self->is_manifold;
|
||||
}
|
||||
|
||||
sub free_model_object {
|
||||
|
@ -1054,22 +1115,29 @@ sub instances_count {
|
|||
|
||||
sub make_thumbnail {
|
||||
my $self = shift;
|
||||
my %params = @_;
|
||||
|
||||
my @points = map [ @$_[X,Y] ], @{$self->model_object->mesh->vertices};
|
||||
my $convex_hull = Slic3r::Polygon->new(convex_hull(\@points));
|
||||
for (@$convex_hull) {
|
||||
@$_ = map $_ * $params{scaling_factor}, @$_;
|
||||
my $mesh = $self->model_object->mesh;
|
||||
my $thumbnail = Slic3r::ExPolygon::Collection->new(
|
||||
expolygons => (@{$mesh->facets} <= 5000)
|
||||
? $mesh->horizontal_projection
|
||||
: [ Slic3r::ExPolygon->new(convex_hull($mesh->vertices)) ],
|
||||
);
|
||||
for (map @$_, map @$_, @{$thumbnail->expolygons}) {
|
||||
@$_ = map $_ * $self->thumbnail_scaling_factor, @$_;
|
||||
}
|
||||
$convex_hull->simplify(0.3);
|
||||
$convex_hull->rotate(Slic3r::Geometry::deg2rad($self->rotate));
|
||||
$convex_hull->scale($self->scale);
|
||||
$convex_hull->align_to_origin;
|
||||
|
||||
$self->thumbnail($convex_hull); # ignored in multi-threaded environments
|
||||
foreach my $expolygon (@{$thumbnail->expolygons}) {
|
||||
@$expolygon = grep $_->area >= 1, @$expolygon;
|
||||
$expolygon->simplify(0.5);
|
||||
$expolygon->rotate(Slic3r::Geometry::deg2rad($self->rotate));
|
||||
$expolygon->scale($self->scale);
|
||||
}
|
||||
@{$thumbnail->expolygons} = grep @$_, @{$thumbnail->expolygons};
|
||||
$thumbnail->align_to_origin;
|
||||
$self->thumbnail($thumbnail); # ignored in multi-threaded environments
|
||||
$self->free_model_object;
|
||||
|
||||
return $convex_hull;
|
||||
return $thumbnail;
|
||||
}
|
||||
|
||||
sub set_rotation {
|
||||
|
@ -1079,6 +1147,8 @@ sub set_rotation {
|
|||
if ($self->thumbnail) {
|
||||
$self->thumbnail->rotate(Slic3r::Geometry::deg2rad($angle - $self->rotate));
|
||||
$self->thumbnail->align_to_origin;
|
||||
my $z_size = $self->size->[Z];
|
||||
$self->size([ (map $_ / $self->thumbnail_scaling_factor, @{$self->thumbnail->size}), $z_size ]);
|
||||
}
|
||||
$self->rotate($angle);
|
||||
}
|
||||
|
@ -1089,20 +1159,67 @@ sub set_scale {
|
|||
|
||||
my $factor = $scale / $self->scale;
|
||||
return if $factor == 1;
|
||||
$self->size->[$_] *= $factor for X,Y;
|
||||
$self->size->[$_] *= $factor for X,Y,Z;
|
||||
if ($self->thumbnail) {
|
||||
$self->thumbnail->scale($factor);
|
||||
$self->thumbnail->align_to_origin;
|
||||
$_->scale($factor) for @{$self->thumbnail->expolygons};
|
||||
$self->thumbnail->align_to_origin;
|
||||
}
|
||||
$self->scale($scale);
|
||||
}
|
||||
|
||||
sub rotated_size {
|
||||
my $self = shift;
|
||||
package Slic3r::GUI::Plater::ObjectInfoDialog;
|
||||
use Wx qw(:dialog :id :misc :sizer :systemsettings);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_TEXT_ENTER);
|
||||
use base 'Wx::Dialog';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, %params) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, "Object Info", wxDefaultPosition, wxDefaultSize);
|
||||
$self->{object} = $params{object};
|
||||
|
||||
my $properties_box = Wx::StaticBox->new($self, -1, "Info", wxDefaultPosition, [400,200]);
|
||||
my $grid_sizer = Wx::FlexGridSizer->new(3, 2, 10, 5);
|
||||
$properties_box->SetSizer($grid_sizer);
|
||||
$grid_sizer->SetFlexibleDirection(wxHORIZONTAL);
|
||||
$grid_sizer->AddGrowableCol(1);
|
||||
|
||||
return Slic3r::Polygon->new([0,0], [$self->size->[X], 0], [@{$self->size}], [0, $self->size->[Y]])
|
||||
->rotate(Slic3r::Geometry::deg2rad($self->rotate))
|
||||
->size;
|
||||
my $label_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
$label_font->SetPointSize(10);
|
||||
|
||||
my $properties = $self->get_properties;
|
||||
foreach my $property (@$properties) {
|
||||
my $label = Wx::StaticText->new($properties_box, -1, $property->[0] . ":");
|
||||
my $value = Wx::StaticText->new($properties_box, -1, $property->[1]);
|
||||
$label->SetFont($label_font);
|
||||
$grid_sizer->Add($label, 1, wxALIGN_BOTTOM);
|
||||
$grid_sizer->Add($value, 0);
|
||||
}
|
||||
|
||||
my $buttons = $self->CreateStdDialogButtonSizer(wxOK);
|
||||
EVT_BUTTON($self, wxID_OK, sub { $self->EndModal(wxID_OK); });
|
||||
|
||||
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$sizer->Add($properties_box, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
|
||||
$sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
|
||||
$self->SetSizer($sizer);
|
||||
$sizer->SetSizeHints($self);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_properties {
|
||||
my $self = shift;
|
||||
|
||||
return [
|
||||
['Name' => $self->{object}->name],
|
||||
['Size' => sprintf "%.2f x %.2f x %.2f", @{$self->{object}->size}],
|
||||
['Facets' => $self->{object}->facets],
|
||||
['Vertices' => $self->{object}->vertices],
|
||||
['Materials' => $self->{object}->materials],
|
||||
['Two-Manifold' => $self->{object}->is_manifold ? 'Yes' : 'No'],
|
||||
];
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -396,7 +396,13 @@ sub build {
|
|||
},
|
||||
{
|
||||
title => 'Horizontal shells',
|
||||
options => [qw(solid_layers)],
|
||||
options => [qw(top_solid_layers bottom_solid_layers)],
|
||||
lines => [
|
||||
{
|
||||
label => 'Solid layers',
|
||||
options => [qw(top_solid_layers bottom_solid_layers)],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title => 'Advanced',
|
||||
|
@ -418,7 +424,7 @@ sub build {
|
|||
$self->add_options_page('Speed', 'time.png', optgroups => [
|
||||
{
|
||||
title => 'Speed for print moves',
|
||||
options => [qw(perimeter_speed small_perimeter_speed external_perimeter_speed infill_speed solid_infill_speed top_solid_infill_speed bridge_speed)],
|
||||
options => [qw(perimeter_speed small_perimeter_speed external_perimeter_speed infill_speed solid_infill_speed top_solid_infill_speed support_material_speed bridge_speed gap_fill_speed)],
|
||||
},
|
||||
{
|
||||
title => 'Speed for non-print moves',
|
||||
|
@ -428,12 +434,16 @@ sub build {
|
|||
title => 'Modifiers',
|
||||
options => [qw(first_layer_speed)],
|
||||
},
|
||||
{
|
||||
title => 'Acceleration control (advanced)',
|
||||
options => [qw(perimeter_acceleration infill_acceleration default_acceleration)],
|
||||
},
|
||||
]);
|
||||
|
||||
$self->add_options_page('Skirt and brim', 'box.png', optgroups => [
|
||||
{
|
||||
title => 'Skirt',
|
||||
options => [qw(skirts skirt_distance skirt_height)],
|
||||
options => [qw(skirts skirt_distance skirt_height min_skirt_length)],
|
||||
},
|
||||
{
|
||||
title => 'Brim',
|
||||
|
@ -460,6 +470,13 @@ sub build {
|
|||
{
|
||||
title => 'Sequential printing',
|
||||
options => [qw(complete_objects extruder_clearance_radius extruder_clearance_height)],
|
||||
lines => [
|
||||
Slic3r::GUI::OptionsGroup->single_option_line('complete_objects'),
|
||||
{
|
||||
label => 'Extruder clearance (mm)',
|
||||
options => [qw(extruder_clearance_radius extruder_clearance_height)],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title => 'Output file',
|
||||
|
@ -513,8 +530,18 @@ sub build {
|
|||
options => ['filament_diameter#0', 'extrusion_multiplier#0'],
|
||||
},
|
||||
{
|
||||
title => 'Temperature',
|
||||
title => 'Temperature (°C)',
|
||||
options => ['temperature#0', 'first_layer_temperature#0', qw(bed_temperature first_layer_bed_temperature)],
|
||||
lines => [
|
||||
{
|
||||
label => 'Extruder',
|
||||
options => ['first_layer_temperature#0', 'temperature#0'],
|
||||
},
|
||||
{
|
||||
label => 'Bed',
|
||||
options => [qw(first_layer_bed_temperature bed_temperature)],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -522,10 +549,26 @@ sub build {
|
|||
{
|
||||
title => 'Enable',
|
||||
options => [qw(cooling)],
|
||||
lines => [
|
||||
Slic3r::GUI::OptionsGroup->single_option_line('cooling'),
|
||||
{
|
||||
label => '',
|
||||
widget => ($self->{description_line} = Slic3r::GUI::OptionsGroup::StaticTextLine->new),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title => 'Fan settings',
|
||||
options => [qw(min_fan_speed max_fan_speed bridge_fan_speed disable_fan_first_layers fan_always_on)],
|
||||
lines => [
|
||||
{
|
||||
label => 'Fan speed',
|
||||
options => [qw(min_fan_speed max_fan_speed)],
|
||||
},
|
||||
Slic3r::GUI::OptionsGroup->single_option_line('bridge_fan_speed'),
|
||||
Slic3r::GUI::OptionsGroup->single_option_line('disable_fan_first_layers'),
|
||||
Slic3r::GUI::OptionsGroup->single_option_line('fan_always_on'),
|
||||
],
|
||||
},
|
||||
{
|
||||
title => 'Cooling thresholds',
|
||||
|
@ -535,6 +578,36 @@ sub build {
|
|||
]);
|
||||
}
|
||||
|
||||
sub _update_description {
|
||||
my $self = shift;
|
||||
|
||||
my $config = $self->config;
|
||||
|
||||
my $msg = "";
|
||||
if ($config->cooling) {
|
||||
$msg = sprintf "If estimated layer time is below ~%ds, fan will run at 100%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s).",
|
||||
$config->slowdown_below_layer_time, $config->slowdown_below_layer_time, $config->min_print_speed;
|
||||
if ($config->fan_below_layer_time > $config->slowdown_below_layer_time) {
|
||||
$msg .= sprintf "\nIf estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%.",
|
||||
$config->fan_below_layer_time, $config->max_fan_speed, $config->min_fan_speed;
|
||||
}
|
||||
if ($config->fan_always_on) {
|
||||
$msg .= sprintf "\nDuring the other layers, fan will always run at %d%%.", $config->min_fan_speed;
|
||||
} else {
|
||||
$msg .= "\nDuring the other layers, fan will be turned off."
|
||||
}
|
||||
}
|
||||
$self->{description_line}->SetText($msg);
|
||||
}
|
||||
|
||||
sub on_value_change {
|
||||
my $self = shift;
|
||||
my ($opt_key) = @_;
|
||||
$self->SUPER::on_value_change(@_);
|
||||
|
||||
$self->_update_description;
|
||||
}
|
||||
|
||||
package Slic3r::GUI::Tab::Printer;
|
||||
use base 'Slic3r::GUI::Tab';
|
||||
|
||||
|
@ -569,6 +642,10 @@ sub build {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title => 'Advanced',
|
||||
options => [qw(vibration_limit)],
|
||||
},
|
||||
]);
|
||||
|
||||
$self->add_options_page('Custom G-code', 'cog.png', optgroups => [
|
||||
|
@ -587,6 +664,11 @@ sub build {
|
|||
no_labels => 1,
|
||||
options => [qw(layer_gcode)],
|
||||
},
|
||||
{
|
||||
title => 'Tool change G-code',
|
||||
no_labels => 1,
|
||||
options => [qw(toolchange_gcode)],
|
||||
},
|
||||
]);
|
||||
|
||||
$self->{extruder_pages} = [];
|
||||
|
@ -726,7 +808,8 @@ sub append_optgroup {
|
|||
my $self = shift;
|
||||
my %params = @_;
|
||||
|
||||
my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
|
||||
my $class = $params{class} || 'Slic3r::GUI::ConfigOptionsGroup';
|
||||
my $optgroup = $class->new(
|
||||
parent => $self,
|
||||
config => $self->GetParent->{config},
|
||||
label_width => 200,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue