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) ;
use Slic3r::Geometry qw(PI X Y unscale scaled_epsilon) ;
2014-06-17 00:25:52 +02:00
use Wx qw(:dialog :id :misc :sizer :choicebook :filedialog wxTAB_TRAVERSAL) ;
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 } = [] ;
$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' ,
},
]);
2014-06-16 23:36:31 +02:00
$self -> _init_shape_options_page ( 'Circular' , [
{
opt_key => 'diameter' ,
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-06-17 00:25:52 +02:00
my $custom = 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 ;
};
$self -> _init_shape_options_page ( 'Custom' , [] , [
{
full_width => 1 ,
widget => $custom ,
},
]);
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
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 ;
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 {};
}
2014-06-16 01:49:49 +02:00
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 ;
}
}
}
2014-06-16 23:36:31 +02:00
# is this a circle?
{
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 ;
if ( ! defined first { abs ( $_ - $avg_dist ) > scaled_epsilon } @vertex_distances ) {
# 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 )));
return ;
}
}
2014-06-16 01:49:49 +02:00
$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 ],
];
2014-06-16 23:36:31 +02:00
} elsif ( $page_idx == SHAPE_CIRCULAR ) {
return if grep ! defined ( $self -> { "_$_" }), qw(diameter) ; # not loaded yet
my $r = $self -> { _diameter } / 2 ;
my $twopi = 2 * PI ;
my $edges = 60 ;
my $polygon = Slic3r::Polygon -> new_scale (
map [ $r * cos $_ , $r * sin $_ ],
map { $twopi / $edges * $_ } 1 .. $edges
);
$self -> { bed_shape } = [
map [ unscale ( $_ -> x ), unscale ( $_ -> y ) ], @$polygon #))
];
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 ) = @_ ;
}
sub _init_shape_options_page {
2014-06-17 00:25:52 +02:00
my ( $self , $title , $options , $lines ) = @_ ;
2014-06-16 01:49:49 +02:00
2014-06-16 23:36:31 +02:00
my $on_change = sub {
my ( $opt_key , $value ) = @_ ;
$self -> { "_$opt_key" } = $value ;
$self -> _update_shape ;
};
2014-06-16 01:49:49 +02:00
my $panel = Wx::Panel -> new ( $self -> { shape_options_book });
push @ { $self -> { optgroups }}, my $optgroup = Slic3r::GUI::OptionsGroup -> new (
parent => $panel ,
title => 'Settings' ,
options => $options ,
2014-06-17 00:25:52 +02:00
$lines ? ( lines => $lines ) : (),
2014-06-16 23:36:31 +02:00
on_change => $on_change ,
2014-06-16 01:49:49 +02:00
label_width => 100 ,
);
2014-06-16 23:36:31 +02:00
$on_change -> ( $_ -> { opt_key }, $_ -> { default }) for @$options ; # populate with defaults
2014-06-16 01:49:49 +02:00
$panel -> SetSizerAndFit ( $optgroup -> sizer );
$self -> { shape_options_book } -> AddPage ( $panel , $title );
}
2014-06-17 00:25:52 +02:00
sub _load_stl {
my ( $self ) = @_ ;
my $dialog = Wx::FileDialog -> new ( $self , 'Choose a file to import bed shape from (STL/OBJ/AMF):' , "" , "" , & Slic3r::GUI:: MODEL_WILDCARD , wxFD_OPEN | wxFD_FILE_MUST_EXIST );
if ( $dialog -> ShowModal != wxID_OK ) {
$dialog -> Destroy ;
return ;
}
my $input_file = $dialog -> GetPaths ;
$dialog -> Destroy ;
my $model = Slic3r::Model -> read_from_file ( $input_file );
my $mesh = $model -> raw_mesh ;
my $expolygons = $mesh -> horizontal_projection ;
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 ;
$self -> { bed_shape } = [ map [ unscale ( $_ -> x ), unscale ( $_ -> y ) ], @$polygon ]; #))
}
2014-06-16 01:49:49 +02:00
sub GetValue {
my ( $self ) = @_ ;
return $self -> { bed_shape };
}
1 ;