2014-06-15 23:49:49 +00: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 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 ) ;
2014-08-02 22:20:55 +00: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 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
2014-08-02 22:20:55 +00:00
my $ canvas = $ self - > { canvas } = Wx::Panel - > new ( $ self , - 1 , wxDefaultPosition , [ 250 , - 1 ] , wxTAB_TRAVERSAL ) ;
EVT_PAINT ( $ canvas , sub {
$ self - > _repaint_canvas ;
} ) ;
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 { } ;
}
2014-06-15 23:49:49 +00: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-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?
{
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 ;
}
}
2014-06-15 23:49:49 +00:00
$ self - > { shape_options_book } - > SetSelection ( SHAPE_CUSTOM ) ;
2014-12-25 01:36:10 +00:00
$ self - > _update_shape ;
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 ;
2014-08-02 22:20:55 +00:00
return if ! $ x || ! $ y ; # empty strings
2014-06-15 23:49:49 +00:00
my ( $ x0 , $ y0 ) = ( 0 , 0 ) ;
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 ;
2014-08-02 22:20:55 +00:00
return if $ dx eq '' || $ dy eq '' ; # empty strings
2014-07-24 22:13:12 +00:00
$ x0 -= $ dx ;
$ x1 -= $ dx ;
$ y0 -= $ dy ;
$ y1 -= $ dy ;
2014-06-15 23:49:49 +00:00
}
$ self - > { bed_shape } = [
[ $ x0 , $ y0 ] ,
[ $ x1 , $ y0 ] ,
[ $ x1 , $ y1 ] ,
[ $ x0 , $ y1 ] ,
] ;
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' ) ;
return if ! $ diameter ;
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
) ;
$ self - > { bed_shape } = [
map [ unscale ( $ _ - > x ) , unscale ( $ _ - > y ) ] , @$ polygon #))
] ;
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 } ;
}
sub _repaint_canvas {
my ( $ self ) = @ _ ;
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
# 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 ) ;
2014-06-15 23:49:49 +00:00
2014-08-02 22:20:55 +00:00
# leave space for origin label
$ cbb - > set_y_max ( $ cbb - > y_max - 10 ) ;
2014-06-15 23:49:49 +00:00
2014-08-02 22:20:55 +00:00
# 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-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
}
2014-06-16 22:25:52 +00: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-15 23:49:49 +00:00
sub GetValue {
my ( $ self ) = @ _ ;
return $ self - > { bed_shape } ;
}
1 ;