2012-05-04 10:15:33 +02:00
package Slic3r::GUI::Plater ;
2012-04-30 14:56:01 +02:00
use strict ;
use warnings ;
use utf8 ;
use File::Basename qw(basename dirname) ;
2015-05-25 19:51:47 +02:00
use List::Util qw(sum first max) ;
2014-06-14 20:52:21 +02:00
use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale deg2rad) ;
2012-05-19 21:13:10 +02:00
use threads::shared qw(shared_clone) ;
2014-06-14 18:58:56 +02:00
use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc
2015-05-26 02:01:43 +02:00
:panel :sizer :toolbar :window wxTheApp :notebook :combobox) ;
2014-06-13 15:54:13 +02:00
use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED
EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL
2015-05-26 02:01:43 +02:00
EVT_CHOICE EVT_COMBOBOX EVT_TIMER EVT_NOTEBOOK_PAGE_CHANGED) ;
2012-04-30 14:56:01 +02:00
use base 'Wx::Panel' ;
2013-12-12 20:19:33 +01:00
use constant TB_ADD => & Wx:: NewId ;
2013-08-25 12:22:05 +02:00
use constant TB_REMOVE => & Wx:: NewId ;
use constant TB_RESET => & Wx:: NewId ;
use constant TB_ARRANGE => & Wx:: NewId ;
use constant TB_EXPORT_GCODE => & Wx:: NewId ;
use constant TB_EXPORT_STL => & Wx:: NewId ;
2012-08-02 21:11:36 +02:00
use constant TB_MORE => & Wx:: NewId ;
2013-08-25 15:45:22 +02:00
use constant TB_FEWER => & Wx:: NewId ;
2012-08-02 21:11:36 +02:00
use constant TB_45CW => & Wx:: NewId ;
use constant TB_45CCW => & Wx:: NewId ;
use constant TB_SCALE => & Wx:: NewId ;
use constant TB_SPLIT => & Wx:: NewId ;
2014-12-10 17:34:59 +01:00
use constant TB_CUT => & Wx:: NewId ;
2013-08-25 18:01:59 +02:00
use constant TB_SETTINGS => & Wx:: NewId ;
2012-05-04 11:22:56 +02:00
2013-10-16 15:13:39 +02:00
# package variables to avoid passing lexicals to threads
our $THUMBNAIL_DONE_EVENT : shared = Wx:: NewEventType ;
our $PROGRESS_BAR_EVENT : shared = Wx:: NewEventType ;
2014-06-13 14:27:55 +02:00
our $ERROR_EVENT : shared = Wx:: NewEventType ;
2013-10-16 15:13:39 +02:00
our $EXPORT_COMPLETED_EVENT : shared = Wx:: NewEventType ;
2014-06-13 11:19:53 +02:00
our $PROCESS_COMPLETED_EVENT : shared = Wx:: NewEventType ;
2012-04-30 20:59:14 +02:00
2015-05-26 02:01:43 +02:00
use constant FILAMENT_CHOOSERS_SPACING => 0 ;
2014-06-13 23:27:52 +02:00
use constant PROCESS_DELAY => 0.5 * 1000 ; # milliseconds
2012-07-27 21:13:03 +02:00
2014-03-25 15:30:56 +01:00
my $PreventListEvents = 0 ;
2012-04-30 14:56:01 +02:00
sub new {
my $class = shift ;
my ( $parent ) = @_ ;
2012-07-24 14:28:21 +02:00
my $self = $class -> SUPER:: new ( $parent , - 1 , wxDefaultPosition , wxDefaultSize , wxTAB_TRAVERSAL );
2012-07-27 21:13:03 +02:00
$self -> { config } = Slic3r::Config -> new_from_defaults ( qw(
2014-07-24 23:43:19 +02:00
bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width
2015-01-03 23:25:55 +01:00
serial_port serial_speed octoprint_host octoprint_apikey
2012-07-27 21:13:03 +02:00
) );
2013-12-12 20:19:33 +01:00
$self -> { model } = Slic3r::Model -> new ;
2013-12-13 14:02:01 +01:00
$self -> { print } = Slic3r::Print -> new ;
2012-09-12 16:30:44 +02:00
$self -> { objects } = [] ;
2012-04-30 14:56:01 +02:00
2014-06-13 14:27:55 +02:00
$self -> { print } -> set_status_cb ( sub {
my ( $percent , $message ) = @_ ;
if ( $ Slic3r:: have_threads ) {
Wx:: PostEvent ( $self , Wx::PlThreadEvent -> new ( - 1 , $PROGRESS_BAR_EVENT , shared_clone ([ $percent , $message ])));
} else {
$self -> on_progress_event ( $percent , $message );
}
});
2014-07-13 12:10:34 +02:00
# Initialize preview notebook
$self -> { preview_notebook } = Wx::Notebook -> new ( $self , - 1 , wxDefaultPosition , [ 335 , 335 ], wxNB_BOTTOM );
2014-12-13 20:41:03 +01:00
# Initialize handlers for canvases
my $on_select_object = sub {
2014-05-28 12:29:43 +02:00
my ( $obj_idx ) = @_ ;
$self -> select_object ( $obj_idx );
2014-12-13 20:41:03 +01:00
};
my $on_double_click = sub {
2014-11-30 20:19:04 +01:00
$self -> object_settings_dialog if $self -> selected_object ;
2014-12-13 20:41:03 +01:00
};
my $on_right_click = sub {
my ( $canvas , $click_pos ) = @_ ;
2014-06-14 21:36:28 +02:00
my ( $obj_idx , $object ) = $self -> selected_object ;
return if ! defined $obj_idx ;
my $menu = $self -> object_menu ;
2014-12-13 20:41:03 +01:00
$canvas -> PopupMenu ( $menu , $click_pos );
2014-06-14 21:36:28 +02:00
$menu -> Destroy ;
2014-12-13 20:41:03 +01:00
};
2015-01-13 18:41:23 +01:00
my $on_instances_moved = sub {
2014-05-28 12:29:43 +02:00
$self -> update ;
2014-12-13 20:41:03 +01:00
};
2015-01-18 19:36:47 +01:00
# Initialize 3D plater
2015-01-17 10:53:01 +01:00
if ( $ Slic3r::GUI:: have_OpenGL ) {
$self -> { canvas3D } = Slic3r::GUI::Plater:: 3 D -> new ( $self -> { preview_notebook }, $self -> { objects }, $self -> { model }, $self -> { config });
$self -> { preview_notebook } -> AddPage ( $self -> { canvas3D }, '3D' );
$self -> { canvas3D } -> set_on_select_object ( $on_select_object );
$self -> { canvas3D } -> set_on_double_click ( $on_double_click );
$self -> { canvas3D } -> set_on_right_click ( sub { $on_right_click -> ( $self -> { canvas3D }, @_ ); });
$self -> { canvas3D } -> set_on_instances_moved ( $on_instances_moved );
2015-02-16 00:37:36 +01:00
$self -> { canvas3D } -> on_viewport_changed ( sub {
$self -> { preview3D } -> canvas -> set_viewport_from_scene ( $self -> { canvas3D });
});
2015-01-17 10:53:01 +01:00
}
2014-12-13 20:41:03 +01:00
# Initialize 2D preview canvas
$self -> { canvas } = Slic3r::GUI::Plater:: 2 D -> new ( $self -> { preview_notebook }, wxDefaultSize , $self -> { objects }, $self -> { model }, $self -> { config });
$self -> { preview_notebook } -> AddPage ( $self -> { canvas }, '2D' );
$self -> { canvas } -> on_select_object ( $on_select_object );
$self -> { canvas } -> on_double_click ( $on_double_click );
$self -> { canvas } -> on_right_click ( sub { $on_right_click -> ( $self -> { canvas }, @_ ); });
2015-01-13 18:41:23 +01:00
$self -> { canvas } -> on_instances_moved ( $on_instances_moved );
2012-04-30 14:56:01 +02:00
2015-01-18 20:55:44 +01:00
# Initialize 3D toolpaths preview
2014-07-13 12:10:34 +02:00
if ( $ Slic3r::GUI:: have_OpenGL ) {
2015-01-18 20:55:44 +01:00
$self -> { preview3D } = Slic3r::GUI::Plater:: 3 DPreview -> new ( $self -> { preview_notebook }, $self -> { print });
2015-02-16 00:37:36 +01:00
$self -> { preview3D } -> canvas -> on_viewport_changed ( sub {
$self -> { canvas3D } -> set_viewport_from_scene ( $self -> { preview3D } -> canvas );
});
2015-01-18 20:55:44 +01:00
$self -> { preview_notebook } -> AddPage ( $self -> { preview3D }, 'Preview' );
$self -> { preview3D_page_idx } = $self -> { preview_notebook } -> GetPageCount - 1 ;
2014-07-13 12:10:34 +02:00
}
2015-01-17 10:53:01 +01:00
# Initialize toolpaths preview
2014-07-13 12:10:34 +02:00
if ( $ Slic3r::GUI:: have_OpenGL ) {
2014-11-30 20:18:09 +01:00
$self -> { toolpaths2D } = Slic3r::GUI::Plater:: 2 DToolpaths -> new ( $self -> { preview_notebook }, $self -> { print });
2015-01-18 20:55:44 +01:00
$self -> { preview_notebook } -> AddPage ( $self -> { toolpaths2D }, 'Layers' );
2015-01-18 19:36:47 +01:00
}
EVT_NOTEBOOK_PAGE_CHANGED ( $self , $self -> { preview_notebook }, sub {
if ( $self -> { preview_notebook } -> GetSelection == $self -> { preview3D_page_idx }) {
2015-01-25 10:59:39 +01:00
$self -> { preview3D } -> load_print ;
2015-01-18 19:36:47 +01:00
}
});
2012-05-04 11:22:56 +02:00
# toolbar for object manipulation
2012-05-20 16:24:10 +02:00
if ( !& Wx:: wxMSW ) {
2012-05-04 12:56:15 +02:00
Wx::ToolTip:: Enable ( 1 );
2012-07-24 14:28:21 +02:00
$self -> { htoolbar } = Wx::ToolBar -> new ( $self , - 1 , wxDefaultPosition , wxDefaultSize , wxTB_HORIZONTAL | wxTB_TEXT | wxBORDER_SIMPLE | wxTAB_TRAVERSAL );
2013-12-12 20:19:33 +01:00
$self -> { htoolbar } -> AddTool ( TB_ADD , "Add…" , Wx::Bitmap -> new ( "$Slic3r::var/brick_add.png" , wxBITMAP_TYPE_PNG ), '' );
2013-08-25 12:22:05 +02:00
$self -> { htoolbar } -> AddTool ( TB_REMOVE , "Delete" , Wx::Bitmap -> new ( "$Slic3r::var/brick_delete.png" , wxBITMAP_TYPE_PNG ), '' );
$self -> { htoolbar } -> AddTool ( TB_RESET , "Delete All" , Wx::Bitmap -> new ( "$Slic3r::var/cross.png" , wxBITMAP_TYPE_PNG ), '' );
2013-08-25 16:10:53 +02:00
$self -> { htoolbar } -> AddTool ( TB_ARRANGE , "Arrange" , Wx::Bitmap -> new ( "$Slic3r::var/bricks.png" , wxBITMAP_TYPE_PNG ), '' );
2013-08-25 12:22:05 +02:00
$self -> { htoolbar } -> AddSeparator ;
2012-07-01 19:24:06 +02:00
$self -> { htoolbar } -> AddTool ( TB_MORE , "More" , Wx::Bitmap -> new ( "$Slic3r::var/add.png" , wxBITMAP_TYPE_PNG ), '' );
2013-08-25 15:45:22 +02:00
$self -> { htoolbar } -> AddTool ( TB_FEWER , "Fewer" , Wx::Bitmap -> new ( "$Slic3r::var/delete.png" , wxBITMAP_TYPE_PNG ), '' );
2012-05-04 11:22:56 +02:00
$self -> { htoolbar } -> AddSeparator ;
2012-07-01 19:24:06 +02:00
$self -> { htoolbar } -> AddTool ( TB_45CCW , "45° ccw" , Wx::Bitmap -> new ( "$Slic3r::var/arrow_rotate_anticlockwise.png" , wxBITMAP_TYPE_PNG ), '' );
$self -> { htoolbar } -> AddTool ( TB_45CW , "45° cw" , Wx::Bitmap -> new ( "$Slic3r::var/arrow_rotate_clockwise.png" , wxBITMAP_TYPE_PNG ), '' );
2012-07-01 23:29:56 +02:00
$self -> { htoolbar } -> AddTool ( TB_SCALE , "Scale…" , Wx::Bitmap -> new ( "$Slic3r::var/arrow_out.png" , wxBITMAP_TYPE_PNG ), '' );
2012-07-01 19:24:06 +02:00
$self -> { htoolbar } -> AddTool ( TB_SPLIT , "Split" , Wx::Bitmap -> new ( "$Slic3r::var/shape_ungroup.png" , wxBITMAP_TYPE_PNG ), '' );
2014-12-10 17:34:59 +01:00
$self -> { htoolbar } -> AddTool ( TB_CUT , "Cut…" , Wx::Bitmap -> new ( "$Slic3r::var/package.png" , wxBITMAP_TYPE_PNG ), '' );
2013-08-25 18:01:59 +02:00
$self -> { htoolbar } -> AddSeparator ;
$self -> { htoolbar } -> AddTool ( TB_SETTINGS , "Settings…" , Wx::Bitmap -> new ( "$Slic3r::var/cog.png" , wxBITMAP_TYPE_PNG ), '' );
2012-05-20 16:24:10 +02:00
} else {
2013-08-25 16:10:53 +02:00
my %tbar_buttons = (
2014-03-25 15:07:21 +01:00
add => "Add…" ,
2013-08-25 16:10:53 +02:00
remove => "Delete" ,
reset => "Delete All" ,
arrange => "Arrange" ,
increase => "" ,
decrease => "" ,
rotate45ccw => "" ,
rotate45cw => "" ,
changescale => "Scale…" ,
split => "Split" ,
2014-12-10 17:34:59 +01:00
cut => "Cut…" ,
2013-08-25 18:01:59 +02:00
settings => "Settings…" ,
2013-08-25 16:10:53 +02:00
);
2012-05-20 16:24:10 +02:00
$self -> { btoolbar } = Wx::BoxSizer -> new ( wxHORIZONTAL );
2014-12-10 17:34:59 +01:00
for ( qw(add remove reset arrange increase decrease rotate45ccw rotate45cw changescale split cut settings) ) {
2012-07-03 01:20:30 +02:00
$self -> { "btn_$_" } = Wx::Button -> new ( $self , - 1 , $tbar_buttons { $_ }, wxDefaultPosition , wxDefaultSize , wxBU_EXACTFIT );
2012-05-20 16:24:10 +02:00
$self -> { btoolbar } -> Add ( $self -> { "btn_$_" });
}
2012-05-04 11:22:56 +02:00
}
2012-07-24 14:42:38 +02:00
2014-12-25 18:50:02 +01:00
$self -> { list } = Wx::ListView -> new ( $self , - 1 , wxDefaultPosition , wxDefaultSize ,
wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS );
2013-08-25 12:22:05 +02:00
$self -> { list } -> InsertColumn ( 0 , "Name" , wxLIST_FORMAT_LEFT , 145 );
$self -> { list } -> InsertColumn ( 1 , "Copies" , wxLIST_FORMAT_CENTER , 45 );
2012-07-24 14:42:38 +02:00
$self -> { list } -> InsertColumn ( 2 , "Scale" , wxLIST_FORMAT_CENTER , wxLIST_AUTOSIZE_USEHEADER );
EVT_LIST_ITEM_SELECTED ( $self , $self -> { list }, \& list_item_selected );
EVT_LIST_ITEM_DESELECTED ( $self , $self -> { list }, \& list_item_deselected );
2012-10-24 22:44:08 +02:00
EVT_LIST_ITEM_ACTIVATED ( $self , $self -> { list }, \& list_item_activated );
2012-07-24 14:42:38 +02:00
EVT_KEY_DOWN ( $self -> { list }, sub {
my ( $list , $event ) = @_ ;
if ( $event -> GetKeyCode == WXK_TAB ) {
$list -> Navigate ( $event -> ShiftDown ? & Wx:: wxNavigateBackward : & Wx:: wxNavigateForward );
} else {
$event -> Skip ;
}
});
2012-05-04 11:22:56 +02:00
2013-08-25 12:22:05 +02:00
# right pane buttons
2014-07-04 10:32:32 +02:00
$self -> { btn_export_gcode } = Wx::Button -> new ( $self , - 1 , "Export G-code…" , wxDefaultPosition , [ - 1 , 30 ], wxBU_LEFT );
2015-01-03 23:25:55 +01:00
$self -> { btn_print } = Wx::Button -> new ( $self , - 1 , "Print…" , wxDefaultPosition , [ - 1 , 30 ], wxBU_LEFT );
2014-12-28 01:30:05 +01:00
$self -> { btn_send_gcode } = Wx::Button -> new ( $self , - 1 , "Send to printer" , wxDefaultPosition , [ - 1 , 30 ], wxBU_LEFT );
2014-07-04 10:32:32 +02:00
$self -> { btn_export_stl } = Wx::Button -> new ( $self , - 1 , "Export STL…" , wxDefaultPosition , [ - 1 , 30 ], wxBU_LEFT );
2014-12-25 18:50:02 +01:00
#$self->{btn_export_gcode}->SetFont($Slic3r::GUI::small_font);
#$self->{btn_export_stl}->SetFont($Slic3r::GUI::small_font);
2015-01-03 23:25:55 +01:00
$self -> { btn_print } -> Hide ;
2014-12-28 01:30:05 +01:00
$self -> { btn_send_gcode } -> Hide ;
2012-05-04 11:22:56 +02:00
2013-08-25 18:01:59 +02:00
if ( $ Slic3r::GUI:: have_button_icons ) {
2012-05-04 11:22:56 +02:00
my %icons = qw(
2014-03-25 15:07:21 +01:00
add brick_add.png
2012-05-04 11:22:56 +02:00
remove brick_delete.png
reset cross.png
arrange bricks.png
2012-05-19 15:02:23 +02:00
export_gcode cog_go.png
2015-01-03 23:25:55 +01:00
print arrow_up.png
2014-12-29 11:45:09 +01:00
send_gcode arrow_up.png
2012-05-04 11:22:56 +02:00
export_stl brick_go.png
increase add.png
decrease delete.png
rotate45cw arrow_rotate_clockwise.png
rotate45ccw arrow_rotate_anticlockwise.png
changescale arrow_out.png
split shape_ungroup.png
2014-12-10 17:34:59 +01:00
cut package.png
2013-08-25 18:08:56 +02:00
settings cog.png
2012-05-04 11:22:56 +02:00
) ;
2012-05-20 16:24:10 +02:00
for ( grep $self -> { "btn_$_" }, keys %icons ) {
2012-07-01 19:24:06 +02:00
$self -> { "btn_$_" } -> SetBitmap ( Wx::Bitmap -> new ( "$Slic3r::var/$icons{$_}" , wxBITMAP_TYPE_PNG ));
2012-05-20 16:24:10 +02:00
}
2012-05-01 16:49:34 +02:00
}
2012-04-30 14:56:01 +02:00
$self -> selection_changed ( 0 );
$self -> object_list_changed ;
2014-06-13 19:23:51 +02:00
EVT_BUTTON ( $self , $self -> { btn_export_gcode }, sub {
$self -> export_gcode ;
});
2015-01-03 23:25:55 +01:00
EVT_BUTTON ( $self , $self -> { btn_print }, sub {
$self -> { print_file } = $self -> export_gcode ( Wx::StandardPaths::Get -> GetTempDir ());
});
2014-12-28 01:30:05 +01:00
EVT_BUTTON ( $self , $self -> { btn_send_gcode }, sub {
$self -> { send_gcode_file } = $self -> export_gcode ( Wx::StandardPaths::Get -> GetTempDir ());
});
2014-12-28 14:58:24 +01:00
EVT_BUTTON ( $self , $self -> { btn_export_stl }, \& export_stl );
2012-04-30 14:56:01 +02:00
2012-05-04 11:22:56 +02:00
if ( $self -> { htoolbar }) {
2014-06-17 16:27:38 +02:00
EVT_TOOL ( $self , TB_ADD , sub { $self -> add ; });
2013-08-25 12:22:05 +02:00
EVT_TOOL ( $self , TB_REMOVE , sub { $self -> remove () }); # explicitly pass no argument to remove
2014-06-17 16:27:38 +02:00
EVT_TOOL ( $self , TB_RESET , sub { $self -> reset ; });
EVT_TOOL ( $self , TB_ARRANGE , sub { $self -> arrange ; });
EVT_TOOL ( $self , TB_MORE , sub { $self -> increase ; });
EVT_TOOL ( $self , TB_FEWER , sub { $self -> decrease ; });
2012-05-04 11:22:56 +02:00
EVT_TOOL ( $self , TB_45CW , sub { $_ [ 0 ] -> rotate ( - 45 ) });
EVT_TOOL ( $self , TB_45CCW , sub { $_ [ 0 ] -> rotate ( 45 ) });
2014-06-17 16:27:38 +02:00
EVT_TOOL ( $self , TB_SCALE , sub { $self -> changescale ( undef ); });
EVT_TOOL ( $self , TB_SPLIT , sub { $self -> split_object ; });
2014-12-10 17:34:59 +01:00
EVT_TOOL ( $self , TB_CUT , sub { $_ [ 0 ] -> object_cut_dialog });
2013-08-25 18:01:59 +02:00
EVT_TOOL ( $self , TB_SETTINGS , sub { $_ [ 0 ] -> object_settings_dialog });
2012-05-04 11:22:56 +02:00
} else {
2014-06-17 16:27:38 +02:00
EVT_BUTTON ( $self , $self -> { btn_add }, sub { $self -> add ; });
2013-08-25 16:10:53 +02:00
EVT_BUTTON ( $self , $self -> { btn_remove }, sub { $self -> remove () }); # explicitly pass no argument to remove
2014-06-17 16:27:38 +02:00
EVT_BUTTON ( $self , $self -> { btn_reset }, sub { $self -> reset ; });
EVT_BUTTON ( $self , $self -> { btn_arrange }, sub { $self -> arrange ; });
EVT_BUTTON ( $self , $self -> { btn_increase }, sub { $self -> increase ; });
EVT_BUTTON ( $self , $self -> { btn_decrease }, sub { $self -> decrease ; });
2012-05-04 11:22:56 +02:00
EVT_BUTTON ( $self , $self -> { btn_rotate45cw }, sub { $_ [ 0 ] -> rotate ( - 45 ) });
EVT_BUTTON ( $self , $self -> { btn_rotate45ccw }, sub { $_ [ 0 ] -> rotate ( 45 ) });
2014-06-17 16:27:38 +02:00
EVT_BUTTON ( $self , $self -> { btn_changescale }, sub { $self -> changescale ( undef ); });
EVT_BUTTON ( $self , $self -> { btn_split }, sub { $self -> split_object ; });
2014-12-10 17:34:59 +01:00
EVT_BUTTON ( $self , $self -> { btn_cut }, sub { $_ [ 0 ] -> object_cut_dialog });
2013-08-25 18:01:59 +02:00
EVT_BUTTON ( $self , $self -> { btn_settings }, sub { $_ [ 0 ] -> object_settings_dialog });
2012-05-04 11:22:56 +02:00
}
2012-05-04 10:15:33 +02:00
$_ -> SetDropTarget ( Slic3r::GUI::Plater::DropTarget -> new ( $self ))
2015-01-18 19:36:47 +01:00
for grep defined ( $_ ),
$self , $self -> { canvas }, $self -> { canvas3D }, $self -> { preview3D }, $self -> { list };
2012-04-30 14:56:01 +02:00
2012-04-30 20:59:14 +02:00
EVT_COMMAND ( $self , - 1 , $THUMBNAIL_DONE_EVENT , sub {
my ( $self , $event ) = @_ ;
2013-11-12 14:30:13 +01:00
my ( $obj_idx ) = @ { $event -> GetData };
2013-06-07 13:54:40 +02:00
return if ! $self -> { objects }[ $obj_idx ]; # object was deleted before thumbnail generation completed
2013-07-14 00:38:01 +02:00
2012-10-01 18:12:14 +02:00
$self -> on_thumbnail_made ( $obj_idx );
2012-04-30 20:59:14 +02:00
});
2012-05-05 21:08:15 +02:00
EVT_COMMAND ( $self , - 1 , $PROGRESS_BAR_EVENT , sub {
my ( $self , $event ) = @_ ;
my ( $percent , $message ) = @ { $event -> GetData };
2014-06-13 14:27:55 +02:00
$self -> on_progress_event ( $percent , $message );
2012-05-05 21:08:15 +02:00
});
2014-06-13 14:27:55 +02:00
EVT_COMMAND ( $self , - 1 , $ERROR_EVENT , sub {
2012-05-05 21:08:15 +02:00
my ( $self , $event ) = @_ ;
2014-06-13 14:27:55 +02:00
Slic3r::GUI:: show_error ( $self , @ { $event -> GetData });
2012-05-05 21:08:15 +02:00
});
EVT_COMMAND ( $self , - 1 , $EXPORT_COMPLETED_EVENT , sub {
my ( $self , $event ) = @_ ;
2014-06-13 14:27:55 +02:00
$self -> on_export_completed ( $event -> GetData );
2012-05-19 21:13:10 +02:00
});
2014-06-13 11:19:53 +02:00
EVT_COMMAND ( $self , - 1 , $PROCESS_COMPLETED_EVENT , sub {
my ( $self , $event ) = @_ ;
2014-06-13 14:27:55 +02:00
$self -> on_process_completed ( $event -> GetData );
2014-06-13 11:19:53 +02:00
});
2015-01-18 21:31:09 +01:00
if ( $ Slic3r:: have_threads ) {
my $timer_id = Wx:: NewId ();
$self -> { apply_config_timer } = Wx::Timer -> new ( $self , $timer_id );
EVT_TIMER ( $self , $timer_id , sub {
my ( $self , $event ) = @_ ;
$self -> async_apply_config ;
});
}
2014-06-13 15:54:13 +02:00
2014-05-28 12:29:43 +02:00
$self -> { canvas } -> update_bed_size ;
2014-12-16 01:12:37 +01:00
if ( $self -> { canvas3D }) {
$self -> { canvas3D } -> update_bed_size ;
$self -> { canvas3D } -> zoom_to_bed ;
}
2015-01-18 19:36:47 +01:00
if ( $self -> { preview3D }) {
$self -> { preview3D } -> set_bed_shape ( $self -> { config } -> bed_shape );
}
2013-12-18 18:54:11 +01:00
$self -> update ;
2012-04-30 14:56:01 +02:00
{
2013-08-25 12:22:05 +02:00
my $presets ;
2014-06-14 19:54:18 +02:00
if ( $self -> GetFrame -> { mode } eq 'expert' ) {
2014-12-25 19:14:18 +01:00
$presets = $self -> { presets_sizer } = Wx::FlexGridSizer -> new ( 3 , 2 , 1 , 2 );
2014-12-25 18:50:02 +01:00
$presets -> AddGrowableCol ( 1 , 1 );
$presets -> SetFlexibleDirection ( wxHORIZONTAL );
2013-01-03 15:49:20 +01:00
my %group_labels = (
print => 'Print settings' ,
filament => 'Filament' ,
printer => 'Printer' ,
);
$self -> { preset_choosers } = {};
for my $group ( qw(print filament printer) ) {
my $text = Wx::StaticText -> new ( $self , - 1 , "$group_labels{$group}:" , wxDefaultPosition , wxDefaultSize , wxALIGN_RIGHT );
2013-08-25 12:22:05 +02:00
$text -> SetFont ( $ Slic3r::GUI:: small_font );
2015-05-26 02:01:43 +02:00
my $choice = Wx::BitmapComboBox -> new ( $self , - 1 , "" , wxDefaultPosition , wxDefaultSize , [] , wxCB_READONLY );
2013-01-03 15:49:20 +01:00
$self -> { preset_choosers }{ $group } = [ $choice ];
2015-06-02 11:19:11 +02:00
# setup the listener
EVT_COMBOBOX ( $choice , $choice , sub {
my ( $choice ) = @_ ;
wxTheApp -> CallAfter ( sub {
$self -> _on_select_preset ( $group , $choice );
});
});
2014-12-25 19:42:24 +01:00
$presets -> Add ( $text , 0 , wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT , 4 );
2015-05-26 02:01:43 +02:00
$presets -> Add ( $choice , 1 , wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM , 0 );
2013-01-03 15:49:20 +01:00
}
}
2012-06-19 17:23:10 +02:00
2013-08-25 12:22:05 +02:00
my $object_info_sizer ;
{
my $box = Wx::StaticBox -> new ( $self , - 1 , "Info" );
$object_info_sizer = Wx::StaticBoxSizer -> new ( $box , wxVERTICAL );
2014-06-16 15:18:39 +02:00
$object_info_sizer -> SetMinSize ([ 350 , - 1 ]);
2013-08-25 17:26:55 +02:00
my $grid_sizer = Wx::FlexGridSizer -> new ( 3 , 4 , 5 , 5 );
$grid_sizer -> SetFlexibleDirection ( wxHORIZONTAL );
$grid_sizer -> AddGrowableCol ( 1 , 1 );
$grid_sizer -> AddGrowableCol ( 3 , 1 );
$object_info_sizer -> Add ( $grid_sizer , 0 , wxEXPAND );
2013-08-25 12:22:05 +02:00
my @info = (
2013-08-25 17:26:55 +02:00
size => "Size" ,
volume => "Volume" ,
facets => "Facets" ,
materials => "Materials" ,
manifold => "Manifold" ,
2013-08-25 12:22:05 +02:00
);
while ( my $field = shift @info ) {
my $label = shift @info ;
2014-07-03 17:12:47 +02:00
my $text = Wx::StaticText -> new ( $self , - 1 , "$label:" , wxDefaultPosition , wxDefaultSize , wxALIGN_LEFT );
2013-08-25 12:22:05 +02:00
$text -> SetFont ( $ Slic3r::GUI:: small_font );
$grid_sizer -> Add ( $text , 0 );
2014-07-03 17:12:47 +02:00
$self -> { "object_info_$field" } = Wx::StaticText -> new ( $self , - 1 , "" , wxDefaultPosition , wxDefaultSize , wxALIGN_LEFT );
2013-08-25 12:22:05 +02:00
$self -> { "object_info_$field" } -> SetFont ( $ Slic3r::GUI:: small_font );
2013-08-25 17:26:55 +02:00
if ( $field eq 'manifold' ) {
2014-07-03 17:12:47 +02:00
$self -> { object_info_manifold_warning_icon } = Wx::StaticBitmap -> new ( $self , - 1 , Wx::Bitmap -> new ( "$Slic3r::var/error.png" , wxBITMAP_TYPE_PNG ));
2013-08-25 17:26:55 +02:00
$self -> { object_info_manifold_warning_icon } -> Hide ;
my $h_sizer = Wx::BoxSizer -> new ( wxHORIZONTAL );
$h_sizer -> Add ( $self -> { object_info_manifold_warning_icon }, 0 );
$h_sizer -> Add ( $self -> { "object_info_$field" }, 0 );
$grid_sizer -> Add ( $h_sizer , 0 , wxEXPAND );
} else {
$grid_sizer -> Add ( $self -> { "object_info_$field" }, 0 );
}
2013-08-25 12:22:05 +02:00
}
}
2014-12-25 18:50:02 +01:00
my $buttons_sizer = Wx::BoxSizer -> new ( wxHORIZONTAL );
$buttons_sizer -> AddStretchSpacer ( 1 );
$buttons_sizer -> Add ( $self -> { btn_export_stl }, 0 , wxALIGN_RIGHT , 0 );
2015-01-03 23:25:55 +01:00
$buttons_sizer -> Add ( $self -> { btn_print }, 0 , wxALIGN_RIGHT , 0 );
2014-12-28 01:30:05 +01:00
$buttons_sizer -> Add ( $self -> { btn_send_gcode }, 0 , wxALIGN_RIGHT , 0 );
2014-12-25 18:50:02 +01:00
$buttons_sizer -> Add ( $self -> { btn_export_gcode }, 0 , wxALIGN_RIGHT , 0 );
2013-08-25 12:22:05 +02:00
my $right_sizer = Wx::BoxSizer -> new ( wxVERTICAL );
2014-12-25 18:50:02 +01:00
$right_sizer -> Add ( $presets , 0 , wxEXPAND | wxTOP , 10 ) if defined $presets ;
$right_sizer -> Add ( $buttons_sizer , 0 , wxEXPAND | wxBOTTOM , 5 );
$right_sizer -> Add ( $self -> { list }, 1 , wxEXPAND , 5 );
$right_sizer -> Add ( $object_info_sizer , 0 , wxEXPAND , 0 );
2013-08-25 12:22:05 +02:00
my $hsizer = Wx::BoxSizer -> new ( wxHORIZONTAL );
2014-07-13 12:10:34 +02:00
$hsizer -> Add ( $self -> { preview_notebook }, 1 , wxEXPAND | wxTOP , 1 );
2014-12-25 18:50:02 +01:00
$hsizer -> Add ( $right_sizer , 0 , wxEXPAND | wxLEFT | wxRIGHT , 3 );
2013-08-25 12:22:05 +02:00
my $sizer = Wx::BoxSizer -> new ( wxVERTICAL );
$sizer -> Add ( $self -> { htoolbar }, 0 , wxEXPAND , 0 ) if $self -> { htoolbar };
$sizer -> Add ( $self -> { btoolbar }, 0 , wxEXPAND , 0 ) if $self -> { btoolbar };
$sizer -> Add ( $hsizer , 1 , wxEXPAND , 0 );
2012-04-30 14:56:01 +02:00
$sizer -> SetSizeHints ( $self );
$self -> SetSizer ( $sizer );
}
2014-07-01 16:40:56 +02:00
2012-04-30 14:56:01 +02:00
return $self ;
}
2014-07-01 16:40:56 +02:00
# sets the callback
2012-10-24 20:24:11 +02:00
sub on_select_preset {
2014-07-01 16:40:56 +02:00
my ( $self , $cb ) = @_ ;
$self -> { on_select_preset } = $cb ;
}
sub _on_select_preset {
2012-10-24 20:24:11 +02:00
my $self = shift ;
my ( $group , $choice ) = @_ ;
2014-07-01 16:40:56 +02:00
# if user changed filament preset, don't propagate this to the tabs
2012-10-24 20:24:11 +02:00
if ( $group eq 'filament' && @ { $self -> { preset_choosers }{ filament }} > 1 ) {
my @filament_presets = $self -> filament_presets ;
$ Slic3r::GUI:: Settings -> { presets }{ filament } = $choice -> GetString ( $filament_presets [ 0 ]) . ".ini" ;
$ Slic3r::GUI:: Settings -> { presets }{ "filament_${_}" } = $choice -> GetString ( $filament_presets [ $_ ])
for 1 .. $#filament_presets ;
2014-06-14 19:59:59 +02:00
wxTheApp -> save_settings ;
2012-10-24 20:24:11 +02:00
return ;
}
2015-06-02 11:19:11 +02:00
# call GetSelection() in scalar context as it's context-aware
$self -> { on_select_preset } -> ( $group , scalar $choice -> GetSelection )
2014-07-01 16:40:56 +02:00
if $self -> { on_select_preset };
# get new config and generate on_config_change() event for updating plater and other things
$self -> on_config_change ( $self -> GetFrame -> config );
2012-10-24 20:24:11 +02:00
}
2014-06-14 18:58:56 +02:00
sub GetFrame {
my ( $self ) = @_ ;
2014-06-14 19:54:18 +02:00
return & Wx:: GetTopLevelParent ( $self );
2012-07-27 21:13:03 +02:00
}
2012-08-07 18:44:47 +02:00
sub update_presets {
my $self = shift ;
2015-06-01 23:34:04 +02:00
my ( $group , $presets , $selected , $is_dirty ) = @_ ;
2012-08-07 18:44:47 +02:00
2015-06-01 23:52:15 +02:00
my @choosers = @ { $self -> { preset_choosers }{ $group } };
foreach my $choice ( @choosers ) {
if ( $group eq 'filament' && @choosers > 1 ) {
# if we have more than one filament chooser, keep our selection
# instead of importing the one from the tab
$selected = $choice -> GetSelection ;
$is_dirty = 0 ;
}
2012-08-07 18:44:47 +02:00
$choice -> Clear ;
2015-05-26 02:01:43 +02:00
foreach my $preset ( @$presets ) {
my $bitmap ;
if ( $group eq 'filament' ) {
my $config = $preset -> config ([ 'filament_colour' ]);
my $rgb_hex = $config -> filament_colour -> [ 0 ];
if ( $preset -> default ) {
$bitmap = Wx::Bitmap -> new ( "$Slic3r::var/spool.png" , wxBITMAP_TYPE_PNG );
} else {
$rgb_hex =~ s/^#// ;
my @rgb = unpack 'C*' , pack 'H*' , $rgb_hex ;
my $image = Wx::Image -> new ( 16 , 16 );
$image -> SetRGB ( Wx::Rect -> new ( 0 , 0 , 16 , 16 ), @rgb );
$bitmap = Wx::Bitmap -> new ( $image );
}
} elsif ( $group eq 'print' ) {
$bitmap = Wx::Bitmap -> new ( "$Slic3r::var/cog.png" , wxBITMAP_TYPE_PNG );
} elsif ( $group eq 'printer' ) {
$bitmap = Wx::Bitmap -> new ( "$Slic3r::var/printer_empty.png" , wxBITMAP_TYPE_PNG );
}
$choice -> AppendString ( $preset -> name , $bitmap );
}
2015-06-01 23:34:04 +02:00
2015-06-01 23:52:15 +02:00
if ( $selected <= $#$presets ) {
2015-06-01 23:34:04 +02:00
if ( $is_dirty ) {
2015-06-01 23:52:15 +02:00
$choice -> SetString ( $selected , $choice -> GetString ( $selected ) . " (modified)" );
2015-06-01 23:34:04 +02:00
}
2015-06-01 23:52:15 +02:00
# call SetSelection() only after SetString() otherwise the new string
# won't be picked up as the visible string
$choice -> SetSelection ( $selected );
2015-06-01 23:34:04 +02:00
}
2012-08-07 18:44:47 +02:00
}
}
sub filament_presets {
my $self = shift ;
2015-06-01 14:32:31 +02:00
# force scalar context for GetSelection() as it's context-aware
2015-06-01 02:48:11 -04:00
return map scalar ( $_ -> GetSelection ), @ { $self -> { preset_choosers }{ filament } };
2012-08-07 18:44:47 +02:00
}
2013-12-12 20:19:33 +01:00
sub add {
2012-04-30 14:56:01 +02:00
my $self = shift ;
2014-06-14 19:59:59 +02:00
my @input_files = wxTheApp -> open_model ( $self );
2012-05-31 09:26:30 +02:00
$self -> load_file ( $_ ) for @input_files ;
2012-04-30 14:56:01 +02:00
}
sub load_file {
my $self = shift ;
my ( $input_file ) = @_ ;
2012-07-30 12:08:28 +02:00
$ Slic3r::GUI:: Settings -> { recent }{ skein_directory } = dirname ( $input_file );
2014-06-14 19:59:59 +02:00
wxTheApp -> save_settings ;
2012-04-30 14:56:01 +02:00
2012-07-01 23:29:56 +02:00
my $process_dialog = Wx::ProgressDialog -> new ( 'Loading…' , "Processing input file…" , 100 , $self , 0 );
2012-04-30 21:49:44 +02:00
$process_dialog -> Pulse ;
2012-04-30 14:56:01 +02:00
2012-08-29 19:37:27 +02:00
local $SIG { __WARN__ } = Slic3r::GUI:: warning_catcher ( $self );
2013-12-17 16:12:44 +01:00
my $model = eval { Slic3r::Model -> read_from_file ( $input_file ) };
Slic3r::GUI:: show_error ( $self , $@ ) if $@ ;
2014-03-25 19:06:51 +01:00
if ( defined $model ) {
$self -> load_model_objects ( @ { $model -> objects });
$self -> statusbar -> SetStatusText ( "Loaded " . basename ( $input_file ));
}
2012-08-29 19:37:27 +02:00
$process_dialog -> Destroy ;
2013-12-12 20:19:33 +01:00
}
2014-01-05 14:04:32 +01:00
sub load_model_objects {
my ( $self , @model_objects ) = @_ ;
2013-12-12 20:19:33 +01:00
2014-06-16 23:58:45 +02:00
my $bed_centerf = $self -> bed_centerf ;
2015-05-25 19:51:47 +02:00
my $bed_shape = Slic3r::Polygon -> new_scale ( @ { $self -> { config } -> bed_shape });
my $bed_size = $bed_shape -> bounding_box -> size ;
2014-06-16 23:58:45 +02:00
2013-12-12 20:19:33 +01:00
my $need_arrange = 0 ;
2015-05-25 19:51:47 +02:00
my $scaled_down = 0 ;
2014-01-05 14:04:32 +01:00
my @obj_idx = ();
foreach my $model_object ( @model_objects ) {
my $o = $self -> { model } -> add_object ( $model_object );
2013-12-12 20:19:33 +01:00
2014-01-05 14:04:32 +01:00
push @ { $self -> { objects } }, Slic3r::GUI::Plater::Object -> new (
name => basename ( $model_object -> input_file ),
);
push @obj_idx , $# { $self -> { objects } };
2013-12-12 20:19:33 +01:00
2014-05-10 16:59:17 +02:00
if ( $model_object -> instances_count == 0 ) {
2014-01-05 14:04:32 +01:00
# if object has no defined position(s) we need to rearrange everything after loading
$need_arrange = 1 ;
# add a default instance and center object around origin
2014-12-12 22:43:04 +01:00
$o -> center_around_origin ; # also aligns object to Z = 0
2014-06-16 23:58:45 +02:00
$o -> add_instance ( offset => $bed_centerf );
2014-01-05 14:04:32 +01:00
}
2015-05-25 19:51:47 +02:00
{
# if the object is too large (more than 5 times the bed), scale it down
my $size = $o -> bounding_box -> size ;
my $ratio = max ( @$size [ X , Y ]) / unscale ( max ( @$bed_size [ X , Y ]));
if ( $ratio > 5 ) {
$_ -> set_scaling_factor ( 1 / $ratio ) for @ { $o -> instances };
$scaled_down = 1 ;
}
}
2014-01-05 14:04:32 +01:00
2014-03-22 20:12:54 +01:00
$self -> { print } -> auto_assign_extruders ( $o );
2014-05-07 01:11:49 +02:00
$self -> { print } -> add_model_object ( $o );
2014-01-05 14:04:32 +01:00
}
2013-12-13 14:02:01 +01:00
2013-12-18 19:11:20 +01:00
# if user turned autocentering off, automatic arranging would disappoint them
if ( ! $ Slic3r::GUI:: Settings -> { _ }{ autocenter }) {
$need_arrange = 0 ;
}
2015-05-25 19:51:47 +02:00
if ( $scaled_down ) {
Slic3r::GUI:: show_info (
$self ,
'Your object appears to be too large, so it was automatically scaled down to fit your print bed.' ,
'Object too large?' ,
);
}
2012-05-01 00:30:46 +02:00
2015-05-25 19:51:47 +02:00
foreach my $obj_idx ( @obj_idx ) {
2014-01-05 14:04:32 +01:00
my $object = $self -> { objects }[ $obj_idx ];
my $model_object = $self -> { model } -> objects -> [ $obj_idx ];
$self -> { list } -> InsertStringItem ( $obj_idx , $object -> name );
$self -> { list } -> SetItemFont ( $obj_idx , Wx::Font -> new ( 10 , wxDEFAULT , wxNORMAL , wxNORMAL ))
if $self -> { list } -> can ( 'SetItemFont' ); # legacy code for wxPerl < 0.9918 not supporting SetItemFont()
2013-08-25 12:22:05 +02:00
2014-01-05 14:04:32 +01:00
$self -> { list } -> SetItem ( $obj_idx , 1 , $model_object -> instances_count );
$self -> { list } -> SetItem ( $obj_idx , 2 , ( $model_object -> instances -> [ 0 ] -> scaling_factor * 100 ) . "%" );
2012-04-30 14:56:01 +02:00
2014-01-05 14:04:32 +01:00
$self -> make_thumbnail ( $obj_idx );
}
2015-05-25 19:51:47 +02:00
$self -> arrange if $need_arrange ;
2013-12-18 18:54:11 +01:00
$self -> update ;
2014-12-16 01:12:37 +01:00
# zoom to objects
$self -> { canvas3D } -> zoom_to_volumes
if $self -> { canvas3D };
2012-04-30 14:56:01 +02:00
$self -> { list } -> Update ;
2015-05-25 19:51:47 +02:00
$self -> { list } -> Select ( $obj_idx [ - 1 ], 1 );
2012-04-30 14:56:01 +02:00
$self -> object_list_changed ;
2014-06-13 11:19:53 +02:00
2014-06-13 20:36:45 +02:00
$self -> schedule_background_process ;
2012-04-30 14:56:01 +02:00
}
2014-06-16 23:58:45 +02:00
sub bed_centerf {
my ( $self ) = @_ ;
my $bed_shape = Slic3r::Polygon -> new_scale ( @ { $self -> { config } -> bed_shape });
my $bed_center = $bed_shape -> bounding_box -> center ;
return Slic3r::Pointf -> new ( unscale ( $bed_center -> x ), unscale ( $bed_center -> y )); #)
}
2012-04-30 14:56:01 +02:00
sub remove {
my $self = shift ;
2012-08-11 16:00:41 +02:00
my ( $obj_idx ) = @_ ;
2012-04-30 14:56:01 +02:00
2014-06-14 20:26:53 +02:00
$self -> stop_background_process ;
2014-12-07 20:23:00 +01:00
# Prevent toolpaths preview from rendering while we modify the Print object
$self -> { toolpaths2D } -> enabled ( 0 ) if $self -> { toolpaths2D };
2015-01-18 19:36:47 +01:00
$self -> { preview3D } -> enabled ( 0 ) if $self -> { preview3D };
2014-12-07 20:23:00 +01:00
2012-08-29 19:37:27 +02:00
# if no object index is supplied, remove the selected one
if ( ! defined $obj_idx ) {
( $obj_idx , undef ) = $self -> selected_object ;
2012-04-30 14:56:01 +02:00
}
2012-08-29 19:37:27 +02:00
splice @ { $self -> { objects }}, $obj_idx , 1 ;
2013-12-12 20:19:33 +01:00
$self -> { model } -> delete_object ( $obj_idx );
2013-12-15 16:17:12 +01:00
$self -> { print } -> delete_object ( $obj_idx );
2012-08-29 19:37:27 +02:00
$self -> { list } -> DeleteItem ( $obj_idx );
2012-04-30 14:56:01 +02:00
$self -> object_list_changed ;
2013-12-12 20:19:33 +01:00
$self -> select_object ( undef );
2013-12-18 18:54:11 +01:00
$self -> update ;
2014-06-14 20:26:53 +02:00
$self -> schedule_background_process ;
2012-04-30 14:56:01 +02:00
}
sub reset {
my $self = shift ;
2014-06-14 20:26:53 +02:00
$self -> stop_background_process ;
2014-12-07 20:23:00 +01:00
# Prevent toolpaths preview from rendering while we modify the Print object
$self -> { toolpaths2D } -> enabled ( 0 ) if $self -> { toolpaths2D };
2015-01-18 19:36:47 +01:00
$self -> { preview3D } -> enabled ( 0 ) if $self -> { preview3D };
2014-12-07 20:23:00 +01:00
2012-08-29 19:37:27 +02:00
@ { $self -> { objects }} = ();
2014-05-07 00:58:29 +02:00
$self -> { model } -> clear_objects ;
$self -> { print } -> clear_objects ;
2012-04-30 14:56:01 +02:00
$self -> { list } -> DeleteAllItems ;
$self -> object_list_changed ;
2013-12-12 20:19:33 +01:00
$self -> select_object ( undef );
2014-07-14 11:58:00 +02:00
$self -> update ;
2012-04-30 14:56:01 +02:00
}
sub increase {
2015-01-14 23:19:13 +01:00
my ( $self , $copies ) = @_ ;
2012-04-30 14:56:01 +02:00
2015-01-14 23:19:13 +01:00
$copies // = 1 ;
2012-08-29 19:37:27 +02:00
my ( $obj_idx , $object ) = $self -> selected_object ;
2013-12-12 20:19:33 +01:00
my $model_object = $self -> { model } -> objects -> [ $obj_idx ];
2015-01-14 23:19:13 +01:00
my $instance = $model_object -> instances -> [ - 1 ];
for my $i ( 1 .. $copies ) {
$instance = $model_object -> add_instance (
offset => Slic3r::Pointf -> new ( map 10 + $_ , @ { $instance -> offset }),
scaling_factor => $instance -> scaling_factor ,
rotation => $instance -> rotation ,
);
$self -> { print } -> objects -> [ $obj_idx ] -> add_copy ( $instance -> offset );
}
2013-12-12 20:19:33 +01:00
$self -> { list } -> SetItem ( $obj_idx , 1 , $model_object -> instances_count );
2013-12-18 19:11:20 +01:00
# only autoarrange if user has autocentering enabled
2014-09-21 10:56:51 +02:00
$self -> stop_background_process ;
2013-12-18 19:11:20 +01:00
if ( $ Slic3r::GUI:: Settings -> { _ }{ autocenter }) {
$self -> arrange ;
} else {
2014-07-13 12:10:34 +02:00
$self -> update ;
2013-12-18 19:11:20 +01:00
}
2014-09-21 10:56:51 +02:00
$self -> schedule_background_process ;
2012-04-30 14:56:01 +02:00
}
sub decrease {
2015-01-14 23:19:13 +01:00
my ( $self , $copies ) = @_ ;
2012-04-30 14:56:01 +02:00
2015-01-14 23:19:13 +01:00
$copies // = 1 ;
2014-09-21 10:56:51 +02:00
$self -> stop_background_process ;
2012-08-29 19:37:27 +02:00
my ( $obj_idx , $object ) = $self -> selected_object ;
2013-12-12 20:19:33 +01:00
my $model_object = $self -> { model } -> objects -> [ $obj_idx ];
2015-01-14 23:19:13 +01:00
if ( $model_object -> instances_count > $copies ) {
for my $i ( 1 .. $copies ) {
$model_object -> delete_last_instance ;
$self -> { print } -> objects -> [ $obj_idx ] -> delete_last_copy ;
}
2013-12-12 20:19:33 +01:00
$self -> { list } -> SetItem ( $obj_idx , 1 , $model_object -> instances_count );
2012-09-12 16:30:44 +02:00
} else {
$self -> remove ;
}
2012-04-30 14:56:01 +02:00
2012-08-29 19:37:27 +02:00
if ( $self -> { objects }[ $obj_idx ]) {
2012-04-30 14:56:01 +02:00
$self -> { list } -> Select ( $obj_idx , 0 );
$self -> { list } -> Select ( $obj_idx , 1 );
}
2013-12-18 18:54:11 +01:00
$self -> update ;
2014-09-21 10:56:51 +02:00
$self -> schedule_background_process ;
2012-04-30 14:56:01 +02:00
}
2015-01-14 23:19:13 +01:00
sub set_number_of_copies {
my ( $self ) = @_ ;
$self -> pause_background_process ;
# get current number of copies
my ( $obj_idx , $object ) = $self -> selected_object ;
my $model_object = $self -> { model } -> objects -> [ $obj_idx ];
# prompt user
my $copies = Wx:: GetNumberFromUser ( "" , "Enter the number of copies of the selected object:" , "Copies" , $model_object -> instances_count , 0 , 1000 , $self );
my $diff = $copies - $model_object -> instances_count ;
if ( $diff == 0 ) {
# no variation
$self -> resume_background_process ;
} elsif ( $diff > 0 ) {
$self -> increase ( $diff );
} elsif ( $diff < 0 ) {
$self -> decrease ( - $diff );
}
}
2012-04-30 14:56:01 +02:00
sub rotate {
my $self = shift ;
2014-06-14 20:52:21 +02:00
my ( $angle , $axis ) = @_ ;
2015-05-06 00:39:16 +02:00
# angle is in degrees
2014-06-14 20:52:21 +02:00
$axis // = Z ;
2012-04-30 14:56:01 +02:00
2012-08-29 19:37:27 +02:00
my ( $obj_idx , $object ) = $self -> selected_object ;
2014-06-14 20:52:21 +02:00
return if ! defined $obj_idx ;
2013-12-12 20:19:33 +01:00
my $model_object = $self -> { model } -> objects -> [ $obj_idx ];
my $model_instance = $model_object -> instances -> [ 0 ];
2012-04-30 14:56:01 +02:00
2012-11-19 17:39:16 +01:00
# we need thumbnail to be computed before allowing rotation
return if ! $object -> thumbnail ;
2012-04-30 22:14:27 +02:00
if ( ! defined $angle ) {
2014-06-14 20:52:21 +02:00
my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z' ;
$angle = Wx:: GetNumberFromUser ( "" , "Enter the rotation angle:" , "Rotate around $axis_name axis" , $model_instance -> rotation , - 364 , 364 , $self );
2012-04-30 22:14:27 +02:00
return if ! $angle || $angle == - 1 ;
2013-06-13 10:27:47 +02:00
$angle = 0 - $angle ; # rotate clockwise (be consistent with button icon)
2012-04-30 22:14:27 +02:00
}
2014-07-01 16:40:56 +02:00
$self -> stop_background_process ;
if ( $axis == Z ) {
2015-05-03 18:40:00 +02:00
my $new_angle = $model_instance -> rotation + deg2rad ( $angle );
2014-07-01 16:40:56 +02:00
$_ -> set_rotation ( $new_angle ) for @ { $model_object -> instances };
$object -> transform_thumbnail ( $self -> { model }, $obj_idx );
} else {
# rotation around X and Y needs to be performed on mesh
# so we first apply any Z rotation
if ( $model_instance -> rotation != 0 ) {
2015-05-03 18:40:00 +02:00
$model_object -> rotate ( $model_instance -> rotation , Z );
2014-07-01 16:40:56 +02:00
$_ -> set_rotation ( 0 ) for @ { $model_object -> instances };
2014-06-14 20:52:21 +02:00
}
2014-07-01 16:40:56 +02:00
$model_object -> rotate ( deg2rad ( $angle ), $axis );
2015-01-09 00:47:40 +01:00
# realign object to Z = 0
$model_object -> center_around_origin ;
2014-07-01 16:40:56 +02:00
$self -> make_thumbnail ( $obj_idx );
2013-12-12 20:19:33 +01:00
}
2014-07-01 16:40:56 +02:00
$model_object -> update_bounding_box ;
# update print and start background processing
$self -> { print } -> add_model_object ( $model_object , $obj_idx );
2013-12-12 20:19:33 +01:00
$self -> selection_changed ; # refresh info (size etc.)
2013-12-18 18:54:11 +01:00
$self -> update ;
2014-12-29 22:29:24 +01:00
$self -> schedule_background_process ;
2012-04-30 14:56:01 +02:00
}
2015-11-04 23:11:30 +01:00
sub mirror {
2014-06-14 21:14:33 +02:00
my ( $self , $axis ) = @_ ;
my ( $obj_idx , $object ) = $self -> selected_object ;
return if ! defined $obj_idx ;
my $model_object = $self -> { model } -> objects -> [ $obj_idx ];
my $model_instance = $model_object -> instances -> [ 0 ];
2015-11-04 23:11:30 +01:00
# apply Z rotation before mirroring
2014-06-14 21:14:33 +02:00
if ( $model_instance -> rotation != 0 ) {
2015-05-03 18:40:00 +02:00
$model_object -> rotate ( $model_instance -> rotation , Z );
2014-06-14 21:14:33 +02:00
$_ -> set_rotation ( 0 ) for @ { $model_object -> instances };
}
2015-11-04 23:11:30 +01:00
$model_object -> mirror ( $axis );
2014-06-14 21:14:33 +02:00
$model_object -> update_bounding_box ;
2015-01-09 00:47:40 +01:00
# realign object to Z = 0
$model_object -> center_around_origin ;
2014-06-14 21:14:33 +02:00
$self -> make_thumbnail ( $obj_idx );
# update print and start background processing
$self -> stop_background_process ;
$self -> { print } -> add_model_object ( $model_object , $obj_idx );
$self -> selection_changed ; # refresh info (size etc.)
$self -> update ;
2014-12-29 22:29:24 +01:00
$self -> schedule_background_process ;
2014-06-14 21:14:33 +02:00
}
2012-04-30 14:56:01 +02:00
sub changescale {
2014-06-17 00:50:44 +02:00
my ( $self , $axis ) = @_ ;
2012-04-30 14:56:01 +02:00
2012-08-29 19:37:27 +02:00
my ( $obj_idx , $object ) = $self -> selected_object ;
2014-06-17 00:50:44 +02:00
return if ! defined $obj_idx ;
2013-12-12 20:19:33 +01:00
my $model_object = $self -> { model } -> objects -> [ $obj_idx ];
my $model_instance = $model_object -> instances -> [ 0 ];
2012-08-29 19:37:27 +02:00
2013-06-12 16:53:19 +02:00
# we need thumbnail to be computed before allowing scaling
return if ! $object -> thumbnail ;
2014-06-17 00:50:44 +02:00
if ( defined $axis ) {
my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z' ;
my $scale = Wx:: GetNumberFromUser ( "" , "Enter the scale % for the selected object:" , "Scale along $axis_name" , 100 , 0 , 100000 , $self );
return if ! $scale || $scale < 0 ;
# apply Z rotation before scaling
if ( $model_instance -> rotation != 0 ) {
2015-05-03 18:40:00 +02:00
$model_object -> rotate ( $model_instance -> rotation , Z );
2014-06-17 00:50:44 +02:00
$_ -> set_rotation ( 0 ) for @ { $model_object -> instances };
}
my $versor = [ 1 , 1 , 1 ];
$versor -> [ $axis ] = $scale / 100 ;
2014-09-21 10:51:36 +02:00
$model_object -> scale_xyz ( Slic3r::Pointf3 -> new ( @$versor ));
2015-01-09 00:47:40 +01:00
# object was already aligned to Z = 0, so no need to realign it
2014-06-17 00:50:44 +02:00
$self -> make_thumbnail ( $obj_idx );
} else {
# max scale factor should be above 2540 to allow importing files exported in inches
my $scale = Wx:: GetNumberFromUser ( "" , "Enter the scale % for the selected object:" , 'Scale' , $model_instance -> scaling_factor * 100 , 0 , 100000 , $self );
return if ! $scale || $scale < 0 ;
2012-04-30 14:56:01 +02:00
2014-06-17 00:50:44 +02:00
$self -> { list } -> SetItem ( $obj_idx , 2 , "$scale%" );
$scale /= 100 ; # turn percent into factor
2013-12-12 20:19:33 +01:00
my $variation = $scale / $model_instance -> scaling_factor ;
foreach my $range ( @ { $model_object -> layer_height_ranges }) {
$range -> [ 0 ] *= $variation ;
$range -> [ 1 ] *= $variation ;
}
2014-04-30 02:04:49 +03:00
$_ -> set_scaling_factor ( $scale ) for @ { $model_object -> instances };
2013-12-12 20:19:33 +01:00
$object -> transform_thumbnail ( $self -> { model }, $obj_idx );
}
2014-06-17 00:50:44 +02:00
$model_object -> update_bounding_box ;
# update print and start background processing
$self -> stop_background_process ;
$self -> { print } -> add_model_object ( $model_object , $obj_idx );
2013-08-25 12:22:05 +02:00
$self -> selection_changed ( 1 ); # refresh info (size, volume etc.)
2013-12-18 19:11:20 +01:00
$self -> update ;
2014-12-29 22:29:24 +01:00
$self -> schedule_background_process ;
2012-08-29 19:37:27 +02:00
}
sub arrange {
my $self = shift ;
2014-11-13 00:23:31 +01:00
$self -> pause_background_process ;
2014-06-16 15:18:39 +02:00
my $bb = Slic3r::Geometry::BoundingBoxf -> new_from_points ( $self -> { config } -> bed_shape );
2012-08-29 19:37:27 +02:00
eval {
2014-06-14 19:54:18 +02:00
$self -> { model } -> arrange_objects ( $self -> GetFrame -> config -> min_object_distance , $bb );
2012-08-29 19:37:27 +02:00
};
2013-12-24 00:30:51 +01:00
# ignore arrange failures on purpose: user has visual feedback and we don't need to warn him
# when parts don't fit in print bed
2012-08-29 19:37:27 +02:00
2013-12-18 19:11:20 +01:00
$self -> update ( 1 );
2012-04-30 14:56:01 +02:00
}
2012-05-01 00:30:46 +02:00
sub split_object {
my $self = shift ;
2013-12-12 20:19:33 +01:00
my ( $obj_idx , $current_object ) = $self -> selected_object ;
2014-11-30 20:53:53 +01:00
# we clone model object because split_object() adds the split volumes
2014-12-30 13:16:28 +01:00
# into the same model object, thus causing duplicates when we call load_model_objects()
my $new_model = $self -> { model } -> clone ; # store this before calling get_object()
my $current_model_object = $new_model -> get_object ( $obj_idx );
2012-10-21 20:56:19 +02:00
2014-12-30 13:16:28 +01:00
if ( $current_model_object -> volumes_count > 1 ) {
2014-11-12 22:36:03 +01:00
Slic3r::GUI:: warning_catcher ( $self ) -> ( "The selected object can't be split because it contains more than one volume/material." );
2012-06-06 19:00:34 +02:00
return ;
}
2012-08-10 16:05:16 +02:00
2014-12-30 13:16:28 +01:00
$self -> pause_background_process ;
2014-06-13 23:27:52 +02:00
2014-11-12 22:36:03 +01:00
my @model_objects = @ { $current_model_object -> split_object };
if ( @model_objects == 1 ) {
2014-12-30 13:16:28 +01:00
$self -> resume_background_process ;
2014-12-21 23:40:05 +01:00
Slic3r::GUI:: warning_catcher ( $self ) -> ( "The selected object couldn't be split because it contains only one part." );
2014-12-30 22:07:21 +01:00
$self -> resume_background_process ;
2014-11-12 22:36:03 +01:00
return ;
}
2012-10-27 21:39:57 +02:00
2014-11-12 22:36:03 +01:00
foreach my $object ( @model_objects ) {
$object -> instances -> [ $_ ] -> offset -> translate ( $_ * 10 , $_ * 10 )
for 1 .. $# { $object -> instances };
2014-01-14 22:06:28 +01:00
2013-12-12 20:19:33 +01:00
# we need to center this single object around origin
2014-11-12 22:36:03 +01:00
$object -> center_around_origin ;
2012-05-01 00:30:46 +02:00
}
2014-05-26 13:10:58 +02:00
# remove the original object before spawning the object_loaded event, otherwise
# we'll pass the wrong $obj_idx to it (which won't be recognized after the
# thumbnail thread returns)
$self -> remove ( $obj_idx );
$current_object = $obj_idx = undef ;
2014-03-24 21:42:38 +01:00
# load all model objects at once, otherwise the plate would be rearranged after each one
# causing original positions not to be kept
$self -> load_model_objects ( @model_objects );
2012-05-01 00:30:46 +02:00
}
2014-06-13 20:36:45 +02:00
sub schedule_background_process {
my ( $self ) = @_ ;
2014-07-04 10:32:32 +02:00
if ( defined $self -> { apply_config_timer }) {
$self -> { apply_config_timer } -> Start ( PROCESS_DELAY , 1 ); # 1 = one shot
}
2014-06-13 20:36:45 +02:00
}
2014-06-13 19:23:51 +02:00
sub async_apply_config {
2014-06-13 15:54:13 +02:00
my ( $self ) = @_ ;
2015-01-25 10:59:39 +01:00
# reset preview canvases
$self -> { toolpaths2D } -> reload_print if $self -> { toolpaths2D };
$self -> { preview3D } -> reload_print if $self -> { preview3D };
2014-06-13 23:27:52 +02:00
# pause process thread before applying new config
# since we don't want to touch data that is being used by the threads
2014-07-12 11:52:19 +02:00
$self -> pause_background_process ;
2014-06-13 19:23:51 +02:00
# apply new config
2014-06-14 19:54:18 +02:00
my $invalidated = $self -> { print } -> apply_config ( $self -> GetFrame -> config );
2014-06-13 19:23:51 +02:00
2014-06-13 15:54:13 +02:00
return if ! $ Slic3r::GUI:: Settings -> { _ }{ background_processing };
2014-06-13 19:23:51 +02:00
if ( $invalidated ) {
# kill current thread if any
$self -> stop_background_process ;
} else {
2014-07-12 11:52:19 +02:00
$self -> resume_background_process ;
2014-06-13 19:23:51 +02:00
}
2014-06-13 20:36:45 +02:00
# schedule a new process thread in case it wasn't running
$self -> start_background_process ;
2014-06-13 15:54:13 +02:00
}
2014-06-13 11:19:53 +02:00
sub start_background_process {
my ( $self ) = @_ ;
return if ! $ Slic3r:: have_threads ;
return if ! @ { $self -> { objects }};
2014-06-13 20:36:45 +02:00
return if $self -> { process_thread };
2014-06-13 11:19:53 +02:00
# It looks like declaring a local $SIG{__WARN__} prevents the ugly
# "Attempt to free unreferenced scalar" warning...
local $SIG { __WARN__ } = Slic3r::GUI:: warning_catcher ( $self );
# don't start process thread if config is not valid
eval {
# this will throw errors if config is not valid
2014-06-14 19:54:18 +02:00
$self -> GetFrame -> config -> validate ;
2014-06-13 11:19:53 +02:00
$self -> { print } -> validate ;
};
2014-12-01 00:15:45 +01:00
if ( $@ ) {
$self -> statusbar -> SetStatusText ( $@ );
return ;
}
2014-06-13 11:19:53 +02:00
# apply extra variables
{
2014-06-14 19:54:18 +02:00
my $extra = $self -> GetFrame -> extra_variables ;
2014-06-13 11:19:53 +02:00
$self -> { print } -> placeholder_parser -> set ( $_ , $extra -> { $_ }) for keys %$extra ;
}
# start thread
2014-06-13 14:27:55 +02:00
@_ = ();
2014-07-01 19:00:23 +02:00
$self -> { process_thread } = Slic3r:: spawn_thread ( sub {
2014-06-13 14:27:55 +02:00
eval {
$self -> { print } -> process ;
};
if ( $@ ) {
2015-11-04 19:27:58 +01:00
Slic3r:: debugf "Background process error: $@\n" ;
2014-12-01 00:15:45 +01:00
Wx:: PostEvent ( $self , Wx::PlThreadEvent -> new ( - 1 , $PROCESS_COMPLETED_EVENT , $@ ));
2014-06-13 14:27:55 +02:00
} else {
2014-12-01 00:15:45 +01:00
Wx:: PostEvent ( $self , Wx::PlThreadEvent -> new ( - 1 , $PROCESS_COMPLETED_EVENT , undef ));
2014-06-13 14:27:55 +02:00
}
2014-06-13 11:19:53 +02:00
Slic3r:: thread_cleanup ();
});
Slic3r:: debugf "Background processing started.\n" ;
}
sub stop_background_process {
my ( $self ) = @_ ;
2014-06-16 15:18:39 +02:00
$self -> { apply_config_timer } -> Stop if defined $self -> { apply_config_timer };
2014-06-13 14:27:55 +02:00
$self -> statusbar -> SetCancelCallback ( undef );
$self -> statusbar -> StopBusy ;
$self -> statusbar -> SetStatusText ( "" );
2015-01-19 18:53:04 +01:00
$self -> { toolpaths2D } -> reload_print if $self -> { toolpaths2D };
$self -> { preview3D } -> reload_print if $self -> { preview3D };
2014-06-13 14:27:55 +02:00
2014-06-13 11:19:53 +02:00
if ( $self -> { process_thread }) {
Slic3r:: debugf "Killing background process.\n" ;
2014-07-01 19:00:23 +02:00
Slic3r:: kill_all_threads ();
2014-06-13 11:19:53 +02:00
$self -> { process_thread } = undef ;
} else {
Slic3r:: debugf "No background process running.\n" ;
}
2014-06-13 14:27:55 +02:00
# if there's an export process, kill that one as well
if ( $self -> { export_thread }) {
Slic3r:: debugf "Killing background export process.\n" ;
2014-07-01 19:00:23 +02:00
Slic3r:: kill_all_threads ();
2014-06-13 14:27:55 +02:00
$self -> { export_thread } = undef ;
}
2014-06-13 11:19:53 +02:00
}
2014-07-12 11:52:19 +02:00
sub pause_background_process {
my ( $self ) = @_ ;
if ( $self -> { process_thread } || $self -> { export_thread }) {
2014-12-22 19:47:39 +01:00
Slic3r:: pause_all_threads ();
2015-02-16 00:05:39 +01:00
return 1 ;
2014-07-12 11:52:19 +02:00
} elsif ( defined $self -> { apply_config_timer } && $self -> { apply_config_timer } -> IsRunning ) {
$self -> { apply_config_timer } -> Stop ;
2015-02-16 00:05:39 +01:00
return 1 ;
2014-07-12 11:52:19 +02:00
}
2015-02-16 00:05:39 +01:00
return 0 ;
2014-07-12 11:52:19 +02:00
}
sub resume_background_process {
my ( $self ) = @_ ;
if ( $self -> { process_thread } || $self -> { export_thread }) {
2014-12-22 19:47:39 +01:00
Slic3r:: resume_all_threads ();
2014-07-12 11:52:19 +02:00
}
}
2012-04-30 14:56:01 +02:00
sub export_gcode {
2014-12-28 01:30:05 +01:00
my ( $self , $output_file ) = @_ ;
2012-04-30 14:56:01 +02:00
2014-06-14 22:40:37 +02:00
return if ! @ { $self -> { objects }};
2014-06-13 11:19:53 +02:00
if ( $self -> { export_gcode_output_file }) {
2014-06-13 14:27:55 +02:00
Wx::MessageDialog -> new ( $self , "Another export job is currently running." , 'Error' , wxOK | wxICON_ERROR ) -> ShowModal ;
2012-05-05 21:08:15 +02:00
return ;
}
2014-06-13 14:27:55 +02:00
# if process is not running, validate config
# (we assume that if it is running, config is valid)
eval {
# this will throw errors if config is not valid
2014-06-14 19:54:18 +02:00
$self -> GetFrame -> config -> validate ;
2014-06-13 14:27:55 +02:00
$self -> { print } -> validate ;
};
Slic3r::GUI:: catch_error ( $self ) and return ;
2014-03-17 00:39:07 +01:00
# apply config and validate print
2014-06-14 19:54:18 +02:00
my $config = $self -> GetFrame -> config ;
2014-03-17 00:39:07 +01:00
eval {
# this will throw errors if config is not valid
$config -> validate ;
$self -> { print } -> apply_config ( $config );
$self -> { print } -> validate ;
};
2014-06-13 14:27:55 +02:00
if ( ! $ Slic3r:: have_threads ) {
Slic3r::GUI:: catch_error ( $self ) and return ;
2014-03-17 00:39:07 +01:00
}
2012-08-01 16:06:03 +02:00
2012-05-05 21:08:15 +02:00
# select output file
2014-12-28 01:30:05 +01:00
if ( $output_file ) {
$self -> { export_gcode_output_file } = $self -> { print } -> expanded_output_filepath ( $output_file );
} else {
my $default_output_file = $self -> { print } -> expanded_output_filepath ( $ main:: opt { output });
2014-06-14 19:59:59 +02:00
my $dlg = Wx::FileDialog -> new ( $self , 'Save G-code file as:' , wxTheApp -> output_path ( dirname ( $default_output_file )),
2015-06-03 13:19:43 +02:00
basename ( $default_output_file ), & Slic3r::GUI::FILE_WILDCARDS -> { gcode }, wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
2012-05-05 21:08:15 +02:00
if ( $dlg -> ShowModal != wxID_OK ) {
$dlg -> Destroy ;
return ;
}
2015-03-09 15:17:50 +01:00
my $path = Slic3r:: decode_path ( $dlg -> GetPath );
$ Slic3r::GUI:: Settings -> { _ }{ last_output_path } = dirname ( $path );
2014-06-14 19:59:59 +02:00
wxTheApp -> save_settings ;
2015-06-02 22:27:11 +02:00
$self -> { export_gcode_output_file } = $path ;
2012-05-05 21:08:15 +02:00
$dlg -> Destroy ;
}
$self -> statusbar -> StartBusy ;
2013-05-20 10:57:27 +02:00
2012-05-19 21:13:10 +02:00
if ( $ Slic3r:: have_threads ) {
2012-05-05 21:08:15 +02:00
$self -> statusbar -> SetCancelCallback ( sub {
2014-06-13 14:27:55 +02:00
$self -> stop_background_process ;
2012-05-05 21:08:15 +02:00
$self -> statusbar -> SetStatusText ( "Export cancelled" );
2015-03-28 18:53:07 +01:00
$self -> { export_gcode_output_file } = undef ;
$self -> { send_gcode_file } = undef ;
# this updates buttons status
$self -> object_list_changed ;
2012-05-05 21:08:15 +02:00
});
2014-06-13 14:27:55 +02:00
# start background process, whose completion event handler
# will detect $self->{export_gcode_output_file} and proceed with export
$self -> start_background_process ;
2012-05-05 21:08:15 +02:00
} else {
2014-06-13 14:27:55 +02:00
eval {
$self -> { print } -> process ;
$self -> { print } -> export_gcode ( output_file => $self -> { export_gcode_output_file });
};
my $result = ! Slic3r::GUI:: catch_error ( $self );
$self -> on_export_completed ( $result );
2012-05-05 21:08:15 +02:00
}
2014-12-28 01:30:05 +01:00
2015-01-04 19:29:34 +01:00
# this updates buttons status
$self -> object_list_changed ;
2014-12-28 01:30:05 +01:00
return $self -> { export_gcode_output_file };
2012-05-05 21:08:15 +02:00
}
2014-06-13 14:27:55 +02:00
# This gets called only if we have threads.
sub on_process_completed {
2014-12-01 00:15:45 +01:00
my ( $self , $error ) = @_ ;
2012-05-05 21:08:15 +02:00
2012-05-05 21:26:19 +02:00
$self -> statusbar -> SetCancelCallback ( undef );
2012-05-05 21:08:15 +02:00
$self -> statusbar -> StopBusy ;
2014-12-01 00:15:45 +01:00
$self -> statusbar -> SetStatusText ( $error // "" );
2014-06-13 14:27:55 +02:00
Slic3r:: debugf "Background processing completed.\n" ;
$self -> { process_thread } -> detach if $self -> { process_thread };
$self -> { process_thread } = undef ;
2015-11-04 19:27:58 +01:00
# if we're supposed to perform an explicit export let's display the error in a dialog
if ( $error && $self -> { export_gcode_output_file }) {
$self -> { export_gcode_output_file } = undef ;
Slic3r::GUI:: show_error ( $self , $error );
}
2014-12-01 00:15:45 +01:00
return if $error ;
2015-01-19 18:53:04 +01:00
$self -> { toolpaths2D } -> reload_print if $self -> { toolpaths2D };
$self -> { preview3D } -> reload_print if $self -> { preview3D };
2014-06-13 14:27:55 +02:00
# if we have an export filename, start a new thread for exporting G-code
if ( $self -> { export_gcode_output_file }) {
@_ = ();
2014-06-13 19:23:51 +02:00
# workaround for "Attempt to free un referenced scalar..."
our $_thread_self = $self ;
2014-07-01 19:00:23 +02:00
$self -> { export_thread } = Slic3r:: spawn_thread ( sub {
2014-06-13 14:27:55 +02:00
eval {
2014-06-13 19:23:51 +02:00
$_thread_self -> { print } -> export_gcode ( output_file => $_thread_self -> { export_gcode_output_file });
2014-06-13 14:27:55 +02:00
};
if ( $@ ) {
2014-06-13 19:23:51 +02:00
Wx:: PostEvent ( $_thread_self , Wx::PlThreadEvent -> new ( - 1 , $ERROR_EVENT , shared_clone ([ $@ ])));
Wx:: PostEvent ( $_thread_self , Wx::PlThreadEvent -> new ( - 1 , $EXPORT_COMPLETED_EVENT , 0 ));
2014-06-13 14:27:55 +02:00
} else {
2014-06-13 19:23:51 +02:00
Wx:: PostEvent ( $_thread_self , Wx::PlThreadEvent -> new ( - 1 , $EXPORT_COMPLETED_EVENT , 1 ));
2014-06-13 14:27:55 +02:00
}
Slic3r:: thread_cleanup ();
});
Slic3r:: debugf "Background G-code export started.\n" ;
}
}
# This gets called also if we have no threads.
sub on_progress_event {
my ( $self , $percent , $message ) = @_ ;
$self -> statusbar -> SetProgress ( $percent );
$self -> statusbar -> SetStatusText ( "$message…" );
}
# This gets called also if we don't have threads.
sub on_export_completed {
my ( $self , $result ) = @_ ;
$self -> statusbar -> SetCancelCallback ( undef );
$self -> statusbar -> StopBusy ;
$self -> statusbar -> SetStatusText ( "" );
Slic3r:: debugf "Background export process completed.\n" ;
$self -> { export_thread } -> detach if $self -> { export_thread };
$self -> { export_thread } = undef ;
my $message ;
2014-12-28 01:30:05 +01:00
my $send_gcode = 0 ;
2015-01-03 23:25:55 +01:00
my $do_print = 0 ;
2014-06-13 14:27:55 +02:00
if ( $result ) {
2015-01-03 23:25:55 +01:00
if ( $self -> { print_file }) {
2015-01-04 23:18:23 +01:00
$message = "File added to print queue" ;
2015-01-03 23:25:55 +01:00
$do_print = 1 ;
} elsif ( $self -> { send_gcode_file }) {
2014-12-29 12:49:32 +01:00
$message = "Sending G-code file to the OctoPrint server..." ;
2014-12-28 01:30:05 +01:00
$send_gcode = 1 ;
} else {
$message = "G-code file exported to " . $self -> { export_gcode_output_file };
}
2014-06-13 14:27:55 +02:00
} else {
$message = "Export failed" ;
}
$self -> { export_gcode_output_file } = undef ;
2013-12-16 00:54:59 +01:00
$self -> statusbar -> SetStatusText ( $message );
2014-06-14 19:59:59 +02:00
wxTheApp -> notify ( $message );
2014-12-28 01:30:05 +01:00
2015-01-03 23:25:55 +01:00
$self -> do_print if $do_print ;
2014-12-28 01:30:05 +01:00
$self -> send_gcode if $send_gcode ;
2015-01-03 23:25:55 +01:00
$self -> { print_file } = undef ;
2014-12-28 01:30:05 +01:00
$self -> { send_gcode_file } = undef ;
2015-01-04 19:29:34 +01:00
# this updates buttons status
$self -> object_list_changed ;
2014-12-28 01:30:05 +01:00
}
2015-01-03 23:25:55 +01:00
sub do_print {
my ( $self ) = @_ ;
my $printer_tab = $self -> GetFrame -> { options_tabs }{ printer };
my $printer_name = $printer_tab -> get_current_preset -> name ;
2015-01-04 23:18:23 +01:00
my $controller = $self -> GetFrame -> { controller };
2015-01-03 23:25:55 +01:00
my $printer_panel = $controller -> add_printer ( $printer_name , $printer_tab -> config );
my $filament_stats = $self -> { print } -> filament_stats ;
my @filament_names = $self -> GetFrame -> filament_preset_names ;
$filament_stats = { map { $filament_names [ $_ ] => $filament_stats -> { $_ } } keys %$filament_stats };
$printer_panel -> load_print_job ( $self -> { print_file }, $filament_stats );
2015-01-04 23:18:23 +01:00
$self -> GetFrame -> select_tab ( 1 );
2015-01-03 23:25:55 +01:00
}
2014-12-28 01:30:05 +01:00
sub send_gcode {
my ( $self ) = @_ ;
$self -> statusbar -> StartBusy ;
my $ua = LWP::UserAgent -> new ;
2015-01-04 15:30:31 +01:00
$ua -> timeout ( 180 );
2014-12-28 01:30:05 +01:00
2015-06-02 17:10:06 +02:00
my $path = Slic3r:: encode_path ( $self -> { send_gcode_file });
2014-12-28 01:30:05 +01:00
my $res = $ua -> post (
"http://" . $self -> { config } -> octoprint_host . "/api/files/local" ,
Content_Type => 'form-data' ,
'X-Api-Key' => $self -> { config } -> octoprint_apikey ,
Content => [
2015-06-02 17:10:06 +02:00
# OctoPrint doesn't like Windows paths so we use basename()
# Also, since we need to read from filesystem we process it through encode_path()
file => [ $path , basename ( $path ) ],
2014-12-28 01:30:05 +01:00
],
);
$self -> statusbar -> StopBusy ;
if ( $res -> is_success ) {
2014-12-29 12:49:32 +01:00
$self -> statusbar -> SetStatusText ( "G-code file successfully uploaded to the OctoPrint server" );
2014-12-28 01:30:05 +01:00
} else {
2014-12-29 12:49:32 +01:00
my $message = "Error while uploading to the OctoPrint server: " . $res -> status_line ;
2014-12-29 11:45:09 +01:00
Slic3r::GUI:: show_error ( $self , $message );
$self -> statusbar -> SetStatusText ( $message );
2014-12-28 01:30:05 +01:00
}
2012-05-05 21:08:15 +02:00
}
2012-04-30 17:10:54 +02:00
sub export_stl {
my $self = shift ;
2014-06-14 22:40:37 +02:00
return if ! @ { $self -> { objects }};
2012-04-30 17:10:54 +02:00
2012-08-29 17:11:56 +02:00
my $output_file = $self -> _get_export_file ( 'STL' ) or return ;
2014-12-12 22:43:04 +01:00
Slic3r::Format::STL -> write_file ( $output_file , $self -> { model }, binary => 1 );
2012-08-29 17:11:56 +02:00
$self -> statusbar -> SetStatusText ( "STL file exported to $output_file" );
}
2015-01-03 15:48:53 +01:00
sub export_object_stl {
my $self = shift ;
my ( $obj_idx , $object ) = $self -> selected_object ;
return if ! defined $obj_idx ;
my $model_object = $self -> { model } -> objects -> [ $obj_idx ];
my $output_file = $self -> _get_export_file ( 'STL' ) or return ;
Slic3r::Format::STL -> write_file ( $output_file , $model_object -> mesh , binary => 1 );
$self -> statusbar -> SetStatusText ( "STL file exported to $output_file" );
}
2012-08-29 17:11:56 +02:00
sub export_amf {
my $self = shift ;
2014-06-14 22:40:37 +02:00
return if ! @ { $self -> { objects }};
2012-08-29 17:11:56 +02:00
my $output_file = $self -> _get_export_file ( 'AMF' ) or return ;
2013-12-12 20:19:33 +01:00
Slic3r::Format::AMF -> write_file ( $output_file , $self -> { model });
2012-08-29 17:11:56 +02:00
$self -> statusbar -> SetStatusText ( "AMF file exported to $output_file" );
}
sub _get_export_file {
my $self = shift ;
my ( $format ) = @_ ;
my $suffix = $format eq 'STL' ? '.stl' : '.amf.xml' ;
2012-04-30 17:10:54 +02:00
my $output_file = $ main:: opt { output };
{
2014-03-17 00:39:07 +01:00
$output_file = $self -> { print } -> expanded_output_filepath ( $output_file );
2012-08-29 17:11:56 +02:00
$output_file =~ s/\.gcode$/$suffix/i ;
my $dlg = Wx::FileDialog -> new ( $self , "Save $format file as:" , dirname ( $output_file ),
2014-06-14 19:54:18 +02:00
basename ( $output_file ), & Slic3r::GUI:: MODEL_WILDCARD , wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
2012-04-30 17:10:54 +02:00
if ( $dlg -> ShowModal != wxID_OK ) {
$dlg -> Destroy ;
2012-08-29 17:11:56 +02:00
return undef ;
2012-04-30 17:10:54 +02:00
}
2015-06-02 22:27:11 +02:00
$output_file = Slic3r:: decode_path ( $dlg -> GetPath );
2012-04-30 17:10:54 +02:00
$dlg -> Destroy ;
}
2012-08-29 17:11:56 +02:00
return $output_file ;
2012-08-29 16:49:38 +02:00
}
2012-04-30 14:56:01 +02:00
sub make_thumbnail {
my $self = shift ;
my ( $obj_idx ) = @_ ;
2013-12-12 20:19:33 +01:00
my $plater_object = $self -> { objects }[ $obj_idx ];
$plater_object -> thumbnail ( Slic3r::ExPolygon::Collection -> new );
2012-04-30 20:59:14 +02:00
my $cb = sub {
2013-12-12 20:19:33 +01:00
$plater_object -> make_thumbnail ( $self -> { model }, $obj_idx );
2012-04-30 20:59:14 +02:00
2012-05-19 21:13:10 +02:00
if ( $ Slic3r:: have_threads ) {
2013-11-12 14:30:13 +01:00
Wx:: PostEvent ( $self , Wx::PlThreadEvent -> new ( - 1 , $THUMBNAIL_DONE_EVENT , shared_clone ([ $obj_idx ])));
2013-07-11 19:34:37 +02:00
Slic3r:: thread_cleanup ();
2012-04-30 22:34:41 +02:00
threads -> exit ;
} else {
2012-10-01 18:12:14 +02:00
$self -> on_thumbnail_made ( $obj_idx );
2012-04-30 22:34:41 +02:00
}
2012-04-30 20:59:14 +02:00
};
2013-05-19 17:34:33 +02:00
@_ = ();
2013-12-22 01:27:09 +01:00
$ Slic3r:: have_threads
? threads -> create ( sub { $cb -> (); Slic3r:: thread_cleanup (); }) -> detach
: $cb -> ();
2012-04-30 14:56:01 +02:00
}
2012-08-29 19:37:27 +02:00
sub on_thumbnail_made {
2012-04-30 22:34:41 +02:00
my $self = shift ;
2012-10-01 18:12:14 +02:00
my ( $obj_idx ) = @_ ;
2013-12-12 20:19:33 +01:00
$self -> { objects }[ $obj_idx ] -> transform_thumbnail ( $self -> { model }, $obj_idx );
2014-12-13 20:41:03 +01:00
$self -> refresh_canvases ;
2012-04-30 22:34:41 +02:00
}
2013-12-15 23:50:05 +01:00
# this method gets called whenever print center is changed or the objects' bounding box changes
2013-12-12 20:19:33 +01:00
# (i.e. when an object is added/removed/moved/rotated/scaled)
2013-12-18 18:54:11 +01:00
sub update {
2013-12-18 19:11:20 +01:00
my ( $self , $force_autocenter ) = @_ ;
2012-04-30 14:56:01 +02:00
2013-12-18 19:11:20 +01:00
if ( $ Slic3r::GUI:: Settings -> { _ }{ autocenter } || $force_autocenter ) {
2014-06-16 23:58:45 +02:00
$self -> { model } -> center_instances_around_point ( $self -> bed_centerf );
2013-12-18 19:11:20 +01:00
}
2013-12-18 18:54:11 +01:00
2015-02-16 00:05:39 +01:00
my $running = $self -> pause_background_process ;
2014-12-29 22:29:24 +01:00
my $invalidated = $self -> { print } -> reload_model_instances ();
2015-02-16 00:05:39 +01:00
# The mere fact that no steps were invalidated when reloading model instances
# doesn't mean that all steps were done: for example, validation might have
# failed upon previous instance move, so we have no running thread and no steps
# are invalidated on this move, thus we need to schedule a new run.
if ( $invalidated || ! $running ) {
2014-12-29 22:29:24 +01:00
$self -> schedule_background_process ;
} else {
$self -> resume_background_process ;
}
2014-12-13 20:41:03 +01:00
$self -> refresh_canvases ;
2012-04-30 14:56:01 +02:00
}
2014-07-01 16:40:56 +02:00
sub on_extruders_change {
my ( $self , $num_extruders ) = @_ ;
my $choices = $self -> { preset_choosers }{ filament };
while ( @$choices < $num_extruders ) {
2015-06-01 23:58:34 +02:00
# copy strings from first choice
2014-07-01 16:40:56 +02:00
my @presets = $choices -> [ 0 ] -> GetStrings ;
2015-06-01 23:58:34 +02:00
# initialize new choice
my $choice = Wx::BitmapComboBox -> new ( $self , - 1 , "" , wxDefaultPosition , wxDefaultSize , [ @presets ], wxCB_READONLY );
push @$choices , $choice ;
# copy icons from first choice
$choice -> SetItemBitmap ( $_ , $choices -> [ 0 ] -> GetItemBitmap ( $_ )) for 0 .. $#presets ;
# insert new choice into sizer
2014-12-25 19:14:18 +01:00
$self -> { presets_sizer } -> Insert ( 4 + ( $#$choices - 1 ) * 2 , 0 , 0 );
2015-06-01 23:58:34 +02:00
$self -> { presets_sizer } -> Insert ( 5 + ( $#$choices - 1 ) * 2 , $choice , 0 , wxEXPAND | wxBOTTOM , FILAMENT_CHOOSERS_SPACING );
# setup the listener
2015-06-02 11:19:11 +02:00
EVT_COMBOBOX ( $choice , $choice , sub {
my ( $choice ) = @_ ;
wxTheApp -> CallAfter ( sub {
$self -> _on_select_preset ( 'filament' , $choice );
});
});
2015-06-01 23:58:34 +02:00
# initialize selection
my $i = first { $choice -> GetString ( $_ ) eq ( $ Slic3r::GUI:: Settings -> { presets }{ "filament_" . $#$choices } || '' ) } 0 .. $#presets ;
$choice -> SetSelection ( $i || 0 );
2014-07-01 16:40:56 +02:00
}
2015-06-01 23:58:34 +02:00
# remove unused choices if any
2014-07-01 16:40:56 +02:00
while ( @$choices > $num_extruders ) {
2014-12-25 19:14:18 +01:00
$self -> { presets_sizer } -> Remove ( 4 + ( $#$choices - 1 ) * 2 ); # label
$self -> { presets_sizer } -> Remove ( 4 + ( $#$choices - 1 ) * 2 ); # wxChoice
2014-07-01 16:40:56 +02:00
$choices -> [ - 1 ] -> Destroy ;
pop @$choices ;
}
$self -> Layout ;
}
2012-07-27 21:13:03 +02:00
sub on_config_change {
my $self = shift ;
2014-07-01 16:40:56 +02:00
my ( $config ) = @_ ;
2014-06-13 11:19:53 +02:00
2014-07-01 16:40:56 +02:00
foreach my $opt_key ( @ { $self -> { config } -> diff ( $config )}) {
$self -> { config } -> set ( $opt_key , $config -> get ( $opt_key ));
2014-06-16 15:18:39 +02:00
if ( $opt_key eq 'bed_shape' ) {
2014-05-28 12:29:43 +02:00
$self -> { canvas } -> update_bed_size ;
2014-12-16 01:12:37 +01:00
$self -> { canvas3D } -> update_bed_size if $self -> { canvas3D };
2015-01-25 00:29:51 +01:00
$self -> { preview3D } -> set_bed_shape ( $self -> { config } -> bed_shape )
if $self -> { preview3D };
2014-05-28 12:29:43 +02:00
$self -> update ;
2015-01-03 23:25:55 +01:00
} elsif ( $opt_key eq 'serial_port' ) {
if ( $config -> get ( 'serial_port' )) {
$self -> { btn_print } -> Show ;
} else {
$self -> { btn_print } -> Hide ;
}
$self -> Layout ;
2014-12-28 01:30:05 +01:00
} elsif ( $opt_key eq 'octoprint_host' ) {
if ( $config -> get ( 'octoprint_host' )) {
$self -> { btn_send_gcode } -> Show ;
} else {
$self -> { btn_send_gcode } -> Hide ;
}
$self -> Layout ;
2014-05-28 12:29:43 +02:00
}
2012-08-06 19:18:31 +02:00
}
2014-06-13 11:19:53 +02:00
2014-06-14 19:54:18 +02:00
return if ! $self -> GetFrame -> is_loaded ;
2014-06-13 15:54:13 +02:00
2014-06-13 19:23:51 +02:00
# (re)start timer
2014-06-13 20:36:45 +02:00
$self -> schedule_background_process ;
2012-07-27 21:13:03 +02:00
}
2012-04-30 14:56:01 +02:00
sub list_item_deselected {
my ( $self , $event ) = @_ ;
2014-03-25 15:30:56 +01:00
return if $PreventListEvents ;
2012-04-30 14:56:01 +02:00
if ( $self -> { list } -> GetFirstSelected == - 1 ) {
2013-12-12 20:19:33 +01:00
$self -> select_object ( undef );
2014-12-13 20:41:03 +01:00
$self -> refresh_canvases ;
2012-04-30 14:56:01 +02:00
}
}
sub list_item_selected {
my ( $self , $event ) = @_ ;
2014-03-25 15:30:56 +01:00
return if $PreventListEvents ;
2012-04-30 14:56:01 +02:00
my $obj_idx = $event -> GetIndex ;
2013-12-12 20:19:33 +01:00
$self -> select_object ( $obj_idx );
2014-12-13 20:41:03 +01:00
$self -> refresh_canvases ;
2012-04-30 14:56:01 +02:00
}
2012-10-24 22:44:08 +02:00
sub list_item_activated {
my ( $self , $event , $obj_idx ) = @_ ;
$obj_idx // = $event -> GetIndex ;
2015-01-14 23:21:54 +01:00
$self -> object_settings_dialog ( $obj_idx );
2013-08-25 15:45:22 +02:00
}
2014-04-25 14:54:08 +02:00
sub object_cut_dialog {
2013-08-25 15:45:22 +02:00
my $self = shift ;
my ( $obj_idx ) = @_ ;
if ( ! defined $obj_idx ) {
( $obj_idx , undef ) = $self -> selected_object ;
}
2013-09-16 10:18:42 +02:00
if ( ! $ Slic3r::GUI:: have_OpenGL ) {
Slic3r::GUI:: show_error ( $self , "Please install the OpenGL modules to use this feature (see build instructions)." );
return ;
}
2014-04-25 14:54:08 +02:00
my $dlg = Slic3r::GUI::Plater::ObjectCutDialog -> new ( $self ,
object => $self -> { objects }[ $obj_idx ],
model_object => $self -> { model } -> objects -> [ $obj_idx ],
2013-08-25 18:01:59 +02:00
);
$dlg -> ShowModal ;
2014-04-25 14:54:08 +02:00
if ( my @new_objects = $dlg -> NewModelObjects ) {
$self -> remove ( $obj_idx );
2014-06-14 22:36:49 +02:00
$self -> load_model_objects ( grep defined ( $_ ), @new_objects );
2014-04-25 17:14:39 +02:00
$self -> arrange ;
2014-04-25 14:54:08 +02:00
}
2013-08-25 18:01:59 +02:00
}
sub object_settings_dialog {
my $self = shift ;
my ( $obj_idx ) = @_ ;
if ( ! defined $obj_idx ) {
( $obj_idx , undef ) = $self -> selected_object ;
}
2014-03-23 16:45:55 +01:00
my $model_object = $self -> { model } -> objects -> [ $obj_idx ];
2013-08-25 18:01:59 +02:00
2013-08-25 22:24:43 +02:00
# validate config before opening the settings dialog because
# that dialog can't be closed if validation fails, but user
# can't fix any error which is outside that dialog
return unless $self -> validate_config ;
2013-08-25 18:01:59 +02:00
my $dlg = Slic3r::GUI::Plater::ObjectSettingsDialog -> new ( $self ,
2013-12-12 20:19:33 +01:00
object => $self -> { objects }[ $obj_idx ],
2014-03-23 16:45:55 +01:00
model_object => $model_object ,
2012-10-24 22:44:08 +02:00
);
2014-07-12 11:52:19 +02:00
$self -> pause_background_process ;
2012-10-24 22:44:08 +02:00
$dlg -> ShowModal ;
2014-03-23 16:45:55 +01:00
2014-12-12 22:43:04 +01:00
# update thumbnail since parts may have changed
if ( $dlg -> PartsChanged ) {
# recenter and re-align to Z = 0
$model_object -> center_around_origin ;
$self -> make_thumbnail ( $obj_idx );
}
2014-03-23 16:45:55 +01:00
# update print
if ( $dlg -> PartsChanged || $dlg -> PartSettingsChanged ) {
2014-07-01 16:40:56 +02:00
$self -> stop_background_process ;
2014-03-26 19:42:01 +01:00
$self -> { print } -> reload_object ( $obj_idx );
2014-06-13 20:36:45 +02:00
$self -> schedule_background_process ;
2014-07-01 16:40:56 +02:00
} else {
2014-07-12 11:52:19 +02:00
$self -> resume_background_process ;
2014-03-23 16:45:55 +01:00
}
2014-07-03 09:24:19 +02:00
}
2012-04-30 14:56:01 +02:00
sub object_list_changed {
my $self = shift ;
2013-08-25 18:01:59 +02:00
my $have_objects = @ { $self -> { objects }} ? 1 : 0 ;
my $method = $have_objects ? 'Enable' : 'Disable' ;
2012-05-01 00:30:46 +02:00
$self -> { "btn_$_" } -> $method
2015-01-03 23:25:55 +01:00
for grep $self -> { "btn_$_" }, qw(reset arrange export_gcode export_stl print send_gcode) ;
2013-08-25 18:01:59 +02:00
2015-01-04 19:29:34 +01:00
if ( $self -> { export_gcode_output_file } || $self -> { send_gcode_file }) {
$self -> { btn_export_gcode } -> Disable ;
2015-01-04 23:18:23 +01:00
$self -> { btn_print } -> Disable ;
2015-01-04 19:29:34 +01:00
$self -> { btn_send_gcode } -> Disable ;
}
2013-08-25 18:01:59 +02:00
if ( $self -> { htoolbar }) {
$self -> { htoolbar } -> EnableTool ( $_ , $have_objects )
for ( TB_RESET , TB_ARRANGE );
}
2012-04-30 14:56:01 +02:00
}
sub selection_changed {
my $self = shift ;
2013-12-12 20:19:33 +01:00
my ( $obj_idx , $object ) = $self -> selected_object ;
my $have_sel = defined $obj_idx ;
2012-04-30 14:56:01 +02:00
my $method = $have_sel ? 'Enable' : 'Disable' ;
2012-05-01 00:30:46 +02:00
$self -> { "btn_$_" } -> $method
2014-12-10 17:34:59 +01:00
for grep $self -> { "btn_$_" }, qw(remove increase decrease rotate45cw rotate45ccw changescale split cut settings) ;
2012-05-04 11:22:56 +02:00
if ( $self -> { htoolbar }) {
2012-08-02 21:49:26 +02:00
$self -> { htoolbar } -> EnableTool ( $_ , $have_sel )
2014-12-10 17:34:59 +01:00
for ( TB_REMOVE , TB_MORE , TB_FEWER , TB_45CW , TB_45CCW , TB_SCALE , TB_SPLIT , TB_CUT , TB_SETTINGS );
2013-08-25 12:22:05 +02:00
}
if ( $self -> { object_info_size }) { # have we already loaded the info pane?
if ( $have_sel ) {
2013-12-12 20:19:33 +01:00
my $model_object = $self -> { model } -> objects -> [ $obj_idx ];
my $model_instance = $model_object -> instances -> [ 0 ];
$self -> { object_info_size } -> SetLabel ( sprintf ( "%.2f x %.2f x %.2f" , @ { $model_object -> instance_bounding_box ( 0 ) -> size }));
$self -> { object_info_materials } -> SetLabel ( $model_object -> materials_count );
2013-08-25 12:22:05 +02:00
2013-12-12 20:19:33 +01:00
if ( my $stats = $model_object -> mesh_stats ) {
$self -> { object_info_volume } -> SetLabel ( sprintf ( '%.2f' , $stats -> { volume } * ( $model_instance -> scaling_factor ** 3 )));
$self -> { object_info_facets } -> SetLabel ( sprintf ( '%d (%d shells)' , $model_object -> facets_count , $stats -> { number_of_parts }));
2013-08-25 17:26:55 +02:00
if ( my $errors = sum ( @$stats { qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges) })) {
$self -> { object_info_manifold } -> SetLabel ( sprintf ( "Auto-repaired (%d errors)" , $errors ));
$self -> { object_info_manifold_warning_icon } -> Show ;
2013-08-25 18:01:59 +02:00
# we don't show normals_fixed because we never provide normals
# to admesh, so it generates normals for all facets
2013-08-25 17:26:55 +02:00
my $message = sprintf '%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d facets reversed, %d backwards edges' ,
@$stats { qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges) };
$self -> { object_info_manifold } -> SetToolTipString ( $message );
$self -> { object_info_manifold_warning_icon } -> SetToolTipString ( $message );
} else {
$self -> { object_info_manifold } -> SetLabel ( "Yes" );
}
} else {
$self -> { object_info_facets } -> SetLabel ( $object -> facets );
2013-08-25 12:22:05 +02:00
}
} else {
2013-08-25 17:26:55 +02:00
$self -> { "object_info_$_" } -> SetLabel ( "" ) for qw(size volume facets materials manifold) ;
$self -> { object_info_manifold_warning_icon } -> Hide ;
$self -> { object_info_manifold } -> SetToolTipString ( "" );
2013-08-25 12:22:05 +02:00
}
2013-08-25 17:26:55 +02:00
$self -> Layout ;
2012-05-04 11:22:56 +02:00
}
2014-06-14 18:58:56 +02:00
# prepagate the event to the frame (a custom Wx event would be cleaner)
2014-06-14 19:11:04 +02:00
$self -> GetFrame -> on_plater_selection_changed ( $have_sel );
2012-04-30 14:56:01 +02:00
}
2013-12-12 20:19:33 +01:00
sub select_object {
my ( $self , $obj_idx ) = @_ ;
$_ -> selected ( 0 ) for @ { $self -> { objects } };
if ( defined $obj_idx ) {
$self -> { objects } -> [ $obj_idx ] -> selected ( 1 );
2014-03-25 15:30:56 +01:00
# We use this flag to avoid circular event handling
# Select() happens to fire a wxEVT_LIST_ITEM_SELECTED on Windows,
# whose event handler calls this method again and again and again
$PreventListEvents = 1 ;
2013-12-12 20:19:33 +01:00
$self -> { list } -> Select ( $obj_idx , 1 );
2014-03-25 15:30:56 +01:00
$PreventListEvents = 0 ;
2013-12-12 20:19:33 +01:00
} else {
# TODO: deselect all in list
}
$self -> selection_changed ( 1 );
}
2012-08-29 19:37:27 +02:00
sub selected_object {
2012-04-30 14:56:01 +02:00
my $self = shift ;
2013-12-12 20:19:33 +01:00
my $obj_idx = first { $self -> { objects }[ $_ ] -> selected } 0 .. $# { $self -> { objects } };
return undef if ! defined $obj_idx ;
2012-08-29 19:37:27 +02:00
return ( $obj_idx , $self -> { objects }[ $obj_idx ]),
2012-04-30 14:56:01 +02:00
}
2014-12-13 20:41:03 +01:00
sub refresh_canvases {
my ( $self ) = @_ ;
$self -> { canvas } -> Refresh ;
$self -> { canvas3D } -> update if $self -> { canvas3D };
2015-01-18 19:36:47 +01:00
$self -> { preview3D } -> reload_print if $self -> { preview3D };
2014-12-13 20:41:03 +01:00
}
2013-08-25 22:24:43 +02:00
sub validate_config {
my $self = shift ;
eval {
2014-06-14 19:54:18 +02:00
$self -> GetFrame -> config -> validate ;
2013-08-25 22:24:43 +02:00
};
return 0 if Slic3r::GUI:: catch_error ( $self );
return 1 ;
}
2012-05-04 12:56:15 +02:00
sub statusbar {
my $self = shift ;
2014-06-14 19:54:18 +02:00
return $self -> GetFrame -> { statusbar };
2012-05-04 12:56:15 +02:00
}
2014-06-14 21:36:28 +02:00
sub object_menu {
my ( $self ) = @_ ;
my $frame = $self -> GetFrame ;
my $menu = Wx::Menu -> new ;
$frame -> _append_menu_item ( $menu , "Delete\tCtrl+Del" , 'Remove the selected object' , sub {
$self -> remove ;
2015-05-25 22:37:04 +02:00
}, undef , 'brick_delete.png' );
2014-06-14 21:36:28 +02:00
$frame -> _append_menu_item ( $menu , "Increase copies\tCtrl++" , 'Place one more copy of the selected object' , sub {
$self -> increase ;
2015-05-25 22:37:04 +02:00
}, undef , 'add.png' );
2014-06-14 21:36:28 +02:00
$frame -> _append_menu_item ( $menu , "Decrease copies\tCtrl+-" , 'Remove one copy of the selected object' , sub {
$self -> decrease ;
2015-05-25 22:37:04 +02:00
}, undef , 'delete.png' );
2015-01-14 23:19:13 +01:00
$frame -> _append_menu_item ( $menu , "Set number of copies…" , 'Change the number of copies of the selected object' , sub {
$self -> set_number_of_copies ;
2015-05-25 22:37:04 +02:00
}, undef , 'textfield.png' );
2014-06-14 21:36:28 +02:00
$menu -> AppendSeparator ();
$frame -> _append_menu_item ( $menu , "Rotate 45° clockwise" , 'Rotate the selected object by 45° clockwise' , sub {
$self -> rotate ( - 45 );
2015-05-25 22:37:04 +02:00
}, undef , 'arrow_rotate_clockwise.png' );
2014-06-14 21:36:28 +02:00
$frame -> _append_menu_item ( $menu , "Rotate 45° counter-clockwise" , 'Rotate the selected object by 45° counter-clockwise' , sub {
$self -> rotate ( + 45 );
2015-05-25 22:37:04 +02:00
}, undef , 'arrow_rotate_anticlockwise.png' );
2014-06-14 21:36:28 +02:00
my $rotateMenu = Wx::Menu -> new ;
2015-05-25 22:37:04 +02:00
my $rotateMenuItem = $menu -> AppendSubMenu ( $rotateMenu , "Rotate" , 'Rotate the selected object by an arbitrary angle' );
$frame -> _set_menu_item_icon ( $rotateMenuItem , 'textfield.png' );
2014-06-14 21:36:28 +02:00
$frame -> _append_menu_item ( $rotateMenu , "Around X axis…" , 'Rotate the selected object by an arbitrary angle around X axis' , sub {
$self -> rotate ( undef , X );
2015-12-05 19:01:17 +01:00
}, undef , 'bullet_red.png' );
2014-06-14 21:36:28 +02:00
$frame -> _append_menu_item ( $rotateMenu , "Around Y axis…" , 'Rotate the selected object by an arbitrary angle around Y axis' , sub {
$self -> rotate ( undef , Y );
2015-12-05 19:01:17 +01:00
}, undef , 'bullet_green.png' );
2014-06-14 21:36:28 +02:00
$frame -> _append_menu_item ( $rotateMenu , "Around Z axis…" , 'Rotate the selected object by an arbitrary angle around Z axis' , sub {
$self -> rotate ( undef , Z );
2015-12-05 19:01:17 +01:00
}, undef , 'bullet_blue.png' );
2014-06-14 21:36:28 +02:00
2015-11-04 23:11:30 +01:00
my $mirrorMenu = Wx::Menu -> new ;
my $mirrorMenuItem = $menu -> AppendSubMenu ( $mirrorMenu , "Mirror" , 'Mirror the selected object' );
$frame -> _set_menu_item_icon ( $mirrorMenuItem , 'shape_flip_horizontal.png' );
$frame -> _append_menu_item ( $mirrorMenu , "Along X axis…" , 'Mirror the selected object along the X axis' , sub {
$self -> mirror ( X );
2015-12-05 19:01:17 +01:00
}, undef , 'bullet_red.png' );
2015-11-04 23:11:30 +01:00
$frame -> _append_menu_item ( $mirrorMenu , "Along Y axis…" , 'Mirror the selected object along the Y axis' , sub {
$self -> mirror ( Y );
2015-12-05 19:01:17 +01:00
}, undef , 'bullet_green.png' );
2015-11-04 23:11:30 +01:00
$frame -> _append_menu_item ( $mirrorMenu , "Along Z axis…" , 'Mirror the selected object along the Z axis' , sub {
$self -> mirror ( Z );
2015-12-05 19:01:17 +01:00
}, undef , 'bullet_blue.png' );
2014-06-14 21:36:28 +02:00
2014-06-17 00:50:44 +02:00
my $scaleMenu = Wx::Menu -> new ;
2015-05-25 22:37:04 +02:00
my $scaleMenuItem = $menu -> AppendSubMenu ( $scaleMenu , "Scale" , 'Scale the selected object along a single axis' );
$frame -> _set_menu_item_icon ( $scaleMenuItem , 'arrow_out.png' );
2014-06-17 00:50:44 +02:00
$frame -> _append_menu_item ( $scaleMenu , "Uniformly…" , 'Scale the selected object along the XYZ axes' , sub {
$self -> changescale ( undef );
2014-06-14 21:36:28 +02:00
});
2014-06-17 00:50:44 +02:00
$frame -> _append_menu_item ( $scaleMenu , "Along X axis…" , 'Scale the selected object along the X axis' , sub {
$self -> changescale ( X );
2015-12-05 19:01:17 +01:00
}, undef , 'bullet_red.png' );
2014-06-17 00:50:44 +02:00
$frame -> _append_menu_item ( $scaleMenu , "Along Y axis…" , 'Scale the selected object along the Y axis' , sub {
$self -> changescale ( Y );
2015-12-05 19:01:17 +01:00
}, undef , 'bullet_green.png' );
2014-06-17 00:50:44 +02:00
$frame -> _append_menu_item ( $scaleMenu , "Along Z axis…" , 'Scale the selected object along the Z axis' , sub {
$self -> changescale ( Z );
2015-12-05 19:01:17 +01:00
}, undef , 'bullet_blue.png' );
2014-06-17 00:50:44 +02:00
2014-06-14 21:36:28 +02:00
$frame -> _append_menu_item ( $menu , "Split" , 'Split the selected object into individual parts' , sub {
$self -> split_object ;
2015-05-25 22:37:04 +02:00
}, undef , 'shape_ungroup.png' );
2014-07-13 12:10:34 +02:00
$frame -> _append_menu_item ( $menu , "Cut…" , 'Open the 3D cutting tool' , sub {
2014-06-14 21:36:28 +02:00
$self -> object_cut_dialog ;
2015-05-25 22:37:04 +02:00
}, undef , 'package.png' );
2014-06-14 21:36:28 +02:00
$menu -> AppendSeparator ();
$frame -> _append_menu_item ( $menu , "Settings…" , 'Open the object editor dialog' , sub {
$self -> object_settings_dialog ;
2015-05-25 22:37:04 +02:00
}, undef , 'cog.png' );
2015-01-03 15:48:53 +01:00
$menu -> AppendSeparator ();
$frame -> _append_menu_item ( $menu , "Export object as STL…" , 'Export this single object as STL file' , sub {
$self -> export_object_stl ;
2015-05-25 22:37:04 +02:00
}, undef , 'brick_go.png' );
2014-06-14 21:36:28 +02:00
return $menu ;
}
2012-05-04 10:15:33 +02:00
package Slic3r::GUI::Plater::DropTarget ;
2012-04-30 14:56:01 +02:00
use Wx::DND ;
use base 'Wx::FileDropTarget' ;
sub new {
my $class = shift ;
my ( $window ) = @_ ;
my $self = $class -> SUPER:: new ;
$self -> { window } = $window ;
return $self ;
}
sub OnDropFiles {
my $self = shift ;
my ( $x , $y , $filenames ) = @_ ;
2012-07-21 14:45:45 +02:00
# stop scalars leaking on older perl
# https://rt.perl.org/rt3/Public/Bug/Display.html?id=70602
@_ = ();
2013-03-19 12:24:39 +01:00
# only accept STL, OBJ and AMF files
return 0 if grep ! /\.(?:stl|obj|amf(?:\.xml)?)$/i , @$filenames ;
2012-04-30 14:56:01 +02:00
$self -> { window } -> load_file ( $_ ) for @$filenames ;
}
2012-08-29 19:37:27 +02:00
package Slic3r::GUI::Plater::Object ;
use Moo ;
2013-07-13 20:34:57 +02:00
use List::Util qw(first) ;
2013-06-07 23:16:02 +02:00
use Slic3r::Geometry qw(X Y Z MIN MAX deg2rad) ;
2012-08-29 19:37:27 +02:00
has 'name' => ( is => 'rw' , required => 1 );
2013-12-12 20:19:33 +01:00
has 'thumbnail' => ( is => 'rw' ); # ExPolygon::Collection in scaled model units with no transforms
2013-06-12 16:53:19 +02:00
has 'transformed_thumbnail' => ( is => 'rw' );
2013-12-12 20:19:33 +01:00
has 'instance_thumbnails' => ( is => 'ro' , default => sub { [] }); # array of ExPolygon::Collection objects, each one representing the actual placed thumbnail of each instance in pixel units
has 'selected' => ( is => 'rw' , default => sub { 0 });
2012-08-29 19:37:27 +02:00
sub make_thumbnail {
2013-12-12 20:19:33 +01:00
my ( $self , $model , $obj_idx ) = @_ ;
2012-08-29 19:37:27 +02:00
2014-06-14 20:52:21 +02:00
# make method idempotent
$self -> thumbnail -> clear ;
2013-12-12 20:19:33 +01:00
2014-06-14 20:52:21 +02:00
my $mesh = $model -> objects -> [ $obj_idx ] -> raw_mesh ;
2013-12-12 20:19:33 +01:00
if ( $mesh -> facets_count <= 5000 ) {
2013-08-25 22:33:50 +02:00
# remove polygons with area <= 1mm
my $area_threshold = Slic3r::Geometry:: scale 1 ;
2013-11-12 14:30:13 +01:00
$self -> thumbnail -> append (
2013-08-25 22:33:50 +02:00
grep $_ -> area >= $area_threshold ,
2013-12-12 20:19:33 +01:00
@ { $mesh -> horizontal_projection }, # horizontal_projection returns scaled expolygons
2013-08-25 22:33:50 +02:00
);
2013-11-21 20:25:24 +01:00
$self -> thumbnail -> simplify ( 0.5 );
2013-08-05 20:48:09 +02:00
} else {
2013-12-12 20:19:33 +01:00
my $convex_hull = Slic3r::ExPolygon -> new ( $mesh -> convex_hull );
2013-11-12 14:30:13 +01:00
$self -> thumbnail -> append ( $convex_hull );
2013-08-05 20:48:09 +02:00
}
2013-06-07 23:16:02 +02:00
2013-11-12 14:30:13 +01:00
return $self -> thumbnail ;
2012-08-29 19:37:27 +02:00
}
2013-12-12 20:19:33 +01:00
sub transform_thumbnail {
my ( $self , $model , $obj_idx ) = @_ ;
2012-08-29 19:37:27 +02:00
2013-07-14 00:38:01 +02:00
return unless defined $self -> thumbnail ;
2013-12-12 20:19:33 +01:00
my $model_object = $model -> objects -> [ $obj_idx ];
my $model_instance = $model_object -> instances -> [ 0 ];
# the order of these transformations MUST be the same everywhere, including
# in Slic3r::Print->add_model_object()
my $t = $self -> thumbnail -> clone ;
2015-05-03 18:40:00 +02:00
$t -> rotate ( $model_instance -> rotation , Slic3r::Point -> new ( 0 , 0 ));
2013-12-12 20:19:33 +01:00
$t -> scale ( $model_instance -> scaling_factor );
2012-08-29 19:37:27 +02:00
2013-06-12 16:53:19 +02:00
$self -> transformed_thumbnail ( $t );
2012-08-29 19:37:27 +02:00
}
2012-04-30 14:56:01 +02:00
1 ;