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) ;
2014-08-03 00:20:55 +02:00
use Slic3r::Geometry qw(PI X Y scale unscale scaled_epsilon deg2rad) ;
use Slic3r::Geometry::Clipper qw(intersection_pl) ;
use Wx qw(:font :id :misc :sizer :choicebook :filedialog :pen :brush wxTAB_TRAVERSAL) ;
use Wx::Event qw(EVT_CLOSE EVT_CHOICEBOOK_PAGE_CHANGED EVT_BUTTON EVT_PAINT) ;
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
2014-08-03 00:20:55 +02:00
my $canvas = $self -> { canvas } = Wx::Panel -> new ( $self , - 1 , wxDefaultPosition , [ 250 , - 1 ], wxTAB_TRAVERSAL );
EVT_PAINT ( $canvas , sub {
$self -> _repaint_canvas ;
});
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 {};
}
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
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?
{
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 ;
}
}
2014-06-16 01:49:49 +02:00
$self -> { shape_options_book } -> SetSelection ( SHAPE_CUSTOM );
2014-12-25 02:36:10 +01:00
$self -> _update_shape ;
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 ;
2014-08-03 00:20:55 +02:00
return if ! $x || ! $y ; # empty strings
2014-06-16 01:49:49 +02:00
my ( $x0 , $y0 ) = ( 0 , 0 );
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 ;
2014-08-03 00:20:55 +02:00
return if $dx eq '' || $dy eq '' ; # empty strings
2014-07-25 00:13:12 +02:00
$x0 -= $dx ;
$x1 -= $dx ;
$y0 -= $dy ;
$y1 -= $dy ;
2014-06-16 01:49:49 +02:00
}
$self -> { bed_shape } = [
[ $x0 , $y0 ],
[ $x1 , $y0 ],
[ $x1 , $y1 ],
[ $x0 , $y1 ],
];
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' );
return if ! $diameter ;
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
);
$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 ) = @_ ;
2014-08-03 00:20:55 +02:00
$self -> { canvas } -> Refresh if $self -> { canvas };
}
sub _repaint_canvas {
my ( $self ) = @_ ;
2014-06-16 01:49:49 +02:00
2014-08-03 00:20:55 +02:00
my $canvas = $self -> { canvas };
my $dc = Wx::PaintDC -> new ( $canvas );
my ( $cw , $ch ) = $canvas -> GetSizeWH ;
return if $cw == 0 ; # when canvas is not rendered yet, size is 0,0
2014-06-16 01:49:49 +02:00
2014-08-03 00:20:55 +02:00
# turn $cw and $ch from sizes to max coordinates
$cw -- ;
$ch -- ;
my $cbb = Slic3r::Geometry::BoundingBoxf -> new_from_points ([
Slic3r::Pointf -> new ( 0 , 0 ),
Slic3r::Pointf -> new ( $cw , $ch ),
]);
# leave space for origin point
$cbb -> set_x_min ( $cbb -> x_min + 2 );
$cbb -> set_y_max ( $cbb -> y_max - 2 );
# leave space for origin label
$cbb -> set_y_max ( $cbb -> y_max - 10 );
# read new size
( $cw , $ch ) = @ { $cbb -> size };
my $ccenter = $cbb -> center ;
# get bounding box of bed shape in G-code coordinates
my $bed_shape = $self -> { bed_shape };
my $bed_polygon = Slic3r::Polygon -> new_scale ( @$bed_shape );
my $bb = Slic3r::Geometry::BoundingBoxf -> new_from_points ( $bed_shape );
$bb -> merge_point ( Slic3r::Pointf -> new ( 0 , 0 )); # origin needs to be in the visible area
my ( $bw , $bh ) = @ { $bb -> size };
my $bcenter = $bb -> center ;
# calculate the scaling factor for fitting bed shape in canvas area
my $sfactor = min ( $cw /$bw, $ch/ $bh );
my $shift = [
$ccenter -> x - $bcenter -> x * $sfactor ,
$ccenter -> y - $bcenter -> y * $sfactor , #-
];
# prepare function to convert G-code coordinates into pixels
my $to_pixel = sub {
my ( $point ) = @_ ;
my $p = Slic3r::Pointf -> new ( @$point );
$p -> scale ( $sfactor );
$p -> translate ( @$shift );
return [ $p -> x + $cbb -> x_min , $ch - $p -> y + $cbb -> y_min ]; #++
};
# draw bed fill
{
$dc -> SetPen ( Wx::Pen -> new ( Wx::Colour -> new ( 0 , 0 , 0 ), 1 , wxSOLID ));
$dc -> SetBrush ( Wx::Brush -> new ( Wx::Colour -> new ( 255 , 255 , 255 ), wxSOLID ));
$dc -> DrawPolygon ([ map $to_pixel -> ( $_ ), @$bed_shape ], 0 , 0 );
}
# draw grid
{
my $step = 10 ; # 1cm grid
my @polylines = ();
for ( my $x = $bb -> x_min - ( $bb -> x_min % $ step ) + $step ; $x < $bb -> x_max ; $x += $step ) {
push @polylines , Slic3r::Polyline -> new_scale ([ $x , $bb -> y_min ], [ $x , $bb -> y_max ]);
}
for ( my $y = $bb -> y_min - ( $bb -> y_min % $ step ) + $step ; $y < $bb -> y_max ; $y += $step ) {
push @polylines , Slic3r::Polyline -> new_scale ([ $bb -> x_min , $y ], [ $bb -> x_max , $y ]);
}
@polylines = @ { intersection_pl ( \ @polylines , [ $bed_polygon ])};
$dc -> SetPen ( Wx::Pen -> new ( Wx::Colour -> new ( 230 , 230 , 230 ), 1 , wxSOLID ));
$dc -> DrawLine ( map @ { $to_pixel -> ([ map unscale ( $_ ), @$_ ])}, @$_ ) for @polylines ;
}
# draw bed contour
{
$dc -> SetPen ( Wx::Pen -> new ( Wx::Colour -> new ( 0 , 0 , 0 ), 1 , wxSOLID ));
$dc -> SetBrush ( Wx::Brush -> new ( Wx::Colour -> new ( 255 , 255 , 255 ), wxTRANSPARENT ));
$dc -> DrawPolygon ([ map $to_pixel -> ( $_ ), @$bed_shape ], 0 , 0 );
}
my $origin_px = $to_pixel -> ( Slic3r::Pointf -> new ( 0 , 0 ));
# draw axes
{
my $axes_len = 50 ;
my $arrow_len = 6 ;
my $arrow_angle = deg2rad ( 45 );
$dc -> SetPen ( Wx::Pen -> new ( Wx::Colour -> new ( 255 , 0 , 0 ), 2 , wxSOLID )); # red
my $x_end = Slic3r::Pointf -> new ( $origin_px -> [ X ] + $axes_len , $origin_px -> [ Y ]);
$dc -> DrawLine ( @$origin_px , @$x_end );
foreach my $angle ( - $arrow_angle , + $arrow_angle ) {
my $end = $x_end -> clone ;
$end -> translate ( - $arrow_len , 0 );
$end -> rotate ( $angle , $x_end );
$dc -> DrawLine ( @$x_end , @$end );
}
$dc -> SetPen ( Wx::Pen -> new ( Wx::Colour -> new ( 0 , 255 , 0 ), 2 , wxSOLID )); # green
my $y_end = Slic3r::Pointf -> new ( $origin_px -> [ X ], $origin_px -> [ Y ] - $axes_len );
$dc -> DrawLine ( @$origin_px , @$y_end );
foreach my $angle ( - $arrow_angle , + $arrow_angle ) {
my $end = $y_end -> clone ;
$end -> translate ( 0 , + $arrow_len );
$end -> rotate ( $angle , $y_end );
$dc -> DrawLine ( @$y_end , @$end );
}
}
# draw origin
{
$dc -> SetPen ( Wx::Pen -> new ( Wx::Colour -> new ( 0 , 0 , 0 ), 1 , wxSOLID ));
$dc -> SetBrush ( Wx::Brush -> new ( Wx::Colour -> new ( 0 , 0 , 0 ), wxSOLID ));
$dc -> DrawCircle ( @$origin_px , 3 );
$dc -> SetTextForeground ( Wx::Colour -> new ( 0 , 0 , 0 ));
$dc -> SetFont ( Wx::Font -> new ( 10 , wxDEFAULT , wxNORMAL , wxNORMAL ));
$dc -> DrawText ( "(0,0)" , $origin_px -> [ X ] + 1 , $origin_px -> [ Y ] + 2 );
}
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
}
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 ;