2016-09-14 11:22:41 +02:00
# The bed shape dialog.
# The dialog opens from Print Settins tab -> Bed Shape: Set...
2014-06-16 01:49:49 +02:00
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) ;
2014-06-16 23:36:31 +02:00
use Wx::Event qw(EVT_CLOSE) ;
2014-06-16 01:49:49 +02:00
use base 'Wx::Dialog' ;
2014-06-16 20:11:52 +02:00
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 );
$self -> { panel } = my $panel = Slic3r::GUI::BedShapePanel -> new ( $self , $default );
my $main_sizer = Wx::BoxSizer -> new ( wxVERTICAL );
$main_sizer -> Add ( $panel , 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 ;
});
return $self ;
}
sub GetValue {
my ( $self ) = @_ ;
return $self -> { panel } -> GetValue ;
}
package Slic3r::GUI::BedShapePanel ;
2014-06-16 23:36:31 +02:00
use List::Util qw(min max sum first) ;
2016-03-18 01:15:52 +01:00
use Scalar::Util qw(looks_like_number) ;
2014-08-03 00:20:55 +02:00
use Slic3r::Geometry qw(PI X Y scale unscale scaled_epsilon deg2rad) ;
use Wx qw(:font :id :misc :sizer :choicebook :filedialog :pen :brush wxTAB_TRAVERSAL) ;
2015-11-02 20:16:37 +01:00
use Wx::Event qw(EVT_CLOSE EVT_CHOICEBOOK_PAGE_CHANGED EVT_BUTTON) ;
2014-06-16 20:11:52 +02:00
use base 'Wx::Panel' ;
2014-06-16 01:49:49 +02:00
use constant SHAPE_RECTANGULAR => 0 ;
use constant SHAPE_CIRCULAR => 1 ;
use constant SHAPE_CUSTOM => 2 ;
sub new {
my $class = shift ;
my ( $parent , $default ) = @_ ;
2014-06-16 20:11:52 +02:00
my $self = $class -> SUPER:: new ( $parent , - 1 );
$self -> on_change ( undef );
2014-06-16 01:49:49 +02:00
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 } = [] ;
2014-07-01 16:40:56 +02:00
{
my $optgroup = $self -> _init_shape_options_page ( 'Rectangular' );
$optgroup -> append_single_option_line ( Slic3r::GUI::OptionsGroup::Option -> new (
opt_id => 'rect_size' ,
2014-06-16 01:49:49 +02:00
type => 'point' ,
label => 'Size' ,
tooltip => 'Size in X and Y of the rectangular plate.' ,
default => [ 200 , 200 ],
2014-07-01 16:40:56 +02:00
));
$optgroup -> append_single_option_line ( Slic3r::GUI::OptionsGroup::Option -> new (
opt_id => 'rect_origin' ,
2014-07-25 00:13:12 +02:00
type => 'point' ,
2014-06-16 01:49:49 +02:00
label => 'Origin' ,
2014-07-25 00:13:12 +02:00
tooltip => 'Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.' ,
default => [ 0 , 0 ],
2014-07-01 16:40:56 +02:00
));
}
{
my $optgroup = $self -> _init_shape_options_page ( 'Circular' );
$optgroup -> append_single_option_line ( Slic3r::GUI::OptionsGroup::Option -> new (
opt_id => 'diameter' ,
2014-06-16 23:36:31 +02:00
type => 'f' ,
label => 'Diameter' ,
tooltip => 'Diameter of the print bed. It is assumed that origin (0,0) is located in the center.' ,
sidetext => 'mm' ,
default => 200 ,
2014-07-01 16:40:56 +02:00
));
}
{
my $optgroup = $self -> _init_shape_options_page ( 'Custom' );
$optgroup -> append_line ( Slic3r::GUI::OptionsGroup::Line -> new (
full_width => 1 ,
widget => sub {
my ( $parent ) = @_ ;
my $btn = Wx::Button -> new ( $parent , - 1 , "Load shape from STL..." , wxDefaultPosition , wxDefaultSize );
EVT_BUTTON ( $self , $btn , sub { $self -> _load_stl });
return $btn ;
}
));
}
2014-06-17 00:25:52 +02:00
2014-06-16 23:36:31 +02:00
EVT_CHOICEBOOK_PAGE_CHANGED ( $self , - 1 , sub {
$self -> _update_shape ;
});
2014-06-16 01:49:49 +02:00
# right pane with preview canvas
2015-11-02 20:16:37 +01:00
my $canvas = $self -> { canvas } = Slic3r::GUI:: 2 DBed -> new ( $self );
2014-06-16 01:49:49 +02:00
# main sizer
my $top_sizer = Wx::BoxSizer -> new ( wxHORIZONTAL );
$top_sizer -> Add ( $sbsizer , 0 , wxEXPAND | wxTOP | wxBOTTOM , 10 );
2014-08-03 00:20:55 +02:00
$top_sizer -> Add ( $canvas , 1 , wxEXPAND | wxALL , 10 ) if $canvas ;
2014-06-16 01:49:49 +02:00
2014-06-16 20:11:52 +02:00
$self -> SetSizerAndFit ( $top_sizer );
2014-06-16 01:49:49 +02:00
$self -> _set_shape ( $default );
$self -> _update_preview ;
return $self ;
}
2014-06-16 20:11:52 +02:00
sub on_change {
my ( $self , $cb ) = @_ ;
$self -> { on_change } = $cb // sub {};
}
2016-06-03 17:21:47 +02:00
# Called from the constructor.
# Set the initial bed shape from a list of points.
# Deduce the bed shape type (rect, circle, custom)
# This routine shall be smart enough if the user messes up
# with the list of points in the ini file directly.
2014-06-16 01:49:49 +02:00
sub _set_shape {
my ( $self , $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
2014-07-25 00:13:12 +02:00
# find origin
# the || 0 hack prevents "-0" which might confuse the user
my $x_min = min ( map $_ -> [ X ], @$points ) || 0 ;
my $x_max = max ( map $_ -> [ X ], @$points ) || 0 ;
my $y_min = min ( map $_ -> [ Y ], @$points ) || 0 ;
my $y_max = max ( map $_ -> [ Y ], @$points ) || 0 ;
my $origin = [ - $x_min , - $y_min ];
$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 );
2014-12-25 02:36:10 +01:00
$self -> _update_shape ;
2014-07-25 00:13:12 +02:00
return ;
2014-06-16 01:49:49 +02:00
}
}
2014-06-16 23:36:31 +02:00
# is this a circle?
{
2016-06-03 17:21:47 +02:00
# Analyze the array of points. Do they reside on a circle?
2014-06-16 23:36:31 +02:00
my $polygon = Slic3r::Polygon -> new_scale ( @$points );
my $center = $polygon -> bounding_box -> center ;
my @vertex_distances = map $center -> distance_to ( $_ ), @$polygon ;
my $avg_dist = sum ( @vertex_distances ) / @vertex_distances ;
2014-12-25 02:36:10 +01:00
if ( ! defined first { abs ( $_ - $avg_dist ) > 10 * scaled_epsilon } @vertex_distances ) {
2014-06-16 23:36:31 +02:00
# all vertices are equidistant to center
$self -> { shape_options_book } -> SetSelection ( SHAPE_CIRCULAR );
my $optgroup = $self -> { optgroups }[ SHAPE_CIRCULAR ];
$optgroup -> set_value ( 'diameter' , sprintf ( "%.0f" , unscale ( $avg_dist * 2 )));
2014-12-25 02:36:10 +01:00
$self -> _update_shape ;
2014-06-16 23:36:31 +02:00
return ;
}
}
2016-06-03 17:21:47 +02:00
if ( @$points < 3 ) {
# Invalid polygon. Revert to default bed dimensions.
$self -> { shape_options_book } -> SetSelection ( SHAPE_RECTANGULAR );
my $optgroup = $self -> { optgroups }[ SHAPE_RECTANGULAR ];
$optgroup -> set_value ( 'rect_size' , [ 200 , 200 ]);
$optgroup -> set_value ( 'rect_origin' , [ 0 , 0 ]);
$self -> _update_shape ;
return ;
}
# This is a custom bed shape, use the polygon provided.
2014-06-16 01:49:49 +02:00
$self -> { shape_options_book } -> SetSelection ( SHAPE_CUSTOM );
2016-06-03 17:21:47 +02:00
# Copy the polygon to the canvas, make a copy of the array.
$self -> { canvas } -> bed_shape ([ @$points ]);
2014-12-25 02:36:10 +01:00
$self -> _update_shape ;
2014-06-16 01:49:49 +02:00
}
2016-06-03 17:21:47 +02:00
# Update the bed shape from the dialog fields.
2014-06-16 01:49:49 +02:00
sub _update_shape {
my ( $self ) = @_ ;
my $page_idx = $self -> { shape_options_book } -> GetSelection ;
if ( $page_idx == SHAPE_RECTANGULAR ) {
2014-12-25 02:36:10 +01:00
my $rect_size = $self -> { optgroups }[ SHAPE_RECTANGULAR ] -> get_value ( 'rect_size' );
my $rect_origin = $self -> { optgroups }[ SHAPE_RECTANGULAR ] -> get_value ( 'rect_origin' );
my ( $x , $y ) = @$rect_size ;
2016-03-18 01:15:52 +01:00
return if ! looks_like_number ( $x ) || ! looks_like_number ( $y ); # empty strings or '-' or other things
2017-02-07 16:46:54 -06:00
return if ! $x || ! $y or $x == 0 or $y == 0 ;
2014-06-16 01:49:49 +02:00
my ( $x0 , $y0 ) = ( 0 , 0 );
2017-02-07 16:46:54 -06:00
my ( $x1 , $y1 ) = ( $x , $y );
2014-07-25 00:13:12 +02:00
{
2014-12-25 02:36:10 +01:00
my ( $dx , $dy ) = @$rect_origin ;
2016-03-18 01:15:52 +01:00
return if ! looks_like_number ( $dx ) || ! looks_like_number ( $dy ); # empty strings or '-' or other things
2014-07-25 00:13:12 +02:00
$x0 -= $dx ;
$x1 -= $dx ;
$y0 -= $dy ;
$y1 -= $dy ;
2014-06-16 01:49:49 +02:00
}
2015-11-02 20:16:37 +01:00
$self -> { canvas } -> bed_shape ([
2014-06-16 01:49:49 +02:00
[ $x0 , $y0 ],
[ $x1 , $y0 ],
[ $x1 , $y1 ],
[ $x0 , $y1 ],
2015-11-02 20:16:37 +01:00
]);
2014-06-16 23:36:31 +02:00
} elsif ( $page_idx == SHAPE_CIRCULAR ) {
2014-12-25 02:36:10 +01:00
my $diameter = $self -> { optgroups }[ SHAPE_CIRCULAR ] -> get_value ( 'diameter' );
2017-02-07 16:46:54 -06:00
return if ! $diameter or $diameter == 0 ;
2014-12-25 02:36:10 +01:00
my $r = $diameter / 2 ;
2014-06-16 23:36:31 +02:00
my $twopi = 2 * PI ;
my $edges = 60 ;
my $polygon = Slic3r::Polygon -> new_scale (
map [ $r * cos $_ , $r * sin $_ ],
map { $twopi / $edges * $_ } 1 .. $edges
);
2015-11-02 20:16:37 +01:00
$self -> { canvas } -> bed_shape ([
2014-06-16 23:36:31 +02:00
map [ unscale ( $_ -> x ), unscale ( $_ -> y ) ], @$polygon #))
2015-11-02 20:16:37 +01:00
]);
2014-06-16 01:49:49 +02:00
}
2014-06-16 20:11:52 +02:00
$self -> { on_change } -> ();
2014-06-16 01:49:49 +02:00
$self -> _update_preview ;
}
sub _update_preview {
my ( $self ) = @_ ;
2014-08-03 00:20:55 +02:00
$self -> { canvas } -> Refresh if $self -> { canvas };
2016-06-03 17:21:47 +02:00
$self -> Refresh ;
2014-08-03 00:20:55 +02:00
}
2016-06-03 17:21:47 +02:00
# Called from the constructor.
# Create a panel for a rectangular / circular / custom bed shape.
2014-06-16 01:49:49 +02:00
sub _init_shape_options_page {
2014-07-01 16:40:56 +02:00
my ( $self , $title ) = @_ ;
2014-06-16 23:36:31 +02:00
2014-06-16 01:49:49 +02:00
my $panel = Wx::Panel -> new ( $self -> { shape_options_book });
2014-07-01 16:40:56 +02:00
my $optgroup ;
push @ { $self -> { optgroups }}, $optgroup = Slic3r::GUI::OptionsGroup -> new (
2014-06-16 01:49:49 +02:00
parent => $panel ,
title => 'Settings' ,
label_width => 100 ,
2014-07-01 16:40:56 +02:00
on_change => sub {
my ( $opt_id ) = @_ ;
2014-12-25 02:36:10 +01:00
#$self->{"_$opt_id"} = $optgroup->get_value($opt_id);
2014-07-01 16:40:56 +02:00
$self -> _update_shape ;
},
2014-06-16 01:49:49 +02:00
);
$panel -> SetSizerAndFit ( $optgroup -> sizer );
$self -> { shape_options_book } -> AddPage ( $panel , $title );
2014-07-01 16:40:56 +02:00
return $optgroup ;
2014-06-16 01:49:49 +02:00
}
2016-06-03 17:21:47 +02:00
# Loads an stl file, projects it to the XY plane and calculates a polygon.
2014-06-17 00:25:52 +02:00
sub _load_stl {
my ( $self ) = @_ ;
2017-02-27 16:09:22 +01:00
my $dialog = Wx::FileDialog -> new ( $self , 'Choose a file to import bed shape from (STL/OBJ/AMF/PRUS):' , "" , "" , & Slic3r::GUI:: MODEL_WILDCARD , wxFD_OPEN | wxFD_FILE_MUST_EXIST );
2014-06-17 00:25:52 +02:00
if ( $dialog -> ShowModal != wxID_OK ) {
$dialog -> Destroy ;
return ;
}
2015-03-09 15:17:50 +01:00
my $input_file = Slic3r:: decode_path ( $dialog -> GetPaths );
2014-06-17 00:25:52 +02:00
$dialog -> Destroy ;
my $model = Slic3r::Model -> read_from_file ( $input_file );
my $mesh = $model -> raw_mesh ;
my $expolygons = $mesh -> horizontal_projection ;
2016-06-03 17:21:47 +02:00
2014-06-17 00:25:52 +02:00
if ( @$expolygons == 0 ) {
Slic3r::GUI:: show_error ( $self , "The selected file contains no geometry." );
return ;
}
if ( @$expolygons > 1 ) {
Slic3r::GUI:: show_error ( $self , "The selected file contains several disjoint areas. This is not supported." );
return ;
}
my $polygon = $expolygons -> [ 0 ] -> contour ;
2016-06-03 17:21:47 +02:00
$self -> { canvas } -> bed_shape ([ map [ unscale ( $_ -> x ), unscale ( $_ -> y ) ], @$polygon ]);
$self -> _update_preview ();
2014-06-17 00:25:52 +02:00
}
2016-06-03 17:21:47 +02:00
# Returns the resulting bed shape polygon. This value will be stored to the ini file.
2014-06-16 01:49:49 +02:00
sub GetValue {
my ( $self ) = @_ ;
2015-11-02 20:16:37 +01:00
return $self -> { canvas } -> bed_shape ;
2014-06-16 01:49:49 +02:00
}
1 ;