2016-09-14 09:22:41 +00:00
# The bed shape dialog.
# The dialog opens from Print Settins tab -> Bed Shape: Set...
2014-06-15 23:49:49 +00:00
package Slic3r::GUI::BedShapeDialog ;
use strict ;
use warnings ;
use utf8 ;
use List::Util qw( min max ) ;
2017-07-19 08:45:39 +00:00
use Slic3r::Geometry qw( X Y unscale ) ;
2014-06-15 23:49:49 +00:00
use Wx qw( :dialog :id :misc :sizer :choicebook wxTAB_TRAVERSAL ) ;
2014-06-16 21:36:31 +00:00
use Wx::Event qw( EVT_CLOSE ) ;
2014-06-15 23:49:49 +00:00
use base 'Wx::Dialog' ;
2014-06-16 18:11:52 +00: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 21:36:31 +00:00
use List::Util qw( min max sum first ) ;
2016-03-18 00:15:52 +00:00
use Scalar::Util qw( looks_like_number ) ;
2017-07-19 08:45:39 +00:00
use Slic3r::Geometry qw( PI X Y unscale scaled_epsilon ) ;
2014-08-02 22:20:55 +00:00
use Wx qw( :font :id :misc :sizer :choicebook :filedialog :pen :brush wxTAB_TRAVERSAL ) ;
2015-11-02 19:16:37 +00:00
use Wx::Event qw( EVT_CLOSE EVT_CHOICEBOOK_PAGE_CHANGED EVT_BUTTON ) ;
2014-06-16 18:11:52 +00:00
use base 'Wx::Panel' ;
2014-06-15 23:49:49 +00: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 18:11:52 +00:00
my $ self = $ class - > SUPER:: new ( $ parent , - 1 ) ;
$ self - > on_change ( undef ) ;
2014-06-15 23:49:49 +00: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 14:40:56 +00: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-15 23:49:49 +00:00
type = > 'point' ,
label = > 'Size' ,
tooltip = > 'Size in X and Y of the rectangular plate.' ,
default = > [ 200 , 200 ] ,
2014-07-01 14:40:56 +00:00
) ) ;
$ optgroup - > append_single_option_line ( Slic3r::GUI::OptionsGroup::Option - > new (
opt_id = > 'rect_origin' ,
2014-07-24 22:13:12 +00:00
type = > 'point' ,
2014-06-15 23:49:49 +00:00
label = > 'Origin' ,
2014-07-24 22:13:12 +00:00
tooltip = > 'Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.' ,
default = > [ 0 , 0 ] ,
2014-07-01 14:40:56 +00: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 21:36:31 +00: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 14:40:56 +00: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-16 22:25:52 +00:00
2014-06-16 21:36:31 +00:00
EVT_CHOICEBOOK_PAGE_CHANGED ( $ self , - 1 , sub {
$ self - > _update_shape ;
} ) ;
2014-06-15 23:49:49 +00:00
# right pane with preview canvas
2015-11-02 19:16:37 +00:00
my $ canvas = $ self - > { canvas } = Slic3r::GUI:: 2 DBed - > new ( $ self ) ;
2014-06-15 23:49:49 +00:00
# main sizer
my $ top_sizer = Wx::BoxSizer - > new ( wxHORIZONTAL ) ;
$ top_sizer - > Add ( $ sbsizer , 0 , wxEXPAND | wxTOP | wxBOTTOM , 10 ) ;
2014-08-02 22:20:55 +00:00
$ top_sizer - > Add ( $ canvas , 1 , wxEXPAND | wxALL , 10 ) if $ canvas ;
2014-06-15 23:49:49 +00:00
2014-06-16 18:11:52 +00:00
$ self - > SetSizerAndFit ( $ top_sizer ) ;
2014-06-15 23:49:49 +00:00
$ self - > _set_shape ( $ default ) ;
$ self - > _update_preview ;
return $ self ;
}
2014-06-16 18:11:52 +00:00
sub on_change {
my ( $ self , $ cb ) = @ _ ;
$ self - > { on_change } = $ cb // sub { } ;
}
2016-06-03 15:21:47 +00: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-15 23:49:49 +00: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-24 22:13:12 +00: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 01:36:10 +00:00
$ self - > _update_shape ;
2014-07-24 22:13:12 +00:00
return ;
2014-06-15 23:49:49 +00:00
}
}
2014-06-16 21:36:31 +00:00
# is this a circle?
{
2016-06-03 15:21:47 +00:00
# Analyze the array of points. Do they reside on a circle?
2014-06-16 21:36:31 +00: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 01:36:10 +00:00
if ( ! defined first { abs ( $ _ - $ avg_dist ) > 10 * scaled_epsilon } @ vertex_distances ) {
2014-06-16 21:36:31 +00: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 01:36:10 +00:00
$ self - > _update_shape ;
2014-06-16 21:36:31 +00:00
return ;
}
}
2016-06-03 15:21:47 +00: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-15 23:49:49 +00:00
$ self - > { shape_options_book } - > SetSelection ( SHAPE_CUSTOM ) ;
2016-06-03 15:21:47 +00:00
# Copy the polygon to the canvas, make a copy of the array.
$ self - > { canvas } - > bed_shape ( [ @$ points ] ) ;
2014-12-25 01:36:10 +00:00
$ self - > _update_shape ;
2014-06-15 23:49:49 +00:00
}
2016-06-03 15:21:47 +00:00
# Update the bed shape from the dialog fields.
2014-06-15 23:49:49 +00:00
sub _update_shape {
my ( $ self ) = @ _ ;
my $ page_idx = $ self - > { shape_options_book } - > GetSelection ;
if ( $ page_idx == SHAPE_RECTANGULAR ) {
2014-12-25 01:36:10 +00: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 00:15:52 +00:00
return if ! looks_like_number ( $ x ) || ! looks_like_number ( $ y ) ; # empty strings or '-' or other things
2017-02-07 22:46:54 +00:00
return if ! $ x || ! $ y or $ x == 0 or $ y == 0 ;
2014-06-15 23:49:49 +00:00
my ( $ x0 , $ y0 ) = ( 0 , 0 ) ;
2017-02-07 22:46:54 +00:00
my ( $ x1 , $ y1 ) = ( $ x , $ y ) ;
2014-07-24 22:13:12 +00:00
{
2014-12-25 01:36:10 +00:00
my ( $ dx , $ dy ) = @$ rect_origin ;
2016-03-18 00:15:52 +00:00
return if ! looks_like_number ( $ dx ) || ! looks_like_number ( $ dy ) ; # empty strings or '-' or other things
2014-07-24 22:13:12 +00:00
$ x0 -= $ dx ;
$ x1 -= $ dx ;
$ y0 -= $ dy ;
$ y1 -= $ dy ;
2014-06-15 23:49:49 +00:00
}
2015-11-02 19:16:37 +00:00
$ self - > { canvas } - > bed_shape ( [
2014-06-15 23:49:49 +00:00
[ $ x0 , $ y0 ] ,
[ $ x1 , $ y0 ] ,
[ $ x1 , $ y1 ] ,
[ $ x0 , $ y1 ] ,
2015-11-02 19:16:37 +00:00
] ) ;
2014-06-16 21:36:31 +00:00
} elsif ( $ page_idx == SHAPE_CIRCULAR ) {
2014-12-25 01:36:10 +00:00
my $ diameter = $ self - > { optgroups } [ SHAPE_CIRCULAR ] - > get_value ( 'diameter' ) ;
2017-02-07 22:46:54 +00:00
return if ! $ diameter or $ diameter == 0 ;
2014-12-25 01:36:10 +00:00
my $ r = $ diameter / 2 ;
2014-06-16 21:36:31 +00: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 19:16:37 +00:00
$ self - > { canvas } - > bed_shape ( [
2014-06-16 21:36:31 +00:00
map [ unscale ( $ _ - > x ) , unscale ( $ _ - > y ) ] , @$ polygon #))
2015-11-02 19:16:37 +00:00
] ) ;
2014-06-15 23:49:49 +00:00
}
2014-06-16 18:11:52 +00:00
$ self - > { on_change } - > ( ) ;
2014-06-15 23:49:49 +00:00
$ self - > _update_preview ;
}
sub _update_preview {
my ( $ self ) = @ _ ;
2014-08-02 22:20:55 +00:00
$ self - > { canvas } - > Refresh if $ self - > { canvas } ;
2016-06-03 15:21:47 +00:00
$ self - > Refresh ;
2014-08-02 22:20:55 +00:00
}
2016-06-03 15:21:47 +00:00
# Called from the constructor.
# Create a panel for a rectangular / circular / custom bed shape.
2014-06-15 23:49:49 +00:00
sub _init_shape_options_page {
2014-07-01 14:40:56 +00:00
my ( $ self , $ title ) = @ _ ;
2014-06-16 21:36:31 +00:00
2014-06-15 23:49:49 +00:00
my $ panel = Wx::Panel - > new ( $ self - > { shape_options_book } ) ;
2014-07-01 14:40:56 +00:00
my $ optgroup ;
push @ { $ self - > { optgroups } } , $ optgroup = Slic3r::GUI::OptionsGroup - > new (
2014-06-15 23:49:49 +00:00
parent = > $ panel ,
title = > 'Settings' ,
label_width = > 100 ,
2014-07-01 14:40:56 +00:00
on_change = > sub {
my ( $ opt_id ) = @ _ ;
2014-12-25 01:36:10 +00:00
#$self->{"_$opt_id"} = $optgroup->get_value($opt_id);
2014-07-01 14:40:56 +00:00
$ self - > _update_shape ;
} ,
2014-06-15 23:49:49 +00:00
) ;
$ panel - > SetSizerAndFit ( $ optgroup - > sizer ) ;
$ self - > { shape_options_book } - > AddPage ( $ panel , $ title ) ;
2014-07-01 14:40:56 +00:00
return $ optgroup ;
2014-06-15 23:49:49 +00:00
}
2016-06-03 15:21:47 +00:00
# Loads an stl file, projects it to the XY plane and calculates a polygon.
2014-06-16 22:25:52 +00:00
sub _load_stl {
my ( $ self ) = @ _ ;
2017-04-05 12:45:43 +00:00
my $ dialog = Wx::FileDialog - > new ( $ self , 'Choose a file to import bed shape from (STL/OBJ/AMF/PRUSA):' , "" , "" , & Slic3r::GUI:: MODEL_WILDCARD , wxFD_OPEN | wxFD_FILE_MUST_EXIST ) ;
2014-06-16 22:25:52 +00:00
if ( $ dialog - > ShowModal != wxID_OK ) {
$ dialog - > Destroy ;
return ;
}
2017-08-03 15:31:31 +00:00
my $ input_file = $ dialog - > GetPaths ;
2014-06-16 22:25:52 +00:00
$ dialog - > Destroy ;
my $ model = Slic3r::Model - > read_from_file ( $ input_file ) ;
2017-06-13 09:35:24 +00:00
my $ mesh = $ model - > mesh ;
2014-06-16 22:25:52 +00:00
my $ expolygons = $ mesh - > horizontal_projection ;
2016-06-03 15:21:47 +00:00
2014-06-16 22:25:52 +00: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 15:21:47 +00:00
$ self - > { canvas } - > bed_shape ( [ map [ unscale ( $ _ - > x ) , unscale ( $ _ - > y ) ] , @$ polygon ] ) ;
$ self - > _update_preview ( ) ;
2014-06-16 22:25:52 +00:00
}
2016-06-03 15:21:47 +00:00
# Returns the resulting bed shape polygon. This value will be stored to the ini file.
2014-06-15 23:49:49 +00:00
sub GetValue {
my ( $ self ) = @ _ ;
2015-11-02 19:16:37 +00:00
return $ self - > { canvas } - > bed_shape ;
2014-06-15 23:49:49 +00:00
}
1 ;