2016-09-14 11:22:41 +02:00
# The "Plater" tab. It contains the "3D", "2D", "Preview" and "Layers" subtabs.
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) ;
2016-12-17 00:26:57 +01:00
use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale deg2rad rad2deg) ;
2016-12-12 18:02:24 +01:00
use LWP::UserAgent ;
2012-05-19 21:13:10 +02:00
use threads::shared qw(shared_clone) ;
2017-05-24 15:20:20 +02:00
use Wx qw(:button :colour :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc
2016-12-14 15:36:18 +01:00
:panel :sizer :toolbar :window wxTheApp :notebook :combobox wxNullBitmap) ;
2016-12-12 18:02:24 +01:00
use Wx::Event qw(EVT_BUTTON EVT_TOGGLEBUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED
2017-05-24 15:20:20 +02:00
EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_LEFT_DOWN 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 ;
2016-12-12 18:02:24 +01:00
use constant TB_LAYER_EDITING => & 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 ;
2016-10-25 13:24:42 +02:00
# Emitted from the worker thread when the G-code export is finished.
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(
2017-02-10 10:21:50 +01:00
bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height
2015-01-03 23:25:55 +01:00
serial_port serial_speed octoprint_host octoprint_apikey
2017-05-17 16:53:40 +02:00
nozzle_diameter single_extruder_multi_material
2017-05-24 15:20:20 +02:00
wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width wipe_tower_per_color_wipe extruder_colour filament_colour
2012-07-27 21:13:03 +02:00
) );
2017-02-07 18:28:53 +01:00
# C++ Slic3r::Model with Perl extensions in Slic3r/Model.pm
2013-12-12 20:19:33 +01:00
$self -> { model } = Slic3r::Model -> new ;
2017-02-07 18:28:53 +01:00
# C++ Slic3r::Print with Perl extensions in Slic3r/Print.pm
2013-12-13 14:02:01 +01:00
$self -> { print } = Slic3r::Print -> new ;
2016-09-13 11:24:55 +02:00
# List of Perl objects Slic3r::GUI::Plater::Object, representing a 2D preview of the platter.
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 ) = @_ ;
2017-05-17 16:53:40 +02:00
# Ignore the special objects (the wipe tower proxy and such).
$self -> select_object (( defined ( $obj_idx ) && $obj_idx < 1000 ) ? $obj_idx : undef );
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 ) {
2016-12-12 18:02:24 +01:00
$self -> { canvas3D } = Slic3r::GUI::Plater:: 3 D -> new ( $self -> { preview_notebook }, $self -> { objects }, $self -> { model }, $self -> { print }, $self -> { config });
2015-01-17 10:53:01 +01:00
$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 );
2017-05-19 21:48:32 +02:00
$self -> { canvas3D } -> set_on_wipe_tower_moved ( sub {
my ( $new_pos_3f ) = @_ ;
my $cfg = Slic3r::Config -> new ;
$cfg -> set ( 'wipe_tower_x' , $new_pos_3f -> x );
$cfg -> set ( 'wipe_tower_y' , $new_pos_3f -> y );
$self -> GetFrame -> { options_tabs }{ print } -> load_config ( $cfg );
});
2017-02-09 14:56:13 +01:00
$self -> { canvas3D } -> set_on_model_update ( sub {
if ( $ Slic3r::GUI:: Settings -> { _ }{ background_processing }) {
$self -> { apply_config_timer } -> Stop if defined $self -> { apply_config_timer };
$self -> async_apply_config ();
2017-02-15 16:02:54 +01:00
} else {
# Hide the print info box, it is no more valid.
$self -> { "print_info_box_show" } -> ( 0 );
2017-02-09 14:56:13 +01:00
}
});
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 ) {
2017-05-24 15:20:20 +02:00
$self -> { preview3D } = Slic3r::GUI::Plater:: 3 DPreview -> new ( $self -> { preview_notebook }, $self -> { print }, $self -> { config });
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 );
2016-04-09 19:10:57 +02:00
$self -> { htoolbar } -> AddTool ( TB_ADD , "Add…" , Wx::Bitmap -> new ( $ Slic3r:: var -> ( "brick_add.png" ), wxBITMAP_TYPE_PNG ), '' );
$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 ), '' );
$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 ;
2016-04-09 19:10:57 +02:00
$self -> { htoolbar } -> AddTool ( TB_MORE , "More" , Wx::Bitmap -> new ( $ Slic3r:: var -> ( "add.png" ), wxBITMAP_TYPE_PNG ), '' );
$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 ;
2016-04-09 19:10:57 +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 ), '' );
$self -> { htoolbar } -> AddTool ( TB_SCALE , "Scale…" , Wx::Bitmap -> new ( $ Slic3r:: var -> ( "arrow_out.png" ), wxBITMAP_TYPE_PNG ), '' );
$self -> { htoolbar } -> AddTool ( TB_SPLIT , "Split" , Wx::Bitmap -> new ( $ Slic3r:: var -> ( "shape_ungroup.png" ), wxBITMAP_TYPE_PNG ), '' );
$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 ;
2016-04-09 19:10:57 +02:00
$self -> { htoolbar } -> AddTool ( TB_SETTINGS , "Settings…" , Wx::Bitmap -> new ( $ Slic3r:: var -> ( "cog.png" ), wxBITMAP_TYPE_PNG ), '' );
2017-02-19 19:44:05 +01:00
$self -> { htoolbar } -> AddTool ( TB_LAYER_EDITING , 'Layer Editing' , Wx::Bitmap -> new ( $ Slic3r:: var -> ( "variable_layer_height.png" ), wxBITMAP_TYPE_PNG ), wxNullBitmap , 1 , 0 , 'Layer Editing' );
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…" ,
2016-12-12 18:02:24 +01:00
layer_editing => "Layer editing" ,
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_$_" });
}
2017-02-03 15:53:31 +01:00
$self -> { "btn_layer_editing" } = Wx::ToggleButton -> new ( $self , - 1 , $tbar_buttons { 'layer_editing' }, wxDefaultPosition , wxDefaultSize , wxBU_EXACTFIT );
$self -> { btoolbar } -> Add ( $self -> { "btn_layer_editing" });
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 );
2016-10-25 13:24:42 +02:00
$self -> { btn_reslice } = Wx::Button -> new ( $self , - 1 , "Slice now" , 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
2016-10-25 13:24:42 +02:00
reslice reslice.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 ) {
2016-04-09 19:10:57 +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 ());
});
2016-10-25 13:24:42 +02:00
EVT_BUTTON ( $self , $self -> { btn_reslice }, \& reslice );
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 ; });
2017-03-23 11:53:59 +01:00
EVT_TOOL ( $self , TB_45CW , sub { $_ [ 0 ] -> rotate ( - 45 , Z , 'relative' ) });
EVT_TOOL ( $self , TB_45CCW , sub { $_ [ 0 ] -> rotate ( 45 , Z , 'relative' ) });
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 });
2016-12-14 15:09:12 +01:00
EVT_TOOL ( $self , TB_LAYER_EDITING , sub {
my $state = $self -> { canvas3D } -> layer_editing_enabled ;
$self -> { htoolbar } -> ToggleTool ( TB_LAYER_EDITING , ! $state );
$self -> on_layer_editing_toggled ( ! $state );
});
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 ; });
2017-03-23 11:53:59 +01:00
EVT_BUTTON ( $self , $self -> { btn_rotate45cw }, sub { $_ [ 0 ] -> rotate ( - 45 , Z , 'relative' ) });
EVT_BUTTON ( $self , $self -> { btn_rotate45ccw }, sub { $_ [ 0 ] -> rotate ( 45 , Z , 'relative' ) });
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 });
2016-12-12 18:02:24 +01:00
EVT_TOGGLEBUTTON ( $self , $self -> { btn_layer_editing }, sub { $self -> on_layer_editing_toggled ( $self -> { btn_layer_editing } -> GetValue ); });
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' ,
);
2016-10-24 16:07:36 +02:00
# UI Combo boxes for a print, multiple filaments, and a printer.
# Initially a single filament combo box is created, but the number of combo boxes for the filament selection may increase,
# once a printer preset with multiple extruders is activated.
# $self->{preset_choosers}{$group}[$idx]
2013-01-03 15:49:20 +01:00
$self -> { preset_choosers } = {};
2016-10-24 16:07:36 +02:00
# Boolean indicating whether the '- default -' preset is shown by the combo box.
$self -> { preset_choosers_default_suppressed } = {};
2013-01-03 15:49:20 +01:00
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 );
2017-05-24 15:20:20 +02:00
EVT_LEFT_DOWN ( $choice , sub { $self -> filament_color_box_lmouse_down ( 0 , @_ ); } );
2013-01-03 15:49:20 +01:00
$self -> { preset_choosers }{ $group } = [ $choice ];
2016-10-24 16:07:36 +02:00
$self -> { preset_choosers_default_suppressed }{ $group } = 0 ;
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' ) {
2016-04-09 19:10:57 +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
}
}
2017-01-20 21:21:57 -06:00
my $print_info_sizer ;
{
my $box = Wx::StaticBox -> new ( $self , - 1 , "Sliced Info" );
$print_info_sizer = Wx::StaticBoxSizer -> new ( $box , wxVERTICAL );
$print_info_sizer -> SetMinSize ([ 350 , - 1 ]);
my $grid_sizer = Wx::FlexGridSizer -> new ( 2 , 2 , 5 , 5 );
$grid_sizer -> SetFlexibleDirection ( wxHORIZONTAL );
$grid_sizer -> AddGrowableCol ( 1 , 1 );
$grid_sizer -> AddGrowableCol ( 3 , 1 );
$print_info_sizer -> Add ( $grid_sizer , 0 , wxEXPAND );
my @info = (
fil_mm3 => "Used Filament (mm^3)" ,
fil_g => "Used Filament (g)" ,
cost => "Cost" ,
);
while ( my $field = shift @info ) {
my $label = shift @info ;
my $text = Wx::StaticText -> new ( $self , - 1 , "$label:" , wxDefaultPosition , wxDefaultSize , wxALIGN_RIGHT );
$text -> SetFont ( $ Slic3r::GUI:: small_font );
$grid_sizer -> Add ( $text , 0 );
$self -> { "print_info_$field" } = Wx::StaticText -> new ( $self , - 1 , "" , wxDefaultPosition , wxDefaultSize , wxALIGN_LEFT );
$self -> { "print_info_$field" } -> SetFont ( $ Slic3r::GUI:: small_font );
$grid_sizer -> Add ( $self -> { "print_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 );
2016-10-25 13:24:42 +02:00
$self -> { buttons_sizer } = $buttons_sizer ;
2014-12-25 18:50:02 +01:00
$buttons_sizer -> AddStretchSpacer ( 1 );
$buttons_sizer -> Add ( $self -> { btn_export_stl }, 0 , wxALIGN_RIGHT , 0 );
2016-10-25 13:24:42 +02:00
$buttons_sizer -> Add ( $self -> { btn_reslice }, 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 );
2017-01-20 21:21:57 -06:00
$right_sizer -> Add ( $print_info_sizer , 0 , wxEXPAND , 0 );
2017-02-15 16:02:54 +01:00
# Callback for showing / hiding the print info box.
$self -> { "print_info_box_show" } = sub {
if ( $right_sizer -> IsShown ( 4 ) != $_ [ 0 ]) {
$right_sizer -> Show ( 4 , $_ [ 0 ]);
$self -> Layout
}
};
# Show the box initially, let it be shown after the slicing is finished.
$self -> { "print_info_box_show" } -> ( 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 );
}
2016-10-25 13:24:42 +02:00
$self -> update_ui_from_settings ();
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 ) = @_ ;
2016-10-24 16:07:36 +02:00
# If user changed a filament preset and the selected machine is equipped with multiple extruders,
# there are multiple filament selection combo boxes shown at the platter. In that case
# don't propagate the filament selection changes to the tab.
my $default_suppressed = $self -> { preset_choosers_default_suppressed }{ $group };
2012-10-24 20:24:11 +02:00
if ( $group eq 'filament' && @ { $self -> { preset_choosers }{ filament }} > 1 ) {
2016-10-24 16:07:36 +02:00
# Indices of the filaments selected.
2012-10-24 20:24:11 +02:00
my @filament_presets = $self -> filament_presets ;
2016-10-24 16:07:36 +02:00
$ Slic3r::GUI:: Settings -> { presets }{ filament } = $choice -> GetString ( $filament_presets [ 0 ] - $default_suppressed ) . ".ini" ;
$ Slic3r::GUI:: Settings -> { presets }{ "filament_${_}" } = $choice -> GetString ( $filament_presets [ $_ ] - $default_suppressed )
2012-10-24 20:24:11 +02:00
for 1 .. $#filament_presets ;
2014-06-14 19:59:59 +02:00
wxTheApp -> save_settings ;
2017-05-24 15:20:20 +02:00
$self -> update_filament_colors_preview ( $choice );
} else {
# call GetSelection() in scalar context as it's context-aware
$self -> { on_select_preset } -> ( $group , scalar ( $choice -> GetSelection ) + $default_suppressed )
if $self -> { on_select_preset };
}
2014-07-01 16:40:56 +02:00
# 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
}
2016-12-12 18:02:24 +01:00
sub on_layer_editing_toggled {
my ( $self , $new_state ) = @_ ;
$self -> { canvas3D } -> layer_editing_enabled ( $new_state );
2017-02-19 18:01:03 +01:00
if ( $new_state && ! $self -> { canvas3D } -> layer_editing_enabled ) {
# Initialization of the OpenGL shaders failed. Disable the tool.
if ( $self -> { htoolbar }) {
$self -> { htoolbar } -> EnableTool ( TB_LAYER_EDITING , 0 );
$self -> { htoolbar } -> ToggleTool ( TB_LAYER_EDITING , 0 );
} else {
$self -> { "btn_layer_editing" } -> Disable ;
$self -> { "btn_layer_editing" } -> SetValue ( 0 );
}
}
2017-05-17 16:53:40 +02:00
$self -> { canvas3D } -> Refresh ;
$self -> { canvas3D } -> Update ;
2016-12-12 18:02:24 +01: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
}
2016-10-25 13:24:42 +02:00
# Called after the Preferences dialog is closed and the program settings are saved.
# Update the UI based on the current preferences.
sub update_ui_from_settings
{
my ( $self ) = @_ ;
if ( defined ( $self -> { btn_reslice }) && $self -> { buttons_sizer } -> IsShown ( $self -> { btn_reslice }) != ( ! $ Slic3r::GUI:: Settings -> { _ }{ background_processing })) {
$self -> { buttons_sizer } -> Show ( $self -> { btn_reslice }, ! $ Slic3r::GUI:: Settings -> { _ }{ background_processing });
$self -> { buttons_sizer } -> Layout ;
}
}
2017-02-07 18:28:53 +01:00
# Update preset combo boxes (Print settings, Filament, Printer) from their respective tabs.
2016-10-24 16:07:36 +02:00
# Called by
# Slic3r::GUI::Tab::Print::_on_presets_changed
# Slic3r::GUI::Tab::Filament::_on_presets_changed
# Slic3r::GUI::Tab::Printer::_on_presets_changed
# when the presets are loaded or the user selects another preset.
# For Print settings and Printer, synchronize the selection index with their tabs.
# For Filament, synchronize the selection index for a single extruder printer only, otherwise keep the selection.
2012-08-07 18:44:47 +02:00
sub update_presets {
my $self = shift ;
2016-10-24 16:07:36 +02:00
# $presets: one of qw(print filament printer)
# $selected: index of the selected preset in the array. This may not correspond
# with the index of selection in the UI element, where not all items are displayed.
my ( $group , $presets , $default_suppressed , $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 } };
2017-05-24 15:20:20 +02:00
my $choice_idx = 0 ;
2015-06-01 23:52:15 +02:00
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
2016-10-24 16:07:36 +02:00
$selected = $choice -> GetSelection + $self -> { preset_choosers_default_suppressed }{ $group };
2015-06-01 23:52:15 +02:00
$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 ) {
2016-10-24 16:07:36 +02:00
next if ( $preset -> default && $default_suppressed );
2015-05-26 02:01:43 +02:00
my $bitmap ;
if ( $group eq 'filament' ) {
2017-05-24 15:20:20 +02:00
$bitmap = Wx::Bitmap -> new ( $ Slic3r:: var -> ( "spool.png" ), wxBITMAP_TYPE_PNG );
2015-05-26 02:01:43 +02:00
} elsif ( $group eq 'print' ) {
2016-04-09 19:10:57 +02:00
$bitmap = Wx::Bitmap -> new ( $ Slic3r:: var -> ( "cog.png" ), wxBITMAP_TYPE_PNG );
2015-05-26 02:01:43 +02:00
} elsif ( $group eq 'printer' ) {
2016-04-09 19:10:57 +02:00
$bitmap = Wx::Bitmap -> new ( $ Slic3r:: var -> ( "printer_empty.png" ), wxBITMAP_TYPE_PNG );
2015-05-26 02:01:43 +02:00
}
$choice -> AppendString ( $preset -> name , $bitmap );
}
2015-06-01 23:34:04 +02:00
2015-06-01 23:52:15 +02:00
if ( $selected <= $#$presets ) {
2016-10-24 16:07:36 +02:00
my $idx = $selected - $default_suppressed ;
if ( $idx >= 0 ) {
if ( $is_dirty ) {
$choice -> SetString ( $idx , $choice -> GetString ( $idx ) . " (modified)" );
}
# call SetSelection() only after SetString() otherwise the new string
# won't be picked up as the visible string
$choice -> SetSelection ( $idx );
2015-06-01 23:34:04 +02:00
}
}
2017-05-24 15:20:20 +02:00
$choice_idx += 1 ;
2012-08-07 18:44:47 +02:00
}
2016-10-24 16:07:36 +02:00
$self -> { preset_choosers_default_suppressed }{ $group } = $default_suppressed ;
2017-05-24 15:20:20 +02:00
$self -> update_filament_colors_preview ;
}
# Update the color icon in front of each filament selection on the platter.
# If the extruder has a preview color assigned, apply the extruder color to the active selection.
# Always apply the filament color to the non-active selections.
sub update_filament_colors_preview {
my ( $self , $extruder_idx ) = shift ;
my @choosers = @ { $self -> { preset_choosers }{ filament }};
if ( ref $extruder_idx ) {
# $extruder_idx is the chooser.
foreach my $chooser ( @choosers ) {
if ( $extruder_idx == $chooser ) {
$extruder_idx = $chooser ;
last ;
}
}
}
my @extruder_colors = @ { $self -> { config } -> extruder_colour };
my @extruder_list ;
if ( defined $extruder_idx ) {
@extruder_list = ( $extruder_idx );
} else {
# Collect extruder indices.
@extruder_list = ( 0 .. $#extruder_colors );
}
my $filament_tab = $self -> GetFrame -> { options_tabs }{ filament };
my $presets = $filament_tab -> { presets };
my $default_suppressed = $filament_tab -> { default_suppressed };
foreach my $extruder_idx ( @extruder_list ) {
my $chooser = $choosers [ $extruder_idx ];
my $extruder_color = $self -> { config } -> extruder_colour -> [ $extruder_idx ];
my $preset_idx = 0 ;
my $selection_idx = $chooser -> GetSelection ;
foreach my $preset ( @$presets ) {
my $bitmap ;
if ( $preset -> default ) {
next if $default_suppressed ;
} else {
# Assign an extruder color to the selected item if the extruder color is defined.
my $filament_rgb = $preset -> config ([ 'filament_colour' ]) -> filament_colour -> [ 0 ];
my $extruder_rgb = ( $preset_idx == $selection_idx && $extruder_color =~ m/^#[[:xdigit:]]{6}/ ) ? $extruder_color : $filament_rgb ;
$filament_rgb =~ s/^#// ;
$extruder_rgb =~ s/^#// ;
my $image = Wx::Image -> new ( 24 , 16 );
if ( $filament_rgb ne $extruder_rgb ) {
my @rgb = unpack 'C*' , pack 'H*' , $extruder_rgb ;
$image -> SetRGB ( Wx::Rect -> new ( 0 , 0 , 16 , 16 ), @rgb );
@rgb = unpack 'C*' , pack 'H*' , $filament_rgb ;
$image -> SetRGB ( Wx::Rect -> new ( 16 , 0 , 8 , 16 ), @rgb );
} else {
my @rgb = unpack 'C*' , pack 'H*' , $filament_rgb ;
$image -> SetRGB ( Wx::Rect -> new ( 0 , 0 , 24 , 16 ), @rgb );
}
$bitmap = Wx::Bitmap -> new ( $image );
}
$chooser -> SetItemBitmap ( $preset_idx , $bitmap ) if $bitmap ;
$preset_idx += 1 ;
}
}
2012-08-07 18:44:47 +02:00
}
2016-10-24 16:07:36 +02:00
# Return a vector of indices of filaments selected by the $self->{preset_choosers}{filament} combo boxes.
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
2016-10-24 16:07:36 +02:00
return map scalar ( $_ -> GetSelection ) + $self -> { preset_choosers_default_suppressed }{ filament }, @ { $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 $@ ;
2016-12-17 21:47:45 +01:00
my @obj_idx = ();
2014-03-25 19:06:51 +01:00
if ( defined $model ) {
2016-03-19 22:22:11 +01:00
if ( $model -> looks_like_multipart_object ) {
my $dialog = Wx::MessageDialog -> new ( $self ,
"This file contains several objects positioned at multiple heights. "
. "Instead of considering them as multiple objects, should I consider\n"
. "this file as a single object having multiple parts?\n" ,
'Multi-part object detected' , wxICON_WARNING | wxYES | wxNO );
if ( $dialog -> ShowModal () == wxID_YES ) {
$model -> convert_multipart_object ;
}
}
2016-12-17 21:47:45 +01:00
@obj_idx = $self -> load_model_objects ( @ { $model -> objects });
2014-03-25 19:06:51 +01:00
$self -> statusbar -> SetStatusText ( "Loaded " . basename ( $input_file ));
}
2012-08-29 19:37:27 +02:00
$process_dialog -> Destroy ;
2016-12-17 21:47:45 +01:00
return @obj_idx ;
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 );
2017-02-09 14:56:13 +01:00
my $object_name = $model_object -> name ;
$object_name = basename ( $model_object -> input_file ) if ( $object_name eq '' );
push @ { $self -> { objects } }, Slic3r::GUI::Plater::Object -> new ( name => $object_name );
2014-01-05 14:04:32 +01:00
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 ;
2016-12-17 21:47:45 +01:00
return @obj_idx ;
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 );
}
}
2017-03-23 14:32:19 +01:00
sub _get_number_from_user {
my ( $self , $title , $prompt_message , $error_message , $default , $only_positive ) = @_ ;
for (;;) {
my $value = Wx:: GetTextFromUser ( $prompt_message , $title , $default , $self );
# Accept both dashes and dots as a decimal separator.
$value =~ s/,/./ ;
# If scaling value is being entered, remove the trailing percent sign.
$value =~ s/%$// if $only_positive ;
# User canceled the selection, return undef.
return if $value eq '' ;
# Validate a numeric value.
return $value if ( $value =~ /^-?\d*(?:\.\d*)?$/ ) && ( ! $only_positive || $value > 0 );
Wx:: MessageBox (
$error_message .
(( $only_positive && $value <= 0 ) ?
": $value\nNon-positive value." :
": $value\nNot a numeric value." ),
"Slic3r Error" , wxOK | wxICON_EXCLAMATION , $self );
$default = $value ;
}
}
2012-04-30 14:56:01 +02:00
sub rotate {
2017-03-23 14:32:19 +01:00
my ( $self , $angle , $axis , $relative_key ) = @_ ;
2017-03-23 11:53:59 +01:00
$relative_key // = 'absolute' ; # relative or absolute coordinates
$axis // = Z ; # angle is in degrees
my $relative = $relative_key eq 'relative' ;
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' ;
2016-12-17 00:26:57 +01:00
my $default = $axis == Z ? rad2deg ( $model_instance -> rotation ) : 0 ;
2017-03-23 14:32:19 +01:00
$angle = $self -> _get_number_from_user ( "Enter the rotation angle:" , "Rotate around $axis_name axis" , "Invalid rotation angle entered" , $default );
return if $angle eq '' ;
2012-04-30 22:14:27 +02:00
}
2014-07-01 16:40:56 +02:00
$self -> stop_background_process ;
if ( $axis == Z ) {
2016-12-17 00:26:57 +01:00
my $new_angle = deg2rad ( $angle );
2017-03-23 11:53:59 +01:00
$_ -> set_rotation (( $relative ? $_ -> rotation : 0 . ) + $new_angle ) for @ { $model_object -> instances };
2014-07-01 16:40:56 +02:00
$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 {
2015-12-05 19:37:57 +01:00
my ( $self , $axis , $tosize ) = @_ ;
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 ;
2015-12-05 19:37:57 +01:00
my $object_size = $model_object -> bounding_box -> size ;
my $bed_size = Slic3r::Polygon -> new_scale ( @ { $self -> { config } -> bed_shape }) -> bounding_box -> size ;
2014-06-17 00:50:44 +02:00
if ( defined $axis ) {
my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z' ;
2015-12-05 19:37:57 +01:00
my $scale ;
if ( $tosize ) {
my $cursize = $object_size -> [ $axis ];
2017-03-23 14:32:19 +01:00
my $newsize = $self -> _get_number_from_user (
sprintf ( 'Enter the new size for the selected object (print bed: %smm):' , $bed_size -> [ $axis ]),
"Scale along $axis_name" , 'Invalid scaling value entered' , $cursize , 1 );
return if $newsize eq '' ;
2015-12-05 19:37:57 +01:00
$scale = $newsize / $cursize * 100 ;
} else {
2017-03-23 14:32:19 +01:00
$scale = $self -> _get_number_from_user ( 'Enter the scale % for the selected object:' , "Scale along $axis_name" , 'Invalid scaling value entered' , 100 , 1 );
return if $scale eq '' ;
2015-12-05 19:37:57 +01:00
}
2014-06-17 00:50:44 +02:00
# 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 ));
2017-02-28 10:29:52 +01:00
#FIXME Scale the layer height profile when $axis == Z?
#FIXME Scale the layer height ranges $axis == Z?
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 {
2015-12-05 19:37:57 +01:00
my $scale ;
if ( $tosize ) {
my $cursize = max ( @$object_size );
2017-03-23 14:32:19 +01:00
my $newsize = $self -> _get_number_from_user ( 'Enter the new max size for the selected object:' , 'Scale' , 'Invalid scaling value entered' , $cursize , 1 );
return if $newsize eq '' ;
$scale = $model_instance -> scaling_factor * $newsize / $cursize * 100 ;
2015-12-05 19:37:57 +01:00
} else {
# max scale factor should be above 2540 to allow importing files exported in inches
2017-03-23 14:32:19 +01:00
$scale = $self -> _get_number_from_user ( 'Enter the scale % for the selected object:' , 'Scale' , 'Invalid scaling value entered' , $model_instance -> scaling_factor * 100 , 1 );
return if $scale eq '' ;
2015-12-05 19:37:57 +01:00
}
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 ;
2017-02-28 10:29:52 +01:00
#FIXME Scale the layer height profile?
2013-12-12 20:19:33 +01:00
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 );
2016-11-04 15:03:51 +01:00
my $success = $self -> { model } -> arrange_objects ( $self -> GetFrame -> config -> min_object_distance , $bb );
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
}
2016-09-13 11:24:55 +02:00
# Executed asynchronously by a timer every PROCESS_DELAY (0.5 second).
# The timer is started by schedule_background_process(),
2014-06-13 19:23:51 +02:00
sub async_apply_config {
2014-06-13 15:54:13 +02:00
my ( $self ) = @_ ;
2017-05-30 17:24:50 +02:00
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 );
2017-02-09 14:56:13 +01:00
# $self->{canvas3D}->Refresh if ($invalidated && $self->{canvas3D}->layer_editing_enabled);
$self -> { canvas3D } -> Refresh if ( $self -> { canvas3D } -> layer_editing_enabled );
2017-02-15 16:02:54 +01:00
# Hide the slicing results if the current slicing status is no more valid.
$self -> { "print_info_box_show" } -> ( 0 ) if $invalidated ;
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 ;
2017-05-30 17:24:50 +02:00
# Reset preview canvases. If the print has been invalidated, the preview canvases will be cleared.
# Otherwise they will be just refreshed.
$self -> { toolpaths2D } -> reload_print if $self -> { toolpaths2D };
$self -> { preview3D } -> reload_print if $self -> { preview3D };
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
}
}
2016-10-25 13:24:42 +02:00
sub reslice {
# explicitly cancel a previous thread and start a new one.
my ( $self ) = @_ ;
# Don't reslice if export of G-code or sending to OctoPrint is running.
if ( $ Slic3r:: have_threads && ! defined ( $self -> { export_gcode_output_file }) && ! defined ( $self -> { send_gcode_file })) {
2017-02-09 14:56:13 +01:00
# Stop the background processing threads, stop the async update timer.
2016-10-25 13:24:42 +02:00
$self -> stop_background_process ;
2017-02-09 14:56:13 +01:00
# Rather perform one additional unnecessary update of the print object instead of skipping a pending async update.
$self -> async_apply_config ;
2016-10-25 13:24:42 +02:00
$self -> statusbar -> SetCancelCallback ( sub {
$self -> stop_background_process ;
$self -> statusbar -> SetStatusText ( "Slicing cancelled" );
# this updates buttons status
$self -> object_list_changed ;
});
$self -> start_background_process ;
}
}
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 ) {
2016-12-20 19:01:51 +01:00
$self -> { export_gcode_output_file } = $self -> { print } -> output_filepath ( $output_file );
2014-12-28 01:30:05 +01:00
} else {
2016-12-20 19:01:51 +01:00
my $default_output_file = $self -> { print } -> 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…" );
}
2016-10-25 13:24:42 +02:00
# Called when the G-code export finishes, either successfully or with an error.
2014-06-13 14:27:55 +02:00
# 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 ) {
2016-10-25 13:24:42 +02:00
# G-code file exported successfully.
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 ;
2016-10-25 13:24:42 +02:00
# Send $self->{send_gcode_file} to OctoPrint.
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 ;
2017-01-20 21:21:57 -06:00
$self -> { "print_info_cost" } -> SetLabel ( sprintf ( "%.2f" , $self -> { print } -> total_cost ));
$self -> { "print_info_fil_g" } -> SetLabel ( sprintf ( "%.2f" , $self -> { print } -> total_weight ));
$self -> { "print_info_fil_mm3" } -> SetLabel ( sprintf ( "%.2f" , $self -> { print } -> total_extruded_volume ));
2017-02-15 16:02:54 +01:00
$self -> { "print_info_box_show" } -> ( 1 );
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
}
2016-10-25 13:24:42 +02:00
# Send $self->{send_gcode_file} to OctoPrint.
#FIXME Currently this call blocks the UI. Make it asynchronous.
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 ;
2017-02-27 01:03:00 +01:00
$self -> { model } -> store_stl ( Slic3r:: encode_path ( $output_file ), 1 );
2012-08-29 17:11:56 +02:00
$self -> statusbar -> SetStatusText ( "STL file exported to $output_file" );
}
2016-12-22 12:13:28 +01:00
sub reload_from_disk {
my ( $self ) = @_ ;
my ( $obj_idx , $object ) = $self -> selected_object ;
return if ! defined $obj_idx ;
my $model_object = $self -> { model } -> objects -> [ $obj_idx ];
return if ! $model_object -> input_file
|| !- e $model_object -> input_file ;
my @new_obj_idx = $self -> load_file ( $model_object -> input_file );
return if ! @new_obj_idx ;
foreach my $new_obj_idx ( @new_obj_idx ) {
my $o = $self -> { model } -> objects -> [ $new_obj_idx ];
$o -> clear_instances ;
$o -> add_instance ( $_ ) for @ { $model_object -> instances };
if ( $o -> volumes_count == $model_object -> volumes_count ) {
for my $i ( 0 .. ( $o -> volumes_count - 1 )) {
$o -> get_volume ( $i ) -> config -> apply ( $model_object -> get_volume ( $i ) -> config );
}
}
}
$self -> remove ( $obj_idx );
# Trigger thumbnail generation again, because the remove() method altered
# object indexes before background thumbnail generation called its completion
# event, so the on_thumbnail_made callback is called with the wrong $obj_idx.
# When porting to C++ we'll probably have cleaner ways to do this.
$self -> make_thumbnail ( $_ - 1 ) for @new_obj_idx ;
}
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 ;
2017-02-27 01:03:00 +01:00
$model_object -> mesh -> write_binary ( Slic3r:: encode_path ( $output_file ));
2015-01-03 15:48:53 +01:00
$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 ;
2017-02-27 01:03:00 +01:00
$self -> { model } -> store_amf ( Slic3r:: encode_path ( $output_file ));
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 };
{
2016-12-20 19:01:51 +01:00
$output_file = $self -> { print } -> 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 ) = @_ ;
2016-12-12 18:02:24 +01: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
}
2016-10-24 16:07:36 +02:00
# When a number of extruders changes, the UI needs to be updated to show a single filament selection combo box per extruder.
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 );
2017-05-24 15:20:20 +02:00
my $extruder_idx = scalar @$choices ;
EVT_LEFT_DOWN ( $choice , sub { $self -> filament_color_box_lmouse_down ( $extruder_idx , @_ ); } );
2015-06-01 23:58:34 +02:00
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
2017-05-17 16:53:40 +02:00
my $update_scheduled ;
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 };
2017-05-17 16:53:40 +02:00
$update_scheduled = 1 ;
2017-05-19 21:48:32 +02:00
} elsif ( $opt_key =~ '^wipe_tower' || $opt_key eq 'single_extruder_multi_material' ) {
2017-05-17 16:53:40 +02:00
$update_scheduled = 1 ;
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 ;
2017-02-10 10:21:50 +01:00
} elsif ( $opt_key eq 'variable_layer_height' ) {
if ( $config -> get ( 'variable_layer_height' ) != 1 ) {
2017-02-13 12:40:12 +01:00
if ( $self -> { htoolbar }) {
$self -> { htoolbar } -> EnableTool ( TB_LAYER_EDITING , 0 );
$self -> { htoolbar } -> ToggleTool ( TB_LAYER_EDITING , 0 );
} else {
$self -> { "btn_layer_editing" } -> Disable ;
$self -> { "btn_layer_editing" } -> SetValue ( 0 );
}
2017-02-10 10:21:50 +01:00
$self -> { canvas3D } -> layer_editing_enabled ( 0 );
2017-05-17 16:53:40 +02:00
$self -> { canvas3D } -> Refresh ;
$self -> { canvas3D } -> Update ;
2017-02-19 18:01:03 +01:00
} elsif ( $self -> { canvas3D } -> layer_editing_allowed ) {
# Want to allow the layer editing, but do it only if the OpenGL supports it.
2017-02-13 12:40:12 +01:00
if ( $self -> { htoolbar }) {
$self -> { htoolbar } -> EnableTool ( TB_LAYER_EDITING , 1 );
} else {
$self -> { "btn_layer_editing" } -> Enable ;
}
2017-02-10 10:21:50 +01:00
}
2017-05-30 17:24:50 +02:00
} elsif ( $opt_key eq 'extruder_colour' ) {
$update_scheduled = 1 ;
my $extruder_colors = $config -> get ( 'extruder_colour' );
$self -> { preview3D } -> set_number_extruders ( scalar ( @ { $extruder_colors }));
2014-05-28 12:29:43 +02:00
}
2012-08-06 19:18:31 +02:00
}
2017-05-17 16:53:40 +02:00
$self -> update if $update_scheduled ;
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
}
2017-05-24 15:20:20 +02:00
# Called when clicked on the filament preset combo box.
# When clicked on the icon, show the color picker.
sub filament_color_box_lmouse_down
{
my ( $self , $extruder_idx , $combobox , $event ) = @_ ;
my $pos = $event -> GetLogicalPosition ( Wx::ClientDC -> new ( $combobox ));
my ( $x , $y ) = ( $pos -> x , $pos -> y );
if ( $x > 24 ) {
# Let the combo box process the mouse click.
$event -> Skip ;
} else {
# Swallow the mouse click and open the color picker.
my $data = Wx::ColourData -> new ;
$data -> SetChooseFull ( 1 );
my $dialog = Wx::ColourDialog -> new ( $self -> GetFrame , $data );
if ( $dialog -> ShowModal == wxID_OK ) {
my $cfg = Slic3r::Config -> new ;
my $colors = $self -> GetFrame -> config -> get ( 'extruder_colour' );
$colors -> [ $extruder_idx ] = $dialog -> GetColourData -> GetColour -> GetAsString ( wxC2S_HTML_SYNTAX );
$cfg -> set ( 'extruder_colour' , $colors );
$self -> GetFrame -> { options_tabs }{ printer } -> load_config ( $cfg );
$self -> update_filament_colors_preview ( $extruder_idx );
}
$dialog -> Destroy ();
}
}
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
);
2015-12-19 20:41:47 +01:00
return unless $dlg -> ShowModal == wxID_OK ;
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 ,
2017-04-05 13:27:00 +02:00
config => $self -> GetFrame -> config ,
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
}
2016-10-25 13:24:42 +02:00
# Called to update various buttons depending on whether there are any objects or
# whether background processing (export of a G-code, sending to Octoprint, forced background re-slicing) is active.
2012-04-30 14:56:01 +02:00
sub object_list_changed {
my $self = shift ;
2017-02-13 12:40:12 +01:00
2016-10-25 13:24:42 +02:00
# Enable/disable buttons depending on whether there are any objects on the platter.
2013-08-25 18:01:59 +02:00
my $have_objects = @ { $self -> { objects }} ? 1 : 0 ;
2017-02-19 18:01:03 +01:00
my $variable_layer_height_allowed = $self -> { config } -> variable_layer_height && $self -> { canvas3D } -> layer_editing_allowed ;
2017-02-13 12:40:12 +01:00
if ( $self -> { htoolbar }) {
# On OSX or Linux
$self -> { htoolbar } -> EnableTool ( $_ , $have_objects )
for ( TB_RESET , TB_ARRANGE , TB_LAYER_EDITING );
2017-02-19 18:01:03 +01:00
$self -> { htoolbar } -> EnableTool ( TB_LAYER_EDITING , 0 ) if ( ! $variable_layer_height_allowed );
2017-02-13 12:40:12 +01:00
} else {
# On MSW
my $method = $have_objects ? 'Enable' : 'Disable' ;
$self -> { "btn_$_" } -> $method
for grep $self -> { "btn_$_" }, qw(reset arrange reslice export_gcode export_stl print send_gcode layer_editing) ;
2017-02-19 18:01:03 +01:00
$self -> { "btn_layer_editing" } -> Disable if ( ! $variable_layer_height_allowed );
2017-02-13 12:40:12 +01:00
}
2017-02-17 15:00:01 +01:00
my $export_in_progress = $self -> { export_gcode_output_file } || $self -> { send_gcode_file };
my $method = ( $have_objects && ! $export_in_progress ) ? 'Enable' : 'Disable' ;
$self -> { "btn_$_" } -> $method
for grep $self -> { "btn_$_" }, qw(reslice export_gcode print send_gcode) ;
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
2012-05-04 11:22:56 +02:00
if ( $self -> { htoolbar }) {
2017-02-13 12:40:12 +01:00
# On OSX or Linux
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 );
2017-02-13 12:40:12 +01:00
} else {
# On MSW
my $method = $have_sel ? 'Enable' : 'Disable' ;
$self -> { "btn_$_" } -> $method
for grep $self -> { "btn_$_" }, qw(remove increase decrease rotate45cw rotate45ccw changescale split cut 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 ];
2016-12-12 18:02:24 +01:00
$model_object -> print_info ;
2013-12-12 20:19:33 +01:00
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 ;
2017-05-17 16:53:40 +02:00
$self -> { canvas3D } -> reload_scene 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 {
2017-03-23 11:53:59 +01:00
$self -> rotate ( - 45 , Z , 'relative' );
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 {
2017-03-23 11:53:59 +01:00
$self -> rotate ( + 45 , Z , 'relative' );
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
2015-12-05 19:37:57 +01:00
my $scaleToSizeMenu = Wx::Menu -> new ;
my $scaleToSizeMenuItem = $menu -> AppendSubMenu ( $scaleToSizeMenu , "Scale to size" , 'Scale the selected object along a single axis' );
$frame -> _set_menu_item_icon ( $scaleToSizeMenuItem , 'arrow_out.png' );
$frame -> _append_menu_item ( $scaleToSizeMenu , "Uniformly…" , 'Scale the selected object along the XYZ axes' , sub {
$self -> changescale ( undef , 1 );
});
$frame -> _append_menu_item ( $scaleToSizeMenu , "Along X axis…" , 'Scale the selected object along the X axis' , sub {
$self -> changescale ( X , 1 );
}, undef , 'bullet_red.png' );
$frame -> _append_menu_item ( $scaleToSizeMenu , "Along Y axis…" , 'Scale the selected object along the Y axis' , sub {
$self -> changescale ( Y , 1 );
}, undef , 'bullet_green.png' );
$frame -> _append_menu_item ( $scaleToSizeMenu , "Along Z axis…" , 'Scale the selected object along the Z axis' , sub {
$self -> changescale ( Z , 1 );
}, undef , 'bullet_blue.png' );
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 ();
2016-12-17 21:47:45 +01:00
$frame -> _append_menu_item ( $menu , "Reload from Disk" , 'Reload the selected file from Disk' , sub {
$self -> reload_from_disk ;
}, undef , 'arrow_refresh.png' );
2015-01-03 15:48:53 +01:00
$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 ;
}
2016-10-05 14:13:07 +02:00
# Set a camera direction, zoom to all objects.
sub select_view {
my ( $self , $direction ) = @_ ;
my $idx_page = $self -> { preview_notebook } -> GetSelection ;
my $page = ( $idx_page == & Wx:: wxNOT_FOUND ) ? '3D' : $self -> { preview_notebook } -> GetPageText ( $idx_page );
if ( $page eq 'Preview' ) {
$self -> { preview3D } -> canvas -> select_view ( $direction );
$self -> { canvas3D } -> set_viewport_from_scene ( $self -> { preview3D } -> canvas );
} else {
$self -> { canvas3D } -> select_view ( $direction );
$self -> { preview3D } -> canvas -> set_viewport_from_scene ( $self -> { canvas3D });
}
}
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
2017-04-05 14:45:43 +02:00
return 0 if grep ! /\.(?:stl|obj|amf(?:\.xml)?|prusa)$/i , @$filenames ;
2012-04-30 14:56:01 +02:00
$self -> { window } -> load_file ( $_ ) for @$filenames ;
}
2016-09-13 11:24:55 +02:00
# 2D preview of an object. Each object is previewed by its convex hull.
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 ;