diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 6baefa545..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")); } @@ -154,6 +169,10 @@ sub _init_tabpanel { my $value = $event->GetInt(); $self->{plater}->on_extruders_change($value); } + if ($opt_key eq 'printer_technology'){ + my $value = $event->GetInt();# 0 ~ "ptFFF"; 1 ~ "ptSLA" + $self->{plater}->show_preset_comboboxes($value); + } } # don't save while loading for the first time $self->config->save($Slic3r::GUI::autosave) if $Slic3r::GUI::autosave && $self->{loaded}; @@ -166,7 +185,7 @@ sub _init_tabpanel { my $tab = Slic3r::GUI::get_preset_tab($tab_name); if ($self->{plater}) { - # Update preset combo boxes (Print settings, Filament, Printer) from their respective tabs. + # Update preset combo boxes (Print settings, Filament, Material, Printer) from their respective tabs. my $presets = $tab->get_presets; if (defined $presets){ my $reload_dependent_tabs = $tab->get_dependent_tabs; @@ -174,7 +193,7 @@ sub _init_tabpanel { $self->{plater}->{"selected_item_$tab_name"} = $tab->get_selected_preset_item; if ($tab_name eq 'printer') { # Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. - for my $tab_name_other (qw(print filament)) { + for my $tab_name_other (qw(print filament sla_material)) { # If the printer tells us that the print or filament preset has been switched or invalidated, # refresh the print or filament tab page. Otherwise just refresh the combo box. my $update_action = ($reload_dependent_tabs && (first { $_ eq $tab_name_other } (@{$reload_dependent_tabs}))) @@ -188,9 +207,43 @@ 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 printer)) { + for my $tab_name (qw(print filament sla_material printer)) { $self->{options_tabs}{$tab_name} = Slic3r::GUI::get_preset_tab("$tab_name"); } @@ -202,8 +255,14 @@ sub _init_tabpanel { # load initial config my $full_config = wxTheApp->{preset_bundle}->full_config; $self->{plater}->on_config_change($full_config); + # Show a correct number of filament fields. - $self->{plater}->on_extruders_change(int(@{$full_config->nozzle_diameter})); + if (defined $full_config->nozzle_diameter){ # nozzle_diameter is undefined when SLA printer is selected + $self->{plater}->on_extruders_change(int(@{$full_config->nozzle_diameter})); + } + + # Show correct preset comboboxes according to the printer_technology + $self->{plater}->show_preset_comboboxes(($full_config->printer_technology eq "FFF") ? 0 : 1); } } diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index b9ae911db..4055b2b36 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 @@ -359,37 +368,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); @@ -399,8 +383,8 @@ sub new { $self->{btn_print}->Hide; $self->{btn_send_gcode}->Hide; +# export_gcode cog_go.png my %icons = qw( - export_gcode cog_go.png print arrow_up.png send_gcode arrow_up.png reslice reslice.png @@ -444,6 +428,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; }); @@ -460,7 +445,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) = @_; @@ -504,20 +489,21 @@ sub new { { my $presets; { - $presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(3, 2, 1, 2); + $presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(4, 2, 1, 2); $presets->AddGrowableCol(1, 1); $presets->SetFlexibleDirection(wxHORIZONTAL); my %group_labels = ( print => L('Print settings'), filament => L('Filament'), + sla_material=> L('SLA material'), printer => L('Printer'), ); - # UI Combo boxes for a print, multiple filaments, and a printer. + # UI Combo boxes for a print, multiple filaments, SLA material and a printer. # Initially a single filament combo box is created, but the number of combo boxes for the filament selection may increase, # once a printer preset with multiple extruders is activated. # $self->{preset_choosers}{$group}[$idx] $self->{preset_choosers} = {}; - for my $group (qw(print filament printer)) { + for my $group (qw(print filament sla_material printer)) { my $text = Wx::StaticText->new($self->{right_panel}, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); $text->SetFont($Slic3r::GUI::small_font); my $choice = Wx::BitmapComboBox->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); @@ -538,15 +524,32 @@ sub new { $presets->Layout; } - my $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); @@ -561,28 +564,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); @@ -592,22 +612,29 @@ 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]); + $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, 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); + $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); # 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->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); @@ -620,6 +647,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 @@ -630,6 +669,7 @@ sub new { } $self->update_ui_from_settings(); + $self->Layout; return $self; } @@ -710,16 +750,17 @@ sub update_ui_from_settings } } -# Update preset combo boxes (Print settings, Filament, Printer) from their respective tabs. +# Update preset combo boxes (Print settings, Filament, Material, Printer) from their respective tabs. # Called by # Slic3r::GUI::Tab::Print::_on_presets_changed # Slic3r::GUI::Tab::Filament::_on_presets_changed +# Slic3r::GUI::Tab::Material::_on_presets_changed # Slic3r::GUI::Tab::Printer::_on_presets_changed # when the presets are loaded or the user selects another preset. # For Print settings and Printer, synchronize the selection index with their tabs. # For Filament, synchronize the selection index for a single extruder printer only, otherwise keep the selection. sub update_presets { - # $group: one of qw(print filament printer) + # $group: one of qw(print filament sla_material printer) # $presets: PresetCollection my ($self, $group, $presets) = @_; my @choosers = @{$self->{preset_choosers}{$group}}; @@ -735,6 +776,8 @@ sub update_presets { } } elsif ($group eq 'print') { wxTheApp->{preset_bundle}->print->update_platter_ui($choosers[0]); + } elsif ($group eq 'sla_material') { + wxTheApp->{preset_bundle}->sla_material->update_platter_ui($choosers[0]); } elsif ($group eq 'printer') { # Update the print choosers to only contain the compatible presets, update the dirty flags. wxTheApp->{preset_bundle}->print->update_platter_ui($self->{preset_choosers}{print}->[0]); @@ -901,12 +944,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); } @@ -916,8 +957,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; @@ -952,7 +991,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); @@ -972,7 +1012,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); @@ -994,7 +1035,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; @@ -1022,7 +1064,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; @@ -1031,11 +1074,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; } @@ -1148,6 +1187,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); @@ -1238,8 +1278,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; @@ -1682,9 +1723,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 (!$sizer || !$show && ($sizer->IsShown(1) == $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}; @@ -1711,17 +1758,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 { @@ -1930,6 +1983,24 @@ sub update { $self->{preview3D}->reload_print if $self->{preview3D}; } +# When a printer technology is changed, the UI needs to be updated to show/hide needed preset combo boxes. +sub show_preset_comboboxes{ + my ($self, $showSLA) = @_; #if showSLA is oposite value to "ptFFF" + + my $choices = $self->{preset_choosers}{filament}; + my $print_filament_ctrls_cnt = 2 + 2 * ($#$choices+1); + + foreach (0..$print_filament_ctrls_cnt-1){ + $self->{presets_sizer}->Show($_, !$showSLA); + } + $self->{presets_sizer}->Show($print_filament_ctrls_cnt , $showSLA); + $self->{presets_sizer}->Show($print_filament_ctrls_cnt+1, $showSLA); + + $self->{frequently_changed_parameters_sizer}->Show(0,!$showSLA); + + $self->Layout; +} + # When a number of extruders changes, the UI needs to be updated to show a single filament selection combo box per extruder. # Also the wxTheApp->{preset_bundle}->filament_presets needs to be resized accordingly # and some reasonable default has to be selected for the additional extruders. @@ -2035,32 +2106,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 { @@ -2072,6 +2129,7 @@ sub collect_selections { return $selections; } +# doesn't used now sub list_item_activated { my ($self, $event, $obj_idx) = @_; @@ -2172,6 +2230,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 { @@ -2273,7 +2356,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 @@ -2283,7 +2367,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(""); } @@ -2292,7 +2377,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(""); } @@ -2305,26 +2391,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/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index 7f75f815d..bd98e060a 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -49,9 +49,9 @@ enum ConfigOptionType { coPercents = coPercent + coVectorType, // a fraction or an absolute value coFloatOrPercent = 5, - // single 2d point. Currently not used. + // single 2d point (Point2f). Currently not used. coPoint = 6, - // vector of 2d points. Currently used for the definition of the print bed and for the extruder offsets. + // vector of 2d points (Point2f). Currently used for the definition of the print bed and for the extruder offsets. coPoints = coPoint + coVectorType, // single boolean value coBool = 7, @@ -821,12 +821,7 @@ public: bool deserialize(const std::string &str, bool append = false) override { UNUSED(append); - const t_config_enum_values &enum_keys_map = ConfigOptionEnum::get_enum_values(); - auto it = enum_keys_map.find(str); - if (it == enum_keys_map.end()) - return false; - this->value = static_cast(it->second); - return true; + return from_string(str, this->value); } static bool has(T value) @@ -838,7 +833,7 @@ public: } // Map from an enum name to an enum integer value. - static t_config_enum_names& get_enum_names() + static const t_config_enum_names& get_enum_names() { static t_config_enum_names names; if (names.empty()) { @@ -855,7 +850,17 @@ public: return names; } // Map from an enum name to an enum integer value. - static t_config_enum_values& get_enum_values(); + static const t_config_enum_values& get_enum_values(); + + static bool from_string(const std::string &str, T &value) + { + const t_config_enum_values &enum_keys_map = ConfigOptionEnum::get_enum_values(); + auto it = enum_keys_map.find(str); + if (it == enum_keys_map.end()) + return false; + value = static_cast(it->second); + return true; + } }; // Generic enum configuration value. @@ -900,7 +905,7 @@ public: // What type? bool, int, string etc. ConfigOptionType type = coNone; // Default value of this option. The default value object is owned by ConfigDef, it is released in its destructor. - ConfigOption *default_value = nullptr; + const ConfigOption *default_value = nullptr; // Usually empty. // Special values - "i_enum_open", "f_enum_open" to provide combo box for int or float selection, @@ -958,7 +963,7 @@ public: std::vector enum_labels; // For enums (when type == coEnum). Maps enum_values to enums. // Initialized by ConfigOptionEnum::get_enum_values() - t_config_enum_values *enum_keys_map = nullptr; + const t_config_enum_values *enum_keys_map = nullptr; bool has_enum_value(const std::string &value) const { for (const std::string &v : enum_values) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 748e1fb72..0df1d6466 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -17,9 +17,51 @@ namespace Slic3r { #define L(s) Slic3r::I18N::translate(s) PrintConfigDef::PrintConfigDef() +{ + this->init_common_params(); + this->init_fff_params(); + this->init_sla_params(); +} + +void PrintConfigDef::init_common_params() { t_optiondef_map &Options = this->options; + ConfigOptionDef* def; + + def = this->add("printer_technology", coEnum); + def->label = L("Printer technology"); + def->tooltip = L("Printer technology"); + def->cli = "printer-technology=s"; + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("FFF"); + def->enum_values.push_back("SLA"); + def->default_value = new ConfigOptionEnum(ptFFF); + + def = this->add("bed_shape", coPoints); + def->label = L("Bed shape"); + def->default_value = new ConfigOptionPoints{ Vec2d(0, 0), Vec2d(200, 0), Vec2d(200, 200), Vec2d(0, 200) }; + def = this->add("layer_height", coFloat); + def->label = L("Layer height"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("This setting controls the height (and thus the total number) of the slices/layers. " + "Thinner layers give better accuracy but take more time to print."); + def->sidetext = L("mm"); + def->cli = "layer-height=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(0.3); + + def = this->add("max_print_height", coFloat); + def->label = L("Max print height"); + def->tooltip = L("Set this to the maximum height that can be reached by your extruder while printing."); + def->sidetext = L("mm"); + def->cli = "max-print-height=f"; + def->default_value = new ConfigOptionFloat(200.0); +} + +void PrintConfigDef::init_fff_params() +{ + t_optiondef_map &Options = this->options; ConfigOptionDef* def; // Maximum extruder temperature, bumped to 1500 to support printing of glass. @@ -33,10 +75,6 @@ PrintConfigDef::PrintConfigDef() def->cli = "avoid-crossing-perimeters!"; def->default_value = new ConfigOptionBool(false); - def = this->add("bed_shape", coPoints); - def->label = L("Bed shape"); - def->default_value = new ConfigOptionPoints { Vec2d(0,0), Vec2d(200,0), Vec2d(200,200), Vec2d(0,200) }; - def = this->add("bed_temperature", coInts); def->label = L("Other layers"); def->tooltip = L("Bed temperature for layers after the first one. " @@ -922,16 +960,6 @@ PrintConfigDef::PrintConfigDef() def->height = 50; def->default_value = new ConfigOptionString(""); - def = this->add("layer_height", coFloat); - def->label = L("Layer height"); - def->category = L("Layers and Perimeters"); - def->tooltip = L("This setting controls the height (and thus the total number) of the slices/layers. " - "Thinner layers give better accuracy but take more time to print."); - def->sidetext = L("mm"); - def->cli = "layer-height=f"; - def->min = 0; - def->default_value = new ConfigOptionFloat(0.3); - def = this->add("remaining_times", coBool); def->label = L("Supports remaining times"); def->tooltip = L("Emit M73 P[percent printed] R[remaining time in minutes] at 1 minute" @@ -1052,13 +1080,6 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloats { 0. }; - def = this->add("max_print_height", coFloat); - def->label = L("Max print height"); - def->tooltip = L("Set this to the maximum height that can be reached by your extruder while printing."); - def->sidetext = L("mm"); - def->cli = "max-print-height=f"; - def->default_value = new ConfigOptionFloat(200.0); - def = this->add("max_print_speed", coFloat); def->label = L("Max print speed"); def->tooltip = L("When setting other speed settings to 0 Slic3r will autocalculate the optimal speed " @@ -2103,6 +2124,103 @@ PrintConfigDef::PrintConfigDef() def->default_value = new ConfigOptionFloat(0); } +void PrintConfigDef::init_sla_params() +{ + t_optiondef_map &Options = this->options; + ConfigOptionDef* def; + + // SLA Printer settings + def = this->add("display_width", coFloat); + def->label = L("Display width"); + def->tooltip = L("Width of the display"); + def->cli = "display-width=f"; + def->min = 1; + def->default_value = new ConfigOptionFloat(150.); + + def = this->add("display_height", coFloat); + def->label = L("Display height"); + def->tooltip = L("Height of the display"); + def->cli = "display-height=f"; + def->min = 1; + def->default_value = new ConfigOptionFloat(100.); + + def = this->add("display_pixels_x", coInt); + def->full_label = L("Number of pixels in"); + def->label = ("X"); + def->tooltip = L("Number of pixels in X"); + def->cli = "display-pixels-x=i"; + def->min = 100; + def->default_value = new ConfigOptionInt(2000); + + def = this->add("display_pixels_y", coInt); + def->label = ("Y"); + def->tooltip = L("Number of pixels in Y"); + def->cli = "display-pixels-y=i"; + def->min = 100; + def->default_value = new ConfigOptionInt(1000); + + def = this->add("printer_correction", coFloats); + def->full_label = L("Printer scaling correction"); + def->tooltip = L("Printer scaling correction"); + def->min = 0; + def->default_value = new ConfigOptionFloats( { 1., 1., 1. } ); + + // SLA Material settings. + def = this->add("initial_layer_height", coFloat); + def->label = L("Initial layer height"); + def->tooltip = L("Initial layer height"); + def->sidetext = L("mm"); + def->cli = "initial-layer-height=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(0.3); + + def = this->add("exposure_time", coFloat); + def->label = L("Exposure time"); + def->tooltip = L("Exposure time"); + def->sidetext = L("s"); + def->cli = "exposure-time=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(10); + + def = this->add("initial_exposure_time", coFloat); + def->label = L("Initial exposure time"); + def->tooltip = L("Initial exposure time"); + def->sidetext = L("s"); + def->cli = "initial-exposure-time=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(15); + + def = this->add("material_correction_printing", coFloats); + def->full_label = L("Correction for expansion when printing"); + def->tooltip = L("Correction for expansion when printing"); + def->min = 0; + def->default_value = new ConfigOptionFloats( { 1. , 1., 1. } ); + + def = this->add("material_correction_curing", coFloats); + def->full_label = L("Correction for expansion after curing"); + def->tooltip = L("Correction for expansion after curing"); + def->min = 0; + def->default_value = new ConfigOptionFloats( { 1. , 1., 1. } ); + + def = this->add("material_notes", coString); + def->label = L("SLA print material notes"); + def->tooltip = L("You can put your notes regarding the SLA print material here."); + def->cli = "material-notes=s"; + def->multiline = true; + def->full_width = true; + def->height = 130; + def->default_value = new ConfigOptionString(""); + + def = this->add("default_sla_material_profile", coString); + def->label = L("Default SLA material profile"); + def->tooltip = L("Default print profile associated with the current printer profile. " + "On selection of the current printer profile, this print profile will be activated."); + def->default_value = new ConfigOptionString(); + + def = this->add("sla_material_settings_id", coString); + def->default_value = new ConfigOptionString(""); +} + void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) { // handle legacy options @@ -2426,4 +2544,8 @@ StaticPrintConfig::StaticCache PrintConfig::s_c StaticPrintConfig::StaticCache HostConfig::s_cache_HostConfig; StaticPrintConfig::StaticCache FullPrintConfig::s_cache_FullPrintConfig; +StaticPrintConfig::StaticCache SLAMaterialConfig::s_cache_SLAMaterialConfig; +StaticPrintConfig::StaticCache SLAPrinterConfig::s_cache_SLAPrinterConfig; +StaticPrintConfig::StaticCache SLAFullPrintConfig::s_cache_SLAFullPrintConfig; + } diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 932ed6054..004228de7 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -22,6 +22,14 @@ namespace Slic3r { +enum PrinterTechnology +{ + // Fused Filament Fabrication + ptFFF, + // Stereolitography + ptSLA, +}; + enum GCodeFlavor { gcfRepRap, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlin, gcfSailfish, gcfMach3, gcfMachinekit, gcfSmoothie, gcfNoExtrusion, @@ -48,7 +56,16 @@ enum FilamentType { ftPLA, ftABS, ftPET, ftHIPS, ftFLEX, ftSCAFF, ftEDGE, ftNGEN, ftPVA }; -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { + static t_config_enum_values keys_map; + if (keys_map.empty()) { + keys_map["FFF"] = ptFFF; + keys_map["SLA"] = ptSLA; + } + return keys_map; +} + +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["reprap"] = gcfRepRap; @@ -65,7 +82,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_ return keys_map; } -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["octoprint"] = htOctoPrint; @@ -74,7 +91,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum::get_enu return keys_map; } -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["rectilinear"] = ipRectilinear; @@ -94,7 +111,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum::get_enu return keys_map; } -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["rectilinear"] = smpRectilinear; @@ -104,7 +121,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum return keys_map; } -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["random"] = spRandom; @@ -115,7 +132,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum::get_enum return keys_map; } -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["PLA"] = ftPLA; @@ -139,6 +156,11 @@ public: PrintConfigDef(); static void handle_legacy(t_config_option_key &opt_key, std::string &value); + +private: + void init_common_params(); + void init_fff_params(); + void init_sla_params(); }; // The one and only global definition of SLic3r configuration options. @@ -851,6 +873,73 @@ protected: } }; +class SLAMaterialConfig : public StaticPrintConfig +{ + STATIC_PRINT_CONFIG_CACHE(SLAMaterialConfig) +public: + ConfigOptionFloat layer_height; + ConfigOptionFloat initial_layer_height; + ConfigOptionFloat exposure_time; + ConfigOptionFloat initial_exposure_time; + ConfigOptionFloats material_correction_printing; + ConfigOptionFloats material_correction_curing; +protected: + void initialize(StaticCacheBase &cache, const char *base_ptr) + { + OPT_PTR(layer_height); + OPT_PTR(initial_layer_height); + OPT_PTR(exposure_time); + OPT_PTR(initial_exposure_time); + OPT_PTR(material_correction_printing); + OPT_PTR(material_correction_curing); + } +}; + +class SLAPrinterConfig : public StaticPrintConfig +{ + STATIC_PRINT_CONFIG_CACHE(SLAPrinterConfig) +public: + ConfigOptionEnum printer_technology; + ConfigOptionPoints bed_shape; + ConfigOptionFloat max_print_height; + ConfigOptionFloat display_width; + ConfigOptionFloat display_height; + ConfigOptionInt display_pixels_x; + ConfigOptionInt display_pixels_y; + ConfigOptionFloats printer_correction; +protected: + void initialize(StaticCacheBase &cache, const char *base_ptr) + { + OPT_PTR(printer_technology); + OPT_PTR(bed_shape); + OPT_PTR(max_print_height); + OPT_PTR(display_width); + OPT_PTR(display_height); + OPT_PTR(display_pixels_x); + OPT_PTR(display_pixels_y); + OPT_PTR(printer_correction); + } +}; + +class SLAFullPrintConfig : public SLAPrinterConfig, public SLAMaterialConfig +{ + STATIC_PRINT_CONFIG_CACHE_DERIVED(SLAFullPrintConfig) + SLAFullPrintConfig() : SLAPrinterConfig(0), SLAMaterialConfig(0) { initialize_cache(); *this = s_cache_SLAFullPrintConfig.defaults(); } + +public: + // Validate the SLAFullPrintConfig. Returns an empty string on success, otherwise an error message is returned. +// std::string validate(); + +protected: + // Protected constructor to be called to initialize ConfigCache::m_default. + SLAFullPrintConfig(int) : SLAPrinterConfig(0), SLAMaterialConfig(0) {} + void initialize(StaticCacheBase &cache, const char *base_ptr) + { + this->SLAPrinterConfig ::initialize(cache, base_ptr); + this->SLAMaterialConfig::initialize(cache, base_ptr); + } +}; + #undef STATIC_PRINT_CONFIG_CACHE #undef STATIC_PRINT_CONFIG_CACHE_BASE #undef STATIC_PRINT_CONFIG_CACHE_DERIVED diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 2d38d78b3..c9607ccf9 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 da86c458b..b4aa6ce95 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/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp index 2a33cd733..0f77b1953 100644 --- a/xs/src/slic3r/GUI/AppConfig.cpp +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -233,6 +233,7 @@ void AppConfig::reset_selections() if (it != m_storage.end()) { it->second.erase("print"); it->second.erase("filament"); + it->second.erase("sla_material"); it->second.erase("printer"); m_dirty = true; } diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index 3c7c817a6..4043a4457 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"))); @@ -274,7 +278,7 @@ void CheckBox::BUILD() { bool check_value = m_opt.type == coBool ? m_opt.default_value->getBool() : m_opt.type == coBools ? - static_cast(m_opt.default_value)->get_at(m_opt_idx) : + static_cast(m_opt.default_value)->get_at(m_opt_idx) : false; auto temp = new wxCheckBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size); @@ -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); @@ -599,7 +604,7 @@ void ColourPicker::BUILD() if (m_opt.height >= 0) size.SetHeight(m_opt.height); if (m_opt.width >= 0) size.SetWidth(m_opt.width); - wxString clr(static_cast(m_opt.default_value)->get_at(m_opt_idx)); + wxString clr(static_cast(m_opt.default_value)->get_at(m_opt_idx)); auto temp = new wxColourPickerCtrl(m_parent, wxID_ANY, clr, wxDefaultPosition, size); // // recast as a wxWindow to fit the calling convention @@ -631,7 +636,7 @@ void PointCtrl::BUILD() // wxSize field_size(40, -1); - auto default_pt = static_cast(m_opt.default_value)->values.at(0); + auto default_pt = static_cast(m_opt.default_value)->values.at(0); double val = default_pt(0); wxString X = val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None); val = default_pt(1); @@ -695,7 +700,7 @@ void StaticText::BUILD() if (m_opt.height >= 0) size.SetHeight(m_opt.height); if (m_opt.width >= 0) size.SetWidth(m_opt.width); - wxString legend(static_cast(m_opt.default_value)->value); + wxString legend(static_cast(m_opt.default_value)->value); auto temp = new wxStaticText(m_parent, wxID_ANY, legend, wxDefaultPosition, size); temp->SetFont(bold_font()); @@ -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 e19c47d68..e305f1e53 100644 --- a/xs/src/slic3r/GUI/Field.hpp +++ b/xs/src/slic3r/GUI/Field.hpp @@ -38,6 +38,7 @@ wxString double_to_string(double const value); class MyButton : public wxButton { + bool hidden = false; // never show button if it's hidden ones public: MyButton() {} MyButton(wxWindow* parent, wxWindowID id, const wxString& label = wxEmptyString, @@ -52,6 +53,12 @@ public: // overridden from wxWindow base class virtual bool AcceptsFocusFromKeyboard() const { return false; } + + virtual bool Show(bool show = true) override { + if (!show) + hidden = true; + return wxButton::Show(!hidden); + } }; class Field { @@ -189,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. @@ -203,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; @@ -214,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; } @@ -408,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 652b8e899..1202045f1 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -2967,7 +2967,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_mouse.set_start_position_2D_as_invalid(); #endif - } + } else if (evt.Leaving()) { // to remove hover on objects when the mouse goes out of this canvas @@ -3214,6 +3214,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)) @@ -3314,6 +3316,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 18f63f65e..9e28de04a 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() @@ -496,20 +595,33 @@ void open_preferences_dialog(int event_preferences) void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed) { update_label_colours_from_appconfig(); - add_created_tab(new TabPrint (g_wxTabPanel, no_controller)); - add_created_tab(new TabFilament (g_wxTabPanel, no_controller)); - add_created_tab(new TabPrinter (g_wxTabPanel, no_controller)); - for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++ i) { - Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); - if (! tab) - continue; - tab->set_event_value_change(wxEventType(event_value_change)); - tab->set_event_presets_changed(wxEventType(event_presets_changed)); - } + add_created_tab(new TabPrint (g_wxTabPanel, no_controller), event_value_change, event_presets_changed); + add_created_tab(new TabFilament (g_wxTabPanel, no_controller), event_value_change, event_presets_changed); + add_created_tab(new TabSLAMaterial (g_wxTabPanel, no_controller), event_value_change, event_presets_changed); + add_created_tab(new TabPrinter (g_wxTabPanel, no_controller), event_value_change, event_presets_changed); +} + +std::vector preset_tabs = { + { "print", nullptr, ptFFF }, + { "filament", nullptr, ptFFF }, + { "sla_material", nullptr, ptSLA } +}; +const std::vector& get_preset_tabs() { + return preset_tabs; +} + +Tab* get_tab(const std::string& name) +{ + std::vector::iterator it = std::find_if(preset_tabs.begin(), preset_tabs.end(), + [name](PresetTab& tab){ return name == tab.name; }); + return it != preset_tabs.end() ? it->panel : nullptr; } TabIface* get_preset_tab_iface(char *name) { + Tab* tab = get_tab(name); + if (tab) return new TabIface(tab); + for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++ i) { Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); if (! tab) @@ -629,13 +741,28 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt } } -void add_created_tab(Tab* panel) +void add_created_tab(Tab* panel, int event_value_change, int event_presets_changed) { panel->create_preset_tab(g_PresetBundle); // Load the currently selected preset into the GUI, update the preset selection box. panel->load_current_preset(); - g_wxTabPanel->AddPage(panel, panel->title()); + + panel->set_event_value_change(wxEventType(event_value_change)); + panel->set_event_presets_changed(wxEventType(event_presets_changed)); + + const wxString& tab_name = panel->GetName(); + bool add_panel = true; + + auto it = std::find_if( preset_tabs.begin(), preset_tabs.end(), + [tab_name](PresetTab& tab){return tab.name == tab_name; }); + if (it != preset_tabs.end()) { + it->panel = panel; + add_panel = it->technology == g_PresetBundle->printers.get_edited_preset().printer_technology(); + } + + if (add_panel) + g_wxTabPanel->AddPage(panel, panel->title()); } void load_current_presets() @@ -725,6 +852,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) @@ -793,14 +936,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)); @@ -815,7 +981,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(); } @@ -838,14 +1004,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); } @@ -853,10 +1019,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; @@ -875,7 +1041,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"); @@ -884,7 +1050,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 = { "", "" }; @@ -910,16 +1076,95 @@ 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; + if (g_PresetBundle->printers.get_selected_preset().printer_technology() == ptFFF) { + g_btn_print->Show(show && !tab->m_config->opt_string("serial_port").empty()); + g_btn_send_gcode->Show(show && !tab->m_config->opt_string("print_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 165288819..69e83eaab 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -3,8 +3,9 @@ #include #include -#include "Config.hpp" +#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,19 +69,26 @@ 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; } +struct PresetTab { + std::string name; + Tab* panel; + PrinterTechnology technology; +}; + + void disable_screensaver(); void enable_screensaver(); bool debugged(); @@ -93,10 +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(); +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(); @@ -108,6 +133,14 @@ 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(); + extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change); // This is called when closing the application, when loading a config file or when starting the config wizard @@ -130,7 +163,7 @@ void create_preset_tabs(bool no_controller, int event_value_change, int event_pr TabIface* get_preset_tab_iface(char *name); // add it at the end of the tab panel. -void add_created_tab(Tab* panel); +void add_created_tab(Tab* panel, int event_value_change, int event_presets_changed); // Change option value in config void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0); @@ -150,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); @@ -169,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..50b1a1f5a --- /dev/null +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -0,0 +1,1644 @@ +#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; +Vec3d m_move_options; +Vec3d 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_selected_preset().printer_technology() == ptSLA ? 1 : + 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; + + Vec3d m = m_move_options; + Vec3d l = m_last_coords; + + auto d = Vec3d(m(0) - l(0), m(1) - l(1), m(2) - l(2)); + auto volume = (*m_objects)[m_selected_object_id]->volumes[volume_id]; + volume->mesh.translate(d(0), d(1), d(2)); + 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(0) != val){ + update = true; + m_move_options(0) = val; + } + else if (opt_key == "y" && m_move_options(1) != val){ + update = true; + m_move_options(1) = val; + } + else if (opt_key == "z" && m_move_options(2) != val){ + update = true; + m_move_options(2) = 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 = Vec3d(0, 0, 0); + m_last_coords = Vec3d(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_selected_preset().printer_technology() == ptSLA ? 1 : + 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(0), + model_object->origin_translation(1), + model_object->origin_translation(2) ); + // 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(0)*1.5, size(1)*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(0)*1.5 / 2.0, -size(1)*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 Vec3d& 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(0) + 0.5)); + og->set_value("scale_y", int(size(1) + 0.5)); + og->set_value("scale_z", int(size(2) + 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(int extruders_count) +{ + if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA) + extruders_count = 1; + + // 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..40dfb9279 --- /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 Vec3d& 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(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/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index af2cd8ff6..6ee92e53d 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -120,6 +120,11 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem VendorProfile::PrinterModel model; model.id = section.first.substr(printer_model_key.size()); model.name = section.second.get("name", model.id); + auto technology_field = section.second.get("technology", "FFF"); + if (! ConfigOptionEnum::from_string(technology_field, model.technology)) { + BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Invalid printer technology field: `%2%`") % id % technology_field; + model.technology = ptFFF; + } section.second.get("variants", ""); const auto variants_field = section.second.get("variants", ""); std::vector variants; @@ -177,7 +182,7 @@ void Preset::normalize(DynamicPrintConfig &config) { auto *nozzle_diameter = dynamic_cast(config.option("nozzle_diameter")); if (nozzle_diameter != nullptr) - // Loaded the Printer settings. Verify, that all extruder dependent values have enough values. + // Loaded the FFF Printer settings. Verify, that all extruder dependent values have enough values. set_num_extruders(config, (unsigned int)nozzle_diameter->values.size()); if (config.option("filament_diameter") != nullptr) { // This config contains single or multiple filament presets. @@ -204,12 +209,9 @@ void Preset::normalize(DynamicPrintConfig &config) } } -// Load a config file, return a C++ class Slic3r::DynamicPrintConfig with $keys initialized from the config file. -// In case of a "default" config item, return the default values. -DynamicPrintConfig& Preset::load(const std::vector &keys) +DynamicPrintConfig& Preset::load(const std::vector &keys, const StaticPrintConfig &defaults) { // Set the configuration from the defaults. - Slic3r::FullPrintConfig defaults; this->config.apply_only(defaults, keys.empty() ? defaults.keys() : keys); if (! this->is_default) { // Load the preset file, apply preset values on top of defaults. @@ -260,8 +262,9 @@ bool Preset::is_compatible_with_printer(const Preset &active_printer) const { DynamicPrintConfig config; config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name)); - config.set_key_value("num_extruders", new ConfigOptionInt( - (int)static_cast(active_printer.config.option("nozzle_diameter"))->values.size())); + const ConfigOption *opt = active_printer.config.option("nozzle_diameter"); + if (opt) + config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); return this->is_compatible_with_printer(active_printer, &config); } @@ -328,8 +331,10 @@ const std::vector& Preset::printer_options() static std::vector s_opts; if (s_opts.empty()) { s_opts = { - "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", "host_type", - "print_host", "printhost_apikey", "printhost_cafile", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", + "printer_technology", + "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", + "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", + "host_type", "print_host", "printhost_apikey", "printhost_cafile", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", "between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction", "cooling_tube_length", "parking_pos_retraction", "extra_loading_move", "max_print_height", "default_print_profile", "inherits", @@ -359,7 +364,39 @@ const std::vector& Preset::nozzle_options() return s_opts; } -PresetCollection::PresetCollection(Preset::Type type, const std::vector &keys) : +const std::vector& Preset::sla_printer_options() +{ + static std::vector s_opts; + if (s_opts.empty()) { + s_opts = { + "printer_technology", + "bed_shape", "max_print_height", + "display_width", "display_height", "display_pixels_x", "display_pixels_y", + "printer_correction", + "printer_notes", + "inherits" + }; + } + return s_opts; +} + +const std::vector& Preset::sla_material_options() +{ + static std::vector s_opts; + if (s_opts.empty()) { + s_opts = { + "layer_height", "initial_layer_height", + "exposure_time", "initial_exposure_time", + "material_correction_printing", "material_correction_curing", + "material_notes", + "compatible_printers", + "compatible_printers_condition", "inherits" + }; + } + return s_opts; +} + +PresetCollection::PresetCollection(Preset::Type type, const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name) : m_type(type), m_edited_preset(type, "", false), m_idx_selected(0), @@ -367,8 +404,7 @@ PresetCollection::PresetCollection(Preset::Type type, const std::vectoradd_default_preset(keys, defaults, default_name); m_edited_preset.config.apply(m_presets.front().config); } @@ -382,7 +418,7 @@ PresetCollection::~PresetCollection() void PresetCollection::reset(bool delete_files) { - if (m_presets.size() > 1) { + if (m_presets.size() > m_num_default_presets) { if (delete_files) { // Erase the preset files. for (Preset &preset : m_presets) @@ -390,11 +426,19 @@ void PresetCollection::reset(bool delete_files) boost::nowide::remove(preset.file.c_str()); } // Don't use m_presets.resize() here as it requires a default constructor for Preset. - m_presets.erase(m_presets.begin() + 1, m_presets.end()); + m_presets.erase(m_presets.begin() + m_num_default_presets, m_presets.end()); this->select_preset(0); } } +void PresetCollection::add_default_preset(const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name) +{ + // Insert just the default preset. + m_presets.emplace_back(Preset(this->type(), preset_name, true)); + m_presets.back().load(keys, defaults); + ++ m_num_default_presets; +} + // Load all presets found in dir_path. // Throws an exception on error. void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir) @@ -417,14 +461,15 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri try { Preset preset(m_type, name, false); preset.file = dir_entry.path().string(); - preset.load(keys); + //FIXME One should initialize with SLAFullPrintConfig for the SLA profiles! + preset.load(keys, static_cast(FullPrintConfig::defaults())); m_presets.emplace_back(preset); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); errors_cummulative += "\n"; } } - std::sort(m_presets.begin() + 1, m_presets.end()); + std::sort(m_presets.begin() + m_num_default_presets, m_presets.end()); this->select_preset(first_visible_idx()); if (! errors_cummulative.empty()) throw std::runtime_error(errors_cummulative); @@ -642,7 +687,7 @@ Preset* PresetCollection::find_preset(const std::string &name, bool first_visibl // Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible. size_t PresetCollection::first_visible_idx() const { - size_t idx = m_default_suppressed ? 1 : 0; + size_t idx = m_default_suppressed ? m_num_default_presets : 0; for (; idx < this->m_presets.size(); ++ idx) if (m_presets[idx].is_visible) break; @@ -655,7 +700,7 @@ void PresetCollection::set_default_suppressed(bool default_suppressed) { if (m_default_suppressed != default_suppressed) { m_default_suppressed = default_suppressed; - m_presets.front().is_visible = ! default_suppressed || (m_presets.size() > 1 && m_idx_selected > 0); + m_presets.front().is_visible = ! default_suppressed || (m_presets.size() > m_num_default_presets && m_idx_selected > 0); } } @@ -663,9 +708,10 @@ size_t PresetCollection::update_compatible_with_printer_internal(const Preset &a { DynamicPrintConfig config; config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name)); - config.set_key_value("num_extruders", new ConfigOptionInt( - (int)static_cast(active_printer.config.option("nozzle_diameter"))->values.size())); - for (size_t idx_preset = 1; idx_preset < m_presets.size(); ++ idx_preset) { + const ConfigOption *opt = active_printer.config.option("nozzle_diameter"); + if (opt) + config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); + for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++ idx_preset) { bool selected = idx_preset == m_idx_selected; Preset &preset_selected = m_presets[idx_preset]; Preset &preset_edited = selected ? m_edited_preset : preset_selected; @@ -706,7 +752,7 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) wxString selected = ""; if (!this->m_presets.front().is_visible) ui->Append("------- " +_(L("System presets")) + " -------", wxNullBitmap); - for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++i) { + for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++i) { const Preset &preset = this->m_presets[i]; if (! preset.is_visible || (! preset.is_compatible && i != m_idx_selected)) continue; @@ -744,7 +790,7 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) if (i == m_idx_selected) selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); } - if (preset.is_default) + if (i + 1 == m_num_default_presets) ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap); } if (!nonsys_presets.empty()) @@ -774,7 +820,7 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati wxString selected = ""; if (!this->m_presets.front().is_visible) ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap); - for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++i) { + for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++i) { const Preset &preset = this->m_presets[i]; if (! preset.is_visible || (! show_incompatible && ! preset.is_compatible && i != m_idx_selected)) continue; @@ -804,7 +850,7 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati if (i == m_idx_selected) selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); } - if (preset.is_default) + if (i + 1 == m_num_default_presets) ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap); } if (!nonsys_presets.empty()) @@ -852,11 +898,11 @@ bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui) return was_dirty != is_dirty; } -std::vector PresetCollection::dirty_options(const Preset *edited, const Preset *reference, const bool is_printer_type /*= false*/) +std::vector PresetCollection::dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare /*= false*/) { std::vector changed; if (edited != nullptr && reference != nullptr) { - changed = is_printer_type ? + changed = deep_compare ? reference->config.deep_diff(edited->config) : reference->config.diff(edited->config); // The "compatible_printers" option key is handled differently from the others: @@ -896,7 +942,7 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b idx = it - m_presets.begin(); else { // Find the first visible preset. - for (size_t i = m_default_suppressed ? 1 : 0; i < m_presets.size(); ++ i) + for (size_t i = m_default_suppressed ? m_num_default_presets : 0; i < m_presets.size(); ++ i) if (m_presets[i].is_visible) { idx = i; break; @@ -938,7 +984,7 @@ std::vector PresetCollection::merge_presets(PresetCollection &&othe if (preset.is_default || preset.is_external) continue; Preset key(m_type, preset.name); - auto it = std::lower_bound(m_presets.begin() + 1, m_presets.end(), key); + auto it = std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key); if (it == m_presets.end() || it->name != preset.name) { if (preset.vendor != nullptr) { // Re-assign a pointer to the vendor structure in the new PresetBundle. diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index 0d00cae48..821d7dc54 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -52,6 +52,7 @@ public: PrinterModel() {} std::string id; std::string name; + PrinterTechnology technology; std::vector variants; PrinterVariant* variant(const std::string &name) { for (auto &v : this->variants) @@ -83,6 +84,7 @@ public: TYPE_INVALID, TYPE_PRINT, TYPE_FILAMENT, + TYPE_SLA_MATERIAL, TYPE_PRINTER, }; @@ -123,8 +125,7 @@ public: DynamicPrintConfig config; // Load this profile for the following keys only. - // Throws std::runtime_error in case the file cannot be read. - DynamicPrintConfig& load(const std::vector &keys); + DynamicPrintConfig& load(const std::vector &keys, const StaticPrintConfig &defaults); void save(); @@ -149,6 +150,10 @@ public: std::string& compatible_printers_condition() { return Preset::compatible_printers_condition(this->config); } const std::string& compatible_printers_condition() const { return Preset::compatible_printers_condition(const_cast(this)->config); } + static PrinterTechnology& printer_technology(DynamicPrintConfig &cfg) { return cfg.option>("printer_technology", true)->value; } + PrinterTechnology& printer_technology() { return Preset::printer_technology(this->config); } + const PrinterTechnology& printer_technology() const { return Preset::printer_technology(const_cast(this)->config); } + // Mark this preset as compatible if it is compatible with active_printer. bool update_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config); @@ -167,6 +172,10 @@ public: static const std::vector& printer_options(); // Nozzle options of the printer options. static const std::vector& nozzle_options(); + + static const std::vector& sla_printer_options(); + static const std::vector& sla_material_options(); + static void update_suffix_modified(); protected: @@ -184,15 +193,15 @@ class PresetCollection { public: // Initialize the PresetCollection with the "- default -" preset. - PresetCollection(Preset::Type type, const std::vector &keys); + PresetCollection(Preset::Type type, const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name = "- default -"); ~PresetCollection(); typedef std::deque::iterator Iterator; typedef std::deque::const_iterator ConstIterator; - Iterator begin() { return m_presets.begin() + 1; } - ConstIterator begin() const { return m_presets.begin() + 1; } - Iterator end() { return m_presets.end(); } - ConstIterator end() const { return m_presets.end(); } + Iterator begin() { return m_presets.begin() + m_num_default_presets; } + ConstIterator begin() const { return m_presets.begin() + m_num_default_presets; } + Iterator end() { return m_presets.end(); } + ConstIterator end() const { return m_presets.end(); } void reset(bool delete_files); @@ -200,6 +209,9 @@ public: std::string name() const; const std::deque& operator()() const { return m_presets; } + // Add default preset at the start of the collection, increment the m_default_preset counter. + void add_default_preset(const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name); + // Load ini files of the particular type from the provided directory path. void load_presets(const std::string &dir_path, const std::string &subdir); @@ -247,6 +259,8 @@ public: Preset& get_selected_preset() { return m_presets[m_idx_selected]; } const Preset& get_selected_preset() const { return m_presets[m_idx_selected]; } int get_selected_idx() const { return m_idx_selected; } + // Returns the name of the selected preset, or an empty string if no preset is selected. + std::string get_selected_preset_name() const { return (m_idx_selected == -1) ? std::string() : this->get_selected_preset().name; } // For the current edited preset, return the parent preset if there is one. // If there is no parent preset, nullptr is returned. // The parent preset may be a system preset or a user preset, which will be @@ -283,7 +297,7 @@ public: template size_t first_compatible_idx(PreferedCondition prefered_condition) const { - size_t i = m_default_suppressed ? 1 : 0; + size_t i = m_default_suppressed ? m_num_default_presets : 0; size_t n = this->m_presets.size(); size_t i_compatible = n; for (; i < n; ++ i) @@ -309,7 +323,8 @@ public: const Preset& first_compatible() const { return this->preset(this->first_compatible_idx()); } // Return number of presets including the "- default -" preset. - size_t size() const { return this->m_presets.size(); } + size_t size() const { return m_presets.size(); } + bool has_defaults_only() const { return m_presets.size() <= m_num_default_presets; } // For Print / Filament presets, disable those, which are not compatible with the printer. template @@ -327,11 +342,11 @@ public: // Compare the content of get_selected_preset() with get_edited_preset() configs, return true if they differ. bool current_is_dirty() const { return ! this->current_dirty_options().empty(); } // Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ. - std::vector current_dirty_options(const bool is_printer_type = false) const - { return dirty_options(&this->get_edited_preset(), &this->get_selected_preset(), is_printer_type); } + std::vector current_dirty_options(const bool deep_compare = false) const + { return dirty_options(&this->get_edited_preset(), &this->get_selected_preset(), deep_compare); } // Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ. - std::vector current_different_from_parent_options(const bool is_printer_type = false) const - { return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent(), is_printer_type); } + std::vector current_different_from_parent_options(const bool deep_compare = false) const + { return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent(), deep_compare); } // Update the choice UI from the list of presets. // If show_incompatible, all presets are shown, otherwise only the compatible presets are shown. @@ -374,8 +389,16 @@ private: std::deque::iterator find_preset_internal(const std::string &name) { Preset key(m_type, name); - auto it = std::lower_bound(m_presets.begin() + 1, m_presets.end(), key); - return ((it == m_presets.end() || it->name != name) && m_presets.front().name == name) ? m_presets.begin() : it; + auto it = std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key); + if (it == m_presets.end() || it->name != name) { + // Preset has not been not found in the sorted list of non-default presets. Try the defaults. + for (size_t i = 0; i < m_num_default_presets; ++ i) + if (m_presets[i].name == name) { + it = m_presets.begin() + i; + break; + } + } + return it; } std::deque::const_iterator find_preset_internal(const std::string &name) const { return const_cast(this)->find_preset_internal(name); } @@ -395,7 +418,8 @@ private: // Selected preset. int m_idx_selected; // Is the "- default -" preset suppressed? - bool m_default_suppressed = true; + bool m_default_suppressed = true; + size_t m_num_default_presets = 0; // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items of a Platter. // These bitmaps are not owned by PresetCollection, but by a PresetBundle. const wxBitmap *m_bitmap_compatible = nullptr; diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index 94baa0e0e..cd3924dd0 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -40,9 +40,10 @@ static std::vector s_project_options { }; PresetBundle::PresetBundle() : - prints(Preset::TYPE_PRINT, Preset::print_options()), - filaments(Preset::TYPE_FILAMENT, Preset::filament_options()), - printers(Preset::TYPE_PRINTER, Preset::printer_options()), + prints(Preset::TYPE_PRINT, Preset::print_options(), static_cast(FullPrintConfig::defaults())), + filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast(FullPrintConfig::defaults())), + sla_materials(Preset::TYPE_SLA_MATERIAL, Preset::sla_material_options(), static_cast(SLAFullPrintConfig::defaults())), + printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast(FullPrintConfig::defaults()), "- default FFF -"), m_bitmapCompatible(new wxBitmap), m_bitmapIncompatible(new wxBitmap), m_bitmapLock(new wxBitmap), @@ -69,24 +70,35 @@ PresetBundle::PresetBundle() : this->filaments.default_preset().compatible_printers_condition(); this->filaments.default_preset().inherits(); - this->printers.default_preset().config.optptr("printer_settings_id", true); - this->printers.default_preset().config.optptr("printer_vendor", true); - this->printers.default_preset().config.optptr("printer_model", true); - this->printers.default_preset().config.optptr("printer_variant", true); - this->printers.default_preset().config.optptr("default_print_profile", true); - this->printers.default_preset().config.option("default_filament_profile", true)->values = { "" }; - this->printers.default_preset().inherits(); + this->sla_materials.default_preset().config.optptr("sla_material_settings_id", true); + this->sla_materials.default_preset().compatible_printers_condition(); + this->sla_materials.default_preset().inherits(); + + this->printers.add_default_preset(Preset::sla_printer_options(), static_cast(SLAFullPrintConfig::defaults()), "- default SLA -"); + this->printers.preset(1).printer_technology() = ptSLA; + for (size_t i = 0; i < 2; ++ i) { + Preset &preset = this->printers.preset(i); + preset.config.optptr("printer_settings_id", true); + preset.config.optptr("printer_vendor", true); + preset.config.optptr("printer_model", true); + preset.config.optptr("printer_variant", true); + preset.config.optptr("default_print_profile", true); + preset.config.option("default_filament_profile", true)->values = { "" }; + preset.inherits(); + } // Load the default preset bitmaps. - this->prints .load_bitmap_default("cog.png"); - this->filaments.load_bitmap_default("spool.png"); - this->printers .load_bitmap_default("printer_empty.png"); + this->prints .load_bitmap_default("cog.png"); + this->filaments .load_bitmap_default("spool.png"); + this->sla_materials.load_bitmap_default("package_green.png"); + this->printers .load_bitmap_default("printer_empty.png"); this->load_compatible_bitmaps(); // Re-activate the default presets, so their "edited" preset copies will be updated with the additional configuration values above. - this->prints .select_preset(0); - this->filaments.select_preset(0); - this->printers .select_preset(0); + this->prints .select_preset(0); + this->filaments .select_preset(0); + this->sla_materials.select_preset(0); + this->printers .select_preset(0); this->project_config.apply_only(FullPrintConfig::defaults(), s_project_options); } @@ -113,13 +125,15 @@ void PresetBundle::reset(bool delete_files) { // Clear the existing presets, delete their respective files. this->vendors.clear(); - this->prints .reset(delete_files); - this->filaments.reset(delete_files); - this->printers .reset(delete_files); + this->prints .reset(delete_files); + this->filaments .reset(delete_files); + this->sla_materials.reset(delete_files); + this->printers .reset(delete_files); this->filament_presets.clear(); - this->filament_presets.emplace_back(this->filaments.get_selected_preset().name); + this->filament_presets.emplace_back(this->filaments.get_selected_preset_name()); this->obsolete_presets.prints.clear(); this->obsolete_presets.filaments.clear(); + this->obsolete_presets.sla_materials.clear(); this->obsolete_presets.printers.clear(); } @@ -135,11 +149,13 @@ void PresetBundle::setup_directories() data_dir / "presets", data_dir / "presets" / "print", data_dir / "presets" / "filament", + data_dir / "presets" / "sla_material", data_dir / "presets" / "printer" #else // Store the print/filament/printer presets at the same location as the upstream Slic3r. data_dir / "print", data_dir / "filament", + data_dir / "sla_material", data_dir / "printer" #endif }; @@ -175,6 +191,11 @@ void PresetBundle::load_presets(const AppConfig &config) } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } + try { + this->sla_materials.load_presets(dir_user_presets, "sla_material"); + } catch (const std::runtime_error &err) { + errors_cummulative += err.what(); + } try { this->printers.load_presets(dir_user_presets, "printer"); } catch (const std::runtime_error &err) { @@ -238,13 +259,16 @@ std::string PresetBundle::load_system_presets() std::vector PresetBundle::merge_presets(PresetBundle &&other) { this->vendors.insert(other.vendors.begin(), other.vendors.end()); - std::vector duplicate_prints = this->prints .merge_presets(std::move(other.prints), this->vendors); - std::vector duplicate_filaments = this->filaments.merge_presets(std::move(other.filaments), this->vendors); - std::vector duplicate_printers = this->printers .merge_presets(std::move(other.printers), this->vendors); - append(this->obsolete_presets.prints, std::move(other.obsolete_presets.prints)); - append(this->obsolete_presets.filaments, std::move(other.obsolete_presets.filaments)); - append(this->obsolete_presets.printers, std::move(other.obsolete_presets.printers)); + std::vector duplicate_prints = this->prints .merge_presets(std::move(other.prints), this->vendors); + std::vector duplicate_filaments = this->filaments .merge_presets(std::move(other.filaments), this->vendors); + std::vector duplicate_sla_materials = this->sla_materials.merge_presets(std::move(other.sla_materials), this->vendors); + std::vector duplicate_printers = this->printers .merge_presets(std::move(other.printers), this->vendors); + append(this->obsolete_presets.prints, std::move(other.obsolete_presets.prints)); + append(this->obsolete_presets.filaments, std::move(other.obsolete_presets.filaments)); + append(this->obsolete_presets.sla_materials, std::move(other.obsolete_presets.sla_materials)); + append(this->obsolete_presets.printers, std::move(other.obsolete_presets.printers)); append(duplicate_prints, std::move(duplicate_filaments)); + append(duplicate_prints, std::move(duplicate_sla_materials)); append(duplicate_prints, std::move(duplicate_printers)); return duplicate_prints; } @@ -275,9 +299,10 @@ void PresetBundle::load_selections(const AppConfig &config) this->load_installed_printers(config); // Parse the initial print / filament / printer profile names. - std::string initial_print_profile_name = remove_ini_suffix(config.get("presets", "print")); - std::string initial_filament_profile_name = remove_ini_suffix(config.get("presets", "filament")); - std::string initial_printer_profile_name = remove_ini_suffix(config.get("presets", "printer")); + std::string initial_print_profile_name = remove_ini_suffix(config.get("presets", "print")); + std::string initial_filament_profile_name = remove_ini_suffix(config.get("presets", "filament")); + std::string initial_sla_material_profile_name = remove_ini_suffix(config.get("presets", "sla_material")); + std::string initial_printer_profile_name = remove_ini_suffix(config.get("presets", "printer")); // Activate print / filament / printer profiles from the config. // If the printer profile enumerated by the config are not visible, select an alternate preset. @@ -285,21 +310,24 @@ void PresetBundle::load_selections(const AppConfig &config) // will be selected by the following call of this->update_compatible_with_printer(true). prints.select_preset_by_name_strict(initial_print_profile_name); filaments.select_preset_by_name_strict(initial_filament_profile_name); + sla_materials.select_preset_by_name_strict(initial_sla_material_profile_name); printers.select_preset_by_name(initial_printer_profile_name, true); - // Load the names of the other filament profiles selected for a multi-material printer. - auto *nozzle_diameter = dynamic_cast(printers.get_selected_preset().config.option("nozzle_diameter")); - size_t num_extruders = nozzle_diameter->values.size(); - this->filament_presets = { initial_filament_profile_name }; - for (unsigned int i = 1; i < (unsigned int)num_extruders; ++ i) { - char name[64]; - sprintf(name, "filament_%d", i); - if (! config.has("presets", name)) - break; - this->filament_presets.emplace_back(remove_ini_suffix(config.get("presets", name))); + if (printers.get_selected_preset().printer_technology() == ptFFF){ + // Load the names of the other filament profiles selected for a multi-material printer. + auto *nozzle_diameter = dynamic_cast(printers.get_selected_preset().config.option("nozzle_diameter")); + size_t num_extruders = nozzle_diameter->values.size(); + this->filament_presets = { initial_filament_profile_name }; + for (unsigned int i = 1; i < (unsigned int)num_extruders; ++i) { + char name[64]; + sprintf(name, "filament_%d", i); + if (!config.has("presets", name)) + break; + this->filament_presets.emplace_back(remove_ini_suffix(config.get("presets", name))); + } + // Do not define the missing filaments, so that the update_compatible_with_printer() will use the preferred filaments. + this->filament_presets.resize(num_extruders, ""); } - // Do not define the missing filaments, so that the update_compatible_with_printer() will use the preferred filaments. - this->filament_presets.resize(num_extruders, ""); // Update visibility of presets based on their compatibility with the active printer. // Always try to select a compatible print and filament preset to the current printer preset, @@ -313,25 +341,33 @@ void PresetBundle::load_selections(const AppConfig &config) void PresetBundle::export_selections(AppConfig &config) { assert(filament_presets.size() >= 1); - assert(filament_presets.size() > 1 || filaments.get_selected_preset().name == filament_presets.front()); + assert(filament_presets.size() > 1 || filaments.get_selected_preset_name() == filament_presets.front()); config.clear_section("presets"); - config.set("presets", "print", prints.get_selected_preset().name); - config.set("presets", "filament", filament_presets.front()); + config.set("presets", "print", prints.get_selected_preset_name()); + config.set("presets", "filament", filament_presets.front()); for (int i = 1; i < filament_presets.size(); ++i) { char name[64]; sprintf(name, "filament_%d", i); config.set("presets", name, filament_presets[i]); } - config.set("presets", "printer", printers.get_selected_preset().name); + config.set("presets", "sla_material", sla_materials.get_selected_preset_name()); + config.set("presets", "printer", printers.get_selected_preset_name()); } void PresetBundle::export_selections(PlaceholderParser &pp) { assert(filament_presets.size() >= 1); - assert(filament_presets.size() > 1 || filaments.get_selected_preset().name == filament_presets.front()); - pp.set("print_preset", prints.get_selected_preset().name); - pp.set("filament_preset", filament_presets); - pp.set("printer_preset", printers.get_selected_preset().name); + assert(filament_presets.size() > 1 || filaments.get_selected_preset_name() == filament_presets.front()); + switch (printers.get_edited_preset().printer_technology()) { + case ptFFF: + pp.set("print_preset", prints.get_selected_preset().name); + pp.set("filament_preset", filament_presets); + break; + case ptSLA: + pp.set("sla_material_preset", sla_materials.get_selected_preset().name); + break; + } + pp.set("printer_preset", printers.get_selected_preset().name); } bool PresetBundle::load_compatible_bitmaps() @@ -349,32 +385,43 @@ bool PresetBundle::load_compatible_bitmaps() bool loaded_lock_open = m_bitmapLockOpen->LoadFile( wxString::FromUTF8(Slic3r::var(path_bitmap_lock_open).c_str()), wxBITMAP_TYPE_PNG); if (loaded_compatible) { - prints .set_bitmap_compatible(m_bitmapCompatible); - filaments.set_bitmap_compatible(m_bitmapCompatible); + prints .set_bitmap_compatible(m_bitmapCompatible); + filaments .set_bitmap_compatible(m_bitmapCompatible); + sla_materials.set_bitmap_compatible(m_bitmapCompatible); // printers .set_bitmap_compatible(m_bitmapCompatible); } if (loaded_incompatible) { - prints .set_bitmap_incompatible(m_bitmapIncompatible); - filaments.set_bitmap_incompatible(m_bitmapIncompatible); -// printers .set_bitmap_incompatible(m_bitmapIncompatible); + prints .set_bitmap_incompatible(m_bitmapIncompatible); + filaments .set_bitmap_incompatible(m_bitmapIncompatible); + sla_materials.set_bitmap_incompatible(m_bitmapIncompatible); +// printers .set_bitmap_incompatible(m_bitmapIncompatible); } if (loaded_lock) { - prints .set_bitmap_lock(m_bitmapLock); - filaments.set_bitmap_lock(m_bitmapLock); - printers .set_bitmap_lock(m_bitmapLock); + prints .set_bitmap_lock(m_bitmapLock); + filaments .set_bitmap_lock(m_bitmapLock); + sla_materials.set_bitmap_lock(m_bitmapLock); + printers .set_bitmap_lock(m_bitmapLock); } if (loaded_lock_open) { - prints .set_bitmap_lock_open(m_bitmapLock); - filaments.set_bitmap_lock_open(m_bitmapLock); - printers .set_bitmap_lock_open(m_bitmapLock); + prints .set_bitmap_lock_open(m_bitmapLock); + filaments .set_bitmap_lock_open(m_bitmapLock); + sla_materials.set_bitmap_lock_open(m_bitmapLock); + printers .set_bitmap_lock_open(m_bitmapLock); } return loaded_compatible && loaded_incompatible && loaded_lock && loaded_lock_open; } DynamicPrintConfig PresetBundle::full_config() const +{ + return (this->printers.get_edited_preset().printer_technology() == ptFFF) ? + this->full_fff_config() : + this->full_sla_config(); +} + +DynamicPrintConfig PresetBundle::full_fff_config() const { DynamicPrintConfig out; - out.apply(FullPrintConfig()); + out.apply(FullPrintConfig::defaults()); out.apply(this->prints.get_edited_preset().config); // Add the default filament preset to have the "filament_preset_id" defined. out.apply(this->filaments.default_preset().config); @@ -466,6 +513,48 @@ DynamicPrintConfig PresetBundle::full_config() const return out; } +DynamicPrintConfig PresetBundle::full_sla_config() const +{ + DynamicPrintConfig out; + out.apply(SLAFullPrintConfig::defaults()); + out.apply(this->sla_materials.get_edited_preset().config); + out.apply(this->printers.get_edited_preset().config); + // There are no project configuration values as of now, the project_config is reserved for FFF printers. +// out.apply(this->project_config); + + // Collect the "compatible_printers_condition" and "inherits" values over all presets (sla_materials, printers) into a single vector. + std::vector compatible_printers_condition; + std::vector inherits; + compatible_printers_condition.emplace_back(this->/*prints*/sla_materials.get_edited_preset().compatible_printers_condition()); + inherits .emplace_back(this->/*prints*/sla_materials.get_edited_preset().inherits()); + inherits .emplace_back(this->printers.get_edited_preset().inherits()); + + // These two value types clash between the print and filament profiles. They should be renamed. + out.erase("compatible_printers"); + out.erase("compatible_printers_condition"); + out.erase("inherits"); + + out.option("sla_material_settings_id", true)->value = this->sla_materials.get_selected_preset().name; + out.option("printer_settings_id", true)->value = this->printers.get_selected_preset().name; + + // Serialize the collected "compatible_printers_condition" and "inherits" fields. + // There will be 1 + num_exturders fields for "inherits" and 2 + num_extruders for "compatible_printers_condition" stored. + // The vector will not be stored if all fields are empty strings. + auto add_if_some_non_empty = [&out](std::vector &&values, const std::string &key) { + bool nonempty = false; + for (const std::string &v : values) + if (! v.empty()) { + nonempty = true; + break; + } + if (nonempty) + out.set_key_value(key, new ConfigOptionStrings(std::move(values))); + }; + add_if_some_non_empty(std::move(compatible_printers_condition), "compatible_printers_condition_cummulative"); + add_if_some_non_empty(std::move(inherits), "inherits_cummulative"); + return out; +} + // Load an external config file containing the print, filament and printer presets. // Instead of a config file, a G-code may be loaded containing the full set of parameters. // In the future the configuration will likely be read from an AMF file as well. @@ -530,6 +619,8 @@ void PresetBundle::load_config_string(const char* str, const char* source_filena // Load a config file from a boost property_tree. This is a private method called from load_config_file. void PresetBundle::load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config) { + PrinterTechnology printer_technology = Preset::printer_technology(config); + // The "compatible_printers" field should not have been exported into a config.ini or a G-code anyway, // but some of the alpha versions of Slic3r did. { @@ -541,8 +632,10 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool } } - size_t num_extruders = std::min(config.option("nozzle_diameter" )->values.size(), - config.option("filament_diameter")->values.size()); + size_t num_extruders = (printer_technology == ptFFF) ? + std::min(config.option("nozzle_diameter" )->values.size(), + config.option("filament_diameter")->values.size()) : + 0; // Make a copy of the "compatible_printers_condition_cummulative" and "inherits_cummulative" vectors, which // accumulate values over all presets (print, filaments, printers). // These values will be distributed into their particular presets when loading. @@ -553,7 +646,8 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool compatible_printers_condition_values.resize(num_extruders + 2, std::string()); inherits_values.resize(num_extruders + 2, std::string()); // The "default_filament_profile" will be later extracted into the printer profile. - config.option("default_filament_profile", true)->values.resize(num_extruders, std::string()); + if (printer_technology == ptFFF) + config.option("default_filament_profile", true)->values.resize(num_extruders, std::string()); // 1) Create a name from the file name. // Keep the suffix (.ini, .gcode, .amf, .3mf etc) to differentiate it from the normal profiles. @@ -562,81 +656,83 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool // 2) If the loading succeeded, split and load the config into print / filament / printer settings. // First load the print and printer presets. for (size_t i_group = 0; i_group < 2; ++ i_group) { - PresetCollection &presets = (i_group == 0) ? this->prints : this->printers; + PresetCollection &presets = (i_group == 0) ? ((printer_technology == ptFFF) ? this->prints : this->sla_materials) : this->printers; // Split the "compatible_printers_condition" and "inherits" values one by one from a single vector to the print & printer profiles. size_t idx = (i_group == 0) ? 0 : num_extruders + 1; inherits = inherits_values[idx]; compatible_printers_condition = compatible_printers_condition_values[idx]; if (is_external) presets.load_external_preset(name_or_path, name, - config.opt_string((i_group == 0) ? "print_settings_id" : "printer_settings_id", true), + config.opt_string((i_group == 0) ? ((printer_technology == ptFFF) ? "print_settings_id" : "sla_material_id") : "printer_settings_id", true), config); else presets.load_preset(presets.path_from_name(name), name, config).save(); } - // 3) Now load the filaments. If there are multiple filament presets, split them and load them. - auto old_filament_profile_names = config.option("filament_settings_id", true); - old_filament_profile_names->values.resize(num_extruders, std::string()); + if (Preset::printer_technology(config) == ptFFF) { + // 3) Now load the filaments. If there are multiple filament presets, split them and load them. + auto old_filament_profile_names = config.option("filament_settings_id", true); + old_filament_profile_names->values.resize(num_extruders, std::string()); - if (num_extruders <= 1) { - // Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets. - inherits = inherits_values[1]; - compatible_printers_condition = compatible_printers_condition_values[1]; - if (is_external) - this->filaments.load_external_preset(name_or_path, name, old_filament_profile_names->values.front(), config); - else - this->filaments.load_preset(this->filaments.path_from_name(name), name, config).save(); - this->filament_presets.clear(); - this->filament_presets.emplace_back(name); - } else { - // Split the filament presets, load each of them separately. - std::vector configs(num_extruders, this->filaments.default_preset().config); - // loop through options and scatter them into configs. - for (const t_config_option_key &key : this->filaments.default_preset().config.keys()) { - const ConfigOption *other_opt = config.option(key); - if (other_opt == nullptr) - continue; - if (other_opt->is_scalar()) { - for (size_t i = 0; i < configs.size(); ++ i) - configs[i].option(key, false)->set(other_opt); - } else if (key != "compatible_printers") { - for (size_t i = 0; i < configs.size(); ++ i) - static_cast(configs[i].option(key, false))->set_at(other_opt, 0, i); - } - } - // Load the configs into this->filaments and make them active. - this->filament_presets.clear(); - for (size_t i = 0; i < configs.size(); ++ i) { - DynamicPrintConfig &cfg = configs[i]; + if (num_extruders <= 1) { // Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets. - cfg.opt_string("compatible_printers_condition", true) = compatible_printers_condition_values[i + 1]; - cfg.opt_string("inherits", true) = inherits_values[i + 1]; - // Load all filament presets, but only select the first one in the preset dialog. - Preset *loaded = nullptr; + inherits = inherits_values[1]; + compatible_printers_condition = compatible_printers_condition_values[1]; if (is_external) - loaded = &this->filaments.load_external_preset(name_or_path, name, - (i < old_filament_profile_names->values.size()) ? old_filament_profile_names->values[i] : "", - std::move(cfg), i == 0); - else { - // Used by the config wizard when creating a custom setup. - // Therefore this block should only be called for a single extruder. - char suffix[64]; - if (i == 0) - suffix[0] = 0; - else - sprintf(suffix, "%d", i); - std::string new_name = name + suffix; - loaded = &this->filaments.load_preset(this->filaments.path_from_name(new_name), - new_name, std::move(cfg), i == 0); - loaded->save(); + this->filaments.load_external_preset(name_or_path, name, old_filament_profile_names->values.front(), config); + else + this->filaments.load_preset(this->filaments.path_from_name(name), name, config).save(); + this->filament_presets.clear(); + this->filament_presets.emplace_back(name); + } else { + // Split the filament presets, load each of them separately. + std::vector configs(num_extruders, this->filaments.default_preset().config); + // loop through options and scatter them into configs. + for (const t_config_option_key &key : this->filaments.default_preset().config.keys()) { + const ConfigOption *other_opt = config.option(key); + if (other_opt == nullptr) + continue; + if (other_opt->is_scalar()) { + for (size_t i = 0; i < configs.size(); ++ i) + configs[i].option(key, false)->set(other_opt); + } else if (key != "compatible_printers") { + for (size_t i = 0; i < configs.size(); ++ i) + static_cast(configs[i].option(key, false))->set_at(other_opt, 0, i); + } + } + // Load the configs into this->filaments and make them active. + this->filament_presets.clear(); + for (size_t i = 0; i < configs.size(); ++ i) { + DynamicPrintConfig &cfg = configs[i]; + // Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets. + cfg.opt_string("compatible_printers_condition", true) = compatible_printers_condition_values[i + 1]; + cfg.opt_string("inherits", true) = inherits_values[i + 1]; + // Load all filament presets, but only select the first one in the preset dialog. + Preset *loaded = nullptr; + if (is_external) + loaded = &this->filaments.load_external_preset(name_or_path, name, + (i < old_filament_profile_names->values.size()) ? old_filament_profile_names->values[i] : "", + std::move(cfg), i == 0); + else { + // Used by the config wizard when creating a custom setup. + // Therefore this block should only be called for a single extruder. + char suffix[64]; + if (i == 0) + suffix[0] = 0; + else + sprintf(suffix, "%d", i); + std::string new_name = name + suffix; + loaded = &this->filaments.load_preset(this->filaments.path_from_name(new_name), + new_name, std::move(cfg), i == 0); + loaded->save(); + } + this->filament_presets.emplace_back(loaded->name); } - this->filament_presets.emplace_back(loaded->name); } - } - // 4) Load the project config values (the per extruder wipe matrix etc). - this->project_config.apply_only(config, s_project_options); + // 4) Load the project config values (the per extruder wipe matrix etc). + this->project_config.apply_only(config, s_project_options); + } this->update_compatible_with_printer(false); } @@ -691,9 +787,10 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const collection_dst.load_preset(path, preset_name_dst, std::move(preset_src->config), activate).is_external = true; return preset_name_dst; }; - load_one(this->prints, tmp_bundle.prints, tmp_bundle.prints .get_selected_preset().name, true); - load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filaments.get_selected_preset().name, true); - load_one(this->printers, tmp_bundle.printers, tmp_bundle.printers .get_selected_preset().name, true); + load_one(this->prints, tmp_bundle.prints, tmp_bundle.prints .get_selected_preset().name, true); + load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filaments .get_selected_preset().name, true); + load_one(this->sla_materials, tmp_bundle.sla_materials, tmp_bundle.sla_materials.get_selected_preset().name, true); + load_one(this->printers, tmp_bundle.printers, tmp_bundle.printers .get_selected_preset().name, true); this->update_multi_material_filament_presets(); for (size_t i = 1; i < std::min(tmp_bundle.filament_presets.size(), this->filament_presets.size()); ++ i) this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false); @@ -817,6 +914,7 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree) { flatten_configbundle_hierarchy(tree, "print"); flatten_configbundle_hierarchy(tree, "filament"); + flatten_configbundle_hierarchy(tree, "sla_material"); flatten_configbundle_hierarchy(tree, "printer"); } @@ -853,9 +951,11 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla // Parse the obsolete preset names, to be deleted when upgrading from the old configuration structure. std::vector loaded_prints; std::vector loaded_filaments; + std::vector loaded_sla_materials; std::vector loaded_printers; std::string active_print; std::vector active_filaments; + std::string active_sla_material; std::string active_printer; size_t presets_loaded = 0; for (const auto §ion : tree) { @@ -870,6 +970,10 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla presets = &this->filaments; loaded = &loaded_filaments; preset_name = section.first.substr(9); + } else if (boost::starts_with(section.first, "sla_material:")) { + presets = &this->sla_materials; + loaded = &loaded_sla_materials; + preset_name = section.first.substr(9); } else if (boost::starts_with(section.first, "printer:")) { presets = &this->printers; loaded = &loaded_printers; @@ -886,6 +990,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla active_filaments.resize(idx + 1, std::string()); active_filaments[idx] = kvp.second.data(); } + } else if (kvp.first == "sla_material") { + active_sla_material = kvp.second.data(); } else if (kvp.first == "printer") { active_printer = kvp.second.data(); } @@ -899,6 +1005,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla dst = &this->obsolete_presets.prints; else if (kvp.first == "filament") dst = &this->obsolete_presets.filaments; + else if (kvp.first == "sla_material") + dst = &this->obsolete_presets.sla_materials; else if (kvp.first == "printer") dst = &this->obsolete_presets.printers; if (dst) @@ -999,6 +1107,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) { if (! active_print.empty()) prints.select_preset_by_name(active_print, true); + if (! active_sla_material.empty()) + sla_materials.select_preset_by_name(active_sla_material, true); if (! active_printer.empty()) printers.select_preset_by_name(active_printer, true); // Activate the first filament preset. @@ -1015,6 +1125,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla void PresetBundle::update_multi_material_filament_presets() { + if (printers.get_edited_preset().printer_technology() != ptFFF) + return; + // Verify and select the filament presets. auto *nozzle_diameter = static_cast(printers.get_edited_preset().config.option("nozzle_diameter")); size_t num_extruders = nozzle_diameter->values.size(); @@ -1055,36 +1168,51 @@ void PresetBundle::update_multi_material_filament_presets() void PresetBundle::update_compatible_with_printer(bool select_other_if_incompatible) { const Preset &printer_preset = this->printers.get_edited_preset(); - const std::string &prefered_print_profile = printer_preset.config.opt_string("default_print_profile"); - const std::vector &prefered_filament_profiles = printer_preset.config.option("default_filament_profile")->values; - prefered_print_profile.empty() ? - this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible) : - this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible, - [&prefered_print_profile](const std::string& profile_name){ return profile_name == prefered_print_profile; }); - prefered_filament_profiles.empty() ? - this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible) : - this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible, - [&prefered_filament_profiles](const std::string& profile_name) - { return std::find(prefered_filament_profiles.begin(), prefered_filament_profiles.end(), profile_name) != prefered_filament_profiles.end(); }); - if (select_other_if_incompatible) { - // Verify validity of the current filament presets. - this->filament_presets.front() = this->filaments.get_edited_preset().name; - for (size_t idx = 1; idx < this->filament_presets.size(); ++ idx) { - std::string &filament_name = this->filament_presets[idx]; - Preset *preset = this->filaments.find_preset(filament_name, false); - if (preset == nullptr || ! preset->is_compatible) { - // Pick a compatible profile. If there are prefered_filament_profiles, use them. - if (prefered_filament_profiles.empty()) - filament_name = this->filaments.first_compatible().name; - else { - const std::string &preferred = (idx < prefered_filament_profiles.size()) ? - prefered_filament_profiles[idx] : prefered_filament_profiles.front(); - filament_name = this->filaments.first_compatible( - [&preferred](const std::string& profile_name){ return profile_name == preferred; }).name; + + switch (printers.get_edited_preset().printer_technology()) { + case ptFFF: + { + const std::string &prefered_print_profile = printer_preset.config.opt_string("default_print_profile"); + const std::vector &prefered_filament_profiles = printer_preset.config.option("default_filament_profile")->values; + prefered_print_profile.empty() ? + this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible) : + this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible, + [&prefered_print_profile](const std::string& profile_name){ return profile_name == prefered_print_profile; }); + prefered_filament_profiles.empty() ? + this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible) : + this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible, + [&prefered_filament_profiles](const std::string& profile_name) + { return std::find(prefered_filament_profiles.begin(), prefered_filament_profiles.end(), profile_name) != prefered_filament_profiles.end(); }); + if (select_other_if_incompatible) { + // Verify validity of the current filament presets. + this->filament_presets.front() = this->filaments.get_edited_preset().name; + for (size_t idx = 1; idx < this->filament_presets.size(); ++ idx) { + std::string &filament_name = this->filament_presets[idx]; + Preset *preset = this->filaments.find_preset(filament_name, false); + if (preset == nullptr || ! preset->is_compatible) { + // Pick a compatible profile. If there are prefered_filament_profiles, use them. + if (prefered_filament_profiles.empty()) + filament_name = this->filaments.first_compatible().name; + else { + const std::string &preferred = (idx < prefered_filament_profiles.size()) ? + prefered_filament_profiles[idx] : prefered_filament_profiles.front(); + filament_name = this->filaments.first_compatible( + [&preferred](const std::string& profile_name){ return profile_name == preferred; }).name; + } } } } } + case ptSLA: + { + const std::string &prefered_print_profile = printer_preset.config.opt_string("default_print_profile"); + const std::vector &prefered_filament_profiles = printer_preset.config.option("default_filament_profile")->values; + prefered_print_profile.empty() ? + this->sla_materials.update_compatible_with_printer(printer_preset, select_other_if_incompatible) : + this->sla_materials.update_compatible_with_printer(printer_preset, select_other_if_incompatible, + [&prefered_print_profile](const std::string& profile_name){ return profile_name == prefered_print_profile; }); + } + } } void PresetBundle::export_configbundle(const std::string &path) //, const DynamicPrintConfig &settings @@ -1111,6 +1239,7 @@ void PresetBundle::export_configbundle(const std::string &path) //, const Dynami // Export the names of the active presets. c << std::endl << "[presets]" << std::endl; c << "print = " << this->prints.get_selected_preset().name << std::endl; + c << "sla_material = " << this->sla_materials.get_selected_preset().name << std::endl; c << "printer = " << this->printers.get_selected_preset().name << std::endl; for (size_t i = 0; i < this->filament_presets.size(); ++ i) { char suffix[64]; @@ -1170,7 +1299,7 @@ bool PresetBundle::parse_color(const std::string &scolor, unsigned char *rgb_out void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitmapComboBox *ui) { - if (ui == nullptr) + if (ui == nullptr || this->printers.get_edited_preset().printer_technology() == ptSLA) return; unsigned char rgb[3]; @@ -1265,6 +1394,7 @@ void PresetBundle::set_default_suppressed(bool default_suppressed) { prints.set_default_suppressed(default_suppressed); filaments.set_default_suppressed(default_suppressed); + sla_materials.set_default_suppressed(default_suppressed); printers.set_default_suppressed(default_suppressed); } diff --git a/xs/src/slic3r/GUI/PresetBundle.hpp b/xs/src/slic3r/GUI/PresetBundle.hpp index a5c5682f9..68ec534da 100644 --- a/xs/src/slic3r/GUI/PresetBundle.hpp +++ b/xs/src/slic3r/GUI/PresetBundle.hpp @@ -40,6 +40,7 @@ public: PresetCollection prints; PresetCollection filaments; + PresetCollection sla_materials; PresetCollection printers; // Filament preset names for a multi-extruder or multi-material print. // extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size() @@ -57,12 +58,13 @@ public: struct ObsoletePresets { std::vector prints; std::vector filaments; + std::vector sla_materials; std::vector printers; }; ObsoletePresets obsolete_presets; bool has_defauls_only() const - { return prints.size() <= 1 && filaments.size() <= 1 && printers.size() <= 1; } + { return prints.has_defaults_only() && filaments.has_defaults_only() && printers.has_defaults_only(); } DynamicPrintConfig full_config() const; @@ -146,6 +148,9 @@ private: void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree); bool load_compatible_bitmaps(); + DynamicPrintConfig full_fff_config() const; + DynamicPrintConfig full_sla_config() const; + // Indicator, that the preset is compatible with the selected printer. wxBitmap *m_bitmapCompatible; // Indicator, that the preset is NOT compatible with the selected printer. diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 6505e1092..5daf2784d 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -214,7 +214,7 @@ void Tab::load_initial_data() m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; } -PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages/* = false*/) +Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages /*= false*/) { // Index of icon in an icon list $self->{icons}. auto icon_idx = 0; @@ -238,7 +238,8 @@ PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bo page->SetScrollbars(1, 1, 1, 1); page->Hide(); m_hsizer->Add(page.get(), 1, wxEXPAND | wxLEFT, 5); - if (!is_extruder_pages) + + if (!is_extruder_pages) m_pages.push_back(page); page->set_config(m_config); @@ -313,10 +314,10 @@ void Tab::update_changed_ui() if (m_postpone_update_ui) return; - const bool is_printer_type = (name() == "printer"); - auto dirty_options = m_presets->current_dirty_options(is_printer_type); - auto nonsys_options = m_presets->current_different_from_parent_options(is_printer_type); - if (is_printer_type){ + const bool deep_compare = (m_name == "printer" || m_name == "sla_material"); + auto dirty_options = m_presets->current_dirty_options(deep_compare); + auto nonsys_options = m_presets->current_different_from_parent_options(deep_compare); + if (name() == "printer"){ TabPrinter* tab = static_cast(this); if (tab->m_initial_extruders_count != tab->m_extruders_count) dirty_options.emplace_back("extruders_count"); @@ -397,7 +398,7 @@ void Tab::init_options_list() } template -void add_correct_opts_to_options_list(const std::string &opt_key, std::map& map, TabPrinter *tab, const int& value) +void add_correct_opts_to_options_list(const std::string &opt_key, std::map& map, Tab *tab, const int& value) { T *opt_cur = static_cast(tab->m_config->option(opt_key)); for (int i = 0; i < opt_cur->values.size(); i++) @@ -429,6 +430,30 @@ void TabPrinter::init_options_list() m_options_list.emplace("extruders_count", m_opt_status_value); } +void TabSLAMaterial::init_options_list() +{ + if (!m_options_list.empty()) + m_options_list.clear(); + + for (const auto opt_key : m_config->keys()) + { + if (opt_key == "compatible_printers"){ + m_options_list.emplace(opt_key, m_opt_status_value); + continue; + } + switch (m_config->option(opt_key)->type()) + { + case coInts: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coBools: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coFloats: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coStrings: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coPercents:add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coPoints: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + default: m_options_list.emplace(opt_key, m_opt_status_value); break; + } + } +} + void Tab::get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page) { auto opt = m_options_list.find(opt_key); @@ -649,12 +674,21 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) int val = boost::any_cast(value); event.SetInt(val); } + + if (opt_key == "printer_technology") + { + int val = boost::any_cast(value); + event.SetInt(val); + g_wxMainFrame->ProcessWindowEvent(event); + return; + } + g_wxMainFrame->ProcessWindowEvent(event); } 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") { @@ -663,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" ) @@ -677,17 +711,16 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) update(); } - // Show/hide the 'purging volumes' button void Tab::update_wiping_button_visibility() { + if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA) + return; // ys_FIXME bool wipe_tower_enabled = dynamic_cast( (m_preset_bundle->prints.get_edited_preset().config ).option("wipe_tower"))->value; 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(); } @@ -754,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(); } @@ -1007,6 +1040,9 @@ void TabPrint::reload_config(){ void TabPrint::update() { + if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA) + return; // ys_FIXME + Freeze(); double fill_density = m_config->option("fill_density")->value; @@ -1368,6 +1404,9 @@ void TabFilament::reload_config(){ void TabFilament::update() { + if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA) + return; // ys_FIXME + Freeze(); wxString text = from_u8(PresetHints::cooling_description(m_presets->get_edited_preset())); m_cooling_description_line->SetText(text); @@ -1413,6 +1452,17 @@ void TabPrinter::build() m_presets = &m_preset_bundle->printers; load_initial_data(); + m_printer_technology = m_presets->get_selected_preset().printer_technology(); + + m_presets->get_selected_preset().printer_technology() == ptSLA ? build_sla() : build_fff(); + +// on_value_change("printer_technology", m_printer_technology); // to update show/hide preset ComboBoxes +} + +void TabPrinter::build_fff() +{ + if (!m_pages.empty()) + m_pages.resize(0); // to avoid redundant memory allocation / deallocation during extruders count changing m_pages.reserve(30); @@ -1428,7 +1478,7 @@ void TabPrinter::build() 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); @@ -1496,7 +1546,7 @@ void TabPrinter::build() 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); @@ -1699,6 +1749,76 @@ void TabPrinter::build() update_serial_ports(); } +void TabPrinter::build_sla() +{ + if (!m_pages.empty()) + m_pages.resize(0); + auto page = add_options_page(_(L("General")), "printer_empty.png"); + auto optgroup = page->new_optgroup(_(L("Size and coordinates"))); + + 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->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG)); + + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); + + btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) + { + auto dlg = new BedShapeDialog(this); + dlg->build_dialog(m_config->option("bed_shape")); + if (dlg->ShowModal() == wxID_OK){ + load_key_value("bed_shape", dlg->GetValue()); + update_changed_ui(); + } + })); + + return sizer; + }; + optgroup->append_line(line, &m_colored_Label); + optgroup->append_single_option_line("max_print_height"); + + optgroup = page->new_optgroup(_(L("Display"))); + optgroup->append_single_option_line("display_width"); + optgroup->append_single_option_line("display_height"); + + auto option = optgroup->get_option("display_pixels_x"); + line = { _(option.opt.full_label), "" }; + line.append_option(option); + line.append_option(optgroup->get_option("display_pixels_y")); + optgroup->append_line(line); + + optgroup = page->new_optgroup(_(L("Corrections"))); + line = Line{ m_config->def()->get("printer_correction")->full_label, "" }; + std::vector axes{ "X", "Y", "Z" }; + int id = 0; + for (auto& axis : axes) { + auto opt = optgroup->get_option("printer_correction", id); + opt.opt.label = axis; + line.append_option(opt); + ++id; + } + optgroup->append_line(line); + + page = add_options_page(_(L("Notes")), "note.png"); + optgroup = page->new_optgroup(_(L("Notes")), 0); + option = optgroup->get_option("printer_notes"); + option.opt.full_width = true; + option.opt.height = 250; + optgroup->append_single_option_line(option); + + page = add_options_page(_(L("Dependencies")), "wrench.png"); + optgroup = page->new_optgroup(_(L("Profile dependencies"))); + line = Line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_parent_preset_description_line); + }; + optgroup->append_line(line); +} + void TabPrinter::update_serial_ports(){ Field *field = get_field("serial_port"); Choice *choice = static_cast(field); @@ -1712,6 +1832,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) @@ -1890,7 +2011,38 @@ void TabPrinter::on_preset_loaded() extruders_count_changed(extruders_count); } -void TabPrinter::update(){ +void TabPrinter::update_pages() +{ + // update m_pages ONLY if printer technology is changed + if (m_presets->get_edited_preset().printer_technology() == m_printer_technology) + return; + + // hide all old pages + for (auto& el : m_pages) + el.get()->Hide(); + + // set m_pages to m_pages_(technology before changing) + m_printer_technology == ptFFF ? m_pages.swap(m_pages_fff) : m_pages.swap(m_pages_sla); + + // build Tab according to the technology, if it's not exist jet OR + // set m_pages_(technology after changing) to m_pages + if (m_presets->get_edited_preset().printer_technology() == ptFFF) + m_pages_fff.empty() ? build_fff() : m_pages.swap(m_pages_fff); + else + m_pages_sla.empty() ? build_sla() : m_pages.swap(m_pages_sla); + + rebuild_page_tree(true); + + on_value_change("printer_technology", m_presets->get_edited_preset().printer_technology()); // to update show/hide preset ComboBoxes +} + +void TabPrinter::update() +{ + m_presets->get_edited_preset().printer_technology() == ptFFF ? update_fff() : update_sla(); +} + +void TabPrinter::update_fff() +{ Freeze(); bool en; @@ -1993,20 +2145,25 @@ void TabPrinter::update(){ Thaw(); } +void TabPrinter::update_sla(){ ; } + // Initialize the UI from the current preset void Tab::load_current_preset() { auto preset = m_presets->get_edited_preset(); (preset.is_default || preset.is_system) ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); - update(); - // For the printer profile, generate the extruder pages. - on_preset_loaded(); - // Reload preset pages with the new configuration values. - reload_config(); + + update(); + // For the printer profile, generate the extruder pages. + if (preset.printer_technology() == ptFFF) + on_preset_loaded(); + // Reload preset pages with the new configuration values. + reload_config(); + m_bmp_non_system = m_presets->get_selected_preset_parent() ? &m_bmp_value_unlock : &m_bmp_white_bullet; m_ttg_non_system = m_presets->get_selected_preset_parent() ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; - m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; + m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; m_undo_to_sys_btn->Enable(!preset.is_default); @@ -2019,6 +2176,27 @@ void Tab::load_current_preset() if (!checked_tab(this)) return; update_tab_ui(); + + // update show/hide tabs + if (m_name == "printer"){ + PrinterTechnology& printer_technology = m_presets->get_edited_preset().printer_technology(); + if (printer_technology != static_cast(this)->m_printer_technology) + { + for (auto& tab : get_preset_tabs()){ + if (tab.technology != printer_technology) + { + int page_id = get_tab_panel()->FindPage(tab.panel); + get_tab_panel()->GetPage(page_id)->Show(false); + get_tab_panel()->RemovePage(page_id); + } + else + get_tab_panel()->InsertPage(get_tab_panel()->FindPage(this), tab.panel, tab.panel->title()); + } + + static_cast(this)->m_printer_technology = printer_technology; + } + } + on_presets_changed(); if (name() == "print") @@ -2036,7 +2214,7 @@ void Tab::load_current_preset() } //Regerenerate content of the page tree. -void Tab::rebuild_page_tree() +void Tab::rebuild_page_tree(bool tree_sel_change_event /*= false*/) { Freeze(); // get label of the currently selected item @@ -2051,9 +2229,9 @@ void Tab::rebuild_page_tree() m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); if (p->title() == selected) { if (!(p->title() == _(L("Machine limits")) || p->title() == _(L("Single extruder MM setup")))) // These Pages have to be updated inside OnTreeSelChange - m_disable_tree_sel_changed_event = 1; + m_disable_tree_sel_changed_event = !tree_sel_change_event; m_treectrl->SelectItem(itemId); - m_disable_tree_sel_changed_event = 0; + m_disable_tree_sel_changed_event = false; have_selection = 1; } } @@ -2068,48 +2246,53 @@ void Tab::rebuild_page_tree() // Called by the UI combo box when the user switches profiles. // Select a preset by a name.If !defined(name), then the default preset is selected. // If the current profile is modified, user is asked to save the changes. -void Tab::select_preset(const std::string& preset_name /*= ""*/) +void Tab::select_preset(std::string preset_name /*= ""*/) { - std::string name = preset_name; - auto force = false; - auto presets = m_presets; // If no name is provided, select the "-- default --" preset. - if (name.empty()) - name= presets->default_preset().name; - auto current_dirty = presets->current_is_dirty(); - auto canceled = false; - auto printer_tab = presets->name().compare("printer")==0; + if (preset_name.empty()) + preset_name = m_presets->default_preset().name; + auto current_dirty = m_presets->current_is_dirty(); + auto printer_tab = m_presets->name() == "printer"; + auto canceled = false; m_reload_dependent_tabs = {}; - if (!force && current_dirty && !may_discard_current_dirty_preset()) { + if (current_dirty && !may_discard_current_dirty_preset()) { canceled = true; - } else if(printer_tab) { + } else if (printer_tab) { // Before switching the printer to a new one, verify, whether the currently active print and filament // are compatible with the new printer. // If they are not compatible and the current print or filament are dirty, let user decide // whether to discard the changes or keep the current printer selection. - auto new_printer_preset = presets->find_preset(name, true); - auto print_presets = &m_preset_bundle->prints; - bool print_preset_dirty = print_presets->current_is_dirty(); - bool print_preset_compatible = print_presets->get_edited_preset().is_compatible_with_printer(*new_printer_preset); - canceled = !force && print_preset_dirty && !print_preset_compatible && - !may_discard_current_dirty_preset(print_presets, name); - auto filament_presets = &m_preset_bundle->filaments; - bool filament_preset_dirty = filament_presets->current_is_dirty(); - bool filament_preset_compatible = filament_presets->get_edited_preset().is_compatible_with_printer(*new_printer_preset); - if (!canceled && !force) { - canceled = filament_preset_dirty && !filament_preset_compatible && - !may_discard_current_dirty_preset(filament_presets, name); + // + // With the introduction of the SLA printer types, we need to support switching between + // the FFF and SLA printers. + const Preset &new_printer_preset = *m_presets->find_preset(preset_name, true); + PrinterTechnology old_printer_technology = m_presets->get_edited_preset().printer_technology(); + PrinterTechnology new_printer_technology = new_printer_preset.printer_technology(); + struct PresetUpdate { + std::string name; + PresetCollection *presets; + PrinterTechnology technology; + bool old_preset_dirty; + bool new_preset_compatible; + }; + std::vector updates = { + { "print", &m_preset_bundle->prints, ptFFF }, + { "filament", &m_preset_bundle->filaments, ptFFF }, + { "sla_material", &m_preset_bundle->sla_materials, ptSLA } + }; + for (PresetUpdate &pu : updates) { + pu.old_preset_dirty = (old_printer_technology == pu.technology) && pu.presets->current_is_dirty(); + pu.new_preset_compatible = (new_printer_technology == pu.technology) && pu.presets->get_edited_preset().is_compatible_with_printer(new_printer_preset); + if (! canceled) + canceled = pu.old_preset_dirty && ! pu.new_preset_compatible && ! may_discard_current_dirty_preset(pu.presets, preset_name); } - if (!canceled) { - if (!print_preset_compatible) { + if (! canceled) { + for (PresetUpdate &pu : updates) { // The preset will be switched to a different, compatible preset, or the '-- default --'. - m_reload_dependent_tabs.push_back("print"); - if (print_preset_dirty) print_presets->discard_current_changes(); - } - if (!filament_preset_compatible) { - // The preset will be switched to a different, compatible preset, or the '-- default --'. - m_reload_dependent_tabs.push_back("filament"); - if (filament_preset_dirty) filament_presets->discard_current_changes(); + if (pu.technology == new_printer_technology) + m_reload_dependent_tabs.emplace_back(pu.name); + if (pu.old_preset_dirty) + pu.presets->discard_current_changes(); } } } @@ -2118,19 +2301,20 @@ void Tab::select_preset(const std::string& preset_name /*= ""*/) // Trigger the on_presets_changed event so that we also restore the previous value in the plater selector, // if this action was initiated from the platter. on_presets_changed(); - } - else { - if (current_dirty) presets->discard_current_changes() ; - presets->select_preset_by_name(name, force); + } else { + if (current_dirty) + m_presets->discard_current_changes() ; + m_presets->select_preset_by_name(preset_name, false); // Mark the print & filament enabled if they are compatible with the currently selected preset. // The following method should not discard changes of current print or filament presets on change of a printer profile, // if they are compatible with the current printer. if (current_dirty || printer_tab) m_preset_bundle->update_compatible_with_printer(true); // Initialize the UI from the current preset. + if (printer_tab) + static_cast(this)->update_pages(); load_current_preset(); } - } // If the current preset is dirty, the user is asked whether the changes may be discarded. @@ -2772,5 +2956,71 @@ void SavePresetWindow::accept() } } +void TabSLAMaterial::build() +{ + m_presets = &m_preset_bundle->sla_materials; + load_initial_data(); + + auto page = add_options_page(_(L("Material")), "package_green.png"); + + auto optgroup = page->new_optgroup(_(L("Layers"))); + optgroup->append_single_option_line("layer_height"); + optgroup->append_single_option_line("initial_layer_height"); + + optgroup = page->new_optgroup(_(L("Exposure"))); + optgroup->append_single_option_line("exposure_time"); + optgroup->append_single_option_line("initial_exposure_time"); + + optgroup = page->new_optgroup(_(L("Corrections"))); + optgroup->label_width = 190; + std::vector corrections = { "material_correction_printing", "material_correction_curing" }; + std::vector axes{ "X", "Y", "Z" }; + for (auto& opt_key : corrections){ + auto line = Line{ m_config->def()->get(opt_key)->full_label, "" }; + int id = 0; + for (auto& axis : axes) { + auto opt = optgroup->get_option(opt_key, id); + opt.opt.label = axis; + opt.opt.width = 60; + line.append_option(opt); + ++id; + } + optgroup->append_line(line); + } + + page = add_options_page(_(L("Notes")), "note.png"); + optgroup = page->new_optgroup(_(L("Notes")), 0); + optgroup->label_width = 0; + Option option = optgroup->get_option("material_notes"); + option.opt.full_width = true; + option.opt.height = 250; + optgroup->append_single_option_line(option); + + page = add_options_page(_(L("Dependencies")), "wrench.png"); + optgroup = page->new_optgroup(_(L("Profile dependencies"))); + auto line = Line { _(L("Compatible printers")), "" }; + line.widget = [this](wxWindow* parent){ + return compatible_printers_widget(parent, &m_compatible_printers_checkbox, &m_compatible_printers_btn); + }; + optgroup->append_line(line, &m_colored_Label); + + option = optgroup->get_option("compatible_printers_condition"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); + + line = Line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_parent_preset_description_line); + }; + optgroup->append_line(line); +} + +void TabSLAMaterial::update() +{ + if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptFFF) + return; // ys_FIXME +} + } // GUI } // Slic3r diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index 230fe659e..a1214465e 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -218,8 +218,8 @@ public: void create_preset_tab(PresetBundle *preset_bundle); void load_current_preset(); - void rebuild_page_tree(); - void select_preset(const std::string& preset_name = ""); + void rebuild_page_tree(bool tree_sel_change_event = false); + void select_preset(std::string preset_name = ""); bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, const std::string& new_printer_name = ""); wxSizer* compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox, wxButton** btn); @@ -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(); @@ -319,6 +319,9 @@ class TabPrinter : public Tab bool m_use_silent_mode = false; void append_option_line(ConfigOptionsGroupShp optgroup, const std::string opt_key); bool m_rebuild_kinematics_page = false; + + std::vector m_pages_fff; + std::vector m_pages_sla; public: wxButton* m_serial_test_btn; wxButton* m_print_host_test_btn; @@ -329,12 +332,19 @@ public: size_t m_initial_extruders_count; size_t m_sys_extruders_count; + PrinterTechnology m_printer_technology = ptFFF; + TabPrinter() {} TabPrinter(wxNotebook* parent, bool no_controller) : Tab(parent, _(L("Printer Settings")), "printer", no_controller) {} ~TabPrinter(){} void build() override; - void update() override; + void build_fff(); + void build_sla(); + void update() override; + void update_fff(); + void update_sla(); + void update_pages(); // update m_pages according to printer technology void update_serial_ports(); void extruders_count_changed(size_t extruders_count); PageShp build_kinematics_page(); @@ -343,6 +353,19 @@ public: void init_options_list() override; }; +class TabSLAMaterial : public Tab +{ +public: + TabSLAMaterial() {} + TabSLAMaterial(wxNotebook* parent, bool no_controller) : + Tab(parent, _(L("SLA Material Settings")), "sla_material", no_controller) {} + ~TabSLAMaterial(){} + + void build() override; + void update() override; + void init_options_list() override; +}; + class SavePresetWindow :public wxDialog { public: 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/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index 6e23ab421..2e423dc5e 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -447,6 +447,7 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons for (const auto &name : bundle.obsolete_presets.prints) { obsolete_remover("print", name); } for (const auto &name : bundle.obsolete_presets.filaments) { obsolete_remover("filament", name); } + for (const auto &name : bundle.obsolete_presets.filaments) { obsolete_remover("sla_material", name); } for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); } } } 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); %}; diff --git a/xs/xsp/GUI_Preset.xsp b/xs/xsp/GUI_Preset.xsp index 2c63db10c..99d23a142 100644 --- a/xs/xsp/GUI_Preset.xsp +++ b/xs/xsp/GUI_Preset.xsp @@ -32,7 +32,6 @@ %name{Slic3r::GUI::PresetCollection} class PresetCollection { Ref preset(size_t idx) %code%{ RETVAL = &THIS->preset(idx); %}; - Ref default_preset() %code%{ RETVAL = &THIS->default_preset(); %}; size_t size() const; size_t num_visible() const; std::string name() const; @@ -133,6 +132,7 @@ PresetCollection::arrayref() Ref print() %code%{ RETVAL = &THIS->prints; %}; Ref filament() %code%{ RETVAL = &THIS->filaments; %}; + Ref sla_material() %code%{ RETVAL = &THIS->sla_materials; %}; Ref printer() %code%{ RETVAL = &THIS->printers; %}; Ref project_config() %code%{ RETVAL = &THIS->project_config; %};