diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index ae1912120..6693dcb18 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -23,7 +23,7 @@ $Options->{threads}{readonly} = !$Slic3r::have_threads; *{$opt_key} = sub { $_[0]->get($opt_key) }; } } - +sub bed_size { [200,200] } sub new_from_defaults { my $class = shift; my (@opt_keys) = @_; diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index e9ad5399b..29c6708a6 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -6,6 +6,7 @@ use utf8; use File::Basename qw(basename); use FindBin; use Slic3r::GUI::AboutDialog; +use Slic3r::GUI::BedShapeDialog; use Slic3r::GUI::ConfigWizard; use Slic3r::GUI::MainFrame; use Slic3r::GUI::Notifier; diff --git a/lib/Slic3r/GUI/BedShapeDialog.pm b/lib/Slic3r/GUI/BedShapeDialog.pm new file mode 100644 index 000000000..2cc7998b7 --- /dev/null +++ b/lib/Slic3r/GUI/BedShapeDialog.pm @@ -0,0 +1,167 @@ +package Slic3r::GUI::BedShapeDialog; +use strict; +use warnings; +use utf8; + +use List::Util qw(min max); +use Slic3r::Geometry qw(PI X Y unscale); +use Wx qw(:dialog :id :misc :sizer :choicebook wxTAB_TRAVERSAL); +use Wx::Event qw(EVT_CLOSE EVT_BUTTON EVT_CHOICE); +use base 'Wx::Dialog'; + +use constant SHAPE_RECTANGULAR => 0; +use constant SHAPE_CIRCULAR => 1; +use constant SHAPE_CUSTOM => 2; + +sub new { + my $class = shift; + my ($parent, $default) = @_; + my $self = $class->SUPER::new($parent, -1, "Bed Shape", wxDefaultPosition, [350,700], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); + + my $box = Wx::StaticBox->new($self, -1, "Shape"); + my $sbsizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); + + # shape options + $self->{shape_options_book} = Wx::Choicebook->new($self, -1, wxDefaultPosition, [300,-1], wxCHB_TOP); + $sbsizer->Add($self->{shape_options_book}); + + $self->{optgroups} = []; + $self->_init_shape_options_page('Rectangular', [ + { + opt_key => 'rect_size', + type => 'point', + label => 'Size', + tooltip => 'Size in X and Y of the rectangular plate.', + default => [200,200], + }, + { + opt_key => 'rect_origin', + type => 'select', + label => 'Origin', + tooltip => 'Position of the 0,0 point.', + labels => ['Front left corner','Center'], + values => ['corner','center'], + default => 'corner', + }, + ]); + + # right pane with preview canvas + my $canvas; + + # main sizer + my $top_sizer = Wx::BoxSizer->new(wxHORIZONTAL); + $top_sizer->Add($sbsizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); + $top_sizer->Add($canvas, 1, wxEXPAND | wxALL, 0) if $canvas; + + my $main_sizer = Wx::BoxSizer->new(wxVERTICAL); + $main_sizer->Add($top_sizer, 1, wxEXPAND); + $main_sizer->Add($self->CreateButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND); + + $self->SetSizer($main_sizer); + $self->SetMinSize($self->GetSize); + $main_sizer->SetSizeHints($self); + + # needed to actually free memory + EVT_CLOSE($self, sub { + $self->EndModal(wxID_OK); + $self->Destroy; + }); + + $self->_set_shape($default); + $self->_update_preview; + + return $self; +} + +sub _set_shape { + my ($self, $points) = @_; + + $self->{bed_shape} = $points; + + # is this a rectangle? + if (@$points == 4) { + my $polygon = Slic3r::Polygon->new_scale(@$points); + my $lines = $polygon->lines; + if ($lines->[0]->parallel_to_line($lines->[2]) && $lines->[1]->parallel_to_line($lines->[3])) { + # okay, it's a rectangle + # let's check whether origin is at a known point + my $x_min = min(map $_->[X], @$points); + my $x_max = max(map $_->[X], @$points); + my $y_min = min(map $_->[Y], @$points); + my $y_max = max(map $_->[Y], @$points); + my $origin; + if ($x_min == 0 && $y_min == 0) { + $origin = 'corner'; + } elsif (($x_min + $x_max)/2 == 0 && ($y_min + $y_max)/2 == 0) { + $origin = 'center'; + } + if (defined $origin) { + $self->{shape_options_book}->SetSelection(SHAPE_RECTANGULAR); + my $optgroup = $self->{optgroups}[SHAPE_RECTANGULAR]; + $optgroup->set_value('rect_size', [ $x_max-$x_min, $y_max-$y_min ]); + $optgroup->set_value('rect_origin', $origin); + return; + } + } + } + + $self->{shape_options_book}->SetSelection(SHAPE_CUSTOM); +} + +sub _update_shape { + my ($self) = @_; + + my $page_idx = $self->{shape_options_book}->GetSelection; + if ($page_idx == SHAPE_RECTANGULAR) { + return if grep !defined($self->{"_$_"}), qw(rect_size rect_origin); # not loaded yet + my ($x, $y) = @{$self->{_rect_size}}; + my ($x0, $y0) = (0,0); + my ($x1, $y1) = ($x,$y); + if ($self->{_rect_origin} eq 'center') { + $x0 -= $x/2; + $x1 -= $x/2; + $y0 -= $y/2; + $y1 -= $y/2; + } + $self->{bed_shape} = [ + [$x0,$y0], + [$x1,$y0], + [$x1,$y1], + [$x0,$y1], + ]; + } + + $self->_update_preview; +} + +sub _update_preview { + my ($self) = @_; + + +} + +sub _init_shape_options_page { + my ($self, $title, $options) = @_; + + my $panel = Wx::Panel->new($self->{shape_options_book}); + push @{$self->{optgroups}}, my $optgroup = Slic3r::GUI::OptionsGroup->new( + parent => $panel, + title => 'Settings', + options => $options, + on_change => sub { + my ($opt_key, $value) = @_; + $self->{"_$opt_key"} = $value; + $self->_update_shape; + }, + label_width => 100, + ); + $panel->SetSizerAndFit($optgroup->sizer); + $self->{shape_options_book}->AddPage($panel, $title); +} + +sub GetValue { + my ($self) = @_; + return $self->{bed_shape}; +} + +1; diff --git a/lib/Slic3r/GUI/OptionsGroup.pm b/lib/Slic3r/GUI/OptionsGroup.pm index 31fda69d7..2c9089a28 100644 --- a/lib/Slic3r/GUI/OptionsGroup.pm +++ b/lib/Slic3r/GUI/OptionsGroup.pm @@ -48,6 +48,7 @@ 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 'staticbox' => (is => 'ro', default => sub { 1 }); has 'label_width' => (is => 'ro', default => sub { 180 }); has 'extra_column' => (is => 'ro'); has 'label_font' => (is => 'ro'); @@ -63,9 +64,11 @@ sub _trigger_options {} sub BUILD { my $self = shift; - { + if ($self->staticbox) { my $box = Wx::StaticBox->new($self->parent, -1, $self->title); $self->sizer(Wx::StaticBoxSizer->new($box, wxVERTICAL)); + } else { + $self->sizer(Wx::BoxSizer->new(wxVERTICAL)); } my $num_columns = $self->extra_column ? 3 : 2; @@ -79,9 +82,9 @@ sub BUILD { foreach my $line (@{$self->lines}) { if ($line->{sizer}) { $self->sizer->Add($line->{sizer}, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15); - } elsif ($line->{widget}) { - my $window = $line->{widget}->GetWindow($self->parent); - $self->sizer->Add($window, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15); + } elsif ($line->{widget} && $line->{full_width}) { + my $sizer = $line->{widget}->($self->parent); + $self->sizer->Add($sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15); } else { $self->_build_line($line, $grid_sizer); } @@ -143,7 +146,7 @@ sub _build_line { push @fields, $self->_build_field($opt); push @field_labels, $opt->{label}; } - if (@fields > 1 || $line->{sidetext}) { + if (@fields > 1 || $line->{widget} || $line->{sidetext}) { my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); for my $i (0 .. $#fields) { if (@fields > 1 && $field_labels[$i]) { @@ -153,7 +156,10 @@ sub _build_line { } $sizer->Add($fields[$i], 0, wxALIGN_CENTER_VERTICAL, 0); } - if ($line->{sidetext}) { + if ($line->{widget}) { + my $widget_sizer = $line->{widget}->($self->parent); + $sizer->Add($widget_sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15); + } elsif ($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); @@ -479,26 +485,24 @@ sub _config_methods { } package Slic3r::GUI::OptionsGroup::StaticTextLine; -use Moo; use Wx qw(:misc :systemsettings); +use base 'Wx::StaticText'; -sub GetWindow { - my $self = shift; - my ($parent) = @_; +sub new { + my ($class, $parent) = @_; - $self->{statictext} = Wx::StaticText->new($parent, -1, "foo", wxDefaultPosition, wxDefaultSize); + my $self = $class->SUPER::new($parent, -1, "", wxDefaultPosition, wxDefaultSize); my $font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - $self->{statictext}->SetFont($font); - return $self->{statictext}; + $self->SetFont($font); + return $self; } sub SetText { - my $self = shift; - my ($value) = @_; + my ($self, $value) = @_; - $self->{statictext}->SetLabel($value); - $self->{statictext}->Wrap(400); - $self->{statictext}->GetParent->Layout; + $self->SetLabel($value); + $self->Wrap(400); + $self->GetParent->Layout; } 1; diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 811621d4d..a4e02b15d 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -599,7 +599,11 @@ sub build { Slic3r::GUI::OptionsGroup->single_option_line('cooling'), { label => '', - widget => ($self->{description_line} = Slic3r::GUI::OptionsGroup::StaticTextLine->new), + full_width => 1, + widget => sub { + my ($parent) = @_; + return $self->{description_line} = Slic3r::GUI::OptionsGroup::StaticTextLine->new($parent); + }, }, ], }, @@ -662,6 +666,8 @@ sub on_value_change { package Slic3r::GUI::Tab::Printer; use base 'Slic3r::GUI::Tab'; +use Wx qw(:sizer :button :bitmap :misc :id); +use Wx::Event qw(EVT_BUTTON); sub name { 'printer' } sub title { 'Printer Settings' } @@ -671,10 +677,42 @@ sub build { $self->{extruders_count} = 1; + my $bed_shape_widget = sub { + my ($parent) = @_; + + my $btn = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + $btn->SetFont($Slic3r::GUI::small_font); + if ($Slic3r::GUI::have_button_icons) { + $btn->SetBitmap(Wx::Bitmap->new("$Slic3r::var/cog.png", wxBITMAP_TYPE_PNG)); + } + + my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); + $sizer->Add($btn); + + EVT_BUTTON($self, $btn, sub { + my $dlg = Slic3r::GUI::BedShapeDialog->new($self, $self->{config}->bed_shape); + if ($dlg->ShowModal == wxID_OK) { + my $value = $dlg->GetValue; + $self->{config}->set('bed_shape', $value); + $self->on_value_change('bed_shape', $value); + } + }); + + return $sizer; + }; + $self->add_options_page('General', 'printer_empty.png', optgroups => [ { title => 'Size and coordinates', - options => [qw(bed_size print_center z_offset)], + options => [qw(bed_shape print_center z_offset)], + lines => [ + { + label => 'Bed shape', + widget => $bed_shape_widget, + }, + Slic3r::GUI::OptionsGroup->single_option_line('print_center'), + Slic3r::GUI::OptionsGroup->single_option_line('z_offset'), + ], }, { title => 'Firmware', diff --git a/xs/src/PrintConfig.cpp b/xs/src/PrintConfig.cpp index 725ef502f..88561f6ed 100644 --- a/xs/src/PrintConfig.cpp +++ b/xs/src/PrintConfig.cpp @@ -11,11 +11,8 @@ PrintConfigDef::build_def() { Options["avoid_crossing_perimeters"].tooltip = "Optimize travel moves in order to minimize the crossing of perimeters. This is mostly useful with Bowden extruders which suffer from oozing. This feature slows down both the print and the G-code generation."; Options["avoid_crossing_perimeters"].cli = "avoid-crossing-perimeters!"; - Options["bed_size"].type = coPoint; - Options["bed_size"].label = "Bed size"; - Options["bed_size"].tooltip = "Size of your bed. This is used to adjust the preview in the plater and for auto-arranging parts in it."; - Options["bed_size"].sidetext = "mm"; - Options["bed_size"].cli = "bed-size=s"; + Options["bed_shape"].type = coPoints; + Options["bed_shape"].label = "Bed shape"; Options["bed_temperature"].type = coInt; Options["bed_temperature"].label = "Other layers"; diff --git a/xs/src/PrintConfig.hpp b/xs/src/PrintConfig.hpp index 6f05dd586..470cdd385 100644 --- a/xs/src/PrintConfig.hpp +++ b/xs/src/PrintConfig.hpp @@ -307,7 +307,7 @@ class PrintConfig : public virtual StaticPrintConfig { public: ConfigOptionBool avoid_crossing_perimeters; - ConfigOptionPoint bed_size; + ConfigOptionPoints bed_shape; ConfigOptionInt bed_temperature; ConfigOptionFloat bridge_acceleration; ConfigOptionInt bridge_fan_speed; @@ -378,7 +378,10 @@ class PrintConfig : public virtual StaticPrintConfig PrintConfig() : StaticPrintConfig() { this->avoid_crossing_perimeters.value = false; - this->bed_size.point = Pointf(200,200); + this->bed_shape.values.push_back(Pointf(0,0)); + this->bed_shape.values.push_back(Pointf(200,0)); + this->bed_shape.values.push_back(Pointf(200,200)); + this->bed_shape.values.push_back(Pointf(0,200)); this->bed_temperature.value = 0; this->bridge_acceleration.value = 0; this->bridge_fan_speed.value = 100; @@ -466,7 +469,7 @@ class PrintConfig : public virtual StaticPrintConfig ConfigOption* option(const t_config_option_key opt_key, bool create = false) { if (opt_key == "avoid_crossing_perimeters") return &this->avoid_crossing_perimeters; - if (opt_key == "bed_size") return &this->bed_size; + if (opt_key == "bed_shape") return &this->bed_shape; if (opt_key == "bed_temperature") return &this->bed_temperature; if (opt_key == "bridge_acceleration") return &this->bridge_acceleration; if (opt_key == "bridge_fan_speed") return &this->bridge_fan_speed; diff --git a/xs/t/10_line.t b/xs/t/10_line.t index b6ec3c316..67c3cd353 100644 --- a/xs/t/10_line.t +++ b/xs/t/10_line.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 34; +use Test::More tests => 39; use constant PI => 4 * atan2(1, 1); use constant EPSILON => 1E-4; @@ -40,10 +40,17 @@ isa_ok $line->[0], 'Slic3r::Point::Ref', 'line point is blessed'; ], 'translate'; } +{ + ok +Slic3r::Line->new([0,0],[200,0])->parallel_to_line(Slic3r::Line->new([200,200],[0,200])), 'parallel_to'; +} + foreach my $base_angle (0, PI/4, PI/2, PI) { my $line = Slic3r::Line->new([0,0], [100,0]); $line->rotate($base_angle, [0,0]); - ok $line->parallel_to_line($line->clone), 'line is parallel to self'; + my $clone = $line->clone; + ok $line->parallel_to_line($clone), 'line is parallel to self'; + $clone->reverse; + ok $line->parallel_to_line($clone), 'line is parallel to self + PI'; ok $line->parallel_to($line->direction), 'line is parallel to its direction'; ok $line->parallel_to($line->direction + PI), 'line is parallel to its direction + PI'; ok $line->parallel_to($line->direction - PI), 'line is parallel to its direction - PI';