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) ;
2014-05-28 12:29:43 +02:00
use List::Util qw(sum first) ;
use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale) ;
2012-05-19 21:13:10 +02:00
use threads::shared qw(shared_clone) ;
2014-06-13 23:27:52 +02:00
use Thread::Semaphore ;
2014-06-14 18:58:56 +02:00
use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc
:panel :sizer :toolbar :window) ;
2014-06-13 15:54:13 +02:00
use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED
EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL
EVT_CHOICE EVT_TIMER) ;
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_ROTATE => & Wx:: NewId ;
use constant TB_SCALE => & Wx:: NewId ;
use constant TB_SPLIT => & Wx:: NewId ;
2013-08-25 18:01:59 +02:00
use constant TB_VIEW => & Wx:: NewId ;
use constant TB_SETTINGS => & Wx:: NewId ;
2014-06-13 19:23:51 +02:00
use constant apply_config_timer_EVENT => & Wx:: NewId ;
2012-05-04 11:22:56 +02:00
2013-10-16 15:13:39 +02:00
# package variables to avoid passing lexicals to threads
our $THUMBNAIL_DONE_EVENT : shared = Wx:: NewEventType ;
our $PROGRESS_BAR_EVENT : shared = Wx:: NewEventType ;
2014-06-13 14:27:55 +02:00
our $ERROR_EVENT : shared = Wx:: NewEventType ;
2013-10-16 15:13:39 +02:00
our $EXPORT_COMPLETED_EVENT : shared = Wx:: NewEventType ;
2014-06-13 11:19:53 +02:00
our $PROCESS_COMPLETED_EVENT : shared = Wx:: NewEventType ;
2012-04-30 20:59:14 +02:00
2013-08-25 12:22:05 +02:00
use constant CANVAS_SIZE => [ 335 , 335 ];
2012-08-07 18:44:47 +02:00
use constant FILAMENT_CHOOSERS_SPACING => 3 ;
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 ;
2014-06-13 23:27:52 +02:00
my $sema = Thread::Semaphore -> new ;
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(
2012-08-07 20:14:28 +02:00
bed_size print_center complete_objects extruder_clearance_radius skirts skirt_distance
2012-07-27 21:13:03 +02:00
) );
2013-12-12 20:19:33 +01:00
$self -> { model } = Slic3r::Model -> new ;
2013-12-13 14:02:01 +01:00
$self -> { print } = Slic3r::Print -> new ;
2012-09-12 16:30:44 +02:00
$self -> { objects } = [] ;
2014-06-13 19:23:51 +02:00
$self -> { apply_config_timer } = Wx::Timer -> new ( $self , apply_config_timer_EVENT )
2014-06-13 15:54:13 +02:00
if $ Slic3r:: have_threads ;
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-05-28 12:29:43 +02:00
$self -> { canvas } = Slic3r::GUI::Plater:: 2 D -> new ( $self , CANVAS_SIZE , $self -> { objects }, $self -> { model }, $self -> { config });
$self -> { canvas } -> on_select_object ( sub {
my ( $obj_idx ) = @_ ;
$self -> select_object ( $obj_idx );
});
$self -> { canvas } -> on_double_click ( sub {
$self -> object_cut_dialog if $self -> selected_object ;
});
$self -> { canvas } -> on_instance_moved ( sub {
$self -> update ;
});
2012-04-30 14:56:01 +02:00
2012-05-04 11:22:56 +02:00
# toolbar for object manipulation
2012-05-20 16:24:10 +02:00
if ( !& Wx:: wxMSW ) {
2012-05-04 12:56:15 +02:00
Wx::ToolTip:: Enable ( 1 );
2012-07-24 14:28:21 +02:00
$self -> { htoolbar } = Wx::ToolBar -> new ( $self , - 1 , wxDefaultPosition , wxDefaultSize , wxTB_HORIZONTAL | wxTB_TEXT | wxBORDER_SIMPLE | wxTAB_TRAVERSAL );
2013-12-12 20:19:33 +01:00
$self -> { htoolbar } -> AddTool ( TB_ADD , "Add…" , Wx::Bitmap -> new ( "$Slic3r::var/brick_add.png" , wxBITMAP_TYPE_PNG ), '' );
2013-08-25 12:22:05 +02:00
$self -> { htoolbar } -> AddTool ( TB_REMOVE , "Delete" , Wx::Bitmap -> new ( "$Slic3r::var/brick_delete.png" , wxBITMAP_TYPE_PNG ), '' );
$self -> { htoolbar } -> AddTool ( TB_RESET , "Delete All" , Wx::Bitmap -> new ( "$Slic3r::var/cross.png" , wxBITMAP_TYPE_PNG ), '' );
2013-08-25 16:10:53 +02:00
$self -> { htoolbar } -> AddTool ( TB_ARRANGE , "Arrange" , Wx::Bitmap -> new ( "$Slic3r::var/bricks.png" , wxBITMAP_TYPE_PNG ), '' );
2013-08-25 12:22:05 +02:00
$self -> { htoolbar } -> AddSeparator ;
2012-07-01 19:24:06 +02:00
$self -> { htoolbar } -> AddTool ( TB_MORE , "More" , Wx::Bitmap -> new ( "$Slic3r::var/add.png" , wxBITMAP_TYPE_PNG ), '' );
2013-08-25 15:45:22 +02:00
$self -> { htoolbar } -> AddTool ( TB_FEWER , "Fewer" , Wx::Bitmap -> new ( "$Slic3r::var/delete.png" , wxBITMAP_TYPE_PNG ), '' );
2012-05-04 11:22:56 +02:00
$self -> { htoolbar } -> AddSeparator ;
2012-07-01 19:24:06 +02:00
$self -> { htoolbar } -> AddTool ( TB_45CCW , "45° ccw" , Wx::Bitmap -> new ( "$Slic3r::var/arrow_rotate_anticlockwise.png" , wxBITMAP_TYPE_PNG ), '' );
$self -> { htoolbar } -> AddTool ( TB_45CW , "45° cw" , Wx::Bitmap -> new ( "$Slic3r::var/arrow_rotate_clockwise.png" , wxBITMAP_TYPE_PNG ), '' );
2012-07-01 23:29:56 +02:00
$self -> { htoolbar } -> AddTool ( TB_ROTATE , "Rotate…" , 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 ), '' );
2012-07-01 19:24:06 +02:00
$self -> { htoolbar } -> AddTool ( TB_SPLIT , "Split" , Wx::Bitmap -> new ( "$Slic3r::var/shape_ungroup.png" , wxBITMAP_TYPE_PNG ), '' );
2013-08-25 18:01:59 +02:00
$self -> { htoolbar } -> AddSeparator ;
2014-04-25 14:54:08 +02:00
$self -> { htoolbar } -> AddTool ( TB_VIEW , "View/Cut…" , Wx::Bitmap -> new ( "$Slic3r::var/package.png" , wxBITMAP_TYPE_PNG ), '' );
2013-08-25 18:01:59 +02:00
$self -> { htoolbar } -> AddTool ( TB_SETTINGS , "Settings…" , Wx::Bitmap -> new ( "$Slic3r::var/cog.png" , wxBITMAP_TYPE_PNG ), '' );
2012-05-20 16:24:10 +02:00
} else {
2013-08-25 16:10:53 +02:00
my %tbar_buttons = (
2014-03-25 15:07:21 +01:00
add => "Add…" ,
2013-08-25 16:10:53 +02:00
remove => "Delete" ,
reset => "Delete All" ,
arrange => "Arrange" ,
increase => "" ,
decrease => "" ,
rotate45ccw => "" ,
rotate45cw => "" ,
rotate => "Rotate…" ,
changescale => "Scale…" ,
split => "Split" ,
2014-04-25 14:54:08 +02:00
view => "View/Cut…" ,
2013-08-25 18:01:59 +02:00
settings => "Settings…" ,
2013-08-25 16:10:53 +02:00
);
2012-05-20 16:24:10 +02:00
$self -> { btoolbar } = Wx::BoxSizer -> new ( wxHORIZONTAL );
2014-03-25 15:07:21 +01:00
for ( qw(add remove reset arrange increase decrease rotate45ccw rotate45cw rotate changescale split view settings) ) {
2012-07-03 01:20:30 +02:00
$self -> { "btn_$_" } = Wx::Button -> new ( $self , - 1 , $tbar_buttons { $_ }, wxDefaultPosition , wxDefaultSize , wxBU_EXACTFIT );
2012-05-20 16:24:10 +02:00
$self -> { btoolbar } -> Add ( $self -> { "btn_$_" });
}
2012-05-04 11:22:56 +02:00
}
2012-07-24 14:42:38 +02:00
2013-08-25 12:22:05 +02:00
$self -> { list } = Wx::ListView -> new ( $self , - 1 , wxDefaultPosition , wxDefaultSize , wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS );
$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
2012-07-03 01:20:30 +02:00
$self -> { btn_export_gcode } = Wx::Button -> new ( $self , - 1 , "Export G-code…" , wxDefaultPosition , wxDefaultSize , wxBU_LEFT );
$self -> { btn_export_stl } = Wx::Button -> new ( $self , - 1 , "Export STL…" , wxDefaultPosition , wxDefaultSize , wxBU_LEFT );
2013-08-25 12:22:05 +02:00
$self -> { btn_export_gcode } -> SetFont ( $ Slic3r::GUI:: small_font );
$self -> { btn_export_stl } -> SetFont ( $ Slic3r::GUI:: small_font );
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
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
rotate arrow_rotate_clockwise.png
changescale arrow_out.png
split shape_ungroup.png
2013-08-25 18:08:56 +02:00
view package.png
settings cog.png
2012-05-04 11:22:56 +02:00
) ;
2012-05-20 16:24:10 +02:00
for ( grep $self -> { "btn_$_" }, keys %icons ) {
2012-07-01 19:24:06 +02:00
$self -> { "btn_$_" } -> SetBitmap ( Wx::Bitmap -> new ( "$Slic3r::var/$icons{$_}" , wxBITMAP_TYPE_PNG ));
2012-05-20 16:24:10 +02:00
}
2012-05-01 16:49:34 +02:00
}
2012-04-30 14:56:01 +02:00
$self -> selection_changed ( 0 );
$self -> object_list_changed ;
2014-06-13 19:23:51 +02:00
EVT_BUTTON ( $self , $self -> { btn_export_gcode }, sub {
$self -> export_gcode ;
Slic3r:: thread_cleanup ();
});
2012-04-30 17:10:54 +02: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 }) {
2013-12-12 20:19:33 +01:00
EVT_TOOL ( $self , TB_ADD , \& add );
2013-08-25 12:22:05 +02:00
EVT_TOOL ( $self , TB_REMOVE , sub { $self -> remove () }); # explicitly pass no argument to remove
EVT_TOOL ( $self , TB_RESET , \& reset );
EVT_TOOL ( $self , TB_ARRANGE , \& arrange );
2012-05-04 11:22:56 +02:00
EVT_TOOL ( $self , TB_MORE , \& increase );
2013-08-25 15:45:22 +02:00
EVT_TOOL ( $self , TB_FEWER , \& decrease );
2012-05-04 11:22:56 +02:00
EVT_TOOL ( $self , TB_45CW , sub { $_ [ 0 ] -> rotate ( - 45 ) });
EVT_TOOL ( $self , TB_45CCW , sub { $_ [ 0 ] -> rotate ( 45 ) });
2012-05-04 12:56:15 +02:00
EVT_TOOL ( $self , TB_ROTATE , sub { $_ [ 0 ] -> rotate ( undef ) });
EVT_TOOL ( $self , TB_SCALE , \& changescale );
2012-05-04 11:22:56 +02:00
EVT_TOOL ( $self , TB_SPLIT , \& split_object );
2014-04-25 14:54:08 +02:00
EVT_TOOL ( $self , TB_VIEW , sub { $_ [ 0 ] -> object_cut_dialog });
2013-08-25 18:01:59 +02:00
EVT_TOOL ( $self , TB_SETTINGS , sub { $_ [ 0 ] -> object_settings_dialog });
2012-05-04 11:22:56 +02:00
} else {
2013-12-12 20:19:33 +01:00
EVT_BUTTON ( $self , $self -> { btn_add }, \& add );
2013-08-25 16:10:53 +02:00
EVT_BUTTON ( $self , $self -> { btn_remove }, sub { $self -> remove () }); # explicitly pass no argument to remove
EVT_BUTTON ( $self , $self -> { btn_reset }, \& reset );
EVT_BUTTON ( $self , $self -> { btn_arrange }, \& arrange );
2012-05-04 11:22:56 +02:00
EVT_BUTTON ( $self , $self -> { btn_increase }, \& increase );
EVT_BUTTON ( $self , $self -> { btn_decrease }, \& decrease );
EVT_BUTTON ( $self , $self -> { btn_rotate45cw }, sub { $_ [ 0 ] -> rotate ( - 45 ) });
EVT_BUTTON ( $self , $self -> { btn_rotate45ccw }, sub { $_ [ 0 ] -> rotate ( 45 ) });
EVT_BUTTON ( $self , $self -> { btn_changescale }, \& changescale );
EVT_BUTTON ( $self , $self -> { btn_rotate }, sub { $_ [ 0 ] -> rotate ( undef ) });
EVT_BUTTON ( $self , $self -> { btn_split }, \& split_object );
2014-04-25 14:54:08 +02:00
EVT_BUTTON ( $self , $self -> { btn_view }, sub { $_ [ 0 ] -> object_cut_dialog });
2013-08-25 18:01:59 +02:00
EVT_BUTTON ( $self , $self -> { btn_settings }, sub { $_ [ 0 ] -> object_settings_dialog });
2012-05-04 11:22:56 +02:00
}
2012-05-04 10:15:33 +02:00
$_ -> SetDropTarget ( Slic3r::GUI::Plater::DropTarget -> new ( $self ))
2012-04-30 17:10:54 +02:00
for $self , $self -> { canvas }, $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 19:23:51 +02:00
Slic3r:: thread_cleanup ();
2014-06-13 11:19:53 +02:00
});
2014-06-13 19:23:51 +02:00
EVT_TIMER ( $self , apply_config_timer_EVENT , sub {
2014-06-13 15:54:13 +02:00
my ( $self , $event ) = @_ ;
2014-06-13 19:23:51 +02:00
$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 ;
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' ) {
2013-08-25 12:22:05 +02:00
$presets = Wx::BoxSizer -> new ( wxVERTICAL );
2013-01-03 15:49:20 +01:00
my %group_labels = (
print => 'Print settings' ,
filament => 'Filament' ,
printer => 'Printer' ,
);
$self -> { preset_choosers } = {};
$self -> { preset_choosers_sizers } = {};
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 );
my $choice = Wx::Choice -> new ( $self , - 1 , wxDefaultPosition , [ 140 , - 1 ], [] );
$choice -> SetFont ( $ Slic3r::GUI:: small_font );
2013-01-03 15:49:20 +01:00
$self -> { preset_choosers }{ $group } = [ $choice ];
EVT_CHOICE ( $choice , $choice , sub { $self -> on_select_preset ( $group , @_ ) });
$self -> { preset_choosers_sizers }{ $group } = Wx::BoxSizer -> new ( wxVERTICAL );
$self -> { preset_choosers_sizers }{ $group } -> Add ( $choice , 0 , wxEXPAND | wxBOTTOM , FILAMENT_CHOOSERS_SPACING );
2013-08-25 12:22:05 +02:00
$presets -> Add ( $text , 0 , wxALIGN_LEFT | wxRIGHT , 4 );
$presets -> Add ( $self -> { preset_choosers_sizers }{ $group }, 0 , wxALIGN_CENTER_VERTICAL | wxBOTTOM , 8 );
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 );
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 ;
my $text = Wx::StaticText -> new ( $self , - 1 , "$label:" , wxDefaultPosition , wxDefaultSize , wxALIGN_LEFT );
$text -> SetFont ( $ Slic3r::GUI:: small_font );
$grid_sizer -> Add ( $text , 0 );
$self -> { "object_info_$field" } = Wx::StaticText -> new ( $self , - 1 , "" , wxDefaultPosition , wxDefaultSize , wxALIGN_LEFT );
$self -> { "object_info_$field" } -> SetFont ( $ Slic3r::GUI:: small_font );
2013-08-25 17:26:55 +02:00
if ( $field eq 'manifold' ) {
$self -> { object_info_manifold_warning_icon } = Wx::StaticBitmap -> new ( $self , - 1 , Wx::Bitmap -> new ( "$Slic3r::var/error.png" , wxBITMAP_TYPE_PNG ));
$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
}
}
my $right_buttons_sizer = Wx::BoxSizer -> new ( wxVERTICAL );
$right_buttons_sizer -> Add ( $presets , 0 , wxEXPAND , 0 ) if defined $presets ;
$right_buttons_sizer -> Add ( $self -> { btn_export_gcode }, 0 , wxEXPAND | wxTOP , 8 );
$right_buttons_sizer -> Add ( $self -> { btn_export_stl }, 0 , wxEXPAND | wxTOP , 2 );
my $right_top_sizer = Wx::BoxSizer -> new ( wxHORIZONTAL );
$right_top_sizer -> Add ( $self -> { list }, 1 , wxEXPAND | wxLEFT , 5 );
$right_top_sizer -> Add ( $right_buttons_sizer , 0 , wxEXPAND | wxALL , 10 );
my $right_sizer = Wx::BoxSizer -> new ( wxVERTICAL );
$right_sizer -> Add ( $right_top_sizer , 1 , wxEXPAND | wxBOTTOM , 10 );
$right_sizer -> Add ( $object_info_sizer , 0 , wxEXPAND | wxLEFT | wxRIGHT , 5 );
my $hsizer = Wx::BoxSizer -> new ( wxHORIZONTAL );
$hsizer -> Add ( $self -> { canvas }, 0 , wxTOP , 1 );
$hsizer -> Add ( $right_sizer , 1 , wxEXPAND | wxBOTTOM , 0 );
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 );
}
return $self ;
}
2012-10-24 20:24:11 +02:00
sub on_select_preset {
my $self = shift ;
my ( $group , $choice ) = @_ ;
if ( $group eq 'filament' && @ { $self -> { preset_choosers }{ filament }} > 1 ) {
my @filament_presets = $self -> filament_presets ;
$ Slic3r::GUI:: Settings -> { presets }{ filament } = $choice -> GetString ( $filament_presets [ 0 ]) . ".ini" ;
$ Slic3r::GUI:: Settings -> { presets }{ "filament_${_}" } = $choice -> GetString ( $filament_presets [ $_ ])
for 1 .. $#filament_presets ;
2014-06-14 19:54:18 +02:00
& Wx::wxTheApp -> save_settings ;
2012-10-24 20:24:11 +02:00
return ;
}
2014-06-14 19:54:18 +02:00
$self -> GetFrame -> { options_tabs }{ $group } -> select_preset ( $choice -> GetSelection );
2012-10-24 20:24:11 +02:00
}
2014-06-14 18:58:56 +02:00
sub GetFrame {
my ( $self ) = @_ ;
2014-06-14 19:54:18 +02:00
return & Wx:: GetTopLevelParent ( $self );
2012-07-27 21:13:03 +02:00
}
2012-08-07 18:44:47 +02:00
sub update_presets {
my $self = shift ;
my ( $group , $items , $selected ) = @_ ;
foreach my $choice ( @ { $self -> { preset_choosers }{ $group } }) {
my $sel = $choice -> GetSelection ;
$choice -> Clear ;
$choice -> Append ( $_ ) for @$items ;
$choice -> SetSelection ( $sel ) if $sel <= $#$items ;
}
$self -> { preset_choosers }{ $group }[ 0 ] -> SetSelection ( $selected );
}
sub filament_presets {
my $self = shift ;
return map $_ -> GetSelection , @ { $self -> { preset_choosers }{ filament } };
}
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:54:18 +02:00
my @input_files = & Wx::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:54:18 +02:00
& Wx::wxTheApp -> save_settings ;
2012-04-30 14:56:01 +02:00
2012-07-01 23:29:56 +02:00
my $process_dialog = Wx::ProgressDialog -> new ( 'Loading…' , "Processing input file…" , 100 , $self , 0 );
2012-04-30 21:49:44 +02:00
$process_dialog -> Pulse ;
2012-04-30 14:56:01 +02:00
2012-08-29 19:37:27 +02:00
local $SIG { __WARN__ } = Slic3r::GUI:: warning_catcher ( $self );
2013-12-17 16:12:44 +01:00
my $model = eval { Slic3r::Model -> read_from_file ( $input_file ) };
Slic3r::GUI:: show_error ( $self , $@ ) if $@ ;
2014-03-25 19:06:51 +01:00
if ( defined $model ) {
$self -> load_model_objects ( @ { $model -> objects });
$self -> statusbar -> SetStatusText ( "Loaded " . basename ( $input_file ));
}
2012-08-29 19:37:27 +02:00
$process_dialog -> Destroy ;
2013-12-12 20:19:33 +01:00
}
2014-01-05 14:04:32 +01:00
sub load_model_objects {
my ( $self , @model_objects ) = @_ ;
2013-12-12 20:19:33 +01:00
my $need_arrange = 0 ;
2014-01-05 14:04:32 +01:00
my @obj_idx = ();
foreach my $model_object ( @model_objects ) {
my $o = $self -> { model } -> add_object ( $model_object );
2013-12-12 20:19:33 +01:00
2014-01-05 14:04:32 +01:00
push @ { $self -> { objects } }, Slic3r::GUI::Plater::Object -> new (
name => basename ( $model_object -> input_file ),
);
push @obj_idx , $# { $self -> { objects } };
2013-12-12 20:19:33 +01:00
2014-05-10 16:59:17 +02:00
if ( $model_object -> instances_count == 0 ) {
2014-01-05 14:04:32 +01:00
# if object has no defined position(s) we need to rearrange everything after loading
$need_arrange = 1 ;
# add a default instance and center object around origin
$o -> center_around_origin ;
2014-05-09 14:24:35 +02:00
$o -> add_instance ( offset => Slic3r::Pointf -> new ( @ { $self -> { config } -> print_center }));
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 ;
}
2014-01-05 14:04:32 +01:00
$self -> objects_loaded ( \ @obj_idx , no_arrange => ! $need_arrange );
2012-05-01 00:30:46 +02:00
}
2014-01-05 14:04:32 +01:00
sub objects_loaded {
2012-05-01 00:30:46 +02:00
my $self = shift ;
2014-01-05 14:04:32 +01:00
my ( $obj_idxs , %params ) = @_ ;
2012-05-01 00:30:46 +02:00
2014-01-05 14:04:32 +01:00
foreach my $obj_idx ( @$obj_idxs ) {
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 );
}
2012-05-01 00:30:46 +02:00
$self -> arrange unless $params { no_arrange };
2013-12-18 18:54:11 +01:00
$self -> update ;
2012-04-30 14:56:01 +02:00
$self -> { list } -> Update ;
2014-01-05 14:04:32 +01:00
$self -> { list } -> Select ( $obj_idxs -> [ - 1 ], 1 );
2012-04-30 14:56:01 +02:00
$self -> object_list_changed ;
2014-06-13 11:19:53 +02:00
2014-06-13 20:36:45 +02:00
$self -> schedule_background_process ;
2012-04-30 14:56:01 +02:00
}
sub remove {
my $self = shift ;
2012-08-11 16:00:41 +02:00
my ( $obj_idx ) = @_ ;
2012-04-30 14:56:01 +02: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 ;
2012-04-30 14:56:01 +02:00
$self -> { canvas } -> Refresh ;
}
sub reset {
my $self = shift ;
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 );
2012-04-30 14:56:01 +02:00
$self -> { canvas } -> Refresh ;
}
sub increase {
my $self = shift ;
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 ];
my $last_instance = $model_object -> instances -> [ - 1 ];
2013-12-15 16:17:12 +01:00
my $i = $model_object -> add_instance (
2014-05-09 14:24:35 +02:00
offset => Slic3r::Pointf -> new ( map 10 + $_ , @ { $last_instance -> offset }),
2013-12-12 20:19:33 +01:00
scaling_factor => $last_instance -> scaling_factor ,
rotation => $last_instance -> rotation ,
);
2013-12-15 16:17:12 +01:00
$self -> { print } -> objects -> [ $obj_idx ] -> add_copy ( @ { $i -> 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
if ( $ Slic3r::GUI:: Settings -> { _ }{ autocenter }) {
$self -> arrange ;
} else {
$self -> { canvas } -> Refresh ;
}
2012-04-30 14:56:01 +02:00
}
sub decrease {
my $self = shift ;
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 ];
if ( $model_object -> instances_count >= 2 ) {
$model_object -> delete_last_instance ;
2013-12-15 16:17:12 +01:00
$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 ;
2012-09-12 16:30:44 +02:00
$self -> { canvas } -> Refresh ;
2012-04-30 14:56:01 +02:00
}
sub rotate {
my $self = shift ;
my ( $angle ) = @_ ;
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 ];
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 ) {
2013-12-12 20:19:33 +01:00
$angle = Wx:: GetNumberFromUser ( "" , "Enter the rotation angle:" , "Rotate" , $model_instance -> rotation , - 364 , 364 , $self );
2012-04-30 22:14:27 +02:00
return if ! $angle || $angle == - 1 ;
2013-06-13 10:27:47 +02:00
$angle = 0 - $angle ; # rotate clockwise (be consistent with button icon)
2012-04-30 22:14:27 +02:00
}
2013-12-12 20:19:33 +01:00
{
my $new_angle = $model_instance -> rotation + $angle ;
2014-04-30 02:04:49 +03:00
$_ -> set_rotation ( $new_angle ) for @ { $model_object -> instances };
2013-12-12 20:19:33 +01:00
$model_object -> update_bounding_box ;
2013-12-16 00:36:53 +01:00
2014-06-13 20:05:18 +02:00
# update print and start background processing
2014-06-14 18:58:56 +02:00
$self -> stop_background_process ;
2014-05-07 01:11:49 +02:00
$self -> { print } -> add_model_object ( $model_object , $obj_idx );
2014-06-13 20:36:45 +02:00
$self -> schedule_background_process ;
2013-12-16 00:36:53 +01:00
2013-12-12 20:19:33 +01:00
$object -> transform_thumbnail ( $self -> { model }, $obj_idx );
}
$self -> selection_changed ; # refresh info (size etc.)
2013-12-18 18:54:11 +01:00
$self -> update ;
2012-04-30 14:56:01 +02:00
$self -> { canvas } -> Refresh ;
}
sub changescale {
my $self = shift ;
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 ];
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 ;
2012-06-29 20:46:51 +02:00
# max scale factor should be above 2540 to allow importing files exported in inches
2013-12-12 20:19:33 +01:00
my $scale = Wx:: GetNumberFromUser ( "" , "Enter the scale % for the selected object:" , "Scale" , $model_instance -> scaling_factor * 100 , 0 , 100000 , $self );
2012-04-30 14:56:01 +02:00
return if ! $scale || $scale == - 1 ;
2012-09-12 16:30:44 +02:00
$self -> { list } -> SetItem ( $obj_idx , 2 , "$scale%" );
2013-12-12 20:19:33 +01:00
$scale /= 100 ; # turn percent into factor
{
my $variation = $scale / $model_instance -> scaling_factor ;
foreach my $range ( @ { $model_object -> layer_height_ranges }) {
$range -> [ 0 ] *= $variation ;
$range -> [ 1 ] *= $variation ;
}
2014-04-30 02:04:49 +03:00
$_ -> set_scaling_factor ( $scale ) for @ { $model_object -> instances };
2013-12-12 20:19:33 +01:00
$model_object -> update_bounding_box ;
2013-12-16 00:36:53 +01:00
2014-06-13 20:05:18 +02:00
# update print and start background processing
2014-06-14 18:58:56 +02:00
$self -> stop_background_process ;
2014-05-07 01:11:49 +02:00
$self -> { print } -> add_model_object ( $model_object , $obj_idx );
2014-06-13 20:36:45 +02:00
$self -> schedule_background_process ;
2013-12-16 00:36:53 +01:00
2013-12-12 20:19:33 +01:00
$object -> transform_thumbnail ( $self -> { model }, $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 ;
$self -> { canvas } -> Refresh ;
2012-08-29 19:37:27 +02:00
}
sub arrange {
my $self = shift ;
2013-12-24 00:30:51 +01:00
# get the bounding box of the model area shown in the viewport
my $bb = Slic3r::Geometry::BoundingBox -> new_from_points ([
2014-05-28 12:29:43 +02:00
Slic3r::Point -> new ( @ { $self -> { canvas } -> point_to_model_units ([ 0 , 0 ]) }),
Slic3r::Point -> new ( @ { $self -> { canvas } -> point_to_model_units ( CANVAS_SIZE ) }),
2013-12-24 00:30:51 +01:00
]);
2012-08-29 19:37:27 +02:00
eval {
2014-06-14 19:54:18 +02:00
$self -> { model } -> arrange_objects ( $self -> GetFrame -> config -> min_object_distance , $bb );
2012-08-29 19:37:27 +02:00
};
2013-12-24 00:30:51 +01:00
# ignore arrange failures on purpose: user has visual feedback and we don't need to warn him
# when parts don't fit in print bed
2012-08-29 19:37:27 +02:00
2013-12-18 19:11:20 +01:00
$self -> update ( 1 );
2012-08-29 19:37:27 +02:00
$self -> { canvas } -> Refresh ;
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 ;
my $current_model_object = $self -> { model } -> objects -> [ $obj_idx ];
2012-10-21 20:56:19 +02:00
2013-12-12 20:19:33 +01:00
if ( @ { $current_model_object -> volumes } > 1 ) {
2013-06-02 16:58:23 +02:00
Slic3r::GUI:: warning_catcher ( $self ) -> ( "The selected object couldn't be split because it contains more than one volume/material." );
2012-10-21 20:56:19 +02:00
return ;
}
2013-12-12 20:19:33 +01:00
my @new_meshes = @ { $current_model_object -> volumes -> [ 0 ] -> mesh -> split };
2012-06-06 19:00:34 +02:00
if ( @new_meshes == 1 ) {
2013-06-02 16:58:23 +02:00
Slic3r::GUI:: warning_catcher ( $self ) -> ( "The selected object couldn't be split because it already contains a single part." );
2012-06-06 19:00:34 +02:00
return ;
}
2012-08-10 16:05:16 +02:00
2014-06-14 18:58:56 +02:00
$self -> stop_background_process ;
2014-06-13 23:27:52 +02:00
2012-10-27 21:39:57 +02:00
# create a bogus Model object, we only need to instantiate the new Model::Object objects
my $new_model = Slic3r::Model -> new ;
2014-03-24 21:42:38 +01:00
my @model_objects = ();
2012-05-04 10:17:36 +02:00
foreach my $mesh ( @new_meshes ) {
2014-01-14 22:06:28 +01:00
$mesh -> repair ;
2013-12-12 20:19:33 +01:00
my $model_object = $new_model -> add_object (
input_file => $current_model_object -> input_file ,
config => $current_model_object -> config -> clone ,
layer_height_ranges => $current_model_object -> layer_height_ranges , # TODO: clone this
);
$model_object -> add_volume (
mesh => $mesh ,
material_id => $current_model_object -> volumes -> [ 0 ] -> material_id ,
2012-09-12 16:30:44 +02:00
);
2013-11-24 14:28:17 +01:00
2013-12-12 20:19:33 +01:00
for my $instance_idx ( 0 .. $# { $current_model_object -> instances }) {
my $current_instance = $current_model_object -> instances -> [ $instance_idx ];
$model_object -> add_instance (
2014-05-09 14:24:35 +02:00
offset => Slic3r::Pointf -> new (
2013-12-12 20:19:33 +01:00
$current_instance -> offset -> [ X ] + ( $instance_idx * 10 ),
$current_instance -> offset -> [ Y ] + ( $instance_idx * 10 ),
2014-05-09 14:24:35 +02:00
),
2013-12-12 20:19:33 +01:00
rotation => $current_instance -> rotation ,
scaling_factor => $current_instance -> scaling_factor ,
);
}
# we need to center this single object around origin
$model_object -> center_around_origin ;
2014-03-24 21:42:38 +01:00
push @model_objects , $model_object ;
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 ) = @_ ;
$self -> { apply_config_timer } -> Start ( PROCESS_DELAY , 1 ); # 1 = one shot
}
2014-06-13 19:23:51 +02:00
sub async_apply_config {
2014-06-13 15:54:13 +02:00
my ( $self ) = @_ ;
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
$self -> suspend_background_process ;
2014-06-13 19:23:51 +02:00
# apply new config
2014-06-14 19:54:18 +02:00
my $invalidated = $self -> { print } -> apply_config ( $self -> GetFrame -> config );
2014-06-13 19:23:51 +02:00
2014-06-13 15:54:13 +02:00
return if ! $ Slic3r::GUI:: Settings -> { _ }{ background_processing };
2014-06-13 19:23:51 +02:00
if ( $invalidated ) {
# kill current thread if any
$self -> stop_background_process ;
} else {
# TODO: restore process thread
}
2014-06-13 20:36:45 +02:00
# schedule a new process thread in case it wasn't running
$self -> start_background_process ;
2014-06-13 15:54:13 +02:00
}
2014-06-13 11:19:53 +02:00
sub start_background_process {
my ( $self ) = @_ ;
2014-06-13 23:27:52 +02:00
$self -> resume_background_process ;
2014-06-13 11:19:53 +02:00
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 ;
};
return if $@ ;
# 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-06-13 11:19:53 +02:00
$self -> { process_thread } = threads -> create ( sub {
2014-06-13 14:27:55 +02:00
local $SIG { 'KILL' } = sub {
Slic3r:: debugf "Background process cancelled; exiting thread...\n" ;
Slic3r:: thread_cleanup ();
threads -> exit ();
};
2014-06-13 23:27:52 +02:00
local $SIG { 'STOP' } = sub {
$sema -> down ;
$sema -> up ;
};
2014-06-13 14:27:55 +02:00
eval {
$self -> { print } -> process ;
};
if ( $@ ) {
Slic3r:: debugf "Discarding background process error: $@\n" ;
Wx:: PostEvent ( $self , Wx::PlThreadEvent -> new ( - 1 , $PROCESS_COMPLETED_EVENT , 0 ));
} else {
Wx:: PostEvent ( $self , Wx::PlThreadEvent -> new ( - 1 , $PROCESS_COMPLETED_EVENT , 1 ));
}
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-13 19:23:51 +02:00
$self -> { apply_config_timer } -> Stop ;
2014-06-13 14:27:55 +02:00
$self -> statusbar -> SetCancelCallback ( undef );
$self -> statusbar -> StopBusy ;
$self -> statusbar -> SetStatusText ( "" );
2014-06-13 11:19:53 +02:00
if ( $self -> { process_thread }) {
Slic3r:: debugf "Killing background process.\n" ;
$self -> { process_thread } -> kill ( 'KILL' ) -> join ;
$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" ;
$self -> { export_thread } -> kill ( 'KILL' ) -> join ;
$self -> { export_thread } = undef ;
}
2014-06-13 11:19:53 +02:00
}
2014-06-13 23:27:52 +02:00
sub suspend_background_process {
my ( $self ) = @_ ;
$sema -> down ;
$_ -> kill ( 'STOP' ) for grep $_ , $self -> { process_thread }, $self -> { export_thread };
}
sub resume_background_process {
my ( $self ) = @_ ;
$sema -> up ;
}
2012-04-30 14:56:01 +02:00
sub export_gcode {
my $self = shift ;
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-06-13 14:27:55 +02:00
$self -> { export_gcode_output_file } = $ main:: opt { output };
2012-05-05 21:08:15 +02:00
{
2014-06-13 14:27:55 +02:00
my $default_output_file = $self -> { print } -> expanded_output_filepath ( $self -> { export_gcode_output_file });
2014-06-14 19:54:18 +02:00
my $dlg = Wx::FileDialog -> new ( $self , 'Save G-code file as:' , & Wx::wxTheApp -> output_path ( dirname ( $default_output_file )),
basename ( $default_output_file ), & Slic3r::GUI::FILE_WILDCARDS -> { gcode }, wxFD_SAVE );
2012-05-05 21:08:15 +02:00
if ( $dlg -> ShowModal != wxID_OK ) {
$dlg -> Destroy ;
2014-06-13 14:27:55 +02:00
$self -> { export_gcode_output_file } = undef ;
2012-05-05 21:08:15 +02:00
return ;
}
2013-04-27 21:07:30 +02:00
$ Slic3r::GUI:: Settings -> { _ }{ last_output_path } = dirname ( $dlg -> GetPath );
2014-06-14 19:54:18 +02:00
& Wx::wxTheApp -> save_settings ;
$self -> { export_gcode_output_file } = $ Slic3r::GUI::MainFrame:: last_output_file = $dlg -> GetPath ;
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" );
});
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-06-13 14:27:55 +02:00
# This gets called only if we have threads.
sub on_process_completed {
my ( $self , $result ) = @_ ;
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-06-13 14:27:55 +02:00
$self -> statusbar -> SetStatusText ( "" );
Slic3r:: debugf "Background processing completed.\n" ;
$self -> { process_thread } -> detach if $self -> { process_thread };
$self -> { process_thread } = undef ;
return if ! $result ;
# 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-06-13 14:27:55 +02:00
$self -> { export_thread } = threads -> create ( sub {
local $SIG { 'KILL' } = sub {
Slic3r:: debugf "Export process cancelled; exiting thread...\n" ;
Slic3r:: thread_cleanup ();
threads -> exit ();
};
2014-06-13 23:27:52 +02:00
local $SIG { 'STOP' } = sub {
$sema -> down ;
$sema -> up ;
};
2014-06-13 14:27:55 +02:00
eval {
2014-06-13 19:23:51 +02:00
$_thread_self -> { print } -> export_gcode ( output_file => $_thread_self -> { export_gcode_output_file });
2014-06-13 14:27:55 +02:00
};
if ( $@ ) {
2014-06-13 19:23:51 +02:00
Wx:: PostEvent ( $_thread_self , Wx::PlThreadEvent -> new ( - 1 , $ERROR_EVENT , shared_clone ([ $@ ])));
Wx:: PostEvent ( $_thread_self , Wx::PlThreadEvent -> new ( - 1 , $EXPORT_COMPLETED_EVENT , 0 ));
2014-06-13 14:27:55 +02:00
} else {
2014-06-13 19:23:51 +02:00
Wx:: PostEvent ( $_thread_self , Wx::PlThreadEvent -> new ( - 1 , $EXPORT_COMPLETED_EVENT , 1 ));
2014-06-13 14:27:55 +02:00
}
Slic3r:: thread_cleanup ();
});
Slic3r:: debugf "Background G-code export started.\n" ;
}
}
# This gets called also if we have no threads.
sub on_progress_event {
my ( $self , $percent , $message ) = @_ ;
$self -> statusbar -> SetProgress ( $percent );
$self -> statusbar -> SetStatusText ( "$message…" );
}
# This gets called also if we don't have threads.
sub on_export_completed {
my ( $self , $result ) = @_ ;
$self -> statusbar -> SetCancelCallback ( undef );
$self -> statusbar -> StopBusy ;
$self -> statusbar -> SetStatusText ( "" );
Slic3r:: debugf "Background export process completed.\n" ;
$self -> { export_thread } -> detach if $self -> { export_thread };
$self -> { export_thread } = undef ;
my $message ;
if ( $result ) {
$message = "G-code file exported to " . $self -> { export_gcode_output_file };
} else {
$message = "Export failed" ;
}
$self -> { export_gcode_output_file } = undef ;
2013-12-16 00:54:59 +01:00
$self -> statusbar -> SetStatusText ( $message );
2012-07-15 23:57:31 +02:00
& Wx::wxTheApp -> notify ( $message );
2012-05-05 21:08:15 +02:00
}
2012-04-30 17:10:54 +02:00
sub export_stl {
my $self = shift ;
2012-08-29 17:11:56 +02:00
my $output_file = $self -> _get_export_file ( 'STL' ) or return ;
2013-12-12 20:19:33 +01:00
Slic3r::Format::STL -> write_file ( $output_file , $self -> { model }, binary => 1 );
2012-08-29 17:11:56 +02:00
$self -> statusbar -> SetStatusText ( "STL file exported to $output_file" );
2013-12-22 01:27:09 +01:00
# this method gets executed in a separate thread by wxWidgets since it's a button handler
Slic3r:: thread_cleanup () if $ Slic3r:: have_threads ;
2012-08-29 17:11:56 +02:00
}
sub export_amf {
my $self = shift ;
my $output_file = $self -> _get_export_file ( 'AMF' ) or return ;
2013-12-12 20:19:33 +01:00
Slic3r::Format::AMF -> write_file ( $output_file , $self -> { model });
2012-08-29 17:11:56 +02:00
$self -> statusbar -> SetStatusText ( "AMF file exported to $output_file" );
2013-12-22 01:27:09 +01:00
# this method gets executed in a separate thread by wxWidgets since it's a menu handler
Slic3r:: thread_cleanup () if $ Slic3r:: have_threads ;
2012-08-29 17:11:56 +02:00
}
sub _get_export_file {
my $self = shift ;
my ( $format ) = @_ ;
my $suffix = $format eq 'STL' ? '.stl' : '.amf.xml' ;
2012-04-30 17:10:54 +02:00
my $output_file = $ main:: opt { output };
{
2014-03-17 00:39:07 +01:00
$output_file = $self -> { print } -> expanded_output_filepath ( $output_file );
2012-08-29 17:11:56 +02:00
$output_file =~ s/\.gcode$/$suffix/i ;
my $dlg = Wx::FileDialog -> new ( $self , "Save $format file as:" , dirname ( $output_file ),
2014-06-14 19:54:18 +02:00
basename ( $output_file ), & Slic3r::GUI:: MODEL_WILDCARD , wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
2012-04-30 17:10:54 +02:00
if ( $dlg -> ShowModal != wxID_OK ) {
$dlg -> Destroy ;
2012-08-29 17:11:56 +02:00
return undef ;
2012-04-30 17:10:54 +02:00
}
2014-06-14 19:54:18 +02:00
$output_file = $ Slic3r::GUI::MainFrame:: last_output_file = $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 );
2013-12-18 18:54:11 +01:00
$self -> update ;
2012-04-30 22:34:41 +02:00
$self -> { canvas } -> Refresh ;
}
2013-12-15 23:50:05 +01:00
# this method gets called whenever print center is changed or the objects' bounding box changes
2013-12-12 20:19:33 +01:00
# (i.e. when an object is added/removed/moved/rotated/scaled)
2013-12-18 18:54:11 +01:00
sub update {
2013-12-18 19:11:20 +01:00
my ( $self , $force_autocenter ) = @_ ;
2012-04-30 14:56:01 +02:00
2013-12-18 19:11:20 +01:00
if ( $ Slic3r::GUI:: Settings -> { _ }{ autocenter } || $force_autocenter ) {
$self -> { model } -> center_instances_around_point ( $self -> { config } -> print_center );
}
2013-12-18 18:54:11 +01:00
# sync model and print object instances
for my $obj_idx ( 0 .. $# { $self -> { objects }}) {
my $model_object = $self -> { model } -> objects -> [ $obj_idx ];
my $print_object = $self -> { print } -> objects -> [ $obj_idx ];
$print_object -> delete_all_copies ;
$print_object -> add_copy ( @ { $_ -> offset }) for @ { $model_object -> instances };
}
2014-02-14 09:18:23 +01:00
$self -> { canvas } -> Refresh ;
2012-04-30 14:56:01 +02:00
}
2012-07-27 21:13:03 +02:00
sub on_config_change {
my $self = shift ;
my ( $opt_key , $value ) = @_ ;
2014-06-13 11:19:53 +02:00
2012-08-07 20:14:28 +02:00
if ( $opt_key eq 'extruders_count' && defined $value ) {
my $choices = $self -> { preset_choosers }{ filament };
while ( @$choices < $value ) {
2012-10-24 20:24:11 +02:00
my @presets = $choices -> [ 0 ] -> GetStrings ;
push @$choices , Wx::Choice -> new ( $self , - 1 , wxDefaultPosition , [ 150 , - 1 ], [ @presets ]);
2013-08-25 18:04:45 +02:00
$choices -> [ - 1 ] -> SetFont ( $ Slic3r::GUI:: small_font );
2012-08-07 20:14:28 +02:00
$self -> { preset_choosers_sizers }{ filament } -> Add ( $choices -> [ - 1 ], 0 , wxEXPAND | wxBOTTOM , FILAMENT_CHOOSERS_SPACING );
2012-10-24 20:24:11 +02:00
EVT_CHOICE ( $choices -> [ - 1 ], $choices -> [ - 1 ], sub { $self -> on_select_preset ( 'filament' , @_ ) });
my $i = first { $choices -> [ - 1 ] -> GetString ( $_ ) eq ( $ Slic3r::GUI:: Settings -> { presets }{ "filament_" . $#$choices } || '' ) } 0 .. $#presets ;
$choices -> [ - 1 ] -> SetSelection ( $i || 0 );
2012-08-07 20:14:28 +02:00
}
while ( @$choices > $value ) {
$self -> { preset_choosers_sizers }{ filament } -> Remove ( - 1 );
$choices -> [ - 1 ] -> Destroy ;
pop @$choices ;
}
$self -> Layout ;
2012-08-09 00:41:03 +02:00
} elsif ( $self -> { config } -> has ( $opt_key )) {
2012-08-06 19:18:31 +02:00
$self -> { config } -> set ( $opt_key , $value );
2014-05-28 12:29:43 +02:00
if ( $opt_key eq 'bed_size' ) {
$self -> { canvas } -> update_bed_size ;
$self -> update ;
}
2013-12-18 18:54:11 +01:00
$self -> update if $opt_key eq 'print_center' ;
2012-08-06 19:18:31 +02:00
}
2014-06-13 11:19:53 +02:00
2014-06-14 19:54:18 +02:00
return if ! $self -> GetFrame -> is_loaded ;
2014-06-13 15:54:13 +02:00
2014-06-13 19:23:51 +02:00
# (re)start timer
2014-06-13 20:36:45 +02:00
$self -> schedule_background_process ;
2012-07-27 21:13:03 +02:00
}
2012-04-30 14:56:01 +02:00
sub list_item_deselected {
my ( $self , $event ) = @_ ;
2014-03-25 15:30:56 +01:00
return if $PreventListEvents ;
2012-04-30 14:56:01 +02:00
if ( $self -> { list } -> GetFirstSelected == - 1 ) {
2013-12-12 20:19:33 +01:00
$self -> select_object ( undef );
2012-04-30 14:56:01 +02:00
$self -> { canvas } -> Refresh ;
}
}
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 );
2012-04-30 14:56:01 +02:00
$self -> { canvas } -> Refresh ;
}
2012-10-24 22:44:08 +02:00
sub list_item_activated {
my ( $self , $event , $obj_idx ) = @_ ;
$obj_idx // = $event -> GetIndex ;
2014-04-25 17:14:39 +02:00
$self -> object_cut_dialog ( $obj_idx );
2013-08-25 15:45:22 +02:00
}
2014-04-25 14:54:08 +02:00
sub object_cut_dialog {
2013-08-25 15:45:22 +02:00
my $self = shift ;
my ( $obj_idx ) = @_ ;
if ( ! defined $obj_idx ) {
( $obj_idx , undef ) = $self -> selected_object ;
}
2013-09-16 10:18:42 +02:00
if ( ! $ Slic3r::GUI:: have_OpenGL ) {
Slic3r::GUI:: show_error ( $self , "Please install the OpenGL modules to use this feature (see build instructions)." );
return ;
}
2014-04-25 14:54:08 +02:00
my $dlg = Slic3r::GUI::Plater::ObjectCutDialog -> new ( $self ,
object => $self -> { objects }[ $obj_idx ],
model_object => $self -> { model } -> objects -> [ $obj_idx ],
2013-08-25 18:01:59 +02:00
);
$dlg -> ShowModal ;
2014-04-25 14:54:08 +02:00
if ( my @new_objects = $dlg -> NewModelObjects ) {
$self -> remove ( $obj_idx );
$self -> load_model_objects ( @new_objects );
2014-04-25 17:14:39 +02:00
$self -> arrange ;
2014-04-25 14:54:08 +02:00
}
2013-08-25 18:01:59 +02:00
}
sub object_settings_dialog {
my $self = shift ;
my ( $obj_idx ) = @_ ;
if ( ! defined $obj_idx ) {
( $obj_idx , undef ) = $self -> selected_object ;
}
2014-03-23 16:45:55 +01:00
my $model_object = $self -> { model } -> objects -> [ $obj_idx ];
2013-08-25 18:01:59 +02:00
2013-08-25 22:24:43 +02:00
# validate config before opening the settings dialog because
# that dialog can't be closed if validation fails, but user
# can't fix any error which is outside that dialog
return unless $self -> validate_config ;
2013-08-25 18:01:59 +02:00
my $dlg = Slic3r::GUI::Plater::ObjectSettingsDialog -> new ( $self ,
2013-12-12 20:19:33 +01:00
object => $self -> { objects }[ $obj_idx ],
2014-03-23 16:45:55 +01:00
model_object => $model_object ,
2012-10-24 22:44:08 +02:00
);
$dlg -> ShowModal ;
2014-03-23 16:45:55 +01:00
# update thumbnail since parts may have changed
if ( $dlg -> PartsChanged ) {
$self -> make_thumbnail ( $obj_idx );
}
# update print
if ( $dlg -> PartsChanged || $dlg -> PartSettingsChanged ) {
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-03-23 16:45:55 +01:00
}
2012-10-24 22:44:08 +02:00
}
2012-04-30 14:56:01 +02:00
sub object_list_changed {
my $self = shift ;
2013-08-25 18:01:59 +02:00
my $have_objects = @ { $self -> { objects }} ? 1 : 0 ;
my $method = $have_objects ? 'Enable' : 'Disable' ;
2012-05-01 00:30:46 +02:00
$self -> { "btn_$_" } -> $method
2012-05-04 11:22:56 +02:00
for grep $self -> { "btn_$_" }, qw(reset arrange export_gcode export_stl) ;
2013-08-25 18:01:59 +02:00
if ( $self -> { htoolbar }) {
$self -> { htoolbar } -> EnableTool ( $_ , $have_objects )
for ( TB_RESET , TB_ARRANGE );
}
2012-04-30 14:56:01 +02:00
}
sub selection_changed {
my $self = shift ;
2013-12-12 20:19:33 +01:00
my ( $obj_idx , $object ) = $self -> selected_object ;
my $have_sel = defined $obj_idx ;
2012-04-30 14:56:01 +02:00
my $method = $have_sel ? 'Enable' : 'Disable' ;
2012-05-01 00:30:46 +02:00
$self -> { "btn_$_" } -> $method
2013-08-25 18:01:59 +02:00
for grep $self -> { "btn_$_" }, qw(remove increase decrease rotate45cw rotate45ccw rotate changescale split view settings) ;
2012-05-04 11:22:56 +02:00
if ( $self -> { htoolbar }) {
2012-08-02 21:49:26 +02:00
$self -> { htoolbar } -> EnableTool ( $_ , $have_sel )
2013-08-25 18:01:59 +02:00
for ( TB_REMOVE , TB_MORE , TB_FEWER , TB_45CW , TB_45CCW , TB_ROTATE , TB_SCALE , TB_SPLIT , TB_VIEW , TB_SETTINGS );
2013-08-25 12:22:05 +02:00
}
if ( $self -> { object_info_size }) { # have we already loaded the info pane?
if ( $have_sel ) {
2013-12-12 20:19:33 +01:00
my $model_object = $self -> { model } -> objects -> [ $obj_idx ];
my $model_instance = $model_object -> instances -> [ 0 ];
$self -> { object_info_size } -> SetLabel ( sprintf ( "%.2f x %.2f x %.2f" , @ { $model_object -> instance_bounding_box ( 0 ) -> size }));
$self -> { object_info_materials } -> SetLabel ( $model_object -> materials_count );
2013-08-25 12:22:05 +02:00
2013-12-12 20:19:33 +01:00
if ( my $stats = $model_object -> mesh_stats ) {
$self -> { object_info_volume } -> SetLabel ( sprintf ( '%.2f' , $stats -> { volume } * ( $model_instance -> scaling_factor ** 3 )));
$self -> { object_info_facets } -> SetLabel ( sprintf ( '%d (%d shells)' , $model_object -> facets_count , $stats -> { number_of_parts }));
2013-08-25 17:26:55 +02:00
if ( my $errors = sum ( @$stats { qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges) })) {
$self -> { object_info_manifold } -> SetLabel ( sprintf ( "Auto-repaired (%d errors)" , $errors ));
$self -> { object_info_manifold_warning_icon } -> Show ;
2013-08-25 18:01:59 +02:00
# we don't show normals_fixed because we never provide normals
# to admesh, so it generates normals for all facets
2013-08-25 17:26:55 +02:00
my $message = sprintf '%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d facets reversed, %d backwards edges' ,
@$stats { qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges) };
$self -> { object_info_manifold } -> SetToolTipString ( $message );
$self -> { object_info_manifold_warning_icon } -> SetToolTipString ( $message );
} else {
$self -> { object_info_manifold } -> SetLabel ( "Yes" );
}
} else {
$self -> { object_info_facets } -> SetLabel ( $object -> facets );
2013-08-25 12:22:05 +02:00
}
} else {
2013-08-25 17:26:55 +02:00
$self -> { "object_info_$_" } -> SetLabel ( "" ) for qw(size volume facets materials manifold) ;
$self -> { object_info_manifold_warning_icon } -> Hide ;
$self -> { object_info_manifold } -> SetToolTipString ( "" );
2013-08-25 12:22:05 +02:00
}
2013-08-25 17:26:55 +02:00
$self -> Layout ;
2012-05-04 11:22:56 +02:00
}
2014-06-14 18:58:56 +02:00
# prepagate the event to the frame (a custom Wx event would be cleaner)
2014-06-14 19:11:04 +02:00
$self -> GetFrame -> on_plater_selection_changed ( $have_sel );
2012-04-30 14:56:01 +02:00
}
2013-12-12 20:19:33 +01:00
sub select_object {
my ( $self , $obj_idx ) = @_ ;
$_ -> selected ( 0 ) for @ { $self -> { objects } };
if ( defined $obj_idx ) {
$self -> { objects } -> [ $obj_idx ] -> selected ( 1 );
2014-03-25 15:30:56 +01:00
# We use this flag to avoid circular event handling
# Select() happens to fire a wxEVT_LIST_ITEM_SELECTED on Windows,
# whose event handler calls this method again and again and again
$PreventListEvents = 1 ;
2013-12-12 20:19:33 +01:00
$self -> { list } -> Select ( $obj_idx , 1 );
2014-03-25 15:30:56 +01:00
$PreventListEvents = 0 ;
2013-12-12 20:19:33 +01:00
} else {
# TODO: deselect all in list
}
$self -> selection_changed ( 1 );
}
2012-08-29 19:37:27 +02:00
sub selected_object {
2012-04-30 14:56:01 +02:00
my $self = shift ;
2013-12-12 20:19:33 +01:00
my $obj_idx = first { $self -> { objects }[ $_ ] -> selected } 0 .. $# { $self -> { objects } };
return undef if ! defined $obj_idx ;
2012-08-29 19:37:27 +02:00
return ( $obj_idx , $self -> { objects }[ $obj_idx ]),
2012-04-30 14:56:01 +02:00
}
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
}
2012-05-04 10:15:33 +02:00
package Slic3r::GUI::Plater::DropTarget ;
2012-04-30 14:56:01 +02:00
use Wx::DND ;
use base 'Wx::FileDropTarget' ;
sub new {
my $class = shift ;
my ( $window ) = @_ ;
my $self = $class -> SUPER:: new ;
$self -> { window } = $window ;
return $self ;
}
sub OnDropFiles {
my $self = shift ;
my ( $x , $y , $filenames ) = @_ ;
2012-07-21 14:45:45 +02:00
# stop scalars leaking on older perl
# https://rt.perl.org/rt3/Public/Bug/Display.html?id=70602
@_ = ();
2013-03-19 12:24:39 +01:00
# only accept STL, OBJ and AMF files
return 0 if grep ! /\.(?:stl|obj|amf(?:\.xml)?)$/i , @$filenames ;
2012-04-30 14:56:01 +02:00
$self -> { window } -> load_file ( $_ ) for @$filenames ;
}
2012-08-29 19:37:27 +02:00
package Slic3r::GUI::Plater::Object ;
use Moo ;
2013-07-13 20:34:57 +02:00
use List::Util qw(first) ;
2013-06-07 23:16:02 +02:00
use Slic3r::Geometry qw(X Y Z MIN MAX deg2rad) ;
2012-08-29 19:37:27 +02:00
has 'name' => ( is => 'rw' , required => 1 );
2013-12-12 20:19:33 +01:00
has 'thumbnail' => ( is => 'rw' ); # ExPolygon::Collection in scaled model units with no transforms
2013-06-12 16:53:19 +02:00
has 'transformed_thumbnail' => ( is => 'rw' );
2013-12-12 20:19:33 +01:00
has 'instance_thumbnails' => ( is => 'ro' , default => sub { [] }); # array of ExPolygon::Collection objects, each one representing the actual placed thumbnail of each instance in pixel units
has 'selected' => ( is => 'rw' , default => sub { 0 });
2012-08-29 19:37:27 +02:00
sub make_thumbnail {
2013-12-12 20:19:33 +01:00
my ( $self , $model , $obj_idx ) = @_ ;
2012-08-29 19:37:27 +02:00
2013-12-12 20:19:33 +01:00
my $mesh = $model -> objects -> [ $obj_idx ] -> raw_mesh ;
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 ;
$t -> rotate ( deg2rad ( $model_instance -> rotation ), Slic3r::Point -> new ( 0 , 0 ));
$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 ;