diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 7bd019070..e897a6353 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -26,6 +26,14 @@ our $appController; our $VALUE_CHANGE_EVENT = Wx::NewEventType; # 2) To inform about a preset selection change or a "modified" status change. our $PRESETS_CHANGED_EVENT = Wx::NewEventType; +# 3) To inform about a change of object selection +our $OBJECT_SELECTION_CHANGED_EVENT = Wx::NewEventType; +# 4) To inform about a change of object settings +our $OBJECT_SETTINGS_CHANGED_EVENT = Wx::NewEventType; +# 5) To inform about a remove of object +our $OBJECT_REMOVE_EVENT = Wx::NewEventType; +# 6) To inform about a update of the scene +our $UPDATE_SCENE_EVENT = Wx::NewEventType; sub new { my ($class, %params) = @_; @@ -114,6 +122,8 @@ sub new { $self->update_ui_from_settings; + Slic3r::GUI::update_mode(); + return $self; } @@ -133,7 +143,12 @@ sub _init_tabpanel { }); if (!$self->{no_plater}) { - $panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel), L("Plater")); + $panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel, + event_object_selection_changed => $OBJECT_SELECTION_CHANGED_EVENT, + event_object_settings_changed => $OBJECT_SETTINGS_CHANGED_EVENT, + event_remove_object => $OBJECT_REMOVE_EVENT, + event_update_scene => $UPDATE_SCENE_EVENT, + ), L("Plater")); if (!$self->{no_controller}) { $panel->AddPage($self->{controller} = Slic3r::GUI::Controller->new($panel), L("Controller")); } @@ -192,6 +207,40 @@ sub _init_tabpanel { } } }); + + # The following event is emited by the C++ Tab implementation on object selection change. + EVT_COMMAND($self, -1, $OBJECT_SELECTION_CHANGED_EVENT, sub { + my ($self, $event) = @_; + my $obj_idx = $event->GetId; + my $child = $event->GetInt == 1 ? 1 : undef; + + $self->{plater}->select_object($obj_idx < 0 ? undef: $obj_idx, $child); + $self->{plater}->item_changed_selection($obj_idx); + }); + + # The following event is emited by the C++ GUI implementation on object settings change. + EVT_COMMAND($self, -1, $OBJECT_SETTINGS_CHANGED_EVENT, sub { + my ($self, $event) = @_; + + my $line = $event->GetString; + my ($obj_idx, $parts_changed, $part_settings_changed) = split('',$line); + + $self->{plater}->changed_object_settings($obj_idx, $parts_changed, $part_settings_changed); + }); + + # The following event is emited by the C++ GUI implementation on object remove. + EVT_COMMAND($self, -1, $OBJECT_REMOVE_EVENT, sub { + my ($self, $event) = @_; + $self->{plater}->remove(); + }); + + # The following event is emited by the C++ GUI implementation on extruder change for object. + EVT_COMMAND($self, -1, $UPDATE_SCENE_EVENT, sub { + my ($self, $event) = @_; + $self->{plater}->update(); + }); + + Slic3r::GUI::create_preset_tabs($self->{no_controller}, $VALUE_CHANGE_EVENT, $PRESETS_CHANGED_EVENT); $self->{options_tabs} = {}; for my $tab_name (qw(print filament sla_material printer)) { diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index f4a2f99ff..2c2cf086c 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -49,7 +49,7 @@ my $PreventListEvents = 0; our $appController; sub new { - my ($class, $parent) = @_; + my ($class, $parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); $self->{config} = Slic3r::Config::new_from_defaults_keys([qw( bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height @@ -57,6 +57,13 @@ sub new { nozzle_diameter single_extruder_multi_material wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width wipe_tower_rotation_angle extruder_colour filament_colour max_print_height printer_model )]); + + # store input params + $self->{event_object_selection_changed} = $params{event_object_selection_changed}; + $self->{event_object_settings_changed} = $params{event_object_settings_changed}; + $self->{event_remove_object} = $params{event_remove_object}; + $self->{event_update_scene} = $params{event_update_scene}; + # C++ Slic3r::Model with Perl extensions in Slic3r/Model.pm $self->{model} = Slic3r::Model->new; # C++ Slic3r::Print with Perl extensions in Slic3r/Print.pm @@ -74,7 +81,7 @@ sub new { }); # Initialize preview notebook - $self->{preview_notebook} = Wx::Notebook->new($self, -1, wxDefaultPosition, [335,335], wxNB_BOTTOM); + $self->{preview_notebook} = Wx::Notebook->new($self, -1, wxDefaultPosition, [-1,335], wxNB_BOTTOM); # Initialize handlers for canvases my $on_select_object = sub { @@ -128,7 +135,9 @@ sub new { } $_->set_scaling_factor($scale) for @{ $model_object->instances }; - $self->{list}->SetItem($obj_idx, 2, ($model_object->instances->[0]->scaling_factor * 100) . "%"); + # Set object scale on c++ side + Slic3r::GUI::set_object_scale($obj_idx, $model_object->instances->[0]->scaling_factor * 100); + # $object->transform_thumbnail($self->{model}, $obj_idx); #update print and start background processing @@ -362,37 +371,12 @@ sub new { # } ### Panel for right column - $self->{right_panel} = Wx::Panel->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - - ### Scrolled Window for info boxes - my $scrolled_window_sizer = $self->{scrolled_window_sizer} = Wx::BoxSizer->new(wxVERTICAL); - $scrolled_window_sizer->SetMinSize([310, -1]); - my $scrolled_window_panel = $self->{scrolled_window_panel} = Wx::ScrolledWindow->new($self->{right_panel}, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - $scrolled_window_panel->SetSizer($scrolled_window_sizer); - $scrolled_window_panel->SetScrollbars(1, 1, 1, 1); - - $self->{list} = Wx::ListView->new($scrolled_window_panel, -1, wxDefaultPosition, wxDefaultSize, - wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS ); - $self->{list}->InsertColumn(0, L("Name"), wxLIST_FORMAT_LEFT, 145); - $self->{list}->InsertColumn(1, L("Copies"), wxLIST_FORMAT_CENTER, 45); - $self->{list}->InsertColumn(2, L("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); - EVT_LIST_ITEM_ACTIVATED($self, $self->{list}, \&list_item_activated); - EVT_KEY_DOWN($self->{list}, sub { - my ($list, $event) = @_; - if ($event->GetKeyCode == WXK_TAB) { - $list->Navigate($event->ShiftDown ? &Wx::wxNavigateBackward : &Wx::wxNavigateForward); - } elsif ($event->GetKeyCode == WXK_DELETE || - ($event->GetKeyCode == WXK_BACK && &Wx::wxMAC) ) { - $self->remove; - } else { - $event->Skip; - } - }); +# $self->{right_panel} = Wx::Panel->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + $self->{right_panel} = Wx::ScrolledWindow->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + $self->{right_panel}->SetScrollbars(0, 1, 1, 1); # right pane buttons - $self->{btn_export_gcode} = Wx::Button->new($self->{right_panel}, -1, L("Export G-code…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); + $self->{btn_export_gcode} = Wx::Button->new($self->{right_panel}, -1, L("Export G-code…"), wxDefaultPosition, [-1, 30], wxNO_BORDER);#, wxBU_LEFT); $self->{btn_reslice} = Wx::Button->new($self->{right_panel}, -1, L("Slice now"), wxDefaultPosition, [-1, 30], wxBU_LEFT); $self->{btn_print} = Wx::Button->new($self->{right_panel}, -1, L("Print…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); $self->{btn_send_gcode} = Wx::Button->new($self->{right_panel}, -1, L("Send to printer"), wxDefaultPosition, [-1, 30], wxBU_LEFT); @@ -460,6 +444,7 @@ sub new { # } else { # EVT_BUTTON($self, $self->{btn_add}, sub { $self->add; }); # EVT_BUTTON($self, $self->{btn_remove}, sub { $self->remove() }); # explicitly pass no argument to remove +# EVT_BUTTON($self, $self->{btn_remove}, sub { Slic3r::GUI::remove_obj() }); # explicitly pass no argument to remove # 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; }); @@ -476,7 +461,7 @@ sub new { $_->SetDropTarget(Slic3r::GUI::Plater::DropTarget->new($self)) for grep defined($_), $self, $self->{canvas3D}, $self->{preview3D}, $self->{list}; -# $self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}, $self->{list}; +# $self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}; EVT_COMMAND($self, -1, $PROGRESS_BAR_EVENT, sub { my ($self, $event) = @_; @@ -555,15 +540,32 @@ sub new { $presets->Layout; } - my $frequently_changed_parameters_sizer = $self->{frequently_changed_parameters_sizer} = Wx::BoxSizer->new(wxHORIZONTAL); + my $frequently_changed_parameters_sizer = $self->{frequently_changed_parameters_sizer} = Wx::BoxSizer->new(wxVERTICAL); Slic3r::GUI::add_frequently_changed_parameters($self->{right_panel}, $frequently_changed_parameters_sizer, $presets); + my $expert_mode_part_sizer = Wx::BoxSizer->new(wxVERTICAL); + Slic3r::GUI::add_expert_mode_part( $self->{right_panel}, $expert_mode_part_sizer, + $self->{model}, + $self->{event_object_selection_changed}, + $self->{event_object_settings_changed}, + $self->{event_remove_object}, + $self->{event_update_scene}); +# if ($expert_mode_part_sizer->IsShown(2)==1) +# { +# $expert_mode_part_sizer->Layout; +# $expert_mode_part_sizer->Show(2, 0); # ? Why doesn't work +# $self->{right_panel}->Layout; +# } + my $object_info_sizer; { - my $box = Wx::StaticBox->new($scrolled_window_panel, -1, L("Info")); +# my $box = Wx::StaticBox->new($scrolled_window_panel, -1, L("Info")); + my $box = Wx::StaticBox->new($self->{right_panel}, -1, L("Info")); + $box->SetFont($Slic3r::GUI::small_bold_font); $object_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); $object_info_sizer->SetMinSize([300,-1]); - my $grid_sizer = Wx::FlexGridSizer->new(3, 4, 5, 5); + #!my $grid_sizer = Wx::FlexGridSizer->new(3, 4, 5, 5); + my $grid_sizer = Wx::FlexGridSizer->new(2, 4, 5, 5); $grid_sizer->SetFlexibleDirection(wxHORIZONTAL); $grid_sizer->AddGrowableCol(1, 1); $grid_sizer->AddGrowableCol(3, 1); @@ -578,28 +580,45 @@ sub new { ); while (my $field = shift @info) { my $label = shift @info; - my $text = Wx::StaticText->new($scrolled_window_panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); +# my $text = Wx::StaticText->new($scrolled_window_panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + my $text = Wx::StaticText->new($self->{right_panel}, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $text->SetFont($Slic3r::GUI::small_font); - $grid_sizer->Add($text, 0); + #!$grid_sizer->Add($text, 0); - $self->{"object_info_$field"} = Wx::StaticText->new($scrolled_window_panel, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); +# $self->{"object_info_$field"} = Wx::StaticText->new($scrolled_window_panel, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + $self->{"object_info_$field"} = Wx::StaticText->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $self->{"object_info_$field"}->SetFont($Slic3r::GUI::small_font); if ($field eq 'manifold') { - $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($scrolled_window_panel, -1, Wx::Bitmap->new(Slic3r::var("error.png"), wxBITMAP_TYPE_PNG)); - $self->{object_info_manifold_warning_icon}->Hide; +# $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($scrolled_window_panel, -1, Wx::Bitmap->new(Slic3r::var("error.png"), wxBITMAP_TYPE_PNG)); + $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($self->{right_panel}, -1, Wx::Bitmap->new(Slic3r::var("error.png"), wxBITMAP_TYPE_PNG)); + #$self->{object_info_manifold_warning_icon}->Hide; + $self->{"object_info_manifold_warning_icon_show"} = sub { + if ($self->{object_info_manifold_warning_icon}->IsShown() != $_[0]) { + Slic3r::GUI::set_show_manifold_warning_icon($_[0]); + my $mode = wxTheApp->{app_config}->get("view_mode"); + return if ($mode eq "" || $mode eq "simple"); + $self->{object_info_manifold_warning_icon}->Show($_[0]); + $self->Layout + } + }; + $self->{"object_info_manifold_warning_icon_show"}->(0); 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); + $h_sizer->Add($text, 0); + $h_sizer->Add($self->{object_info_manifold_warning_icon}, 0, wxLEFT, 2); + $h_sizer->Add($self->{"object_info_$field"}, 0, wxLEFT, 2); + #!$grid_sizer->Add($h_sizer, 0, wxEXPAND); + $object_info_sizer->Add($h_sizer, 0, wxEXPAND|wxTOP, 4); } else { + $grid_sizer->Add($text, 0); $grid_sizer->Add($self->{"object_info_$field"}, 0); } } } my $print_info_sizer = $self->{print_info_sizer} = Wx::StaticBoxSizer->new( - Wx::StaticBox->new($scrolled_window_panel, -1, L("Sliced Info")), wxVERTICAL); +# Wx::StaticBox->new($scrolled_window_panel, -1, L("Sliced Info")), wxVERTICAL); + Wx::StaticBox->new($self->{right_panel}, -1, L("Sliced Info")), wxVERTICAL); $print_info_sizer->SetMinSize([300,-1]); my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL); @@ -609,22 +628,27 @@ sub new { $buttons_sizer->Add($self->{btn_reslice}, 0, wxALIGN_RIGHT, 0); $buttons_sizer->Add($self->{btn_print}, 0, wxALIGN_RIGHT, 0); $buttons_sizer->Add($self->{btn_send_gcode}, 0, wxALIGN_RIGHT, 0); - $buttons_sizer->Add($self->{btn_export_gcode}, 0, wxALIGN_RIGHT, 0); - $scrolled_window_sizer->Add($self->{list}, 1, wxEXPAND, 5); - $scrolled_window_sizer->Add($object_info_sizer, 0, wxEXPAND, 0); - $scrolled_window_sizer->Add($print_info_sizer, 0, wxEXPAND, 0); +# $scrolled_window_sizer->Add($self->{list}, 1, wxEXPAND, 5); +# $scrolled_window_sizer->Add($object_info_sizer, 0, wxEXPAND, 0); +# $scrolled_window_sizer->Add($print_info_sizer, 0, wxEXPAND, 0); + #$buttons_sizer->Add($self->{btn_export_gcode}, 0, wxALIGN_RIGHT, 0); + + ### Sizer for info boxes + my $info_sizer = $self->{info_sizer} = Wx::BoxSizer->new(wxVERTICAL); + $info_sizer->SetMinSize([318, -1]); + $info_sizer->Add($object_info_sizer, 0, wxEXPAND | wxBOTTOM, 5); + $info_sizer->Add($print_info_sizer, 0, wxEXPAND | wxBOTTOM, 5); my $right_sizer = Wx::BoxSizer->new(wxVERTICAL); - $right_sizer->SetMinSize([320,-1]); - $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets; - $right_sizer->Add($frequently_changed_parameters_sizer, 0, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; - $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5); - $right_sizer->Add($scrolled_window_panel, 1, wxEXPAND | wxALL, 1); - # Show the box initially, let it be shown after the slicing is finished. - $self->print_info_box_show(0); - $self->{right_panel}->SetSizer($right_sizer); + $right_sizer->SetMinSize([320, -1]); + $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets; + $right_sizer->Add($frequently_changed_parameters_sizer, 1, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; + $right_sizer->Add($expert_mode_part_sizer, 0, wxEXPAND | wxTOP, 10) if defined $expert_mode_part_sizer; + $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM | wxTOP, 10); + $right_sizer->Add($info_sizer, 0, wxEXPAND | wxLEFT, 20); + $right_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 20); my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); $hsizer->Add($self->{preview_notebook}, 1, wxEXPAND | wxTOP, 1); @@ -637,6 +661,18 @@ sub new { $sizer->SetSizeHints($self); $self->SetSizer($sizer); + + # Send sizers/buttons to C++ + Slic3r::GUI::set_objects_from_perl( $self->{right_panel}, + $frequently_changed_parameters_sizer, + $expert_mode_part_sizer, + $info_sizer, + $self->{btn_export_gcode}, + $self->{btn_export_stl}, + $self->{btn_reslice}, + $self->{btn_print}, + $self->{btn_send_gcode}, + $self->{object_info_manifold_warning_icon} ); } # Last correct selected item for each preset @@ -647,6 +683,7 @@ sub new { } $self->update_ui_from_settings(); + $self->Layout; return $self; } @@ -921,12 +958,10 @@ sub load_model_objects { foreach my $obj_idx (@obj_idx) { 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() + + # Add object to list on c++ side + Slic3r::GUI::add_object_to_list($object->name, $model_object); - $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); - $self->{list}->SetItem($obj_idx, 2, ($model_object->instances->[0]->scaling_factor * 100) . "%"); # $self->reset_thumbnail($obj_idx); } @@ -936,8 +971,6 @@ sub load_model_objects { # zoom to objects Slic3r::GUI::_3DScene::zoom_to_volumes($self->{canvas3D}) if $self->{canvas3D}; - $self->{list}->Update; - $self->{list}->Select($obj_idx[-1], 1); $self->object_list_changed; $self->schedule_background_process; @@ -972,7 +1005,8 @@ sub remove { splice @{$self->{objects}}, $obj_idx, 1; $self->{model}->delete_object($obj_idx); $self->{print}->delete_object($obj_idx); - $self->{list}->DeleteItem($obj_idx); + # Delete object from list on c++ side + Slic3r::GUI::delete_object_from_list(); $self->object_list_changed; $self->select_object(undef); @@ -992,7 +1026,8 @@ sub reset { @{$self->{objects}} = (); $self->{model}->clear_objects; $self->{print}->clear_objects; - $self->{list}->DeleteAllItems; + # Delete all objects from list on c++ side + Slic3r::GUI::delete_all_objects_from_list(); $self->object_list_changed; $self->select_object(undef); @@ -1014,7 +1049,8 @@ sub increase { ); $self->{print}->objects->[$obj_idx]->add_copy($instance->offset); } - $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); + # Set conut of object on c++ side + Slic3r::GUI::set_object_count($obj_idx, $model_object->instances_count); # only autoarrange if user has autocentering enabled $self->stop_background_process; @@ -1042,7 +1078,8 @@ sub decrease { $model_object->delete_last_instance; $self->{print}->objects->[$obj_idx]->delete_last_copy; } - $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); + # Set conut of object on c++ side + Slic3r::GUI::set_object_count($obj_idx, $model_object->instances_count); } elsif (defined $copies_asked) { # The "decrease" came from the "set number of copies" dialog. $self->remove; @@ -1051,11 +1088,7 @@ sub decrease { $self->resume_background_process; return; } - - if ($self->{objects}[$obj_idx]) { - $self->{list}->Select($obj_idx, 0); - $self->{list}->Select($obj_idx, 1); - } + $self->update; $self->schedule_background_process; } @@ -1153,6 +1186,7 @@ sub rotate { # $model_object->center_around_origin; # $self->reset_thumbnail($obj_idx); } + Slic3r::GUI::update_rotation_value(deg2rad($angle), $axis == X ? "x" : ($axis == Y ? "y" : "z")); # update print and start background processing $self->{print}->add_model_object($model_object, $obj_idx); @@ -1243,8 +1277,9 @@ sub changescale { $scale = $self->_get_number_from_user(L('Enter the scale % for the selected object:'), L('Scale'), L('Invalid scaling value entered'), $model_instance->scaling_factor*100, 1); return if ! defined($scale) || $scale eq ''; } - - $self->{list}->SetItem($obj_idx, 2, "$scale%"); + + # Set object scale on c++ side + Slic3r::GUI::set_object_scale($obj_idx, $scale); $scale /= 100; # turn percent into factor my $variation = $scale / $model_instance->scaling_factor; @@ -1687,9 +1722,15 @@ sub on_export_completed { # Fill in the "Sliced info" box with the result of the G-code generator. sub print_info_box_show { my ($self, $show) = @_; - my $scrolled_window_panel = $self->{scrolled_window_panel}; - my $scrolled_window_sizer = $self->{scrolled_window_sizer}; - return if (!$show && ($scrolled_window_sizer->IsShown(2) == $show)); +# my $scrolled_window_panel = $self->{scrolled_window_panel}; +# my $scrolled_window_sizer = $self->{scrolled_window_sizer}; +# return if (!$show && ($scrolled_window_sizer->IsShown(2) == $show)); + my $panel = $self->{right_panel}; + my $sizer = $self->{info_sizer}; + return if (!$show && ($sizer->IsShown(2) == $show)); + + Slic3r::GUI::set_show_print_info($show); + return if (wxTheApp->{app_config}->get("view_mode") eq "simple"); if ($show) { my $print_info_sizer = $self->{print_info_sizer}; @@ -1716,17 +1757,23 @@ sub print_info_box_show { while ( my $label = shift @info) { my $value = shift @info; next if $value eq "N/A"; - my $text = Wx::StaticText->new($scrolled_window_panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); +# my $text = Wx::StaticText->new($scrolled_window_panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); + my $text = Wx::StaticText->new($panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); $text->SetFont($Slic3r::GUI::small_font); $grid_sizer->Add($text, 0); - my $field = Wx::StaticText->new($scrolled_window_panel, -1, $value, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); +# my $field = Wx::StaticText->new($scrolled_window_panel, -1, $value, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + my $field = Wx::StaticText->new($panel, -1, $value, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $field->SetFont($Slic3r::GUI::small_font); $grid_sizer->Add($field, 0); } } - $scrolled_window_sizer->Show(2, $show); - $scrolled_window_panel->Layout; +# $scrolled_window_sizer->Show(2, $show); +# $scrolled_window_panel->Layout; + $sizer->Show(1, $show); + +#? $self->Layout; +#? $panel->Refresh; } sub do_print { @@ -2058,32 +2105,18 @@ sub on_config_change { $self->schedule_background_process; } -sub list_item_deselected { - my ($self, $event) = @_; - return if $PreventListEvents; - $self->{_lecursor} = Wx::BusyCursor->new(); - if ($self->{list}->GetFirstSelected == -1) { - $self->select_object(undef); -# $self->{canvas}->Refresh; - Slic3r::GUI::_3DScene::deselect_volumes($self->{canvas3D}) if $self->{canvas3D}; - Slic3r::GUI::_3DScene::render($self->{canvas3D}) if $self->{canvas3D}; - } - undef $self->{_lecursor}; -} +sub item_changed_selection{ + my ($self, $obj_idx) = @_; -sub list_item_selected { - my ($self, $event) = @_; - return if $PreventListEvents; - $self->{_lecursor} = Wx::BusyCursor->new(); - my $obj_idx = $event->GetIndex; - $self->select_object($obj_idx); # $self->{canvas}->Refresh; if ($self->{canvas3D}) { - my $selections = $self->collect_selections; - Slic3r::GUI::_3DScene::update_volumes_selection($self->{canvas3D}, \@$selections); + Slic3r::GUI::_3DScene::deselect_volumes($self->{canvas3D}); + if ($obj_idx >= 0){ + my $selections = $self->collect_selections; + Slic3r::GUI::_3DScene::update_volumes_selection($self->{canvas3D}, \@$selections); + } Slic3r::GUI::_3DScene::render($self->{canvas3D}); } - undef $self->{_lecursor}; } sub collect_selections { @@ -2095,6 +2128,7 @@ sub collect_selections { return $selections; } +# doesn't used now sub list_item_activated { my ($self, $event, $obj_idx) = @_; @@ -2195,6 +2229,31 @@ sub object_settings_dialog { } } +sub changed_object_settings { + my ($self, $obj_idx, $parts_changed, $part_settings_changed) = @_; + + # update thumbnail since parts may have changed + if ($parts_changed) { + # recenter and re-align to Z = 0 + my $model_object = $self->{model}->objects->[$obj_idx]; + $model_object->center_around_origin; + $self->reset_thumbnail($obj_idx); + } + + # update print + if ($parts_changed || $part_settings_changed) { + $self->stop_background_process; + $self->{print}->reload_object($obj_idx); + $self->schedule_background_process; + $self->{canvas}->reload_scene if $self->{canvas}; + my $selections = $self->collect_selections; + Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections); + Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 0); + } else { + $self->resume_background_process; + } +} + # 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. sub object_list_changed { @@ -2296,7 +2355,8 @@ sub selection_changed { $self->{object_info_facets}->SetLabel(sprintf(L('%d (%d shells)'), $model_object->facets_count, $stats->{number_of_parts})); if (my $errors = sum(@$stats{qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)})) { $self->{object_info_manifold}->SetLabel(sprintf(L("Auto-repaired (%d errors)"), $errors)); - $self->{object_info_manifold_warning_icon}->Show; + #$self->{object_info_manifold_warning_icon}->Show; + $self->{"object_info_manifold_warning_icon_show"}->(1); # we don't show normals_fixed because we never provide normals # to admesh, so it generates normals for all facets @@ -2306,7 +2366,8 @@ sub selection_changed { $self->{object_info_manifold_warning_icon}->SetToolTipString($message); } else { $self->{object_info_manifold}->SetLabel(L("Yes")); - $self->{object_info_manifold_warning_icon}->Hide; + #$self->{object_info_manifold_warning_icon}->Hide; + $self->{"object_info_manifold_warning_icon_show"}->(0); $self->{object_info_manifold}->SetToolTipString(""); $self->{object_info_manifold_warning_icon}->SetToolTipString(""); } @@ -2315,7 +2376,8 @@ sub selection_changed { } } else { $self->{"object_info_$_"}->SetLabel("") for qw(size volume facets materials manifold); - $self->{object_info_manifold_warning_icon}->Hide; + #$self->{object_info_manifold_warning_icon}->Hide; + $self->{"object_info_manifold_warning_icon_show"}->(0); $self->{object_info_manifold}->SetToolTipString(""); $self->{object_info_manifold_warning_icon}->SetToolTipString(""); } @@ -2328,26 +2390,21 @@ sub selection_changed { } sub select_object { - my ($self, $obj_idx) = @_; + my ($self, $obj_idx, $child) = @_; # remove current selection foreach my $o (0..$#{$self->{objects}}) { - $PreventListEvents = 1; $self->{objects}->[$o]->selected(0); - $self->{list}->Select($o, 0); - $PreventListEvents = 0; } - + if (defined $obj_idx) { $self->{objects}->[$obj_idx]->selected(1); - # 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; - $self->{list}->Select($obj_idx, 1); - $PreventListEvents = 0; + # Select current object in the list on c++ side, if item isn't child + if (!defined $child){ + Slic3r::GUI::select_current_object($obj_idx);} } else { - # TODO: deselect all in list + # Unselect all objects in the list on c++ side + Slic3r::GUI::unselect_objects(); } $self->selection_changed(1); } diff --git a/resources/icons/add_object.png b/resources/icons/add_object.png new file mode 100644 index 000000000..ffc958edc Binary files /dev/null and b/resources/icons/add_object.png differ diff --git a/resources/icons/colorchange_add_off.png b/resources/icons/colorchange_add_off.png new file mode 100644 index 000000000..6ddeccbe0 Binary files /dev/null and b/resources/icons/colorchange_add_off.png differ diff --git a/resources/icons/colorchange_add_on.png b/resources/icons/colorchange_add_on.png new file mode 100644 index 000000000..cc800b81e Binary files /dev/null and b/resources/icons/colorchange_add_on.png differ diff --git a/resources/icons/colorchange_delete_off.png b/resources/icons/colorchange_delete_off.png new file mode 100644 index 000000000..c16655271 Binary files /dev/null and b/resources/icons/colorchange_delete_off.png differ diff --git a/resources/icons/colorchange_delete_on.png b/resources/icons/colorchange_delete_on.png new file mode 100644 index 000000000..8f27ce9fe Binary files /dev/null and b/resources/icons/colorchange_delete_on.png differ diff --git a/resources/icons/disclosure_triangle_close.png b/resources/icons/disclosure_triangle_close.png new file mode 100644 index 000000000..0660422c7 Binary files /dev/null and b/resources/icons/disclosure_triangle_close.png differ diff --git a/resources/icons/disclosure_triangle_open.png b/resources/icons/disclosure_triangle_open.png new file mode 100644 index 000000000..81112f2a2 Binary files /dev/null and b/resources/icons/disclosure_triangle_open.png differ diff --git a/resources/icons/down_half_circle.png b/resources/icons/down_half_circle.png new file mode 100644 index 000000000..84aba5c3c Binary files /dev/null and b/resources/icons/down_half_circle.png differ diff --git a/resources/icons/erase.png b/resources/icons/erase.png new file mode 100644 index 000000000..4c4cfd755 Binary files /dev/null and b/resources/icons/erase.png differ diff --git a/resources/icons/exclamation_mark_.png b/resources/icons/exclamation_mark_.png new file mode 100644 index 000000000..5fe7c1355 Binary files /dev/null and b/resources/icons/exclamation_mark_.png differ diff --git a/resources/icons/lambda.png b/resources/icons/lambda.png new file mode 100644 index 000000000..3be73b142 Binary files /dev/null and b/resources/icons/lambda.png differ diff --git a/resources/icons/lambda_.png b/resources/icons/lambda_.png new file mode 100644 index 000000000..8e9d2b0ea Binary files /dev/null and b/resources/icons/lambda_.png differ diff --git a/resources/icons/left_half_circle.png b/resources/icons/left_half_circle.png new file mode 100644 index 000000000..c1e8d3a9c Binary files /dev/null and b/resources/icons/left_half_circle.png differ diff --git a/resources/icons/object.png b/resources/icons/object.png new file mode 100644 index 000000000..c85cbaf2f Binary files /dev/null and b/resources/icons/object.png differ diff --git a/resources/icons/one_layer_lock_off.png b/resources/icons/one_layer_lock_off.png new file mode 100644 index 000000000..7dc8b0611 Binary files /dev/null and b/resources/icons/one_layer_lock_off.png differ diff --git a/resources/icons/one_layer_lock_on.png b/resources/icons/one_layer_lock_on.png new file mode 100644 index 000000000..41b7ff173 Binary files /dev/null and b/resources/icons/one_layer_lock_on.png differ diff --git a/resources/icons/one_layer_unlock_off.png b/resources/icons/one_layer_unlock_off.png new file mode 100644 index 000000000..c3931f8b9 Binary files /dev/null and b/resources/icons/one_layer_unlock_off.png differ diff --git a/resources/icons/one_layer_unlock_on.png b/resources/icons/one_layer_unlock_on.png new file mode 100644 index 000000000..99e0db5c2 Binary files /dev/null and b/resources/icons/one_layer_unlock_on.png differ diff --git a/resources/icons/right_half_circle.png b/resources/icons/right_half_circle.png new file mode 100644 index 000000000..9f182b0b4 Binary files /dev/null and b/resources/icons/right_half_circle.png differ diff --git a/resources/icons/split.png b/resources/icons/split.png new file mode 100644 index 000000000..c5680ab91 Binary files /dev/null and b/resources/icons/split.png differ diff --git a/resources/icons/up_half_circle.png b/resources/icons/up_half_circle.png new file mode 100644 index 000000000..9eea07fda Binary files /dev/null and b/resources/icons/up_half_circle.png differ diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index fe33514a9..0d1f1bf13 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -211,6 +211,10 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/PresetHints.hpp ${LIBDIR}/slic3r/GUI/GUI.cpp ${LIBDIR}/slic3r/GUI/GUI.hpp + ${LIBDIR}/slic3r/GUI/GUI_ObjectParts.cpp + ${LIBDIR}/slic3r/GUI/GUI_ObjectParts.hpp + ${LIBDIR}/slic3r/GUI/LambdaObjectDialog.cpp + ${LIBDIR}/slic3r/GUI/LambdaObjectDialog.hpp ${LIBDIR}/slic3r/GUI/Tab.cpp ${LIBDIR}/slic3r/GUI/Tab.hpp ${LIBDIR}/slic3r/GUI/TabIface.cpp diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index d836866e2..1b9106d75 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -258,6 +258,11 @@ const Vec3d& GLVolume::get_origin() const return m_origin; } +float GLVolume::get_angle_z() +{ + return m_angle_z; +} + void GLVolume::set_origin(const Vec3d& origin) { if (m_origin != origin) diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index 1fbd958a8..9aa023fae 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -323,6 +323,7 @@ public: // Sets render color in dependence of current state void set_render_color(); + float get_angle_z(); const Vec3d& get_origin() const; void set_origin(const Vec3d& origin); void set_angle_z(float angle_z); diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index 87f6693a8..b9753fe93 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -132,7 +132,11 @@ namespace Slic3r { namespace GUI { break; } double val; - str.ToCDouble(&val); + if(!str.ToCDouble(&val)) + { + show_error(m_parent, _(L("Input value contains incorrect symbol(s).\nUse, please, only digits"))); + set_value(double_to_string(val), true); + } if (m_opt.min > val || val > m_opt.max) { show_error(m_parent, _(L("Input value is out of range"))); @@ -330,9 +334,7 @@ void SpinCtrl::BUILD() { break; } - const int min_val = m_opt_id == "standby_temperature_delta" ? - -500 : m_opt.min > 0 ? - m_opt.min : 0; + const int min_val = m_opt.min == INT_MIN ? 0: m_opt.min; const int max_val = m_opt.max < 2147483647 ? m_opt.max : 2147483647; auto temp = new wxSpinCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, @@ -558,8 +560,11 @@ boost::any& Choice::get_value() // boost::any m_value; wxString ret_str = static_cast(window)->GetValue(); - if (m_opt_id == "support") - return m_value = boost::any(ret_str);//ret_str; + // options from right panel + std::vector right_panel_options{ "support", "scale_unit" }; + for (auto rp_option: right_panel_options) + if (m_opt_id == rp_option) + return m_value = boost::any(ret_str); if (m_opt.type != coEnum) /*m_value = */get_value_by_opt_type(ret_str); @@ -705,6 +710,69 @@ void StaticText::BUILD() temp->SetToolTip(get_tooltip_text(legend)); } +void SliderCtrl::BUILD() +{ + auto size = wxSize(wxDefaultSize); + if (m_opt.height >= 0) size.SetHeight(m_opt.height); + if (m_opt.width >= 0) size.SetWidth(m_opt.width); + + auto temp = new wxBoxSizer(wxHORIZONTAL); + + auto def_val = static_cast(m_opt.default_value)->value; + auto min = m_opt.min == INT_MIN ? 0 : m_opt.min; + auto max = m_opt.max == INT_MAX ? 100 : m_opt.max; + + m_slider = new wxSlider(m_parent, wxID_ANY, def_val * m_scale, + min * m_scale, max * m_scale, + wxDefaultPosition, size); + wxSize field_size(40, -1); + + m_textctrl = new wxTextCtrl(m_parent, wxID_ANY, wxString::Format("%d", m_slider->GetValue()/m_scale), + wxDefaultPosition, field_size); + + temp->Add(m_slider, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL, 0); + temp->Add(m_textctrl, 0, wxALIGN_CENTER_VERTICAL, 0); + + m_slider->Bind(wxEVT_SLIDER, ([this](wxCommandEvent e) { + if (!m_disable_change_event){ + int val = boost::any_cast(get_value()); + m_textctrl->SetLabel(wxString::Format("%d", val)); + on_change_field(); + } + }), m_slider->GetId()); + + m_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { + std::string value = e.GetString().utf8_str().data(); + if (is_matched(value, "^-?\\d+(\\.\\d*)?$")){ + m_disable_change_event = true; + m_slider->SetValue(stoi(value)*m_scale); + m_disable_change_event = false; + on_change_field(); + } + }), m_textctrl->GetId()); + + m_sizer = dynamic_cast(temp); +} + +void SliderCtrl::set_value(const boost::any& value, bool change_event) +{ + m_disable_change_event = !change_event; + + m_slider->SetValue(boost::any_cast(value)*m_scale); + int val = boost::any_cast(get_value()); + m_textctrl->SetLabel(wxString::Format("%d", val)); + + m_disable_change_event = false; +} + +boost::any& SliderCtrl::get_value() +{ +// int ret_val; +// x_textctrl->GetValue().ToDouble(&val); + return m_value = int(m_slider->GetValue()/m_scale); +} + + } // GUI } // Slic3r diff --git a/xs/src/slic3r/GUI/Field.hpp b/xs/src/slic3r/GUI/Field.hpp index 41eef0303..e305f1e53 100644 --- a/xs/src/slic3r/GUI/Field.hpp +++ b/xs/src/slic3r/GUI/Field.hpp @@ -196,6 +196,10 @@ public: return false; } + void set_side_text_ptr(wxStaticText* side_text) { + m_side_text = side_text; + } + protected: MyButton* m_Undo_btn = nullptr; // Bitmap and Tooltip text for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one. @@ -210,6 +214,8 @@ protected: // Color for Label. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one. const wxColour* m_label_color = nullptr; + wxStaticText* m_side_text = nullptr; + // current value boost::any m_value; @@ -221,7 +227,7 @@ protected: inline bool is_bad_field(const t_field& obj) { return obj->getSizer() == nullptr && obj->getWindow() == nullptr; } /// Covenience function to determine whether this field is a valid window field. -inline bool is_window_field(const t_field& obj) { return !is_bad_field(obj) && obj->getWindow() != nullptr; } +inline bool is_window_field(const t_field& obj) { return !is_bad_field(obj) && obj->getWindow() != nullptr && obj->getSizer() == nullptr; } /// Covenience function to determine whether this field is a valid sizer field. inline bool is_sizer_field(const t_field& obj) { return !is_bad_field(obj) && obj->getSizer() != nullptr; } @@ -415,11 +421,44 @@ public: boost::any& get_value()override { return m_value; } - void enable() override { dynamic_cast(window)->Enable(); }; - void disable() override{ dynamic_cast(window)->Disable(); }; + void enable() override { dynamic_cast(window)->Enable(); }; + void disable() override{ dynamic_cast(window)->Disable(); }; wxWindow* getWindow() override { return window; } }; +class SliderCtrl : public Field { + using Field::Field; +public: + SliderCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {} + SliderCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {} + ~SliderCtrl() {} + + wxSizer* m_sizer{ nullptr }; + wxTextCtrl* m_textctrl{ nullptr }; + wxSlider* m_slider{ nullptr }; + + int m_scale = 10; + + void BUILD() override; + + void set_value(const int value, bool change_event = false); + void set_value(const boost::any& value, bool change_event = false); + boost::any& get_value() override; + + void enable() override { + m_slider->Enable(); + m_textctrl->Enable(); + m_textctrl->SetEditable(true); + } + void disable() override{ + m_slider->Disable(); + m_textctrl->Disable(); + m_textctrl->SetEditable(false); + } + wxSizer* getSizer() override { return m_sizer; } + wxWindow* getWindow() override { return dynamic_cast(m_slider); } +}; + } // GUI } // Slic3r diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 0bb1df25d..2b548896d 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -2950,7 +2950,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_mouse.set_start_position_2D_as_invalid(); #endif - } + } + else if (evt.Leaving()) + { + // to remove hover when mouse goes out of this canvas + m_mouse.position = Pointf((coordf_t)pos.x, (coordf_t)pos.y); + render(); + } else if (evt.Leaving()) { // to remove hover on objects when the mouse goes out of this canvas @@ -3184,6 +3190,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } const Vec3d& size = bb.size(); m_on_update_geometry_info_callback.call(size(0), size(1), size(2), m_gizmos.get_scale()); + update_scale_values(size, m_gizmos.get_scale()); + update_rotation_value(volumes[0]->get_angle_z(), "z"); } if ((m_gizmos.get_current_type() != Gizmos::Rotate) && (volumes.size() > 1)) @@ -3284,6 +3292,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) case Gizmos::Scale: { m_on_gizmo_scale_uniformly_callback.call((double)m_gizmos.get_scale()); + Slic3r::GUI::update_settings_value(); break; } case Gizmos::Rotate: diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 8348db9c2..ed160b421 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -38,6 +38,8 @@ #include #include #include +#include +#include #include "wxExtensions.hpp" @@ -53,12 +55,15 @@ #include "PresetBundle.hpp" #include "UpdateDialogs.hpp" #include "FirmwareDialog.hpp" +#include "GUI_ObjectParts.hpp" #include "../Utils/PresetUpdater.hpp" #include "../Config/Snapshot.hpp" #include "3DScene.hpp" #include "libslic3r/I18N.hpp" +#include "Model.hpp" +#include "LambdaObjectDialog.hpp" namespace Slic3r { namespace GUI { @@ -123,10 +128,26 @@ wxLocale* g_wxLocale; wxFont g_small_font; wxFont g_bold_font; -std::shared_ptr m_optgroup; -double m_brim_width = 0.0; +std::vector > m_optgroups; +double m_brim_width = 0.0; +size_t m_label_width = 100; wxButton* g_wiping_dialog_button = nullptr; +//showed/hided controls according to the view mode +wxWindow *g_right_panel = nullptr; +wxBoxSizer *g_frequently_changed_parameters_sizer = nullptr; +wxBoxSizer *g_expert_mode_part_sizer = nullptr; +wxBoxSizer *g_scrolled_window_sizer = nullptr; +wxBoxSizer *g_object_list_sizer = nullptr; +wxButton *g_btn_export_gcode = nullptr; +wxButton *g_btn_export_stl = nullptr; +wxButton *g_btn_reslice = nullptr; +wxButton *g_btn_print = nullptr; +wxButton *g_btn_send_gcode = nullptr; +wxStaticBitmap *g_manifold_warning_icon = nullptr; +bool g_show_print_info = false; +bool g_show_manifold_warning_icon = false; + static void init_label_colours() { auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -206,6 +227,39 @@ void set_3DScene(_3DScene *scene) g_3DScene = scene; } +void set_objects_from_perl( wxWindow* parent, wxBoxSizer *frequently_changed_parameters_sizer, + wxBoxSizer *expert_mode_part_sizer, wxBoxSizer *scrolled_window_sizer, + wxButton *btn_export_gcode, + wxButton *btn_export_stl, wxButton *btn_reslice, + wxButton *btn_print, wxButton *btn_send_gcode, + wxStaticBitmap *manifold_warning_icon) +{ + g_right_panel = parent; + g_frequently_changed_parameters_sizer = frequently_changed_parameters_sizer; + g_expert_mode_part_sizer = expert_mode_part_sizer; + g_scrolled_window_sizer = scrolled_window_sizer; + g_btn_export_gcode = btn_export_gcode; + g_btn_export_stl = btn_export_stl; + g_btn_reslice = btn_reslice; + g_btn_print = btn_print; + g_btn_send_gcode = btn_send_gcode; + g_manifold_warning_icon = manifold_warning_icon; +} + +void set_show_print_info(bool show) +{ + g_show_print_info = show; +} + +void set_show_manifold_warning_icon(bool show) +{ + g_show_manifold_warning_icon = show; +} + +void set_objects_list_sizer(wxBoxSizer *objects_list_sizer){ + g_object_list_sizer = objects_list_sizer; +} + std::vector& get_tabs_list() { return g_tabs_list; @@ -332,10 +386,21 @@ enum ConfigMenuIDs { ConfigMenuTakeSnapshot, ConfigMenuUpdate, ConfigMenuPreferences, + ConfigMenuModeSimple, + ConfigMenuModeExpert, ConfigMenuLanguage, ConfigMenuFlashFirmware, ConfigMenuCnt, }; + +ConfigMenuIDs get_view_mode() +{ + if (!g_AppConfig->has("view_mode")) + return ConfigMenuModeSimple; + + const auto mode = g_AppConfig->get("view_mode"); + return mode == "expert" ? ConfigMenuModeExpert : ConfigMenuModeSimple; +} static wxString dots("…", wxConvUTF8); @@ -353,8 +418,15 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l // local_menu->Append(config_id_base + ConfigMenuUpdate, _(L("Check for updates")), _(L("Check for configuration updates"))); local_menu->AppendSeparator(); local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("Preferences"))+dots+"\tCtrl+,", _(L("Application preferences"))); - local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application Language"))); + local_menu->AppendSeparator(); + auto mode_menu = new wxMenu(); + mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _(L("&Simple")), _(L("Simple View Mode"))); + mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _(L("&Expert")), _(L("Expert View Mode"))); + mode_menu->Check(config_id_base + get_view_mode(), true); + local_menu->AppendSubMenu(mode_menu, _(L("&Mode")), _(L("Slic3r View Mode"))); local_menu->AppendSeparator(); + local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application Language"))); + local_menu->AppendSeparator(); local_menu->Append(config_id_base + ConfigMenuFlashFirmware, _(L("Flash printer firmware")), _(L("Upload a firmware image into an Arduino based printer"))); // TODO: for when we're able to flash dictionaries // local_menu->Append(config_id_base + FirmwareMenuDict, _(L("Flash language file")), _(L("Upload a language dictionary file into a Prusa printer"))); @@ -421,6 +493,13 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l break; } }); + mode_menu->Bind(wxEVT_MENU, [config_id_base](wxEvent& event) { + std::string mode = event.GetId() - config_id_base == ConfigMenuModeExpert ? + "expert" : "simple"; + g_AppConfig->set("view_mode", mode); + g_AppConfig->save(); + update_mode(); + }); menu->Append(local_menu, _(L("&Configuration"))); } @@ -429,6 +508,26 @@ void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_languag add_config_menu(menu, event_preferences_changed, event_language_change); } +void open_model(wxWindow *parent, wxArrayString& input_files){ + t_file_wild_card vec_FILE_WILDCARDS = get_file_wild_card(); + std::vector file_types = { "known", "stl", "obj", "amf", "3mf", "prusa" }; + wxString MODEL_WILDCARD; + for (auto file_type : file_types) + MODEL_WILDCARD += vec_FILE_WILDCARDS.at(file_type) + "|"; + + auto dlg_title = _(L("Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):")); + auto dialog = new wxFileDialog(parent /*? parent : GetTopWindow(g_wxMainFrame)*/, dlg_title, + g_AppConfig->get_last_dir(), "", + MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); + if (dialog->ShowModal() != wxID_OK) { + dialog->Destroy(); + return ; + } + + dialog->GetPaths(input_files); + dialog->Destroy(); +} + // This is called when closing the application, when loading a config file or when starting the config wizard // to notify the user whether he is aware that some preset changes will be lost. bool check_unsaved_changes() @@ -757,6 +856,22 @@ unsigned get_colour_approx_luma(const wxColour &colour) )); } +wxWindow* get_right_panel(){ + return g_right_panel; +} + +wxFrame* get_main_frame() { + return g_wxMainFrame; +} + +wxNotebook * get_tab_panel() { + return g_wxTabPanel; +} + +const size_t& label_width(){ + return m_label_width; +} + void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value) { if (comboCtrl == nullptr) @@ -825,14 +940,37 @@ wxString from_u8(const std::string &str) return wxString::FromUTF8(str.c_str()); } +void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, + Model &model, + int event_object_selection_changed, + int event_object_settings_changed, + int event_remove_object, + int event_update_scene) +{ + set_event_object_selection_changed(event_object_selection_changed); + set_event_object_settings_changed(event_object_settings_changed); + set_event_remove_object(event_remove_object); + set_event_update_scene(event_update_scene); + set_objects_from_model(model); + init_mesh_icons(); + +// wxWindowUpdateLocker noUpdates(parent); + +// add_objects_list(parent, sizer); + +// add_collapsible_panes(parent, sizer); +} void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer) { DynamicPrintConfig* config = &g_PresetBundle->prints.get_edited_preset().config; - m_optgroup = std::make_shared(parent, "", config); + std::shared_ptr optgroup = std::make_shared(parent, "", config); const wxArrayInt& ar = preset_sizer->GetColWidths(); - m_optgroup->label_width = ar.IsEmpty() ? 100 : ar.front()-4; // doesn't work - m_optgroup->m_on_change = [config](t_config_option_key opt_key, boost::any value){ + m_label_width = ar.IsEmpty() ? 100 : ar.front()-4; + optgroup->label_width = m_label_width; + + //Frequently changed parameters + optgroup->m_on_change = [config](t_config_option_key opt_key, boost::any value){ TabPrint* tab_print = nullptr; for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) { Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); @@ -847,7 +985,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl return; if (opt_key == "fill_density"){ - value = m_optgroup->get_config_value(*config, opt_key); + value = m_optgroups[ogFrequentlyChangingParameters]->get_config_value(*config, opt_key); tab_print->set_value(opt_key, value); tab_print->update(); } @@ -870,14 +1008,14 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl } else{ //(opt_key == "support") const wxString& selection = boost::any_cast(value); - + auto support_material = selection == _("None") ? false : true; new_conf.set_key_value("support_material", new ConfigOptionBool(support_material)); if (selection == _("Everywhere")) new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(false)); else if (selection == _("Support on build plate only")) - new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(true)); + new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(true)); } tab_print->load_config(new_conf); } @@ -885,10 +1023,10 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl tab_print->update_dirty(); }; - Option option = m_optgroup->get_option("fill_density"); + Option option = optgroup->get_option("fill_density"); option.opt.sidetext = ""; option.opt.full_width = true; - m_optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); ConfigOptionDef def; @@ -907,7 +1045,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl def.default_value = new ConfigOptionStrings { selection }; option = Option(def, "support"); option.opt.full_width = true; - m_optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); m_brim_width = config->opt_float("brim_width"); def.label = L("Brim"); @@ -916,7 +1054,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl def.gui_type = ""; def.default_value = new ConfigOptionBool{ m_brim_width > 0.0 ? true : false }; option = Option(def, "brim"); - m_optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); Line line = { "", "" }; @@ -942,16 +1080,93 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl })); return sizer; }; - m_optgroup->append_line(line); + optgroup->append_line(line); + sizer->Add(optgroup->sizer, 0, wxEXPAND | wxBOTTOM, 2); + m_optgroups.push_back(optgroup);// ogFrequentlyChangingParameters - sizer->Add(m_optgroup->sizer, 1, wxEXPAND | wxBOTTOM, 2); + // Object List + add_objects_list(parent, sizer); + + // Frequently Object Settings + add_object_settings(parent, sizer); } -ConfigOptionsGroup* get_optgroup() +void show_frequently_changed_parameters(bool show) { - return m_optgroup.get(); + g_frequently_changed_parameters_sizer->Show(show); + if (!show) return; + + for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) { + Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); + if (!tab) + continue; + tab->update_wiping_button_visibility(); + break; + } +} + +void show_buttons(bool show) +{ + g_btn_export_stl->Show(show); + g_btn_reslice->Show(show); + for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) { + TabPrinter *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); + if (!tab) + continue; + g_btn_print->Show(show && !tab->m_config->opt_string("serial_port").empty()); + g_btn_send_gcode->Show(show && !tab->m_config->opt_string("octoprint_host").empty()); + break; + } +} + +void show_info_sizer(bool show) +{ + g_scrolled_window_sizer->Show(static_cast(0), show); + g_scrolled_window_sizer->Show(1, show && g_show_print_info); + g_manifold_warning_icon->Show(show && g_show_manifold_warning_icon); +} + +void update_mode() +{ + wxWindowUpdateLocker noUpdates(g_right_panel); + + // TODO There is a not the best place of it! + //*** Update style of the "Export G-code" button**** + if (g_btn_export_gcode->GetFont() != bold_font()){ + g_btn_export_gcode->SetBackgroundColour(wxColour(252, 77, 1)); + g_btn_export_gcode->SetFont(bold_font()); + } + // *********************************** + + ConfigMenuIDs mode = get_view_mode(); + +// show_frequently_changed_parameters(mode >= ConfigMenuModeRegular); +// g_expert_mode_part_sizer->Show(mode == ConfigMenuModeExpert); + g_object_list_sizer->Show(mode == ConfigMenuModeExpert); + show_info_sizer(mode == ConfigMenuModeExpert); + show_buttons(mode == ConfigMenuModeExpert); + + // TODO There is a not the best place of it! + // *** Update showing of the collpane_settings +// show_collpane_settings(mode == ConfigMenuModeExpert); + // ************************* + g_right_panel->GetParent()->Layout(); + g_right_panel->Layout(); +} + +bool is_expert_mode(){ + return get_view_mode() == ConfigMenuModeExpert; +} + +ConfigOptionsGroup* get_optgroup(size_t i) +{ + return m_optgroups[i].get(); +} + +std::vector >& get_optgroups() { + return m_optgroups; } wxButton* get_wiping_dialog_button() diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 990711323..69e83eaab 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -5,6 +5,7 @@ #include #include "PrintConfig.hpp" #include "../../libslic3r/Utils.hpp" +#include "GUI_ObjectParts.hpp" #include #include @@ -12,7 +13,6 @@ class wxApp; class wxWindow; class wxFrame; -class wxFont; class wxMenuBar; class wxNotebook; class wxComboCtrl; @@ -24,6 +24,8 @@ class wxBoxSizer; class wxFlexGridSizer; class wxButton; class wxFileDialog; +class wxStaticBitmap; +class wxFont; namespace Slic3r { @@ -67,15 +69,15 @@ typedef std::map t_file_wild_card; inline t_file_wild_card& get_file_wild_card() { static t_file_wild_card FILE_WILDCARDS; if (FILE_WILDCARDS.empty()){ - FILE_WILDCARDS["known"] = "Known files (*.stl, *.obj, *.amf, *.xml, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.prusa;*.PRUSA"; - FILE_WILDCARDS["stl"] = "STL files (*.stl)|*.stl;*.STL"; - FILE_WILDCARDS["obj"] = "OBJ files (*.obj)|*.obj;*.OBJ"; - FILE_WILDCARDS["amf"] = "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML"; - FILE_WILDCARDS["3mf"] = "3MF files (*.3mf)|*.3mf;*.3MF;"; - FILE_WILDCARDS["prusa"] = "Prusa Control files (*.prusa)|*.prusa;*.PRUSA"; - FILE_WILDCARDS["ini"] = "INI files *.ini|*.ini;*.INI"; + FILE_WILDCARDS["known"] = "Known files (*.stl, *.obj, *.amf, *.xml, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.prusa;*.PRUSA"; + FILE_WILDCARDS["stl"] = "STL files (*.stl)|*.stl;*.STL"; + FILE_WILDCARDS["obj"] = "OBJ files (*.obj)|*.obj;*.OBJ"; + FILE_WILDCARDS["amf"] = "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML"; + FILE_WILDCARDS["3mf"] = "3MF files (*.3mf)|*.3mf;*.3MF;"; + FILE_WILDCARDS["prusa"] = "Prusa Control files (*.prusa)|*.prusa;*.PRUSA"; + FILE_WILDCARDS["ini"] = "INI files *.ini|*.ini;*.INI"; FILE_WILDCARDS["gcode"] = "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC"; - FILE_WILDCARDS["svg"] = "SVG files *.svg|*.svg;*.SVG"; + FILE_WILDCARDS["svg"] = "SVG files *.svg|*.svg;*.SVG"; } return FILE_WILDCARDS; } @@ -100,11 +102,26 @@ void set_app_config(AppConfig *app_config); void set_preset_bundle(PresetBundle *preset_bundle); void set_preset_updater(PresetUpdater *updater); void set_3DScene(_3DScene *scene); +void set_objects_from_perl( wxWindow* parent, + wxBoxSizer *frequently_changed_parameters_sizer, + wxBoxSizer *expert_mode_part_sizer, + wxBoxSizer *scrolled_window_sizer, + wxButton *btn_export_gcode, + wxButton *btn_export_stl, + wxButton *btn_reslice, + wxButton *btn_print, + wxButton *btn_send_gcode, + wxStaticBitmap *manifold_warning_icon); +void set_show_print_info(bool show); +void set_show_manifold_warning_icon(bool show); +void set_objects_list_sizer(wxBoxSizer *objects_list_sizer); -AppConfig* get_app_config(); -wxApp* get_app(); -PresetBundle* get_preset_bundle(); -wxNotebook* get_tab_panel(); +AppConfig* get_app_config(); +wxApp* get_app(); +PresetBundle* get_preset_bundle(); +wxFrame* get_main_frame(); +wxNotebook * get_tab_panel(); +wxNotebook* get_tab_panel(); const wxColour& get_label_clr_modified(); const wxColour& get_label_clr_sys(); @@ -116,6 +133,11 @@ void set_label_clr_sys(const wxColour& clr); const wxFont& small_font(); const wxFont& bold_font(); +void open_model(wxWindow *parent, wxArrayString& input_files); + +wxWindow* get_right_panel(); +const size_t& label_width(); + Tab* get_tab(const std::string& name); const std::vector& get_preset_tabs(); @@ -161,6 +183,8 @@ void save_language(); void get_installed_languages(wxArrayString & names, wxArrayLong & identifiers); // select language from the list of installed languages bool select_language(wxArrayString & names, wxArrayLong & identifiers); +// update right panel of the Plater according to view mode +void update_mode(); std::vector& get_tabs_list(); bool checked_tab(Tab* tab); @@ -180,13 +204,22 @@ wxString L_str(const std::string &str); // Return wxString from std::string in UTF8 wxString from_u8(const std::string &str); - +void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, + Model &model, + int event_object_selection_changed, + int event_object_settings_changed, + int event_remove_object, + int event_update_scene); void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer); +// Update view mode according to selected menu +void update_mode(); +bool is_expert_mode(); // Callback to trigger a configuration update timer on the Plater. static PerlCallback g_on_request_update_callback; -ConfigOptionsGroup* get_optgroup(); +ConfigOptionsGroup* get_optgroup(size_t i); +std::vector >& get_optgroups(); wxButton* get_wiping_dialog_button(); void add_export_option(wxFileDialog* dlg, const std::string& format); diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp new file mode 100644 index 000000000..e9d08ede5 --- /dev/null +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -0,0 +1,1639 @@ +#include "GUI.hpp" +#include "OptionsGroup.hpp" +#include "PresetBundle.hpp" +#include "GUI_ObjectParts.hpp" +#include "Model.hpp" +#include "wxExtensions.hpp" +#include "LambdaObjectDialog.hpp" +#include "../../libslic3r/Utils.hpp" + +#include +#include +#include +#include "Geometry.hpp" +#include "slic3r/Utils/FixModelByWin10.hpp" + +namespace Slic3r +{ +namespace GUI +{ +wxSizer *m_sizer_object_buttons = nullptr; +wxSizer *m_sizer_part_buttons = nullptr; +wxSizer *m_sizer_object_movers = nullptr; +wxDataViewCtrl *m_objects_ctrl = nullptr; +PrusaObjectDataViewModel *m_objects_model = nullptr; +wxCollapsiblePane *m_collpane_settings = nullptr; + +wxIcon m_icon_modifiermesh; +wxIcon m_icon_solidmesh; +wxIcon m_icon_manifold_warning; +wxBitmap m_bmp_cog; +wxBitmap m_bmp_split; + +wxSlider* m_mover_x = nullptr; +wxSlider* m_mover_y = nullptr; +wxSlider* m_mover_z = nullptr; +wxButton* m_btn_move_up = nullptr; +wxButton* m_btn_move_down = nullptr; +Point3 m_move_options; +Point3 m_last_coords; +int m_selected_object_id = -1; + +bool g_prevent_list_events = false; // We use this flag to avoid circular event handling Select() + // happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler + // calls this method again and again and again +bool g_is_percent_scale = false; // It indicates if scale unit is percentage +int g_rotation_x = 0; // Last value of the rotation around the X axis +int g_rotation_y = 0; // Last value of the rotation around the Y axis +ModelObjectPtrs* m_objects; +std::shared_ptr m_config; +std::shared_ptr m_default_config; +wxBoxSizer* m_option_sizer = nullptr; + +// option groups for settings +std::vector > m_og_settings; + +int m_event_object_selection_changed = 0; +int m_event_object_settings_changed = 0; +int m_event_remove_object = 0; +int m_event_update_scene = 0; + +bool m_parts_changed = false; +bool m_part_settings_changed = false; + +#ifdef __WXOSX__ + wxString g_selected_extruder = ""; +#endif //__WXOSX__ + +// typedef std::map t_category_icon; +typedef std::map t_category_icon; +inline t_category_icon& get_category_icon() { + static t_category_icon CATEGORY_ICON; + if (CATEGORY_ICON.empty()){ + CATEGORY_ICON[L("Layers and Perimeters")] = wxBitmap(from_u8(Slic3r::var("layers.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Infill")] = wxBitmap(from_u8(Slic3r::var("infill.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Support material")] = wxBitmap(from_u8(Slic3r::var("building.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Speed")] = wxBitmap(from_u8(Slic3r::var("time.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Extruders")] = wxBitmap(from_u8(Slic3r::var("funnel.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Extrusion Width")] = wxBitmap(from_u8(Slic3r::var("funnel.png")), wxBITMAP_TYPE_PNG); +// CATEGORY_ICON[L("Skirt and brim")] = wxBitmap(from_u8(Slic3r::var("box.png")), wxBITMAP_TYPE_PNG); +// CATEGORY_ICON[L("Speed > Acceleration")] = wxBitmap(from_u8(Slic3r::var("time.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Advanced")] = wxBitmap(from_u8(Slic3r::var("wand.png")), wxBITMAP_TYPE_PNG); + } + return CATEGORY_ICON; +} + +std::vector get_options(const bool is_part) +{ + PrintRegionConfig reg_config; + auto options = reg_config.keys(); + if (!is_part) { + PrintObjectConfig obj_config; + std::vector obj_options = obj_config.keys(); + options.insert(options.end(), obj_options.begin(), obj_options.end()); + } + return options; +} + +// category -> vector ( option ; label ) +typedef std::map< std::string, std::vector< std::pair > > settings_menu_hierarchy; +void get_options_menu(settings_menu_hierarchy& settings_menu, bool is_part) +{ + auto options = get_options(is_part); + + auto extruders_cnt = get_preset_bundle()->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); + + DynamicPrintConfig config; + for (auto& option : options) + { + auto const opt = config.def()->get(option); + auto category = opt->category; + if (category.empty() || + (category == "Extruders" && extruders_cnt == 1)) continue; + + std::pair option_label(option, opt->label); + std::vector< std::pair > new_category; + auto& cat_opt_label = settings_menu.find(category) == settings_menu.end() ? new_category : settings_menu.at(category); + cat_opt_label.push_back(option_label); + if (cat_opt_label.size() == 1) + settings_menu[category] = cat_opt_label; + } +} + +void set_event_object_selection_changed(const int& event){ + m_event_object_selection_changed = event; +} +void set_event_object_settings_changed(const int& event){ + m_event_object_settings_changed = event; +} +void set_event_remove_object(const int& event){ + m_event_remove_object = event; +} +void set_event_update_scene(const int& event){ + m_event_update_scene = event; +} + +void set_objects_from_model(Model &model) { + m_objects = &(model.objects); +} + +void init_mesh_icons(){ + m_icon_modifiermesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("lambda.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG); + m_icon_solidmesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("object.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); + + // init icon for manifold warning + m_icon_manifold_warning = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("exclamation_mark_.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG); + + // init bitmap for "Split to sub-objects" context menu + m_bmp_split = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("split.png")), wxBITMAP_TYPE_PNG); + + // init bitmap for "Add Settings" context menu + m_bmp_cog = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("cog.png")), wxBITMAP_TYPE_PNG); +} + +bool is_parts_changed(){return m_parts_changed;} +bool is_part_settings_changed(){ return m_part_settings_changed; } + +static wxString dots("…", wxConvUTF8); + +void set_tooltip_for_item(const wxPoint& pt) +{ + wxDataViewItem item; + wxDataViewColumn* col; + m_objects_ctrl->HitTest(pt, item, col); + if (!item) return; + + if (col->GetTitle() == " ") + m_objects_ctrl->GetMainWindow()->SetToolTip(_(L("Right button click the icon to change the object settings"))); + else if (col->GetTitle() == _("Name") && + m_objects_model->GetIcon(item).GetRefData() == m_icon_manifold_warning.GetRefData()) { + int obj_idx = m_objects_model->GetIdByItem(item); + auto& stats = (*m_objects)[obj_idx]->volumes[0]->mesh.stl.stats; + int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + + stats.facets_added + stats.facets_reversed + stats.backwards_edges; + + wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors):\n")), errors); + + std::map error_msg; + error_msg[L("degenerate facets")] = stats.degenerate_facets; + error_msg[L("edges fixed")] = stats.edges_fixed; + error_msg[L("facets removed")] = stats.facets_removed; + error_msg[L("facets added")] = stats.facets_added; + error_msg[L("facets reversed")] = stats.facets_reversed; + error_msg[L("backwards edges")] = stats.backwards_edges; + + for (auto error : error_msg) + { + if (error.second > 0) + tooltip += wxString::Format(_("\t%d %s\n"), error.second, error.first); + } +// OR +// tooltip += wxString::Format(_(L("%d degenerate facets, %d edges fixed, %d facets removed, " +// "%d facets added, %d facets reversed, %d backwards edges")), +// stats.degenerate_facets, stats.edges_fixed, stats.facets_removed, +// stats.facets_added, stats.facets_reversed, stats.backwards_edges); + + if (is_windows10()) + tooltip += _(L("Right button click the icon to fix STL through Netfabb")); + + m_objects_ctrl->GetMainWindow()->SetToolTip(tooltip); + } + else + m_objects_ctrl->GetMainWindow()->SetToolTip(""); // hide tooltip +} + +wxPoint get_mouse_position_in_control() { + const wxPoint& pt = wxGetMousePosition(); + wxWindow* win = m_objects_ctrl->GetMainWindow(); + return wxPoint(pt.x - win->GetScreenPosition().x, + pt.y - win->GetScreenPosition().y); +} + +bool is_mouse_position_in_control(wxPoint& pt) { + pt = get_mouse_position_in_control(); + const wxSize& cz = m_objects_ctrl->GetSize(); + if (pt.x > 0 && pt.x < cz.x && + pt.y > 0 && pt.y < cz.y) + return true; + return false; +} + +wxDataViewColumn* object_ctrl_create_extruder_column(int extruders_count) +{ + wxArrayString choices; + choices.Add("default"); + for (int i = 1; i <= extruders_count; ++i) + choices.Add(wxString::Format("%d", i)); + wxDataViewChoiceRenderer *c = + new wxDataViewChoiceRenderer(choices, wxDATAVIEW_CELL_EDITABLE, wxALIGN_CENTER_HORIZONTAL); + wxDataViewColumn* column = new wxDataViewColumn(_(L("Extruder")), c, 3, 60, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); + return column; +} + +void create_objects_ctrl(wxWindow* win, wxBoxSizer*& objects_sz) +{ + m_objects_ctrl = new wxDataViewCtrl(win, wxID_ANY, wxDefaultPosition, wxDefaultSize); + m_objects_ctrl->SetInitialSize(wxSize(-1, 150)); // TODO - Set correct height according to the opened/closed objects + + objects_sz = new wxBoxSizer(wxVERTICAL); + objects_sz->Add(m_objects_ctrl, 1, wxGROW | wxLEFT, 20); + + m_objects_model = new PrusaObjectDataViewModel; + m_objects_ctrl->AssociateModel(m_objects_model); +#if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE + m_objects_ctrl->EnableDragSource(wxDF_UNICODETEXT); + m_objects_ctrl->EnableDropTarget(wxDF_UNICODETEXT); +#endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE + + // column 0(Icon+Text) of the view control: + m_objects_ctrl->AppendIconTextColumn(_(L("Name")), 0, wxDATAVIEW_CELL_INERT, 120, + wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE); + + // column 1 of the view control: + m_objects_ctrl->AppendTextColumn(_(L("Copy")), 1, wxDATAVIEW_CELL_INERT, 45, + wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); + + // column 2 of the view control: + m_objects_ctrl->AppendTextColumn(_(L("Scale")), 2, wxDATAVIEW_CELL_INERT, 55, + wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); + + // column 3 of the view control: + m_objects_ctrl->AppendColumn(object_ctrl_create_extruder_column(4)); + + // column 4 of the view control: + m_objects_ctrl->AppendBitmapColumn(" ", 4, wxDATAVIEW_CELL_INERT, 25, + wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); +} + +// ****** from GUI.cpp +wxBoxSizer* create_objects_list(wxWindow *win) +{ + wxBoxSizer* objects_sz; + // create control + create_objects_ctrl(win, objects_sz); + + // describe control behavior + m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) { + object_ctrl_selection_changed(); +#ifndef __WXMSW__ + set_tooltip_for_item(get_mouse_position_in_control()); +#endif //__WXMSW__ + }); + + m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, [](wxDataViewEvent& event) { + object_ctrl_context_menu(); + event.Skip(); + }); + + m_objects_ctrl->Bind(wxEVT_CHAR, [](wxKeyEvent& event) { object_ctrl_key_event(event); }); // doesn't work on OSX + +#ifdef __WXMSW__ + // Extruder value changed + m_objects_ctrl->Bind(wxEVT_CHOICE, [](wxCommandEvent& event) { update_extruder_in_config(event.GetString()); }); + + m_objects_ctrl->GetMainWindow()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) { + set_tooltip_for_item(event.GetPosition()); + event.Skip(); + }); +#else + // equivalent to wxEVT_CHOICE on __WXMSW__ + m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [](wxDataViewEvent& event) { object_ctrl_item_value_change(event); }); +#endif //__WXMSW__ + + m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_BEGIN_DRAG, [](wxDataViewEvent& e) {on_begin_drag(e);}); + m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, [](wxDataViewEvent& e) {on_drop_possible(e); }); + m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_DROP, [](wxDataViewEvent& e) {on_drop(e);}); + return objects_sz; +} + +wxBoxSizer* create_edit_object_buttons(wxWindow* win) +{ + auto sizer = new wxBoxSizer(wxVERTICAL); + + auto btn_load_part = new wxButton(win, wxID_ANY, /*Load */"part" + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); + auto btn_load_modifier = new wxButton(win, wxID_ANY, /*Load */"modifier" + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); + auto btn_load_lambda_modifier = new wxButton(win, wxID_ANY, /*Load */"generic" + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); + auto btn_delete = new wxButton(win, wxID_ANY, "Delete"/*" part"*/, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); + auto btn_split = new wxButton(win, wxID_ANY, "Split"/*" part"*/, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); + m_btn_move_up = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); + m_btn_move_down = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); + + //*** button's functions + btn_load_part->Bind(wxEVT_BUTTON, [win](wxEvent&) { + on_btn_load(win); + }); + + btn_load_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) { + on_btn_load(win, true); + }); + + btn_load_lambda_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) { + on_btn_load(win, true, true); + }); + + btn_delete ->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_del(); }); + btn_split ->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_split(true); }); + m_btn_move_up ->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_move_up(); }); + m_btn_move_down ->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_move_down(); }); + //*** + + m_btn_move_up->SetMinSize(wxSize(20, -1)); + m_btn_move_down->SetMinSize(wxSize(20, -1)); + btn_load_part->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG)); + btn_load_modifier->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG)); + btn_load_lambda_modifier->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG)); + btn_delete->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_delete.png")), wxBITMAP_TYPE_PNG)); + btn_split->SetBitmap(wxBitmap(from_u8(Slic3r::var("shape_ungroup.png")), wxBITMAP_TYPE_PNG)); + m_btn_move_up->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_up.png")), wxBITMAP_TYPE_PNG)); + m_btn_move_down->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_down.png")), wxBITMAP_TYPE_PNG)); + + m_sizer_object_buttons = new wxGridSizer(1, 3, 0, 0); + m_sizer_object_buttons->Add(btn_load_part, 0, wxEXPAND); + m_sizer_object_buttons->Add(btn_load_modifier, 0, wxEXPAND); + m_sizer_object_buttons->Add(btn_load_lambda_modifier, 0, wxEXPAND); + m_sizer_object_buttons->Show(false); + + m_sizer_part_buttons = new wxGridSizer(1, 3, 0, 0); + m_sizer_part_buttons->Add(btn_delete, 0, wxEXPAND); + m_sizer_part_buttons->Add(btn_split, 0, wxEXPAND); + { + auto up_down_sizer = new wxGridSizer(1, 2, 0, 0); + up_down_sizer->Add(m_btn_move_up, 1, wxEXPAND); + up_down_sizer->Add(m_btn_move_down, 1, wxEXPAND); + m_sizer_part_buttons->Add(up_down_sizer, 0, wxEXPAND); + } + m_sizer_part_buttons->Show(false); + + btn_load_part->SetFont(Slic3r::GUI::small_font()); + btn_load_modifier->SetFont(Slic3r::GUI::small_font()); + btn_load_lambda_modifier->SetFont(Slic3r::GUI::small_font()); + btn_delete->SetFont(Slic3r::GUI::small_font()); + btn_split->SetFont(Slic3r::GUI::small_font()); + m_btn_move_up->SetFont(Slic3r::GUI::small_font()); + m_btn_move_down->SetFont(Slic3r::GUI::small_font()); + + sizer->Add(m_sizer_object_buttons, 0, wxEXPAND | wxLEFT, 20); + sizer->Add(m_sizer_part_buttons, 0, wxEXPAND | wxLEFT, 20); + return sizer; +} + +void update_after_moving() +{ + auto item = m_objects_ctrl->GetSelection(); + if (!item || m_selected_object_id<0) + return; + + auto volume_id = m_objects_model->GetVolumeIdByItem(item); + if (volume_id < 0) + return; + + Point3 m = m_move_options; + Point3 l = m_last_coords; + + auto d = Pointf3(m.x - l.x, m.y - l.y, m.z - l.z); + auto volume = (*m_objects)[m_selected_object_id]->volumes[volume_id]; + volume->mesh.translate(d.x,d.y,d.z); + m_last_coords = m; + + m_parts_changed = true; + parts_changed(m_selected_object_id); +} + +wxSizer* object_movers(wxWindow *win) +{ +// DynamicPrintConfig* config = &get_preset_bundle()->/*full_config();//*/printers.get_edited_preset().config; // TODO get config from Model_volume + std::shared_ptr optgroup = std::make_shared(win, "Move"/*, config*/); + optgroup->label_width = 20; + optgroup->m_on_change = [](t_config_option_key opt_key, boost::any value){ + int val = boost::any_cast(value); + bool update = false; + if (opt_key == "x" && m_move_options.x != val){ + update = true; + m_move_options.x = val; + } + else if (opt_key == "y" && m_move_options.y != val){ + update = true; + m_move_options.y = val; + } + else if (opt_key == "z" && m_move_options.z != val){ + update = true; + m_move_options.z = val; + } + if (update) update_after_moving(); + }; + + ConfigOptionDef def; + def.label = L("X"); + def.type = coInt; + def.gui_type = "slider"; + def.default_value = new ConfigOptionInt(0); + + Option option = Option(def, "x"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); + m_mover_x = dynamic_cast(optgroup->get_field("x")->getWindow()); + + def.label = L("Y"); + option = Option(def, "y"); + optgroup->append_single_option_line(option); + m_mover_y = dynamic_cast(optgroup->get_field("y")->getWindow()); + + def.label = L("Z"); + option = Option(def, "z"); + optgroup->append_single_option_line(option); + m_mover_z = dynamic_cast(optgroup->get_field("z")->getWindow()); + + get_optgroups().push_back(optgroup); // ogObjectMovers + + m_sizer_object_movers = optgroup->sizer; + m_sizer_object_movers->Show(false); + + m_move_options = Point3(0, 0, 0); + m_last_coords = Point3(0, 0, 0); + + return optgroup->sizer; +} + +wxBoxSizer* content_settings(wxWindow *win) +{ + DynamicPrintConfig* config = &get_preset_bundle()->/*full_config();//*/printers.get_edited_preset().config; // TODO get config from Model_volume + std::shared_ptr optgroup = std::make_shared(win, "Extruders", config); + optgroup->label_width = label_width(); + + Option option = optgroup->get_option("extruder"); + option.opt.default_value = new ConfigOptionInt(1); + optgroup->append_single_option_line(option); + + get_optgroups().push_back(optgroup); // ogObjectSettings + + auto sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(create_edit_object_buttons(win), 0, wxEXPAND, 0); // *** Edit Object Buttons*** + + sizer->Add(optgroup->sizer, 1, wxEXPAND | wxLEFT, 20); + + auto add_btn = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + if (wxMSW) add_btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + add_btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("add.png")), wxBITMAP_TYPE_PNG)); + sizer->Add(add_btn, 0, wxALIGN_LEFT | wxLEFT, 20); + + sizer->Add(object_movers(win), 0, wxEXPAND | wxLEFT, 20); + + return sizer; +} + +void add_objects_list(wxWindow* parent, wxBoxSizer* sizer) +{ + const auto ol_sizer = create_objects_list(parent); + sizer->Add(ol_sizer, 1, wxEXPAND | wxTOP, 20); + set_objects_list_sizer(ol_sizer); +} + +Line add_og_to_object_settings(const std::string& option_name, const std::string& sidetext, int def_value = 0) +{ + Line line = { _(option_name), "" }; + + ConfigOptionDef def; + def.type = coInt; + def.default_value = new ConfigOptionInt(def_value); + def.sidetext = sidetext; + def.width = 70; + + if (option_name == "Rotation") + def.min = -360; + + const std::string lower_name = boost::algorithm::to_lower_copy(option_name); + + std::vector axes{ "x", "y", "z" }; + for (auto axis : axes) { + def.label = boost::algorithm::to_upper_copy(axis); + Option option = Option(def, lower_name + "_" + axis); + option.opt.full_width = true; + line.append_option(option); + } + + if (option_name == "Scale") + { + def.label = L("Units"); + def.type = coStrings; + def.gui_type = "select_open"; + def.enum_labels.push_back(L("%")); + def.enum_labels.push_back(L("mm")); + def.default_value = new ConfigOptionStrings{ "%" }; + def.sidetext = " "; + + Option option = Option(def, lower_name + "_unit"); + line.append_option(option); + } + + return line; +} + +void add_object_settings(wxWindow* parent, wxBoxSizer* sizer) +{ + auto optgroup = std::make_shared(parent, _(L("Object Settings"))); + optgroup->label_width = 100; + optgroup->set_grid_vgap(5); + + optgroup->m_on_change = [](t_config_option_key opt_key, boost::any value){ + if (opt_key == "scale_unit"){ + const wxString& selection = boost::any_cast(value); + std::vector axes{ "x", "y", "z" }; + for (auto axis : axes) { + std::string key = "scale_" + axis; + get_optgroup(ogFrequentlyObjectSettings)->set_side_text(key, selection); + } + + g_is_percent_scale = selection == _("%"); + update_scale_values(); + } + }; + +// def.label = L("Name"); +// def.type = coString; +// def.tooltip = L("Object name"); +// def.full_width = true; +// def.default_value = new ConfigOptionString{ "BlaBla_object.stl" }; +// optgroup->append_single_option_line(Option(def, "object_name")); + + ConfigOptionDef def; + + def.label = L("Name"); +// def.type = coString; + def.gui_type = "legend"; + def.tooltip = L("Object name"); + def.full_width = true; + def.default_value = new ConfigOptionString{ " " }; + optgroup->append_single_option_line(Option(def, "object_name")); + + optgroup->set_flag(ogSIDE_OPTIONS_VERTICAL); + optgroup->sidetext_width = 25; + + optgroup->append_line(add_og_to_object_settings(L("Position"), L("mm"))); + optgroup->append_line(add_og_to_object_settings(L("Rotation"), "°")); + optgroup->append_line(add_og_to_object_settings(L("Scale"), "%")); + + optgroup->set_flag(ogDEFAULT); + + def.label = L("Place on bed"); + def.type = coBool; + def.tooltip = L("Automatic placing of models on printing bed in Y axis"); + def.gui_type = ""; + def.sidetext = ""; + def.default_value = new ConfigOptionBool{ false }; + optgroup->append_single_option_line(Option(def, "place_on_bed")); + + m_option_sizer = new wxBoxSizer(wxVERTICAL); + optgroup->sizer->Add(m_option_sizer, 1, wxEXPAND | wxLEFT, 5); + + sizer->Add(optgroup->sizer, 0, wxEXPAND | wxLEFT | wxTOP, 20); + + optgroup->disable(); + + get_optgroups().push_back(optgroup); // ogFrequentlyObjectSettings + +// add_current_settings(); +} + + +// add Collapsible Pane to sizer +wxCollapsiblePane* add_collapsible_pane(wxWindow* parent, wxBoxSizer* sizer_parent, const wxString& name, std::function content_function) +{ +#ifdef __WXMSW__ + auto *collpane = new PrusaCollapsiblePaneMSW(parent, wxID_ANY, name); +#else + auto *collpane = new PrusaCollapsiblePane/*wxCollapsiblePane*/(parent, wxID_ANY, name); +#endif // __WXMSW__ + // add the pane with a zero proportion value to the sizer which contains it + sizer_parent->Add(collpane, 0, wxGROW | wxALL, 0); + + wxWindow *win = collpane->GetPane(); + + wxSizer *sizer = content_function(win); + + wxSizer *sizer_pane = new wxBoxSizer(wxVERTICAL); + sizer_pane->Add(sizer, 1, wxGROW | wxEXPAND | wxBOTTOM, 2); + win->SetSizer(sizer_pane); + // sizer_pane->SetSizeHints(win); + return collpane; +} + +void add_collapsible_panes(wxWindow* parent, wxBoxSizer* sizer) +{ + // *** Objects List *** + auto collpane = add_collapsible_pane(parent, sizer, "Objects List:", create_objects_list); + collpane->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([collpane](wxCommandEvent& e){ + // wxWindowUpdateLocker noUpdates(g_right_panel); + if (collpane->IsCollapsed()) { + m_sizer_object_buttons->Show(false); + m_sizer_part_buttons->Show(false); + m_sizer_object_movers->Show(false); + if (!m_objects_ctrl->HasSelection()) + m_collpane_settings->Show(false); + } + })); + + // *** Object/Part Settings *** + m_collpane_settings = add_collapsible_pane(parent, sizer, "Object Settings", content_settings); +} + +void show_collpane_settings(bool expert_mode) +{ + m_collpane_settings->Show(expert_mode && !m_objects_model->IsEmpty()); +} + +void add_object_to_list(const std::string &name, ModelObject* model_object) +{ + wxString item_name = name; + int scale = model_object->instances[0]->scaling_factor * 100; + auto item = m_objects_model->Add(item_name, model_object->instances.size(), scale); + m_objects_ctrl->Select(item); + + // Add error icon if detected auto-repaire + auto stats = model_object->volumes[0]->mesh.stl.stats; + int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + + stats.facets_added + stats.facets_reversed + stats.backwards_edges; + if (errors > 0) { + const wxDataViewIconText data(item_name, m_icon_manifold_warning); + wxVariant variant; + variant << data; + m_objects_model->SetValue(variant, item, 0); + } + + if (model_object->volumes.size() > 1) { + for (auto id = 0; id < model_object->volumes.size(); id++) + m_objects_model->AddChild(item, + model_object->volumes[id]->name, + m_icon_solidmesh, + model_object->volumes[id]->config.option("extruder")->value, + false); + m_objects_ctrl->Expand(item); + } + +#ifndef __WXOSX__ + object_ctrl_selection_changed(); +#endif //__WXMSW__ +} + +void delete_object_from_list() +{ + auto item = m_objects_ctrl->GetSelection(); + if (!item || m_objects_model->GetParent(item) != wxDataViewItem(0)) + return; +// m_objects_ctrl->Select(m_objects_model->Delete(item)); + m_objects_model->Delete(item); + +// if (m_objects_model->IsEmpty()) +// m_collpane_settings->Show(false); +} + +void delete_all_objects_from_list() +{ + m_objects_model->DeleteAll(); +// m_collpane_settings->Show(false); +} + +void set_object_count(int idx, int count) +{ + m_objects_model->SetValue(wxString::Format("%d", count), idx, 1); + m_objects_ctrl->Refresh(); +} + +void set_object_scale(int idx, int scale) +{ + m_objects_model->SetValue(wxString::Format("%d%%", scale), idx, 2); + m_objects_ctrl->Refresh(); +} + +void unselect_objects() +{ + if (!m_objects_ctrl->GetSelection()) + return; + + g_prevent_list_events = true; + m_objects_ctrl->UnselectAll(); + part_selection_changed(); + g_prevent_list_events = false; +} + +void select_current_object(int idx) +{ + g_prevent_list_events = true; + m_objects_ctrl->UnselectAll(); + if (idx < 0) { + g_prevent_list_events = false; + return; + } + m_objects_ctrl->Select(m_objects_model->GetItemById(idx)); + part_selection_changed(); + g_prevent_list_events = false; +} + +void remove() +{ + auto item = m_objects_ctrl->GetSelection(); + if (!item) + return; + + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + if (m_event_remove_object > 0) { + wxCommandEvent event(m_event_remove_object); + get_main_frame()->ProcessWindowEvent(event); + } +// delete_object_from_list(); + } + else + on_btn_del(); +} + +void object_ctrl_selection_changed() +{ + if (g_prevent_list_events) return; + + part_selection_changed(); + + if (m_event_object_selection_changed > 0) { + wxCommandEvent event(m_event_object_selection_changed); + event.SetInt(int(m_objects_model->GetParent(m_objects_ctrl->GetSelection()) != wxDataViewItem(0))); + event.SetId(m_selected_object_id); + get_main_frame()->ProcessWindowEvent(event); + } + +#ifdef __WXOSX__ + update_extruder_in_config(g_selected_extruder); +#endif //__WXOSX__ +} + +void object_ctrl_context_menu() +{ + wxDataViewItem item; + wxDataViewColumn* col; + m_objects_ctrl->HitTest(get_mouse_position_in_control(), item, col); + wxString title = col->GetTitle(); + if (!item) return; + + if (title == " ") + show_context_menu(); +// ys_FIXME +// else if (title == _("Name") && pt.x >15 && +// m_objects_model->GetIcon(item).GetRefData() == m_icon_manifold_warning.GetRefData()) +// { +// if (is_windows10()) +// fix_through_netfabb(); +// } +#ifndef __WXMSW__ + m_objects_ctrl->GetMainWindow()->SetToolTip(""); // hide tooltip +#endif //__WXMSW__ +} + +void object_ctrl_key_event(wxKeyEvent& event) +{ + if (event.GetKeyCode() == WXK_TAB) + m_objects_ctrl->Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward); + else if (event.GetKeyCode() == WXK_DELETE +#ifdef __WXOSX__ + || event.GetKeyCode() == WXK_BACK +#endif //__WXOSX__ + ){ + printf("WXK_BACK\n"); + remove(); + } + else + event.Skip(); +} + +void object_ctrl_item_value_change(wxDataViewEvent& event) +{ + if (event.GetColumn() == 3) + { + wxVariant variant; + m_objects_model->GetValue(variant, event.GetItem(), 3); +#ifdef __WXOSX__ + g_selected_extruder = variant.GetString(); +#else // --> for Linux + update_extruder_in_config(variant.GetString()); +#endif //__WXOSX__ + } +} + +//update_optgroup +void update_settings_list() +{ +#ifdef __WXGTK__ + auto parent = get_optgroup(ogFrequentlyObjectSettings)->get_parent(); +#else + auto parent = get_optgroup(ogFrequentlyObjectSettings)->parent(); +#endif /* __WXGTK__ */ + +// There is a bug related to Ubuntu overlay scrollbars, see https://github.com/prusa3d/Slic3r/issues/898 and https://github.com/prusa3d/Slic3r/issues/952. +// The issue apparently manifests when Show()ing a window with overlay scrollbars while the UI is frozen. For this reason, +// we will Thaw the UI prematurely on Linux. This means destroing the no_updates object prematurely. +#ifdef __linux__ + std::unique_ptr no_updates(new wxWindowUpdateLocker(parent)); +#else + wxWindowUpdateLocker noUpdates(parent); +#endif + + m_option_sizer->Clear(true); + + if (m_config) + { + auto extra_column = [](wxWindow* parent, const Line& line) + { + auto opt_key = (line.get_options())[0].opt_id; //we assume that we have one option per line + + auto btn = new wxBitmapButton(parent, wxID_ANY, wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("erase.png")), wxBITMAP_TYPE_PNG), + wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); + btn->Bind(wxEVT_BUTTON, [opt_key](wxEvent &event){ + (*m_config)->erase(opt_key); + wxTheApp->CallAfter([]() { update_settings_list(); }); + }); + return btn; + }; + + std::map> cat_options; + auto opt_keys = (*m_config)->keys(); + if (opt_keys.size() == 1 && opt_keys[0] == "extruder") + return; + + auto extruders_cnt = get_preset_bundle()->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); + + for (auto& opt_key : opt_keys) { + auto category = (*m_config)->def()->get(opt_key)->category; + if (category.empty() || + (category == "Extruders" && extruders_cnt==1)) continue; + + std::vector< std::string > new_category; + + auto& cat_opt = cat_options.find(category) == cat_options.end() ? new_category : cat_options.at(category); + cat_opt.push_back(opt_key); + if (cat_opt.size() == 1) + cat_options[category] = cat_opt; + } + + + m_og_settings.resize(0); + for (auto& cat : cat_options) { + if (cat.second.size() == 1 && cat.second[0] == "extruder") + continue; + + auto optgroup = std::make_shared(parent, cat.first, *m_config, false, ogDEFAULT, extra_column); + optgroup->label_width = 100; + optgroup->sidetext_width = 70; + + for (auto& opt : cat.second) + { + if (opt == "extruder") + continue; + Option option = optgroup->get_option(opt); + option.opt.width = 70; + optgroup->append_single_option_line(option); + } + optgroup->reload_config(); + m_option_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0); + m_og_settings.push_back(optgroup); + } + } + +#ifdef __linux__ + no_updates.reset(nullptr); +#endif + + get_right_panel()->Layout(); + get_right_panel()->GetParent()->Layout(); +} + +void get_settings_choice(wxMenu *menu, int id, bool is_part) +{ + auto category_name = menu->GetLabel(id); + + wxArrayString names; + wxArrayInt selections; + + settings_menu_hierarchy settings_menu; + get_options_menu(settings_menu, is_part); + std::vector< std::pair > *settings_list = nullptr; + + auto opt_keys = (*m_config)->keys(); + + for (auto& cat : settings_menu) + { + if (_(cat.first) == category_name) { + int sel = 0; + for (auto& pair : cat.second) { + names.Add(_(pair.second)); + if (find(opt_keys.begin(), opt_keys.end(), pair.first) != opt_keys.end()) + selections.Add(sel); + sel++; + } + settings_list = &cat.second; + break; + } + } + + if (!settings_list) + return; + + if (wxGetMultipleChoices(selections, _(L("Select showing settings")), category_name, names) ==0 ) + return; + + std::vector selected_options; + for (auto sel : selections) + selected_options.push_back((*settings_list)[sel].first); + + for (auto& setting:(*settings_list) ) + { + auto& opt_key = setting.first; + if (find(opt_keys.begin(), opt_keys.end(), opt_key) != opt_keys.end() && + find(selected_options.begin(), selected_options.end(), opt_key) == selected_options.end()) + (*m_config)->erase(opt_key); + + if(find(opt_keys.begin(), opt_keys.end(), opt_key) == opt_keys.end() && + find(selected_options.begin(), selected_options.end(), opt_key) != selected_options.end()) + (*m_config)->set_key_value(opt_key, m_default_config.get()->option(opt_key)->clone()); + } + + update_settings_list(); +} + +bool cur_item_hase_children() +{ + wxDataViewItemArray children; + if (m_objects_model->GetChildren(m_objects_ctrl->GetSelection(), children) > 0) + return true; + return false; +} + +wxMenuItem* menu_item_split(wxMenu* menu, int id) { + auto menu_item = new wxMenuItem(menu, id, _(L("Split to parts"))); + menu_item->SetBitmap(m_bmp_split); + return menu_item; +} + +wxMenuItem* menu_item_settings(wxMenu* menu, int id) { + auto menu_item = new wxMenuItem(menu, id, _(L("Add settings"))); + menu_item->SetBitmap(m_bmp_cog); + + auto sub_menu = create_add_settings_popupmenu(false); + menu_item->SetSubMenu(sub_menu); + return menu_item; +} + +wxMenu *create_add_part_popupmenu() +{ + wxMenu *menu = new wxMenu; + std::vector menu_items = { L("Add part"), L("Add modifier"), L("Add generic") }; + + wxWindowID config_id_base = wxWindow::NewControlId(menu_items.size()+2); + + int i = 0; + for (auto& item : menu_items) { + auto menu_item = new wxMenuItem(menu, config_id_base + i, _(item)); + menu_item->SetBitmap(i == 0 ? m_icon_solidmesh : m_icon_modifiermesh); + menu->Append(menu_item); + i++; + } + + menu->AppendSeparator(); + auto menu_item = menu_item_split(menu, config_id_base + i); + menu->Append(menu_item); + menu_item->Enable(!cur_item_hase_children()); + + menu->AppendSeparator(); + // Append settings popupmenu + menu->Append(menu_item_settings(menu, config_id_base + i + 1)); + + wxWindow* win = get_tab_panel()->GetPage(0); + + menu->Bind(wxEVT_MENU, [config_id_base, win, menu](wxEvent &event){ + switch (event.GetId() - config_id_base) { + case 0: + on_btn_load(win); + break; + case 1: + on_btn_load(win, true); + break; + case 2: + on_btn_load(win, true, true); + break; + case 3: + on_btn_split(false); + break; + default:{ + get_settings_choice(menu, event.GetId(), false); + break;} + } + }); + + return menu; +} + +wxMenu *create_part_settings_popupmenu() +{ + wxMenu *menu = new wxMenu; + wxWindowID config_id_base = wxWindow::NewControlId(2); + + menu->Append(menu_item_split(menu, config_id_base)); + + menu->AppendSeparator(); + // Append settings popupmenu + menu->Append(menu_item_settings(menu, config_id_base + 1)); + + menu->Bind(wxEVT_MENU, [config_id_base, menu](wxEvent &event){ + switch (event.GetId() - config_id_base) { + case 0: + on_btn_split(true); + break; + default:{ + get_settings_choice(menu, event.GetId(), true); + break; } + } + }); + + return menu; +} + +wxMenu *create_add_settings_popupmenu(bool is_part) +{ + wxMenu *menu = new wxMenu; + + auto categories = get_category_icon(); + + settings_menu_hierarchy settings_menu; + get_options_menu(settings_menu, is_part); + + for (auto cat : settings_menu) + { + auto menu_item = new wxMenuItem(menu, wxID_ANY, _(cat.first)); + menu_item->SetBitmap(categories.find(cat.first) == categories.end() ? + wxNullBitmap : categories.at(cat.first)); + menu->Append(menu_item); + } + + menu->Bind(wxEVT_MENU, [menu](wxEvent &event) { + get_settings_choice(menu, event.GetId(), true); + }); + + return menu; +} + +void show_context_menu() +{ + auto item = m_objects_ctrl->GetSelection(); + if (item) + { + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + auto menu = create_add_part_popupmenu(); + get_tab_panel()->GetPage(0)->PopupMenu(menu); + } + else { + auto menu = create_part_settings_popupmenu(); + get_tab_panel()->GetPage(0)->PopupMenu(menu); + } + } +} + +// ****** + +void load_part( wxWindow* parent, ModelObject* model_object, + wxArrayString& part_names, const bool is_modifier) +{ + wxArrayString input_files; + open_model(parent, input_files); + for (int i = 0; i < input_files.size(); ++i) { + std::string input_file = input_files.Item(i).ToStdString(); + + Model model; + try { + model = Model::read_from_file(input_file); + } + catch (std::exception &e) { + auto msg = _(L("Error! ")) + input_file + " : " + e.what() + "."; + show_error(parent, msg); + exit(1); + } + + for ( auto object : model.objects) { + for (auto volume : object->volumes) { + auto new_volume = model_object->add_volume(*volume); + new_volume->modifier = is_modifier; + boost::filesystem::path(input_file).filename().string(); + new_volume->name = boost::filesystem::path(input_file).filename().string(); + + part_names.Add(new_volume->name); + + // apply the same translation we applied to the object + new_volume->mesh.translate( model_object->origin_translation.x, + model_object->origin_translation.y, + model_object->origin_translation.y ); + // set a default extruder value, since user can't add it manually + new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); + + m_parts_changed = true; + } + } + } +} + +void load_lambda( wxWindow* parent, ModelObject* model_object, + wxArrayString& part_names, const bool is_modifier) +{ + auto dlg = new LambdaObjectDialog(parent); + if (dlg->ShowModal() == wxID_CANCEL) { + return; + } + + std::string name = "lambda-"; + TriangleMesh mesh; + + auto params = dlg->ObjectParameters(); + switch (params.type) + { + case LambdaTypeBox:{ + mesh = make_cube(params.dim[0], params.dim[1], params.dim[2]); + name += "Box"; + break;} + case LambdaTypeCylinder:{ + mesh = make_cylinder(params.cyl_r, params.cyl_h); + name += "Cylinder"; + break;} + case LambdaTypeSphere:{ + mesh = make_sphere(params.sph_rho); + name += "Sphere"; + break;} + case LambdaTypeSlab:{ + const auto& size = model_object->bounding_box().size(); + mesh = make_cube(size.x*1.5, size.y*1.5, params.slab_h); + // box sets the base coordinate at 0, 0, move to center of plate and move it up to initial_z + mesh.translate(-size.x*1.5 / 2.0, -size.y*1.5 / 2.0, params.slab_z); + name += "Slab"; + break; } + default: + break; + } + mesh.repair(); + + auto new_volume = model_object->add_volume(mesh); + new_volume->modifier = is_modifier; + new_volume->name = name; + // set a default extruder value, since user can't add it manually + new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); + + part_names.Add(name); + + m_parts_changed = true; +} + +void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/, bool is_lambda/* = false*/) +{ + auto item = m_objects_ctrl->GetSelection(); + if (!item) + return; + int obj_idx = -1; + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) + obj_idx = m_objects_model->GetIdByItem(item); + else + return; + + if (obj_idx < 0) return; + wxArrayString part_names; + if (is_lambda) + load_lambda(parent, (*m_objects)[obj_idx], part_names, is_modifier); + else + load_part(parent, (*m_objects)[obj_idx], part_names, is_modifier); + + parts_changed(obj_idx); + + for (int i = 0; i < part_names.size(); ++i) + m_objects_ctrl->Select( m_objects_model->AddChild(item, part_names.Item(i), + is_modifier ? m_icon_modifiermesh : m_icon_solidmesh)); +// part_selection_changed(); +#ifdef __WXMSW__ + object_ctrl_selection_changed(); +#endif //__WXMSW__ +} + +void on_btn_del() +{ + auto item = m_objects_ctrl->GetSelection(); + if (!item) return; + + auto volume_id = m_objects_model->GetVolumeIdByItem(item); + if (volume_id < 0) + return; + auto volume = (*m_objects)[m_selected_object_id]->volumes[volume_id]; + + // if user is deleting the last solid part, throw error + int solid_cnt = 0; + for (auto vol : (*m_objects)[m_selected_object_id]->volumes) + if (!vol->modifier) + ++solid_cnt; + if (!volume->modifier && solid_cnt == 1) { + Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last solid part from this object."))); + return; + } + + (*m_objects)[m_selected_object_id]->delete_volume(volume_id); + m_parts_changed = true; + + parts_changed(m_selected_object_id); + + m_objects_ctrl->Select(m_objects_model->Delete(item)); + part_selection_changed(); +// #ifdef __WXMSW__ +// object_ctrl_selection_changed(); +// #endif //__WXMSW__ +} + +void on_btn_split(const bool split_part) +{ + auto item = m_objects_ctrl->GetSelection(); + if (!item || m_selected_object_id<0) + return; + auto volume_id = m_objects_model->GetVolumeIdByItem(item); + ModelVolume* volume; + if (volume_id < 0) { + if (split_part) return; + else + volume = (*m_objects)[m_selected_object_id]->volumes[0]; } + else + volume = (*m_objects)[m_selected_object_id]->volumes[volume_id]; + DynamicPrintConfig& config = get_preset_bundle()->printers.get_edited_preset().config; + auto nozzle_dmrs_cnt = config.option("nozzle_diameter")->values.size(); + auto split_rez = volume->split(nozzle_dmrs_cnt); + if (split_rez == 1) { + wxMessageBox(_(L("The selected object couldn't be split because it contains only one part."))); + return; + } + + auto model_object = (*m_objects)[m_selected_object_id]; + + if (split_part) { + auto parent = m_objects_model->GetParent(item); + m_objects_model->DeleteChildren(parent); + + for (auto id = 0; id < model_object->volumes.size(); id++) + m_objects_model->AddChild(parent, model_object->volumes[id]->name, + model_object->volumes[id]->modifier ? m_icon_modifiermesh : m_icon_solidmesh, + model_object->volumes[id]->config.option("extruder")->value, + false); + + m_objects_ctrl->Expand(parent); + } + else { + for (auto id = 0; id < model_object->volumes.size(); id++) + m_objects_model->AddChild(item, model_object->volumes[id]->name, + m_icon_solidmesh, + model_object->volumes[id]->config.option("extruder")->value, + false); + m_objects_ctrl->Expand(item); + } +} + +void on_btn_move_up(){ + auto item = m_objects_ctrl->GetSelection(); + if (!item) + return; + auto volume_id = m_objects_model->GetVolumeIdByItem(item); + if (volume_id < 0) + return; + auto& volumes = (*m_objects)[m_selected_object_id]->volumes; + if (0 < volume_id && volume_id < volumes.size()) { + std::swap(volumes[volume_id - 1], volumes[volume_id]); + m_parts_changed = true; + m_objects_ctrl->Select(m_objects_model->MoveChildUp(item)); + part_selection_changed(); +// #ifdef __WXMSW__ +// object_ctrl_selection_changed(); +// #endif //__WXMSW__ + } +} + +void on_btn_move_down(){ + auto item = m_objects_ctrl->GetSelection(); + if (!item) + return; + auto volume_id = m_objects_model->GetVolumeIdByItem(item); + if (volume_id < 0) + return; + auto& volumes = (*m_objects)[m_selected_object_id]->volumes; + if (0 <= volume_id && volume_id+1 < volumes.size()) { + std::swap(volumes[volume_id + 1], volumes[volume_id]); + m_parts_changed = true; + m_objects_ctrl->Select(m_objects_model->MoveChildDown(item)); + part_selection_changed(); +// #ifdef __WXMSW__ +// object_ctrl_selection_changed(); +// #endif //__WXMSW__ + } +} + +void parts_changed(int obj_idx) +{ + if (m_event_object_settings_changed <= 0) return; + + wxCommandEvent e(m_event_object_settings_changed); + auto event_str = wxString::Format("%d %d %d", obj_idx, + is_parts_changed() ? 1 : 0, + is_part_settings_changed() ? 1 : 0); + e.SetString(event_str); + get_main_frame()->ProcessWindowEvent(e); +} + +void update_settings_value() +{ + auto og = get_optgroup(ogFrequentlyObjectSettings); + if (m_selected_object_id < 0 || m_objects->size() <= m_selected_object_id) { + og->set_value("scale_x", 0); + og->set_value("scale_y", 0); + og->set_value("scale_z", 0); + og->disable(); + return; + } + g_is_percent_scale = boost::any_cast(og->get_value("scale_unit")) == _("%"); + update_scale_values(); + update_rotation_values(); + og->enable(); +} + +void part_selection_changed() +{ + auto item = m_objects_ctrl->GetSelection(); + int obj_idx = -1; + auto og = get_optgroup(ogFrequentlyObjectSettings); + if (item) + { + bool is_part = false; + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + obj_idx = m_objects_model->GetIdByItem(item); + og->set_name(" " + _(L("Object Settings")) + " "); + m_config = std::make_shared(&(*m_objects)[obj_idx]->config); + } + else { + auto parent = m_objects_model->GetParent(item); + // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene + obj_idx = m_objects_model->GetIdByItem(parent); + og->set_name(" " + _(L("Part Settings")) + " "); + is_part = true; + auto volume_id = m_objects_model->GetVolumeIdByItem(item); + m_config = std::make_shared(&(*m_objects)[obj_idx]->volumes[volume_id]->config); + } + + auto config = m_config; + og->set_value("object_name", m_objects_model->GetName(item)); + m_default_config = std::make_shared(*DynamicPrintConfig::new_from_defaults_keys(get_options(is_part))); + } + else { + wxString empty_str = wxEmptyString; + og->set_value("object_name", empty_str); + m_config = nullptr; + } + + update_settings_list(); + + m_selected_object_id = obj_idx; + + update_settings_value(); + +/* wxWindowUpdateLocker noUpdates(get_right_panel()); + + m_move_options = Point3(0, 0, 0); + m_last_coords = Point3(0, 0, 0); + // reset move sliders + std::vector opt_keys = {"x", "y", "z"}; + auto og = get_optgroup(ogObjectMovers); + for (auto opt_key: opt_keys) + og->set_value(opt_key, int(0)); + +// if (!item || m_selected_object_id < 0){ + if (m_selected_object_id < 0){ + m_sizer_object_buttons->Show(false); + m_sizer_part_buttons->Show(false); + m_sizer_object_movers->Show(false); + m_collpane_settings->Show(false); + return; + } + + m_collpane_settings->Show(true); + + auto volume_id = m_objects_model->GetVolumeIdByItem(item); + if (volume_id < 0){ + m_sizer_object_buttons->Show(true); + m_sizer_part_buttons->Show(false); + m_sizer_object_movers->Show(false); + m_collpane_settings->SetLabelText(_(L("Object Settings")) + ":"); + +// elsif($itemData->{type} eq 'object') { +// # select nothing in 3D preview +// +// # attach object config to settings panel +// $self->{optgroup_movers}->disable; +// $self->{staticbox}->SetLabel('Object Settings'); +// @opt_keys = (map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new); +// $config = $self->{model_object}->config; +// } + + return; + } + + m_collpane_settings->SetLabelText(_(L("Part Settings")) + ":"); + + m_sizer_object_buttons->Show(false); + m_sizer_part_buttons->Show(true); + m_sizer_object_movers->Show(true); + + auto bb_size = m_objects[m_selected_object_id]->bounding_box().size(); + int scale = 10; //?? + + m_mover_x->SetMin(-bb_size.x * 4 * scale); + m_mover_x->SetMax(bb_size.x * 4 * scale); + + m_mover_y->SetMin(-bb_size.y * 4 * scale); + m_mover_y->SetMax(bb_size.y * 4 * scale); + + m_mover_z->SetMin(-bb_size.z * 4 * scale); + m_mover_z->SetMax(bb_size.z * 4 * scale); + + + +// my ($config, @opt_keys); + m_btn_move_up->Enable(volume_id > 0); + m_btn_move_down->Enable(volume_id + 1 < m_objects[m_selected_object_id]->volumes.size()); + + // attach volume config to settings panel + auto volume = m_objects[m_selected_object_id]->volumes[volume_id]; + + if (volume->modifier) + og->enable(); + else + og->disable(); + +// auto config = volume->config; + + // get default values +// @opt_keys = @{Slic3r::Config::PrintRegion->new->get_keys}; +// } +/* + # get default values + my $default_config = Slic3r::Config::new_from_defaults_keys(\@opt_keys); + + # append default extruder + push @opt_keys, 'extruder'; + $default_config->set('extruder', 0); + $config->set_ifndef('extruder', 0); + $self->{settings_panel}->set_default_config($default_config); + $self->{settings_panel}->set_config($config); + $self->{settings_panel}->set_opt_keys(\@opt_keys); + $self->{settings_panel}->set_fixed_options([qw(extruder)]); + $self->{settings_panel}->enable; + } + */ +} + +void set_extruder_column_hidden(bool hide) +{ + m_objects_ctrl->GetColumn(3)->SetHidden(hide); +} + +void update_extruder_in_config(const wxString& selection) +{ + if (!m_config || selection.empty()) + return; + + int extruder = selection.size() > 1 ? 0 : atoi(selection.c_str()); + (*m_config)->set_key_value("extruder", new ConfigOptionInt(extruder)); + + if (m_event_update_scene > 0) { + wxCommandEvent e(m_event_update_scene); + get_main_frame()->ProcessWindowEvent(e); + } +} + +void update_scale_values() +{ + update_scale_values((*m_objects)[m_selected_object_id]->instance_bounding_box(0).size(), + (*m_objects)[m_selected_object_id]->instances[0]->scaling_factor); +} + +void update_scale_values(const Pointf3& size, float scaling_factor) +{ + auto og = get_optgroup(ogFrequentlyObjectSettings); + + if (g_is_percent_scale) { + auto scale = scaling_factor * 100; + og->set_value("scale_x", int(scale)); + og->set_value("scale_y", int(scale)); + og->set_value("scale_z", int(scale)); + } + else { + og->set_value("scale_x", int(size.x + 0.5)); + og->set_value("scale_y", int(size.y + 0.5)); + og->set_value("scale_z", int(size.z + 0.5)); + } +} + +void update_rotation_values() +{ + auto og = get_optgroup(ogFrequentlyObjectSettings); + + og->set_value("rotation_x", 0); + og->set_value("rotation_y", 0); + + auto rotation_z = (*m_objects)[m_selected_object_id]->instances[0]->rotation; + auto deg = int(Geometry::rad2deg(rotation_z)); +// if (deg > 180) deg -= 360; + + og->set_value("rotation_z", deg); +} + +void update_rotation_value(const double angle, const std::string& axis) +{ + auto og = get_optgroup(ogFrequentlyObjectSettings); + + int deg = int(Geometry::rad2deg(angle)); +// if (deg>180) deg -= 360; + + og->set_value("rotation_"+axis, deg); +} + +void on_begin_drag(wxDataViewEvent &event) +{ + wxDataViewItem item(event.GetItem()); + + // only allow drags for item, not containers + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + event.Veto(); + return; + } + + /* Under MSW or OSX, DnD moves an item to the place of another selected item + * But under GTK, DnD moves an item between another two items. + * And as a result - call EVT_CHANGE_SELECTION to unselect all items. + * To prevent such behavior use g_prevent_list_events + **/ + g_prevent_list_events = true;//it's needed for GTK + + wxTextDataObject *obj = new wxTextDataObject; + obj->SetText(wxString::Format("%d", m_objects_model->GetVolumeIdByItem(item))); + event.SetDataObject(obj); + event.SetDragFlags(/*wxDrag_AllowMove*/wxDrag_DefaultMove); // allows both copy and move; +} + +void on_drop_possible(wxDataViewEvent &event) +{ + wxDataViewItem item(event.GetItem()); + + // only allow drags for item or background, not containers + if (item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0) || + event.GetDataFormat() != wxDF_UNICODETEXT) + event.Veto(); +} + +void on_drop(wxDataViewEvent &event) +{ + wxDataViewItem item(event.GetItem()); + + // only allow drops for item, not containers + if (item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0) || + event.GetDataFormat() != wxDF_UNICODETEXT) { + event.Veto(); + return; + } + + wxTextDataObject obj; + obj.SetData(wxDF_UNICODETEXT, event.GetDataSize(), event.GetDataBuffer()); + + int from_volume_id = std::stoi(obj.GetText().ToStdString()); + int to_volume_id = m_objects_model->GetVolumeIdByItem(item); + +#ifdef __WXGTK__ + /* Under GTK, DnD moves an item between another two items. + * And event.GetItem() return item, which is under "insertion line" + * So, if we move item down we should to decrease the to_volume_id value + **/ + if (to_volume_id > from_volume_id) to_volume_id--; +#endif // __WXGTK__ + + m_objects_ctrl->Select(m_objects_model->ReorganizeChildren(from_volume_id, to_volume_id, + m_objects_model->GetParent(item))); + + auto& volumes = (*m_objects)[m_selected_object_id]->volumes; + auto delta = to_volume_id < from_volume_id ? -1 : 1; + int cnt = 0; + for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id+=delta, cnt++) + std::swap(volumes[id], volumes[id +delta]); + + g_prevent_list_events = false; +} + +void update_objects_list_extruder_column(const int extruders_count) +{ + // delete old 3rd column + m_objects_ctrl->DeleteColumn(m_objects_ctrl->GetColumn(3)); + // insert new created 3rd column + m_objects_ctrl->InsertColumn(3, object_ctrl_create_extruder_column(extruders_count)); + // set show/hide for this column + set_extruder_column_hidden(extruders_count <= 1); +} + +} //namespace GUI +} //namespace Slic3r \ No newline at end of file diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp new file mode 100644 index 000000000..15be90bbf --- /dev/null +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -0,0 +1,126 @@ +#ifndef slic3r_GUI_ObjectParts_hpp_ +#define slic3r_GUI_ObjectParts_hpp_ + +class wxWindow; +class wxSizer; +class wxBoxSizer; +class wxString; +class wxArrayString; +class wxMenu; +class wxDataViewEvent; +class wxKeyEvent; + +namespace Slic3r { +class ModelObject; +class Model; + +namespace GUI { + +enum ogGroup{ + ogFrequentlyChangingParameters, + ogFrequentlyObjectSettings, + ogCurrentSettings +// ogObjectSettings, +// ogObjectMovers, +// ogPartSettings +}; + +enum LambdaTypeIDs{ + LambdaTypeBox, + LambdaTypeCylinder, + LambdaTypeSphere, + LambdaTypeSlab +}; + +struct OBJECT_PARAMETERS +{ + LambdaTypeIDs type = LambdaTypeBox; + double dim[3];// = { 1.0, 1.0, 1.0 }; + int cyl_r = 1; + int cyl_h = 1; + double sph_rho = 1.0; + double slab_h = 1.0; + double slab_z = 0.0; +}; + +void add_collapsible_panes(wxWindow* parent, wxBoxSizer* sizer); +void add_objects_list(wxWindow* parent, wxBoxSizer* sizer); +void add_object_settings(wxWindow* parent, wxBoxSizer* sizer); +void show_collpane_settings(bool expert_mode); + +wxMenu *create_add_settings_popupmenu(bool is_part); +wxMenu *create_add_part_popupmenu(); +wxMenu *create_part_settings_popupmenu(); + +// Add object to the list +//void add_object(const std::string &name); +void add_object_to_list(const std::string &name, ModelObject* model_object); +// Delete object from the list +void delete_object_from_list(); +// Delete all objects from the list +void delete_all_objects_from_list(); +// Set count of object on c++ side +void set_object_count(int idx, int count); +// Set object scale on c++ side +void set_object_scale(int idx, int scale); +// Unselect all objects in the list on c++ side +void unselect_objects(); +// Select current object in the list on c++ side +void select_current_object(int idx); +// Remove objects/sub-object from the list +void remove(); + +void object_ctrl_selection_changed(); +void object_ctrl_context_menu(); +void object_ctrl_key_event(wxKeyEvent& event); +void object_ctrl_item_value_change(wxDataViewEvent& event); +void show_context_menu(); + +void init_mesh_icons(); +void set_event_object_selection_changed(const int& event); +void set_event_object_settings_changed(const int& event); +void set_event_remove_object(const int& event); +void set_event_update_scene(const int& event); +void set_objects_from_model(Model &model); + +bool is_parts_changed(); +bool is_part_settings_changed(); + +void load_part( wxWindow* parent, ModelObject* model_object, + wxArrayString& part_names, const bool is_modifier); + +void load_lambda(wxWindow* parent, ModelObject* model_object, + wxArrayString& part_names, const bool is_modifier); + +void on_btn_load(wxWindow* parent, bool is_modifier = false, bool is_lambda = false); +void on_btn_del(); +void on_btn_split(const bool split_part); +void on_btn_move_up(); +void on_btn_move_down(); + +void parts_changed(int obj_idx); +void part_selection_changed(); + +void update_settings_value(); +// show/hide "Extruder" column for Objects List +void set_extruder_column_hidden(bool hide); +// update extruder in current config +void update_extruder_in_config(const wxString& selection); +// update scale values after scale unit changing or "gizmos" +void update_scale_values(); +void update_scale_values(const Pointf3& size, float scale); +// update rotation values object selection changing +void update_rotation_values(); +// update rotation value after "gizmos" +void update_rotation_value(const double angle, const std::string& axis); + +void on_begin_drag(wxDataViewEvent &event); +void on_drop_possible(wxDataViewEvent &event); +void on_drop(wxDataViewEvent &event); + +// update extruder column for objects_ctrl according to extruders count +void update_objects_list_extruder_column(const int extruders_count); + +} //namespace GUI +} //namespace Slic3r +#endif //slic3r_GUI_ObjectParts_hpp_ \ No newline at end of file diff --git a/xs/src/slic3r/GUI/LambdaObjectDialog.cpp b/xs/src/slic3r/GUI/LambdaObjectDialog.cpp new file mode 100644 index 000000000..7543821c0 --- /dev/null +++ b/xs/src/slic3r/GUI/LambdaObjectDialog.cpp @@ -0,0 +1,176 @@ +#include "LambdaObjectDialog.hpp" + +#include +#include +#include "OptionsGroup.hpp" + +namespace Slic3r +{ +namespace GUI +{ +static wxString dots("…", wxConvUTF8); + +LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent) +{ + Create(parent, wxID_ANY, _(L("Lambda Object")), + wxDefaultPosition, wxDefaultSize, + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); + + // instead of double dim[3] = { 1.0, 1.0, 1.0 }; + object_parameters.dim[0] = 1.0; + object_parameters.dim[1] = 1.0; + object_parameters.dim[2] = 1.0; + + sizer = new wxBoxSizer(wxVERTICAL); + + // modificator options + m_modificator_options_book = new wxChoicebook( this, wxID_ANY, wxDefaultPosition, + wxDefaultSize, wxCHB_TOP); + sizer->Add(m_modificator_options_book, 1, wxEXPAND| wxALL, 10); + + auto optgroup = init_modificator_options_page(_(L("Box"))); + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ + int opt_id = opt_key == "l" ? 0 : + opt_key == "w" ? 1 : + opt_key == "h" ? 2 : -1; + if (opt_id < 0) return; + object_parameters.dim[opt_id] = boost::any_cast(value); + }; + + ConfigOptionDef def; + def.width = 70; + def.type = coFloat; + def.default_value = new ConfigOptionFloat{ 1.0 }; + def.label = L("L"); + Option option(def, "l"); + optgroup->append_single_option_line(option); + + def.label = L("W"); + option = Option(def, "w"); + optgroup->append_single_option_line(option); + + def.label = L("H"); + option = Option(def, "h"); + optgroup->append_single_option_line(option); + + optgroup = init_modificator_options_page(_(L("Cylinder"))); + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ + int val = boost::any_cast(value); + if (opt_key == "cyl_r") + object_parameters.cyl_r = val; + else if (opt_key == "cyl_h") + object_parameters.cyl_h = val; + else return; + }; + + def.type = coInt; + def.default_value = new ConfigOptionInt{ 1 }; + def.label = L("Radius"); + option = Option(def, "cyl_r"); + optgroup->append_single_option_line(option); + + def.label = L("Height"); + option = Option(def, "cyl_h"); + optgroup->append_single_option_line(option); + + optgroup = init_modificator_options_page(_(L("Sphere"))); + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ + if (opt_key == "sph_rho") + object_parameters.sph_rho = boost::any_cast(value); + else return; + }; + + def.type = coFloat; + def.default_value = new ConfigOptionFloat{ 1.0 }; + def.label = L("Rho"); + option = Option(def, "sph_rho"); + optgroup->append_single_option_line(option); + + optgroup = init_modificator_options_page(_(L("Slab"))); + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ + double val = boost::any_cast(value); + if (opt_key == "slab_z") + object_parameters.slab_z = val; + else if (opt_key == "slab_h") + object_parameters.slab_h = val; + else return; + }; + + def.label = L("H"); + option = Option(def, "slab_h"); + optgroup->append_single_option_line(option); + + def.label = L("Initial Z"); + option = Option(def, "slab_z"); + optgroup->append_single_option_line(option); + + Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent e) + { + auto page_idx = m_modificator_options_book->GetSelection(); + if (page_idx < 0) return; + switch (page_idx) + { + case 0: + object_parameters.type = LambdaTypeBox; + break; + case 1: + object_parameters.type = LambdaTypeCylinder; + break; + case 2: + object_parameters.type = LambdaTypeSphere; + break; + case 3: + object_parameters.type = LambdaTypeSlab; + break; + default: + break; + } + })); + + + auto button_sizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL); + + wxButton* btn_OK = static_cast(FindWindowById(wxID_OK, this)); + btn_OK->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + // validate user input + if (!CanClose())return; + EndModal(wxID_OK); + Destroy(); + }); + + wxButton* btn_CANCEL = static_cast(FindWindowById(wxID_CANCEL, this)); + btn_CANCEL->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + // validate user input + if (!CanClose())return; + EndModal(wxID_CANCEL); + Destroy(); + }); + + sizer->Add(button_sizer, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10); + + SetSizer(sizer); + sizer->Fit(this); + sizer->SetSizeHints(this); +} + +// Called from the constructor. +// Create a panel for a rectangular / circular / custom bed shape. +ConfigOptionsGroupShp LambdaObjectDialog::init_modificator_options_page(wxString title){ + + auto panel = new wxPanel(m_modificator_options_book); + + ConfigOptionsGroupShp optgroup; + optgroup = std::make_shared(panel, _(L("Add")) + " " +title + " " +dots); + optgroup->label_width = 100; + + m_optgroups.push_back(optgroup); + + panel->SetSizerAndFit(optgroup->sizer); + m_modificator_options_book->AddPage(panel, title); + + return optgroup; +} + + +} //namespace GUI +} //namespace Slic3r diff --git a/xs/src/slic3r/GUI/LambdaObjectDialog.hpp b/xs/src/slic3r/GUI/LambdaObjectDialog.hpp new file mode 100644 index 000000000..a70c12449 --- /dev/null +++ b/xs/src/slic3r/GUI/LambdaObjectDialog.hpp @@ -0,0 +1,35 @@ +#ifndef slic3r_LambdaObjectDialog_hpp_ +#define slic3r_LambdaObjectDialog_hpp_ + +#include "GUI.hpp" + +#include +#include +#include + +namespace Slic3r +{ +namespace GUI +{ +using ConfigOptionsGroupShp = std::shared_ptr; +class LambdaObjectDialog : public wxDialog +{ + wxChoicebook* m_modificator_options_book; + std::vector m_optgroups; +public: + LambdaObjectDialog(wxWindow* parent); + ~LambdaObjectDialog(){} + + bool CanClose() { return true; } // ??? + OBJECT_PARAMETERS& ObjectParameters(){ return object_parameters; } + + ConfigOptionsGroupShp init_modificator_options_page(wxString title); + + // Note whether the window was already closed, so a pending update is not executed. + bool m_already_closed = false; + OBJECT_PARAMETERS object_parameters; + wxBoxSizer* sizer = nullptr; +}; +} //namespace GUI +} //namespace Slic3r +#endif //slic3r_LambdaObjectDialog_hpp_ diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index a2d6559a9..453a2f3f7 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -30,6 +30,7 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co opt.gui_type.compare("i_enum_closed") == 0) { m_fields.emplace(id, STDMOVE(Choice::Create(parent(), opt, id))); } else if (opt.gui_type.compare("slider") == 0) { + m_fields.emplace(id, STDMOVE(SliderCtrl::Create(parent(), opt, id))); } else if (opt.gui_type.compare("i_spin") == 0) { // Spinctrl } else if (opt.gui_type.compare("legend") == 0) { // StaticText m_fields.emplace(id, STDMOVE(StaticText::Create(parent(), opt, id))); @@ -88,17 +89,23 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co if (!this->m_disabled) this->back_to_sys_value(opt_id); }; - if (!m_show_modified_btns) { - field->m_Undo_btn->Hide(); - field->m_Undo_to_sys_btn->Hide(); - } -// if (nonsys_btn_icon != nullptr) -// field->set_nonsys_btn_icon(*nonsys_btn_icon); // assign function objects for callbacks, etc. return field; } +void OptionsGroup::add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field) +{ + if (!m_show_modified_btns) { + field->m_Undo_btn->Hide(); + field->m_Undo_to_sys_btn->Hide(); + return; + } + + sizer->Add(field->m_Undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); + sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL); +} + void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* = nullptr*/) { //! if (line.sizer != nullptr || (line.widget != nullptr && line.full_width > 0)){ if ( (line.sizer != nullptr || line.widget != nullptr) && line.full_width){ @@ -133,8 +140,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* const auto& field = build_field(option); auto btn_sizer = new wxBoxSizer(wxHORIZONTAL); - btn_sizer->Add(field->m_Undo_to_sys_btn); - btn_sizer->Add(field->m_Undo_btn); + add_undo_buttuns_to_sizer(btn_sizer, field); tmp_sizer->Add(btn_sizer, 0, wxEXPAND | wxALL, 0); if (is_window_field(field)) tmp_sizer->Add(field->getWindow(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5); @@ -149,6 +155,18 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* m_panel->Layout(); #endif /* __WXGTK__ */ + // if we have an extra column, build it + if (extra_column) { + if (extra_column) { + grid_sizer->Add(extra_column(parent(), line), 0, wxALIGN_CENTER_VERTICAL, 0); + } + else { + // if the callback provides no sizer for the extra cell, put a spacer + grid_sizer->AddSpacer(1); + } + } + + // Build a label if we have it wxStaticText* label=nullptr; if (label_width != 0) { @@ -163,7 +181,8 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* wxDefaultPosition, wxSize(label_width, -1), label_style); label->SetFont(label_font); label->Wrap(label_width); // avoid a Linux/GTK bug - grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | wxALIGN_CENTER_VERTICAL, 5); + grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | + (m_flag == ogSIDE_OPTIONS_VERTICAL ? wxTOP : wxALIGN_CENTER_VERTICAL), 5); if (line.label_tooltip.compare("") != 0) label->SetToolTip(line.label_tooltip); } @@ -177,28 +196,35 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* return; } - // if we have a single option with no sidetext just add it directly to the grid sizer - auto sizer = new wxBoxSizer(wxHORIZONTAL); - grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM|wxTOP|wxLEFT), staticbox ? 0 : 1); + // If we're here, we have more than one option or a single option with sidetext + // so we need a horizontal sizer to arrange these things + auto sizer = new wxBoxSizer(m_flag == ogSIDE_OPTIONS_VERTICAL ? wxVERTICAL : wxHORIZONTAL); + grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM | wxTOP | wxLEFT), staticbox ? 0 : 1); + // If we have a single option with no sidetext just add it directly to the grid sizer if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 && option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0) { const auto& option = option_set.front(); const auto& field = build_field(option, label); - sizer->Add(field->m_Undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); - sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL); + add_undo_buttuns_to_sizer(sizer, field); if (is_window_field(field)) sizer->Add(field->getWindow(), option.opt.full_width ? 1 : 0, (option.opt.full_width ? wxEXPAND : 0) | wxBOTTOM | wxTOP | wxALIGN_CENTER_VERTICAL, (wxOSX||!staticbox) ? 0 : 2); if (is_sizer_field(field)) - sizer->Add(field->getSizer(), 0, (option.opt.full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0); + sizer->Add(field->getSizer(), 1, (option.opt.full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0); return; } - // if we're here, we have more than one option or a single option with sidetext - // so we need a horizontal sizer to arrange these things - for (auto opt : option_set) { + for (auto opt : option_set) { ConfigOptionDef option = opt.opt; + wxSizer* sizer_tmp; + if (m_flag == ogSIDE_OPTIONS_VERTICAL){ + auto sz = new wxFlexGridSizer(1, 3, 2, 2); + sz->RemoveGrowableCol(2); + sizer_tmp = sz; + } + else + sizer_tmp = sizer; // add label if any if (option.label != "") { wxString str_label = _(option.label); @@ -208,34 +234,38 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* // L_str(option.label); label = new wxStaticText(parent(), wxID_ANY, str_label + ":", wxDefaultPosition, wxDefaultSize); label->SetFont(label_font); - sizer->Add(label, 0, wxALIGN_CENTER_VERTICAL, 0); + sizer_tmp->Add(label, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, 0); } // add field const Option& opt_ref = opt; auto& field = build_field(opt_ref, label); - sizer->Add(field->m_Undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); - sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL, 0); + add_undo_buttuns_to_sizer(sizer_tmp, field); is_sizer_field(field) ? - sizer->Add(field->getSizer(), 0, wxALIGN_CENTER_VERTICAL, 0) : - sizer->Add(field->getWindow(), 0, wxALIGN_CENTER_VERTICAL, 0); + sizer_tmp->Add(field->getSizer(), 0, wxALIGN_CENTER_VERTICAL, 0) : + sizer_tmp->Add(field->getWindow(), 0, wxALIGN_CENTER_VERTICAL, 0); // add sidetext if any if (option.sidetext != "") { - auto sidetext = new wxStaticText(parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition, wxDefaultSize); + auto sidetext = new wxStaticText( parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition, + wxSize(sidetext_width, -1)/*wxDefaultSize*/, wxALIGN_LEFT); sidetext->SetFont(sidetext_font); - sizer->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); + sizer_tmp->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, m_flag == ogSIDE_OPTIONS_VERTICAL ? 0 : 4); + field->set_side_text_ptr(sidetext); } // add side widget if any if (opt.side_widget != nullptr) { - sizer->Add(opt.side_widget(parent())/*!.target()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1); //! requires verification + sizer_tmp->Add(opt.side_widget(parent())/*!.target()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1); //! requires verification } - if (opt.opt_id != option_set.back().opt_id) //! istead of (opt != option_set.back()) + if (opt.opt_id != option_set.back().opt_id && m_flag != ogSIDE_OPTIONS_VERTICAL) //! istead of (opt != option_set.back()) { - sizer->AddSpacer(6); + sizer_tmp->AddSpacer(6); } + + if (m_flag == ogSIDE_OPTIONS_VERTICAL) + sizer->Add(sizer_tmp, 0, wxALIGN_RIGHT|wxALL, 0); } // add extra sizers if any for (auto extra_widget : line.get_extra_widgets()) { @@ -259,7 +289,7 @@ void OptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost:: Option ConfigOptionsGroup::get_option(const std::string& opt_key, int opt_index /*= -1*/) { if (!m_config->has(opt_key)) { - std::cerr << "No " << opt_key << " in ConfigOptionsGroup config."; + std::cerr << "No " << opt_key << " in ConfigOptionsGroup config.\n"; } std::string opt_id = opt_index == -1 ? opt_key : opt_key + "#" + std::to_string(opt_index); diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index 422e1afd9..4f263f5e3 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -26,9 +26,13 @@ namespace Slic3r { namespace GUI { +enum ogDrawFlag{ + ogDEFAULT, + ogSIDE_OPTIONS_VERTICAL +}; + /// Widget type describes a function object that returns a wxWindow (our widget) and accepts a wxWidget (parent window). using widget_t = std::function;//!std::function; -using column_t = std::function; //auto default_label_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); //GetSystemColour //auto modified_label_clr = *new wxColour(254, 189, 101); @@ -71,10 +75,13 @@ private: std::vector m_extra_widgets;//! {std::vector()}; }; +using column_t = std::function;//std::function; + using t_optionfield_map = std::map; using t_opt_map = std::map< std::string, std::pair >; class OptionsGroup { + wxStaticBox* stb; public: const bool staticbox {true}; const wxString title {wxString("")}; @@ -88,8 +95,7 @@ public: wxFont sidetext_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; wxFont label_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; - -// std::function nonsys_btn_icon{ nullptr }; + int sidetext_width{ -1 }; /// Returns a copy of the pointer of the parent wxWindow. /// Accessor function is because users are not allowed to change the parent @@ -101,6 +107,11 @@ public: return m_parent; #endif /* __WXGTK__ */ } +#ifdef __WXGTK__ + wxWindow* get_parent() const { + return m_parent; + } +#endif /* __WXGTK__ */ void append_line(const Line& line, wxStaticText** colored_Label = nullptr); Line create_single_option_line(const Option& option) const; @@ -124,23 +135,39 @@ public: return out; } + bool set_side_text(const t_config_option_key& opt_key, const wxString& side_text) { + if (m_fields.find(opt_key) == m_fields.end()) return false; + auto st = m_fields.at(opt_key)->m_side_text; + if (!st) return false; + st->SetLabel(side_text); + return true; + } + + void set_name(const wxString& new_name) { + stb->SetLabel(new_name); + } + inline void enable() { for (auto& field : m_fields) field.second->enable(); } inline void disable() { for (auto& field : m_fields) field.second->disable(); } + void set_flag(ogDrawFlag flag) { m_flag = flag; } + void set_grid_vgap(int gap) { m_grid_sizer->SetVGap(gap); } void set_show_modified_btns_val(bool show) { m_show_modified_btns = show; } - OptionsGroup(wxWindow* _parent, const wxString& title, bool is_tab_opt=false) : - m_parent(_parent), title(title), m_show_modified_btns(is_tab_opt), staticbox(title!="") { - auto stb = new wxStaticBox(_parent, wxID_ANY, title); + OptionsGroup( wxWindow* _parent, const wxString& title, bool is_tab_opt = false, + ogDrawFlag flag = ogDEFAULT, column_t extra_clmn = nullptr) : + m_parent(_parent), title(title), m_show_modified_btns(is_tab_opt), + staticbox(title!=""), m_flag(flag), extra_column(extra_clmn){ + stb = new wxStaticBox(_parent, wxID_ANY, title); stb->SetFont(bold_font()); - sizer = (staticbox ? new wxStaticBoxSizer(stb/*new wxStaticBox(_parent, wxID_ANY, title)*/, wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); + sizer = (staticbox ? new wxStaticBoxSizer(stb, wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); auto num_columns = 1U; if (label_width != 0) num_columns++; if (extra_column != nullptr) num_columns++; - m_grid_sizer = new wxFlexGridSizer(0, num_columns, 0,0); - static_cast(m_grid_sizer)->SetFlexibleDirection(wxHORIZONTAL); + m_grid_sizer = new wxFlexGridSizer(0, num_columns, 1,0); + static_cast(m_grid_sizer)->SetFlexibleDirection(wxBOTH/*wxHORIZONTAL*/); static_cast(m_grid_sizer)->AddGrowableCol(label_width != 0); #ifdef __WXGTK__ m_panel = new wxPanel( _parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); @@ -164,6 +191,8 @@ protected: // "true" if option is created in preset tabs bool m_show_modified_btns{ false }; + ogDrawFlag m_flag{ ogDEFAULT }; + // This panel is needed for correct showing of the ToolTips for Button, StaticText and CheckBox // Tooltips on GTK doesn't work inside wxStaticBoxSizer unless you insert a panel // inside it before you insert the other controls. @@ -177,6 +206,7 @@ protected: const t_field& build_field(const t_config_option_key& id, const ConfigOptionDef& opt, wxStaticText* label = nullptr); const t_field& build_field(const t_config_option_key& id, wxStaticText* label = nullptr); const t_field& build_field(const Option& opt, wxStaticText* label = nullptr); + void add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field); virtual void on_kill_focus (){}; virtual void on_change_OG(const t_config_option_key& opt_id, const boost::any& value); @@ -186,8 +216,9 @@ protected: class ConfigOptionsGroup: public OptionsGroup { public: - ConfigOptionsGroup(wxWindow* parent, const wxString& title, DynamicPrintConfig* _config = nullptr, bool is_tab_opt = false) : - OptionsGroup(parent, title, is_tab_opt), m_config(_config) {} + ConfigOptionsGroup( wxWindow* parent, const wxString& title, DynamicPrintConfig* _config = nullptr, + bool is_tab_opt = false, ogDrawFlag flag = ogDEFAULT, column_t extra_clmn = nullptr) : + OptionsGroup(parent, title, is_tab_opt, flag, extra_clmn), m_config(_config) {} /// reference to libslic3r config, non-owning pointer (?). DynamicPrintConfig* m_config {nullptr}; diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 07785e818..111b2b933 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -687,8 +687,8 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) } if (opt_key == "fill_density") { - boost::any val = get_optgroup()->get_config_value(*m_config, opt_key); - get_optgroup()->set_value(opt_key, val); + boost::any val = get_optgroup(ogFrequentlyChangingParameters)->get_config_value(*m_config, opt_key); + get_optgroup(ogFrequentlyChangingParameters)->set_value(opt_key, val); } if (opt_key == "support_material" || opt_key == "support_material_buildplate_only") { @@ -697,12 +697,12 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) m_config->opt_bool("support_material_buildplate_only") ? _("Support on build plate only") : _("Everywhere"); - get_optgroup()->set_value("support", new_selection); + get_optgroup(ogFrequentlyChangingParameters)->set_value("support", new_selection); } if (opt_key == "brim_width") { bool val = m_config->opt_float("brim_width") > 0.0 ? true : false; - get_optgroup()->set_value("brim", val); + get_optgroup(ogFrequentlyChangingParameters)->set_value("brim", val); } if (opt_key == "wipe_tower" || opt_key == "single_extruder_multi_material" || opt_key == "extruders_count" ) @@ -719,10 +719,8 @@ void Tab::update_wiping_button_visibility() { bool multiple_extruders = dynamic_cast((m_preset_bundle->printers.get_edited_preset().config).option("nozzle_diameter"))->values.size() > 1; bool single_extruder_mm = dynamic_cast( (m_preset_bundle->printers.get_edited_preset().config).option("single_extruder_multi_material"))->value; - if (wipe_tower_enabled && multiple_extruders && single_extruder_mm) - get_wiping_dialog_button()->Show(); - else get_wiping_dialog_button()->Hide(); - + get_wiping_dialog_button()->Show(wipe_tower_enabled && multiple_extruders && single_extruder_mm); + (get_wiping_dialog_button()->GetParent())->Layout(); } @@ -789,18 +787,18 @@ void Tab::update_preset_description_line() void Tab::update_frequently_changed_parameters() { - boost::any value = get_optgroup()->get_config_value(*m_config, "fill_density"); - get_optgroup()->set_value("fill_density", value); + boost::any value = get_optgroup(ogFrequentlyChangingParameters)->get_config_value(*m_config, "fill_density"); + get_optgroup(ogFrequentlyChangingParameters)->set_value("fill_density", value); wxString new_selection = !m_config->opt_bool("support_material") ? _("None") : m_config->opt_bool("support_material_buildplate_only") ? _("Support on build plate only") : _("Everywhere"); - get_optgroup()->set_value("support", new_selection); + get_optgroup(ogFrequentlyChangingParameters)->set_value("support", new_selection); bool val = m_config->opt_float("brim_width") > 0.0 ? true : false; - get_optgroup()->set_value("brim", val); + get_optgroup(ogFrequentlyChangingParameters)->set_value("brim", val); update_wiping_button_visibility(); } @@ -1478,7 +1476,7 @@ void TabPrinter::build_fff() Line line{ _(L("Bed shape")), "" }; line.widget = [this](wxWindow* parent){ auto btn = new wxButton(parent, wxID_ANY, _(L(" Set "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); - // btn->SetFont(Slic3r::GUI::small_font); + btn->SetFont(Slic3r::GUI::small_font()); btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG)); auto sizer = new wxBoxSizer(wxHORIZONTAL); @@ -1546,7 +1544,7 @@ void TabPrinter::build_fff() auto serial_test = [this](wxWindow* parent){ auto btn = m_serial_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")), wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); -// btn->SetFont($Slic3r::GUI::small_font); + btn->SetFont(Slic3r::GUI::small_font()); btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("wrench.png")), wxBITMAP_TYPE_PNG)); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); @@ -1832,6 +1830,7 @@ void TabPrinter::extruders_count_changed(size_t extruders_count){ build_extruder_pages(); reload_config(); on_value_change("extruders_count", extruders_count); + update_objects_list_extruder_column(extruders_count); } void TabPrinter::append_option_line(ConfigOptionsGroupShp optgroup, const std::string opt_key) @@ -2174,7 +2173,7 @@ void Tab::load_current_preset() // checking out if this Tab exists till this moment if (!checked_tab(this)) return; - update_tab_ui(); + update_tab_ui(); // update show/hide tabs if (m_name == "printer"){ diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index 353277d64..a1214465e 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -267,11 +267,11 @@ public: void on_value_change(const std::string& opt_key, const boost::any& value); + void update_wiping_button_visibility(); protected: void on_presets_changed(); void update_preset_description_line(); void update_frequently_changed_parameters(); - void update_wiping_button_visibility(); void update_tab_presets(wxComboCtrl* ui, bool show_incompatible); void fill_icon_descriptions(); void set_tooltips_text(); diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 5949efb37..7fcad9e65 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -1,5 +1,13 @@ #include "wxExtensions.hpp" +#include "GUI.hpp" +#include "../../libslic3r/Utils.hpp" + +#include +#include +#include +#include + const unsigned int wxCheckListBoxComboPopup::DefaultWidth = 200; const unsigned int wxCheckListBoxComboPopup::DefaultHeight = 200; const unsigned int wxCheckListBoxComboPopup::DefaultItemHeight = 18; @@ -182,3 +190,1216 @@ void wxDataViewTreeCtrlComboPopup::OnDataViewTreeCtrlSelection(wxCommandEvent& e auto selected = GetItemText(GetSelection()); cmb->SetText(selected); } + +// ---------------------------------------------------------------------------- +// *** PrusaCollapsiblePane *** +// ---------------------------------------------------------------------------- +void PrusaCollapsiblePane::OnStateChange(const wxSize& sz) +{ +#ifdef __WXOSX__ + wxCollapsiblePane::OnStateChange(sz); +#else + SetSize(sz); + + if (this->HasFlag(wxCP_NO_TLW_RESIZE)) + { + // the user asked to explicitly handle the resizing itself... + return; + } + + auto top = GetParent(); //right_panel + if (!top) + return; + + wxSizer *sizer = top->GetSizer(); + if (!sizer) + return; + + const wxSize newBestSize = sizer->ComputeFittingClientSize(top); + top->SetMinClientSize(newBestSize); + + wxWindowUpdateLocker noUpdates_p(top->GetParent()); + // we shouldn't attempt to resize a maximized window, whatever happens + // if (!top->IsMaximized()) + // top->SetClientSize(newBestSize); + top->GetParent()->Layout(); + top->Refresh(); +#endif //__WXOSX__ +} + +// ---------------------------------------------------------------------------- +// *** PrusaCollapsiblePaneMSW *** used only #ifdef __WXMSW__ +// ---------------------------------------------------------------------------- +#ifdef __WXMSW__ +bool PrusaCollapsiblePaneMSW::Create(wxWindow *parent, wxWindowID id, const wxString& label, + const wxPoint& pos, const wxSize& size, long style, const wxValidator& val, const wxString& name) +{ + if (!wxControl::Create(parent, id, pos, size, style, val, name)) + return false; + m_pStaticLine = NULL; + m_strLabel = label; + + // sizer containing the expand button and possibly a static line + m_sz = new wxBoxSizer(wxHORIZONTAL); + + m_bmp_close.LoadFile(Slic3r::GUI::from_u8(Slic3r::var("disclosure_triangle_close.png")), wxBITMAP_TYPE_PNG); + m_bmp_open.LoadFile(Slic3r::GUI::from_u8(Slic3r::var("disclosure_triangle_open.png")), wxBITMAP_TYPE_PNG); + + m_pDisclosureTriangleButton = new wxButton(this, wxID_ANY, m_strLabel, wxPoint(0, 0), + wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + UpdateBtnBmp(); + m_pDisclosureTriangleButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) + { + if (event.GetEventObject() != m_pDisclosureTriangleButton) + { + event.Skip(); + return; + } + + Collapse(!IsCollapsed()); + + // this change was generated by the user - send the event + wxCollapsiblePaneEvent ev(this, GetId(), IsCollapsed()); + GetEventHandler()->ProcessEvent(ev); + }); + + m_sz->Add(m_pDisclosureTriangleButton, 0, wxLEFT | wxTOP | wxBOTTOM, GetBorder()); + + // do not set sz as our sizers since we handle the pane window without using sizers + m_pPane = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, + wxTAB_TRAVERSAL | wxNO_BORDER, wxT("wxCollapsiblePanePane")); + + wxColour& clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + m_pDisclosureTriangleButton->SetBackgroundColour(clr); + this->SetBackgroundColour(clr); + m_pPane->SetBackgroundColour(clr); + + // start as collapsed: + m_pPane->Hide(); + + return true; +} + +void PrusaCollapsiblePaneMSW::UpdateBtnBmp() +{ + if (IsCollapsed()) + m_pDisclosureTriangleButton->SetBitmap(m_bmp_close); + else{ + m_pDisclosureTriangleButton->SetBitmap(m_bmp_open); + // To updating button bitmap it's needed to lost focus on this button, so + // we set focus to mainframe + //GetParent()->GetParent()->GetParent()->SetFocus(); + //or to pane + GetPane()->SetFocus(); + } + Layout(); +} + +void PrusaCollapsiblePaneMSW::SetLabel(const wxString &label) +{ + m_strLabel = label; + m_pDisclosureTriangleButton->SetLabel(m_strLabel); + Layout(); +} + +bool PrusaCollapsiblePaneMSW::Layout() +{ + if (!m_pDisclosureTriangleButton || !m_pPane || !m_sz) + return false; // we need to complete the creation first! + + wxSize oursz(GetSize()); + + // move & resize the button and the static line + m_sz->SetDimension(0, 0, oursz.GetWidth(), m_sz->GetMinSize().GetHeight()); + m_sz->Layout(); + + if (IsExpanded()) + { + // move & resize the container window + int yoffset = m_sz->GetSize().GetHeight() + GetBorder(); + m_pPane->SetSize(0, yoffset, + oursz.x, oursz.y - yoffset); + + // this is very important to make the pane window layout show correctly + m_pPane->Layout(); + } + + return true; +} + +void PrusaCollapsiblePaneMSW::Collapse(bool collapse) +{ + // optimization + if (IsCollapsed() == collapse) + return; + + InvalidateBestSize(); + + // update our state + m_pPane->Show(!collapse); + + // update button bitmap + UpdateBtnBmp(); + + OnStateChange(GetBestSize()); +} +#endif //__WXMSW__ + +// ***************************************************************************** +// ---------------------------------------------------------------------------- +// PrusaObjectDataViewModelNode +// ---------------------------------------------------------------------------- + +void PrusaObjectDataViewModelNode::set_object_action_icon() { + m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("add_object.png")), wxBITMAP_TYPE_PNG); +} +void PrusaObjectDataViewModelNode::set_part_action_icon() { + m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("cog.png")), wxBITMAP_TYPE_PNG); +} + +// ***************************************************************************** +// ---------------------------------------------------------------------------- +// PrusaObjectDataViewModel +// ---------------------------------------------------------------------------- + +wxDataViewItem PrusaObjectDataViewModel::Add(wxString &name) +{ + auto root = new PrusaObjectDataViewModelNode(name); + m_objects.push_back(root); + // notify control + wxDataViewItem child((void*)root); + wxDataViewItem parent((void*)NULL); + ItemAdded(parent, child); + return child; +} + +wxDataViewItem PrusaObjectDataViewModel::Add(wxString &name, int instances_count, int scale) +{ + auto root = new PrusaObjectDataViewModelNode(name, instances_count, scale); + m_objects.push_back(root); + // notify control + wxDataViewItem child((void*)root); + wxDataViewItem parent((void*)NULL); + ItemAdded(parent, child); + return child; +} + +wxDataViewItem PrusaObjectDataViewModel::AddChild( const wxDataViewItem &parent_item, + const wxString &name, + const wxIcon& icon, + const int extruder/* = 0*/, + const bool create_frst_child/* = true*/) +{ + PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent_item.GetID(); + if (!root) return wxDataViewItem(0); + + wxString extruder_str = extruder == 0 ? "default" : wxString::Format("%d", extruder); + + if (root->GetChildren().Count() == 0 && create_frst_child) + { + auto icon_solid_mesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("object.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); + auto node = new PrusaObjectDataViewModelNode(root, root->m_name, icon_solid_mesh, extruder_str, 0); + root->Append(node); + // notify control + wxDataViewItem child((void*)node); + ItemAdded(parent_item, child); + } + + auto volume_id = root->GetChildCount(); + auto node = new PrusaObjectDataViewModelNode(root, name, icon, extruder_str, volume_id); + root->Append(node); + // notify control + wxDataViewItem child((void*)node); + ItemAdded(parent_item, child); + return child; +} + +wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item) +{ + auto ret_item = wxDataViewItem(0); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return ret_item; + + auto node_parent = node->GetParent(); + wxDataViewItem parent(node_parent); + + // first remove the node from the parent's array of children; + // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ + // thus removing the node from it doesn't result in freeing it + if (node_parent){ + auto id = node_parent->GetChildren().Index(node); + auto v_id = node->GetVolumeId(); + node_parent->GetChildren().Remove(node); + if (id > 0){ + if(id == node_parent->GetChildCount()) id--; + ret_item = wxDataViewItem(node_parent->GetChildren().Item(id)); + } + + //update volume_id value for remaining child-nodes + auto children = node_parent->GetChildren(); + for (size_t i = 0; i < node_parent->GetChildCount(); i++) + { + auto volume_id = children[i]->GetVolumeId(); + if (volume_id > v_id) + children[i]->SetVolumeId(volume_id-1); + } + } + else + { + auto it = find(m_objects.begin(), m_objects.end(), node); + auto id = it - m_objects.begin(); + if (it != m_objects.end()) + m_objects.erase(it); + if (id > 0){ + if(id == m_objects.size()) id--; + ret_item = wxDataViewItem(m_objects[id]); + } + } + // free the node + delete node; + + // set m_containet to FALSE if parent has no child + if (node_parent && node_parent->GetChildCount() == 0){ +#ifndef __WXGTK__ + node_parent->m_container = false; +#endif //__WXGTK__ + ret_item = parent; + } + + // notify control + ItemDeleted(parent, item); + return ret_item; +} + +void PrusaObjectDataViewModel::DeleteAll() +{ + while (!m_objects.empty()) + { + auto object = m_objects.back(); +// object->RemoveAllChildren(); + Delete(wxDataViewItem(object)); + } +} + +void PrusaObjectDataViewModel::DeleteChildren(wxDataViewItem& parent) +{ + PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent.GetID(); + if (!root) // happens if item.IsOk()==false + return; + + // first remove the node from the parent's array of children; + // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ + // thus removing the node from it doesn't result in freeing it + auto& children = root->GetChildren(); + for (int id = root->GetChildCount() - 1; id >= 0; --id) + { + auto node = children[id]; + auto item = wxDataViewItem(node); + children.RemoveAt(id); + + // free the node + delete node; + + // notify control + ItemDeleted(parent, item); + } + + // set m_containet to FALSE if parent has no child +#ifndef __WXGTK__ + root->m_container = false; +#endif //__WXGTK__ +} + +wxDataViewItem PrusaObjectDataViewModel::GetItemById(int obj_idx) +{ + if (obj_idx >= m_objects.size()) + { + printf("Error! Out of objects range.\n"); + return wxDataViewItem(0); + } + return wxDataViewItem(m_objects[obj_idx]); +} + + +int PrusaObjectDataViewModel::GetIdByItem(wxDataViewItem& item) +{ + wxASSERT(item.IsOk()); + + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + auto it = find(m_objects.begin(), m_objects.end(), node); + if (it == m_objects.end()) + return -1; + + return it - m_objects.begin(); +} + +int PrusaObjectDataViewModel::GetVolumeIdByItem(wxDataViewItem& item) +{ + wxASSERT(item.IsOk()); + + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return -1; + return node->GetVolumeId(); +} + +wxString PrusaObjectDataViewModel::GetName(const wxDataViewItem &item) const +{ + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return wxEmptyString; + + return node->m_name; +} + +wxString PrusaObjectDataViewModel::GetCopy(const wxDataViewItem &item) const +{ + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return wxEmptyString; + + return node->m_copy; +} + +wxString PrusaObjectDataViewModel::GetScale(const wxDataViewItem &item) const +{ + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return wxEmptyString; + + return node->m_scale; +} + +wxIcon& PrusaObjectDataViewModel::GetIcon(const wxDataViewItem &item) const +{ + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + return node->m_icon; +} + +void PrusaObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned int col) const +{ + wxASSERT(item.IsOk()); + + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + switch (col) + { + case 0:{ + const wxDataViewIconText data(node->m_name, node->m_icon); + variant << data; + break;} + case 1: + variant = node->m_copy; + break; + case 2: + variant = node->m_scale; + break; + case 3: + variant = node->m_extruder; + break; + case 4: + variant << node->m_action_icon; + break; + default: + ; + } +} + +bool PrusaObjectDataViewModel::SetValue(const wxVariant &variant, const wxDataViewItem &item, unsigned int col) +{ + wxASSERT(item.IsOk()); + + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + return node->SetValue(variant, col); +} + +bool PrusaObjectDataViewModel::SetValue(const wxVariant &variant, const int item_idx, unsigned int col) +{ + if (item_idx < 0 || item_idx >= m_objects.size()) + return false; + + return m_objects[item_idx]->SetValue(variant, col); +} + +wxDataViewItem PrusaObjectDataViewModel::MoveChildUp(const wxDataViewItem &item) +{ + auto ret_item = wxDataViewItem(0); + wxASSERT(item.IsOk()); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return ret_item; + + auto node_parent = node->GetParent(); + if (!node_parent) // If isn't part, but object + return ret_item; + + auto volume_id = node->GetVolumeId(); + if (0 < volume_id && volume_id < node_parent->GetChildCount()){ + node_parent->SwapChildrens(volume_id - 1, volume_id); + ret_item = wxDataViewItem(node_parent->GetNthChild(volume_id - 1)); + ItemChanged(item); + ItemChanged(ret_item); + } + else + ret_item = wxDataViewItem(node_parent->GetNthChild(0)); + return ret_item; +} + +wxDataViewItem PrusaObjectDataViewModel::MoveChildDown(const wxDataViewItem &item) +{ + auto ret_item = wxDataViewItem(0); + wxASSERT(item.IsOk()); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return ret_item; + + auto node_parent = node->GetParent(); + if (!node_parent) // If isn't part, but object + return ret_item; + + auto volume_id = node->GetVolumeId(); + if (0 <= volume_id && volume_id+1 < node_parent->GetChildCount()){ + node_parent->SwapChildrens(volume_id + 1, volume_id); + ret_item = wxDataViewItem(node_parent->GetNthChild(volume_id + 1)); + ItemChanged(item); + ItemChanged(ret_item); + } + else + ret_item = wxDataViewItem(node_parent->GetNthChild(node_parent->GetChildCount()-1)); + return ret_item; +} + +wxDataViewItem PrusaObjectDataViewModel::ReorganizeChildren(int current_volume_id, int new_volume_id, const wxDataViewItem &parent) +{ + auto ret_item = wxDataViewItem(0); + if (current_volume_id == new_volume_id) + return ret_item; + wxASSERT(parent.IsOk()); + PrusaObjectDataViewModelNode *node_parent = (PrusaObjectDataViewModelNode*)parent.GetID(); + if (!node_parent) // happens if item.IsOk()==false + return ret_item; + + PrusaObjectDataViewModelNode *deleted_node = node_parent->GetNthChild(current_volume_id); + node_parent->GetChildren().Remove(deleted_node); + ItemDeleted(parent, wxDataViewItem(deleted_node)); + node_parent->Insert(deleted_node, new_volume_id); + ItemAdded(parent, wxDataViewItem(deleted_node)); + + //update volume_id value for child-nodes + auto children = node_parent->GetChildren(); + int id_frst = current_volume_id < new_volume_id ? current_volume_id : new_volume_id; + int id_last = current_volume_id > new_volume_id ? current_volume_id : new_volume_id; + for (int id = id_frst; id <= id_last; ++id) + children[id]->SetVolumeId(id); + + return wxDataViewItem(node_parent->GetNthChild(new_volume_id)); +} + +// bool MyObjectTreeModel::IsEnabled(const wxDataViewItem &item, unsigned int col) const +// { +// +// } + +wxDataViewItem PrusaObjectDataViewModel::GetParent(const wxDataViewItem &item) const +{ + // the invisible root node has no parent + if (!item.IsOk()) + return wxDataViewItem(0); + + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + + // objects nodes has no parent too + if (find(m_objects.begin(), m_objects.end(),node) != m_objects.end()) + return wxDataViewItem(0); + + return wxDataViewItem((void*)node->GetParent()); +} + +bool PrusaObjectDataViewModel::IsContainer(const wxDataViewItem &item) const +{ + // the invisible root node can have children + if (!item.IsOk()) + return true; + + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + return node->IsContainer(); +} + +unsigned int PrusaObjectDataViewModel::GetChildren(const wxDataViewItem &parent, wxDataViewItemArray &array) const +{ + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)parent.GetID(); + if (!node) + { + for (auto object : m_objects) + array.Add(wxDataViewItem((void*)object)); + return m_objects.size(); + } + + if (node->GetChildCount() == 0) + { + return 0; + } + + unsigned int count = node->GetChildren().GetCount(); + for (unsigned int pos = 0; pos < count; pos++) + { + PrusaObjectDataViewModelNode *child = node->GetChildren().Item(pos); + array.Add(wxDataViewItem((void*)child)); + } + + return count; +} + +// ************************************** EXPERIMENTS *************************************** +PrusaDoubleSlider::PrusaDoubleSlider( wxWindow *parent, + wxWindowID id, + int lowerValue, + int higherValue, + int minValue, + int maxValue, + const wxPoint& pos, + const wxSize& size, + long style, + const wxValidator& val, + const wxString& name) : + wxControl(parent, id, pos, size, wxWANTS_CHARS | wxBORDER_NONE), + m_lower_value(lowerValue), m_higher_value (higherValue), + m_min_value(minValue), m_max_value(maxValue), + m_style(style == wxSL_HORIZONTAL || style == wxSL_VERTICAL ? style: wxSL_HORIZONTAL) +{ +#ifndef __WXOSX__ // SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX + SetDoubleBuffered(true); +#endif //__WXOSX__ + + m_bmp_thumb_higher = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("right_half_circle.png")) : + Slic3r::GUI::from_u8(Slic3r::var("up_half_circle.png")), wxBITMAP_TYPE_PNG); + m_bmp_thumb_lower = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("left_half_circle.png")) : + Slic3r::GUI::from_u8(Slic3r::var("down_half_circle.png")), wxBITMAP_TYPE_PNG); + m_thumb_size = m_bmp_thumb_lower.GetSize(); + + m_bmp_add_tick_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_add_on.png")), wxBITMAP_TYPE_PNG); + m_bmp_add_tick_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_add_off.png")), wxBITMAP_TYPE_PNG); + m_bmp_del_tick_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_delete_on.png")), wxBITMAP_TYPE_PNG); + m_bmp_del_tick_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_delete_off.png")), wxBITMAP_TYPE_PNG); + m_tick_icon_dim = m_bmp_add_tick_on.GetSize().x; + + m_bmp_one_layer_lock_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_lock_on.png")), wxBITMAP_TYPE_PNG); + m_bmp_one_layer_lock_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_lock_off.png")), wxBITMAP_TYPE_PNG); + m_bmp_one_layer_unlock_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_unlock_on.png")), wxBITMAP_TYPE_PNG); + m_bmp_one_layer_unlock_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_unlock_off.png")), wxBITMAP_TYPE_PNG); + m_lock_icon_dim = m_bmp_one_layer_lock_on.GetSize().x; + + m_selection = ssUndef; + + // slider events + Bind(wxEVT_PAINT, &PrusaDoubleSlider::OnPaint, this); + Bind(wxEVT_LEFT_DOWN, &PrusaDoubleSlider::OnLeftDown, this); + Bind(wxEVT_MOTION, &PrusaDoubleSlider::OnMotion, this); + Bind(wxEVT_LEFT_UP, &PrusaDoubleSlider::OnLeftUp, this); + Bind(wxEVT_MOUSEWHEEL, &PrusaDoubleSlider::OnWheel, this); + Bind(wxEVT_ENTER_WINDOW,&PrusaDoubleSlider::OnEnterWin, this); + Bind(wxEVT_LEAVE_WINDOW,&PrusaDoubleSlider::OnLeaveWin, this); + Bind(wxEVT_KEY_DOWN, &PrusaDoubleSlider::OnKeyDown, this); + Bind(wxEVT_KEY_UP, &PrusaDoubleSlider::OnKeyUp, this); + Bind(wxEVT_RIGHT_DOWN, &PrusaDoubleSlider::OnRightDown,this); + Bind(wxEVT_RIGHT_UP, &PrusaDoubleSlider::OnRightUp, this); + + // control's view variables + SLIDER_MARGIN = 4 + (style == wxSL_HORIZONTAL ? m_bmp_thumb_higher.GetWidth() : m_bmp_thumb_higher.GetHeight()); + + DARK_ORANGE_PEN = wxPen(wxColour(253, 84, 2)); + ORANGE_PEN = wxPen(wxColour(253, 126, 66)); + LIGHT_ORANGE_PEN = wxPen(wxColour(254, 177, 139)); + + DARK_GREY_PEN = wxPen(wxColour(128, 128, 128)); + GREY_PEN = wxPen(wxColour(164, 164, 164)); + LIGHT_GREY_PEN = wxPen(wxColour(204, 204, 204)); + + line_pens = { &DARK_GREY_PEN, &GREY_PEN, &LIGHT_GREY_PEN }; + segm_pens = { &DARK_ORANGE_PEN, &ORANGE_PEN, &LIGHT_ORANGE_PEN }; +} + +int PrusaDoubleSlider::GetActiveValue() const +{ + return m_selection == ssLower ? + m_lower_value : m_selection == ssHigher ? + m_higher_value : -1; +} + +wxSize PrusaDoubleSlider::DoGetBestSize() const +{ + const wxSize size = wxControl::DoGetBestSize(); + if (size.x > 1 && size.y > 1) + return size; + const int new_size = is_horizontal() ? 80 : 120; + return wxSize(new_size, new_size); +} + +void PrusaDoubleSlider::SetLowerValue(const int lower_val) +{ + m_lower_value = lower_val; + Refresh(); + Update(); +} + +void PrusaDoubleSlider::SetHigherValue(const int higher_val) +{ + m_higher_value = higher_val; + Refresh(); + Update(); +} + +void PrusaDoubleSlider::SetMaxValue(const int max_value) +{ + m_max_value = max_value; + Refresh(); + Update(); +} + +void PrusaDoubleSlider::draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos) +{ + int width; + int height; + get_size(&width, &height); + + wxCoord line_beg_x = is_horizontal() ? SLIDER_MARGIN : width*0.5 - 1; + wxCoord line_beg_y = is_horizontal() ? height*0.5 - 1 : SLIDER_MARGIN; + wxCoord line_end_x = is_horizontal() ? width - SLIDER_MARGIN + 1 : width*0.5 - 1; + wxCoord line_end_y = is_horizontal() ? height*0.5 - 1 : height - SLIDER_MARGIN + 1; + + wxCoord segm_beg_x = is_horizontal() ? lower_pos : width*0.5 - 1; + wxCoord segm_beg_y = is_horizontal() ? height*0.5 - 1 : lower_pos-1; + wxCoord segm_end_x = is_horizontal() ? higher_pos : width*0.5 - 1; + wxCoord segm_end_y = is_horizontal() ? height*0.5 - 1 : higher_pos-1; + + for (int id = 0; id < line_pens.size(); id++) + { + dc.SetPen(*line_pens[id]); + dc.DrawLine(line_beg_x, line_beg_y, line_end_x, line_end_y); + dc.SetPen(*segm_pens[id]); + dc.DrawLine(segm_beg_x, segm_beg_y, segm_end_x, segm_end_y); + if (is_horizontal()) + line_beg_y = line_end_y = segm_beg_y = segm_end_y += 1; + else + line_beg_x = line_end_x = segm_beg_x = segm_end_x += 1; + } +} + +double PrusaDoubleSlider::get_scroll_step() +{ + const wxSize sz = get_size(); + const int& slider_len = m_style == wxSL_HORIZONTAL ? sz.x : sz.y; + return double(slider_len - SLIDER_MARGIN * 2) / (m_max_value - m_min_value); +} + +// get position on the slider line from entered value +wxCoord PrusaDoubleSlider::get_position_from_value(const int value) +{ + const double step = get_scroll_step(); + const int val = is_horizontal() ? value : m_max_value - value; + return wxCoord(SLIDER_MARGIN + int(val*step + 0.5)); +} + +wxSize PrusaDoubleSlider::get_size() +{ + int w, h; + get_size(&w, &h); + return wxSize(w, h); +} + +void PrusaDoubleSlider::get_size(int *w, int *h) +{ + GetSize(w, h); + is_horizontal() ? *w -= m_lock_icon_dim : *h -= m_lock_icon_dim; +} + +void PrusaDoubleSlider::get_lower_and_higher_position(int& lower_pos, int& higher_pos) +{ + const double step = get_scroll_step(); + if (is_horizontal()) { + lower_pos = SLIDER_MARGIN + int(m_lower_value*step + 0.5); + higher_pos = SLIDER_MARGIN + int(m_higher_value*step + 0.5); + } + else { + lower_pos = SLIDER_MARGIN + int((m_max_value - m_lower_value)*step + 0.5); + higher_pos = SLIDER_MARGIN + int((m_max_value - m_higher_value)*step + 0.5); + } +} + +void PrusaDoubleSlider::draw_focus_rect() +{ + if (!m_is_focused) + return; + const wxSize sz = GetSize(); + wxPaintDC dc(this); + const wxPen pen = wxPen(wxColour(128, 128, 10), 1, wxPENSTYLE_DOT); + dc.SetPen(pen); + dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT)); + dc.DrawRectangle(1, 1, sz.x - 2, sz.y - 2); +} + +void PrusaDoubleSlider::render() +{ + SetBackgroundColour(GetParent()->GetBackgroundColour()); + draw_focus_rect(); + + wxPaintDC dc(this); + wxFont font = dc.GetFont(); + const wxFont smaller_font = font.Smaller(); + dc.SetFont(smaller_font); + + const wxCoord lower_pos = get_position_from_value(m_lower_value); + const wxCoord higher_pos = get_position_from_value(m_higher_value); + + // draw line + draw_scroll_line(dc, lower_pos, higher_pos); + +// //lower slider: +// draw_thumb(dc, lower_pos, ssLower); +// //higher slider: +// draw_thumb(dc, higher_pos, ssHigher); + + // draw both sliders + draw_thumbs(dc, lower_pos, higher_pos); + + //draw color print ticks + draw_ticks(dc); + + //draw color print ticks + draw_one_layer_icon(dc); +} + +void PrusaDoubleSlider::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end) +{ + const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; + wxBitmap* icon = m_is_action_icon_focesed ? &m_bmp_add_tick_off : &m_bmp_add_tick_on; + if (m_ticks.find(tick) != m_ticks.end()) + icon = m_is_action_icon_focesed ? &m_bmp_del_tick_off : &m_bmp_del_tick_on; + + wxCoord x_draw, y_draw; + is_horizontal() ? x_draw = pt_beg.x - 0.5*m_tick_icon_dim : y_draw = pt_beg.y - 0.5*m_tick_icon_dim; + if (m_selection == ssLower) + is_horizontal() ? y_draw = pt_end.y + 3 : x_draw = pt_beg.x - m_tick_icon_dim-2; + else + is_horizontal() ? y_draw = pt_beg.y - m_tick_icon_dim-2 : x_draw = pt_end.x + 3; + + dc.DrawBitmap(*icon, x_draw, y_draw); + + //update rect of the tick action icon + m_rect_tick_action = wxRect(x_draw, y_draw, m_tick_icon_dim, m_tick_icon_dim); +} + +void PrusaDoubleSlider::draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, const SelectedSlider selection) +{ + if (m_selection == selection) { + //draw info line + dc.SetPen(DARK_ORANGE_PEN); + const wxPoint pt_beg = is_horizontal() ? wxPoint(pos.x, pos.y - m_thumb_size.y) : wxPoint(pos.x - m_thumb_size.x, pos.y - 1); + const wxPoint pt_end = is_horizontal() ? wxPoint(pos.x, pos.y + m_thumb_size.y) : wxPoint(pos.x + m_thumb_size.x, pos.y - 1); + dc.DrawLine(pt_beg, pt_end); + + //draw action icon + draw_action_icon(dc, pt_beg, pt_end); + } +} + +wxString PrusaDoubleSlider::get_label(const SelectedSlider& selection) const +{ + const int value = selection == ssLower ? m_lower_value : m_higher_value; + + if (m_label_koef == 1.0 && m_values.empty()) + return wxString::Format("%d", value); + + const wxString str = m_values.empty() ? + wxNumberFormatter::ToString(m_label_koef*value, 2, wxNumberFormatter::Style_None) : + wxNumberFormatter::ToString(m_values[value], 2, wxNumberFormatter::Style_None); + return wxString::Format("%s\n(%d)", str, value); +} + +void PrusaDoubleSlider::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const +{ + if (m_is_one_layer && selection != m_selection || !selection) + return; + wxCoord text_width, text_height; + const wxString label = get_label(selection); + dc.GetMultiLineTextExtent(label, &text_width, &text_height); + wxPoint text_pos; + if (selection ==ssLower) + text_pos = is_horizontal() ? wxPoint(pos.x + 1, pos.y + m_thumb_size.x) : + wxPoint(pos.x + m_thumb_size.x+1, pos.y - 0.5*text_height - 1); + else + text_pos = is_horizontal() ? wxPoint(pos.x - text_width - 1, pos.y - m_thumb_size.x - text_height) : + wxPoint(pos.x - text_width - 1 - m_thumb_size.x, pos.y - 0.5*text_height + 1); + dc.DrawText(label, text_pos); +} + +void PrusaDoubleSlider::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) +{ + wxCoord x_draw, y_draw; + if (selection == ssLower) { + if (is_horizontal()) { + x_draw = pos.x - m_thumb_size.x; + y_draw = pos.y - int(0.5*m_thumb_size.y); + } + else { + x_draw = pos.x - int(0.5*m_thumb_size.x); + y_draw = pos.y; + } + } + else{ + if (is_horizontal()) { + x_draw = pos.x; + y_draw = pos.y - int(0.5*m_thumb_size.y); + } + else { + x_draw = pos.x - int(0.5*m_thumb_size.x); + y_draw = pos.y - m_thumb_size.y; + } + } + dc.DrawBitmap(selection == ssLower ? m_bmp_thumb_lower : m_bmp_thumb_higher, x_draw, y_draw); + + // Update thumb rect + update_thumb_rect(x_draw, y_draw, selection); +} + +void PrusaDoubleSlider::draw_thumb(wxDC& dc, const wxCoord& pos_coord, const SelectedSlider& selection) +{ + //calculate thumb position on slider line + int width, height; + get_size(&width, &height); + const wxPoint pos = is_horizontal() ? wxPoint(pos_coord, height*0.5) : wxPoint(0.5*width, pos_coord); + + // Draw thumb + draw_thumb_item(dc, pos, selection); + + // Draw info_line + draw_info_line_with_icon(dc, pos, selection); + + // Draw thumb text + draw_thumb_text(dc, pos, selection); +} + +void PrusaDoubleSlider::draw_thumbs(wxDC& dc, const wxCoord& lower_pos, const wxCoord& higher_pos) +{ + //calculate thumb position on slider line + int width, height; + get_size(&width, &height); + const wxPoint pos_l = is_horizontal() ? wxPoint(lower_pos, height*0.5) : wxPoint(0.5*width, lower_pos); + const wxPoint pos_h = is_horizontal() ? wxPoint(higher_pos, height*0.5) : wxPoint(0.5*width, higher_pos); + + // Draw lower thumb + draw_thumb_item(dc, pos_l, ssLower); + // Draw lower info_line + draw_info_line_with_icon(dc, pos_l, ssLower); + + // Draw higher thumb + draw_thumb_item(dc, pos_h, ssHigher); + // Draw higher info_line + draw_info_line_with_icon(dc, pos_h, ssHigher); + // Draw higher thumb text + draw_thumb_text(dc, pos_h, ssHigher); + + // Draw lower thumb text + draw_thumb_text(dc, pos_l, ssLower); +} + +void PrusaDoubleSlider::draw_ticks(wxDC& dc) +{ + dc.SetPen(DARK_GREY_PEN); + int height, width; + get_size(&width, &height); + const wxCoord mid = is_horizontal() ? 0.5*height : 0.5*width; + for (auto tick : m_ticks) + { + const wxCoord pos = get_position_from_value(tick); + + is_horizontal() ? dc.DrawLine(pos, mid-14, pos, mid-9) : + dc.DrawLine(mid - 14, pos - 1, mid - 9, pos - 1); + is_horizontal() ? dc.DrawLine(pos, mid+14, pos, mid+9) : + dc.DrawLine(mid + 14, pos - 1, mid + 9, pos - 1); + } +} + +void PrusaDoubleSlider::draw_one_layer_icon(wxDC& dc) +{ + wxBitmap* icon = m_is_one_layer ? + m_is_one_layer_icon_focesed ? &m_bmp_one_layer_lock_off : &m_bmp_one_layer_lock_on : + m_is_one_layer_icon_focesed ? &m_bmp_one_layer_unlock_off : &m_bmp_one_layer_unlock_on; + + int width, height; + get_size(&width, &height); + + wxCoord x_draw, y_draw; + is_horizontal() ? x_draw = width-2 : x_draw = 0.5*width - 0.5*m_lock_icon_dim; + is_horizontal() ? y_draw = 0.5*height - 0.5*m_lock_icon_dim : y_draw = height-2; + + dc.DrawBitmap(*icon, x_draw, y_draw); + + //update rect of the lock/unlock icon + m_rect_one_layer_icon = wxRect(x_draw, y_draw, m_lock_icon_dim, m_lock_icon_dim); +} + +void PrusaDoubleSlider::update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection) +{ + const wxRect& rect = wxRect(begin_x, begin_y, m_thumb_size.x, m_thumb_size.y); + if (selection == ssLower) + m_rect_lower_thumb = rect; + else + m_rect_higher_thumb = rect; +} + +int PrusaDoubleSlider::get_value_from_position(const wxCoord x, const wxCoord y) +{ + const int height = get_size().y; + const double step = get_scroll_step(); + + if (is_horizontal()) + return int(double(x - SLIDER_MARGIN) / step + 0.5); + else + return int(m_min_value + double(height - SLIDER_MARGIN - y) / step + 0.5); +} + +void PrusaDoubleSlider::detect_selected_slider(const wxPoint& pt, const bool is_mouse_wheel /*= false*/) +{ + if (is_mouse_wheel) + { + if (is_horizontal()) { + m_selection = pt.x <= m_rect_lower_thumb.GetRight() ? ssLower : + pt.x >= m_rect_higher_thumb.GetLeft() ? ssHigher : ssUndef; + } + else { + m_selection = pt.y >= m_rect_lower_thumb.GetTop() ? ssLower : + pt.y <= m_rect_higher_thumb.GetBottom() ? ssHigher : ssUndef; + } + return; + } + + m_selection = is_point_in_rect(pt, m_rect_lower_thumb) ? ssLower : + is_point_in_rect(pt, m_rect_higher_thumb) ? ssHigher : ssUndef; +} + +bool PrusaDoubleSlider::is_point_in_rect(const wxPoint& pt, const wxRect& rect) +{ + if (rect.GetLeft() <= pt.x && pt.x <= rect.GetRight() && + rect.GetTop() <= pt.y && pt.y <= rect.GetBottom()) + return true; + return false; +} + +void PrusaDoubleSlider::OnLeftDown(wxMouseEvent& event) +{ + this->CaptureMouse(); + wxClientDC dc(this); + wxPoint pos = event.GetLogicalPosition(dc); + if (is_point_in_rect(pos, m_rect_tick_action)) { + action_tick(taOnIcon); + return; + } + + m_is_left_down = true; + if (is_point_in_rect(pos, m_rect_one_layer_icon)){ + m_is_one_layer = !m_is_one_layer; + m_selection == ssLower ? correct_lower_value() : correct_higher_value(); + if (!m_selection) m_selection = ssHigher; + } + else + detect_selected_slider(pos); + + Refresh(); + Update(); + event.Skip(); +} + +void PrusaDoubleSlider::correct_lower_value() +{ + if (m_lower_value < m_min_value) + m_lower_value = m_min_value; + else if (m_lower_value > m_max_value) + m_lower_value = m_max_value; + + if (m_lower_value >= m_higher_value && m_lower_value <= m_max_value || m_is_one_layer) + m_higher_value = m_lower_value; +} + +void PrusaDoubleSlider::correct_higher_value() +{ + if (m_higher_value > m_max_value) + m_higher_value = m_max_value; + else if (m_higher_value < m_min_value) + m_higher_value = m_min_value; + + if (m_higher_value <= m_lower_value && m_higher_value >= m_min_value || m_is_one_layer) + m_lower_value = m_higher_value; +} + +void PrusaDoubleSlider::OnMotion(wxMouseEvent& event) +{ + const wxClientDC dc(this); + const wxPoint pos = event.GetLogicalPosition(dc); + m_is_one_layer_icon_focesed = is_point_in_rect(pos, m_rect_one_layer_icon); + if (!m_is_left_down && !m_is_one_layer){ + m_is_action_icon_focesed = is_point_in_rect(pos, m_rect_tick_action); + } + else if (m_is_left_down || m_is_right_down){ + if (m_selection == ssLower) { + m_lower_value = get_value_from_position(pos.x, pos.y); + correct_lower_value(); + } + else if (m_selection == ssHigher) { + m_higher_value = get_value_from_position(pos.x, pos.y); + correct_higher_value(); + } + } + Refresh(); + Update(); + event.Skip(); +} + +void PrusaDoubleSlider::OnLeftUp(wxMouseEvent& event) +{ + this->ReleaseMouse(); + m_is_left_down = false; + Refresh(); + Update(); + event.Skip(); + + wxCommandEvent e(wxEVT_SCROLL_CHANGED); + e.SetEventObject(this); + ProcessWindowEvent(e); +} + +void PrusaDoubleSlider::enter_window(wxMouseEvent& event, const bool enter) +{ + m_is_focused = enter; + Refresh(); + Update(); + event.Skip(); +} + +// "condition" have to be true for: +// - value increase (if wxSL_VERTICAL) +// - value decrease (if wxSL_HORIZONTAL) +void PrusaDoubleSlider::move_current_thumb(const bool condition) +{ + m_is_one_layer = wxGetKeyState(WXK_CONTROL); + int delta = condition ? -1 : 1; + if (is_horizontal()) + delta *= -1; + + if (m_selection == ssLower) { + m_lower_value -= delta; + correct_lower_value(); + } + else if (m_selection == ssHigher) { + m_higher_value -= delta; + correct_higher_value(); + } + Refresh(); + Update(); + + wxCommandEvent e(wxEVT_SCROLL_CHANGED); + e.SetEventObject(this); + ProcessWindowEvent(e); +} + +void PrusaDoubleSlider::action_tick(const TicksAction action) +{ + if (m_selection == ssUndef) + return; + + const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; + + if (action == taOnIcon && !m_ticks.insert(tick).second) + m_ticks.erase(tick); + else { + const auto it = m_ticks.find(tick); + if (it == m_ticks.end() && action == taAdd) + m_ticks.insert(tick); + else if (it != m_ticks.end() && action == taDel) + m_ticks.erase(tick); + else + return; + } + + Refresh(); + Update(); +} + +void PrusaDoubleSlider::OnWheel(wxMouseEvent& event) +{ + wxClientDC dc(this); + wxPoint pos = event.GetLogicalPosition(dc); + detect_selected_slider(pos, true); + + if (m_selection == ssUndef) + return; + + move_current_thumb(event.GetWheelRotation() > 0); +} + +void PrusaDoubleSlider::OnKeyDown(wxKeyEvent &event) +{ + const int key = event.GetKeyCode(); + if (key == '+' || key == WXK_NUMPAD_ADD) + action_tick(taAdd); + else if (key == '-' || key == 390 || key == WXK_DELETE || key == WXK_BACK) + action_tick(taDel); + else if (is_horizontal()) + { + if (key == WXK_LEFT || key == WXK_RIGHT) + move_current_thumb(key == WXK_LEFT); + else if (key == WXK_UP || key == WXK_DOWN){ + m_selection = key == WXK_UP ? ssHigher : ssLower; + Refresh(); + } + } + else { + if (key == WXK_LEFT || key == WXK_RIGHT) { + m_selection = key == WXK_LEFT ? ssHigher : ssLower; + Refresh(); + } + else if (key == WXK_UP || key == WXK_DOWN) + move_current_thumb(key == WXK_UP); + } +} + +void PrusaDoubleSlider::OnKeyUp(wxKeyEvent &event) +{ + if (event.GetKeyCode() == WXK_CONTROL) + m_is_one_layer = false; + Refresh(); + Update(); + event.Skip(); +} + +void PrusaDoubleSlider::OnRightDown(wxMouseEvent& event) +{ + this->CaptureMouse(); + const wxClientDC dc(this); + detect_selected_slider(event.GetLogicalPosition(dc)); + if (!m_selection) + return; + + if (m_selection == ssLower) + m_higher_value = m_lower_value; + else + m_lower_value = m_higher_value; + + m_is_right_down = m_is_one_layer = true; + + Refresh(); + Update(); + event.Skip(); +} + +void PrusaDoubleSlider::OnRightUp(wxMouseEvent& event) +{ + this->ReleaseMouse(); + m_is_right_down = m_is_one_layer = false; + + Refresh(); + Update(); + event.Skip(); +} + +// ***************************************************************************** diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index 3667c7905..064ce1038 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -4,6 +4,14 @@ #include #include #include +#include +#include +#include +#include +#include + +#include +#include class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup { @@ -65,4 +73,568 @@ public: void SetItemsCnt(int cnt) { m_cnt_open_items = cnt; } }; + + +// *** PrusaCollapsiblePane *** +// ---------------------------------------------------------------------------- +class PrusaCollapsiblePane : public wxCollapsiblePane +{ +public: + PrusaCollapsiblePane() {} + PrusaCollapsiblePane(wxWindow *parent, + wxWindowID winid, + const wxString& label, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxCP_DEFAULT_STYLE, + const wxValidator& val = wxDefaultValidator, + const wxString& name = wxCollapsiblePaneNameStr) + { + Create(parent, winid, label, pos, size, style, val, name); + } + ~PrusaCollapsiblePane() {} + + void OnStateChange(const wxSize& sz); //override/hide of OnStateChange from wxCollapsiblePane + virtual bool Show(bool show = true) override { + wxCollapsiblePane::Show(show); + OnStateChange(GetBestSize()); + return true; + } +}; + + +// *** PrusaCollapsiblePaneMSW *** used only #ifdef __WXMSW__ +// ---------------------------------------------------------------------------- +#ifdef __WXMSW__ +class PrusaCollapsiblePaneMSW : public PrusaCollapsiblePane//wxCollapsiblePane +{ + wxButton* m_pDisclosureTriangleButton = nullptr; + wxBitmap m_bmp_close; + wxBitmap m_bmp_open; +public: + PrusaCollapsiblePaneMSW() {} + PrusaCollapsiblePaneMSW( wxWindow *parent, + wxWindowID winid, + const wxString& label, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxCP_DEFAULT_STYLE, + const wxValidator& val = wxDefaultValidator, + const wxString& name = wxCollapsiblePaneNameStr) + { + Create(parent, winid, label, pos, size, style, val, name); + } + + ~PrusaCollapsiblePaneMSW() {} + + bool Create(wxWindow *parent, + wxWindowID id, + const wxString& label, + const wxPoint& pos, + const wxSize& size, + long style, + const wxValidator& val, + const wxString& name); + + void UpdateBtnBmp(); + void SetLabel(const wxString &label) override; + bool Layout() override; + void Collapse(bool collapse) override; +}; +#endif //__WXMSW__ + +// ***************************************************************************** +// ---------------------------------------------------------------------------- +// PrusaObjectDataViewModelNode: a node inside PrusaObjectDataViewModel +// ---------------------------------------------------------------------------- + +class PrusaObjectDataViewModelNode; +WX_DEFINE_ARRAY_PTR(PrusaObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray); + +class PrusaObjectDataViewModelNode +{ + PrusaObjectDataViewModelNode* m_parent; + MyObjectTreeModelNodePtrArray m_children; + wxIcon m_empty_icon; +public: + PrusaObjectDataViewModelNode(const wxString &name, int instances_count=1, int scale=100) { + m_parent = NULL; + m_name = name; + m_copy = wxString::Format("%d", instances_count); + m_scale = wxString::Format("%d%%", scale); + m_type = "object"; + m_volume_id = -1; +#ifdef __WXGTK__ + // it's necessary on GTK because of control have to know if this item will be container + // in another case you couldn't to add subitem for this item + // it will be produce "segmentation fault" + m_container = true; +#endif //__WXGTK__ + set_object_action_icon(); + } + + PrusaObjectDataViewModelNode( PrusaObjectDataViewModelNode* parent, + const wxString& sub_obj_name, + const wxIcon& icon, + const wxString& extruder, + const int volume_id=-1) { + m_parent = parent; + m_name = sub_obj_name; + m_copy = wxEmptyString; + m_scale = wxEmptyString; + m_icon = icon; + m_type = "volume"; + m_volume_id = volume_id; + m_extruder = extruder; + set_part_action_icon(); + } + + ~PrusaObjectDataViewModelNode() + { + // free all our children nodes + size_t count = m_children.GetCount(); + for (size_t i = 0; i < count; i++) + { + PrusaObjectDataViewModelNode *child = m_children[i]; + delete child; + } + } + + wxString m_name; + wxIcon& m_icon = m_empty_icon; + wxString m_copy; + wxString m_scale; + std::string m_type; + int m_volume_id; + bool m_container = false; + wxString m_extruder = "default"; + wxBitmap m_action_icon; + + bool IsContainer() const + { + return m_container; + } + + PrusaObjectDataViewModelNode* GetParent() + { + return m_parent; + } + MyObjectTreeModelNodePtrArray& GetChildren() + { + return m_children; + } + PrusaObjectDataViewModelNode* GetNthChild(unsigned int n) + { + return m_children.Item(n); + } + void Insert(PrusaObjectDataViewModelNode* child, unsigned int n) + { + m_children.Insert(child, n); + } + void Append(PrusaObjectDataViewModelNode* child) + { + if (!m_container) + m_container = true; + m_children.Add(child); + } + void RemoveAllChildren() + { + if (GetChildCount() == 0) + return; + for (size_t id = GetChildCount() - 1; id >= 0; --id) + { + if (m_children.Item(id)->GetChildCount() > 0) + m_children[id]->RemoveAllChildren(); + auto node = m_children[id]; + m_children.RemoveAt(id); + delete node; + } + } + + size_t GetChildCount() const + { + return m_children.GetCount(); + } + + bool SetValue(const wxVariant &variant, unsigned int col) + { + switch (col) + { + case 0:{ + wxDataViewIconText data; + data << variant; + m_icon = data.GetIcon(); + m_name = data.GetText(); + return true;} + case 1: + m_copy = variant.GetString(); + return true; + case 2: + m_scale = variant.GetString(); + return true; + case 3: + m_extruder = variant.GetString(); + return true; + case 4: + m_action_icon << variant; + return true; + default: + printf("MyObjectTreeModel::SetValue: wrong column"); + } + return false; + } + void SetIcon(const wxIcon &icon) + { + m_icon = icon; + } + + void SetType(const std::string& type){ + m_type = type; + } + const std::string& GetType(){ + return m_type; + } + + void SetVolumeId(const int& volume_id){ + m_volume_id = volume_id; + } + const int& GetVolumeId(){ + return m_volume_id; + } + + // use this function only for childrens + void AssignAllVal(PrusaObjectDataViewModelNode& from_node) + { + // ! Don't overwrite other values because of equality of this values for all children -- + m_name = from_node.m_name; + m_icon = from_node.m_icon; + m_volume_id = from_node.m_volume_id; + m_extruder = from_node.m_extruder; + } + + bool SwapChildrens(int frst_id, int scnd_id) { + if (GetChildCount() < 2 || + frst_id < 0 || frst_id >= GetChildCount() || + scnd_id < 0 || scnd_id >= GetChildCount()) + return false; + + PrusaObjectDataViewModelNode new_scnd = *GetNthChild(frst_id); + PrusaObjectDataViewModelNode new_frst = *GetNthChild(scnd_id); + + new_scnd.m_volume_id = m_children.Item(scnd_id)->m_volume_id; + new_frst.m_volume_id = m_children.Item(frst_id)->m_volume_id; + + m_children.Item(frst_id)->AssignAllVal(new_frst); + m_children.Item(scnd_id)->AssignAllVal(new_scnd); + return true; + } + + // Set action icons for node + void set_object_action_icon(); + void set_part_action_icon(); +}; + +// ---------------------------------------------------------------------------- +// PrusaObjectDataViewModel +// ---------------------------------------------------------------------------- + +class PrusaObjectDataViewModel :public wxDataViewModel +{ + std::vector m_objects; +public: + PrusaObjectDataViewModel(){} + ~PrusaObjectDataViewModel() + { + for (auto object : m_objects) + delete object; + } + + wxDataViewItem Add(wxString &name); + wxDataViewItem Add(wxString &name, int instances_count, int scale); + wxDataViewItem AddChild(const wxDataViewItem &parent_item, + const wxString &name, + const wxIcon& icon, + const int = 0, + const bool create_frst_child = true); + wxDataViewItem Delete(const wxDataViewItem &item); + void DeleteAll(); + void DeleteChildren(wxDataViewItem& parent); + wxDataViewItem GetItemById(int obj_idx); + int GetIdByItem(wxDataViewItem& item); + int GetVolumeIdByItem(wxDataViewItem& item); + bool IsEmpty() { return m_objects.empty(); } + + // helper method for wxLog + + wxString GetName(const wxDataViewItem &item) const; + wxString GetCopy(const wxDataViewItem &item) const; + wxString GetScale(const wxDataViewItem &item) const; + wxIcon& GetIcon(const wxDataViewItem &item) const; + + // helper methods to change the model + + virtual unsigned int GetColumnCount() const override { return 3;} + virtual wxString GetColumnType(unsigned int col) const override{ return wxT("string"); } + + virtual void GetValue(wxVariant &variant, + const wxDataViewItem &item, unsigned int col) const override; + virtual bool SetValue(const wxVariant &variant, + const wxDataViewItem &item, unsigned int col) override; + bool SetValue(const wxVariant &variant, const int item_idx, unsigned int col); + + wxDataViewItem MoveChildUp(const wxDataViewItem &item); + wxDataViewItem MoveChildDown(const wxDataViewItem &item); + // For parent move child from cur_volume_id place to new_volume_id + // Remaining items will moved up/down accordingly + wxDataViewItem ReorganizeChildren(int cur_volume_id, + int new_volume_id, + const wxDataViewItem &parent); + +// virtual bool IsEnabled(const wxDataViewItem &item, +// unsigned int col) const override; + + virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override; + virtual bool IsContainer(const wxDataViewItem &item) const override; + virtual unsigned int GetChildren(const wxDataViewItem &parent, + wxDataViewItemArray &array) const override; + + // Is the container just a header or an item with all columns + // In our case it is an item with all columns + virtual bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } +}; + + + +// ---------------------------------------------------------------------------- +// MyCustomRenderer +// ---------------------------------------------------------------------------- + +class MyCustomRenderer : public wxDataViewCustomRenderer +{ +public: + // This renderer can be either activatable or editable, for demonstration + // purposes. In real programs, you should select whether the user should be + // able to activate or edit the cell and it doesn't make sense to switch + // between the two -- but this is just an example, so it doesn't stop us. + explicit MyCustomRenderer(wxDataViewCellMode mode) + : wxDataViewCustomRenderer("string", mode, wxALIGN_CENTER) + { } + + virtual bool Render(wxRect rect, wxDC *dc, int state) override/*wxOVERRIDE*/ + { + dc->SetBrush(*wxLIGHT_GREY_BRUSH); + dc->SetPen(*wxTRANSPARENT_PEN); + + rect.Deflate(2); + dc->DrawRoundedRectangle(rect, 5); + + RenderText(m_value, + 0, // no offset + wxRect(dc->GetTextExtent(m_value)).CentreIn(rect), + dc, + state); + return true; + } + + virtual bool ActivateCell(const wxRect& WXUNUSED(cell), + wxDataViewModel *WXUNUSED(model), + const wxDataViewItem &WXUNUSED(item), + unsigned int WXUNUSED(col), + const wxMouseEvent *mouseEvent) override/*wxOVERRIDE*/ + { + wxString position; + if (mouseEvent) + position = wxString::Format("via mouse at %d, %d", mouseEvent->m_x, mouseEvent->m_y); + else + position = "from keyboard"; +// wxLogMessage("MyCustomRenderer ActivateCell() %s", position); + return false; + } + + virtual wxSize GetSize() const override/*wxOVERRIDE*/ + { + return wxSize(60, 20); + } + + virtual bool SetValue(const wxVariant &value) override/*wxOVERRIDE*/ + { + m_value = value.GetString(); + return true; + } + + virtual bool GetValue(wxVariant &WXUNUSED(value)) const override/*wxOVERRIDE*/{ return true; } + + virtual bool HasEditorCtrl() const override/*wxOVERRIDE*/{ return true; } + + virtual wxWindow* + CreateEditorCtrl(wxWindow* parent, + wxRect labelRect, + const wxVariant& value) override/*wxOVERRIDE*/ + { + wxTextCtrl* text = new wxTextCtrl(parent, wxID_ANY, value, + labelRect.GetPosition(), + labelRect.GetSize(), + wxTE_PROCESS_ENTER); + text->SetInsertionPointEnd(); + + return text; + } + + virtual bool + GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override/*wxOVERRIDE*/ + { + wxTextCtrl* text = wxDynamicCast(ctrl, wxTextCtrl); + if (!text) + return false; + + value = text->GetValue(); + + return true; + } + +private: + wxString m_value; +}; +// ******************************* EXPERIMENTS ********************************************** +enum SelectedSlider { + ssUndef, + ssLower, + ssHigher +}; +enum TicksAction{ + taOnIcon, + taAdd, + taDel +}; +class PrusaDoubleSlider : public wxControl +{ +public: + PrusaDoubleSlider( + wxWindow *parent, + wxWindowID id, + int lowerValue, + int higherValue, + int minValue, + int maxValue, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxSL_HORIZONTAL, + const wxValidator& val = wxDefaultValidator, + const wxString& name = wxEmptyString); + + int GetLowerValue() const { + return m_lower_value; + } + int GetHigherValue() const { + return m_higher_value; + } + int GetActiveValue() const; + wxSize DoGetBestSize() const override; + void SetLowerValue(const int lower_val); + void SetHigherValue(const int higher_val); + void SetMaxValue(const int max_value); + void SetKoefForLabels(const double koef) { + m_label_koef = koef; + } + void SetSliderValues(const std::vector& values) { + m_values = values; + } + + void OnPaint(wxPaintEvent& ){ render();} + void OnLeftDown(wxMouseEvent& event); + void OnMotion(wxMouseEvent& event); + void OnLeftUp(wxMouseEvent& event); + void OnEnterWin(wxMouseEvent& event){ enter_window(event, true); } + void OnLeaveWin(wxMouseEvent& event){ enter_window(event, false); } + void OnWheel(wxMouseEvent& event); + void OnKeyDown(wxKeyEvent &event); + void OnKeyUp(wxKeyEvent &event); + void OnRightDown(wxMouseEvent& event); + void OnRightUp(wxMouseEvent& event); + +protected: + + void render(); + void draw_focus_rect(); + void draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end); + void draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos); + void draw_thumb(wxDC& dc, const wxCoord& pos_coord, const SelectedSlider& selection); + void draw_thumbs(wxDC& dc, const wxCoord& lower_pos, const wxCoord& higher_pos); + void draw_ticks(wxDC& dc); + void draw_one_layer_icon(wxDC& dc); + void draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection); + void draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, SelectedSlider selection); + void draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const; + + void update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection); + void detect_selected_slider(const wxPoint& pt, const bool is_mouse_wheel = false); + void correct_lower_value(); + void correct_higher_value(); + void move_current_thumb(const bool condition); + void action_tick(const TicksAction action); + void enter_window(wxMouseEvent& event, const bool enter); + + bool is_point_in_rect(const wxPoint& pt, const wxRect& rect); + bool is_horizontal() const { return m_style == wxSL_HORIZONTAL; } + + double get_scroll_step(); + wxString get_label(const SelectedSlider& selection) const; + void get_lower_and_higher_position(int& lower_pos, int& higher_pos); + int get_value_from_position(const wxCoord x, const wxCoord y); + wxCoord get_position_from_value(const int value); + wxSize get_size(); + void get_size(int *w, int *h); + +private: + int m_min_value; + int m_max_value; + int m_lower_value; + int m_higher_value; + wxBitmap m_bmp_thumb_higher; + wxBitmap m_bmp_thumb_lower; + wxBitmap m_bmp_add_tick_on; + wxBitmap m_bmp_add_tick_off; + wxBitmap m_bmp_del_tick_on; + wxBitmap m_bmp_del_tick_off; + wxBitmap m_bmp_one_layer_lock_on; + wxBitmap m_bmp_one_layer_lock_off; + wxBitmap m_bmp_one_layer_unlock_on; + wxBitmap m_bmp_one_layer_unlock_off; + SelectedSlider m_selection; + bool m_is_left_down = false; + bool m_is_right_down = false; + bool m_is_one_layer = false; + bool m_is_focused = false; + bool m_is_action_icon_focesed = false; + bool m_is_one_layer_icon_focesed = false; + + wxRect m_rect_lower_thumb; + wxRect m_rect_higher_thumb; + wxRect m_rect_tick_action; + wxRect m_rect_one_layer_icon; + wxSize m_thumb_size; + int m_tick_icon_dim; + int m_lock_icon_dim = 16; + long m_style; + float m_label_koef = 1.0; + +// control's view variables + wxCoord SLIDER_MARGIN; // margin around slider + + wxPen DARK_ORANGE_PEN; + wxPen ORANGE_PEN; + wxPen LIGHT_ORANGE_PEN; + + wxPen DARK_GREY_PEN; + wxPen GREY_PEN; + wxPen LIGHT_GREY_PEN; + + std::vector line_pens; + std::vector segm_pens; + std::set m_ticks; + std::vector m_values; +}; +// ****************************************************************************************** + + #endif // slic3r_GUI_wxExtensions_hpp_ diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index c6eead1ad..b9183af1d 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -87,6 +87,80 @@ void add_frequently_changed_parameters(SV *ui_parent, SV *ui_sizer, SV *ui_p_siz (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), (wxFlexGridSizer*)wxPli_sv_2_object(aTHX_ ui_p_sizer, "Wx::FlexGridSizer")); %}; +void add_expert_mode_part( SV *ui_parent, SV *ui_sizer, + Model *model, + int event_object_selection_changed, + int event_object_settings_changed, + int event_remove_object, + int event_update_scene) + %code%{ Slic3r::GUI::add_expert_mode_part( (wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), + (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), + *model, + event_object_selection_changed, + event_object_settings_changed, + event_remove_object, + event_update_scene); %}; + +void set_objects_from_perl( SV *ui_parent, + SV *frequently_changed_parameters_sizer, + SV *expert_mode_part_sizer, + SV *scrolled_window_sizer, + SV *btn_export_gcode, + SV *btn_export_stl, + SV *btn_reslice, + SV *btn_print, + SV *btn_send_gcode, + SV *manifold_warning_icon) + %code%{ Slic3r::GUI::set_objects_from_perl( + (wxWindow *)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), + (wxBoxSizer *)wxPli_sv_2_object(aTHX_ frequently_changed_parameters_sizer, "Wx::BoxSizer"), + (wxBoxSizer *)wxPli_sv_2_object(aTHX_ expert_mode_part_sizer, "Wx::BoxSizer"), + (wxBoxSizer *)wxPli_sv_2_object(aTHX_ scrolled_window_sizer, "Wx::BoxSizer"), + (wxButton *)wxPli_sv_2_object(aTHX_ btn_export_gcode, "Wx::Button"), + (wxButton *)wxPli_sv_2_object(aTHX_ btn_export_stl, "Wx::Button"), + (wxButton *)wxPli_sv_2_object(aTHX_ btn_reslice, "Wx::Button"), + (wxButton *)wxPli_sv_2_object(aTHX_ btn_print, "Wx::Button"), + (wxButton *)wxPli_sv_2_object(aTHX_ btn_send_gcode, "Wx::Button"), + (wxStaticBitmap *)wxPli_sv_2_object(aTHX_ manifold_warning_icon, "Wx::StaticBitmap")); %}; + +void set_show_print_info(bool show) + %code%{ Slic3r::GUI::set_show_print_info(show); %}; + +void set_show_manifold_warning_icon(bool show) + %code%{ Slic3r::GUI::set_show_manifold_warning_icon(show); %}; + +void update_mode() + %code%{ Slic3r::GUI::update_mode(); %}; + +void add_object_to_list(const char *name, SV *object_model) + %code%{ Slic3r::GUI::add_object_to_list( + name, + (ModelObject *)wxPli_sv_2_object(aTHX_ object_model, "Slic3r::Model::Object") ); %}; + +void delete_object_from_list() + %code%{ Slic3r::GUI::delete_object_from_list(); %}; + +void delete_all_objects_from_list() + %code%{ Slic3r::GUI::delete_all_objects_from_list(); %}; + +void set_object_count(int idx, int count) + %code%{ Slic3r::GUI::set_object_count(idx, count); %}; + +void set_object_scale(int idx, int scale) + %code%{ Slic3r::GUI::set_object_scale(idx, scale); %}; + +void unselect_objects() + %code%{ Slic3r::GUI::unselect_objects(); %}; + +void select_current_object(int idx) + %code%{ Slic3r::GUI::select_current_object(idx); %}; + +void remove_obj() + %code%{ Slic3r::GUI::remove(); %}; + +void update_rotation_value(double angle, const char *axis) + %code%{ Slic3r::GUI::update_rotation_value(angle, axis); %}; + std::string fold_utf8_to_ascii(const char *src) %code%{ RETVAL = Slic3r::fold_utf8_to_ascii(src); %};